mirror of
https://github.com/tcsenpai/swingmusic.git
synced 2025-06-07 03:35:35 +00:00
prevent running migrations if is_fresh_install
+ fix: sqlite3.ProgrammingError: Cannot operate on a closed cursor on ProcessAlbumColors() + move processing artist images from periodic_scans to Populate + bump hash string limit from 7 to 10 + add last_mod property to database + fix: TypeError: '<' not supported between instances of 'int' and 'str' on album page
This commit is contained in:
parent
cc6552cb94
commit
1eac009fde
@ -18,7 +18,6 @@ class SQLiteAlbumMethods:
|
|||||||
|
|
||||||
cur.execute(sql, (albumhash, colors))
|
cur.execute(sql, (albumhash, colors))
|
||||||
lastrowid = cur.lastrowid
|
lastrowid = cur.lastrowid
|
||||||
cur.close()
|
|
||||||
|
|
||||||
return lastrowid
|
return lastrowid
|
||||||
|
|
||||||
|
@ -21,7 +21,6 @@ class SQLiteArtistMethods:
|
|||||||
"""
|
"""
|
||||||
colors = json.dumps(colors)
|
colors = json.dumps(colors)
|
||||||
cur.execute(sql, (artisthash, colors))
|
cur.execute(sql, (artisthash, colors))
|
||||||
cur.close()
|
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def get_all_artists():
|
def get_all_artists():
|
||||||
|
@ -37,12 +37,13 @@ CREATE TABLE IF NOT EXISTS tracks (
|
|||||||
artist text NOT NULL,
|
artist text NOT NULL,
|
||||||
bitrate integer NOT NULL,
|
bitrate integer NOT NULL,
|
||||||
copyright text,
|
copyright text,
|
||||||
date text NOT NULL,
|
date integer NOT NULL,
|
||||||
disc integer NOT NULL,
|
disc integer NOT NULL,
|
||||||
duration integer NOT NULL,
|
duration integer NOT NULL,
|
||||||
filepath text NOT NULL,
|
filepath text NOT NULL,
|
||||||
folder text NOT NULL,
|
folder text NOT NULL,
|
||||||
genre text,
|
genre text,
|
||||||
|
last_mod float NOT NULL,
|
||||||
title text NOT NULL,
|
title text NOT NULL,
|
||||||
track integer NOT NULL,
|
track integer NOT NULL,
|
||||||
trackhash text NOT NULL,
|
trackhash text NOT NULL,
|
||||||
|
@ -34,11 +34,12 @@ class SQLiteTrackMethods:
|
|||||||
filepath,
|
filepath,
|
||||||
folder,
|
folder,
|
||||||
genre,
|
genre,
|
||||||
|
last_mod,
|
||||||
title,
|
title,
|
||||||
track,
|
track,
|
||||||
trackhash
|
trackhash
|
||||||
) VALUES(:album, :albumartist, :albumhash, :artist, :bitrate, :copyright,
|
) VALUES(:album, :albumartist, :albumhash, :artist, :bitrate, :copyright,
|
||||||
:date, :disc, :duration, :filepath, :folder, :genre, :title, :track, :trackhash)
|
:date, :disc, :duration, :filepath, :folder, :genre, :last_mod, :title, :track, :trackhash)
|
||||||
"""
|
"""
|
||||||
|
|
||||||
track = OrderedDict(sorted(track.items()))
|
track = OrderedDict(sorted(track.items()))
|
||||||
@ -83,12 +84,16 @@ class SQLiteTrackMethods:
|
|||||||
return None
|
return None
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def remove_track_by_filepath(filepath: str):
|
def remove_tracks_by_filepaths(filepaths: str | list[str]):
|
||||||
"""
|
"""
|
||||||
Removes a track from the database using its filepath.
|
Removes a track or tracks from the database using their filepaths.
|
||||||
"""
|
"""
|
||||||
|
if isinstance(filepaths, str):
|
||||||
|
filepaths = [filepaths]
|
||||||
|
|
||||||
with SQLiteManager() as cur:
|
with SQLiteManager() as cur:
|
||||||
cur.execute("DELETE FROM tracks WHERE filepath=?", (filepath,))
|
for filepath in filepaths:
|
||||||
|
cur.execute("DELETE FROM tracks WHERE filepath=?", (filepath,))
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def remove_tracks_by_folders(folders: set[str]):
|
def remove_tracks_by_folders(folders: set[str]):
|
||||||
|
@ -79,7 +79,7 @@ class CheckArtistImages:
|
|||||||
tqdm(
|
tqdm(
|
||||||
pool.map(self.download_image, artist_store.ArtistStore.artists),
|
pool.map(self.download_image, artist_store.ArtistStore.artists),
|
||||||
total=len(artist_store.ArtistStore.artists),
|
total=len(artist_store.ArtistStore.artists),
|
||||||
desc="Downloading artist images",
|
desc="Downloading missing artist images",
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -34,7 +34,11 @@ def get_image_colors(image: str, count=1) -> list[str]:
|
|||||||
|
|
||||||
|
|
||||||
def process_color(item_hash: str, is_album=True):
|
def process_color(item_hash: str, is_album=True):
|
||||||
path = settings.Paths.get_sm_thumb_path() if is_album else settings.Paths.get_artist_img_sm_path()
|
path = (
|
||||||
|
settings.Paths.get_sm_thumb_path()
|
||||||
|
if is_album
|
||||||
|
else settings.Paths.get_artist_img_sm_path()
|
||||||
|
)
|
||||||
path = Path(path) / (item_hash + ".webp")
|
path = Path(path) / (item_hash + ".webp")
|
||||||
|
|
||||||
if not path.exists():
|
if not path.exists():
|
||||||
@ -69,6 +73,8 @@ class ProcessAlbumColors:
|
|||||||
color_str = json.dumps(colors)
|
color_str = json.dumps(colors)
|
||||||
db.insert_one_album(cur, album.albumhash, color_str)
|
db.insert_one_album(cur, album.albumhash, color_str)
|
||||||
|
|
||||||
|
cur.close()
|
||||||
|
|
||||||
|
|
||||||
class ProcessArtistColors:
|
class ProcessArtistColors:
|
||||||
"""
|
"""
|
||||||
@ -95,3 +101,5 @@ class ProcessArtistColors:
|
|||||||
|
|
||||||
artist.set_colors(colors)
|
artist.set_colors(colors)
|
||||||
adb.insert_one_artist(cur, artist.artisthash, colors)
|
adb.insert_one_artist(cur, artist.artisthash, colors)
|
||||||
|
|
||||||
|
cur.close()
|
||||||
|
@ -1,10 +1,14 @@
|
|||||||
from concurrent.futures import ThreadPoolExecutor
|
import os
|
||||||
from tqdm import tqdm
|
from tqdm import tqdm
|
||||||
|
from concurrent.futures import ThreadPoolExecutor
|
||||||
|
from requests import ConnectionError as RequestConnectionError
|
||||||
|
from requests import ReadTimeout
|
||||||
|
|
||||||
from app import settings
|
from app import settings
|
||||||
from app.db.sqlite.tracks import SQLiteTrackMethods
|
from app.db.sqlite.tracks import SQLiteTrackMethods
|
||||||
from app.db.sqlite.settings import SettingsSQLMethods as sdb
|
from app.db.sqlite.settings import SettingsSQLMethods as sdb
|
||||||
from app.db.sqlite.favorite import SQLiteFavoriteMethods as favdb
|
from app.db.sqlite.favorite import SQLiteFavoriteMethods as favdb
|
||||||
|
from app.lib.artistlib import CheckArtistImages
|
||||||
from app.lib.colorlib import ProcessAlbumColors, ProcessArtistColors
|
from app.lib.colorlib import ProcessAlbumColors, ProcessArtistColors
|
||||||
|
|
||||||
from app.lib.taglib import extract_thumb, get_tags
|
from app.lib.taglib import extract_thumb, get_tags
|
||||||
@ -16,9 +20,11 @@ from app.utils.filesystem import run_fast_scandir
|
|||||||
from app.store.albums import AlbumStore
|
from app.store.albums import AlbumStore
|
||||||
from app.store.tracks import TrackStore
|
from app.store.tracks import TrackStore
|
||||||
from app.store.artists import ArtistStore
|
from app.store.artists import ArtistStore
|
||||||
|
from app.utils.network import Ping
|
||||||
|
|
||||||
get_all_tracks = SQLiteTrackMethods.get_all_tracks
|
get_all_tracks = SQLiteTrackMethods.get_all_tracks
|
||||||
insert_many_tracks = SQLiteTrackMethods.insert_many_tracks
|
insert_many_tracks = SQLiteTrackMethods.insert_many_tracks
|
||||||
|
remove_tracks_by_filepaths = SQLiteTrackMethods.remove_tracks_by_filepaths
|
||||||
|
|
||||||
POPULATE_KEY = ""
|
POPULATE_KEY = ""
|
||||||
|
|
||||||
@ -48,8 +54,8 @@ class Populate:
|
|||||||
if len(dirs_to_scan) == 0:
|
if len(dirs_to_scan) == 0:
|
||||||
log.warning(
|
log.warning(
|
||||||
(
|
(
|
||||||
"The root directory is not configured. "
|
"The root directory is not configured. "
|
||||||
+ "Open the app in your webbrowser to configure."
|
+ "Open the app in your webbrowser to configure."
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
return
|
return
|
||||||
@ -65,18 +71,44 @@ class Populate:
|
|||||||
for _dir in dirs_to_scan:
|
for _dir in dirs_to_scan:
|
||||||
files.extend(run_fast_scandir(_dir, full=True)[1])
|
files.extend(run_fast_scandir(_dir, full=True)[1])
|
||||||
|
|
||||||
|
self.remove_modified(tracks)
|
||||||
untagged = self.filter_untagged(tracks, files)
|
untagged = self.filter_untagged(tracks, files)
|
||||||
|
|
||||||
if len(untagged) == 0:
|
if len(untagged) != 0:
|
||||||
log.info("All clear, no unread files.")
|
self.tag_untagged(untagged, key)
|
||||||
return
|
|
||||||
|
|
||||||
self.tag_untagged(untagged, key)
|
|
||||||
|
|
||||||
ProcessTrackThumbnails()
|
ProcessTrackThumbnails()
|
||||||
ProcessAlbumColors()
|
ProcessAlbumColors()
|
||||||
ProcessArtistColors()
|
ProcessArtistColors()
|
||||||
|
|
||||||
|
tried_to_download_new_images = False
|
||||||
|
|
||||||
|
if Ping()():
|
||||||
|
tried_to_download_new_images = True
|
||||||
|
try:
|
||||||
|
CheckArtistImages()
|
||||||
|
except (RequestConnectionError, ReadTimeout):
|
||||||
|
log.error(
|
||||||
|
"Internet connection lost. Downloading artist images stopped."
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
log.warning(
|
||||||
|
f"No internet connection. Downloading artist images halted for {settings.get_scan_sleep_time()} seconds."
|
||||||
|
)
|
||||||
|
|
||||||
|
# Re-process the new artist images.
|
||||||
|
if tried_to_download_new_images:
|
||||||
|
ProcessArtistColors()
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def remove_modified(tracks: list[Track]):
|
||||||
|
modified = [
|
||||||
|
t.filepath for t in tracks if t.last_mod != os.path.getmtime(t.filepath)
|
||||||
|
]
|
||||||
|
|
||||||
|
TrackStore.remove_tracks_by_filepaths(modified)
|
||||||
|
remove_tracks_by_filepaths(modified)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def filter_untagged(tracks: list[Track], files: list[str]):
|
def filter_untagged(tracks: list[Track], files: list[str]):
|
||||||
tagged_files = [t.filepath for t in tracks]
|
tagged_files = [t.filepath for t in tracks]
|
||||||
@ -120,6 +152,7 @@ class Populate:
|
|||||||
log.warning("Could not read file: %s", file)
|
log.warning("Could not read file: %s", file)
|
||||||
|
|
||||||
if len(tagged_tracks) > 0:
|
if len(tagged_tracks) > 0:
|
||||||
|
log.info("Adding %s tracks to database", len(tagged_tracks))
|
||||||
insert_many_tracks(tagged_tracks)
|
insert_many_tracks(tagged_tracks)
|
||||||
|
|
||||||
log.info("Added %s/%s tracks", tagged_count, len(untagged))
|
log.info("Added %s/%s tracks", tagged_count, len(untagged))
|
||||||
|
@ -64,17 +64,18 @@ def extract_thumb(filepath: str, webp_path: str) -> bool:
|
|||||||
return False
|
return False
|
||||||
|
|
||||||
|
|
||||||
def extract_date(date_str: str | None, filepath: str) -> int:
|
def extract_date(date_str: str | None, timestamp: float) -> int:
|
||||||
try:
|
try:
|
||||||
return int(date_str.split("-")[0])
|
return int(date_str.split("-")[0])
|
||||||
except: # pylint: disable=bare-except
|
except: # pylint: disable=bare-except
|
||||||
# TODO: USE FILEPATH LAST-MOD DATE instead of current date
|
print(datetime.datetime.fromtimestamp(timestamp).year)
|
||||||
return datetime.date.today().today().year
|
return datetime.datetime.fromtimestamp(timestamp).year
|
||||||
|
|
||||||
|
|
||||||
def get_tags(filepath: str):
|
def get_tags(filepath: str):
|
||||||
filetype = filepath.split(".")[-1]
|
filetype = filepath.split(".")[-1]
|
||||||
filename = (filepath.split("/")[-1]).replace(f".{filetype}", "")
|
filename = (filepath.split("/")[-1]).replace(f".{filetype}", "")
|
||||||
|
last_mod = os.path.getmtime(filepath)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
tags = TinyTag.get(filepath)
|
tags = TinyTag.get(filepath)
|
||||||
@ -141,9 +142,10 @@ def get_tags(filepath: str):
|
|||||||
tags.image = f"{tags.albumhash}.webp"
|
tags.image = f"{tags.albumhash}.webp"
|
||||||
tags.folder = win_replace_slash(os.path.dirname(filepath))
|
tags.folder = win_replace_slash(os.path.dirname(filepath))
|
||||||
|
|
||||||
tags.date = extract_date(tags.year, filepath)
|
tags.date = extract_date(tags.year, last_mod)
|
||||||
tags.filepath = win_replace_slash(filepath)
|
tags.filepath = win_replace_slash(filepath)
|
||||||
tags.filetype = filetype
|
tags.filetype = filetype
|
||||||
|
tags.last_mod = last_mod
|
||||||
|
|
||||||
tags = tags.__dict__
|
tags = tags.__dict__
|
||||||
|
|
||||||
|
@ -16,4 +16,4 @@ def validate_tracks() -> None:
|
|||||||
for track in tqdm(TrackStore.tracks, desc="Checking for deleted tracks"):
|
for track in tqdm(TrackStore.tracks, desc="Checking for deleted tracks"):
|
||||||
if not os.path.exists(track.filepath):
|
if not os.path.exists(track.filepath):
|
||||||
TrackStore.tracks.remove(track)
|
TrackStore.tracks.remove(track)
|
||||||
tdb.remove_track_by_filepath(track.filepath)
|
tdb.remove_tracks_by_filepaths(track.filepath)
|
||||||
|
@ -8,18 +8,16 @@ import time
|
|||||||
from watchdog.events import PatternMatchingEventHandler
|
from watchdog.events import PatternMatchingEventHandler
|
||||||
from watchdog.observers import Observer
|
from watchdog.observers import Observer
|
||||||
|
|
||||||
from app.logger import log
|
|
||||||
from app.lib.taglib import get_tags
|
|
||||||
from app.models import Artist, Track
|
|
||||||
from app import settings
|
from app import settings
|
||||||
|
from app.db.sqlite.settings import SettingsSQLMethods as sdb
|
||||||
from app.db.sqlite.tracks import SQLiteManager
|
from app.db.sqlite.tracks import SQLiteManager
|
||||||
from app.db.sqlite.tracks import SQLiteTrackMethods as db
|
from app.db.sqlite.tracks import SQLiteTrackMethods as db
|
||||||
from app.db.sqlite.settings import SettingsSQLMethods as sdb
|
from app.lib.taglib import get_tags
|
||||||
|
from app.logger import log
|
||||||
from app.store.tracks import TrackStore
|
from app.models import Artist, Track
|
||||||
from app.store.albums import AlbumStore
|
from app.store.albums import AlbumStore
|
||||||
from app.store.artists import ArtistStore
|
from app.store.artists import ArtistStore
|
||||||
|
from app.store.tracks import TrackStore
|
||||||
|
|
||||||
|
|
||||||
class Watcher:
|
class Watcher:
|
||||||
@ -133,7 +131,7 @@ def add_track(filepath: str) -> None:
|
|||||||
"""
|
"""
|
||||||
# remove the track if it already exists
|
# remove the track if it already exists
|
||||||
TrackStore.remove_track_by_filepath(filepath)
|
TrackStore.remove_track_by_filepath(filepath)
|
||||||
db.remove_track_by_filepath(filepath)
|
db.remove_tracks_by_filepaths(filepath)
|
||||||
|
|
||||||
# add the track to the database and store.
|
# add the track to the database and store.
|
||||||
tags = get_tags(filepath)
|
tags = get_tags(filepath)
|
||||||
@ -142,7 +140,6 @@ def add_track(filepath: str) -> None:
|
|||||||
return
|
return
|
||||||
|
|
||||||
with SQLiteManager() as cur:
|
with SQLiteManager() as cur:
|
||||||
db.remove_track_by_filepath(tags["filepath"])
|
|
||||||
db.insert_one_track(tags, cur)
|
db.insert_one_track(tags, cur)
|
||||||
|
|
||||||
track = Track(**tags)
|
track = Track(**tags)
|
||||||
@ -168,7 +165,7 @@ def remove_track(filepath: str) -> None:
|
|||||||
except IndexError:
|
except IndexError:
|
||||||
return
|
return
|
||||||
|
|
||||||
db.remove_track_by_filepath(filepath)
|
db.remove_tracks_by_filepaths(filepath)
|
||||||
TrackStore.remove_track_by_filepath(filepath)
|
TrackStore.remove_track_by_filepath(filepath)
|
||||||
|
|
||||||
empty_album = TrackStore.count_tracks_by_hash(track.albumhash) > 0
|
empty_album = TrackStore.count_tracks_by_hash(track.albumhash) > 0
|
||||||
|
@ -24,6 +24,10 @@ def apply_migrations():
|
|||||||
userdb_version = MigrationManager.get_userdatadb_postinit_version()
|
userdb_version = MigrationManager.get_userdatadb_postinit_version()
|
||||||
maindb_version = MigrationManager.get_maindb_postinit_version()
|
maindb_version = MigrationManager.get_maindb_postinit_version()
|
||||||
|
|
||||||
|
# No migrations to run
|
||||||
|
if userdb_version == 0 and maindb_version == 0:
|
||||||
|
return
|
||||||
|
|
||||||
for migration in main_db_migrations:
|
for migration in main_db_migrations:
|
||||||
if migration.version > maindb_version:
|
if migration.version > maindb_version:
|
||||||
log.info("Running new MAIN-DB post-init migration: %s", migration.name)
|
log.info("Running new MAIN-DB post-init migration: %s", migration.name)
|
||||||
|
@ -28,6 +28,10 @@ def run_preinit_migrations():
|
|||||||
except OperationalError:
|
except OperationalError:
|
||||||
userdb_version = 0
|
userdb_version = 0
|
||||||
|
|
||||||
|
# No migrations to run
|
||||||
|
if userdb_version == 0:
|
||||||
|
return
|
||||||
|
|
||||||
for migration in all_preinits:
|
for migration in all_preinits:
|
||||||
if migration.version > userdb_version:
|
if migration.version > userdb_version:
|
||||||
log.warn("Running new pre-init migration: %s", migration.name)
|
log.warn("Running new pre-init migration: %s", migration.name)
|
||||||
|
@ -19,12 +19,13 @@ class Track:
|
|||||||
artist: str | list[ArtistMinimal]
|
artist: str | list[ArtistMinimal]
|
||||||
bitrate: int
|
bitrate: int
|
||||||
copyright: str
|
copyright: str
|
||||||
date: str
|
date: int
|
||||||
disc: int
|
disc: int
|
||||||
duration: int
|
duration: int
|
||||||
filepath: str
|
filepath: str
|
||||||
folder: str
|
folder: str
|
||||||
genre: str | list[str]
|
genre: str | list[str]
|
||||||
|
last_mod: float
|
||||||
title: str
|
title: str
|
||||||
track: int
|
track: int
|
||||||
trackhash: str
|
trackhash: str
|
||||||
|
@ -2,11 +2,8 @@
|
|||||||
This module contains functions for the server
|
This module contains functions for the server
|
||||||
"""
|
"""
|
||||||
import time
|
import time
|
||||||
from requests import ReadTimeout
|
|
||||||
from requests import ConnectionError as RequestConnectionError
|
|
||||||
|
|
||||||
from app.logger import log
|
from app.logger import log
|
||||||
from app.lib.artistlib import CheckArtistImages
|
|
||||||
from app.lib.populate import Populate, PopulateCancelledError
|
from app.lib.populate import Populate, PopulateCancelledError
|
||||||
|
|
||||||
from app.utils.generators import get_random_str
|
from app.utils.generators import get_random_str
|
||||||
@ -30,13 +27,5 @@ def run_periodic_scans():
|
|||||||
except PopulateCancelledError:
|
except PopulateCancelledError:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
if Ping()():
|
|
||||||
try:
|
|
||||||
CheckArtistImages()
|
|
||||||
except (RequestConnectionError, ReadTimeout):
|
|
||||||
log.error(
|
|
||||||
"Internet connection lost. Downloading artist images stopped."
|
|
||||||
)
|
|
||||||
|
|
||||||
sleep_time = get_scan_sleep_time()
|
sleep_time = get_scan_sleep_time()
|
||||||
time.sleep(sleep_time)
|
time.sleep(sleep_time)
|
||||||
|
@ -52,6 +52,18 @@ class TrackStore:
|
|||||||
cls.tracks.remove(track)
|
cls.tracks.remove(track)
|
||||||
break
|
break
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def remove_tracks_by_filepaths(cls, filepaths: list[str]):
|
||||||
|
"""
|
||||||
|
Removes multiple tracks from the store by their filepaths.
|
||||||
|
"""
|
||||||
|
|
||||||
|
paths_str = "~".join(filepaths)
|
||||||
|
|
||||||
|
for track in cls.tracks:
|
||||||
|
if track.filepath in paths_str:
|
||||||
|
cls.tracks.remove(track)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def remove_tracks_by_dir_except(cls, dirs: list[str]):
|
def remove_tracks_by_dir_except(cls, dirs: list[str]):
|
||||||
"""Removes all tracks not in the root directories."""
|
"""Removes all tracks not in the root directories."""
|
||||||
@ -98,7 +110,9 @@ class TrackStore:
|
|||||||
track.is_favorite = False
|
track.is_favorite = False
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def append_track_artists(cls, albumhash: str, artists: list[str], new_album_title: str):
|
def append_track_artists(
|
||||||
|
cls, albumhash: str, artists: list[str], new_album_title: str
|
||||||
|
):
|
||||||
tracks = cls.get_tracks_by_albumhash(albumhash)
|
tracks = cls.get_tracks_by_albumhash(albumhash)
|
||||||
|
|
||||||
for track in tracks:
|
for track in tracks:
|
||||||
|
@ -3,7 +3,7 @@ import hashlib
|
|||||||
from unidecode import unidecode
|
from unidecode import unidecode
|
||||||
|
|
||||||
|
|
||||||
def create_hash(*args: str, decode=False, limit=7) -> str:
|
def create_hash(*args: str, decode=False, limit=10) -> str:
|
||||||
"""
|
"""
|
||||||
Creates a simple hash for an album
|
Creates a simple hash for an album
|
||||||
"""
|
"""
|
||||||
@ -19,7 +19,7 @@ def create_hash(*args: str, decode=False, limit=7) -> str:
|
|||||||
return str_[-limit:]
|
return str_[-limit:]
|
||||||
|
|
||||||
|
|
||||||
def create_folder_hash(*args: str, limit=7) -> str:
|
def create_folder_hash(*args: str, limit=10) -> str:
|
||||||
"""
|
"""
|
||||||
Creates a simple hash for an album
|
Creates a simple hash for an album
|
||||||
"""
|
"""
|
||||||
|
Loading…
x
Reference in New Issue
Block a user