mirror of
https://github.com/tcsenpai/swingmusic.git
synced 2025-06-08 12:15:39 +00:00
add file watcher
- disable double flask instances - remove unused files - play song by id (instead of from nginx)
This commit is contained in:
parent
1802e02179
commit
1caef9d3a2
@ -17,6 +17,7 @@ def create_app():
|
|||||||
app.config.from_mapping(config)
|
app.config.from_mapping(config)
|
||||||
cache.init_app(app)
|
cache.init_app(app)
|
||||||
|
|
||||||
|
with app.app_context():
|
||||||
from . import api
|
from . import api
|
||||||
app.register_blueprint(api.bp, url_prefix='/')
|
app.register_blueprint(api.bp, url_prefix='/')
|
||||||
|
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import os
|
import os
|
||||||
import urllib
|
import urllib
|
||||||
from typing import List
|
from typing import List
|
||||||
from flask import Blueprint, request
|
from flask import Blueprint, request, send_file
|
||||||
from app import functions, instances, helpers, cache
|
from app import functions, instances, helpers, cache
|
||||||
|
|
||||||
bp = Blueprint("api", __name__, url_prefix="")
|
bp = Blueprint("api", __name__, url_prefix="")
|
||||||
@ -17,6 +17,7 @@ def initialize() -> None:
|
|||||||
"""
|
"""
|
||||||
helpers.create_config_dir()
|
helpers.create_config_dir()
|
||||||
helpers.check_for_new_songs()
|
helpers.check_for_new_songs()
|
||||||
|
helpers.start_watchdog()
|
||||||
|
|
||||||
|
|
||||||
initialize()
|
initialize()
|
||||||
@ -283,3 +284,14 @@ def get_album_bio(title, artist):
|
|||||||
"""Returns the album bio for the given album."""
|
"""Returns the album bio for the given album."""
|
||||||
bio = functions.get_album_bio(title, artist)
|
bio = functions.get_album_bio(title, artist)
|
||||||
return {"bio": bio}, 200
|
return {"bio": bio}, 200
|
||||||
|
|
||||||
|
|
||||||
|
@bp.route("/file/<track_id>")
|
||||||
|
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")
|
||||||
|
@ -32,6 +32,7 @@ def populate():
|
|||||||
"""
|
"""
|
||||||
start = time.time()
|
start = time.time()
|
||||||
print("\nchecking for new tracks")
|
print("\nchecking for new tracks")
|
||||||
|
|
||||||
files = helpers.run_fast_scandir(helpers.home_dir, [".flac", ".mp3"])[1]
|
files = helpers.run_fast_scandir(helpers.home_dir, [".flac", ".mp3"])[1]
|
||||||
|
|
||||||
for file in files:
|
for file in files:
|
||||||
@ -41,6 +42,7 @@ def populate():
|
|||||||
instances.songs_instance.insert_song(tags)
|
instances.songs_instance.insert_song(tags)
|
||||||
|
|
||||||
api.all_the_f_music = helpers.get_all_songs()
|
api.all_the_f_music = helpers.get_all_songs()
|
||||||
|
|
||||||
print("\n check done")
|
print("\n check done")
|
||||||
end = time.time()
|
end = time.time()
|
||||||
|
|
||||||
@ -251,18 +253,18 @@ def parse_disk_number(audio):
|
|||||||
return disk_number
|
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.
|
Returns a dictionary of tags for a given file.
|
||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
audio = mutagen.File(full_path, easy=True)
|
audio = mutagen.File(fullpath, easy=True)
|
||||||
except MutagenError:
|
except MutagenError:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
tags = {
|
tags = {
|
||||||
"artists": parse_artist_tag(audio),
|
"artists": parse_artist_tag(audio),
|
||||||
"title": parse_title_tag(audio, full_path),
|
"title": parse_title_tag(audio, fullpath),
|
||||||
"albumartist": parse_album_artist_tag(audio),
|
"albumartist": parse_album_artist_tag(audio),
|
||||||
"album": parse_album_tag(audio),
|
"album": parse_album_tag(audio),
|
||||||
"genre": parse_genre_tag(audio),
|
"genre": parse_genre_tag(audio),
|
||||||
@ -271,9 +273,9 @@ def get_tags(full_path: str) -> dict:
|
|||||||
"discnumber": parse_disk_number(audio),
|
"discnumber": parse_disk_number(audio),
|
||||||
"length": round(audio.info.length),
|
"length": round(audio.info.length),
|
||||||
"bitrate": round(int(audio.info.bitrate) / 1000),
|
"bitrate": round(int(audio.info.bitrate) / 1000),
|
||||||
"filepath": full_path.replace(helpers.home_dir, ""),
|
"filepath": fullpath,
|
||||||
"image": extract_thumb(full_path),
|
"image": extract_thumb(fullpath),
|
||||||
"folder": os.path.dirname(full_path).replace(helpers.home_dir, ""),
|
"folder": os.path.dirname(fullpath).replace(helpers.home_dir, ""),
|
||||||
}
|
}
|
||||||
|
|
||||||
return tags
|
return tags
|
||||||
@ -314,7 +316,6 @@ def create_track_class(tags):
|
|||||||
tags["artists"],
|
tags["artists"],
|
||||||
tags["albumartist"],
|
tags["albumartist"],
|
||||||
tags["album"],
|
tags["album"],
|
||||||
tags["filepath"],
|
|
||||||
tags["folder"],
|
tags["folder"],
|
||||||
tags["length"],
|
tags["length"],
|
||||||
tags["date"],
|
tags["date"],
|
||||||
|
@ -9,24 +9,26 @@ from typing import List
|
|||||||
import requests
|
import requests
|
||||||
|
|
||||||
from io import BytesIO
|
from io import BytesIO
|
||||||
|
|
||||||
from PIL import Image
|
from PIL import Image
|
||||||
|
|
||||||
from app import instances
|
from app import instances
|
||||||
from app import functions
|
from app import functions
|
||||||
|
from app import watchdoge
|
||||||
|
|
||||||
home_dir = os.path.expanduser('~') + '/'
|
home_dir = os.path.expanduser('~') + '/'
|
||||||
app_dir = os.path.join(home_dir, '.musicx')
|
app_dir = os.path.join(home_dir, '.musicx')
|
||||||
LAST_FM_API_KEY = "762db7a44a9e6fb5585661f5f2bdf23a"
|
LAST_FM_API_KEY = "762db7a44a9e6fb5585661f5f2bdf23a"
|
||||||
|
|
||||||
|
|
||||||
def background(f):
|
def background(func):
|
||||||
"""
|
"""
|
||||||
a threading decorator
|
a threading decorator
|
||||||
use @background above the function you want to run in the background
|
use @background above the function you want to run in the background
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def background_func(*a, **kw):
|
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
|
return background_func
|
||||||
|
|
||||||
@ -44,6 +46,11 @@ def check_for_new_songs():
|
|||||||
time.sleep(300)
|
time.sleep(300)
|
||||||
|
|
||||||
|
|
||||||
|
@background
|
||||||
|
def start_watchdog():
|
||||||
|
watchdoge.watch.run()
|
||||||
|
|
||||||
|
|
||||||
def run_fast_scandir(_dir: str, ext: list):
|
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.
|
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:
|
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)
|
response = requests.get(url)
|
||||||
@ -138,14 +145,11 @@ def get_all_songs() -> List:
|
|||||||
tracks = []
|
tracks = []
|
||||||
|
|
||||||
for track in instances.songs_instance.get_all_songs():
|
for track in instances.songs_instance.get_all_songs():
|
||||||
track = functions.create_track_class(track)
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
os.chmod(os.path.join(home_dir, track.filepath), 0o755)
|
os.chmod(os.path.join(track["filepath"]), 0o755)
|
||||||
except FileNotFoundError:
|
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
|
return tracks
|
||||||
|
@ -202,7 +202,6 @@ class Track:
|
|||||||
artists: str
|
artists: str
|
||||||
albumartist: str
|
albumartist: str
|
||||||
album: str
|
album: str
|
||||||
filepath: str
|
|
||||||
folder: str
|
folder: str
|
||||||
length: int
|
length: int
|
||||||
date: int
|
date: int
|
||||||
|
@ -4,9 +4,8 @@ import os
|
|||||||
from watchdog.observers import Observer
|
from watchdog.observers import Observer
|
||||||
from watchdog.events import PatternMatchingEventHandler
|
from watchdog.events import PatternMatchingEventHandler
|
||||||
|
|
||||||
|
|
||||||
class OnMyWatch:
|
class OnMyWatch:
|
||||||
watchDirectory = "/home/cwilvx/Music"
|
directory = "/home/cwilvx/Music"
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
self.observer = Observer()
|
self.observer = Observer()
|
||||||
@ -14,8 +13,10 @@ class OnMyWatch:
|
|||||||
def run(self):
|
def run(self):
|
||||||
event_handler = Handler()
|
event_handler = Handler()
|
||||||
self.observer.schedule(
|
self.observer.schedule(
|
||||||
event_handler, self.watchDirectory, recursive=True)
|
event_handler, self.directory, recursive=True
|
||||||
|
)
|
||||||
self.observer.start()
|
self.observer.start()
|
||||||
|
|
||||||
try:
|
try:
|
||||||
while True:
|
while True:
|
||||||
time.sleep(5)
|
time.sleep(5)
|
||||||
@ -38,21 +39,26 @@ def create_thumb_dir(filepath):
|
|||||||
|
|
||||||
class Handler(PatternMatchingEventHandler):
|
class Handler(PatternMatchingEventHandler):
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
|
print("💠 started watchxx")
|
||||||
PatternMatchingEventHandler.__init__(
|
PatternMatchingEventHandler.__init__(
|
||||||
self, patterns=['*.flac', '*.mp3'], ignore_directories=True, case_sensitive=False)
|
self, patterns=['*.flac', '*.mp3'], ignore_directories=True, case_sensitive=False)
|
||||||
|
|
||||||
def on_created(self, event):
|
def on_created(self, event):
|
||||||
|
print("🔵 created +++")
|
||||||
print(event.src_path)
|
print(event.src_path)
|
||||||
create_thumb_dir(event.src_path)
|
create_thumb_dir(event.src_path)
|
||||||
|
|
||||||
def on_deleted(self, event):
|
def on_deleted(self, event):
|
||||||
|
print("🔴 deleted ---")
|
||||||
print(event.src_path)
|
print(event.src_path)
|
||||||
|
|
||||||
def on_moved(self, event):
|
def on_moved(self, event):
|
||||||
|
print("🔘 moved -->")
|
||||||
print(event.src_path)
|
print(event.src_path)
|
||||||
print(event.dest_path)
|
print(event.dest_path)
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
# if __name__ == '__main__':
|
||||||
watch = OnMyWatch()
|
watch = OnMyWatch()
|
||||||
watch.run()
|
# watch.run()
|
||||||
|
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
from app import create_app
|
from app import create_app
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
app = create_app()
|
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)
|
||||||
|
@ -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
|
# gunicorn -b 0.0.0.0:9876 --workers=4 "wsgi:create_app()" --log-level=debug
|
||||||
|
@ -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()
|
|
Loading…
x
Reference in New Issue
Block a user