commit 5b9232a937ba9b443ba0c8a2a745ecea4641fc6d Author: agropunx Date: Sun Dec 31 15:50:02 2023 +0000 many fix diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..8972cfb --- /dev/null +++ b/.gitignore @@ -0,0 +1,5 @@ +*.tmp +*.zip +*.tar +__pycache__/ +music/ \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..91f589a --- /dev/null +++ b/README.md @@ -0,0 +1,12 @@ +Welcome to celaigia + +an app to gently query youtube music, and possibly download only if you don't have already locally + + +- gitignore music +- standardize postprocess +- parametrize and make config +- fix ui and reduce number of searches +- add direct download with url +- place query on ui +- document \ No newline at end of file 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/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/database.py b/database.py new file mode 100644 index 0000000..32a0b7e --- /dev/null +++ b/database.py @@ -0,0 +1,26 @@ +# 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 diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..e69de29 diff --git a/ui.py b/ui.py new file mode 100644 index 0000000..c1ac7fb --- /dev/null +++ b/ui.py @@ -0,0 +1,86 @@ +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 + + self.status_label = tk.Label(self.root, text="Ready") + self.status_label.pack() + + self.search_frame = ttk.Frame(self.root) + self.search_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.results_frame = ttk.Frame(self.root) + self.results_frame.pack() + + self.download_status_label = tk.Label(self.root, text="Enter URL to download:") + self.download_status_label.pack() + self.download_entry = tk.Entry(self.root) + self.download_entry.pack() + self.download_button = tk.Button(self.root, text="Download", command=lambda: self.handle_download(self.download_entry.get())) + self.download_button.pack() + + 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 + + def display_results(self, results): + for i, result in enumerate(results): + 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() + + 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