|
|
|
@ -3,135 +3,157 @@ from tkinter import ttk
|
|
|
|
|
import tkinter.simpledialog as sd
|
|
|
|
|
import time
|
|
|
|
|
import os
|
|
|
|
|
import pygame
|
|
|
|
|
from config import name, ui_size,colors, youtube_prefix, audio_codec
|
|
|
|
|
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.root.title(name)
|
|
|
|
|
self.root.geometry(ui_size)
|
|
|
|
|
self.status_label = tk.Label(self.root, text="ready")
|
|
|
|
|
self.status_label.pack()
|
|
|
|
|
self.notebook = ttk.Notebook(self.root)
|
|
|
|
|
self.notebook.pack(fill='both', expand=True)
|
|
|
|
|
self.create_search_tab()
|
|
|
|
|
self.create_database_tab(db)
|
|
|
|
|
pygame.init()
|
|
|
|
|
|
|
|
|
|
# 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')
|
|
|
|
|
def set_status(self, status):
|
|
|
|
|
self.status_label.config(text=status)
|
|
|
|
|
self.root.update()
|
|
|
|
|
|
|
|
|
|
# Create the top part of the UI
|
|
|
|
|
self.top_frame = ttk.Frame(self.paned_window)
|
|
|
|
|
self.paned_window.add(self.top_frame)
|
|
|
|
|
def add_to_db(self, entry):
|
|
|
|
|
self.db.add_entry(entry)
|
|
|
|
|
|
|
|
|
|
self.status_label = tk.Label(self.top_frame, text="Ready")
|
|
|
|
|
self.status_label.pack()
|
|
|
|
|
def run(self):
|
|
|
|
|
self.root.mainloop()
|
|
|
|
|
|
|
|
|
|
self.search_frame = ttk.Frame(self.top_frame)
|
|
|
|
|
self.search_frame.pack()
|
|
|
|
|
self.download_frame = ttk.Frame(self.top_frame)
|
|
|
|
|
self.download_frame.pack()
|
|
|
|
|
def create_search_tab(self):
|
|
|
|
|
search_tab = ttk.Frame(self.notebook)
|
|
|
|
|
self.notebook.add(search_tab, text='search & download')
|
|
|
|
|
|
|
|
|
|
self.search_status_label = tk.Label(self.search_frame, text="Enter search query:")
|
|
|
|
|
self.search_status_label.grid(row=0, column=0)
|
|
|
|
|
# search_frame
|
|
|
|
|
self.search_frame = ttk.Frame(search_tab)
|
|
|
|
|
self.search_label = tk.Label(self.search_frame, text="search youtube videos:")
|
|
|
|
|
self.search_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 = tk.Button(
|
|
|
|
|
self.search_frame,
|
|
|
|
|
text="Search",
|
|
|
|
|
background=colors['SearchButton'],
|
|
|
|
|
command=lambda: self.search_and_display()
|
|
|
|
|
)
|
|
|
|
|
self.search_button.grid(row=0, column=2)
|
|
|
|
|
self.search_frame.pack(side='top')
|
|
|
|
|
|
|
|
|
|
# results frame
|
|
|
|
|
self.results_frame = ttk.Frame(search_tab)
|
|
|
|
|
self.results_frame.pack()
|
|
|
|
|
|
|
|
|
|
self.download_status_label = tk.Label(self.download_frame, text="Enter URL to download:")
|
|
|
|
|
self.download_status_label.grid(row=0, column=0)
|
|
|
|
|
# download frame
|
|
|
|
|
self.download_frame = ttk.Frame(search_tab)
|
|
|
|
|
self.download_label = tk.Label(self.download_frame, text="...or directly enter a youtube URL to download:")
|
|
|
|
|
self.download_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 = tk.Button(
|
|
|
|
|
self.download_frame,
|
|
|
|
|
text="Download",
|
|
|
|
|
background=colors['DownloadButton'],
|
|
|
|
|
command=lambda: self.handle_download(self.download_entry.get())
|
|
|
|
|
)
|
|
|
|
|
self.download_button.grid(row=0, column=2)
|
|
|
|
|
self.download_frame.pack(side='bottom')
|
|
|
|
|
|
|
|
|
|
# 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 play_selected_music(self, event):
|
|
|
|
|
item = self.music_tree.selection()[0]
|
|
|
|
|
filename = f"{self.db.base_path}/audios/{self.music_tree.item(item, 'values')[2]}.{audio_codec}"
|
|
|
|
|
if pygame.mixer.music.get_busy() and pygame.mixer.music.get_pos() > 0:
|
|
|
|
|
pygame.mixer.music.stop()
|
|
|
|
|
else:
|
|
|
|
|
pygame.mixer.music.load(filename)
|
|
|
|
|
pygame.mixer.music.play()
|
|
|
|
|
|
|
|
|
|
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
|
|
|
|
|
self.database_tab = ttk.Frame(self.notebook)
|
|
|
|
|
self.notebook.add(self.database_tab, text='available music')
|
|
|
|
|
self.music_tree = ttk.Treeview(self.database_tab, columns=("title", "artist", "filename", "tags", "timestamp"))
|
|
|
|
|
self.music_tree.heading("#0", text="ID")
|
|
|
|
|
self.music_tree.heading("title", text="Title")
|
|
|
|
|
self.music_tree.heading("artist", text="Artist")
|
|
|
|
|
self.music_tree.heading("filename", text="Filename")
|
|
|
|
|
self.music_tree.heading("tags", text="Tags")
|
|
|
|
|
self.music_tree.heading("timestamp", text="Timestamp")
|
|
|
|
|
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)
|
|
|
|
|
self.music_tree.insert("", "end", text=entry["id"], values=(entry["title"], entry["artist"], entry["filename"], ", ".join(entry["tags"]), entry["timestamp"]))
|
|
|
|
|
self.music_tree.bind("<Double-1>", self.play_selected_music)
|
|
|
|
|
self.music_tree.pack(fill='both', expand=True)
|
|
|
|
|
|
|
|
|
|
def update_database_tab(self, entry):
|
|
|
|
|
tree = self.notebook.winfo_children()[1].winfo_children()[0] # Access the treeview in the database tab
|
|
|
|
|
tree.insert("", "end", text=entry["id"], values=(entry["title"], entry["artist"], entry["filename"], ", ".join(entry["tags"]), entry["timestamp"]))
|
|
|
|
|
|
|
|
|
|
def search_and_display(self):
|
|
|
|
|
self.set_status("searching")
|
|
|
|
|
query = self.search_entry.get()
|
|
|
|
|
# Clear the existing results
|
|
|
|
|
self.set_status(f"searching {query} on youtube...")
|
|
|
|
|
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):
|
|
|
|
|
result_label = tk.Label(self.results_frame, text="choose from the list and click to download:")
|
|
|
|
|
result_label.pack()
|
|
|
|
|
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
|
|
|
|
|
text = result['title']
|
|
|
|
|
state = 'active'
|
|
|
|
|
if not self.db.downloadable(result['webpage_url'], result['title']):
|
|
|
|
|
text = f'celaigia : {text}'
|
|
|
|
|
state = 'disabled'
|
|
|
|
|
|
|
|
|
|
result_button = tk.Button(
|
|
|
|
|
self.results_frame,
|
|
|
|
|
text=text,
|
|
|
|
|
background=colors['DownloadButton'],
|
|
|
|
|
command=lambda url=result['webpage_url'], filename=result['title']: self.handle_download(url, filename)
|
|
|
|
|
)
|
|
|
|
|
result_button.pack()
|
|
|
|
|
result_button.configure(state=state)
|
|
|
|
|
self.set_status("ready")
|
|
|
|
|
|
|
|
|
|
def handle_download(self, url, filename=None):
|
|
|
|
|
local_path = f"{self.db.base_path}/audios/{filename}.{audio_codec}"
|
|
|
|
|
self.set_status(f"downloading to {local_path} ...")
|
|
|
|
|
success = download_audio(url, self.db.base_path)
|
|
|
|
|
if success:
|
|
|
|
|
self.set_status("download completed, waiting for user prompt and then adding to db")
|
|
|
|
|
try:
|
|
|
|
|
entry = self.prompt_user_for_details(url, filename)
|
|
|
|
|
self.add_to_db(entry)
|
|
|
|
|
self.update_database_tab(entry)
|
|
|
|
|
for widget in self.results_frame.winfo_children():
|
|
|
|
|
if widget['text'] == entry['filename']:
|
|
|
|
|
widget['text'] = f"celaigia : {widget['text']}"
|
|
|
|
|
widget.configure(state='disabled')
|
|
|
|
|
|
|
|
|
|
except:
|
|
|
|
|
if os.path.exists(local_path):
|
|
|
|
|
os.remove(local_path)
|
|
|
|
|
self.set_status("ready")
|
|
|
|
|
|
|
|
|
|
def prompt_user_for_details(self,url, filename):
|
|
|
|
|
if filename is None:
|
|
|
|
|
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(),
|
|
|
|
|
"id": url.split(youtube_prefix)[-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()
|
|
|
|
|
return entry
|