Add Song
This commit is contained in:
parent
8d33ef6b85
commit
6224713939
88
karaokatalog/Song.py
Normal file
88
karaokatalog/Song.py
Normal file
@ -0,0 +1,88 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from pathlib import Path
|
||||
from typing import Self
|
||||
import filecmp
|
||||
|
||||
from dataclasses import dataclass
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class Song:
|
||||
"""
|
||||
A song, represented by a txt file and accompanying files (most importantly, an audio file).
|
||||
|
||||
See https://usdx.eu/format/ for the specification.
|
||||
"""
|
||||
|
||||
title: str
|
||||
artist: str
|
||||
audio: Path | None
|
||||
video: Path | None
|
||||
cover: Path | None
|
||||
song_txt: Path
|
||||
|
||||
@property
|
||||
def dir(self) -> Path:
|
||||
return self.song_txt.parent
|
||||
|
||||
def has_identic_files(self, other: Song) -> bool:
|
||||
"""
|
||||
Return if the directory for this song and the directory for the other song contain identic files.
|
||||
"""
|
||||
comparison = filecmp.dircmp(self.dir, other.dir, shallow=False)
|
||||
|
||||
# Two directories are identic if they don't have any differing files, and if there were also no
|
||||
# errors during comparison.
|
||||
#
|
||||
# See also:
|
||||
# - https://docs.python.org/3/library/filecmp.html#filecmp.dircmp.diff_files
|
||||
return not comparison.diff_files and not comparison.funny_files
|
||||
|
||||
@classmethod
|
||||
def from_song_txt(cls, song_txt: Path) -> Self | None:
|
||||
with song_txt.open(encoding="utf-8", errors="ignore") as f:
|
||||
tags = dict(_parse_tag_line(line) for line in f if line.startswith("#"))
|
||||
|
||||
title = tags.get("TITLE")
|
||||
artist = tags.get("ARTIST")
|
||||
audio_name = tags.get("AUDIO", tags.get("MP3"))
|
||||
video_name = tags.get("VIDEO")
|
||||
cover_name = tags.get("COVER")
|
||||
|
||||
if not title or not artist:
|
||||
# Both are mandatory according to the specification
|
||||
return None
|
||||
|
||||
return cls(
|
||||
title=title,
|
||||
artist=artist,
|
||||
audio=song_txt / audio_name if audio_name else None,
|
||||
video=song_txt / video_name if video_name else None,
|
||||
cover=song_txt / cover_name if cover_name else None,
|
||||
song_txt=song_txt,
|
||||
)
|
||||
|
||||
|
||||
def _parse_tag_line(tag_line: str) -> tuple[str, str | None]:
|
||||
"""
|
||||
Parse a tag line of the format:
|
||||
|
||||
#KEY:Value
|
||||
|
||||
or
|
||||
|
||||
#KEY:
|
||||
|
||||
Returns a tuple of (key, value), where the value might be None.
|
||||
"""
|
||||
|
||||
key_and_potentially_value = tuple(
|
||||
tag_line.strip().removeprefix("#").split(":", maxsplit=1)
|
||||
)
|
||||
|
||||
return (
|
||||
key_and_potentially_value
|
||||
if len(key_and_potentially_value) == 2
|
||||
else (key_and_potentially_value[0], None)
|
||||
)
|
Loading…
x
Reference in New Issue
Block a user