mirror of
https://git.srhtnin.garden/sarah/SaraTune.git
synced 2026-10-13 18:18:11 +02:00
Initial commit
This commit is contained in:
commit
0eeff23589
25 files changed
+3328
No files matched your search
@@ -0,0 +1,84 @@
|
||||
"""Saved playlists: one TOML file each in ~/.config/SaraSuite/SaraTune/Playlists/,
|
||||
holding the playlist's name and its files' paths in order. Also reads and
|
||||
writes .m3u/.m3u8, so playlists can move to and from other players."""
|
||||
|
||||
import re
|
||||
from dataclasses import dataclass, field
|
||||
from pathlib import Path
|
||||
|
||||
import tomlkit
|
||||
|
||||
from Core import IsAudioFile, IsWindows, PlaylistsDir
|
||||
|
||||
|
||||
@dataclass
|
||||
class Playlist:
|
||||
Name: str
|
||||
Tracks: list[str] = field(default_factory=list)
|
||||
|
||||
@property
|
||||
def FilePath(self) -> Path:
|
||||
return PlaylistsDir / f"{SafeFileName(self.Name)}.toml"
|
||||
|
||||
def Save(self) -> None:
|
||||
PlaylistsDir.mkdir(parents=True, exist_ok=True)
|
||||
Document = tomlkit.document()
|
||||
Document["Name"] = self.Name
|
||||
Document["Tracks"] = tomlkit.array()
|
||||
Document["Tracks"].multiline(True)
|
||||
Document["Tracks"].extend(self.Tracks)
|
||||
self.FilePath.write_text(tomlkit.dumps(Document), encoding="utf-8")
|
||||
|
||||
def Delete(self) -> None:
|
||||
self.FilePath.unlink(missing_ok=True)
|
||||
|
||||
|
||||
def SafeFileName(Name: str) -> str:
|
||||
"""A name that works as a file name on Linux and Windows."""
|
||||
return re.sub(r'[<>:"/\\|?*\x00-\x1f]', "_", Name).strip(" .") or "Playlist"
|
||||
|
||||
|
||||
def LoadPlaylists() -> list[Playlist]:
|
||||
Result: list[Playlist] = []
|
||||
if not PlaylistsDir.is_dir():
|
||||
return Result
|
||||
for FilePath in PlaylistsDir.glob("*.toml"):
|
||||
try:
|
||||
Data = tomlkit.parse(FilePath.read_text(encoding="utf-8")).unwrap()
|
||||
except (OSError, tomlkit.exceptions.TOMLKitError):
|
||||
continue
|
||||
Tracks = [str(Item) for Item in Data.get("Tracks", []) if isinstance(Item, str)]
|
||||
Result.append(Playlist(str(Data.get("Name") or FilePath.stem), Tracks))
|
||||
return sorted(Result, key=lambda Item: Item.Name.lower())
|
||||
|
||||
|
||||
def NameTaken(Name: str, Except: str = "") -> bool:
|
||||
"""Two names that make the same file count as the same playlist."""
|
||||
Wanted = SafeFileName(Name).lower()
|
||||
return any(SafeFileName(Item.Name).lower() == Wanted and Item.Name != Except for Item in LoadPlaylists())
|
||||
|
||||
|
||||
def ExportM3U(Item: Playlist, Destination: Path) -> None:
|
||||
Lines = ["#EXTM3U", f"#PLAYLIST:{Item.Name}"] + Item.Tracks
|
||||
Destination.write_text("\n".join(Lines) + "\n", encoding="utf-8")
|
||||
|
||||
|
||||
def ReadM3U(Source: Path) -> Playlist:
|
||||
"""Relative entries count from the .m3u file's folder; URLs and missing
|
||||
files are skipped."""
|
||||
Name = Source.stem
|
||||
Tracks: list[str] = []
|
||||
for Line in Source.read_text(encoding="utf-8", errors="replace").splitlines():
|
||||
Line = Line.strip().lstrip("")
|
||||
if Line.startswith("#PLAYLIST:"):
|
||||
Name = Line.split(":", 1)[1].strip() or Name
|
||||
continue
|
||||
if not Line or Line.startswith("#") or "://" in Line:
|
||||
continue
|
||||
# Playlists made on Windows use backslashes, which Linux paths don't split on.
|
||||
Entry = Path(Line if IsWindows else Line.replace("\\", "/"))
|
||||
if not Entry.is_absolute():
|
||||
Entry = Source.parent / Entry
|
||||
if Entry.is_file() and IsAudioFile(Entry):
|
||||
Tracks.append(str(Entry.resolve()))
|
||||
return Playlist(Name, Tracks)
|
||||
Reference in new issue
Block a user