Loading karaokatalog/Song.py 0 → 100644 +88 −0 Changes for karaokatalog/Song.py: 88 added lines, 0 removed lines. Original line number Diff line number Diff line 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
karaokatalog/Song.py 0 → 100644 +88 −0 Changes for karaokatalog/Song.py: 88 added lines, 0 removed lines. Original line number Diff line number Diff line 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) )