Initial commit

This commit is contained in:
sarah committed 2026-10-09 15:33:43 +02:00
commit 0eeff23589
25 files changed
+3328

No files matched your search

+84
View File
@@ -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)