commit be9c0cfbeb85273d12389578ccd73b3aedc8800a Author: agropunx Date: Sun Mar 12 18:35:27 2023 +0000 first commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..6c23209 --- /dev/null +++ b/.gitignore @@ -0,0 +1,138 @@ +*.mp3 +*.ogg +*.wav +*.flac + +*.ipynb +*.idea + + +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] +*$py.class + +# C extensions +*.so + +# Distribution / packaging +.Python +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +wheels/ +pip-wheel-metadata/ +share/python-wheels/ +*.egg-info/ +.installed.cfg +*.egg +MANIFEST + +# PyInstaller +# Usually these files are written by a python script from a template +# before PyInstaller builds the exe, so as to inject date/other infos into it. +*.manifest +*.spec + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt + +# Unit test / coverage reports +htmlcov/ +.tox/ +.nox/ +.coverage +.coverage.* +.cache +nosetests.xml +coverage.xml +*.cover +*.py,cover +.hypothesis/ +.pytest_cache/ + +# Translations +*.mo +*.pot + +# Django stuff: +*.log +local_settings.py +db.sqlite3 +db.sqlite3-journal + +# Flask stuff: +instance/ +.webassets-cache + +# Scrapy stuff: +.scrapy + +# Sphinx documentation +docs/_build/ + +# PyBuilder +target/ + +# Jupyter Notebook +.ipynb_checkpoints + +# IPython +profile_default/ +ipython_config.py + +# pyenv +.python-version + +# pipenv +# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. +# However, in case of collaboration, if having platform-specific dependencies or dependencies +# having no cross-platform support, pipenv may install dependencies that don't work, or not +# install all needed dependencies. +#Pipfile.lock + +# PEP 582; used by e.g. github.com/David-OConnor/pyflow +__pypackages__/ + +# Celery stuff +celerybeat-schedule +celerybeat.pid + +# SageMath parsed files +*.sage.py + +# Environments +.env +.venv +env/ +venv/ +ENV/ +env.bak/ +venv.bak/ + +# Spyder project settings +.spyderproject +.spyproject + +# Rope project settings +.ropeproject + +# mkdocs documentation +/site + +# mypy +.mypy_cache/ +.dmypy.json +dmypy.json + +# Pyre type checker +.pyre/ diff --git a/README.md b/README.md new file mode 100644 index 0000000..2ea51a5 --- /dev/null +++ b/README.md @@ -0,0 +1,5 @@ +## Install +- **Download the repository** +- **Install dependencies using pip `pip install -r requirements.txt`** +- **Run celaigia.py** + diff --git a/celaigia.py b/celaigia.py new file mode 100644 index 0000000..84006d9 --- /dev/null +++ b/celaigia.py @@ -0,0 +1,300 @@ +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() + +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/data/songs.json b/data/songs.json new file mode 100644 index 0000000..5908a99 --- /dev/null +++ b/data/songs.json @@ -0,0 +1,4 @@ +{ + "songs": [ + ] +} diff --git a/fonts/MonoLisa-Bold.ttf b/fonts/MonoLisa-Bold.ttf new file mode 100644 index 0000000..385908d Binary files /dev/null and b/fonts/MonoLisa-Bold.ttf differ diff --git a/logo.ico b/logo.ico new file mode 100644 index 0000000..df62037 Binary files /dev/null and b/logo.ico differ diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..5df73e2 --- /dev/null +++ b/requirements.txt @@ -0,0 +1,6 @@ +dearpygui +mutagen +pygame +pytube +tk +