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 "