From ea87320c9a7092876c28c2bc938b3bf58102cb47 Mon Sep 17 00:00:00 2001 From: Hatsune Miku <73402249+Weebed-Coder@users.noreply.github.com> Date: Mon, 1 May 2023 02:17:46 -0400 Subject: [PATCH 01/21] Update main.py --- main.py | 41 +++++++++++++++++++++++++++-------------- 1 file changed, 27 insertions(+), 14 deletions(-) diff --git a/main.py b/main.py index 3f7bb3f..36f510e 100644 --- a/main.py +++ b/main.py @@ -21,17 +21,21 @@ def download_audio(user_input: str): print("Loading audio... this may take a moment.") - if "https://" or "www" in user_input: - with yt_dlp.YoutubeDL(ydl_opts) as ydl: - info = ydl.extract_info(user_input, False) - info = ydl.prepare_filename(info) - ydl.download([user_input]) - + with yt_dlp.YoutubeDL(ydl_opts) as ydl: + info = ydl.extract_info(user_input, False) + info = ydl.prepare_filename(info) + + if info.endswith(".webm"): + info = info.replace(".webm", ".wav") + else: + info = info.replace(info[-4:], ".wav") + + print(os.listdir()) + if info in os.listdir(): # This is for caching + print(os.listdir()) + return info + ydl.download([user_input]) - if info.endswith(".webm"): - info = info.replace(".webm", ".wav") - else: - info = info.replace(info[-4:], ".wav") return info def start_audio(): @@ -46,11 +50,13 @@ def start_audio(): print(f"{colorama.Fore.RED}An error has occured:\n{exc}") pass + print(file) + global audio_process - audio_wavobj = simpleaudio.WaveObject.from_wave_file(file) + audio_wavobj = simpleaudio.WaveObject.from_wave_file(file[0]) audio_process = audio_wavobj.play() audio_process.wait_done() - os.remove(file) + if file[1]: os.remove(file) def attempt_clear(): try: @@ -65,15 +71,22 @@ def user_interface(): audio_thread = False while True: - clear = True + clear = False + del_cache = False user_input = input("Please enter a command. Use help for a list of commands.\n> ").split(" ") + try: + if user_input[2] in ["-nocache", "-n"]: + del_cache = True + except: + pass + if user_input[0].lower() in ["help", "-?"]: print("Help list is not yet ready.") elif user_input[0].lower() in ["play", "start", "queue"]: file_loc = download_audio(user_input[1]) - audio_queue.put(file_loc) + audio_queue.put([file_loc, del_cache]) if audio_thread == False: audio_thread = threading.Thread(target = start_audio, name = "PyMedia Audio Player") audio_thread.start() From 84f304b15fefdbdbb9eba4d54c3a55c233d4d21d Mon Sep 17 00:00:00 2001 From: Hatsune Miku <73402249+Weebed-Coder@users.noreply.github.com> Date: Mon, 1 May 2023 02:18:54 -0400 Subject: [PATCH 02/21] Update README.md --- README.md | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 377151c..c9790fe 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,8 @@ # PyMedia -A Python CLI/GUI based media player. -If you have any suggestions on how to improve the code or repeatedly run in to an error then please open an issue. -Also yes I am aware of the formatting issue with the readme, Github didn't wanna while I was editing this. +A Python CLI/GUI based media player.
+If you have any suggestions on how to improve the code or repeatedly run in to an error then please open an issue.
+Also yes I am aware of the formatting issue with the readme, Github didn't wanna while I was editing this.

+This is the dev branch, do expect bugs and debug information being shit out. # Already known issues 1. Alsa shitting out a warning sometimes From 38af44ecb9135158451c7242ae4ec9e94e606165 Mon Sep 17 00:00:00 2001 From: Hatsune Miku <73402249+Weebed-Coder@users.noreply.github.com> Date: Mon, 1 May 2023 03:06:16 -0400 Subject: [PATCH 03/21] Update main.py --- main.py | 47 ++++++++++++++++++++++++++++++++++------------- 1 file changed, 34 insertions(+), 13 deletions(-) diff --git a/main.py b/main.py index 36f510e..0674deb 100644 --- a/main.py +++ b/main.py @@ -2,12 +2,13 @@ """ PyMedia from Weebed-Coder -Version 1.1.0 +Version 1.1.1 """ ydl_opts = { 'format': 'bestaudio/best', 'quiet': True, + 'outtmpl': r"cache/%(title)s-%(id)s.%(ext)s", "paths": {'wav':'cache', 'webm':'cache'}, 'postprocessors': [{ 'key': 'FFmpegExtractAudio', @@ -16,6 +17,28 @@ }], } +help_info = """ +Commands: + play - Play an audio by link. + Usage: play [link] + Aliases: start + pause - Pause the currently playing audio. + Usage: pause + resume - Resumes the paused audio. Has no effect if audio isn't paused. + Usage: resume + Aliases: unpause, continue + exit - Exits the program. + Usage: exit + Aliases: leave, quit + stop - Stops the currently playing audio. + Usage: stop + +Flags: + -nocache - Will delete the .wav file once it is done playing, this is useful for when you're minimizing disk space usage. + Usage: play [link] -nocache + Aliases: -n +""" # I understand this is bad practice however for now I'll keep it this way, simplifies the program. (aka am lazy to make a more convoluted solution) + global audio_process def download_audio(user_input: str): @@ -30,9 +53,7 @@ def download_audio(user_input: str): else: info = info.replace(info[-4:], ".wav") - print(os.listdir()) - if info in os.listdir(): # This is for caching - print(os.listdir()) + if info.replace("cache/", "") in os.listdir("cache"): # This is for caches (if any) return info ydl.download([user_input]) @@ -50,13 +71,11 @@ def start_audio(): print(f"{colorama.Fore.RED}An error has occured:\n{exc}") pass - print(file) - global audio_process audio_wavobj = simpleaudio.WaveObject.from_wave_file(file[0]) audio_process = audio_wavobj.play() audio_process.wait_done() - if file[1]: os.remove(file) + if file[1]: os.remove(f"cache/{file}") def attempt_clear(): try: @@ -71,7 +90,7 @@ def user_interface(): audio_thread = False while True: - clear = False + clear = True del_cache = False user_input = input("Please enter a command. Use help for a list of commands.\n> ").split(" ") @@ -82,7 +101,9 @@ def user_interface(): pass if user_input[0].lower() in ["help", "-?"]: - print("Help list is not yet ready.") + attempt_clear() + clear = False + print(help_info) elif user_input[0].lower() in ["play", "start", "queue"]: file_loc = download_audio(user_input[1]) @@ -108,7 +129,7 @@ def user_interface(): if __name__ == "__main__": while True: - try: - user_interface() - except Exception as e: - print(f"{colorama.Fore.RED}An error has occured, if this error continues to occur then open an issue in the project github. Restarting interface.\n\nError:\n{e}{colorama.Fore.RESET}") + #try: + user_interface() + #except Exception as e: + # print(f"{colorama.Fore.RED}An error has occured, if this error continues to occur then open an issue in the project github. Restarting interface.\n\nError:\n{e}{colorama.Fore.RESET}") From ba64db10a4d468ad7a81a9f7ca6e61cc41ea8929 Mon Sep 17 00:00:00 2001 From: Hatsune Miku <73402249+Weebed-Coder@users.noreply.github.com> Date: Mon, 1 May 2023 03:08:28 -0400 Subject: [PATCH 04/21] Update README.md --- README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index c9790fe..ae33951 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,8 @@ Also yes I am aware of the formatting issue with the readme, Github didn't wanna # Already known issues 1. Alsa shitting out a warning sometimes -2. Help command still unavailable (I forgot to add this during release of v1.1.0) +2. ValueError upon trying to use any command directly after queue is empty. (Actual cause is not 100% known) +3. `exit` command doesn't exit fully when playing audio and will need to press ctrl+c in order to fully exit. # How to install

Windows

