mirror of
https://github.com/Dvorinka/swingmusic-extended.git
synced 2026-06-05 04:53:01 +00:00
implement artist split ingore list
+ move post processing of tags to the track model + rebuild stores on settings update via API + check files from the store instead of the db when streaming + remove deprecetated table columns +misc
This commit is contained in:
@@ -33,17 +33,9 @@
|
|||||||
|
|
||||||
# THE BIG ONE
|
# THE BIG ONE
|
||||||
|
|
||||||
- Updating settings
|
|
||||||
- Cleaning out commented code
|
|
||||||
- Watchdog
|
- Watchdog
|
||||||
- Periodic scans
|
- Periodic scans
|
||||||
- Remove legacy db methods
|
|
||||||
- Review: We don't need server side image colors
|
|
||||||
- Clean up main db and userdata modules
|
|
||||||
- Move plugins to a config file
|
|
||||||
- What about our migrations?
|
- What about our migrations?
|
||||||
- Add userid to queries
|
|
||||||
- Remove duplicates on artist page (test with Hanson)
|
|
||||||
- Test foreign keys on delete
|
- Test foreign keys on delete
|
||||||
- Normalize playlists table:
|
- Normalize playlists table:
|
||||||
- New table to hold playlist entries
|
- New table to hold playlist entries
|
||||||
@@ -56,3 +48,5 @@
|
|||||||
|
|
||||||
- Duplicates on search
|
- Duplicates on search
|
||||||
- Audio stops on ending
|
- Audio stops on ending
|
||||||
|
- Show users on account settings when logged in as admin and show users on login is disabled.
|
||||||
|
-
|
||||||
+14
-114
@@ -7,11 +7,7 @@ from app.api.auth import admin_required
|
|||||||
|
|
||||||
from app.db.userdata import PluginTable
|
from app.db.userdata import PluginTable
|
||||||
from app.lib.index import index_everything
|
from app.lib.index import index_everything
|
||||||
from app.logger import log
|
|
||||||
from app.settings import Info
|
from app.settings import Info
|
||||||
from app.store.albums import AlbumStore
|
|
||||||
from app.store.artists import ArtistStore
|
|
||||||
from app.store.tracks import TrackStore
|
|
||||||
from app.config import UserConfig
|
from app.config import UserConfig
|
||||||
|
|
||||||
bp_tag = Tag(name="Settings", description="Customize stuff")
|
bp_tag = Tag(name="Settings", description="Customize stuff")
|
||||||
@@ -24,65 +20,6 @@ def get_child_dirs(parent: str, children: list[str]):
|
|||||||
return [_dir for _dir in children if _dir.startswith(parent) and _dir != parent]
|
return [_dir for _dir in children if _dir.startswith(parent) and _dir != parent]
|
||||||
|
|
||||||
|
|
||||||
def reload_everything(instance_key: str):
|
|
||||||
"""
|
|
||||||
Reloads all stores using the current database items
|
|
||||||
"""
|
|
||||||
try:
|
|
||||||
TrackStore.load_all_tracks(instance_key)
|
|
||||||
except Exception as e:
|
|
||||||
log.error(e)
|
|
||||||
|
|
||||||
try:
|
|
||||||
AlbumStore.load_albums(instance_key=instance_key)
|
|
||||||
except Exception as e:
|
|
||||||
log.error(e)
|
|
||||||
|
|
||||||
try:
|
|
||||||
ArtistStore.load_artists(instance_key)
|
|
||||||
except Exception as e:
|
|
||||||
log.error(e)
|
|
||||||
|
|
||||||
|
|
||||||
# CHECKPOINT: TEST SETTINGS API ENDPOINTS
|
|
||||||
|
|
||||||
# @background
|
|
||||||
# def rebuild_store(db_dirs: list[str]):
|
|
||||||
# """
|
|
||||||
# Restarts watchdog and rebuilds the music library.
|
|
||||||
# """
|
|
||||||
# instance_key = get_random_str()
|
|
||||||
|
|
||||||
# log.info("Rebuilding library...")
|
|
||||||
# trackdb.remove_tracks_not_in_folders(db_dirs)
|
|
||||||
# reload_everything(instance_key)
|
|
||||||
|
|
||||||
# try:
|
|
||||||
# populate.Populate(instance_key=instance_key)
|
|
||||||
# except populate.PopulateCancelledError as e:
|
|
||||||
# print(e)
|
|
||||||
# reload_everything(instance_key)
|
|
||||||
# return
|
|
||||||
|
|
||||||
# WatchDog().restart()
|
|
||||||
|
|
||||||
# log.info("Rebuilding library... ✅")
|
|
||||||
|
|
||||||
|
|
||||||
# # I freaking don't know what this function does anymore
|
|
||||||
# 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_)
|
|
||||||
|
|
||||||
|
|
||||||
class AddRootDirsBody(BaseModel):
|
class AddRootDirsBody(BaseModel):
|
||||||
new_dirs: list[str] = Field(
|
new_dirs: list[str] = Field(
|
||||||
description="The new directories to add",
|
description="The new directories to add",
|
||||||
@@ -151,18 +88,6 @@ def get_root_dirs():
|
|||||||
return {"dirs": UserConfig().rootDirs}
|
return {"dirs": UserConfig().rootDirs}
|
||||||
|
|
||||||
|
|
||||||
# maps settings to their parser flags
|
|
||||||
# mapp = {
|
|
||||||
# "artist_separators": SessionVarKeys.ARTIST_SEPARATORS,
|
|
||||||
# "extract_feat": SessionVarKeys.EXTRACT_FEAT,
|
|
||||||
# "remove_prod": SessionVarKeys.REMOVE_PROD,
|
|
||||||
# "clean_album_title": SessionVarKeys.CLEAN_ALBUM_TITLE,
|
|
||||||
# "remove_remaster": SessionVarKeys.REMOVE_REMASTER_FROM_TRACK,
|
|
||||||
# "merge_albums": SessionVarKeys.MERGE_ALBUM_VERSIONS,
|
|
||||||
# "show_albums_as_singles": SessionVarKeys.SHOW_ALBUMS_AS_SINGLES,
|
|
||||||
# }
|
|
||||||
|
|
||||||
|
|
||||||
@api.get("")
|
@api.get("")
|
||||||
def get_all_settings():
|
def get_all_settings():
|
||||||
"""
|
"""
|
||||||
@@ -176,11 +101,6 @@ def get_all_settings():
|
|||||||
return config
|
return config
|
||||||
|
|
||||||
|
|
||||||
# @background
|
|
||||||
# def reload_all_for_set_setting():
|
|
||||||
# reload_everything(get_random_str())
|
|
||||||
|
|
||||||
|
|
||||||
class SetSettingBody(BaseModel):
|
class SetSettingBody(BaseModel):
|
||||||
key: str = Field(
|
key: str = Field(
|
||||||
description="The setting key",
|
description="The setting key",
|
||||||
@@ -192,39 +112,6 @@ class SetSettingBody(BaseModel):
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
# @api.post("/set")
|
|
||||||
# @admin_required()
|
|
||||||
# def set_setting(body: SetSettingBody):
|
|
||||||
# """
|
|
||||||
# Set a setting.
|
|
||||||
# """
|
|
||||||
# key = body.key
|
|
||||||
# value = body.value
|
|
||||||
|
|
||||||
# if key is None or value is None or key == "root_dirs":
|
|
||||||
# return {"msg": "Invalid arguments!"}, 400
|
|
||||||
|
|
||||||
# root_dir = sdb.get_root_dirs()
|
|
||||||
|
|
||||||
# if not root_dir:
|
|
||||||
# return {"msg": "No root directories set!"}, 400
|
|
||||||
|
|
||||||
# if key not in mapp:
|
|
||||||
# return {"msg": "Invalid key!"}, 400
|
|
||||||
|
|
||||||
# if key == "artist_separators":
|
|
||||||
# value = str(value).split(",")
|
|
||||||
# value = set(value)
|
|
||||||
|
|
||||||
# reload_all_for_set_setting()
|
|
||||||
|
|
||||||
# # if value is a set, convert it to a string
|
|
||||||
# # (artist_separators)
|
|
||||||
# if type(value) == set:
|
|
||||||
# value = ",".join(value)
|
|
||||||
|
|
||||||
# return {"result": value}
|
|
||||||
|
|
||||||
@api.get("/trigger-scan")
|
@api.get("/trigger-scan")
|
||||||
def trigger_scan():
|
def trigger_scan():
|
||||||
"""
|
"""
|
||||||
@@ -256,7 +143,20 @@ def update_config(body: UpdateConfigBody):
|
|||||||
body.value = body.value.split(",")
|
body.value = body.value.split(",")
|
||||||
|
|
||||||
setattr(config, body.key, body.value)
|
setattr(config, body.key, body.value)
|
||||||
print(getattr(config, body.key))
|
|
||||||
|
# INFO: Rebuild stores when these settings are updated
|
||||||
|
reset_stores_lists = {
|
||||||
|
"artistSeparators",
|
||||||
|
"artistSplitIgnoreList",
|
||||||
|
"removeProdBy",
|
||||||
|
"removeRemasterInfo",
|
||||||
|
"mergeAlbums",
|
||||||
|
"cleanAlbumTitle",
|
||||||
|
"showAlbumsAsSingles",
|
||||||
|
}
|
||||||
|
|
||||||
|
if body.key in reset_stores_lists:
|
||||||
|
index_everything()
|
||||||
|
|
||||||
return {
|
return {
|
||||||
"msg": "Config updated!",
|
"msg": "Config updated!",
|
||||||
|
|||||||
+38
-8
@@ -10,8 +10,7 @@ from pydantic import BaseModel, Field
|
|||||||
from app.api.apischemas import TrackHashSchema
|
from app.api.apischemas import TrackHashSchema
|
||||||
from app.lib.trackslib import get_silence_paddings
|
from app.lib.trackslib import get_silence_paddings
|
||||||
|
|
||||||
# from app.store.tracks import TrackStore
|
from app.store.tracks import TrackStore
|
||||||
from app.db.libdata import TrackTable
|
|
||||||
from app.utils.files import guess_mime_type
|
from app.utils.files import guess_mime_type
|
||||||
|
|
||||||
bp_tag = Tag(name="File", description="Audio files")
|
bp_tag = Tag(name="File", description="Audio files")
|
||||||
@@ -35,10 +34,26 @@ def send_track_file_legacy(path: TrackHashSchema, query: SendTrackFileQuery):
|
|||||||
filepath = query.filepath
|
filepath = query.filepath
|
||||||
msg = {"msg": "File Not Found"}
|
msg = {"msg": "File Not Found"}
|
||||||
|
|
||||||
track = TrackTable.get_track_by_trackhash(trackhash, filepath)
|
track = None
|
||||||
track_exists = track is not None and os.path.exists(track.filepath)
|
tracks = TrackStore.get_tracks_by_filepaths([filepath])
|
||||||
|
|
||||||
if track_exists:
|
|
||||||
|
if len(tracks) > 0 and os.path.exists(filepath):
|
||||||
|
track = tracks[0]
|
||||||
|
else:
|
||||||
|
res = TrackStore.trackhashmap.get(trackhash)
|
||||||
|
|
||||||
|
# When finding by trackhash, sort by bitrate
|
||||||
|
# and get the first track that exists
|
||||||
|
if res is not None:
|
||||||
|
tracks = sorted(res.tracks, key=lambda x: x.bitrate, reverse=True)
|
||||||
|
|
||||||
|
for t in tracks:
|
||||||
|
if os.path.exists(t.filepath):
|
||||||
|
track = t
|
||||||
|
break
|
||||||
|
|
||||||
|
if track is not None:
|
||||||
audio_type = guess_mime_type(filepath)
|
audio_type = guess_mime_type(filepath)
|
||||||
return send_file(filepath, mimetype=audio_type, conditional=True)
|
return send_file(filepath, mimetype=audio_type, conditional=True)
|
||||||
|
|
||||||
@@ -57,10 +72,25 @@ def send_track_file(path: TrackHashSchema, query: SendTrackFileQuery):
|
|||||||
msg = {"msg": "File Not Found"}
|
msg = {"msg": "File Not Found"}
|
||||||
|
|
||||||
# If filepath is provided, try to send that
|
# If filepath is provided, try to send that
|
||||||
track = TrackTable.get_track_by_trackhash(trackhash, filepath)
|
track = None
|
||||||
track_exists = track is not None and os.path.exists(track.filepath)
|
tracks = TrackStore.get_tracks_by_filepaths([filepath])
|
||||||
|
|
||||||
if track_exists:
|
if len(tracks) > 0 and os.path.exists(filepath):
|
||||||
|
track = tracks[0]
|
||||||
|
else:
|
||||||
|
res = TrackStore.trackhashmap.get(trackhash)
|
||||||
|
|
||||||
|
# When finding by trackhash, sort by bitrate
|
||||||
|
# and get the first track that exists
|
||||||
|
if res is not None:
|
||||||
|
tracks = sorted(res.tracks, key=lambda x: x.bitrate, reverse=True)
|
||||||
|
|
||||||
|
for t in tracks:
|
||||||
|
if os.path.exists(t.filepath):
|
||||||
|
track = t
|
||||||
|
break
|
||||||
|
|
||||||
|
if track is not None:
|
||||||
audio_type = guess_mime_type(filepath)
|
audio_type = guess_mime_type(filepath)
|
||||||
return send_file_as_chunks(track.filepath, audio_type)
|
return send_file_as_chunks(track.filepath, audio_type)
|
||||||
|
|
||||||
|
|||||||
@@ -22,6 +22,13 @@ class UserConfig:
|
|||||||
rootDirs: list[str] = field(default_factory=list)
|
rootDirs: list[str] = field(default_factory=list)
|
||||||
excludeDirs: list[str] = field(default_factory=list)
|
excludeDirs: list[str] = field(default_factory=list)
|
||||||
artistSeparators: set[str] = field(default_factory=lambda: {";", "/"})
|
artistSeparators: set[str] = field(default_factory=lambda: {";", "/"})
|
||||||
|
artistSplitIgnoreList: set[str] = field(
|
||||||
|
default_factory=lambda: {
|
||||||
|
"AC/DC",
|
||||||
|
"Bob marley & the wailers",
|
||||||
|
"Crosby, Stills, Nash & Young",
|
||||||
|
}
|
||||||
|
)
|
||||||
genreSeparators: set[str] = field(default_factory=lambda: {"/", ";", "&"})
|
genreSeparators: set[str] = field(default_factory=lambda: {"/", ";", "&"})
|
||||||
|
|
||||||
# tracks
|
# tracks
|
||||||
|
|||||||
+20
-7
@@ -109,10 +109,10 @@ class TrackTable(Base):
|
|||||||
|
|
||||||
id: Mapped[int] = mapped_column(init=False, primary_key=True)
|
id: Mapped[int] = mapped_column(init=False, primary_key=True)
|
||||||
album: Mapped[str] = mapped_column(String())
|
album: Mapped[str] = mapped_column(String())
|
||||||
albumartists: Mapped[list[dict[str, str]]] = mapped_column(JSON())
|
albumartists: Mapped[str] = mapped_column(String())
|
||||||
albumhash: Mapped[str] = mapped_column(String(), index=True)
|
albumhash: Mapped[str] = mapped_column(String(), index=True)
|
||||||
artisthashes: Mapped[list[str]] = mapped_column(JSON(), index=True)
|
# artisthashes: Mapped[list[str]] = mapped_column(JSON(), index=True)
|
||||||
artists: Mapped[list[dict[str, str]]] = mapped_column(JSON(), index=True)
|
artists: Mapped[str] = mapped_column(String())
|
||||||
bitrate: Mapped[int] = mapped_column(Integer())
|
bitrate: Mapped[int] = mapped_column(Integer())
|
||||||
copyright: Mapped[Optional[str]] = mapped_column(String())
|
copyright: Mapped[Optional[str]] = mapped_column(String())
|
||||||
date: Mapped[int] = mapped_column(Integer(), nullable=True)
|
date: Mapped[int] = mapped_column(Integer(), nullable=True)
|
||||||
@@ -120,11 +120,11 @@ class TrackTable(Base):
|
|||||||
duration: Mapped[int] = mapped_column(Integer())
|
duration: Mapped[int] = mapped_column(Integer())
|
||||||
filepath: Mapped[str] = mapped_column(String(), index=True, unique=True)
|
filepath: Mapped[str] = mapped_column(String(), index=True, unique=True)
|
||||||
folder: Mapped[str] = mapped_column(String(), index=True)
|
folder: Mapped[str] = mapped_column(String(), index=True)
|
||||||
genrehashes: Mapped[list[str]] = mapped_column(JSON(), index=True)
|
# genrehashes: Mapped[list[str]] = mapped_column(JSON(), index=True)
|
||||||
genres: Mapped[Optional[list[dict[str, str]]]] = mapped_column(JSON())
|
genres: Mapped[Optional[str]] = mapped_column(String())
|
||||||
last_mod: Mapped[float] = mapped_column(Integer())
|
last_mod: Mapped[float] = mapped_column(Integer())
|
||||||
og_album: Mapped[str] = mapped_column(String())
|
# og_album: Mapped[str] = mapped_column(String())
|
||||||
og_title: Mapped[str] = mapped_column(String())
|
# og_title: Mapped[str] = mapped_column(String())
|
||||||
title: Mapped[str] = mapped_column(String())
|
title: Mapped[str] = mapped_column(String())
|
||||||
track: Mapped[int] = mapped_column(Integer())
|
track: Mapped[int] = mapped_column(Integer())
|
||||||
trackhash: Mapped[str] = mapped_column(String(), index=True)
|
trackhash: Mapped[str] = mapped_column(String(), index=True)
|
||||||
@@ -250,6 +250,19 @@ class TrackTable(Base):
|
|||||||
TrackTable, TrackTable.trackhash, trackhash, duration, timestamp
|
TrackTable, TrackTable.trackhash, trackhash, duration, timestamp
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# @classmethod
|
||||||
|
# def update_artist_separators(cls, separators: set[str]):
|
||||||
|
# tracks = cls.get_all()
|
||||||
|
|
||||||
|
# with DbEngine.manager(commit=True) as conn:
|
||||||
|
# for track in tracks:
|
||||||
|
# track.split_artists(separators)
|
||||||
|
# conn.execute(
|
||||||
|
# update(cls)
|
||||||
|
# .where(cls.trackhash == track.trackhash)
|
||||||
|
# .values(artists=track.artists, artisthashes=track.artisthashes)
|
||||||
|
# )
|
||||||
|
|
||||||
|
|
||||||
class AlbumTable(Base):
|
class AlbumTable(Base):
|
||||||
__tablename__ = "album"
|
__tablename__ = "album"
|
||||||
|
|||||||
+4
-3
@@ -1,5 +1,6 @@
|
|||||||
from typing import Any
|
from typing import Any
|
||||||
|
|
||||||
|
from app.config import UserConfig
|
||||||
from app.models import Album as AlbumModel, Artist as ArtistModel, Track as TrackModel
|
from app.models import Album as AlbumModel, Artist as ArtistModel, Track as TrackModel
|
||||||
from app.models.favorite import Favorite
|
from app.models.favorite import Favorite
|
||||||
from app.models.lastfm import SimilarArtist
|
from app.models.lastfm import SimilarArtist
|
||||||
@@ -9,12 +10,12 @@ from app.models.plugins import Plugin
|
|||||||
from app.models.user import User
|
from app.models.user import User
|
||||||
|
|
||||||
|
|
||||||
def track_to_dataclass(track: Any):
|
def track_to_dataclass(track: Any, config: UserConfig):
|
||||||
return TrackModel(**track._asdict())
|
return TrackModel(**track._asdict(), config=config)
|
||||||
|
|
||||||
|
|
||||||
def tracks_to_dataclasses(tracks: Any):
|
def tracks_to_dataclasses(tracks: Any):
|
||||||
return [track_to_dataclass(track) for track in tracks]
|
return [track_to_dataclass(track, UserConfig()) for track in tracks]
|
||||||
|
|
||||||
|
|
||||||
def album_to_dataclass(album: Any):
|
def album_to_dataclass(album: Any):
|
||||||
|
|||||||
+1
-1
@@ -124,7 +124,7 @@ class IndexTracks:
|
|||||||
log.warning("'Populate.tag_untagged': Populate key changed")
|
log.warning("'Populate.tag_untagged': Populate key changed")
|
||||||
return
|
return
|
||||||
|
|
||||||
tags = get_tags(file, artist_separators=config.artistSeparators)
|
tags = get_tags(file, config=config)
|
||||||
|
|
||||||
if tags is not None:
|
if tags is not None:
|
||||||
TrackTable.insert_one(tags)
|
TrackTable.insert_one(tags)
|
||||||
|
|||||||
+89
-88
@@ -14,13 +14,7 @@ from tinytag import TinyTag
|
|||||||
from app.config import UserConfig
|
from app.config import UserConfig
|
||||||
from app.settings import Defaults, Paths
|
from app.settings import Defaults, Paths
|
||||||
from app.utils.hashing import create_hash
|
from app.utils.hashing import create_hash
|
||||||
from app.utils.parsers import (
|
from app.utils.parsers import split_artists
|
||||||
clean_title,
|
|
||||||
get_base_title_and_versions,
|
|
||||||
parse_feat_from_title,
|
|
||||||
remove_prod,
|
|
||||||
split_artists,
|
|
||||||
)
|
|
||||||
from app.utils.wintools import win_replace_slash
|
from app.utils.wintools import win_replace_slash
|
||||||
|
|
||||||
|
|
||||||
@@ -109,13 +103,13 @@ def clean_filename(filename: str):
|
|||||||
class ParseData:
|
class ParseData:
|
||||||
artist: str
|
artist: str
|
||||||
title: str
|
title: str
|
||||||
artist_separators: set[str]
|
config: UserConfig
|
||||||
|
|
||||||
def __post_init__(self):
|
def __post_init__(self):
|
||||||
self.artist = split_artists(self.artist, self.artist_separators)
|
self.artist = split_artists(self.artist, self.config)
|
||||||
|
|
||||||
|
|
||||||
def extract_artist_title(filename: str, artist_separators: set[str]):
|
def extract_artist_title(filename: str, config: UserConfig):
|
||||||
path = Path(filename).with_suffix("")
|
path = Path(filename).with_suffix("")
|
||||||
|
|
||||||
path = clean_filename(str(path))
|
path = clean_filename(str(path))
|
||||||
@@ -123,24 +117,30 @@ def extract_artist_title(filename: str, artist_separators: set[str]):
|
|||||||
split_result = [x.strip() for x in split_result]
|
split_result = [x.strip() for x in split_result]
|
||||||
|
|
||||||
if len(split_result) == 1:
|
if len(split_result) == 1:
|
||||||
return ParseData("", split_result[0], artist_separators)
|
return ParseData(
|
||||||
|
"",
|
||||||
|
split_result[0],
|
||||||
|
config,
|
||||||
|
)
|
||||||
|
|
||||||
if len(split_result) > 2:
|
if len(split_result) > 2:
|
||||||
try:
|
try:
|
||||||
int(split_result[0])
|
int(split_result[0])
|
||||||
|
|
||||||
return ParseData(
|
return ParseData(
|
||||||
split_result[1], " - ".join(split_result[2:]), artist_separators
|
split_result[1],
|
||||||
|
" - ".join(split_result[2:]),
|
||||||
|
config,
|
||||||
)
|
)
|
||||||
except ValueError:
|
except ValueError:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
artist = split_result[0]
|
artist = split_result[0]
|
||||||
title = split_result[1]
|
title = split_result[1]
|
||||||
return ParseData(artist, title, artist_separators)
|
return ParseData(artist, title, config)
|
||||||
|
|
||||||
|
|
||||||
def get_tags(filepath: str, artist_separators: set[str]):
|
def get_tags(filepath: str, config: UserConfig):
|
||||||
"""
|
"""
|
||||||
Returns the tags for a given audio file.
|
Returns the tags for a given audio file.
|
||||||
"""
|
"""
|
||||||
@@ -173,17 +173,20 @@ def get_tags(filepath: str, artist_separators: set[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:
|
||||||
parse_data = extract_artist_title(filename, artist_separators)
|
parse_data = extract_artist_title(filename, config)
|
||||||
title = parse_data.title
|
title = parse_data.title.replace("_", " ")
|
||||||
setattr(tags, tag, title)
|
setattr(tags, tag, title)
|
||||||
|
|
||||||
|
# tags.title = tags.title.replace("_", " ")
|
||||||
|
# tags.album = tags.album.replace("_", " ")
|
||||||
|
|
||||||
parse = ["artist", "albumartist"]
|
parse = ["artist", "albumartist"]
|
||||||
for tag in parse:
|
for tag in parse:
|
||||||
p = getattr(tags, tag)
|
p = getattr(tags, tag)
|
||||||
|
|
||||||
if p == "" or p is None:
|
if p == "" or p is None:
|
||||||
if not parse_data:
|
if not parse_data:
|
||||||
parse_data = extract_artist_title(filename, artist_separators)
|
parse_data = extract_artist_title(filename, config)
|
||||||
|
|
||||||
artist = parse_data.artist
|
artist = parse_data.artist
|
||||||
|
|
||||||
@@ -229,112 +232,110 @@ def get_tags(filepath: str, artist_separators: set[str]):
|
|||||||
tags.artists = tags.artist
|
tags.artists = tags.artist
|
||||||
tags.albumartists = tags.albumartist
|
tags.albumartists = tags.albumartist
|
||||||
|
|
||||||
split_artist = split_artists(tags.artist, separators=artist_separators)
|
# split_artist = split_artists(tags.artist, separators=config.artistSeparators)
|
||||||
split_albumartists = split_artists(tags.albumartist, separators=artist_separators)
|
# split_albumartists = split_artists(tags.albumartist, separators=config.artistSeparators)
|
||||||
new_title = tags.title
|
# new_title = tags.title
|
||||||
|
|
||||||
# TODO: Figure out which is the best spot to create these hashes
|
# TODO: Figure out which is the best spot to create these hashes
|
||||||
# create albumhash using og_album
|
# create albumhash using og_album
|
||||||
tags.albumhash = create_hash(tags.album or "", tags.albumartist)
|
tags.albumhash = create_hash(tags.album or "", tags.albumartist)
|
||||||
|
|
||||||
config = UserConfig()
|
|
||||||
|
|
||||||
# extract featured artists
|
# extract featured artists
|
||||||
if config.extractFeaturedArtists:
|
# if config.extractFeaturedArtists:
|
||||||
feat, new_title = parse_feat_from_title(
|
# feat, new_title = parse_feat_from_title(
|
||||||
tags.title, separators=artist_separators
|
# tags.title, separators=config.artistSeparators
|
||||||
)
|
# )
|
||||||
original_lower = "-".join([create_hash(a) for a in split_artist])
|
# original_lower = "-".join([create_hash(a) for a in split_artist])
|
||||||
split_artist.extend(a for a in feat if create_hash(a) not in original_lower)
|
# split_artist.extend(a for a in feat if create_hash(a) not in original_lower)
|
||||||
|
|
||||||
# if no albumartist, assign to the first artist
|
# if no albumartist, assign to the first artist
|
||||||
if not tags.albumartist:
|
if not tags.albumartist:
|
||||||
tags.albumartist = split_artist[:1]
|
tags.albumartist = split_artists(tags.artist, config)[:1]
|
||||||
|
|
||||||
# create json objects for artists and albumartists
|
# create json objects for artists and albumartists
|
||||||
tags.artists = [
|
# tags.artists = [
|
||||||
{
|
# {
|
||||||
"artisthash": create_hash(a, decode=True),
|
# "artisthash": create_hash(a, decode=True),
|
||||||
"name": a,
|
# "name": a,
|
||||||
}
|
# }
|
||||||
for a in split_artist
|
# for a in split_artist
|
||||||
]
|
# ]
|
||||||
|
|
||||||
tags.albumartists = [
|
# tags.albumartists = [
|
||||||
{
|
# {
|
||||||
"artisthash": create_hash(a, decode=True),
|
# "artisthash": create_hash(a, decode=True),
|
||||||
"name": a,
|
# "name": a,
|
||||||
}
|
# }
|
||||||
for a in split_albumartists
|
# for a in split_albumartists
|
||||||
]
|
# ]
|
||||||
|
|
||||||
tags.artisthashes = list(
|
# tags.artisthashes = list(
|
||||||
{a["artisthash"] for a in tags.artists}
|
# {a["artisthash"] for a in tags.artists}
|
||||||
)
|
# )
|
||||||
|
|
||||||
# remove prod by
|
# remove prod by
|
||||||
if config.removeProdBy:
|
# if config.removeProdBy:
|
||||||
new_title = remove_prod(new_title)
|
# new_title = remove_prod(new_title)
|
||||||
|
|
||||||
# if track is a single, ie.
|
# if track is a single, ie.
|
||||||
# if og_title == album, rename album to new_title
|
# if og_title == album, rename album to new_title
|
||||||
if tags.title == tags.album:
|
# if tags.title == tags.album:
|
||||||
tags.album = new_title
|
# tags.album = new_title
|
||||||
|
|
||||||
# remove remaster from track title
|
# remove remaster from track title
|
||||||
if config.removeRemasterInfo:
|
# if config.removeRemasterInfo:
|
||||||
new_title = clean_title(new_title)
|
# new_title = clean_title(new_title)
|
||||||
|
|
||||||
# save final title
|
# save final title
|
||||||
tags.og_title = tags.title
|
# tags.og_title = tags.title
|
||||||
tags.title = new_title
|
# tags.title = new_title
|
||||||
tags.og_album = tags.album
|
# tags.og_album = tags.album
|
||||||
|
|
||||||
# clean album title
|
# clean album title
|
||||||
if config.cleanAlbumTitle:
|
# if config.cleanAlbumTitle:
|
||||||
tags.album, _ = get_base_title_and_versions(tags.album, get_versions=False)
|
# tags.album, _ = get_base_title_and_versions(tags.album, get_versions=False)
|
||||||
|
|
||||||
# merge album versions
|
# merge album versions
|
||||||
if config.mergeAlbums:
|
# if config.mergeAlbums:
|
||||||
tags.albumhash = create_hash(
|
# tags.albumhash = create_hash(
|
||||||
tags.album, *(a["name"] for a in tags.albumartists)
|
# tags.album, *(a["name"] for a in tags.albumartists)
|
||||||
)
|
# )
|
||||||
|
|
||||||
# process genres
|
# process genres
|
||||||
if tags.genre:
|
# if tags.genre:
|
||||||
src_genres: str = tags.genre
|
# src_genres: str = tags.genre
|
||||||
src_genres = src_genres.lower()
|
# src_genres = src_genres.lower()
|
||||||
# separators = {"/", ";", "&"}
|
# # separators = {"/", ";", "&"}
|
||||||
separators = set(config.genreSeparators)
|
# separators = set(config.genreSeparators)
|
||||||
|
|
||||||
contains_rnb = "r&b" in src_genres
|
# contains_rnb = "r&b" in src_genres
|
||||||
contains_rock = "rock & roll" in src_genres
|
# contains_rock = "rock & roll" in src_genres
|
||||||
|
|
||||||
if contains_rnb:
|
# if contains_rnb:
|
||||||
src_genres = src_genres.replace("r&b", "RnB")
|
# src_genres = src_genres.replace("r&b", "RnB")
|
||||||
|
|
||||||
if contains_rock:
|
# if contains_rock:
|
||||||
src_genres = src_genres.replace("rock & roll", "rock")
|
# src_genres = src_genres.replace("rock & roll", "rock")
|
||||||
|
|
||||||
for s in separators:
|
# for s in separators:
|
||||||
src_genres = src_genres.replace(s, ",")
|
# src_genres = src_genres.replace(s, ",")
|
||||||
|
|
||||||
genres_list: list[str] = src_genres.split(",")
|
# genres_list: list[str] = src_genres.split(",")
|
||||||
tags.genres = [
|
# tags.genres = [
|
||||||
{"name": g.strip(), "genrehash": create_hash(g.strip())}
|
# {"name": g.strip(), "genrehash": create_hash(g.strip())}
|
||||||
for g in genres_list
|
# for g in genres_list
|
||||||
]
|
# ]
|
||||||
tags.genrehashes = [g["genrehash"] for g in tags.genres]
|
# tags.genrehashes = [g["genrehash"] for g in tags.genres]
|
||||||
else:
|
# else:
|
||||||
tags.genres = []
|
# tags.genres = []
|
||||||
tags.genrehashes = []
|
# tags.genrehashes = []
|
||||||
|
|
||||||
|
tags.genres = tags.genre
|
||||||
|
|
||||||
# sub underscore with space
|
# sub underscore with space
|
||||||
tags.title = tags.title.replace("_", " ")
|
# tags.title = tags.title.replace("_", " ")
|
||||||
tags.album = tags.album.replace("_", " ")
|
# tags.album = tags.album.replace("_", " ")
|
||||||
tags.trackhash = create_hash(
|
tags.trackhash = create_hash(tags.artists, tags.album, tags.title)
|
||||||
*[a["name"] for a in tags.artists], tags.album, tags.title
|
|
||||||
)
|
|
||||||
|
|
||||||
more_extra = {
|
more_extra = {
|
||||||
"audio_offset": tags.audio_offset,
|
"audio_offset": tags.audio_offset,
|
||||||
|
|||||||
+125
-5
@@ -1,6 +1,15 @@
|
|||||||
from dataclasses import dataclass, field
|
from dataclasses import dataclass, field
|
||||||
|
|
||||||
|
from app.config import UserConfig
|
||||||
from app.utils.auth import get_current_userid
|
from app.utils.auth import get_current_userid
|
||||||
|
from app.utils.hashing import create_hash
|
||||||
|
from app.utils.parsers import (
|
||||||
|
clean_title,
|
||||||
|
get_base_title_and_versions,
|
||||||
|
parse_feat_from_title,
|
||||||
|
remove_prod,
|
||||||
|
split_artists,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
@dataclass(slots=True)
|
@dataclass(slots=True)
|
||||||
@@ -13,7 +22,6 @@ class Track:
|
|||||||
album: str
|
album: str
|
||||||
albumartists: list[dict[str, str]]
|
albumartists: list[dict[str, str]]
|
||||||
albumhash: str
|
albumhash: str
|
||||||
artisthashes: list[str]
|
|
||||||
artists: list[dict[str, str]]
|
artists: list[dict[str, str]]
|
||||||
bitrate: int
|
bitrate: int
|
||||||
copyright: str
|
copyright: str
|
||||||
@@ -22,11 +30,8 @@ class Track:
|
|||||||
duration: int
|
duration: int
|
||||||
filepath: str
|
filepath: str
|
||||||
folder: str
|
folder: str
|
||||||
genres: list[dict[str, str]]
|
genres: str | list[dict[str, str]]
|
||||||
genrehashes: list[str]
|
|
||||||
last_mod: int
|
last_mod: int
|
||||||
og_album: str
|
|
||||||
og_title: str
|
|
||||||
title: str
|
title: str
|
||||||
track: int
|
track: int
|
||||||
trackhash: str
|
trackhash: str
|
||||||
@@ -35,6 +40,12 @@ class Track:
|
|||||||
playcount: int
|
playcount: int
|
||||||
playduration: int
|
playduration: int
|
||||||
|
|
||||||
|
config: UserConfig
|
||||||
|
og_album: str = ""
|
||||||
|
og_title: str = ""
|
||||||
|
artisthashes: list[str] = field(default_factory=list)
|
||||||
|
genrehashes: list[str] = field(default_factory=list)
|
||||||
|
|
||||||
_pos: int = 0
|
_pos: int = 0
|
||||||
_ati: str = ""
|
_ati: str = ""
|
||||||
image: str = ""
|
image: str = ""
|
||||||
@@ -55,9 +66,118 @@ class Track:
|
|||||||
self.fav_userids.append(userid)
|
self.fav_userids.append(userid)
|
||||||
|
|
||||||
def __post_init__(self):
|
def __post_init__(self):
|
||||||
|
self.og_title = self.title
|
||||||
|
self.og_album = self.album
|
||||||
|
|
||||||
self.image = self.albumhash + ".webp"
|
self.image = self.albumhash + ".webp"
|
||||||
self.extra = {
|
self.extra = {
|
||||||
"disc_total": self.extra.get("disc_total", 0),
|
"disc_total": self.extra.get("disc_total", 0),
|
||||||
"track_total": self.extra.get("track_total", 0),
|
"track_total": self.extra.get("track_total", 0),
|
||||||
"samplerate": self.extra.get("samplerate", -1),
|
"samplerate": self.extra.get("samplerate", -1),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
self.split_artists()
|
||||||
|
self.map_with_config()
|
||||||
|
self.process_genres()
|
||||||
|
|
||||||
|
# Remove duplicates from artists and albumartists
|
||||||
|
seen_artists = set()
|
||||||
|
self.artists = [
|
||||||
|
d
|
||||||
|
for d in self.artists
|
||||||
|
if tuple(d.items()) not in seen_artists
|
||||||
|
and not seen_artists.add(tuple(d.items()))
|
||||||
|
]
|
||||||
|
|
||||||
|
seen_albumartists = set()
|
||||||
|
self.albumartists = [
|
||||||
|
d
|
||||||
|
for d in self.albumartists
|
||||||
|
if tuple(d.items()) not in seen_albumartists
|
||||||
|
and not seen_albumartists.add(tuple(d.items()))
|
||||||
|
]
|
||||||
|
|
||||||
|
self.config = None
|
||||||
|
|
||||||
|
def split_artists(self):
|
||||||
|
"""
|
||||||
|
Splits the artists and albumartists based on the given separators, and updates the artisthashes.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def split(artists: str):
|
||||||
|
return [
|
||||||
|
{"name": a, "artisthash": create_hash(a, decode=True)}
|
||||||
|
for a in split_artists(artists, config=self.config)
|
||||||
|
]
|
||||||
|
|
||||||
|
self.artists = split(self.artists)
|
||||||
|
self.albumartists = split(self.albumartists)
|
||||||
|
self.artisthashes = [a["artisthash"] for a in self.artists]
|
||||||
|
|
||||||
|
def map_with_config(self):
|
||||||
|
new_title = self.title
|
||||||
|
|
||||||
|
# Extract featured artists
|
||||||
|
if self.config.extractFeaturedArtists:
|
||||||
|
feat, new_title = parse_feat_from_title(self.title, self.config)
|
||||||
|
feat = [
|
||||||
|
{"name": f, "artisthash": create_hash(f, decode=True)} for f in feat
|
||||||
|
]
|
||||||
|
feat = [f for f in feat if f["artisthash"] not in self.artisthashes]
|
||||||
|
self.artists.extend(feat)
|
||||||
|
self.artisthashes.extend([f["artisthash"] for f in feat])
|
||||||
|
|
||||||
|
# Update album title for singles
|
||||||
|
# ie. album: "Title (feat. Artist)"
|
||||||
|
# title: "Title (feat. Artist)"
|
||||||
|
# becomes: album: "Title", title: "Title"
|
||||||
|
if self.og_album == self.og_title:
|
||||||
|
self.album = new_title
|
||||||
|
|
||||||
|
# Clean track title
|
||||||
|
if self.config.removeProdBy:
|
||||||
|
new_title = remove_prod(new_title)
|
||||||
|
|
||||||
|
# if self.title == new_title:
|
||||||
|
# self.album = new_title
|
||||||
|
|
||||||
|
if self.config.removeRemasterInfo:
|
||||||
|
new_title = clean_title(new_title)
|
||||||
|
|
||||||
|
self.title = new_title
|
||||||
|
|
||||||
|
# Clean album title
|
||||||
|
if self.config.cleanAlbumTitle:
|
||||||
|
self.album, _ = get_base_title_and_versions(self.album, get_versions=False)
|
||||||
|
|
||||||
|
if self.config.mergeAlbums:
|
||||||
|
self.albumhash = create_hash(
|
||||||
|
self.album, *(a["name"] for a in self.albumartists)
|
||||||
|
)
|
||||||
|
|
||||||
|
def process_genres(self):
|
||||||
|
if self.genres:
|
||||||
|
src_genres: str = self.genres
|
||||||
|
|
||||||
|
src_genres = src_genres.lower()
|
||||||
|
# separators = {"/", ";", "&"}
|
||||||
|
separators = set(self.config.genreSeparators)
|
||||||
|
|
||||||
|
contains_rnb = "r&b" in src_genres
|
||||||
|
contains_rock = "rock & roll" in src_genres
|
||||||
|
|
||||||
|
if contains_rnb:
|
||||||
|
src_genres = src_genres.replace("r&b", "RnB")
|
||||||
|
|
||||||
|
if contains_rock:
|
||||||
|
src_genres = src_genres.replace("rock & roll", "rock")
|
||||||
|
|
||||||
|
for s in separators:
|
||||||
|
src_genres = src_genres.replace(s, ",")
|
||||||
|
|
||||||
|
genres_list: list[str] = src_genres.split(",")
|
||||||
|
self.genres = [
|
||||||
|
{"name": g.strip(), "genrehash": create_hash(g.strip())}
|
||||||
|
for g in genres_list
|
||||||
|
]
|
||||||
|
self.genrehashes = [g["genrehash"] for g in self.genres]
|
||||||
|
|||||||
@@ -215,8 +215,6 @@ class TrackStore:
|
|||||||
def get_tracks_by_filepaths(cls, paths: list[str]) -> list[Track]:
|
def get_tracks_by_filepaths(cls, paths: list[str]) -> list[Track]:
|
||||||
"""
|
"""
|
||||||
Returns all tracks matching the given paths.
|
Returns all tracks matching the given paths.
|
||||||
|
|
||||||
⛔⛔⛔⛔⛔⛔⛔⛔⛔⛔⛔⛔⛔⛔⛔⛔⛔⛔⛔⛔⛔⛔
|
|
||||||
"""
|
"""
|
||||||
# tracks = sorted(cls.trackhashmap, key=lambda x: x.filepath)
|
# tracks = sorted(cls.trackhashmap, key=lambda x: x.filepath)
|
||||||
# tracks = use_bisection(tracks, "filepath", paths)
|
# tracks = use_bisection(tracks, "filepath", paths)
|
||||||
|
|||||||
+44
-9
@@ -1,19 +1,54 @@
|
|||||||
import re
|
import re
|
||||||
|
|
||||||
|
from app.config import UserConfig
|
||||||
from app.enums.album_versions import AlbumVersionEnum, get_all_keywords
|
from app.enums.album_versions import AlbumVersionEnum, get_all_keywords
|
||||||
|
|
||||||
|
|
||||||
def split_artists(src: str, separators: set[str]):
|
def split_artists(src: str, config: UserConfig):
|
||||||
"""
|
"""
|
||||||
Splits a string of artists into a list of artists.
|
Splits a string of artists into a list of artists, preserving those in ignoreList.
|
||||||
|
Case-insensitive matching is used for the ignoreList.
|
||||||
"""
|
"""
|
||||||
for sep in separators:
|
result = []
|
||||||
src = src.replace(sep, ",")
|
current = ""
|
||||||
|
i = 0
|
||||||
|
|
||||||
artists = src.split(",")
|
while i < len(src):
|
||||||
artists = [a.strip() for a in artists]
|
# Check if any ignored artist starts at this position (case-insensitive)
|
||||||
|
ignored_match = next(
|
||||||
|
(
|
||||||
|
src[i : i + len(ignored)]
|
||||||
|
for ignored in config.artistSplitIgnoreList
|
||||||
|
if src.lower().startswith(ignored.lower(), i)
|
||||||
|
),
|
||||||
|
None,
|
||||||
|
)
|
||||||
|
|
||||||
return [a for a in artists if a]
|
if ignored_match:
|
||||||
|
# If we have accumulated any current string, add it to result
|
||||||
|
if current.strip():
|
||||||
|
result.extend([a.strip() for a in current.split(",") if a.strip()])
|
||||||
|
current = ""
|
||||||
|
# Add the ignored artist to the result (preserving original case)
|
||||||
|
result.append(ignored_match)
|
||||||
|
# Move past the ignored artist
|
||||||
|
i += len(ignored_match)
|
||||||
|
elif src[i] in config.artistSeparators:
|
||||||
|
# If we encounter a separator, process the current string
|
||||||
|
if current.strip():
|
||||||
|
result.extend([a.strip() for a in current.split(",") if a.strip()])
|
||||||
|
current = ""
|
||||||
|
i += 1
|
||||||
|
else:
|
||||||
|
# If it's not an ignored artist or a separator, add to current
|
||||||
|
current += src[i]
|
||||||
|
i += 1
|
||||||
|
|
||||||
|
# Process any remaining current string
|
||||||
|
if current.strip():
|
||||||
|
result.extend([a.strip() for a in current.split(",") if a.strip()])
|
||||||
|
|
||||||
|
return result
|
||||||
|
|
||||||
|
|
||||||
def remove_prod(title: str) -> str:
|
def remove_prod(title: str) -> str:
|
||||||
@@ -36,7 +71,7 @@ def remove_prod(title: str) -> str:
|
|||||||
return title.strip()
|
return title.strip()
|
||||||
|
|
||||||
|
|
||||||
def parse_feat_from_title(title: str, separators: set[str]) -> tuple[list[str], str]:
|
def parse_feat_from_title(title: str, config: UserConfig) -> tuple[list[str], str]:
|
||||||
"""
|
"""
|
||||||
Extracts featured artists from a song title using regex.
|
Extracts featured artists from a song title using regex.
|
||||||
"""
|
"""
|
||||||
@@ -54,7 +89,7 @@ def parse_feat_from_title(title: str, separators: set[str]) -> tuple[list[str],
|
|||||||
return [], title
|
return [], title
|
||||||
|
|
||||||
artists = match.group(1)
|
artists = match.group(1)
|
||||||
artists = split_artists(artists, separators)
|
artists = split_artists(artists, config)
|
||||||
|
|
||||||
# remove "feat" group from title
|
# remove "feat" group from title
|
||||||
new_title = re.sub(regex, "", title, flags=re.IGNORECASE)
|
new_title = re.sub(regex, "", title, flags=re.IGNORECASE)
|
||||||
|
|||||||
@@ -0,0 +1,137 @@
|
|||||||
|
import unittest
|
||||||
|
|
||||||
|
def split_artists(src: str, separators: set[str], ignoreList: set[str] = set()):
|
||||||
|
"""
|
||||||
|
Splits a string of artists into a list of artists, preserving those in ignoreList.
|
||||||
|
Case-insensitive matching is used for the ignoreList.
|
||||||
|
"""
|
||||||
|
result = []
|
||||||
|
current = ""
|
||||||
|
i = 0
|
||||||
|
|
||||||
|
# Convert ignoreList to lowercase for case-insensitive matching
|
||||||
|
ignore_lower = {artist.lower() for artist in ignoreList}
|
||||||
|
|
||||||
|
while i < len(src):
|
||||||
|
# Check if any ignored artist starts at this position (case-insensitive)
|
||||||
|
ignored_match = next(
|
||||||
|
(
|
||||||
|
src[i:i+len(ignored)]
|
||||||
|
for ignored in ignoreList
|
||||||
|
if src.lower().startswith(ignored.lower(), i)
|
||||||
|
),
|
||||||
|
None
|
||||||
|
)
|
||||||
|
|
||||||
|
if ignored_match:
|
||||||
|
# If we have accumulated any current string, add it to result
|
||||||
|
if current.strip():
|
||||||
|
result.extend([a.strip() for a in current.split(',') if a.strip()])
|
||||||
|
current = ""
|
||||||
|
# Add the ignored artist to the result (preserving original case)
|
||||||
|
result.append(ignored_match)
|
||||||
|
# Move past the ignored artist
|
||||||
|
i += len(ignored_match)
|
||||||
|
elif src[i] in separators:
|
||||||
|
# If we encounter a separator, process the current string
|
||||||
|
if current.strip():
|
||||||
|
result.extend([a.strip() for a in current.split(',') if a.strip()])
|
||||||
|
current = ""
|
||||||
|
i += 1
|
||||||
|
else:
|
||||||
|
# If it's not an ignored artist or a separator, add to current
|
||||||
|
current += src[i]
|
||||||
|
i += 1
|
||||||
|
|
||||||
|
# Process any remaining current string
|
||||||
|
if current.strip():
|
||||||
|
result.extend([a.strip() for a in current.split(',') if a.strip()])
|
||||||
|
|
||||||
|
return result
|
||||||
|
|
||||||
|
|
||||||
|
class TestSplitArtists(unittest.TestCase):
|
||||||
|
|
||||||
|
def test_basic_splitting(self):
|
||||||
|
self.assertEqual(
|
||||||
|
split_artists("Beatles, Queen; Rolling Stones", {";"}),
|
||||||
|
["Beatles", "Queen", "Rolling Stones"],
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_multiple_separators(self):
|
||||||
|
self.assertEqual(
|
||||||
|
split_artists("Beatles; Queen & Rolling Stones | ABBA", {";", "&", "|"}),
|
||||||
|
["Beatles", "Queen", "Rolling Stones", "ABBA"],
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_ignore_list(self):
|
||||||
|
self.assertEqual(
|
||||||
|
split_artists(
|
||||||
|
"Beatles; Earth, Wind & Fire; Queen", {";", "&"}, {"Earth, Wind & Fire"}
|
||||||
|
),
|
||||||
|
["Beatles", "Earth, Wind & Fire", "Queen"],
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_empty_string(self):
|
||||||
|
self.assertEqual(split_artists("", {";"}), [])
|
||||||
|
|
||||||
|
def test_only_separators(self):
|
||||||
|
self.assertEqual(split_artists(";;;", {";"}), [])
|
||||||
|
|
||||||
|
def test_extra_spaces(self):
|
||||||
|
self.assertEqual(
|
||||||
|
split_artists(" Beatles ; Queen ", {";"}), ["Beatles", "Queen"]
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_comma_splitting(self):
|
||||||
|
self.assertEqual(
|
||||||
|
split_artists("Beatles, Queen; Rolling Stones, ABBA", {";"}),
|
||||||
|
["Beatles", "Queen", "Rolling Stones", "ABBA"],
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_ignore_list_with_comma(self):
|
||||||
|
self.assertEqual(
|
||||||
|
split_artists(
|
||||||
|
"Beatles; Earth, Wind & Fire, Queen", {";"}, {"Earth, Wind & Fire"}
|
||||||
|
),
|
||||||
|
["Beatles", "Earth, Wind & Fire", "Queen"],
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_ignore_list_with_separator(self):
|
||||||
|
self.assertEqual(
|
||||||
|
split_artists("Beatles; AC/DC", {"/", ";"}, {"AC/DC"}), ["Beatles", "AC/DC"]
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_ignore_list_at_start(self):
|
||||||
|
self.assertEqual(
|
||||||
|
split_artists("AC/DC; Beatles", {"/", ";"}, {"AC/DC"}), ["AC/DC", "Beatles"]
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_ignore_list_at_end(self):
|
||||||
|
self.assertEqual(
|
||||||
|
split_artists("Beatles; AC/DC", {"/", ";"}, {"AC/DC"}), ["Beatles", "AC/DC"]
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_multiple_ignored_artists(self):
|
||||||
|
self.assertEqual(
|
||||||
|
split_artists(
|
||||||
|
"Beatles; AC/DC; Guns N' Roses; Queen",
|
||||||
|
{"/", ";", "'"},
|
||||||
|
{"AC/DC", "Guns N' Roses"},
|
||||||
|
),
|
||||||
|
["Beatles", "AC/DC", "Guns N' Roses", "Queen"],
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_bob_marley(self):
|
||||||
|
self.assertEqual(
|
||||||
|
split_artists(
|
||||||
|
"Bob marley & The wailers; Beatles",
|
||||||
|
{";", "&"},
|
||||||
|
{"Bob marley & the wailers"},
|
||||||
|
),
|
||||||
|
["Bob marley & The wailers", "Beatles"],
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
unittest.main()
|
||||||
Reference in New Issue
Block a user