diff --git a/README.md b/README.md index f6daa92..28c102d 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,9 @@ -# Celaigia - -ascolti lo stesso pezzo a ripetizione su youtube? provi antipatia per gesu? usa celaigia! - -celaigia è un player musicale scritto con python (pygame & dearpygui mostly). -Oltre alle classiche funzionalità audio player, con celaigia puoi cercare e scaricare le tue canzoncine preferite direttamente da youtube (yt-dlp). +Welcome to celaigia +an app to gently query youtube music, and possibly download only if you don't have already locally +# TODO +- explore the db +- nicer ui +- contenerize +- mobile diff --git a/__init__.py b/__init__.py new file mode 100644 index 0000000..94cbff7 --- /dev/null +++ b/__init__.py @@ -0,0 +1,12 @@ +import importlib +import config +importlib.reload(config) +import utils +importlib.reload(utils) +import database +importlib.reload(database) +import ui +importlib.reload(ui) +import app +importlib.reload(app) + diff --git a/app.py b/app.py new file mode 100644 index 0000000..a8a509d --- /dev/null +++ b/app.py @@ -0,0 +1,9 @@ +from config import music_path +from ui import UIEngine +from database import Database + +if __name__ == "__main__": + # Add your main code here + db = Database(base_path=music_path) + ui = UIEngine(db) + ui.run() diff --git a/celaigia.py b/celaigia.py deleted file mode 100644 index 8507c3d..0000000 --- a/celaigia.py +++ /dev/null @@ -1,301 +0,0 @@ -import pygame -import threading -import time -import random -import os -import atexit -import ntpath -import json -import webbrowser -import subprocess - -import dearpygui.dearpygui as dpg - -from mutagen.mp3 import MP3 -from tkinter import Tk,filedialog, simpledialog, Button, OptionMenu, N, S , E, W, Label, StringVar -import pytube - -dpg.create_context() -dpg.create_viewport(title="celaigia, stai senza pensieri",large_icon="logo.ico",small_icon="logo.ico") -pygame.mixer.init() - -#https://www.redhat.com/sysadmin/write-GUI-applications-python -global state -state=None - -_SONG_FILE = "data/songs.json" -_NUM_YT_SEARCH_RESULTS = 5 -_DEFAULT_DOWNLOAD_PATH = 'data/music' -_DEFAULT_MUSIC_VOLUME = 0.5 -pygame.mixer.music.set_volume(_DEFAULT_MUSIC_VOLUME) - -def bash(cmd): - subprocess.call(['/bin/bash', '-c', cmd]) - -def string_sanitizer(s: str)->str: - s = ''.join(filter(lambda x: x in '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ ', s)) - return s.replace(' ','_') - -def ismusic(filename: str)->bool: - return filename.split('.')[-1] in ['mp3', 'ogg', 'flac', 'wav'] - -def update_volume(sender, app_data): - pygame.mixer.music.set_volume(app_data / 100.0) - -def load_database(): - songs = json.load(open(_SONG_FILE, "r+"))["songs"] - for filename in songs: - dpg.add_button( - label=f"{ntpath.basename(filename)}", - callback=play, - width=-1, - height=25, - user_data=filename.replace("\\", "/"), - parent="list" - ) - dpg.add_spacer(height=2, parent="list") - - -def update_database(filename: str): - data = json.load(open(_SONG_FILE, "r+")) - if filename not in data["songs"]: - data["songs"] += [filename] - dpg.add_button( - label=f"{ntpath.basename(filename)}", - callback=play, - width=-1, - height=25, - user_data=filename.replace("\\", "/"), - parent="list" - ) - dpg.add_spacer(height=2, parent="list") - json.dump(data, open(_SONG_FILE, "r+"), indent=4) - -def update_slider(): - global state - while pygame.mixer.music.get_busy(): - dpg.configure_item(item="pos",default_value=pygame.mixer.music.get_pos()/1000) - time.sleep(0.7) - state=None - dpg.configure_item("cstate",default_value=f"State: None") - dpg.configure_item("csong",default_value="Now Playing : ") - dpg.configure_item("play",label="Play") - dpg.configure_item(item="pos",max_value=100) - dpg.configure_item(item="pos",default_value=0) - -def play(sender, app_data, user_data): - global state - if user_data: - pygame.mixer.music.load(user_data) - audio = MP3(user_data) - dpg.configure_item(item="pos",max_value=audio.info.length) - pygame.mixer.music.play() - thread=threading.Thread(target=update_slider,daemon=False).start() - if pygame.mixer.music.get_busy(): - dpg.configure_item("play",label="Pause") - state="playing" - dpg.configure_item("cstate",default_value=f"State: Playing") - dpg.configure_item("csong",default_value=f"Now Playing : {ntpath.basename(user_data)}") - -def play_pause(): - global state - if state=="playing": - state="paused" - pygame.mixer.music.pause() - dpg.configure_item("play",label="Play") - dpg.configure_item("cstate",default_value=f"State: Paused") - elif state=="paused": - state="playing" - pygame.mixer.music.unpause() - dpg.configure_item("play",label="Pause") - dpg.configure_item("cstate",default_value=f"State: Playing") - else: - song = json.load(open(_SONG_FILE, "r"))["songs"] - if song: - song=random.choice(song) - pygame.mixer.music.load(song) - pygame.mixer.music.play() - thread=threading.Thread(target=update_slider,daemon=False).start() - dpg.configure_item("play",label="Pause") - if pygame.mixer.music.get_busy(): - audio = MP3(song) - dpg.configure_item(item="pos",max_value=audio.info.length) - state="playing" - dpg.configure_item("csong",default_value=f"Now Playing : {ntpath.basename(song)}") - dpg.configure_item("cstate",default_value=f"State: Playing") - -def stop(): - global state - pygame.mixer.music.stop() - state=None - -def add_files(): - data=json.load(open(_SONG_FILE,"r")) - root=Tk() - root.withdraw() - filename=filedialog.askopenfilename(filetypes=[("Music Files", ("*.mp3","*.wav","*.ogg"))]) - root.quit() - if filename.endswith(".mp3" or ".wav" or ".ogg"): - if filename not in data["songs"]: - update_database(filename) - #dpg.add_button(label=f"{ntpath.basename(filename)}",callback=play,width=-1,height=25,,parent="list") - #dpg.add_spacer(height=2,parent="list") - -def add_folder(): - data=json.load(open(_SONG_FILE,"r")) - root=Tk() - root.withdraw() - folder=filedialog.askdirectory() - root.quit() - for filename in os.listdir(folder): - if filename.endswith(".mp3" or ".wav" or ".ogg"): - if filename not in data["songs"]: - update_database(os.path.join(folder,filename).replace("\\","/")) - #dpg.add_button(label=f"{ntpath.basename(filename)}",callback=play,width=-1,height=25,user_data=os.path.join(folder,filename).replace("\\","/"),parent="list") - #dpg.add_spacer(height=2,parent="list") - -def search(sender, app_data, user_data): - songs = json.load(open(_SONG_FILE, "r"))["songs"] - dpg.delete_item("list", children_only=True) - for index, song in enumerate(songs): - if app_data in song.lower(): - dpg.add_button(label=f"{ntpath.basename(song)}", callback=play,width=-1, height=25, user_data=song, parent="list") - dpg.add_spacer(height=2,parent="list") - -def add_download_choices(sender, app_data, user_data)->None: - yt_urls = {string_sanitizer(r.title): r.watch_url for r in pytube.Search(app_data).results[:_NUM_YT_SEARCH_RESULTS]} - - for url_key in yt_urls: - dpg.add_button(label=url_key, tag=url_key, callback= download ,width=-1, height=25, user_data = (url_key,yt_urls), parent="sidebar") - dpg.add_spacer(height=2,parent="sidebar") - -def download(sender, app_data, user_data): - - url_key, yt_urls = user_data - url = yt_urls[url_key] - for tmp_url_key in yt_urls: - dpg.delete_item(tmp_url_key) - video_file = f'{_DEFAULT_DOWNLOAD_PATH}/{url_key}.mp4' - audio_file = f"{video_file[:-4]}.mp3" - stream = pytube.YouTube(url).streams.filter(only_audio=True).first() - stream.download(filename=video_file, skip_existing=True) - bash(f"ffmpeg -i {video_file} -vn -acodec mp3 -y {audio_file}") - bash(f"rm {video_file}") - update_database(audio_file) - -def removeall(): - songs = json.load(open(_SONG_FILE, "r")) - songs["songs"].clear() - json.dump(songs,open(_SONG_FILE, "w"),indent=4) - dpg.delete_item("list", children_only=True) - load_database() - -with dpg.theme(tag="base"): - with dpg.theme_component(): - dpg.add_theme_color(dpg.mvThemeCol_Button, (130, 142, 250)) - dpg.add_theme_color(dpg.mvThemeCol_ButtonActive, (137, 142, 255, 95)) - dpg.add_theme_color(dpg.mvThemeCol_ButtonHovered, (137, 142, 255)) - dpg.add_theme_style(dpg.mvStyleVar_FrameRounding, 3) - dpg.add_theme_style(dpg.mvStyleVar_ChildRounding, 4) - dpg.add_theme_style(dpg.mvStyleVar_FramePadding, 4, 4) - dpg.add_theme_style(dpg.mvStyleVar_WindowRounding, 4, 4) - dpg.add_theme_style(dpg.mvStyleVar_WindowTitleAlign, 0.50, 0.50) - dpg.add_theme_style(dpg.mvStyleVar_WindowBorderSize,0) - dpg.add_theme_style(dpg.mvStyleVar_WindowPadding,10,14) - dpg.add_theme_color(dpg.mvThemeCol_ChildBg, (25, 25, 25)) - dpg.add_theme_color(dpg.mvThemeCol_Border, (0,0,0,0)) - dpg.add_theme_color(dpg.mvThemeCol_ScrollbarBg, (0,0,0,0)) - dpg.add_theme_color(dpg.mvThemeCol_TitleBgActive, (130, 142, 250)) - dpg.add_theme_color(dpg.mvThemeCol_CheckMark, (221, 166, 185)) - dpg.add_theme_color(dpg.mvThemeCol_FrameBgHovered, (172, 174, 197)) - -with dpg.theme(tag="slider_thin"): - with dpg.theme_component(): - dpg.add_theme_color(dpg.mvThemeCol_FrameBgActive, (130, 142, 250,99)) - dpg.add_theme_color(dpg.mvThemeCol_FrameBgHovered, (130, 142, 250,99)) - dpg.add_theme_color(dpg.mvThemeCol_SliderGrabActive, (255, 255, 255)) - dpg.add_theme_color(dpg.mvThemeCol_SliderGrab, (255, 255, 255)) - dpg.add_theme_color(dpg.mvThemeCol_FrameBg, (130, 142, 250,99)) - dpg.add_theme_style(dpg.mvStyleVar_GrabRounding, 3) - dpg.add_theme_style(dpg.mvStyleVar_GrabMinSize, 30) - -with dpg.theme(tag="slider"): - with dpg.theme_component(): - dpg.add_theme_color(dpg.mvThemeCol_FrameBgActive, (130, 142, 250,99)) - dpg.add_theme_color(dpg.mvThemeCol_FrameBgHovered, (130, 142, 250,99)) - dpg.add_theme_color(dpg.mvThemeCol_SliderGrabActive, (255, 255, 255)) - dpg.add_theme_color(dpg.mvThemeCol_SliderGrab, (255, 255, 255)) - dpg.add_theme_color(dpg.mvThemeCol_FrameBg, (130, 142, 250,99)) - dpg.add_theme_style(dpg.mvStyleVar_GrabRounding, 3) - dpg.add_theme_style(dpg.mvStyleVar_GrabMinSize, 30) - -with dpg.theme(tag="songs"): - with dpg.theme_component(): - dpg.add_theme_style(dpg.mvStyleVar_FrameRounding, 2) - dpg.add_theme_color(dpg.mvThemeCol_Button, (89, 89, 144,40)) - dpg.add_theme_color(dpg.mvThemeCol_ButtonHovered, (0,0,0,0)) - -with dpg.font_registry(): - monobold = dpg.add_font("fonts/MonoLisa-Bold.ttf", 12) - head = dpg.add_font("fonts/MonoLisa-Bold.ttf", 15) - -with dpg.window(tag="main",label="window title"): - with dpg.child_window(autosize_x=True,height=45,no_scrollbar=True): - dpg.add_text(f"Now Playing : ",tag="csong") - dpg.add_spacer(height=2) - - with dpg.group(horizontal=True): - with dpg.child_window(width=250, tag="sidebar"): - dpg.add_text("Celaigia",color=(137, 142, 255)) - dpg.add_text("phuturemachine") - dpg.add_spacer(height=2) - dpg.add_button(label="Support",width=-1,height=23,callback=lambda:webbrowser.open(url="gitgitigitigiti")) - dpg.add_spacer(height=5) - dpg.add_separator() - dpg.add_spacer(height=5) - dpg.add_button(label="Add File",width=-1,height=28,callback=add_files) - dpg.add_button(label="Add Folder",width=-1,height=28,callback=add_folder) - dpg.add_button(label="Remove All Songs",width=-1,height=28,callback=removeall) - dpg.add_spacer(height=5) - dpg.add_separator() - dpg.add_spacer(height=5) - dpg.add_text(f"State: {state}",tag="cstate") - dpg.add_spacer(height=5) - dpg.add_separator() - dpg.add_input_text(hint="search youtube",width=-1,callback=add_download_choices, on_enter=True) - dpg.add_spacer(height=5) - - with dpg.child_window(autosize_x=True,border=False): - with dpg.child_window(autosize_x=True,height=50,no_scrollbar=True): - with dpg.group(horizontal=True): - dpg.add_button(label="Play",width=65,height=30,tag="play",callback=play_pause) - dpg.add_button(label="Stop",callback=stop,width=65,height=30) - dpg.add_slider_float(tag="volume", width=120,height=15,pos=(160,19),format="%.0f%.0%",default_value=_DEFAULT_MUSIC_VOLUME * 100,callback=update_volume) - dpg.add_slider_float(tag="pos",width=-1,pos=(295,19),format="") - - with dpg.child_window(autosize_x=True,delay_search=True): - with dpg.group(horizontal=True,tag="query"): - dpg.add_input_text(hint="search for a song locally",width=-1,callback=search) - dpg.add_spacer(height=5) - with dpg.child_window(autosize_x=True,delay_search=True,tag="list"): - load_database() - - dpg.bind_item_theme("volume","slider_thin") - dpg.bind_item_theme("pos","slider") - dpg.bind_item_theme("list","songs") - -dpg.bind_theme("base") -dpg.bind_font(monobold) - -def safe_exit(): - pygame.mixer.music.stop() - pygame.quit() - -atexit.register(safe_exit) - -dpg.setup_dearpygui() -dpg.show_viewport() -dpg.set_primary_window("main",True) -dpg.maximize_viewport() -dpg.start_dearpygui() -dpg.destroy_context() diff --git a/config.py b/config.py new file mode 100644 index 0000000..931daa1 --- /dev/null +++ b/config.py @@ -0,0 +1,11 @@ +music_path = "./music" + +yt_dlp_opts = { + 'format': 'bestaudio/best', + 'default_search': 'ytsearch5', + 'postprocessors': [{ + 'key': 'FFmpegExtractAudio', + 'preferredcodec': 'mp3', + 'preferredquality': '192', + }], +} \ No newline at end of file diff --git a/data/songs.json b/data/songs.json deleted file mode 100644 index f802461..0000000 --- a/data/songs.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "songs": [ - "data/music/Mac_Miller__Self_Care.mp3", - "data/music/COLLE_DER_FOMENTO__Il_cielo_su_Roma.mp3" - ] -} \ No newline at end of file diff --git a/database.py b/database.py new file mode 100644 index 0000000..0627e0b --- /dev/null +++ b/database.py @@ -0,0 +1,37 @@ +# Updated database.py +import json +import datetime + +class Database: + def __init__(self, base_path): + self.base_path = base_path + self.file_path = f'{base_path}/index.json' + try: + with open(self.file_path, 'r') as file: + self.data = json.load(file) + except FileNotFoundError: + self.data = [] + + def add_entry(self, entry): + entry['timestamp'] = datetime.datetime.now().strftime('%d/%m/%Y') + self.data.append(entry) + with open(self.file_path, 'w') as file: + json.dump(self.data, file, indent=4) + + def downloadable(self, url, title): + idx = url.split('https://www.youtube.com/watch?v=')[-1] + for entry in self.data: + if entry['id'] == idx or entry['title'] == title: + return False + return True + + def get_all_entries(self): + return self.data + + def update_entry(self, entry_id, new_data): + for entry in self.data: + if entry['id'] == entry_id: + entry.update(new_data) + with open(self.file_path, 'w') as file: + json.dump(self.data, file, indent=4) + return diff --git a/fonts/MonoLisa-Bold.ttf b/fonts/MonoLisa-Bold.ttf deleted file mode 100644 index 385908d..0000000 Binary files a/fonts/MonoLisa-Bold.ttf and /dev/null differ diff --git a/logo.ico b/logo.ico deleted file mode 100644 index df62037..0000000 Binary files a/logo.ico and /dev/null differ diff --git a/music/index.json b/music/index.json new file mode 100644 index 0000000..c6e22be --- /dev/null +++ b/music/index.json @@ -0,0 +1,102 @@ +[ + { + "id": "debIyWS6Byc", + "title": "Actin Crazy", + "artist": "action bronson", + "filename": "Action Bronson - Actin Crazy (Official Music Video)", + "tags": [ + "hip hop", + "rap", + "flow" + ], + "timestamp": "31/12/2023" + }, + { + "id": "xEhwlJQMVO8", + "title": "blue chips full album", + "artist": "action bronson", + "filename": "Action Bronson - Blue Chips (Full Album)", + "tags": [ + "rap", + "hip hop", + "" + ], + "timestamp": "31/12/2023" + }, + { + "id": "C3LXkdpHNOQ", + "title": "Waiting Room", + "artist": "Fugazi", + "filename": "Waiting Room", + "tags": [ + "punk", + "hardcore", + "diy", + "fugazi" + ], + "timestamp": "31/12/2023" + }, + { + "id": "zG2TgCuMcjM", + "title": "Death in Midsummer", + "artist": "Deerhunter", + "filename": "Deerhunter - Death in Midsummer (Official Video)", + "tags": [ + "rock" + ], + "timestamp": "31/12/2023" + }, + { + "id": "ZycbeMV6_4s", + "title": "Non Ci Sto", + "artist": "colle der fomento", + "filename": "Non Ci Sto - Colle Der Fomento", + "tags": [ + "hip hop", + "italiano", + "rap" + ], + "timestamp": "31/12/2023" + }, + { + "id": "ukgraQ-xkp4", + "title": "The Court Of The Crimson King", + "artist": "King Crimson", + "filename": "King Crimson - The Court Of The Crimson King", + "tags": [], + "timestamp": "31/12/2023" + }, + { + "id": "FhKJgqxNDD8", + "title": "Starless", + "artist": "King Crimson", + "filename": "King Crimson - Starless", + "tags": [], + "timestamp": "31/12/2023" + }, + { + "id": "PBwAxmrE194", + "title": "C.R.E.A.M.", + "artist": "wu tang clan", + "filename": "Wu-Tang Clan - C.R.E.A.M. (Official HD Video)", + "tags": [ + "hip hop", + "90s", + "rap" + ], + "timestamp": "31/12/2023" + }, + { + "id": "00uHDPrL8cQ", + "title": "Piombo e Fango", + "artist": "colle der fomento", + "filename": "Danno Piombo e Fango", + "tags": [ + "hip hop", + "rap", + "danno", + "italiano" + ], + "timestamp": "31/12/2023" + } +] \ No newline at end of file diff --git a/requirements.txt b/requirements.txt index 5df73e2..b21e42f 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,6 +1 @@ -dearpygui -mutagen -pygame -pytube -tk - +yt-dlp \ No newline at end of file diff --git a/ui.py b/ui.py new file mode 100644 index 0000000..6ed7198 --- /dev/null +++ b/ui.py @@ -0,0 +1,137 @@ +import tkinter as tk +from tkinter import ttk +import tkinter.simpledialog as sd +import time +import os +from utils import search_youtube, download_audio + +class UIEngine: + def __init__(self, db): + self.db = db + self.root = tk.Tk() + self.root.title("celaigia") + self.root.geometry("400x300") # Set a fixed size + + # Create a PanedWindow to split the UI vertically + self.paned_window = tk.PanedWindow(self.root, orient=tk.VERTICAL) + self.paned_window.pack(expand=True, fill='both') + + # Create the top part of the UI + self.top_frame = ttk.Frame(self.paned_window) + self.paned_window.add(self.top_frame) + + self.status_label = tk.Label(self.top_frame, text="Ready") + self.status_label.pack() + + self.search_frame = ttk.Frame(self.top_frame) + self.search_frame.pack() + self.download_frame = ttk.Frame(self.top_frame) + self.download_frame.pack() + + self.search_status_label = tk.Label(self.search_frame, text="Enter search query:") + self.search_status_label.grid(row=0, column=0) + self.search_entry = tk.Entry(self.search_frame) + self.search_entry.grid(row=0, column=1) + self.search_button = tk.Button(self.search_frame, text="Search", command=lambda: self.search_and_display()) + self.search_button.grid(row=0, column=2) + + self.download_status_label = tk.Label(self.download_frame, text="Enter URL to download:") + self.download_status_label.grid(row=0, column=0) + self.download_entry = tk.Entry(self.download_frame) + self.download_entry.grid(row=0,column=1) + self.download_button = tk.Button(self.download_frame, text="Download", command=lambda: self.handle_download(self.download_entry.get())) + self.download_button.grid(row=0, column=2) + + # Create the bottom part of the UI with tabs + self.bottom_frame = ttk.Frame(self.paned_window) + self.paned_window.add(self.bottom_frame) + + self.notebook = ttk.Notebook(self.bottom_frame) + self.notebook.pack(fill='both', expand=True) + + self.create_search_tab() + self.create_database_tab(db) + + def create_search_tab(self): + search_tab = ttk.Frame(self.notebook) + self.notebook.add(search_tab, text='Search&Download') + self.status_label = tk.Label(search_tab, text="Ready") + self.status_label.pack() + + # Create a frame for displaying search results + self.results_frame = ttk.Frame(search_tab) + self.results_frame.pack() + + def create_database_tab(self, db): + database_tab = ttk.Frame(self.notebook) + self.notebook.add(database_tab, text='Database') + + tree = ttk.Treeview(database_tab, columns=("title", "artist", "filename", "tags", "timestamp")) + tree.heading("#0", text="ID") + tree.heading("title", text="Title") + tree.heading("artist", text="Artist") + tree.heading("filename", text="Filename") + tree.heading("tags", text="Tags") + tree.heading("timestamp", text="Timestamp") + + # Populate the treeview with data from the database + for entry in db.get_all_entries(): + tree.insert("", "end", text=entry["id"], values=(entry["title"], entry["artist"], entry["filename"], ", ".join(entry["tags"]), entry["timestamp"])) + + tree.pack(fill='both', expand=True) + + + def search_and_display(self): + self.set_status("searching") + query = self.search_entry.get() + # Clear the existing results + for widget in self.results_frame.winfo_children(): + widget.destroy() + results = search_youtube(query) + self.display_results(results) + self.set_status("Enter search query or select item to download") + + def set_status(self, status): + self.status_label.config(text=status) + self.root.update() # Force GUI update + + # Modify the display_results method to use the results_frame in the search_tab + def display_results(self, results): + for i, result in enumerate(results): + if self.db.downloadable(result['webpage_url'], result['title']): + result_button = ttk.Button(self.results_frame, text=result['title'], command=lambda url=result['webpage_url'], filename=result['title']: self.handle_download(url, filename)) + result_button.pack() + else: + result_button = tk.Label(self.results_frame, text=f"celaigia: {result['title']}") + result_button.pack() + + def handle_download(self, url, filename): + + if self.db.downloadable(url, filename): + self.set_status("Downloading...") + success = download_audio(url, self.db.base_path) + if success: + self.set_status("Download completed, adding to db") + self.prompt_user_for_details(url, filename) + self.set_status("Enter search query or select item to download") + + # Modify the prompt_user_for_details method + def prompt_user_for_details(self,url, filename): + title = sd.askstring("Enter Title", "Enter Title:", initialvalue=filename) + artist = sd.askstring("Enter Artist", "Enter Artist:") + tags = sd.askstring("Enter Tags", "Enter Tags (comma separated):") + entry = { + "id": url.split('https://www.youtube.com/watch?v=')[-1].strip(), + "title": title.strip(), + "artist": artist.strip(), + "filename": filename.strip(), + "tags": [tag.strip() for tag in tags.split(",")] if tags else [], + "timestamp": time.time() + } + self.add_to_db(entry) + + def add_to_db(self, entry): + self.db.add_entry(entry) + + def run(self): + self.root.mainloop() \ No newline at end of file diff --git a/utils.py b/utils.py new file mode 100644 index 0000000..cf8a6da --- /dev/null +++ b/utils.py @@ -0,0 +1,28 @@ +import yt_dlp +from config import yt_dlp_opts + + +# Function to search YouTube +def search_youtube(query, opts = yt_dlp_opts): + with yt_dlp.YoutubeDL(opts) as ydl: + info = ydl.extract_info(query, download=False) + return info['entries'] + + +def download_audio(url, base_path, opts = yt_dlp_opts): + + opts['outtmpl'] = f'{base_path}/audios/%(title)s.%(ext)s' + if not isinstance(url, list): + url = [url] + + success = False + + try: + with yt_dlp.YoutubeDL(opts) as ydl: + ydl.download(url) + success = True + except: + success = False + return success + + \ No newline at end of file