From 1caef9d3a242744945a2da68e65e6e46e7c5eb17 Mon Sep 17 00:00:00 2001 From: geoffrey45 Date: Wed, 16 Feb 2022 15:02:36 +0300 Subject: [PATCH] add file watcher - disable double flask instances - remove unused files - play song by id (instead of from nginx) --- server/app/__init__.py | 7 +- server/app/api.py | 26 +++++-- server/app/functions.py | 15 ++-- server/app/helpers.py | 22 +++--- server/app/models.py | 1 - server/app/watchdoge.py | 18 +++-- server/manage.py | 3 +- server/start.sh | 5 +- server/thumbnail_extractor.py | 132 ---------------------------------- 9 files changed, 60 insertions(+), 169 deletions(-) delete mode 100644 server/thumbnail_extractor.py diff --git a/server/app/__init__.py b/server/app/__init__.py index 5cbdcf7..d751658 100644 --- a/server/app/__init__.py +++ b/server/app/__init__.py @@ -17,7 +17,8 @@ def create_app(): app.config.from_mapping(config) cache.init_app(app) - from . import api - app.register_blueprint(api.bp, url_prefix='/') + with app.app_context(): + from . import api + app.register_blueprint(api.bp, url_prefix='/') - return app + return app diff --git a/server/app/api.py b/server/app/api.py index 8909d3f..8dd5daa 100644 --- a/server/app/api.py +++ b/server/app/api.py @@ -1,7 +1,7 @@ import os import urllib from typing import List -from flask import Blueprint, request +from flask import Blueprint, request, send_file from app import functions, instances, helpers, cache bp = Blueprint("api", __name__, url_prefix="") @@ -17,6 +17,7 @@ def initialize() -> None: """ helpers.create_config_dir() helpers.check_for_new_songs() + helpers.start_watchdog() initialize() @@ -85,8 +86,8 @@ def search_by_title(): artist_obj = { "name": artist, "image": "http://0.0.0.0:8900/images/artists/" - + artist.replace("/", "::") - + ".webp", + + artist.replace("/", "::") + + ".webp", } if artist_obj not in artists_dicts: @@ -135,8 +136,8 @@ def get_albumartists(album, artist): artist_obj = { "name": artist, "image": "http://0.0.0.0:8900/images/artists/" - + artist.replace("/", "::") - + ".webp", + + artist.replace("/", "::") + + ".webp", } final_artists.append(artist_obj) @@ -270,8 +271,8 @@ def get_album_tracks(title: str, artist: str): "image": songs[0].image, "artist": songs[0].albumartist, "artist_image": "http://127.0.0.1:8900/images/artists/" - + songs[0].albumartist.replace("/", "::") - + ".webp", + + songs[0].albumartist.replace("/", "::") + + ".webp", } return {"songs": songs, "info": album_obj} @@ -283,3 +284,14 @@ def get_album_bio(title, artist): """Returns the album bio for the given album.""" bio = functions.get_album_bio(title, artist) return {"bio": bio}, 200 + + +@bp.route("/file/") +def send_track_file(track_id): + """ + Returns an audio file that matches the passed id to the client. + """ + + filepath = instances.songs_instance.get_song_by_id(track_id)['filepath'] + + return send_file(filepath, mimetype="audio/mp3") diff --git a/server/app/functions.py b/server/app/functions.py index 3457719..44b5c54 100644 --- a/server/app/functions.py +++ b/server/app/functions.py @@ -32,6 +32,7 @@ def populate(): """ start = time.time() print("\nchecking for new tracks") + files = helpers.run_fast_scandir(helpers.home_dir, [".flac", ".mp3"])[1] for file in files: @@ -41,6 +42,7 @@ def populate(): instances.songs_instance.insert_song(tags) api.all_the_f_music = helpers.get_all_songs() + print("\n check done") end = time.time() @@ -251,18 +253,18 @@ def parse_disk_number(audio): return disk_number -def get_tags(full_path: str) -> dict: +def get_tags(fullpath: str) -> dict: """ Returns a dictionary of tags for a given file. """ try: - audio = mutagen.File(full_path, easy=True) + audio = mutagen.File(fullpath, easy=True) except MutagenError: return None tags = { "artists": parse_artist_tag(audio), - "title": parse_title_tag(audio, full_path), + "title": parse_title_tag(audio, fullpath), "albumartist": parse_album_artist_tag(audio), "album": parse_album_tag(audio), "genre": parse_genre_tag(audio), @@ -271,9 +273,9 @@ def get_tags(full_path: str) -> dict: "discnumber": parse_disk_number(audio), "length": round(audio.info.length), "bitrate": round(int(audio.info.bitrate) / 1000), - "filepath": full_path.replace(helpers.home_dir, ""), - "image": extract_thumb(full_path), - "folder": os.path.dirname(full_path).replace(helpers.home_dir, ""), + "filepath": fullpath, + "image": extract_thumb(fullpath), + "folder": os.path.dirname(fullpath).replace(helpers.home_dir, ""), } return tags @@ -314,7 +316,6 @@ def create_track_class(tags): tags["artists"], tags["albumartist"], tags["album"], - tags["filepath"], tags["folder"], tags["length"], tags["date"], diff --git a/server/app/helpers.py b/server/app/helpers.py index 0a64141..b7f9a22 100644 --- a/server/app/helpers.py +++ b/server/app/helpers.py @@ -9,24 +9,26 @@ from typing import List import requests from io import BytesIO + from PIL import Image from app import instances from app import functions +from app import watchdoge home_dir = os.path.expanduser('~') + '/' app_dir = os.path.join(home_dir, '.musicx') LAST_FM_API_KEY = "762db7a44a9e6fb5585661f5f2bdf23a" -def background(f): +def background(func): """ a threading decorator use @background above the function you want to run in the background """ def background_func(*a, **kw): - threading.Thread(target=f, args=a, kwargs=kw).start() + threading.Thread(target=func, args=a, kwargs=kw).start() return background_func @@ -44,6 +46,11 @@ def check_for_new_songs(): time.sleep(300) +@background +def start_watchdog(): + watchdoge.watch.run() + + def run_fast_scandir(_dir: str, ext: list): """ Scans a directory for files with a specific extension. Returns a list of files and folders in the directory. @@ -89,7 +96,7 @@ def remove_duplicates(array: list) -> list: def save_image(url: str, path: str) -> None: """ - Saves an image from a url to a path. + Saves an image from an url to a path. """ response = requests.get(url) @@ -138,14 +145,11 @@ def get_all_songs() -> List: tracks = [] for track in instances.songs_instance.get_all_songs(): - track = functions.create_track_class(track) - try: - os.chmod(os.path.join(home_dir, track.filepath), 0o755) + os.chmod(os.path.join(track["filepath"]), 0o755) except FileNotFoundError: - print("❌") - instances.songs_instance.remove_song_by_filepath(track.filepath) + instances.songs_instance.remove_song_by_filepath(track['filepath']) - tracks.append(track) + tracks.append(functions.create_track_class(track)) return tracks diff --git a/server/app/models.py b/server/app/models.py index e614cc6..52e25e9 100644 --- a/server/app/models.py +++ b/server/app/models.py @@ -202,7 +202,6 @@ class Track: artists: str albumartist: str album: str - filepath: str folder: str length: int date: int diff --git a/server/app/watchdoge.py b/server/app/watchdoge.py index 11ec5c7..3c68154 100644 --- a/server/app/watchdoge.py +++ b/server/app/watchdoge.py @@ -4,9 +4,8 @@ import os from watchdog.observers import Observer from watchdog.events import PatternMatchingEventHandler - class OnMyWatch: - watchDirectory = "/home/cwilvx/Music" + directory = "/home/cwilvx/Music" def __init__(self): self.observer = Observer() @@ -14,8 +13,10 @@ class OnMyWatch: def run(self): event_handler = Handler() self.observer.schedule( - event_handler, self.watchDirectory, recursive=True) + event_handler, self.directory, recursive=True + ) self.observer.start() + try: while True: time.sleep(5) @@ -38,21 +39,26 @@ def create_thumb_dir(filepath): class Handler(PatternMatchingEventHandler): def __init__(self): + print("💠 started watchxx") PatternMatchingEventHandler.__init__( self, patterns=['*.flac', '*.mp3'], ignore_directories=True, case_sensitive=False) def on_created(self, event): + print("🔵 created +++") print(event.src_path) create_thumb_dir(event.src_path) def on_deleted(self, event): + print("🔴 deleted ---") print(event.src_path) def on_moved(self, event): + print("🔘 moved -->") print(event.src_path) print(event.dest_path) -if __name__ == '__main__': - watch = OnMyWatch() - watch.run() +# if __name__ == '__main__': +watch = OnMyWatch() +# watch.run() + diff --git a/server/manage.py b/server/manage.py index 239537a..69de31f 100644 --- a/server/manage.py +++ b/server/manage.py @@ -1,6 +1,5 @@ from app import create_app - if __name__ == '__main__': app = create_app() - app.run(debug=True, threaded=True, host="0.0.0.0", port=9876) + app.run(debug=True, threaded=True, host="0.0.0.0", port=9876, use_reloader=False) diff --git a/server/start.sh b/server/start.sh index 7c33554..aaa7431 100755 --- a/server/start.sh +++ b/server/start.sh @@ -1,5 +1,6 @@ -# /home/cwilvx/.local/share/virtualenvs/server-PQNgo_Nv/bin/ +/home/cwilvx/.cache/pypoetry/virtualenvs/musicx_server-jsG71GtA-py3.8/bin/python manage.py -python manage.py + +#python manage.py # gunicorn -b 0.0.0.0:9876 --workers=4 "wsgi:create_app()" --log-level=debug diff --git a/server/thumbnail_extractor.py b/server/thumbnail_extractor.py deleted file mode 100644 index e380686..0000000 --- a/server/thumbnail_extractor.py +++ /dev/null @@ -1,132 +0,0 @@ -import os, sys, logging, time - -from io import BytesIO -from pathlib import Path - -import PIL - -from watchdog import observers - -from watchdog.observers import Observer -from watchdog.events import LoggingEventHandler, FileSystemEventHandler - -from mutagen.mp3 import MP3, MutagenError -from mutagen.id3 import ID3 -from mutagen.flac import FLAC -from PIL import Image - -music_dir = "/home/cwilvx/Music/" -folders = os.listdir(music_dir) - - -def updateThumbnails(): - start_time = time.time() - print("Updating thumbnails ...") - - for folder in folders: - print(folder) - try: - dir = music_dir + folder - thumbnail_folder = dir + "/"+ ".thumbnails" - - if not os.path.exists(thumbnail_folder): - os.makedirs(thumbnail_folder) - - def thumbnail_extractor(type, song): - if type == "mp3": - tags = ID3(song) - image_path = "{}/.thumbnails/{}".format(dir, song.name.replace(".mp3", ".jpg")) - album_art = tags.getall('APIC')[0].data - elif type == "flac": - tags = FLAC(song) - image_path = "{}/.thumbnails/{}".format(dir, song.name.replace(".flac", ".jpg")) - album_art = tags.pictures[0].data - else: - print("Unsupported file type") - return - - image = Image.open(BytesIO(album_art)) - - if not os.path.exists(image_path): - try: - image.save(image_path, 'JPEG') - except OSError: - image.convert('RGB').save(image_path, 'JPEG') - - for song in Path(dir).rglob('*.mp3'): - try: - thumbnail_extractor("mp3", song) - except (MutagenError, IndexError): - pass - for song in Path(dir).rglob('*.flac'): - try: - thumbnail_extractor("flac", song) - except (MutagenError, IndexError): - pass - except NotADirectoryError: - pass - - print("done") - - print("Done in: %s seconds" % round((time.time() - start_time), 1)) - - -class watchMusicDirs(FileSystemEventHandler): - def __init__(self, logger=None): - super().__init__() - - self.logger = logger or logging.root - - # def on_moved(self, event): - # super().on_moved(event) - - # what = 'directory' if event.is_directory else 'file' - # self.logger.info("Moved %s: from %s to %s", what, event.src_path, - # event.dest_path) - - # def on_created(self, event): - # super().on_created(event) - - # what = 'directory' if event.is_directory else 'file' - # self.logger.info("Created %s: %s", what, event.src_path) - - # def on_deleted(self, event): - # super().on_deleted(event) - - # what = 'directory' if event.is_directory else 'file' - # self.logger.info("Deleted %s: %s", what, event.src_path) - - def on_modified(self, event): - super().on_modified(event) - - what = 'directory' if event.is_directory else 'file' - # self.logger.info("Modified %s: %s", what, event.src_path) - print("Modified %s: %s" % (what, event.src_path)) - updateThumbnails() - -paths = [music_dir, '/home/cwilvx/watched'] - -if __name__ == "__main__": - observer = Observer() - event_handler = watchMusicDirs() - - observers = [] - - for path in paths: - observer.schedule(event_handler, path, recursive=True) - observers.append(observer) - - observer.start() - - try: - while True: - time.sleep(1) - - except KeyboardInterrupt: - for observer in observers: - observer.unschedule_all() - - observer.stop() - - for observer in observers: - observer.join() \ No newline at end of file