diff --git a/.gitignore b/.gitignore index f89fbc1..964cb94 100644 --- a/.gitignore +++ b/.gitignore @@ -29,6 +29,9 @@ share/python-wheels/ *.egg-info/ .installed.cfg *.egg +*.mp4 +*.vtt +testing/ MANIFEST # PyInstaller diff --git a/newbot.py b/newbot.py new file mode 100644 index 0000000..d0833f1 --- /dev/null +++ b/newbot.py @@ -0,0 +1,551 @@ +import obsws_python +import yt_dlp +from time import sleep +import time +import disnake +import random +from urllib.parse import urlparse +from disnake.ext import commands, tasks +from disnake import TextInputStyle +import asyncio +from dotenv import load_dotenv +import os +import datetime +import logging +import math +from tempfile import TemporaryDirectory +from pathlib import Path +#https://stackoverflow.com/a/11706463 +def randshuffle(x, start=0, stop=None, seed=10): + if stop is None: + stop = len(x) + + loop = 0 + for i in reversed(range(start + 1, stop)): + random.seed(seed+loop) + j = random.randint(start, i) + x[i], x[j] = x[j], x[i] + loop = loop + 1 + +load_dotenv() +queue = [] +user_queue = [] +skip_list = [] +shuffle = True +downloading = False +failures = 0 +vidcounter = 0 +temp_dir = TemporaryDirectory() +vid_dir = Path(temp_dir.name) +download_dir = TemporaryDirectory() +full_dl_dir = Path(download_dir.name) +vid_details = {"title": "", "channel": ""} + +def video_check(info, *, incomplete): + duration = info.get('duration') + vid_details["title"] = info.get("title") + vid_details["channel"] = info.get("channel") + print(info.get("channel")) + print(info.get("title")) + if duration and duration >= ((float(os.getenv("MAX_MIN")) * 60) + 480): + queue.pop(0) + if not (os.getenv("PERMANENT_MAX_QUEUE","FALSE") == "TRUE"): + user_queue.pop(0) + skip_list.clear() + if queue: + ydl.download(queue[0]) + return "video too long... :(" + +ydl_opts = { + 'match_filter': video_check, + 'hls_prefer_native': True, + 'extract_flat': 'discard_in_playlist', + 'format': 'bestvideo[height<=1080][ext=mp4]+bestaudio[abr<=256][ext=m4a]/best[ext=mp4]/best', + 'fragment_retries': 10, + 'noplaylist': True, + 'restrictfilenames': True, + 'source_address': '0.0.0.0', + 'concurrent_fragment_downloads': 3, + 'paths': {'home': f'{vid_dir}', 'temp': f'{full_dl_dir}'}, + 'outtmpl': {'default': f'999zznext'}, + 'postprocessors': [{'api': 'https://sponsor.ajay.app', + 'categories': {'sponsor', 'selfpromo'}, + 'key': 'SponsorBlock', + 'when': 'after_filter'}, + {'force_keyframes': False, + 'key': 'ModifyChapters', + 'remove_chapters_patterns': [], + 'remove_ranges': [], + 'remove_sponsor_segments': {'sponsor', 'selfpromo'}, + 'sponsorblock_chapter_title': '[SponsorBlock]: ' + '%(category_names)l'}, + {'key': 'FFmpegConcat', + 'only_multi_video': True, + 'when': 'playlist'}, + {'format': 'vtt', + 'key': 'FFmpegSubtitlesConvertor', + 'when': 'before_dl'}, + {'already_have_subtitle': False, + 'key': 'FFmpegEmbedSubtitle'}], + 'proxy': f'{os.getenv("PROXY")}', + 'subtitlesformat': 'vtt', + 'subtitleslangs': ['en.*'], + 'writesubtitles': True, + 'retries': 10, +} + +#logging.basicConfig(level=logging.DEBUG) +ydl = yt_dlp.YoutubeDL(ydl_opts) +obs = obsws_python.ReqClient() +obse = obsws_python.EventClient() +intents = disnake.Intents.all() +intents.message_content = False +bot = commands.Bot(intents=intents, command_prefix=".", test_guilds=[int(os.getenv("GUILD_ID"))]) + +ver = obs.get_version() +print(f"OBS Version: {ver.obs_version}") + +@bot.event +async def on_ready(): + obs.set_current_program_scene("waiting") + obse.callback.register(on_media_input_playback_ended) + videotimer.start() + ensurewaiting.start() + titlehandler.start() + if (not os.path.isfile(f"{vid_dir}/{vidcounter}.mp4")) and len(queue) > 1: + print("predefined queue!") + loop = asyncio.get_running_loop() + await loop.run_in_executor(None, cold_run) + + +def download_video(index): + while len(os.listdir(full_dl_dir)) != 0: + pass + try: + ydl.download(queue[index]) + sleep(2) #allow ytdlp to fully cleanup + except Exception: + print("handling youtube exception") + global failures + if failures % 2 == 0: + queue.pop(index) + if not (os.getenv("PERMANENT_MAX_QUEUE","FALSE") == "TRUE"): + user_queue.pop(index) + failures = failures + 1 + download_video(index) + + +def wait_for_next_video(): + counter = 0 + if len(queue) <= 1: + return + stat = obs.get_scene_item_id("youtube", "notice") + print(stat.scene_item_id) + #if not os.path.isfile(f"{vid_dir}/{vidcounter+1}.mp4"): + print("Attempting to enable") + obs.set_scene_item_enabled("youtube", stat.scene_item_id, True) + while not os.path.isfile(f"{vid_dir}/{vidcounter+1}.mp4"): + counter = counter + 1 + sleep(0.5) + if counter == 20 and len(os.listdir(full_dl_dir)) == 0: + print("failsafe activated") + download_video(0) + os.rename(f"{vid_dir}/999zznext.mp4", f"{vid_dir}/{vidcounter+1}.mp4") + obs.set_scene_item_enabled("youtube", stat.scene_item_id, False) + return + +def cold_run(): + download_video(0) + os.rename(f"{vid_dir}/999zznext.mp4", f"{vid_dir}/{vidcounter}.mp4") + obs.set_current_program_scene("youtube") + nowplayingid = obs.get_scene_item_id("youtube", "nowplaying") + stat = obs.get_scene_item_id("youtube", "notice") + obs.set_scene_item_enabled("youtube", stat.scene_item_id, False) + obs.set_input_settings("player", {'playlist': [{'hidden': False, 'selected': False, 'value': f"{str(vid_dir)}"}]}, True) + obs.set_input_settings("nowplaying", {'text': f'{vid_details["title"]}\nBy {vid_details["channel"]}'}, True) + obs.set_scene_item_enabled("youtube", nowplayingid.scene_item_id, True) + obs.trigger_media_input_action("player", "OBS_WEBSOCKET_MEDIA_INPUT_ACTION_RESTART") + if len(queue) > 1: + download_video(1) + os.rename(f"{vid_dir}/999zznext.mp4", f"{vid_dir}/{vidcounter+1}.mp4") + +def on_media_input_playback_ended(data): + global vidcounter + queue.pop(0) + if not (os.getenv("PERMANENT_MAX_QUEUE","FALSE") == "TRUE"): + user_queue.pop(0) + skip_list.clear() + if not queue: + obs.set_current_program_scene("waiting") + os.remove(f"{vid_dir}/{vidcounter}.mp4") + vidcounter = vidcounter + 1 + return + wait_for_next_video() + print(queue) + print(user_queue) + if shuffle: + seed = time.time() + randshuffle(queue,1,None,seed) + if not (os.getenv("PERMANENT_MAX_QUEUE","FALSE") == "TRUE"): + randshuffle(user_queue,1,None,seed) + print(queue) + print(user_queue) + #obs.set_current_program_scene("youtube2") + os.remove(f"{vid_dir}/{vidcounter}.mp4") + vidcounter = vidcounter + 1 + #os.rename(f"{vid_dir}/999zznext.mp4", f"{vid_dir}/{vidcounter}.mp4") + print("changing obs settigs") + scene = obs.get_current_program_scene() + if scene.scene_name != "youtube": + obs.set_current_program_scene("youtube") + nowplayingid = obs.get_scene_item_id("youtube", "nowplaying") + obs.set_input_settings("player", {'playlist': [{'hidden': False, 'selected': False, 'value': f"{str(vid_dir)}"}]}, True) + obs.set_input_settings("nowplaying", {'text': f'{vid_details["title"]}\nBy {vid_details["channel"]}'}, True) + obs.set_input_settings("nowplaying", {'text': f'{vid_details["title"]}\nBy {vid_details["channel"]}'}, True) #sometimes it doesn't apply for some reason TODO: investigate further + print("starting playback") + obs.trigger_media_input_action("player", "OBS_WEBSOCKET_MEDIA_INPUT_ACTION_RESTART") + obs.set_scene_item_enabled("youtube", nowplayingid.scene_item_id, True) + obs.set_scene_item_enabled("youtube", nowplayingid.scene_item_id, True) #same as above + if len(queue) > 1: + download_video(1) + os.rename(f"{vid_dir}/999zznext.mp4", f"{vid_dir}/{vidcounter+1}.mp4") + + +@bot.slash_command( + name="stats", + description="get information about whats happening", +) +async def stats(inter: disnake.AppCmdInter): + scene = obs.get_current_program_scene() + if scene.scene_name != "youtube": + await inter.response.send_message("Stats can only be shown while a video is playing", ephemeral=True) + return + await inter.response.defer(ephemeral=True) + ver = obs.get_version() + message = f"OBS Version: {ver.obs_version}\n" + message = message + f"Failures: {failures}\n" + nowplaying = obs.get_input_settings("nowplaying") + message = message + f"Title: {nowplaying.input_settings['text']}\n" + playing = obs.get_media_input_status("player") + message = message + f"Video Duration: {str(datetime.timedelta(seconds=(round(playing.media_cursor/1000))))}/{str(datetime.timedelta(seconds=(round(playing.media_duration/1000))))}\n" + if inter.permissions.moderate_members and not os.getenv("PERMANENT_MAX_QUEUE",False): + message = message + f"Queued by <@{user_queue[0]}>" + await inter.edit_original_response(message) + + +@bot.slash_command( + name="play", + description="adds a video to the queue", +) +async def play(inter: disnake.AppCmdInter, link: str): + await inter.response.defer(ephemeral=True) + if user_queue.count(inter.user.id) >= (int(os.getenv("MAX_QUEUE"))): + await inter.edit_original_response(f"You have reached the queue limit of {os.getenv('MAX_QUEUE')}, " + ("try again after one of your videos has played." if not (os.getenv("PERMANENT_MAX_QUEUE","FALSE") == "TRUE") else "you may not queue videos for the rest of the session.")) + return + if urlparse(link).netloc == 'youtube.com' or urlparse(link).netloc == 'www.youtube.com' or urlparse(link).netloc == 'youtu.be': + queue.append(link) + user_queue.append(inter.user.id) + await inter.edit_original_response(f"added to queue!") + if (not os.path.isfile(f"{vid_dir}/{vidcounter}.mp4")) and len(queue) > 1: + loop = asyncio.get_running_loop() + await loop.run_in_executor(None, cold_run) + return + else: + await inter.edit_original_response(f"This bot only accepts youtube links") + return + +@bot.slash_command( + name="shuffle", + description="toggles shuffle on or off, the queue cannot be unshuffled once it is shuffled", +) +async def shuffleplay(inter: disnake.AppCmdInter, toggle: str = commands.Param(choices=["on", "off"])): + if os.getenv("LOCK_SHUFFLE","FALSE") == "TRUE": + await inter.response.send_message("the bot owner has locked shuffling",ephemeral=True) + return + await inter.response.defer(ephemeral=True) + global shuffle + if toggle == "on": + shuffle = True + await inter.edit_original_response(f"shuffle enabled") + return + else: + shuffle = False + await inter.edit_original_response(f"shuffle disabled") + return + +@bot.slash_command( + name="queue", + description="list the videos in queue", +) +async def getqueue(inter: disnake.AppCmdInter): + await inter.response.defer(ephemeral=True) + if not queue: + await inter.edit_original_response("There are no items in queue") + return + message = f"Now Playing: <{queue[0]}>\n" + message = message + f"Shuffle is currently " + ("off\n" if not shuffle else "on!\n") + for i in range(11): + if i == 0: + continue + try: + message = message + f"{i}. <{queue[i]}>\n" + except IndexError: + break + message = message + f"1 of {math.ceil((len(queue)-1)/10) if (len(queue)-1)/10 > 1 else 1}" + if math.ceil((len(queue)-1)/10) > 1: + await inter.edit_original_response(message, components=[disnake.ui.Button(label=">>", style=disnake.ButtonStyle.primary, custom_id="Forward"),]) + else: + await inter.edit_original_response(message) + +@bot.listen("on_button_click") +async def button_listener(inter: disnake.MessageInteraction): + if not queue: + await inter.response.edit_message("There are no items in queue") + return + ogmsg = inter.message.content + page = ogmsg.split("\n") + page = page[-1].split(" of ") + if inter.component.custom_id == "Forward": + message = f"Now Playing: <{queue[0]}>\n" + message = message + f"Shuffle is currently " + ("off\n" if not shuffle else "on!\n") + offset = int(int(page[0]) * 10) + for i in range(11): + if i == 0: + continue + try: + message = message + f"{int(i+offset)}. <{queue[int(i+offset)]}>\n" + except IndexError: + break + message = message + f"{int(page[0])+1} of {math.ceil((len(queue)-1)/10) if (len(queue)-1)/10 > 1 else 1}" + if (int(page[0])+1) >= int(page[1]): + await inter.response.edit_message(message, components=[disnake.ui.Button(label="<<", style=disnake.ButtonStyle.primary, custom_id="Backward"),]) + else: + await inter.response.edit_message(message, components=[disnake.ui.Button(label="<<", style=disnake.ButtonStyle.primary, custom_id="Backward"), + disnake.ui.Button(label=">>", style=disnake.ButtonStyle.primary, custom_id="Forward"),]) + return + if inter.component.custom_id == "Backward": + message = f"Now Playing: <{queue[0]}>\n" + message = message + f"Shuffle is currently " + ("off\n" if not shuffle else "on!\n") + offset = int((int(page[0]) - 2) * 10) + for i in range(11): + if i == 0: + continue + try: + message = message + f"{int(i+offset)}. <{queue[int(i+offset)]}>\n" + except IndexError: + break + message = message + f"{int(page[0])-1} of {math.ceil((len(queue)-1)/10) if (len(queue)-1)/10 > 1 else 1}" + if (int(page[0])-1) <= 1 and int(int(page[1]) == 1): + await inter.response.edit_message(message) + elif (int(page[0])-1) <= 1 and int(int(page[1]) > 1): + await inter.response.edit_message(message, components=[disnake.ui.Button(label=">>", style=disnake.ButtonStyle.primary, custom_id="Forward"),]) + else: + await inter.response.edit_message(message, components=[disnake.ui.Button(label="<<", style=disnake.ButtonStyle.primary, custom_id="Backward"), + disnake.ui.Button(label=">>", style=disnake.ButtonStyle.primary, custom_id="Forward"),]) + await inter.response.edit_message(message, components=[]) + return + +@bot.slash_command( + name="toggleplayback", + description="play or pause the video", +) +async def toggleplayback(inter: disnake.AppCmdInter): + stat = obs.get_media_input_status("player") + print(stat.media_state) + if stat.media_state == "OBS_MEDIA_STATE_PLAYING": + obs.trigger_media_input_action("player","OBS_WEBSOCKET_MEDIA_INPUT_ACTION_PAUSE") + elif stat.media_state == "OBS_MEDIA_STATE_PAUSED": + obs.trigger_media_input_action("player","OBS_WEBSOCKET_MEDIA_INPUT_ACTION_PLAY") + await inter.response.send_message("done",ephemeral=True) + +@bot.slash_command( + name="skip", + description="skips the current video", + default_member_permissions=disnake.Permissions(8192), +) +async def skip(inter: disnake.AppCmdInter): + if os.getenv("ALLOW_SKIP","TRUE") == "FALSE": + await inter.response.send_message("the bot owner has disabled skipping", ephemeral=True) + return + await inter.response.defer(ephemeral=False) + loop = asyncio.get_running_loop() + global vidcounter + queue.pop(0) + if not (os.getenv("PERMANENT_MAX_QUEUE","FALSE") == "TRUE"): + user_queue.pop(0) + skip_list.clear() + if not queue: + obs.set_current_program_scene("waiting") + os.remove(f"{vid_dir}/{vidcounter}.mp4") + vidcounter = vidcounter + 1 + await inter.edit_original_response("skipped as sufficient votes were reached") + return + await loop.run_in_executor(None, wait_for_next_video) + print("stopping video") + #obs.trigger_media_input_action("player", "OBS_WEBSOCKET_MEDIA_INPUT_ACTION_PAUSE") + os.remove(f"{vid_dir}/{vidcounter}.mp4") + vidcounter = vidcounter + 1 + nowplayingid = obs.get_scene_item_id("youtube", "nowplaying") + obs.set_input_settings("player", {'playlist': [{'hidden': False, 'selected': False, 'value': f"{str(vid_dir)}"}]}, True) + obs.set_input_settings("nowplaying", {'text': f'{vid_details["title"]}\nBy {vid_details["channel"]}'}, True) + obs.set_scene_item_enabled("youtube", nowplayingid.scene_item_id, True) + obs.trigger_media_input_action("player", "OBS_WEBSOCKET_MEDIA_INPUT_ACTION_RESTART") + await inter.edit_original_response("skipped as sufficient votes were reached") + if len(queue) > 1: + await loop.run_in_executor(None, download_video, 1) + os.rename(f"{vid_dir}/999zznext.mp4", f"{vid_dir}/{vidcounter+1}.mp4") + + +@bot.slash_command( + name="voteskip", + description="vote to skip the current video", +) +async def voteskip(inter: disnake.AppCmdInter): + if os.getenv("ALLOW_SKIP","TRUE") == "FALSE": + await inter.response.send_message("the bot owner has disabled skipping", ephemeral=True) + return + await inter.response.defer(ephemeral=False) + if not inter.user.voice: + await inter.edit_original_response("You are not in the voice channel") + return + if inter.user.id in skip_list: + await inter.edit_original_response("You have already voted to skip this video") + return + vc = inter.user.voice.channel.members #this could be better due to potential for abuse, but it's fine for now + print(inter.user.voice.channel.name) + broadcaster = False + for m in vc: + if m.voice.self_video or m.voice.self_stream: + broadcaster = True + break + if not broadcaster: + await inter.edit_original_response("No one is playing video so how can this be the correct vc?") + return + skip_list.append(inter.user.id) + print(len(skip_list)) + print(math.floor(len(vc)/2)) + print(vc) + if len(skip_list) >= (math.floor(len(vc)/2)): + loop = asyncio.get_running_loop() + global vidcounter + queue.pop(0) + if not (os.getenv("PERMANENT_MAX_QUEUE","FALSE") == "TRUE"): + user_queue.pop(0) + skip_list.clear() + if not queue: + obs.set_current_program_scene("waiting") + os.remove(f"{vid_dir}/{vidcounter}.mp4") + vidcounter = vidcounter + 1 + await inter.edit_original_response("skipped as sufficient votes were reached") + return + await loop.run_in_executor(None, wait_for_next_video) + print("stopping video") + obs.trigger_media_input_action("player", "OBS_WEBSOCKET_MEDIA_INPUT_ACTION_PAUSE") + os.remove(f"{vid_dir}/{vidcounter}.mp4") + vidcounter = vidcounter + 1 + nowplayingid = obs.get_scene_item_id("youtube", "nowplaying") + obs.set_input_settings("player", {'playlist': [{'hidden': False, 'selected': False, 'value': f"{str(vid_dir)}"}]}, True) + obs.set_input_settings("nowplaying", {'text': f'{vid_details["title"]}\nBy {vid_details["channel"]}'}, True) + obs.set_scene_item_enabled("youtube", nowplayingid.scene_item_id, True) + obs.trigger_media_input_action("player", "OBS_WEBSOCKET_MEDIA_INPUT_ACTION_RESTART") + await inter.edit_original_response("skipped as sufficient votes were reached") + if len(queue) > 1: + await loop.run_in_executor(None, download_video, 1) + os.rename(f"{vid_dir}/999zznext.mp4", f"{vid_dir}/{vidcounter+1}.mp4") + else: + await inter.edit_original_response(f"**{inter.user.display_name}** has voted to skip the video, {len(skip_list)}/{math.floor(len(vc)/2)}") + + +@bot.slash_command( + name="remove", + description="removes a video from the queue", + default_member_permissions=disnake.Permissions(8192), +) +async def remove(inter: disnake.AppCmdInter, toremove: int): + await inter.response.defer(ephemeral=True) + if toremove == 0 or toremove == 1: + await inter.edit_original_response("that is the currently playing video!", ephemeral=True) + return + else: + queue.pop(toremove) + if not (os.getenv("PERMANENT_MAX_QUEUE","FALSE") == "TRUE"): + user_queue.pop(toremove) + await inter.edit_original_response("removed!") + + +@tasks.loop(seconds=5.0) +async def videotimer(): + try: + scene = obs.get_current_program_scene() + if scene.scene_name != "youtube": + return + playing = obs.get_media_input_status("player") + if playing.media_cursor >= ((float(os.getenv("MAX_MIN")) * 60000)): + #skip + loop = asyncio.get_running_loop() + global vidcounter + playing = obs.get_media_input_status("player") + if playing.media_state == "OBS_MEDIA_STATE_STOPPED" or playing.media_state == "OBS_MEDIA_STATE_ENDED": + #we are already handling it + print("skip return") + return + queue.pop(0) + if not (os.getenv("PERMANENT_MAX_QUEUE","FALSE") == "TRUE"): + user_queue.pop(0) + skip_list.clear() + if not queue: + obs.set_current_program_scene("waiting") + os.remove(f"{vid_dir}/{vidcounter}.mp4") + vidcounter = vidcounter + 1 + return + await loop.run_in_executor(None, wait_for_next_video) + print("stopping video") + #obs.trigger_media_input_action("player", "OBS_WEBSOCKET_MEDIA_INPUT_ACTION_PAUSE") + os.remove(f"{vid_dir}/{vidcounter}.mp4") + vidcounter = vidcounter + 1 + obs.set_input_settings("player", {'playlist': [{'hidden': False, 'selected': False, 'value': f"{str(vid_dir)}"}]}, True) + obs.trigger_media_input_action("player", "OBS_WEBSOCKET_MEDIA_INPUT_ACTION_RESTART") + if len(queue) > 1: + await loop.run_in_executor(None, download_video, 1) + os.rename(f"{vid_dir}/999zznext.mp4", f"{vid_dir}/{vidcounter+1}.mp4") + except Exception: + pass + return + +@tasks.loop(seconds=20) +async def ensurewaiting(): + scene = obs.get_current_program_scene() + if scene.scene_name == "waiting": + return + if scene.scene_name == "error" and len(os.listdir(full_dl_dir)) == 0: + obs.set_current_program_scene("waiting") + return + elif scene.scene_name == "error": + return + stat = obs.get_scene_item_id("youtube", "notice") + enabled = obs.get_scene_item_enabled("youtube", stat.scene_item_id) + if enabled.scene_item_enabled: + return + playing = obs.get_media_input_status("player") + if playing.media_cursor == None: + obs.set_current_program_scene("error") + if len(os.listdir(full_dl_dir)) == 0 and len(queue) >= 1: #just in case + await loop.run_in_executor(None, download_video, 0) + +@tasks.loop(seconds=5) +async def titlehandler(): + scene = obs.get_current_program_scene() + if scene.scene_name != "youtube": + return + nowplayingid = obs.get_scene_item_id("youtube", "nowplaying") + enabled = obs.get_scene_item_enabled("youtube", nowplayingid.scene_item_id) + if enabled.scene_item_enabled: + await asyncio.sleep(4) + obs.set_scene_item_enabled("youtube", nowplayingid.scene_item_id, False) + return + + +bot.run(os.getenv("TOKEN")) +print("cleaning up tempdir") +temp_dir.cleanup() +download_dir.cleanup() \ No newline at end of file diff --git a/obs_scenes.json b/obs_scenes.json new file mode 100644 index 0000000..c4de480 --- /dev/null +++ b/obs_scenes.json @@ -0,0 +1,794 @@ +{ + "DesktopAudioDevice1": { + "prev_ver": 503447555, + "name": "Desktop Audio", + "uuid": "12ba8a35-1150-4603-bba1-4f08d8caaf9d", + "id": "pulse_output_capture", + "versioned_id": "pulse_output_capture", + "settings": { + "device_id": "default" + }, + "mixers": 255, + "sync": 0, + "flags": 0, + "volume": 1.0, + "balance": 0.5, + "enabled": true, + "muted": true, + "push-to-mute": false, + "push-to-mute-delay": 0, + "push-to-talk": false, + "push-to-talk-delay": 0, + "hotkeys": { + "libobs.mute": [], + "libobs.unmute": [], + "libobs.push-to-mute": [], + "libobs.push-to-talk": [] + }, + "deinterlace_mode": 0, + "deinterlace_field_order": 0, + "monitoring_type": 0, + "private_settings": {} + }, + "current_scene": "waiting", + "current_program_scene": "waiting", + "scene_order": [ + { + "name": "waiting" + }, + { + "name": "youtube" + }, + { + "name": "error" + }, + { + "name": "screenshare" + }, + { + "name": "banned" + } + ], + "name": "Untitled", + "groups": [], + "quick_transitions": [ + { + "name": "Cut", + "duration": 300, + "hotkeys": [], + "id": 1, + "fade_to_black": false + }, + { + "name": "Fade", + "duration": 300, + "hotkeys": [], + "id": 2, + "fade_to_black": false + }, + { + "name": "Fade", + "duration": 300, + "hotkeys": [], + "id": 3, + "fade_to_black": true + } + ], + "transitions": [], + "saved_projectors": [], + "current_transition": "Fade", + "transition_duration": 200, + "preview_locked": false, + "scaling_enabled": false, + "scaling_level": 0, + "scaling_off_x": 0.0, + "scaling_off_y": 0.0, + "virtual-camera": { + "type2": 1, + "scene": "screenshare" + }, + "modules": { + "scripts-tool": [], + "output-timer": { + "streamTimerHours": 0, + "streamTimerMinutes": 0, + "streamTimerSeconds": 30, + "recordTimerHours": 0, + "recordTimerMinutes": 0, + "recordTimerSeconds": 30, + "autoStartStreamTimer": false, + "autoStartRecordTimer": false, + "pauseRecordTimer": true + }, + "auto-scene-switcher": { + "interval": 300, + "non_matching_scene": "", + "switch_if_not_matching": false, + "active": false, + "switches": [] + } + }, + "resolution": { + "x": 1920, + "y": 1080 + }, + "sources": [ + { + "prev_ver": 503447555, + "name": "banned", + "uuid": "10ed3781-690c-4783-80c3-d1ca7b78303a", + "id": "scene", + "versioned_id": "scene", + "settings": { + "id_counter": 1, + "custom_size": false, + "items": [ + { + "name": "banned:(", + "source_uuid": "71c4e4fc-becc-4cde-aee3-e9d50d3c3077", + "visible": true, + "locked": false, + "rot": 0.0, + "pos": { + "x": 0.0, + "y": 0.0 + }, + "scale": { + "x": 1.0, + "y": 1.0 + }, + "align": 5, + "bounds_type": 2, + "bounds_align": 0, + "bounds_crop": false, + "bounds": { + "x": 1920.0, + "y": 1080.0 + }, + "crop_left": 0, + "crop_top": 0, + "crop_right": 0, + "crop_bottom": 0, + "id": 1, + "group_item_backup": false, + "scale_filter": "disable", + "blend_method": "default", + "blend_type": "normal", + "show_transition": { + "duration": 0 + }, + "hide_transition": { + "duration": 0 + }, + "private_settings": {} + } + ] + }, + "mixers": 0, + "sync": 0, + "flags": 0, + "volume": 1.0, + "balance": 0.5, + "enabled": true, + "muted": false, + "push-to-mute": false, + "push-to-mute-delay": 0, + "push-to-talk": false, + "push-to-talk-delay": 0, + "hotkeys": { + "OBSBasic.SelectScene": [], + "libobs.show_scene_item.1": [], + "libobs.hide_scene_item.1": [] + }, + "deinterlace_mode": 0, + "deinterlace_field_order": 0, + "monitoring_type": 0, + "private_settings": {} + }, + { + "prev_ver": 503447555, + "name": "banned", + "uuid": "71c4e4fc-becc-4cde-aee3-e9d50d3c3077", + "id": "text_ft2_source", + "versioned_id": "text_ft2_source_v2", + "settings": { + "text": "we got banned from youtube", + "font": { + "face": "Monocraft", + "style": "Regular", + "size": 256, + "flags": 0 + } + }, + "mixers": 0, + "sync": 0, + "flags": 0, + "volume": 1.0, + "balance": 0.5, + "enabled": true, + "muted": false, + "push-to-mute": false, + "push-to-mute-delay": 0, + "push-to-talk": false, + "push-to-talk-delay": 0, + "hotkeys": {}, + "deinterlace_mode": 0, + "deinterlace_field_order": 0, + "monitoring_type": 0, + "private_settings": {} + }, + { + "prev_ver": 503447555, + "name": "error", + "uuid": "8c1f123a-2c08-4716-aec6-d59a57225536", + "id": "scene", + "versioned_id": "scene", + "settings": { + "id_counter": 2, + "custom_size": false, + "items": [ + { + "name": "Text (FreeType 2)", + "source_uuid": "01d3bab9-cc17-4702-acc6-6e25319188eb", + "visible": true, + "locked": false, + "rot": 0.0, + "pos": { + "x": 0.0, + "y": 0.0 + }, + "scale": { + "x": 1.0, + "y": 1.0 + }, + "align": 5, + "bounds_type": 2, + "bounds_align": 0, + "bounds_crop": false, + "bounds": { + "x": 1920.0, + "y": 1080.0 + }, + "crop_left": 0, + "crop_top": 0, + "crop_right": 0, + "crop_bottom": 0, + "id": 1, + "group_item_backup": false, + "scale_filter": "disable", + "blend_method": "default", + "blend_type": "normal", + "show_transition": { + "duration": 0 + }, + "hide_transition": { + "duration": 0 + }, + "private_settings": {} + } + ] + }, + "mixers": 0, + "sync": 0, + "flags": 0, + "volume": 1.0, + "balance": 0.5, + "enabled": true, + "muted": false, + "push-to-mute": false, + "push-to-mute-delay": 0, + "push-to-talk": false, + "push-to-talk-delay": 0, + "hotkeys": { + "OBSBasic.SelectScene": [], + "libobs.show_scene_item.1": [], + "libobs.hide_scene_item.1": [] + }, + "deinterlace_mode": 0, + "deinterlace_field_order": 0, + "monitoring_type": 0, + "private_settings": {} + }, + { + "prev_ver": 503447555, + "name": "Image", + "uuid": "a9588972-73f5-4176-a837-5492bba04b6a", + "id": "image_source", + "versioned_id": "image_source", + "settings": { + "file": "/your/thumbnail/waitingforvideo.png", + "unload": false + }, + "mixers": 0, + "sync": 0, + "flags": 0, + "volume": 1.0, + "balance": 0.5, + "enabled": true, + "muted": false, + "push-to-mute": false, + "push-to-mute-delay": 0, + "push-to-talk": false, + "push-to-talk-delay": 0, + "hotkeys": {}, + "deinterlace_mode": 0, + "deinterlace_field_order": 0, + "monitoring_type": 0, + "private_settings": {} + }, + { + "prev_ver": 503447555, + "name": "notice", + "uuid": "e4912bae-e9c8-4574-9e7b-898963c4b20e", + "id": "text_ft2_source", + "versioned_id": "text_ft2_source_v2", + "settings": { + "log_mode": false, + "text": "video not ready :(\nplease wait...", + "font": { + "face": "Monocraft", + "style": "Regular", + "size": 100, + "flags": 0 + }, + "antialiasing": false + }, + "mixers": 0, + "sync": 0, + "flags": 0, + "volume": 1.0, + "balance": 0.5, + "enabled": true, + "muted": false, + "push-to-mute": false, + "push-to-mute-delay": 0, + "push-to-talk": false, + "push-to-talk-delay": 0, + "hotkeys": {}, + "deinterlace_mode": 0, + "deinterlace_field_order": 0, + "monitoring_type": 0, + "private_settings": {} + }, + { + "prev_ver": 503447555, + "name": "nowplaying", + "uuid": "2778ed68-2717-45e4-bf7d-f96c5df940bc", + "id": "text_ft2_source", + "versioned_id": "text_ft2_source_v2", + "settings": { + "font": { + "face": "Noto Sans Mono CJK JP", + "style": "Bold", + "size": 100, + "flags": 1 + }, + "antialiasing": true, + "color1": 4294967295, + "color2": 4294967295, + "text": "Video by author" + }, + "mixers": 0, + "sync": 0, + "flags": 0, + "volume": 1.0, + "balance": 0.5, + "enabled": true, + "muted": false, + "push-to-mute": false, + "push-to-mute-delay": 0, + "push-to-talk": false, + "push-to-talk-delay": 0, + "hotkeys": {}, + "deinterlace_mode": 0, + "deinterlace_field_order": 0, + "monitoring_type": 0, + "private_settings": {} + }, + { + "prev_ver": 503447555, + "name": "player", + "uuid": "b915c093-dd10-4b86-9047-d6c069da09c9", + "id": "vlc_source", + "versioned_id": "vlc_source", + "settings": { + "playlist": [ + { + "hidden": false, + "selected": false, + "value": "Ignore/this/error/the/bot/will/fix/it" + } + ], + "loop": false, + "playback_behavior": "always_play", + "subtitle_enable": true, + "subtitle": 1 + }, + "mixers": 255, + "sync": 0, + "flags": 0, + "volume": 1.0, + "balance": 0.5, + "enabled": true, + "muted": false, + "push-to-mute": false, + "push-to-mute-delay": 0, + "push-to-talk": false, + "push-to-talk-delay": 0, + "hotkeys": { + "libobs.mute": [], + "libobs.unmute": [], + "libobs.push-to-mute": [], + "libobs.push-to-talk": [], + "VLCSource.PlayPause": [], + "VLCSource.Restart": [], + "VLCSource.Stop": [], + "VLCSource.PlaylistNext": [], + "VLCSource.PlaylistPrev": [] + }, + "deinterlace_mode": 0, + "deinterlace_field_order": 0, + "monitoring_type": 0, + "private_settings": {} + }, + { + "prev_ver": 503447555, + "name": "screenshare", + "uuid": "1c5d73c6-f2ed-4468-a78d-80a3cb669298", + "id": "scene", + "versioned_id": "scene", + "settings": { + "id_counter": 10, + "custom_size": false, + "items": [ + { + "name": "Wayland output(dmabuf)", + "source_uuid": "4f0ff3e5-ad9d-495b-8c9b-34ad166791e4", + "visible": true, + "locked": false, + "rot": 0.0, + "pos": { + "x": 0.0, + "y": 0.0 + }, + "scale": { + "x": 1.0, + "y": 1.0 + }, + "align": 5, + "bounds_type": 0, + "bounds_align": 0, + "bounds_crop": false, + "bounds": { + "x": 0.0, + "y": 0.0 + }, + "crop_left": 0, + "crop_top": 0, + "crop_right": 0, + "crop_bottom": 0, + "id": 10, + "group_item_backup": false, + "scale_filter": "disable", + "blend_method": "default", + "blend_type": "normal", + "show_transition": { + "duration": 0 + }, + "hide_transition": { + "duration": 0 + }, + "private_settings": {} + } + ] + }, + "mixers": 0, + "sync": 0, + "flags": 0, + "volume": 1.0, + "balance": 0.5, + "enabled": true, + "muted": false, + "push-to-mute": false, + "push-to-mute-delay": 0, + "push-to-talk": false, + "push-to-talk-delay": 0, + "hotkeys": { + "OBSBasic.SelectScene": [], + "libobs.show_scene_item.10": [], + "libobs.hide_scene_item.10": [] + }, + "deinterlace_mode": 0, + "deinterlace_field_order": 0, + "monitoring_type": 0, + "private_settings": {} + }, + { + "prev_ver": 503447555, + "name": "Text (FreeType 2)", + "uuid": "01d3bab9-cc17-4702-acc6-6e25319188eb", + "id": "text_ft2_source", + "versioned_id": "text_ft2_source_v2", + "settings": { + "text": "Video still downloading :(\n OR\nan unhandled error has occurred", + "font": { + "face": "Monocraft", + "style": "Regular", + "size": 100, + "flags": 0 + }, + "word_wrap": false, + "outline": false, + "drop_shadow": false + }, + "mixers": 0, + "sync": 0, + "flags": 0, + "volume": 1.0, + "balance": 0.5, + "enabled": true, + "muted": false, + "push-to-mute": false, + "push-to-mute-delay": 0, + "push-to-talk": false, + "push-to-talk-delay": 0, + "hotkeys": {}, + "deinterlace_mode": 0, + "deinterlace_field_order": 0, + "monitoring_type": 0, + "private_settings": {} + }, + { + "prev_ver": 503447555, + "name": "waiting", + "uuid": "c7ebc81c-31a1-4ed8-b492-1c5729562465", + "id": "scene", + "versioned_id": "scene", + "settings": { + "id_counter": 2, + "custom_size": false, + "items": [ + { + "name": "Image", + "source_uuid": "a9588972-73f5-4176-a837-5492bba04b6a", + "visible": true, + "locked": false, + "rot": 0.0, + "pos": { + "x": 0.0, + "y": 0.0 + }, + "scale": { + "x": 1.0, + "y": 1.0 + }, + "align": 5, + "bounds_type": 0, + "bounds_align": 0, + "bounds_crop": false, + "bounds": { + "x": 0.0, + "y": 0.0 + }, + "crop_left": 0, + "crop_top": 0, + "crop_right": 0, + "crop_bottom": 0, + "id": 2, + "group_item_backup": false, + "scale_filter": "disable", + "blend_method": "default", + "blend_type": "normal", + "show_transition": { + "duration": 0 + }, + "hide_transition": { + "duration": 0 + }, + "private_settings": {} + } + ] + }, + "mixers": 0, + "sync": 0, + "flags": 0, + "volume": 1.0, + "balance": 0.5, + "enabled": true, + "muted": false, + "push-to-mute": false, + "push-to-mute-delay": 0, + "push-to-talk": false, + "push-to-talk-delay": 0, + "hotkeys": { + "OBSBasic.SelectScene": [], + "libobs.show_scene_item.2": [], + "libobs.hide_scene_item.2": [] + }, + "deinterlace_mode": 0, + "deinterlace_field_order": 0, + "monitoring_type": 0, + "private_settings": {} + }, + { + "prev_ver": 503447555, + "name": "Wayland output(dmabuf)", + "uuid": "4f0ff3e5-ad9d-495b-8c9b-34ad166791e4", + "id": "wlrobs-dmabuf", + "versioned_id": "wlrobs-dmabuf", + "settings": { + "output": "HDMI-A-2" + }, + "mixers": 0, + "sync": 0, + "flags": 0, + "volume": 1.0, + "balance": 0.5, + "enabled": true, + "muted": false, + "push-to-mute": false, + "push-to-mute-delay": 0, + "push-to-talk": false, + "push-to-talk-delay": 0, + "hotkeys": {}, + "deinterlace_mode": 0, + "deinterlace_field_order": 0, + "monitoring_type": 0, + "private_settings": {} + }, + { + "prev_ver": 503447555, + "name": "youtube", + "uuid": "8df47719-9e3b-4c24-99c8-fbfa5ee715bf", + "id": "scene", + "versioned_id": "scene", + "settings": { + "id_counter": 13, + "custom_size": false, + "items": [ + { + "name": "notice", + "source_uuid": "e4912bae-e9c8-4574-9e7b-898963c4b20e", + "visible": false, + "locked": true, + "rot": 0.0, + "pos": { + "x": 0.0, + "y": 0.0 + }, + "scale": { + "x": 1.0, + "y": 1.0 + }, + "align": 5, + "bounds_type": 2, + "bounds_align": 0, + "bounds_crop": false, + "bounds": { + "x": 1920.0, + "y": 1080.0 + }, + "crop_left": 0, + "crop_top": 0, + "crop_right": 0, + "crop_bottom": 0, + "id": 11, + "group_item_backup": false, + "scale_filter": "disable", + "blend_method": "default", + "blend_type": "normal", + "show_transition": { + "duration": 0 + }, + "hide_transition": { + "duration": 0 + }, + "private_settings": {} + }, + { + "name": "player", + "source_uuid": "b915c093-dd10-4b86-9047-d6c069da09c9", + "visible": true, + "locked": true, + "rot": 0.0, + "pos": { + "x": 0.0, + "y": 0.0 + }, + "scale": { + "x": 1.0, + "y": 1.0 + }, + "align": 5, + "bounds_type": 2, + "bounds_align": 0, + "bounds_crop": false, + "bounds": { + "x": 1920.0, + "y": 1080.0 + }, + "crop_left": 0, + "crop_top": 0, + "crop_right": 0, + "crop_bottom": 0, + "id": 9, + "group_item_backup": false, + "scale_filter": "disable", + "blend_method": "default", + "blend_type": "normal", + "show_transition": { + "duration": 0 + }, + "hide_transition": { + "duration": 0 + }, + "private_settings": {} + }, + { + "name": "nowplaying", + "source_uuid": "2778ed68-2717-45e4-bf7d-f96c5df940bc", + "visible": false, + "locked": false, + "rot": 0.0, + "pos": { + "x": 5.0, + "y": 922.0 + }, + "scale": { + "x": 1.0, + "y": 1.0 + }, + "align": 5, + "bounds_type": 2, + "bounds_align": 0, + "bounds_crop": false, + "bounds": { + "x": 1920.0, + "y": 159.0 + }, + "crop_left": 0, + "crop_top": 0, + "crop_right": 0, + "crop_bottom": 0, + "id": 13, + "group_item_backup": false, + "scale_filter": "disable", + "blend_method": "default", + "blend_type": "normal", + "show_transition": { + "duration": 0 + }, + "hide_transition": { + "duration": 0 + }, + "private_settings": {} + } + ] + }, + "mixers": 0, + "sync": 0, + "flags": 0, + "volume": 1.0, + "balance": 0.5, + "enabled": true, + "muted": false, + "push-to-mute": false, + "push-to-mute-delay": 0, + "push-to-talk": false, + "push-to-talk-delay": 0, + "hotkeys": { + "OBSBasic.SelectScene": [], + "libobs.show_scene_item.11": [], + "libobs.hide_scene_item.11": [], + "libobs.show_scene_item.9": [], + "libobs.hide_scene_item.9": [], + "libobs.show_scene_item.13": [], + "libobs.hide_scene_item.13": [] + }, + "deinterlace_mode": 0, + "deinterlace_field_order": 0, + "monitoring_type": 0, + "private_settings": {} + } + ] +} \ No newline at end of file diff --git a/shell.nix b/shell.nix index 123508f..798cc3f 100644 --- a/shell.nix +++ b/shell.nix @@ -6,8 +6,11 @@ pkgs.mkShell { pkgs.python311Packages.selenium pkgs.python311Packages.discordpy pkgs.python311Packages.pip + pkgs.python311Packages.yt-dlp pkgs.python311Packages.python-dotenv - pkgs.obs-studio + pkgs.python311Packages.venvShellHook + pkgs.ffmpeg pkgs.discord ]; + venvDir = "./.venv"; } \ No newline at end of file