From 3c8a36f63a49050a8f75f12193895a0146cf8f88 Mon Sep 17 00:00:00 2001 From: Hatsune Miku <73402249+Weebed-Coder@users.noreply.github.com> Date: Mon, 1 May 2023 03:09:00 -0400 Subject: [PATCH 05/21] Changed version number in 1.2.0 --- main.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/main.py b/main.py index 0674deb..1c909a9 100644 --- a/main.py +++ b/main.py @@ -2,7 +2,7 @@ """ PyMedia from Weebed-Coder -Version 1.1.1 +Version 1.2.0 """ ydl_opts = { From 41e534765d554c026615b73f7633156c8051b7b1 Mon Sep 17 00:00:00 2001 From: Hatsune Miku <73402249+Weebed-Coder@users.noreply.github.com> Date: Fri, 5 May 2023 02:27:45 -0400 Subject: [PATCH 06/21] GUI alpha update --- main.py | 104 ++++++++++++++++++++++++++++++++++++++--------- requirements.txt | 5 +++ 2 files changed, 90 insertions(+), 19 deletions(-) create mode 100644 requirements.txt diff --git a/main.py b/main.py index 1c909a9..dbccca1 100644 --- a/main.py +++ b/main.py @@ -1,8 +1,15 @@ -import yt_dlp, threading, queue, colorama, os, simpleaudio +import yt_dlp, threading, queue, colorama, os, time, argparse +import dearpygui.dearpygui as dpg +from pygame import mixer + +mixer.init() + +version = "1.2.0a" """ PyMedia from Weebed-Coder -Version 1.2.0 + +This is an alpha version of PyMedia 1.2.0, do expect bugs and missing features. """ ydl_opts = { @@ -40,6 +47,10 @@ """ # I understand this is bad practice however for now I'll keep it this way, simplifies the program. (aka am lazy to make a more convoluted solution) global audio_process +global audio_queue +global audio_thread +audio_thread = False +audio_queue = queue.Queue() def download_audio(user_input: str): print("Loading audio... this may take a moment.") @@ -60,6 +71,7 @@ def download_audio(user_input: str): return info def start_audio(): + global audio_thread while True: if audio_queue.empty(): audio_thread = False @@ -72,9 +84,15 @@ def start_audio(): pass global audio_process - audio_wavobj = simpleaudio.WaveObject.from_wave_file(file[0]) - audio_process = audio_wavobj.play() - audio_process.wait_done() + global paused + #audio_wavobj = simpleaudio.WaveObject.from_wave_file(file[0]) + #audio_process = audio_wavobj.play() + #audio_process.wait_done() + tada = mixer.Sound(file[0]) + audio_process = tada.play() + paused = False + while audio_process.get_busy(): + time.sleep(0.1) if file[1]: os.remove(f"cache/{file}") def attempt_clear(): @@ -83,11 +101,18 @@ def attempt_clear(): except Exception as hi_neko: # This is not tested on a windows machine yet. os.system("cls") -def user_interface(): - global audio_queue +def prepare_and_play(sender = False, user_input: str = None, del_cache: bool = True): + if sender: + user_input = dpg.get_value("user_url") + print(user_input) global audio_thread - audio_queue = queue.Queue() - audio_thread = False + file_loc = download_audio(user_input) + audio_queue.put([file_loc, del_cache]) + if audio_thread == False: + audio_thread = threading.Thread(target = start_audio, name = "PyMedia Audio Player") + audio_thread.start() + +def user_interface(): while True: clear = True @@ -106,17 +131,13 @@ def user_interface(): print(help_info) elif user_input[0].lower() in ["play", "start", "queue"]: - file_loc = download_audio(user_input[1]) - audio_queue.put([file_loc, del_cache]) - if audio_thread == False: - audio_thread = threading.Thread(target = start_audio, name = "PyMedia Audio Player") - audio_thread.start() + prepare_and_play(user_input = user_input[0], del_cache = del_cache) elif user_input[0].lower() in ["pause"]: audio_process.pause() elif user_input[0].lower() in ["continue", "resume", "unpause"]: - audio_process.resume() + audio_process.unpause() elif user_input[0].lower() in ["stop"]: audio_process.stop() @@ -127,9 +148,54 @@ def user_interface(): if clear: attempt_clear() +def gui_interface(): + def change_pause_state(): + try: + global audio_process + global paused + if paused == False: + audio_process.pause() + paused = True + else: + audio_process.unpause() + paused = False + except Exception as err: + raise err + + def move_along_now(): + audio_process.stop() + + dpg.create_context() + dpg.create_viewport() + + with dpg.window(label=f"PyMedia v{version}", tag="Primary Window"): + # Note: Will need to add a bottom border for :sparkles:style:sparkles: + with dpg.group(label="search_upper", horizontal=True): + user_url = dpg.add_input_text(label="URL", tag="user_url") + dpg.add_button(label="Confirm", callback = prepare_and_play) + + with dpg.group(label="main_middle"): + pass # This section will contain locally installed files that can use + with dpg.group(label="media_controls"): + with dpg.group(label="playback_control", horizontal=True): + dpg.add_button(label = "<") # Will need to round these out later when possible, also the button "<" will not work because there is no functionality for it yet + dpg.add_button(label = "||", callback = change_pause_state) + dpg.add_button(label = ">", callback = move_along_now) + + dpg.create_viewport(title=f'PyMedia v{version}', width=600, height=200) + dpg.setup_dearpygui() + dpg.show_viewport() + dpg.set_primary_window("Primary Window", True) + dpg.start_dearpygui() + dpg.destroy_context() + if __name__ == "__main__": + parser = argparse.ArgumentParser() + parser.add_argument("-g", "--gui", help="Change if the app boots with or without GUI.", type=bool) while True: - #try: - user_interface() - #except Exception as e: - # print(f"{colorama.Fore.RED}An error has occured, if this error continues to occur then open an issue in the project github. Restarting interface.\n\nError:\n{e}{colorama.Fore.RESET}") + try: + args = parser.parse_args() + if args.gui: user_interface() + gui_interface() + except Exception as e: + print(f"{colorama.Fore.RED}An error has occured, if this error continues to occur then open an issue in the project github. Restarting interface.\n\nError:\n{e}{colorama.Fore.RESET}") diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..9223148 --- /dev/null +++ b/requirements.txt @@ -0,0 +1,5 @@ +colorama==0.4.4 +complexaudio==1.0.4 +yt-dlp==2023.3.4 +dearpygui==1.9.0 + From efd175c26667c5b33252097de82021bf631a189b Mon Sep 17 00:00:00 2001 From: Hatsune Miku <73402249+Weebed-Coder@users.noreply.github.com> Date: Fri, 5 May 2023 02:30:00 -0400 Subject: [PATCH 07/21] Update README.md --- README.md | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index ae33951..37148cb 100644 --- a/README.md +++ b/README.md @@ -24,7 +24,9 @@ Also yes I am aware of the formatting issue with the readme, Github didn't wanna # How to use 1. Get a youtube or soundcloud link, or generally anything youtube-dlp supports. 2. Run `main.py` -3. Type in "play " and then paste in your link. (Example: `start https://youtu.be/dQw4w9WgXcQ`) +3. Choose if you want to use gui with `--nogui` or `-ng`. Or, leave it blank to default to gui. +4 (1/2). If you're using CLI, type in "play " and then paste in your link. (Example: `start https://youtu.be/dQw4w9WgXcQ`) +4 (2/2). If you're using GUI, type in your link in to the text box with "URL" next to it then press "confirm". # Media controls `play`: Loads an audio file from either local files or a youtube link. (Aliases: `start`)
@@ -36,5 +38,5 @@ Also yes I am aware of the formatting issue with the readme, Github didn't wanna # To do 1. Allow for user to select between keeping downloaded files or deleting them. 2. Check if file is already downloaded. -3. Allow for GUI usage of app. +3. Finish app GUI. 4. Compile app in to a `.exe` From 73c63953eb64cf69e08435def0b3e7c3de5db4bc Mon Sep 17 00:00:00 2001 From: Hatsune Miku <73402249+Weebed-Coder@users.noreply.github.com> Date: Wed, 24 May 2023 02:21:58 -0400 Subject: [PATCH 08/21] Moved to v1.2.1a Changes include: - Searching is now possible on GUI (not tested on CLI) by not using a link but rather a search term, goes with the topmost term. - Possibly improved caching - Moving from .wav format to .mp3 due to some issues with pygame mixer generating high pitched noises when using .wav --- main.py | 49 ++++++++++++++++++++++++++++++++++++++++-------- requirements.txt | 2 +- 2 files changed, 42 insertions(+), 9 deletions(-) diff --git a/main.py b/main.py index dbccca1..b695d3b 100644 --- a/main.py +++ b/main.py @@ -4,7 +4,7 @@ mixer.init() -version = "1.2.0a" +version = "1.2.1a" """ PyMedia from Weebed-Coder @@ -15,11 +15,11 @@ ydl_opts = { 'format': 'bestaudio/best', 'quiet': True, - 'outtmpl': r"cache/%(title)s-%(id)s.%(ext)s", - "paths": {'wav':'cache', 'webm':'cache'}, + 'outtmpl': "cache/%(title)s-%(id)s.%(ext)s", + "paths": {'mp3':'cache', 'webm':'cache'}, 'postprocessors': [{ 'key': 'FFmpegExtractAudio', - 'preferredcodec': 'wav', + 'preferredcodec': 'mp3', 'preferredquality': '192', }], } @@ -44,7 +44,10 @@ -nocache - Will delete the .wav file once it is done playing, this is useful for when you're minimizing disk space usage. Usage: play [link] -nocache Aliases: -n -""" # I understand this is bad practice however for now I'll keep it this way, simplifies the program. (aka am lazy to make a more convoluted solution) +""" +# I understand this may be bad practice however for now I'll +# keep it this way, simplifies the program. (aka am +# too lazy to make a more convoluted solution) global audio_process global audio_queue @@ -53,24 +56,42 @@ audio_queue = queue.Queue() def download_audio(user_input: str): + """ + Downloads audio using yt_dlp library + """ print("Loading audio... this may take a moment.") with yt_dlp.YoutubeDL(ydl_opts) as ydl: + if user_input.startswith("https://") == False and user_input.startswith("www.") == False: + funny_dict = ydl.extract_info(f"ytsearch:{user_input}", download=False)['entries'][0]['id'] + user_input = f"https://youtube.com/watch?v={funny_dict}" + elif user_input.startswith("http://"): + print("Visiting http websites is unsafe, please refrain from visiting websites using http and not https as they're insecure.") + info = ydl.extract_info(user_input, False) info = ydl.prepare_filename(info) + print(info) + if info.endswith(".webm"): - info = info.replace(".webm", ".wav") + info = info.replace(".webm", ".mp3") else: - info = info.replace(info[-4:], ".wav") + print(1) + info = info.replace(info[-4:], ".mp3") - if info.replace("cache/", "") in os.listdir("cache"): # This is for caches (if any) + print(info) + + if os.path.exists(info): # This is for caches (if any) return info ydl.download([user_input]) return info def start_audio(): + """ + Starts the audio thread, alongside handling removing items from queue. + """ + global audio_thread while True: if audio_queue.empty(): @@ -102,6 +123,10 @@ def attempt_clear(): os.system("cls") def prepare_and_play(sender = False, user_input: str = None, del_cache: bool = True): + """ + Prepares and plays the audio. + """ + if sender: user_input = dpg.get_value("user_url") print(user_input) @@ -113,6 +138,9 @@ def prepare_and_play(sender = False, user_input: str = None, del_cache: bool = T audio_thread.start() def user_interface(): + """ + CLI user interface + """ while True: clear = True @@ -149,6 +177,10 @@ def user_interface(): attempt_clear() def gui_interface(): + """ + GUI user interface + """ + def change_pause_state(): try: global audio_process @@ -189,6 +221,7 @@ def move_along_now(): dpg.start_dearpygui() dpg.destroy_context() + if __name__ == "__main__": parser = argparse.ArgumentParser() parser.add_argument("-g", "--gui", help="Change if the app boots with or without GUI.", type=bool) diff --git a/requirements.txt b/requirements.txt index 9223148..b8d27fe 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,5 +1,5 @@ colorama==0.4.4 -complexaudio==1.0.4 +pygame==2.2.0 yt-dlp==2023.3.4 dearpygui==1.9.0 From a2d3b5fe52839c0bd4010fea21d45b9397ce05ff Mon Sep 17 00:00:00 2001 From: Hatsune Miku <73402249+Weebed-Coder@users.noreply.github.com> Date: Wed, 24 May 2023 02:30:46 -0400 Subject: [PATCH 09/21] Update README.md Updated the readme to be more up to date --- README.md | 22 ++++++++++++---------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/README.md b/README.md index 37148cb..8c1652a 100644 --- a/README.md +++ b/README.md @@ -5,9 +5,8 @@ Also yes I am aware of the formatting issue with the readme, Github didn't wanna This is the dev branch, do expect bugs and debug information being shit out. # Already known issues -1. Alsa shitting out a warning sometimes -2. ValueError upon trying to use any command directly after queue is empty. (Actual cause is not 100% known) -3. `exit` command doesn't exit fully when playing audio and will need to press ctrl+c in order to fully exit. +1. ValueError upon trying to use any command directly after queue is empty. (Actual cause is not 100% known) +2. `exit` command doesn't exit fully when playing audio and will need to press ctrl+c in order to fully exit. (?) # How to install

Windows

@@ -24,9 +23,11 @@ Also yes I am aware of the formatting issue with the readme, Github didn't wanna # How to use 1. Get a youtube or soundcloud link, or generally anything youtube-dlp supports. 2. Run `main.py` -3. Choose if you want to use gui with `--nogui` or `-ng`. Or, leave it blank to default to gui. -4 (1/2). If you're using CLI, type in "play " and then paste in your link. (Example: `start https://youtu.be/dQw4w9WgXcQ`) -4 (2/2). If you're using GUI, type in your link in to the text box with "URL" next to it then press "confirm". +3. Choose if you want to use gui with `--nogui` or `-ng`. Or, leave it blank to default to gui.
+4. (1/2) If you're using CLI, type in "play " and then paste in your link. (Example: `start https://youtu.be/dQw4w9WgXcQ`)
+(2/2) If you're using GUI, type in your link in to the text box with "URL" next to it then press "confirm".
+ +Quick note: You can now use a search term such as "Rick Astley - Never Gonna Give You Up" on both CLI and GUI. # Media controls `play`: Loads an audio file from either local files or a youtube link. (Aliases: `start`)
@@ -36,7 +37,8 @@ Also yes I am aware of the formatting issue with the readme, Github didn't wanna `exit`: Exits out of the media player. (Aliases: `quit`, `leave`) # To do -1. Allow for user to select between keeping downloaded files or deleting them. -2. Check if file is already downloaded. -3. Finish app GUI. -4. Compile app in to a `.exe` +1. Allow for user to select between keeping downloaded files or deleting them. (Currently implemented in CLI, still needs to be implemented in GUI) +2. Finish app GUI. +3. Compile app in to a `.exe` +4. Allow for going backwards in queue (this also allows one to loop in a queue). +5. Clean everything up From a4c8a4779d36f712ecc98aac1253717dbf14a3f5 Mon Sep 17 00:00:00 2001 From: Hatsune Miku <73402249+Weebed-Coder@users.noreply.github.com> Date: Wed, 24 May 2023 02:59:24 -0400 Subject: [PATCH 10/21] Quick bug fix Added button to exit the window, will properly fix the window closing issue later. --- main.py | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/main.py b/main.py index b695d3b..8eba082 100644 --- a/main.py +++ b/main.py @@ -1,7 +1,7 @@ import yt_dlp, threading, queue, colorama, os, time, argparse import dearpygui.dearpygui as dpg -from pygame import mixer +from pygame import mixer mixer.init() version = "1.2.1a" @@ -26,9 +26,9 @@ help_info = """ Commands: - play - Play an audio by link. + play - Play an audio by link or search term. Usage: play [link] - Aliases: start + Aliases: start, queue pause - Pause the currently playing audio. Usage: pause resume - Resumes the paused audio. Has no effect if audio isn't paused. @@ -194,8 +194,11 @@ def change_pause_state(): except Exception as err: raise err + # These are here to bypass some issues in DearPyGUI def move_along_now(): audio_process.stop() + def close_app(_sender, _data): + os._exit(0) dpg.create_context() dpg.create_viewport() @@ -204,7 +207,8 @@ def move_along_now(): # Note: Will need to add a bottom border for :sparkles:style:sparkles: with dpg.group(label="search_upper", horizontal=True): user_url = dpg.add_input_text(label="URL", tag="user_url") - dpg.add_button(label="Confirm", callback = prepare_and_play) + dpg.add_button(label="Confirm", callback=prepare_and_play) + dpg.add_button(label="Exit", callback=close_app) # This is here due to a minor issue in DearPyGUI with dpg.group(label="main_middle"): pass # This section will contain locally installed files that can use From 07c420872a86f7493c1053075667d7fc824e313c Mon Sep 17 00:00:00 2001 From: Hatsune Miku <73402249+Weebed-Coder@users.noreply.github.com> Date: Wed, 24 May 2023 03:34:55 -0700 Subject: [PATCH 11/21] Update README.md Added a new to do --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 8c1652a..4765f37 100644 --- a/README.md +++ b/README.md @@ -42,3 +42,4 @@ Quick note: You can now use a search term such as "Rick Astley - Never Gonna Giv 3. Compile app in to a `.exe` 4. Allow for going backwards in queue (this also allows one to loop in a queue). 5. Clean everything up +6. Debug logs to allow for easier debugging on the developers end. From 50616fd708ba39489e07b597ce6c67cfc9cc5413 Mon Sep 17 00:00:00 2001 From: Hatsune Miku <73402249+Weebed-Coder@users.noreply.github.com> Date: Wed, 24 May 2023 06:44:19 -0400 Subject: [PATCH 12/21] Update README.md Updated todo, added binary installation instructions, and removed unnecessary profanity. --- README.md | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index 4765f37..50df98f 100644 --- a/README.md +++ b/README.md @@ -1,14 +1,20 @@ # PyMedia A Python CLI/GUI based media player.
-If you have any suggestions on how to improve the code or repeatedly run in to an error then please open an issue.
-Also yes I am aware of the formatting issue with the readme, Github didn't wanna while I was editing this.

-This is the dev branch, do expect bugs and debug information being shit out. +If you have any suggestions on how to improve the code or repeatedly run in to an error then please open an issue.

+This is the dev branch, do expect bugs and debug information being printed out. # Already known issues 1. ValueError upon trying to use any command directly after queue is empty. (Actual cause is not 100% known) 2. `exit` command doesn't exit fully when playing audio and will need to press ctrl+c in order to fully exit. (?) # How to install +

Binaries

+1. Go to https://github.com/Weebed-Coder/PyMedia/releases/tag/v1.2.1a
+2. Download the binary for your appropriate platform
+3. (Optional) Move the binary to an appropriate folder
+4. Run the binary. + +

Installation from source

Windows

1. Install 7zip if you haven't already, the install file is actively available in the repo files.
2. Use 7zip to unzip `ffmpeg.7z`. This file is important as it is required in YT-DLP @@ -39,7 +45,6 @@ Quick note: You can now use a search term such as "Rick Astley - Never Gonna Giv # To do 1. Allow for user to select between keeping downloaded files or deleting them. (Currently implemented in CLI, still needs to be implemented in GUI) 2. Finish app GUI. -3. Compile app in to a `.exe` -4. Allow for going backwards in queue (this also allows one to loop in a queue). -5. Clean everything up -6. Debug logs to allow for easier debugging on the developers end. +3. Allow for going backwards in queue (this also allows one to loop in a queue). +4. Get rid of debug prints +5. Debug logs to allow for easier debugging on the developers end. From 22a8acbd7ccb9d7afe211f2d3575d348839e4ecb Mon Sep 17 00:00:00 2001 From: Hatsune Miku <73402249+Weebed-Coder@users.noreply.github.com> Date: Sun, 28 May 2023 12:35:50 -0400 Subject: [PATCH 13/21] Update README.md --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 50df98f..5dfa23b 100644 --- a/README.md +++ b/README.md @@ -48,3 +48,4 @@ Quick note: You can now use a search term such as "Rick Astley - Never Gonna Giv 3. Allow for going backwards in queue (this also allows one to loop in a queue). 4. Get rid of debug prints 5. Debug logs to allow for easier debugging on the developers end. +6. Add a status text for progress of download. From 23d51d888ffc785d01f667132bac0ec7f3ff1eaa Mon Sep 17 00:00:00 2001 From: Hatsune Miku <73402249+Weebed-Coder@users.noreply.github.com> Date: Sat, 3 Jun 2023 03:30:53 -0400 Subject: [PATCH 14/21] Update README.md Moved to-do to GitHub issues. This allows for better organization. --- README.md | 8 -------- 1 file changed, 8 deletions(-) diff --git a/README.md b/README.md index 5dfa23b..bf316a0 100644 --- a/README.md +++ b/README.md @@ -41,11 +41,3 @@ Quick note: You can now use a search term such as "Rick Astley - Never Gonna Giv `unpause`: Resume the current audio. (Aliases: `resume`, `unpause`, `continue`)
`stop`: Stops playing the current soundtrack and moves on to the next one in queue.
`exit`: Exits out of the media player. (Aliases: `quit`, `leave`) - -# To do -1. Allow for user to select between keeping downloaded files or deleting them. (Currently implemented in CLI, still needs to be implemented in GUI) -2. Finish app GUI. -3. Allow for going backwards in queue (this also allows one to loop in a queue). -4. Get rid of debug prints -5. Debug logs to allow for easier debugging on the developers end. -6. Add a status text for progress of download. From 9c45f1b733e7468e22aacfd60de9c6ae0c99b8b9 Mon Sep 17 00:00:00 2001 From: Hatsune Miku <73402249+Weebed-Coder@users.noreply.github.com> Date: Sun, 11 Jun 2023 14:23:29 -0400 Subject: [PATCH 15/21] Bug fixes Squashed two major bugs. --- main.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/main.py b/main.py index 8eba082..ffd6c31 100644 --- a/main.py +++ b/main.py @@ -96,7 +96,7 @@ def start_audio(): while True: if audio_queue.empty(): audio_thread = False - exit() + return try: file = audio_queue.get() @@ -159,7 +159,7 @@ def user_interface(): print(help_info) elif user_input[0].lower() in ["play", "start", "queue"]: - prepare_and_play(user_input = user_input[0], del_cache = del_cache) + prepare_and_play(user_input = user_input[1], del_cache = del_cache) elif user_input[0].lower() in ["pause"]: audio_process.pause() From e8f343c95a8317176d014bd652fb4ad14b5cb722 Mon Sep 17 00:00:00 2001 From: BnDLett <73402249+BnDLett@users.noreply.github.com> Date: Mon, 25 Dec 2023 15:32:58 -0500 Subject: [PATCH 16/21] Updated to 1.3.0a Switched from DearPyGUI to PyQT6. --- main.py | 221 ++++++++++++++++++++++++++++++++------------------------ 1 file changed, 127 insertions(+), 94 deletions(-) diff --git a/main.py b/main.py index ffd6c31..4214ab7 100644 --- a/main.py +++ b/main.py @@ -1,22 +1,19 @@ +import sys + import yt_dlp, threading, queue, colorama, os, time, argparse -import dearpygui.dearpygui as dpg +from PyQt6.QtWidgets import * from pygame import mixer -mixer.init() - -version = "1.2.1a" -""" -PyMedia from Weebed-Coder +mixer.init() -This is an alpha version of PyMedia 1.2.0, do expect bugs and missing features. -""" +version = "1.3.0a" ydl_opts = { 'format': 'bestaudio/best', 'quiet': True, 'outtmpl': "cache/%(title)s-%(id)s.%(ext)s", - "paths": {'mp3':'cache', 'webm':'cache'}, + "paths": {'mp3': 'cache', 'webm': 'cache'}, 'postprocessors': [{ 'key': 'FFmpegExtractAudio', 'preferredcodec': 'mp3', @@ -44,31 +41,33 @@ -nocache - Will delete the .wav file once it is done playing, this is useful for when you're minimizing disk space usage. Usage: play [link] -nocache Aliases: -n -""" -# I understand this may be bad practice however for now I'll -# keep it this way, simplifies the program. (aka am -# too lazy to make a more convoluted solution) +""" # I understand this may be bad practice however for now I'll keep it this way, it simplifies the program. global audio_process global audio_queue global audio_thread +audio_process = mixer.Channel audio_thread = False audio_queue = queue.Queue() -def download_audio(user_input: str): + +def download_audio(link: str): """ - Downloads audio using yt_dlp library + Downloads audio using the YT-DLP library. + :param link: The link to the audio to be downloaded. + :return: """ print("Loading audio... this may take a moment.") with yt_dlp.YoutubeDL(ydl_opts) as ydl: - if user_input.startswith("https://") == False and user_input.startswith("www.") == False: - funny_dict = ydl.extract_info(f"ytsearch:{user_input}", download=False)['entries'][0]['id'] - user_input = f"https://youtube.com/watch?v={funny_dict}" - elif user_input.startswith("http://"): - print("Visiting http websites is unsafe, please refrain from visiting websites using http and not https as they're insecure.") - - info = ydl.extract_info(user_input, False) + if link.startswith("https://") == False and link.startswith("www.") == False: + funny_dict = ydl.extract_info(f"ytsearch:{link}", download=False)['entries'][0]['id'] + link = f"https://youtube.com/watch?v={funny_dict}" + elif link.startswith("http://"): + print("Visiting http websites is unsafe, please refrain from visiting websites using http and not https as " + "they're insecure.") + + info = ydl.extract_info(link, False) info = ydl.prepare_filename(info) print(info) @@ -78,18 +77,20 @@ def download_audio(user_input: str): else: print(1) info = info.replace(info[-4:], ".mp3") - + print(info) - if os.path.exists(info): # This is for caches (if any) + if os.path.exists(info): # This is for caches (if any) return info - ydl.download([user_input]) + ydl.download([link]) return info + def start_audio(): """ - Starts the audio thread, alongside handling removing items from queue. + Starts the audio thread and deletes audio cache if specified. + :return: """ global audio_thread @@ -106,40 +107,49 @@ def start_audio(): global audio_process global paused - #audio_wavobj = simpleaudio.WaveObject.from_wave_file(file[0]) - #audio_process = audio_wavobj.play() - #audio_process.wait_done() + # audio_wavobj = simpleaudio.WaveObject.from_wave_file(file[0]) + # audio_process = audio_wavobj.play() + # audio_process.wait_done() tada = mixer.Sound(file[0]) audio_process = tada.play() paused = False while audio_process.get_busy(): time.sleep(0.1) - if file[1]: os.remove(f"cache/{file}") + if file[1]: os.remove(f"{file[0]}") + def attempt_clear(): + """ + Attempts to clear the terminal. Does not work for macOS. + :return: + """ try: os.system("clear") - except Exception as hi_neko: # This is not tested on a windows machine yet. + except Exception as hi_neko: # This is not tested on a Windows machine yet. os.system("cls") -def prepare_and_play(sender = False, user_input: str = None, del_cache: bool = True): + +def prepare_and_play(link: str = None, del_cache: bool = True): """ - Prepares and plays the audio. + Prepares and plays audio using YT-DLP and PyGame Mixer. + :param link: The link for the audio to be downloaded. + :param del_cache: If audio cache should be deleted or not. + :return: """ - if sender: - user_input = dpg.get_value("user_url") - print(user_input) + print(link) global audio_thread - file_loc = download_audio(user_input) + file_loc = download_audio(link) audio_queue.put([file_loc, del_cache]) - if audio_thread == False: - audio_thread = threading.Thread(target = start_audio, name = "PyMedia Audio Player") + if not audio_thread: + audio_thread = threading.Thread(target=start_audio, name="PyMedia Audio Player") audio_thread.start() + def user_interface(): """ CLI user interface + :return: """ while True: @@ -159,7 +169,7 @@ def user_interface(): print(help_info) elif user_input[0].lower() in ["play", "start", "queue"]: - prepare_and_play(user_input = user_input[1], del_cache = del_cache) + prepare_and_play(user_input=user_input[1], del_cache=del_cache) elif user_input[0].lower() in ["pause"]: audio_process.pause() @@ -169,70 +179,93 @@ def user_interface(): elif user_input[0].lower() in ["stop"]: audio_process.stop() - + elif user_input[0].lower() in ["quit", "exit", "leave"]: exit() - + if clear: attempt_clear() -def gui_interface(): + +class GUIInterface(QMainWindow): """ - GUI user interface + GUI interface + :return: """ - def change_pause_state(): - try: - global audio_process - global paused - if paused == False: - audio_process.pause() - paused = True - else: - audio_process.unpause() - paused = False - except Exception as err: - raise err - - # These are here to bypass some issues in DearPyGUI - def move_along_now(): - audio_process.stop() - def close_app(_sender, _data): - os._exit(0) - - dpg.create_context() - dpg.create_viewport() - - with dpg.window(label=f"PyMedia v{version}", tag="Primary Window"): - # Note: Will need to add a bottom border for :sparkles:style:sparkles: - with dpg.group(label="search_upper", horizontal=True): - user_url = dpg.add_input_text(label="URL", tag="user_url") - dpg.add_button(label="Confirm", callback=prepare_and_play) - dpg.add_button(label="Exit", callback=close_app) # This is here due to a minor issue in DearPyGUI - - with dpg.group(label="main_middle"): - pass # This section will contain locally installed files that can use - with dpg.group(label="media_controls"): - with dpg.group(label="playback_control", horizontal=True): - dpg.add_button(label = "<") # Will need to round these out later when possible, also the button "<" will not work because there is no functionality for it yet - dpg.add_button(label = "||", callback = change_pause_state) - dpg.add_button(label = ">", callback = move_along_now) - - dpg.create_viewport(title=f'PyMedia v{version}', width=600, height=200) - dpg.setup_dearpygui() - dpg.show_viewport() - dpg.set_primary_window("Primary Window", True) - dpg.start_dearpygui() - dpg.destroy_context() + def __init__(self, version): + super().__init__() + + global audio_process + global paused + paused = False + + self.__audio_process__ = audio_process + self.__paused__ = paused + self.__version__ = version + self.spacing = 5 + + self.setWindowTitle(f"PyMedia {self.__version__}") + self.setGeometry(0, 0, 600, 200) + + self.link = QLineEdit("", self) + self.link.setGeometry(5, 5, 325, 25) + + enter = QPushButton("Enter", self) + enter.clicked.connect(self.run_audio) + enter.setGeometry((self.link.x() + self.link.width()) + self.spacing, self.link.y(), 50, self.link.height()) + + previous_btn = QPushButton("Previous", self) + previous_btn.setGeometry(self.link.x(), (self.link.y() + self.link.height()) + self.spacing, 70, + self.link.height()) + + self.pause_play = QPushButton("Pause/Play", self) + self.pause_play.clicked.connect(self.change_pause_state) + self.pause_play.setGeometry((previous_btn.x() + previous_btn.width()) + self.spacing, previous_btn.y(), + previous_btn.width(), previous_btn.height()) + + self.skip_btn = QPushButton("Skip", self) + self.skip_btn.clicked.connect(lambda: audio_process.stop()) + self.skip_btn.setGeometry((self.pause_play.x() + self.pause_play.width()) + self.spacing, self.pause_play.y(), + self.pause_play.width(), self.pause_play.height()) + + previous_btn.setDisabled(True) + self.pause_play.setDisabled(audio_queue.empty()) + self.skip_btn.setDisabled(audio_queue.empty()) + + def change_pause_state(self): + if not self.__paused__: + audio_process.pause() + self.__paused__ = True + else: + audio_process.unpause() + self.__paused__ = False + + def run_audio(self): + """ + Runs the audio. + :return: + """ + import threading # Ensures threading is imported + thread = threading.Thread(target=prepare_and_play, args=(self.link.text(),)) + thread.start() + self.pause_play.setDisabled(False) + self.skip_btn.setDisabled(False) if __name__ == "__main__": parser = argparse.ArgumentParser() parser.add_argument("-g", "--gui", help="Change if the app boots with or without GUI.", type=bool) - while True: - try: - args = parser.parse_args() - if args.gui: user_interface() - gui_interface() - except Exception as e: - print(f"{colorama.Fore.RED}An error has occured, if this error continues to occur then open an issue in the project github. Restarting interface.\n\nError:\n{e}{colorama.Fore.RESET}") + + try: + args = parser.parse_args() + if args.gui: user_interface() + + App = QApplication(sys.argv) + window = GUIInterface(version) + window.show() + os._exit(App.exec()) + except Exception as e: + print( + f"{colorama.Fore.RED}An error has occurred, if this error continues to occur then open an issue in the " + f"project github.\n\nError:\n{e}{colorama.Fore.RESET}") From b6522f243e1d1a62692d0e279318048094d08e62 Mon Sep 17 00:00:00 2001 From: BnDLett <73402249+BnDLett@users.noreply.github.com> Date: Mon, 25 Dec 2023 15:37:08 -0500 Subject: [PATCH 17/21] Update requirements.txt --- requirements.txt | 22 +++++++++++++++++----- 1 file changed, 17 insertions(+), 5 deletions(-) diff --git a/requirements.txt b/requirements.txt index b8d27fe..070ef98 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,5 +1,17 @@ -colorama==0.4.4 -pygame==2.2.0 -yt-dlp==2023.3.4 -dearpygui==1.9.0 - +Brotli==1.1.0 +certifi==2023.11.17 +charset-normalizer==3.3.2 +colorama==0.4.6 +idna==3.6 +mutagen==1.47.0 +pycryptodomex==3.19.0 +pygame==2.5.2 +PyQt5-Qt5==5.15.2 +PyQt5-sip==12.13.0 +PyQt6==6.6.1 +PyQt6-Qt6==6.6.1 +PyQt6-sip==13.6.0 +requests==2.31.0 +urllib3==2.1.0 +websockets==12.0 +yt-dlp==2023.11.16 From f589197d33c942f04bf1c90465abbc8b4f4129a6 Mon Sep 17 00:00:00 2001 From: BnDLett <73402249+BnDLett@users.noreply.github.com> Date: Tue, 26 Dec 2023 14:34:12 -0500 Subject: [PATCH 18/21] Clarity and syntax changes Improved clarity and fixed syntax mistakes. --- README.md | 45 ++++++++++++++++++++++----------------------- 1 file changed, 22 insertions(+), 23 deletions(-) diff --git a/README.md b/README.md index bf316a0..ca6d37f 100644 --- a/README.md +++ b/README.md @@ -1,42 +1,41 @@ # PyMedia -A Python CLI/GUI based media player.
-If you have any suggestions on how to improve the code or repeatedly run in to an error then please open an issue.

-This is the dev branch, do expect bugs and debug information being printed out. +A Python based CLI and GUI media player.
+Feel free to open an issue for bugs, glitches, or suggestions.

+This is a dev branch, you may run into bugs. # Already known issues 1. ValueError upon trying to use any command directly after queue is empty. (Actual cause is not 100% known) 2. `exit` command doesn't exit fully when playing audio and will need to press ctrl+c in order to fully exit. (?) # How to install -

Binaries

-1. Go to https://github.com/Weebed-Coder/PyMedia/releases/tag/v1.2.1a
-2. Download the binary for your appropriate platform
-3. (Optional) Move the binary to an appropriate folder
+### Binaries +1. Go to PyMedia v1.3.0a pre-release (or whatever version suits you best). +2. Download the binary for your platform. +3. (Recommended) Move the binary to an appropriate folder. 4. Run the binary. -

Installation from source

-

Windows

-1. Install 7zip if you haven't already, the install file is actively available in the repo files.
-2. Use 7zip to unzip `ffmpeg.7z`. This file is important as it is required in YT-DLP -

Ubuntu

-1. Open your terminal application with ctrl+alt+t
+## Installation from source +### Windows +1. Install 7zip if you haven't already, the install file is actively available in the repo files. +2. Use 7zip to unzip `ffmpeg.7z`. This file is required for YT-DLP. +### Ubuntu +1. Open your terminal application with ctrl+alt+t. 2. Run `sudo apt install ffmpeg` -

Final

-(Follow the next steps only if you've downloaded/cloned the source code)
-3. cd to project directory
+### Final +(Follow the next steps only if you've downloaded/cloned the source code) +3. cd to project directory in the terminal. 4. Run `python3 -m pip install -r requirements.txt` +5. Run `python3 main.py` # How to use -1. Get a youtube or soundcloud link, or generally anything youtube-dlp supports. +1. Get a link that YT-DLP supports (YouTube, TikTok, Soundcloud, etc.). 2. Run `main.py` -3. Choose if you want to use gui with `--nogui` or `-ng`. Or, leave it blank to default to gui.
-4. (1/2) If you're using CLI, type in "play " and then paste in your link. (Example: `start https://youtu.be/dQw4w9WgXcQ`)
-(2/2) If you're using GUI, type in your link in to the text box with "URL" next to it then press "confirm".
- -Quick note: You can now use a search term such as "Rick Astley - Never Gonna Give You Up" on both CLI and GUI. +3. Choose if you want to use CLI with `--nogui` or `-ng`. You can also leave it blank to default to the GUI. +4. (1/2) CLI: Type in `[either start or play] [link or search term]`. (Example: `start https://youtu.be/dQw4w9WgXcQ`)
+ (2/2) GUI: Enter the link or search term into the text box and press "enter." # Media controls -`play`: Loads an audio file from either local files or a youtube link. (Aliases: `start`)
+`play`: Loads an audio file from either local files (not yet supported), a search term, or a supported link. (Aliases: `start`)
`pause`: Pauses the current audio.
`unpause`: Resume the current audio. (Aliases: `resume`, `unpause`, `continue`)
`stop`: Stops playing the current soundtrack and moves on to the next one in queue.
From 0a82f64b4462b510da8620c6b10680e5579e6cf2 Mon Sep 17 00:00:00 2001 From: BnDLett <73402249+BnDLett@users.noreply.github.com> Date: Fri, 9 Feb 2024 23:03:40 -0500 Subject: [PATCH 19/21] Updated to v1.5.0a - Added support for previous button - Temporarily removed file cache removal feature as it is currently incompatible with the current queuing system Found 1 bug. --- letts_utils/__init__.py | 1 + .../__pycache__/__init__.cpython-312.pyc | Bin 0 -> 158 bytes letts_utils/__pycache__/queue.cpython-312.pyc | Bin 0 -> 3931 bytes letts_utils/queue.py | 84 +++ main.py | 562 +++++++++--------- 5 files changed, 376 insertions(+), 271 deletions(-) create mode 100644 letts_utils/__init__.py create mode 100644 letts_utils/__pycache__/__init__.cpython-312.pyc create mode 100644 letts_utils/__pycache__/queue.cpython-312.pyc create mode 100644 letts_utils/queue.py diff --git a/letts_utils/__init__.py b/letts_utils/__init__.py new file mode 100644 index 0000000..a658258 --- /dev/null +++ b/letts_utils/__init__.py @@ -0,0 +1 @@ +# Wow. Such empty. diff --git a/letts_utils/__pycache__/__init__.cpython-312.pyc b/letts_utils/__pycache__/__init__.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..59de925258f3249307a9478771d7b2849cc81139 GIT binary patch literal 158 zcmX@j%ge<81Xj0?rHTOQ#~=7&-6`o!0QWSqgQKD=)P~?dnN1gf77GE^hlwd-)d=Q&d#GxYs657(&XxWh)qNPv6>cuCMrLVqOar=UMtm2)GW`tu5_u-Z3q3g0v>R2vEi176M3% zpkO}&f9C}en!2l=JlB9202SeJ0M0iZ4`(N|5&V0Y=299`m&JUHn`GE~Z!b*`5H)#!*k z{dfgH&p37MfF*R+^dcnP(IpV-6qR5n6vws8oIw^;9LO$EaK5*f@8&$64G#!u6do!( z)7NNmPT^Sf`7jVKUSvzI4`MH>m&?pDauFW`VqWz(L_dQgd@Q7vnX92IG;~F6)JMb- z<_O3;SAndP_7KU8ZKfWjw)EyOZKz+Q$6BM~jT?W9B_E{jr}kqL&Dg}&t-aX!7I4z{ z)BCZ>W^8h+vKPD1N@p8MA5oH7=nXbv7RQ}G0XYdobyK8 z-UO&A{eifOJ01Jrb_^hPF9KTKJG4it?|mrOBi-9aBESBLLaLFRqMwJQ!T^Ox3j=-< z+CqU75b-~tukVum(AXd8W}Z%$?208oyKK`%y>J_;IehLA(qCmxZAlOLZ_a#6q}ccX66ng)n^1#97Zv?7@f=HZ!#iucE8#Va?w^Hw zM%17&u&jIMV33a7Ica|*&9(gkRU7kTbBz5u>Y=izHG`?%s`4y$-uG*euOQ{u4N*JZ zhJo;F@2n7kJCdySJ{JORH}4GzU&P{?cq{a62SgGVovu zbp)ax%N#FK;gZLa0I!2b3Sg=V&04~@UJMWsoe3adbDRukAJp&Hx32we=C?Du>5Dsh zGyU4W;U0_7nY@$n5KDgO)J?(rHK8c2I$g@!3?}}47;FdK0L9^aD0DxO3`#& zPch7b>#1&~T!gygxLm}|QxdWmBRlV}i?GR|%LqN8be_-GD9i9o@ zRMO$WtL0Qry-@W4*Ib0y_GN=2d zP|F;hfo}0#D%>Lc`Udhu$j~3(#eV9CI424thf!K~nlJrmcx+GmWAV}MDGEtI^ z+SACHzh_Q8J^jvZ=IZ(fUnql(i~I3&&G@ any: + """ + Gets the next item (index 0) from the queue. + :param remove: Whether to remove the value from the queue and move it to the previous values queue. + :return: Next item in the queue + """ + + value = self.in_queue[0] + if not remove: + return value + + self.in_queue.remove(value) + self.left_queue.append(value) + return value + + def get_previous_items(self, remove: bool = True, i: int = -1) -> any: + """ + Gets the previous item (index 0) from the previous values queue. + :param remove: Whether to remove the value from the previous values queue and move it to the queue. + :param i: Amount to go back inside of the queue. + :return: Previous item in the queue + """ + + value = self.left_queue[i] + if not remove: + return value + + for x in range(-i): + value = self.left_queue.pop(-1) + self.in_queue.insert(0, value) + + def append_to_queue(self, value) -> None: + """ + Appends to the end of the queue. + :param value: The value to append to the end of the queue. + :return: Nothing. + """ + + self.in_queue.append(value) + + def empty(self) -> bool: + """ + :return: If the audio queue is empty or not + """ + + return self.__len__(self.in_queue) == 0 + + def empty_previous(self) -> bool: + """ + :return: If the previous audio queue is empty or not + """ + + return self.__len__(self.in_queue) == 0 + + +if __name__ == "__main__": + # Testing initialization and getting next item without removing it. + test_queue = Queue(["Lorem", "ipsum,", "dolor", "sit", "amet"]) + print(test_queue.get_next_item(False)) + print(test_queue.in_queue) + + # Testing getting the next item with removing it and retrieving it again from the previous queue + test_queue.get_next_item() + test_queue.get_next_item() + print(test_queue.get_previous_items(True, -2)) + print(test_queue.in_queue) + print(test_queue.left_queue) diff --git a/main.py b/main.py index 4214ab7..302166c 100644 --- a/main.py +++ b/main.py @@ -1,271 +1,291 @@ -import sys - -import yt_dlp, threading, queue, colorama, os, time, argparse -from PyQt6.QtWidgets import * - -from pygame import mixer - -mixer.init() - -version = "1.3.0a" - -ydl_opts = { - 'format': 'bestaudio/best', - 'quiet': True, - 'outtmpl': "cache/%(title)s-%(id)s.%(ext)s", - "paths": {'mp3': 'cache', 'webm': 'cache'}, - 'postprocessors': [{ - 'key': 'FFmpegExtractAudio', - 'preferredcodec': 'mp3', - 'preferredquality': '192', - }], -} - -help_info = """ -Commands: - play - Play an audio by link or search term. - Usage: play [link] - Aliases: start, queue - pause - Pause the currently playing audio. - Usage: pause - resume - Resumes the paused audio. Has no effect if audio isn't paused. - Usage: resume - Aliases: unpause, continue - exit - Exits the program. - Usage: exit - Aliases: leave, quit - stop - Stops the currently playing audio. - Usage: stop - -Flags: - -nocache - Will delete the .wav file once it is done playing, this is useful for when you're minimizing disk space usage. - Usage: play [link] -nocache - Aliases: -n -""" # I understand this may be bad practice however for now I'll keep it this way, it simplifies the program. - -global audio_process -global audio_queue -global audio_thread -audio_process = mixer.Channel -audio_thread = False -audio_queue = queue.Queue() - - -def download_audio(link: str): - """ - Downloads audio using the YT-DLP library. - :param link: The link to the audio to be downloaded. - :return: - """ - print("Loading audio... this may take a moment.") - - with yt_dlp.YoutubeDL(ydl_opts) as ydl: - if link.startswith("https://") == False and link.startswith("www.") == False: - funny_dict = ydl.extract_info(f"ytsearch:{link}", download=False)['entries'][0]['id'] - link = f"https://youtube.com/watch?v={funny_dict}" - elif link.startswith("http://"): - print("Visiting http websites is unsafe, please refrain from visiting websites using http and not https as " - "they're insecure.") - - info = ydl.extract_info(link, False) - info = ydl.prepare_filename(info) - - print(info) - - if info.endswith(".webm"): - info = info.replace(".webm", ".mp3") - else: - print(1) - info = info.replace(info[-4:], ".mp3") - - print(info) - - if os.path.exists(info): # This is for caches (if any) - return info - ydl.download([link]) - - return info - - -def start_audio(): - """ - Starts the audio thread and deletes audio cache if specified. - :return: - """ - - global audio_thread - while True: - if audio_queue.empty(): - audio_thread = False - return - - try: - file = audio_queue.get() - except Exception as exc: - print(f"{colorama.Fore.RED}An error has occured:\n{exc}") - pass - - global audio_process - global paused - # audio_wavobj = simpleaudio.WaveObject.from_wave_file(file[0]) - # audio_process = audio_wavobj.play() - # audio_process.wait_done() - tada = mixer.Sound(file[0]) - audio_process = tada.play() - paused = False - while audio_process.get_busy(): - time.sleep(0.1) - if file[1]: os.remove(f"{file[0]}") - - -def attempt_clear(): - """ - Attempts to clear the terminal. Does not work for macOS. - :return: - """ - try: - os.system("clear") - except Exception as hi_neko: # This is not tested on a Windows machine yet. - os.system("cls") - - -def prepare_and_play(link: str = None, del_cache: bool = True): - """ - Prepares and plays audio using YT-DLP and PyGame Mixer. - :param link: The link for the audio to be downloaded. - :param del_cache: If audio cache should be deleted or not. - :return: - """ - - print(link) - global audio_thread - file_loc = download_audio(link) - audio_queue.put([file_loc, del_cache]) - if not audio_thread: - audio_thread = threading.Thread(target=start_audio, name="PyMedia Audio Player") - audio_thread.start() - - -def user_interface(): - """ - CLI user interface - :return: - """ - - while True: - clear = True - del_cache = False - user_input = input("Please enter a command. Use help for a list of commands.\n> ").split(" ") - - try: - if user_input[2] in ["-nocache", "-n"]: - del_cache = True - except: - pass - - if user_input[0].lower() in ["help", "-?"]: - attempt_clear() - clear = False - print(help_info) - - elif user_input[0].lower() in ["play", "start", "queue"]: - prepare_and_play(user_input=user_input[1], del_cache=del_cache) - - elif user_input[0].lower() in ["pause"]: - audio_process.pause() - - elif user_input[0].lower() in ["continue", "resume", "unpause"]: - audio_process.unpause() - - elif user_input[0].lower() in ["stop"]: - audio_process.stop() - - elif user_input[0].lower() in ["quit", "exit", "leave"]: - exit() - - if clear: - attempt_clear() - - -class GUIInterface(QMainWindow): - """ - GUI interface - :return: - """ - - def __init__(self, version): - super().__init__() - - global audio_process - global paused - paused = False - - self.__audio_process__ = audio_process - self.__paused__ = paused - self.__version__ = version - self.spacing = 5 - - self.setWindowTitle(f"PyMedia {self.__version__}") - self.setGeometry(0, 0, 600, 200) - - self.link = QLineEdit("", self) - self.link.setGeometry(5, 5, 325, 25) - - enter = QPushButton("Enter", self) - enter.clicked.connect(self.run_audio) - enter.setGeometry((self.link.x() + self.link.width()) + self.spacing, self.link.y(), 50, self.link.height()) - - previous_btn = QPushButton("Previous", self) - previous_btn.setGeometry(self.link.x(), (self.link.y() + self.link.height()) + self.spacing, 70, - self.link.height()) - - self.pause_play = QPushButton("Pause/Play", self) - self.pause_play.clicked.connect(self.change_pause_state) - self.pause_play.setGeometry((previous_btn.x() + previous_btn.width()) + self.spacing, previous_btn.y(), - previous_btn.width(), previous_btn.height()) - - self.skip_btn = QPushButton("Skip", self) - self.skip_btn.clicked.connect(lambda: audio_process.stop()) - self.skip_btn.setGeometry((self.pause_play.x() + self.pause_play.width()) + self.spacing, self.pause_play.y(), - self.pause_play.width(), self.pause_play.height()) - - previous_btn.setDisabled(True) - self.pause_play.setDisabled(audio_queue.empty()) - self.skip_btn.setDisabled(audio_queue.empty()) - - def change_pause_state(self): - if not self.__paused__: - audio_process.pause() - self.__paused__ = True - else: - audio_process.unpause() - self.__paused__ = False - - def run_audio(self): - """ - Runs the audio. - :return: - """ - import threading # Ensures threading is imported - thread = threading.Thread(target=prepare_and_play, args=(self.link.text(),)) - thread.start() - self.pause_play.setDisabled(False) - self.skip_btn.setDisabled(False) - - -if __name__ == "__main__": - parser = argparse.ArgumentParser() - parser.add_argument("-g", "--gui", help="Change if the app boots with or without GUI.", type=bool) - - try: - args = parser.parse_args() - if args.gui: user_interface() - - App = QApplication(sys.argv) - window = GUIInterface(version) - window.show() - os._exit(App.exec()) - except Exception as e: - print( - f"{colorama.Fore.RED}An error has occurred, if this error continues to occur then open an issue in the " - f"project github.\n\nError:\n{e}{colorama.Fore.RESET}") +import sys + +import argparse +import colorama +import os +import threading +import time +import yt_dlp +from PyQt6.QtWidgets import * +from pygame import mixer + +from letts_utils import queue + +mixer.init() + +version = "1.4.0a" + +colorama.init(True) +print(f"{colorama.Back.YELLOW}Halt! This is a dev release, expect bugs and incomplete features. Current version is " + f"{version}.") + +ydl_opts = { + 'format': 'bestaudio/best', + 'quiet': True, + 'outtmpl': "cache/%(title)s-%(id)s.%(ext)s", + "paths": {'mp3': 'cache', 'webm': 'cache'}, + 'postprocessors': [{ + 'key': 'FFmpegExtractAudio', + 'preferredcodec': 'mp3', + 'preferredquality': '192', + }], +} + +help_info = """ +Commands: + play - Play an audio by link or search term. + Usage: play [link] + Aliases: start, queue + pause - Pause the currently playing audio. + Usage: pause + resume - Resumes the paused audio. Has no effect if audio isn't paused. + Usage: resume + Aliases: unpause, continue + exit - Exits the program. + Usage: exit + Aliases: leave, quit + stop - Stops the currently playing audio. + Usage: stop + +Flags: + -nocache - Will delete the .wav file once it is done playing, this is useful for when you're minimizing disk space usage. + Usage: play [link] -nocache + Aliases: -n +""" # I understand this may be bad practice however for now I'll keep it this way, it simplifies the program. + +global audio_process +global audio_queue +global audio_thread +audio_process = mixer.Channel +audio_thread = False +audio_queue = queue.Queue() + + +def download_audio(link: str): + """ + Downloads audio using the YT-DLP library. + :param link: The link to the audio to be downloaded. + :return: + """ + print("Loading audio... this may take a moment.") + + with yt_dlp.YoutubeDL(ydl_opts) as ydl: + if link.startswith("https://") == False and link.startswith("www.") == False: + funny_dict = ydl.extract_info(f"ytsearch:{link}", download=False)['entries'][0]['id'] + link = f"https://youtube.com/watch?v={funny_dict}" + elif link.startswith("http://"): + print("Visiting http websites is unsafe, please refrain from visiting websites using http and not https as " + "they're insecure.") + + info = ydl.extract_info(link, False) + info = ydl.prepare_filename(info) + + print(info) + + if info.endswith(".webm"): + info = info.replace(".webm", ".mp3") + else: + print(1) + info = info.replace(info[-4:], ".mp3") + + print(info) + + if os.path.exists(info): # This is for caches (if any) + return info + ydl.download([link]) + + return info + + +def start_audio(): + """ + Starts the audio thread and deletes audio cache if specified. + :return: + """ + + global audio_thread, file + while True: + if audio_queue.empty(): + audio_thread = False + return + + try: + file = audio_queue.get_next_item() + except Exception as exc: + print(f"{colorama.Fore.RED}An error has occured:\n{exc}") + pass + + global audio_process + global paused + # audio_wavobj = simpleaudio.WaveObject.from_wave_file(file[0]) + # audio_process = audio_wavobj.play() + # audio_process.wait_done() + tada = mixer.Sound(file[0]) + audio_process = tada.play() + paused = False + while audio_process.get_busy(): + time.sleep(0.1) + # if file[1]: + # os.remove(f"{file[0]}") + # TODO: Reimplement this in a manner that supports queuing. This will be implemented in v1.5.0a + + +def attempt_clear(): + """ + Attempts to clear the terminal. Does not work for macOS. + :return: + """ + try: + os.system("clear") + except Exception as hi_neko: # This is not tested on a Windows machine yet. + os.system("cls") + + +def prepare_and_play(link: str = None, del_cache: bool = True): + """ + Prepares and plays audio using YT-DLP and PyGame Mixer. + :param link: The link for the audio to be downloaded. + :param del_cache: If audio cache should be deleted or not. + :return: + """ + + print(link) + global audio_thread + file_loc = download_audio(link) + audio_queue.append_to_queue([file_loc, del_cache]) + if not audio_thread: + audio_thread = threading.Thread(target=start_audio, name="PyMedia Audio Player") + audio_thread.start() + + +def user_interface(): + """ + CLI user interface + :return: + """ + + while True: + clear = True + del_cache = False + user_input = input("Please enter a command. Use help for a list of commands.\n> ").split(" ") + + try: + if user_input[2] in ["-nocache", "-n"]: + del_cache = True + except: + pass + + if user_input[0].lower() in ["help", "-?"]: + attempt_clear() + clear = False + print(help_info) + + elif user_input[0].lower() in ["play", "start", "queue"]: + prepare_and_play(link=user_input[1], del_cache=del_cache) + + elif user_input[0].lower() in ["pause"]: + audio_process.pause() + + elif user_input[0].lower() in ["continue", "resume", "unpause"]: + audio_process.unpause() + + elif user_input[0].lower() in ["stop"]: + audio_process.stop() + + elif user_input[0].lower() in ["quit", "exit", "leave"]: + exit() + + if clear: + attempt_clear() + + +def previous_in_queue(): + audio_process.stop() + audio_queue.get_previous_items(True) + + +class GUIInterface(QMainWindow): + """ + GUI interface + :return: + """ + + def __init__(self, program_version): + super().__init__() + + global audio_process + global paused + paused = False + + self.__audio_process__ = audio_process + self.__paused__ = paused + self.__version__ = program_version + self.spacing = 5 + + self.setWindowTitle(f"PyMedia {self.__version__}") + self.setGeometry(0, 0, 600, 200) + + self.link = QLineEdit("", self) + self.link.setGeometry(5, 5, 325, 25) + + enter = QPushButton("Enter", self) + enter.clicked.connect(self.run_audio) + enter.setGeometry((self.link.x() + self.link.width()) + self.spacing, self.link.y(), 50, self.link.height()) + + self.previous_btn = QPushButton("Previous", self) + self.previous_btn.clicked.connect(previous_in_queue) + self.previous_btn.setGeometry(self.link.x(), (self.link.y() + self.link.height()) + self.spacing, 70, + self.link.height()) + + self.pause_play = QPushButton("Pause/Play", self) + self.pause_play.clicked.connect(self.change_pause_state) + self.pause_play.setGeometry((self.previous_btn.x() + self.previous_btn.width()) + self.spacing, + self.previous_btn.y(), + self.previous_btn.width(), self.previous_btn.height()) + + self.skip_btn = QPushButton("Skip", self) + self.skip_btn.clicked.connect(lambda: audio_process.stop()) + self.skip_btn.setGeometry((self.pause_play.x() + self.pause_play.width()) + self.spacing, self.pause_play.y(), + self.pause_play.width(), self.pause_play.height()) + + self.pause_play.setDisabled(audio_queue.empty()) + self.skip_btn.setDisabled(audio_queue.empty()) + + def change_pause_state(self): + if not self.__paused__: + audio_process.pause() + self.__paused__ = True + return + + audio_process.unpause() + self.__paused__ = False + + def run_audio(self): + """ + Runs the audio. + :return: + """ + import threading # Ensures threading is imported + thread = threading.Thread(target=prepare_and_play, args=(self.link.text(),)) + thread.start() + self.pause_play.setDisabled(False) + self.skip_btn.setDisabled(False) + + +if __name__ == "__main__": + parser = argparse.ArgumentParser() + parser.add_argument("-g", "--gui", help="Change if the app boots with or without GUI.", type=bool) + + try: + args = parser.parse_args() + if args.gui: user_interface() + + App = QApplication(sys.argv) + window = GUIInterface(version) + window.show() + os._exit(App.exec()) + except Exception as e: + print( + f"{colorama.Fore.RED}An error has occurred, if this error continues to occur then open an issue in the " + f"project github.\n\nError:\n{e}{colorama.Fore.RESET}") + raise e From 88e6539fc4b9f7187a9232ef63d52b2c4e707d6e Mon Sep 17 00:00:00 2001 From: BnDLett <73402249+BnDLett@users.noreply.github.com> Date: Sat, 10 Feb 2024 13:49:02 -0500 Subject: [PATCH 20/21] v1.5.0a General changes: - Reimplemented cache clearing to support the new queuing system - Disables previous button upon initial startup. Feature additions: - Added a check box for deleting cache. Bug fixes: - Fixed the exit command for the command line interface. - Fixed a bug that caused the audio queue to not show up as empty when it is empty. --- letts_utils/__pycache__/queue.cpython-312.pyc | Bin 3931 -> 4591 bytes letts_utils/queue.py | 23 +++++-- main.py | 58 +++++++++++++----- 3 files changed, 62 insertions(+), 19 deletions(-) diff --git a/letts_utils/__pycache__/queue.cpython-312.pyc b/letts_utils/__pycache__/queue.cpython-312.pyc index aa1658f1d21d46db742baf40640a5f73a192a252..59c23afda3c60a36d628f235acf4f84178b9bc4e 100644 GIT binary patch delta 1586 zcmZ8h%}*Og6rb5I*536eUSk}9ft$|aW`dI?IZI@?5T{(>4&scKJsvo?fwqkz{}c)Z5v*tTex%>%--Q?P>eq1GL=>elL1}^Dv^bB_5_~d=5s@lHSZ+gT zP9V~noNgmBRjA4(G~m-`;s7y_)>*vAdQj^qJw>m zbdvKS-$4h5FsR~SrbDm@sM~>8fR6&-u_TxYH!A#n#tY@FoRiDsXFe*?63q&{p4Q^L z2;adu*ASCq^Qeqw@S$=E-NsX<;Hf#Q>FNdZbk1q=AeU^KpJNi3yIFCzF9}Qq)wS*i zno&>nRf zB8^d5aMRl57kOO(1qZQNid$)_~MPooJs1Zc*#Aa$ERU_LA zTMG|kgLm*=>_Sy}tVN&bvGxAV!HvP1v%Rvl@-RO1NFQ!B$Hm%v?z{R#pPz#8JOvZ` z<=cEHc$0t7P5CVz&wZ@DfOYUYfv4Tylw@iJm+=i=@&8sC-H_a?I5knm*I|;DKMwlX z1G!XKn9W(#Hf9UX2S$<6#ay9e8ySlj*%D*aat!~0_!35)60>Y0=eX-?YW0{=eyL@a zZQe(Y0swH$DWf+Xm@*&JRzE$>DL98O@Tjklx)+$I&Wx{Ij=as^=kSqW3iS2>kjfQ~ zbXRh-uu-ULb+!E$wP#Q5sf})rZ;d}v&3dHgf!f3PY_uNGv?)w8{vaWKJ8V-XzG8N| zYG`=XA2mNuDW9_)rh(d%NG|JmvQsMNVYWf)x5SfX3x&KVW~?RF&CNs@4`u+zPH}RE zlXIL50=bQv8SwulJ8>RceA(yNV^nUi^)UAfiRVQx0nEOeL_UaduNoEwqOs zxd_4__28i*h{cQISr8O2Ui^VrS1W zad{87MVcpsH_3Q%!K!3Hlqgk6fz-D=lyYPPwxHROZ!OmkG9S@vMEyK^N5#vwa zgn<&*7uqI#-2rvZ+&bJM%XHkQak5swe4{Cv!Z%_TieNJKYV}%Fs|IVdS~9Qp_d?!0 zS#kT{yTz4lgY9!G{X=bb$5SOXbZh{EizK43=`~BAuG^Hfh_rx&bId^0eM3UOM5X~k z)Ke_Vl)B5d_Vyr?0+=V?lWzO;9Z$M1-m*vZpbezV0>n-rCZjb`v0ZI6M4kKQT0tGR zrmI0ygxF(I7k*&xrg`^f#nct%0;|fbpmNUVL9KQ(FdMC6?*CSqyO)ltvb&wS z>V5i>veL&6p{Qj;;smsgWvb5)afUD+z}OPk8&PY$SpLfiHS{TKQ*CwLa zikgi81{OwAtBu*!si`0~tM!)b!=KGsJ(Alow}dc+FbuG~elqqV@p?31f8i{^0$DYf zmFzeqn^*g?iBgxD)~=32t-=ypiXOU8#E0VkXA>Wa2f75vRa;%jmIpd{%PQZEx&*dP z24IQ(LR-4(W%ksZ50zsl65+;^u^JNfp;A5PHKn@MG34}cP~zX@;p any: """ @@ -55,12 +58,15 @@ def append_to_queue(self, value) -> None: self.in_queue.append(value) - def empty(self) -> bool: + def empty(self, debug: bool = False) -> bool: """ :return: If the audio queue is empty or not """ - return self.__len__(self.in_queue) == 0 + result = self.__len__(self.in_queue) == 0 + if debug: + print(self.__len__(self.in_queue)) + return result def empty_previous(self) -> bool: """ @@ -69,6 +75,15 @@ def empty_previous(self) -> bool: return self.__len__(self.in_queue) == 0 + def get_total(self) -> list: + """ + Combines both previous and current queue and returns it. + :return: Previous and currently queue combined + """ + total_queue = self.left_queue + total_queue.extend(self.in_queue) + return total_queue + if __name__ == "__main__": # Testing initialization and getting next item without removing it. diff --git a/main.py b/main.py index 302166c..e0bf541 100644 --- a/main.py +++ b/main.py @@ -1,3 +1,4 @@ +import platform import sys import argparse @@ -13,7 +14,7 @@ mixer.init() -version = "1.4.0a" +version = "1.5.0a" colorama.init(True) print(f"{colorama.Back.YELLOW}Halt! This is a dev release, expect bugs and incomplete features. Current version is " @@ -56,9 +57,12 @@ global audio_process global audio_queue global audio_thread +global paused +global file audio_process = mixer.Channel -audio_thread = False audio_queue = queue.Queue() +audio_thread = False +paused = False def download_audio(link: str): @@ -117,9 +121,6 @@ def start_audio(): global audio_process global paused - # audio_wavobj = simpleaudio.WaveObject.from_wave_file(file[0]) - # audio_process = audio_wavobj.play() - # audio_process.wait_done() tada = mixer.Sound(file[0]) audio_process = tada.play() paused = False @@ -133,12 +134,22 @@ def start_audio(): def attempt_clear(): """ Attempts to clear the terminal. Does not work for macOS. - :return: + :return: Nothing """ - try: + + if platform.system() == "Linux": os.system("clear") - except Exception as hi_neko: # This is not tested on a Windows machine yet. - os.system("cls") + return + os.system("cls") + + +def empty_cache(): + result = audio_queue.get_total() + for file in result: + if not file[1]: + continue + + os.remove(f"{file[0]}") def prepare_and_play(link: str = None, del_cache: bool = True): @@ -193,7 +204,8 @@ def user_interface(): audio_process.stop() elif user_input[0].lower() in ["quit", "exit", "leave"]: - exit() + empty_cache() + os._exit(0) if clear: attempt_clear() @@ -232,6 +244,10 @@ def __init__(self, program_version): enter.clicked.connect(self.run_audio) enter.setGeometry((self.link.x() + self.link.width()) + self.spacing, self.link.y(), 50, self.link.height()) + self.delete_cache = QCheckBox("Delete cache", self) + self.delete_cache.setGeometry((enter.x() + enter.width()) + self.spacing, enter.y(), enter.width()+2, + enter.height()) + self.previous_btn = QPushButton("Previous", self) self.previous_btn.clicked.connect(previous_in_queue) self.previous_btn.setGeometry(self.link.x(), (self.link.y() + self.link.height()) + self.spacing, 70, @@ -248,8 +264,10 @@ def __init__(self, program_version): self.skip_btn.setGeometry((self.pause_play.x() + self.pause_play.width()) + self.spacing, self.pause_play.y(), self.pause_play.width(), self.pause_play.height()) - self.pause_play.setDisabled(audio_queue.empty()) - self.skip_btn.setDisabled(audio_queue.empty()) + disabled = audio_queue.empty() + self.previous_btn.setDisabled(disabled) + self.pause_play.setDisabled(disabled) + self.skip_btn.setDisabled(disabled) def change_pause_state(self): if not self.__paused__: @@ -266,8 +284,10 @@ def run_audio(self): :return: """ import threading # Ensures threading is imported - thread = threading.Thread(target=prepare_and_play, args=(self.link.text(),)) + thread = threading.Thread(target=prepare_and_play, args=(self.link.text(), self.delete_cache.isChecked())) thread.start() + + self.previous_btn.setDisabled(False) self.pause_play.setDisabled(False) self.skip_btn.setDisabled(False) @@ -278,14 +298,22 @@ def run_audio(self): try: args = parser.parse_args() - if args.gui: user_interface() + if args.gui: + user_interface() App = QApplication(sys.argv) window = GUIInterface(version) window.show() - os._exit(App.exec()) + + exit_code = App.exec() + empty_cache() + os._exit(exit_code) + except Exception as e: print( f"{colorama.Fore.RED}An error has occurred, if this error continues to occur then open an issue in the " f"project github.\n\nError:\n{e}{colorama.Fore.RESET}") raise e + + finally: + empty_cache() From 422d874ccedb65346092321a053e9659c76f52fb Mon Sep 17 00:00:00 2001 From: BnDLett <73402249+BnDLett@users.noreply.github.com> Date: Sat, 10 Feb 2024 13:51:13 -0500 Subject: [PATCH 21/21] v1.5.1b Fixed the version variable to display as beta and not alpha. --- main.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/main.py b/main.py index e0bf541..f59ada3 100644 --- a/main.py +++ b/main.py @@ -14,7 +14,7 @@ mixer.init() -version = "1.5.0a" +version = "1.5.1b" colorama.init(True) print(f"{colorama.Back.YELLOW}Halt! This is a dev release, expect bugs and incomplete features. Current version is "