mirror of
https://github.com/Dvorinka/swingmusic-extended.git
synced 2026-06-03 20:13:02 +00:00
update supported audio files in settings.py
+ add win_replace_slash function to format win path strings + misc
This commit is contained in:
+38
-7
@@ -2,6 +2,7 @@
|
|||||||
Contains all the folder routes.
|
Contains all the folder routes.
|
||||||
"""
|
"""
|
||||||
import os
|
import os
|
||||||
|
import psutil
|
||||||
|
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from flask import Blueprint, request
|
from flask import Blueprint, request
|
||||||
@@ -10,7 +11,7 @@ from app import settings
|
|||||||
from app.lib.folderslib import GetFilesAndDirs
|
from app.lib.folderslib import GetFilesAndDirs
|
||||||
from app.db.sqlite.settings import SettingsSQLMethods as db
|
from app.db.sqlite.settings import SettingsSQLMethods as db
|
||||||
from app.models import Folder
|
from app.models import Folder
|
||||||
from app.utils import create_folder_hash
|
from app.utils import create_folder_hash, is_windows, win_replace_slash
|
||||||
|
|
||||||
api = Blueprint("folder", __name__, url_prefix="/")
|
api = Blueprint("folder", __name__, url_prefix="/")
|
||||||
|
|
||||||
@@ -42,8 +43,8 @@ def get_folder_tree():
|
|||||||
return {
|
return {
|
||||||
"folders": [
|
"folders": [
|
||||||
Folder(
|
Folder(
|
||||||
name=f.name,
|
name=f.name if f.name != "" else str(f).replace("\\", "/"),
|
||||||
path=str(f),
|
path=win_replace_slash(str(f)),
|
||||||
has_tracks=True,
|
has_tracks=True,
|
||||||
is_sym=f.is_symlink(),
|
is_sym=f.is_symlink(),
|
||||||
path_hash=create_folder_hash(*f.parts[1:]),
|
path_hash=create_folder_hash(*f.parts[1:]),
|
||||||
@@ -61,26 +62,56 @@ def get_folder_tree():
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def get_all_drives():
|
||||||
|
"""
|
||||||
|
Returns a list of all the drives on a windows machine.
|
||||||
|
"""
|
||||||
|
drives = psutil.disk_partitions()
|
||||||
|
return [d.mountpoint for d in drives]
|
||||||
|
|
||||||
|
|
||||||
@api.route("/folder/dir-browser", methods=["POST"])
|
@api.route("/folder/dir-browser", methods=["POST"])
|
||||||
def list_folders():
|
def list_folders():
|
||||||
"""
|
"""
|
||||||
Returns a list of all the folders in the given folder.
|
Returns a list of all the folders in the given folder.
|
||||||
"""
|
"""
|
||||||
data = request.get_json()
|
data = request.get_json()
|
||||||
|
is_win = is_windows()
|
||||||
|
|
||||||
try:
|
try:
|
||||||
req_dir: str = data["folder"]
|
req_dir: str = data["folder"]
|
||||||
except KeyError:
|
except KeyError:
|
||||||
req_dir = settings.USER_HOME_DIR
|
req_dir = "$home"
|
||||||
|
|
||||||
if req_dir == "$home":
|
if req_dir == "$home":
|
||||||
req_dir = settings.USER_HOME_DIR
|
# req_dir = settings.USER_HOME_DIR
|
||||||
|
if is_win:
|
||||||
|
return {
|
||||||
|
"folders": [
|
||||||
|
{"name": win_replace_slash(d), "path": win_replace_slash(d)}
|
||||||
|
for d in get_all_drives()
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
entries = os.scandir(req_dir)
|
req_dir = req_dir + "/"
|
||||||
|
|
||||||
|
try:
|
||||||
|
entries = os.scandir(req_dir)
|
||||||
|
except PermissionError:
|
||||||
|
return {"folders": []}
|
||||||
|
|
||||||
dirs = [e.name for e in entries if e.is_dir() and not e.name.startswith(".")]
|
dirs = [e.name for e in entries if e.is_dir() and not e.name.startswith(".")]
|
||||||
dirs = [{"name": d, "path": os.path.join(req_dir, d)} for d in dirs]
|
dirs = [
|
||||||
|
{"name": d, "path": win_replace_slash(os.path.join(req_dir, d))} for d in dirs
|
||||||
|
]
|
||||||
|
|
||||||
return {
|
return {
|
||||||
"folders": sorted(dirs, key=lambda i: i["name"]),
|
"folders": sorted(dirs, key=lambda i: i["name"]),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
# todo:
|
||||||
|
|
||||||
|
# - handle showing windows disks in root_dir configuration
|
||||||
|
# - handle the above, but for all partitions mounted in linux.
|
||||||
|
# - handle the "\" in client's folder page breadcrumb
|
||||||
|
|||||||
+25
-17
@@ -48,6 +48,19 @@ def rebuild_store(db_dirs: list[str]):
|
|||||||
log.info("Rebuilding library... ✅")
|
log.info("Rebuilding library... ✅")
|
||||||
|
|
||||||
|
|
||||||
|
def finalize(new_: list[str], removed_: list[str], db_dirs_: list[str]):
|
||||||
|
"""
|
||||||
|
Params:
|
||||||
|
new_: will be added to the database
|
||||||
|
removed_: will be removed from the database
|
||||||
|
db_dirs_: will be used to remove tracks that
|
||||||
|
are outside these directories from the database and store.
|
||||||
|
"""
|
||||||
|
sdb.remove_root_dirs(removed_)
|
||||||
|
sdb.add_root_dirs(new_)
|
||||||
|
rebuild_store(db_dirs_)
|
||||||
|
|
||||||
|
|
||||||
@api.route("/settings/add-root-dirs", methods=["POST"])
|
@api.route("/settings/add-root-dirs", methods=["POST"])
|
||||||
def add_root_dirs():
|
def add_root_dirs():
|
||||||
"""
|
"""
|
||||||
@@ -66,33 +79,28 @@ def add_root_dirs():
|
|||||||
except KeyError:
|
except KeyError:
|
||||||
return msg, 400
|
return msg, 400
|
||||||
|
|
||||||
def finalize(new_: list[str], removed_: list[str], db_dirs_: list[str]):
|
|
||||||
sdb.remove_root_dirs(removed_)
|
|
||||||
sdb.add_root_dirs(new_)
|
|
||||||
rebuild_store(db_dirs_)
|
|
||||||
|
|
||||||
# ---
|
|
||||||
db_dirs = sdb.get_root_dirs()
|
db_dirs = sdb.get_root_dirs()
|
||||||
_h = "$home"
|
_h = "$home"
|
||||||
|
|
||||||
try:
|
db_home = any([d == _h for d in db_dirs]) # if $home is in db
|
||||||
if db_dirs[0] == _h and new_dirs[0] == _h.strip():
|
incoming_home = any([d == _h for d in new_dirs]) # if $home is in incoming
|
||||||
return {"msg": "Not changed!"}
|
|
||||||
|
|
||||||
if db_dirs[0] == _h:
|
# handle $home case
|
||||||
sdb.remove_root_dirs(db_dirs)
|
if db_home and incoming_home:
|
||||||
|
return {"msg": "Not changed!"}
|
||||||
|
|
||||||
if new_dirs[0] == _h:
|
if db_home or incoming_home:
|
||||||
finalize([_h], db_dirs, [settings.USER_HOME_DIR])
|
sdb.remove_root_dirs(db_dirs)
|
||||||
|
|
||||||
return {"root_dirs": [_h]}
|
if incoming_home:
|
||||||
except IndexError:
|
finalize([_h], [], [settings.USER_HOME_DIR])
|
||||||
pass
|
return {"root_dirs": [_h]}
|
||||||
|
|
||||||
|
# ---
|
||||||
|
|
||||||
for _dir in new_dirs:
|
for _dir in new_dirs:
|
||||||
children = get_child_dirs(_dir, db_dirs)
|
children = get_child_dirs(_dir, db_dirs)
|
||||||
removed_dirs.extend(children)
|
removed_dirs.extend(children)
|
||||||
# ---
|
|
||||||
|
|
||||||
for _dir in removed_dirs:
|
for _dir in removed_dirs:
|
||||||
try:
|
try:
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import json
|
|
||||||
from app.db.sqlite.utils import SQLiteManager
|
from app.db.sqlite.utils import SQLiteManager
|
||||||
|
from app.utils import win_replace_slash
|
||||||
|
|
||||||
|
|
||||||
class SettingsSQLMethods:
|
class SettingsSQLMethods:
|
||||||
@@ -19,7 +19,8 @@ class SettingsSQLMethods:
|
|||||||
cur.execute(sql)
|
cur.execute(sql)
|
||||||
dirs = cur.fetchall()
|
dirs = cur.fetchall()
|
||||||
|
|
||||||
return [dir[0] for dir in dirs]
|
dirs = [dir[0] for dir in dirs]
|
||||||
|
return [win_replace_slash(d) for d in dirs]
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def add_root_dirs(dirs: list[str]):
|
def add_root_dirs(dirs: list[str]):
|
||||||
|
|||||||
+13
-3
@@ -17,6 +17,7 @@ from app.utils import (
|
|||||||
create_folder_hash,
|
create_folder_hash,
|
||||||
get_all_artists,
|
get_all_artists,
|
||||||
remove_duplicates,
|
remove_duplicates,
|
||||||
|
win_replace_slash,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@@ -174,7 +175,7 @@ class Store:
|
|||||||
|
|
||||||
return Folder(
|
return Folder(
|
||||||
name=folder.name,
|
name=folder.name,
|
||||||
path=str(folder),
|
path=win_replace_slash(str(folder)),
|
||||||
is_sym=folder.is_symlink(),
|
is_sym=folder.is_symlink(),
|
||||||
has_tracks=True,
|
has_tracks=True,
|
||||||
path_hash=create_folder_hash(*folder.parts[1:]),
|
path_hash=create_folder_hash(*folder.parts[1:]),
|
||||||
@@ -218,9 +219,18 @@ class Store:
|
|||||||
]
|
]
|
||||||
|
|
||||||
all_folders = [Path(f) for f in all_folders]
|
all_folders = [Path(f) for f in all_folders]
|
||||||
all_folders = [f for f in all_folders if f.exists()]
|
# all_folders = [f for f in all_folders if f.exists()]
|
||||||
|
|
||||||
for path in tqdm(all_folders, desc="Processing folders"):
|
valid_folders = []
|
||||||
|
|
||||||
|
for folder in all_folders:
|
||||||
|
try:
|
||||||
|
if folder.exists():
|
||||||
|
valid_folders.append(folder)
|
||||||
|
except PermissionError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
for path in tqdm(valid_folders, desc="Processing folders"):
|
||||||
folder = cls.create_folder(str(path))
|
folder = cls.create_folder(str(path))
|
||||||
|
|
||||||
cls.folders.append(folder)
|
cls.folders.append(folder)
|
||||||
|
|||||||
+19
-6
@@ -4,6 +4,8 @@ from concurrent.futures import ThreadPoolExecutor
|
|||||||
from app.db.store import Store
|
from app.db.store import Store
|
||||||
from app.models import Folder, Track
|
from app.models import Folder, Track
|
||||||
from app.settings import SUPPORTED_FILES
|
from app.settings import SUPPORTED_FILES
|
||||||
|
from app.logger import log
|
||||||
|
from app.utils import win_replace_slash
|
||||||
|
|
||||||
|
|
||||||
class GetFilesAndDirs:
|
class GetFilesAndDirs:
|
||||||
@@ -26,14 +28,25 @@ class GetFilesAndDirs:
|
|||||||
ext = os.path.splitext(entry.name)[1].lower()
|
ext = os.path.splitext(entry.name)[1].lower()
|
||||||
|
|
||||||
if entry.is_dir() and not entry.name.startswith("."):
|
if entry.is_dir() and not entry.name.startswith("."):
|
||||||
dirs.append(entry.path)
|
dirs.append(win_replace_slash(entry.path))
|
||||||
elif entry.is_file() and ext in SUPPORTED_FILES:
|
elif entry.is_file() and ext in SUPPORTED_FILES:
|
||||||
files.append(entry.path)
|
files.append(win_replace_slash(entry.path))
|
||||||
|
|
||||||
# sort files by modified time
|
files_ = []
|
||||||
files.sort(
|
|
||||||
key=lambda f: os.path.getmtime(f) # pylint: disable=unnecessary-lambda
|
for file in files:
|
||||||
)
|
try:
|
||||||
|
files_.append(
|
||||||
|
{
|
||||||
|
"path": file,
|
||||||
|
"time": os.path.getmtime(file),
|
||||||
|
}
|
||||||
|
)
|
||||||
|
except OSError as e:
|
||||||
|
log.error(e)
|
||||||
|
|
||||||
|
files_.sort(key=lambda f: f["time"])
|
||||||
|
files = [f["path"] for f in files_]
|
||||||
|
|
||||||
tracks = Store.get_tracks_by_filepaths(files)
|
tracks = Store.get_tracks_by_filepaths(files)
|
||||||
|
|
||||||
|
|||||||
+5
-2
@@ -43,7 +43,10 @@ class Populate:
|
|||||||
|
|
||||||
if len(dirs_to_scan) == 0:
|
if len(dirs_to_scan) == 0:
|
||||||
log.warning(
|
log.warning(
|
||||||
"The root directory is not configured. Open the app in your web browser to configure."
|
(
|
||||||
|
"The root directory is not configured. "
|
||||||
|
+ "Open the app in your webbrowser to configure."
|
||||||
|
)
|
||||||
)
|
)
|
||||||
return
|
return
|
||||||
|
|
||||||
@@ -85,7 +88,7 @@ class Populate:
|
|||||||
|
|
||||||
for file in tqdm(untagged, desc="Reading files"):
|
for file in tqdm(untagged, desc="Reading files"):
|
||||||
if POPULATE_KEY != key:
|
if POPULATE_KEY != key:
|
||||||
raise PopulateCancelledError('Populate key changed')
|
raise PopulateCancelledError("Populate key changed")
|
||||||
|
|
||||||
tags = get_tags(file)
|
tags = get_tags(file)
|
||||||
|
|
||||||
|
|||||||
+31
-9
@@ -1,13 +1,17 @@
|
|||||||
import os
|
|
||||||
import datetime
|
import datetime
|
||||||
|
import os
|
||||||
from io import BytesIO
|
from io import BytesIO
|
||||||
|
|
||||||
from tinytag import TinyTag
|
|
||||||
from PIL import Image, UnidentifiedImageError
|
from PIL import Image, UnidentifiedImageError
|
||||||
|
from tinytag import TinyTag
|
||||||
|
|
||||||
from app import settings
|
from app import settings
|
||||||
from app.utils import create_hash
|
from app.utils import (
|
||||||
|
create_hash,
|
||||||
|
parse_artist_from_filename,
|
||||||
|
parse_title_from_filename,
|
||||||
|
win_replace_slash,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def parse_album_art(filepath: str):
|
def parse_album_art(filepath: str):
|
||||||
@@ -81,7 +85,7 @@ def get_tags(filepath: str):
|
|||||||
|
|
||||||
try:
|
try:
|
||||||
tags = TinyTag.get(filepath)
|
tags = TinyTag.get(filepath)
|
||||||
except: # pylint: disable=bare-except
|
except: # noqa: E722
|
||||||
return None
|
return None
|
||||||
|
|
||||||
no_albumartist: bool = (tags.albumartist == "") or (tags.albumartist is None)
|
no_albumartist: bool = (tags.albumartist == "") or (tags.albumartist is None)
|
||||||
@@ -97,9 +101,22 @@ def get_tags(filepath: str):
|
|||||||
for tag in to_filename:
|
for tag in to_filename:
|
||||||
p = getattr(tags, tag)
|
p = getattr(tags, tag)
|
||||||
if p == "" or p is None:
|
if p == "" or p is None:
|
||||||
setattr(tags, tag, filename)
|
maybe = parse_title_from_filename(filename)
|
||||||
|
setattr(tags, tag, maybe)
|
||||||
|
|
||||||
to_check = ["album", "artist", "year", "albumartist"]
|
parse = ["artist", "albumartist"]
|
||||||
|
for tag in parse:
|
||||||
|
p = getattr(tags, tag)
|
||||||
|
|
||||||
|
if p == "" or p is None:
|
||||||
|
maybe = parse_artist_from_filename(filename)
|
||||||
|
|
||||||
|
if maybe != []:
|
||||||
|
setattr(tags, tag, ", ".join(maybe))
|
||||||
|
else:
|
||||||
|
setattr(tags, tag, "Unknown")
|
||||||
|
|
||||||
|
to_check = ["album", "year", "albumartist"]
|
||||||
for prop in to_check:
|
for prop in to_check:
|
||||||
p = getattr(tags, prop)
|
p = getattr(tags, prop)
|
||||||
if (p is None) or (p == ""):
|
if (p is None) or (p == ""):
|
||||||
@@ -127,10 +144,10 @@ def get_tags(filepath: str):
|
|||||||
tags.albumhash = create_hash(tags.album, tags.albumartist)
|
tags.albumhash = create_hash(tags.album, tags.albumartist)
|
||||||
tags.trackhash = create_hash(tags.artist, tags.album, tags.title)
|
tags.trackhash = create_hash(tags.artist, tags.album, tags.title)
|
||||||
tags.image = f"{tags.albumhash}.webp"
|
tags.image = f"{tags.albumhash}.webp"
|
||||||
tags.folder = os.path.dirname(filepath)
|
tags.folder = win_replace_slash(os.path.dirname(filepath))
|
||||||
|
|
||||||
tags.date = extract_date(tags.year)
|
tags.date = extract_date(tags.year)
|
||||||
tags.filepath = filepath
|
tags.filepath = win_replace_slash(filepath)
|
||||||
tags.filetype = filetype
|
tags.filetype = filetype
|
||||||
|
|
||||||
tags = tags.__dict__
|
tags = tags.__dict__
|
||||||
@@ -157,3 +174,8 @@ def get_tags(filepath: str):
|
|||||||
del tags[tag]
|
del tags[tag]
|
||||||
|
|
||||||
return tags
|
return tags
|
||||||
|
|
||||||
|
for tag in to_delete:
|
||||||
|
del tags[tag]
|
||||||
|
|
||||||
|
return tags
|
||||||
|
|||||||
@@ -63,7 +63,10 @@ class Watcher:
|
|||||||
|
|
||||||
dir_map = [d for d in dir_map if d["realpath"] != d["original"]]
|
dir_map = [d for d in dir_map if d["realpath"] != d["original"]]
|
||||||
|
|
||||||
if len(dirs) > 0 and dirs[0] == "$home":
|
# if len(dirs) > 0 and dirs[0] == "$home":
|
||||||
|
# dirs = [settings.USER_HOME_DIR]
|
||||||
|
|
||||||
|
if any([d == "$home" for d in dirs]):
|
||||||
dirs = [settings.USER_HOME_DIR]
|
dirs = [settings.USER_HOME_DIR]
|
||||||
|
|
||||||
event_handler = Handler(root_dirs=dirs, dir_map=dir_map)
|
event_handler = Handler(root_dirs=dirs, dir_map=dir_map)
|
||||||
@@ -83,7 +86,7 @@ class Watcher:
|
|||||||
try:
|
try:
|
||||||
self.observer.start()
|
self.observer.start()
|
||||||
log.info("Started watchdog")
|
log.info("Started watchdog")
|
||||||
except FileNotFoundError:
|
except (FileNotFoundError, PermissionError):
|
||||||
log.error(
|
log.error(
|
||||||
"WatchdogError: Failed to start watchdog, root directories could not be resolved."
|
"WatchdogError: Failed to start watchdog, root directories could not be resolved."
|
||||||
)
|
)
|
||||||
@@ -189,10 +192,11 @@ class Handler(PatternMatchingEventHandler):
|
|||||||
def __init__(self, root_dirs: list[str], dir_map: dict[str:str]):
|
def __init__(self, root_dirs: list[str], dir_map: dict[str:str]):
|
||||||
self.root_dirs = root_dirs
|
self.root_dirs = root_dirs
|
||||||
self.dir_map = dir_map
|
self.dir_map = dir_map
|
||||||
|
patterns = [f"*{f}" for f in settings.SUPPORTED_FILES]
|
||||||
|
|
||||||
PatternMatchingEventHandler.__init__(
|
PatternMatchingEventHandler.__init__(
|
||||||
self,
|
self,
|
||||||
patterns=["*.flac", "*.mp3"],
|
patterns=patterns,
|
||||||
ignore_directories=True,
|
ignore_directories=True,
|
||||||
case_sensitive=False,
|
case_sensitive=False,
|
||||||
)
|
)
|
||||||
|
|||||||
+1
-1
@@ -60,7 +60,7 @@ class Track:
|
|||||||
if self.artist is not None:
|
if self.artist is not None:
|
||||||
artists = utils.split_artists(self.artist)
|
artists = utils.split_artists(self.artist)
|
||||||
|
|
||||||
featured = utils.extract_featured_artists_from_title(self.title)
|
featured = utils.parse_feat_from_title(self.title)
|
||||||
original_lower = "-".join([a.lower() for a in artists])
|
original_lower = "-".join([a.lower() for a in artists])
|
||||||
artists.extend([a for a in featured if a.lower() not in original_lower])
|
artists.extend([a for a in featured if a.lower() not in original_lower])
|
||||||
|
|
||||||
|
|||||||
+1
-1
@@ -71,7 +71,7 @@ SM_ARTIST_IMG_SIZE = 64
|
|||||||
The size of extracted images in pixels
|
The size of extracted images in pixels
|
||||||
"""
|
"""
|
||||||
|
|
||||||
FILES = ["flac", "mp3", "wav", "m4a"]
|
FILES = ["flac", "mp3", "wav", "m4a", "ogg", "wma", "opus", "alac", "aiff"]
|
||||||
SUPPORTED_FILES = tuple(f".{file}" for file in FILES)
|
SUPPORTED_FILES = tuple(f".{file}" for file in FILES)
|
||||||
|
|
||||||
# ===== SQLite =====
|
# ===== SQLite =====
|
||||||
|
|||||||
+74
-27
@@ -1,19 +1,18 @@
|
|||||||
"""
|
"""
|
||||||
This module contains mini functions for the server.
|
This module contains mini functions for the server.
|
||||||
"""
|
"""
|
||||||
import random
|
import hashlib
|
||||||
import re
|
|
||||||
import string
|
|
||||||
from pathlib import Path
|
|
||||||
from datetime import datetime
|
|
||||||
|
|
||||||
import os
|
import os
|
||||||
import platform
|
import platform
|
||||||
|
import random
|
||||||
|
import re
|
||||||
import socket as Socket
|
import socket as Socket
|
||||||
import hashlib
|
import string
|
||||||
import threading
|
import threading
|
||||||
import requests
|
from datetime import datetime
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
import requests
|
||||||
from unidecode import unidecode
|
from unidecode import unidecode
|
||||||
|
|
||||||
from app import models
|
from app import models
|
||||||
@@ -36,7 +35,8 @@ def background(func):
|
|||||||
|
|
||||||
def run_fast_scandir(_dir: str, full=False) -> tuple[list[str], list[str]]:
|
def run_fast_scandir(_dir: str, full=False) -> tuple[list[str], list[str]]:
|
||||||
"""
|
"""
|
||||||
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.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
if _dir == "":
|
if _dir == "":
|
||||||
@@ -46,20 +46,20 @@ def run_fast_scandir(_dir: str, full=False) -> tuple[list[str], list[str]]:
|
|||||||
files = []
|
files = []
|
||||||
|
|
||||||
try:
|
try:
|
||||||
for _files in os.scandir(_dir):
|
for _file in os.scandir(_dir):
|
||||||
if _files.is_dir() and not _files.name.startswith("."):
|
if _file.is_dir() and not _file.name.startswith("."):
|
||||||
subfolders.append(_files.path)
|
subfolders.append(_file.path)
|
||||||
if _files.is_file():
|
if _file.is_file():
|
||||||
ext = os.path.splitext(_files.name)[1].lower()
|
ext = os.path.splitext(_file.name)[1].lower()
|
||||||
if ext in SUPPORTED_FILES:
|
if ext in SUPPORTED_FILES:
|
||||||
files.append(_files.path)
|
files.append(win_replace_slash(_file.path))
|
||||||
|
|
||||||
if full or len(files) == 0:
|
if full or len(files) == 0:
|
||||||
for _dir in list(subfolders):
|
for _dir in list(subfolders):
|
||||||
sub_dirs, _files = run_fast_scandir(_dir, full=True)
|
sub_dirs, _file = run_fast_scandir(_dir, full=True)
|
||||||
subfolders.extend(sub_dirs)
|
subfolders.extend(sub_dirs)
|
||||||
files.extend(_files)
|
files.extend(_file)
|
||||||
except (PermissionError, FileNotFoundError, ValueError):
|
except (OSError, PermissionError, FileNotFoundError, ValueError):
|
||||||
return [], []
|
return [], []
|
||||||
|
|
||||||
return subfolders, files
|
return subfolders, files
|
||||||
@@ -191,7 +191,7 @@ def get_albumartists(albums: list[models.Album]) -> set[str]:
|
|||||||
|
|
||||||
|
|
||||||
def get_all_artists(
|
def get_all_artists(
|
||||||
tracks: list[models.Track], albums: list[models.Album]
|
tracks: list[models.Track], albums: list[models.Album]
|
||||||
) -> list[models.Artist]:
|
) -> list[models.Artist]:
|
||||||
artists_from_tracks = get_artists_from_tracks(tracks)
|
artists_from_tracks = get_artists_from_tracks(tracks)
|
||||||
artist_from_albums = get_albumartists(albums)
|
artist_from_albums = get_albumartists(albums)
|
||||||
@@ -232,7 +232,8 @@ def bisection_search_string(strings: list[str], target: str) -> str | None:
|
|||||||
|
|
||||||
def get_home_res_path(filename: str):
|
def get_home_res_path(filename: str):
|
||||||
"""
|
"""
|
||||||
Returns a path to resources in the home directory of this project. Used to resolve resources in builds.
|
Returns a path to resources in the home directory of this project.
|
||||||
|
Used to resolve resources in builds.
|
||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
return (CWD / ".." / filename).resolve()
|
return (CWD / ".." / filename).resolve()
|
||||||
@@ -259,12 +260,7 @@ def is_windows():
|
|||||||
return platform.system() == "Windows"
|
return platform.system() == "Windows"
|
||||||
|
|
||||||
|
|
||||||
def split_artists(src: str):
|
def parse_feat_from_title(title: str) -> list[str]:
|
||||||
artists = re.split(r"\s*[&,;]\s*", src)
|
|
||||||
return [a.strip() for a in artists]
|
|
||||||
|
|
||||||
|
|
||||||
def extract_featured_artists_from_title(title: str) -> list[str]:
|
|
||||||
"""
|
"""
|
||||||
Extracts featured artists from a song title using regex.
|
Extracts featured artists from a song title using regex.
|
||||||
"""
|
"""
|
||||||
@@ -275,7 +271,7 @@ def extract_featured_artists_from_title(title: str) -> list[str]:
|
|||||||
return []
|
return []
|
||||||
|
|
||||||
artists = match.group(1)
|
artists = match.group(1)
|
||||||
artists = split_artists(artists)
|
artists = split_artists(artists, with_and=True)
|
||||||
return artists
|
return artists
|
||||||
|
|
||||||
|
|
||||||
@@ -284,3 +280,54 @@ def get_random_str(length=5):
|
|||||||
Generates a random string of length `length`.
|
Generates a random string of length `length`.
|
||||||
"""
|
"""
|
||||||
return "".join(random.choices(string.ascii_letters + string.digits, k=length))
|
return "".join(random.choices(string.ascii_letters + string.digits, k=length))
|
||||||
|
|
||||||
|
|
||||||
|
def win_replace_slash(path: str):
|
||||||
|
if is_windows():
|
||||||
|
return path.replace("\\", "/").replace("//", "/")
|
||||||
|
|
||||||
|
return path
|
||||||
|
|
||||||
|
|
||||||
|
def split_artists(src: str, with_and: bool = False):
|
||||||
|
exp = r"\s*(?:and|&|,|;)\s*" if with_and else r"\s*[,;]\s*"
|
||||||
|
|
||||||
|
artists = re.split(exp, src)
|
||||||
|
return [a.strip() for a in artists]
|
||||||
|
|
||||||
|
def parse_artist_from_filename(title: str):
|
||||||
|
"""
|
||||||
|
Extracts artist names from a song title using regex.
|
||||||
|
"""
|
||||||
|
|
||||||
|
regex = r"^(.+?)\s*[-–—]\s*(?:.+?)$"
|
||||||
|
match = re.search(regex, title, re.IGNORECASE)
|
||||||
|
|
||||||
|
if not match:
|
||||||
|
return []
|
||||||
|
|
||||||
|
artists = match.group(1)
|
||||||
|
artists = split_artists(artists)
|
||||||
|
return artists
|
||||||
|
|
||||||
|
|
||||||
|
def parse_title_from_filename(title: str):
|
||||||
|
"""
|
||||||
|
Extracts track title from a song title using regex.
|
||||||
|
"""
|
||||||
|
|
||||||
|
regex = r"^(?:.+?)\s*[-–—]\s*(.+?)$"
|
||||||
|
match = re.search(regex, title, re.IGNORECASE)
|
||||||
|
|
||||||
|
if not match:
|
||||||
|
return title
|
||||||
|
|
||||||
|
res = match.group(1)
|
||||||
|
# remove text in brackets starting with "official" case insensitive
|
||||||
|
res = re.sub(r"\s*\([^)]*official[^)]*\)", "", res, flags=re.IGNORECASE)
|
||||||
|
return res.strip()
|
||||||
|
|
||||||
|
|
||||||
|
# for title in sample_titles:
|
||||||
|
# print(parse_artist_from_filename(title))
|
||||||
|
# print(parse_title_from_filename(title))
|
||||||
|
|||||||
@@ -18,7 +18,6 @@ from app.utils import background, get_home_res_path, get_ip, is_windows
|
|||||||
werkzeug = logging.getLogger("werkzeug")
|
werkzeug = logging.getLogger("werkzeug")
|
||||||
werkzeug.setLevel(logging.ERROR)
|
werkzeug.setLevel(logging.ERROR)
|
||||||
|
|
||||||
|
|
||||||
class Variables:
|
class Variables:
|
||||||
FLASK_PORT = 1970
|
FLASK_PORT = 1970
|
||||||
FLASK_HOST = "localhost"
|
FLASK_HOST = "localhost"
|
||||||
@@ -180,6 +179,7 @@ if __name__ == "__main__":
|
|||||||
log_info()
|
log_info()
|
||||||
run_bg_checks()
|
run_bg_checks()
|
||||||
start_watchdog()
|
start_watchdog()
|
||||||
|
|
||||||
app.run(
|
app.run(
|
||||||
debug=True,
|
debug=True,
|
||||||
threaded=True,
|
threaded=True,
|
||||||
|
|||||||
Generated
+34
-7
@@ -340,14 +340,14 @@ tornado = ["tornado (>=0.2)"]
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "hypothesis"
|
name = "hypothesis"
|
||||||
version = "6.65.0"
|
version = "6.65.1"
|
||||||
description = "A library for property-based testing"
|
description = "A library for property-based testing"
|
||||||
category = "dev"
|
category = "dev"
|
||||||
optional = false
|
optional = false
|
||||||
python-versions = ">=3.7"
|
python-versions = ">=3.7"
|
||||||
files = [
|
files = [
|
||||||
{file = "hypothesis-6.65.0-py3-none-any.whl", hash = "sha256:24e3219b0b181414c06bb7a62649a6edb471f148d25c9c9687f47505b0f50b1c"},
|
{file = "hypothesis-6.65.1-py3-none-any.whl", hash = "sha256:4b7ae16db09151d17e5feebea07f4f84693cc1573c25e280bc92e619df24182b"},
|
||||||
{file = "hypothesis-6.65.0.tar.gz", hash = "sha256:d25914dd4008b0292d116ac315f01f6691c5460c494a0291c01d96f4bc17fe68"},
|
{file = "hypothesis-6.65.1.tar.gz", hash = "sha256:fb9757f4b556fc73c2eaa2c1b7d39d0184c75e4cb77dadaf6fa59373838bd629"},
|
||||||
]
|
]
|
||||||
|
|
||||||
[package.dependencies]
|
[package.dependencies]
|
||||||
@@ -602,14 +602,14 @@ files = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "pathspec"
|
name = "pathspec"
|
||||||
version = "0.10.3"
|
version = "0.11.0"
|
||||||
description = "Utility library for gitignore style pattern matching of file paths."
|
description = "Utility library for gitignore style pattern matching of file paths."
|
||||||
category = "dev"
|
category = "dev"
|
||||||
optional = false
|
optional = false
|
||||||
python-versions = ">=3.7"
|
python-versions = ">=3.7"
|
||||||
files = [
|
files = [
|
||||||
{file = "pathspec-0.10.3-py3-none-any.whl", hash = "sha256:3c95343af8b756205e2aba76e843ba9520a24dd84f68c22b9f93251507509dd6"},
|
{file = "pathspec-0.11.0-py3-none-any.whl", hash = "sha256:3a66eb970cbac598f9e5ccb5b2cf58930cd8e3ed86d393d541eaf2d8b1705229"},
|
||||||
{file = "pathspec-0.10.3.tar.gz", hash = "sha256:56200de4077d9d0791465aa9095a01d421861e405b5096955051deefd697d6f6"},
|
{file = "pathspec-0.11.0.tar.gz", hash = "sha256:64d338d4e0914e91c1792321e6907b5a593f1ab1851de7fc269557a21b30ebbc"},
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -749,6 +749,33 @@ files = [
|
|||||||
dev = ["pre-commit", "tox"]
|
dev = ["pre-commit", "tox"]
|
||||||
testing = ["pytest", "pytest-benchmark"]
|
testing = ["pytest", "pytest-benchmark"]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "psutil"
|
||||||
|
version = "5.9.4"
|
||||||
|
description = "Cross-platform lib for process and system monitoring in Python."
|
||||||
|
category = "main"
|
||||||
|
optional = false
|
||||||
|
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
|
||||||
|
files = [
|
||||||
|
{file = "psutil-5.9.4-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:c1ca331af862803a42677c120aff8a814a804e09832f166f226bfd22b56feee8"},
|
||||||
|
{file = "psutil-5.9.4-cp27-cp27m-manylinux2010_i686.whl", hash = "sha256:68908971daf802203f3d37e78d3f8831b6d1014864d7a85937941bb35f09aefe"},
|
||||||
|
{file = "psutil-5.9.4-cp27-cp27m-manylinux2010_x86_64.whl", hash = "sha256:3ff89f9b835100a825b14c2808a106b6fdcc4b15483141482a12c725e7f78549"},
|
||||||
|
{file = "psutil-5.9.4-cp27-cp27m-win32.whl", hash = "sha256:852dd5d9f8a47169fe62fd4a971aa07859476c2ba22c2254d4a1baa4e10b95ad"},
|
||||||
|
{file = "psutil-5.9.4-cp27-cp27m-win_amd64.whl", hash = "sha256:9120cd39dca5c5e1c54b59a41d205023d436799b1c8c4d3ff71af18535728e94"},
|
||||||
|
{file = "psutil-5.9.4-cp27-cp27mu-manylinux2010_i686.whl", hash = "sha256:6b92c532979bafc2df23ddc785ed116fced1f492ad90a6830cf24f4d1ea27d24"},
|
||||||
|
{file = "psutil-5.9.4-cp27-cp27mu-manylinux2010_x86_64.whl", hash = "sha256:efeae04f9516907be44904cc7ce08defb6b665128992a56957abc9b61dca94b7"},
|
||||||
|
{file = "psutil-5.9.4-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:54d5b184728298f2ca8567bf83c422b706200bcbbfafdc06718264f9393cfeb7"},
|
||||||
|
{file = "psutil-5.9.4-cp36-abi3-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:16653106f3b59386ffe10e0bad3bb6299e169d5327d3f187614b1cb8f24cf2e1"},
|
||||||
|
{file = "psutil-5.9.4-cp36-abi3-manylinux_2_12_x86_64.manylinux2010_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:54c0d3d8e0078b7666984e11b12b88af2db11d11249a8ac8920dd5ef68a66e08"},
|
||||||
|
{file = "psutil-5.9.4-cp36-abi3-win32.whl", hash = "sha256:149555f59a69b33f056ba1c4eb22bb7bf24332ce631c44a319cec09f876aaeff"},
|
||||||
|
{file = "psutil-5.9.4-cp36-abi3-win_amd64.whl", hash = "sha256:fd8522436a6ada7b4aad6638662966de0d61d241cb821239b2ae7013d41a43d4"},
|
||||||
|
{file = "psutil-5.9.4-cp38-abi3-macosx_11_0_arm64.whl", hash = "sha256:6001c809253a29599bc0dfd5179d9f8a5779f9dffea1da0f13c53ee568115e1e"},
|
||||||
|
{file = "psutil-5.9.4.tar.gz", hash = "sha256:3d7f9739eb435d4b1338944abe23f49584bde5395f27487d2ee25ad9a8774a62"},
|
||||||
|
]
|
||||||
|
|
||||||
|
[package.extras]
|
||||||
|
test = ["enum34", "ipaddress", "mock", "pywin32", "wmi"]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "pyinstaller"
|
name = "pyinstaller"
|
||||||
version = "5.7.0"
|
version = "5.7.0"
|
||||||
@@ -1261,4 +1288,4 @@ files = [
|
|||||||
[metadata]
|
[metadata]
|
||||||
lock-version = "2.0"
|
lock-version = "2.0"
|
||||||
python-versions = ">=3.10,<3.12"
|
python-versions = ">=3.10,<3.12"
|
||||||
content-hash = "15b3fba920faab237353240b2b3dbb32603744f6a8ff19e77fe6296d5252c2d7"
|
content-hash = "54e3995dc11627cb8d20d27ba6d593e4d6d102698c9bd3c29d5505287d22b9e2"
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ tqdm = "^4.64.0"
|
|||||||
rapidfuzz = "^2.13.7"
|
rapidfuzz = "^2.13.7"
|
||||||
tinytag = "^1.8.1"
|
tinytag = "^1.8.1"
|
||||||
Unidecode = "^1.3.6"
|
Unidecode = "^1.3.6"
|
||||||
|
psutil = "^5.9.4"
|
||||||
|
|
||||||
[tool.poetry.dev-dependencies]
|
[tool.poetry.dev-dependencies]
|
||||||
pylint = "^2.15.5"
|
pylint = "^2.15.5"
|
||||||
@@ -28,6 +29,7 @@ pyinstaller = "^5.7.0"
|
|||||||
version = "^22.6.0"
|
version = "^22.6.0"
|
||||||
allow-prereleases = true
|
allow-prereleases = true
|
||||||
|
|
||||||
|
|
||||||
[build-system]
|
[build-system]
|
||||||
requires = ["poetry-core>=1.0.0"]
|
requires = ["poetry-core>=1.0.0"]
|
||||||
build-backend = "poetry.core.masonry.api"
|
build-backend = "poetry.core.masonry.api"
|
||||||
|
|||||||
+14
-10
@@ -1,33 +1,37 @@
|
|||||||
|
from hypothesis import given
|
||||||
|
from hypothesis import strategies as st
|
||||||
|
|
||||||
import app.utils
|
import app.utils
|
||||||
from hypothesis import given, strategies as st
|
from app.utils import parse_feat_from_title
|
||||||
from app.utils import extract_featured_artists_from_title
|
|
||||||
|
|
||||||
|
|
||||||
def test_extract_featured_artists_from_title():
|
def test_extract_featured_artists_from_title():
|
||||||
test_titles = [
|
test_titles = [
|
||||||
"Own it (Featuring Ed Sheeran & Stormzy)",
|
"Own it (Featuring Ed Sheeran & Stormzy)",
|
||||||
|
"Own it (Featuring Ed Sheeran and Stormzy)",
|
||||||
"Autograph (On my line)(Feat. Lil Peep)(Deluxe)",
|
"Autograph (On my line)(Feat. Lil Peep)(Deluxe)",
|
||||||
"Why so sad? (with Juice Wrld, Lil Peep)",
|
"Why so sad? (with Juice Wrld, Lil Peep)",
|
||||||
"Why so sad? (with Juice Wrld/Lil Peep)",
|
"Why so sad? (with Juice Wrld/Lil Peep)",
|
||||||
"Simmer (with Burna Boy)",
|
"Simmer (with Burna Boy)",
|
||||||
"Simmer (without Burna Boy)"
|
"Simmer (without Burna Boy)",
|
||||||
]
|
]
|
||||||
|
|
||||||
results = [
|
results = [
|
||||||
["Ed Sheeran", "Stormzy"],
|
["Ed Sheeran", "Stormzy"],
|
||||||
['Lil Peep'],
|
["Ed Sheeran", "Stormzy"],
|
||||||
["Juice Wrld", "Lil Peep"],
|
["Lil Peep"],
|
||||||
["Juice Wrld", "Lil Peep"],
|
["Juice Wrld", "Lil Peep"],
|
||||||
|
["Juice Wrld/Lil Peep"],
|
||||||
["Burna Boy"],
|
["Burna Boy"],
|
||||||
[]
|
[],
|
||||||
]
|
]
|
||||||
|
|
||||||
for title, expected in zip(test_titles, results):
|
for title, expected in zip(test_titles, results):
|
||||||
assert extract_featured_artists_from_title(title) == expected
|
assert parse_feat_from_title(title) == expected
|
||||||
|
|
||||||
|
|
||||||
# === HYPOTHESIS GHOSTWRITER TESTS ===
|
# === HYPOTHESIS GHOSTWRITER TESTS ===
|
||||||
|
|
||||||
@given(__dir=st.text(), full=st.booleans())
|
# @given(__dir=st.text(), full=st.booleans())
|
||||||
def test_fuzz_run_fast_scandir(__dir: str, full) -> None:
|
# def test_fuzz_run_fast_scandir(__dir: str, full) -> None:
|
||||||
app.utils.run_fast_scandir(_dir=__dir, full=full)
|
# app.utils.run_fast_scandir(_dir=__dir, full=full)
|
||||||
|
|||||||
Reference in New Issue
Block a user