mirror of
https://git.srhtnin.garden/sarah/SaraTune.git
synced 2026-10-15 10:23:17 +02:00
Initial commit
This commit is contained in:
commit
0eeff23589
25 files changed
+3328
No files matched your search
+154
@@ -0,0 +1,154 @@
|
||||
"""Music mode's queue: what plays next, shuffle and repeat. Shuffling
|
||||
reorders the queue itself (the current track first), so the Queue page
|
||||
always shows the real play order; turning it off brings the original order
|
||||
back. Saved between runs as Session.toml."""
|
||||
|
||||
import random
|
||||
from dataclasses import dataclass, field
|
||||
from typing import Optional
|
||||
|
||||
import tomlkit
|
||||
|
||||
from Core import ConfigDir, SessionPath
|
||||
|
||||
RepeatModes = ["Off", "All", "One"]
|
||||
RestartAfterMs = 3000 # Previous restarts the track when it's further in than this
|
||||
|
||||
|
||||
@dataclass
|
||||
class PlayQueue:
|
||||
Paths: list[str] = field(default_factory=list)
|
||||
Index: int = -1 # into Paths; -1 = nothing
|
||||
Shuffle: bool = False
|
||||
Repeat: str = "Off"
|
||||
Unshuffled: list[str] = field(default_factory=list) # the order before shuffling
|
||||
|
||||
@property
|
||||
def Current(self) -> Optional[str]:
|
||||
return self.Paths[self.Index] if 0 <= self.Index < len(self.Paths) else None
|
||||
|
||||
def Set(self, Paths: list[str], Start: int = 0) -> None:
|
||||
"""Replaces the queue, e.g. with an album, starting at Paths[Start]."""
|
||||
self.Paths = list(Paths)
|
||||
self.Index = Start if Paths else -1
|
||||
self.Unshuffled = list(Paths)
|
||||
if self.Shuffle and Paths:
|
||||
self._ShuffleRest()
|
||||
|
||||
def _ShuffleRest(self) -> None:
|
||||
Current = self.Paths[self.Index]
|
||||
Rest = self.Paths[: self.Index] + self.Paths[self.Index + 1 :]
|
||||
random.shuffle(Rest)
|
||||
self.Paths = [Current] + Rest
|
||||
self.Index = 0
|
||||
|
||||
def SetShuffle(self, Enabled: bool) -> None:
|
||||
if Enabled == self.Shuffle:
|
||||
return
|
||||
self.Shuffle = Enabled
|
||||
if not self.Paths:
|
||||
return
|
||||
if Enabled:
|
||||
self.Unshuffled = list(self.Paths)
|
||||
self._ShuffleRest()
|
||||
else:
|
||||
Current = self.Current
|
||||
self.Paths = self.Unshuffled or self.Paths
|
||||
self.Index = self.Paths.index(Current) if Current in self.Paths else 0
|
||||
|
||||
def CycleRepeat(self) -> str:
|
||||
self.Repeat = RepeatModes[(RepeatModes.index(self.Repeat) + 1) % len(RepeatModes)]
|
||||
return self.Repeat
|
||||
|
||||
def Next(self, Finished: bool = False) -> Optional[int]:
|
||||
"""The index to play after the current one (None: stop). Finished means
|
||||
the track ran out by itself, which is when Repeat One applies."""
|
||||
if not self.Paths:
|
||||
return None
|
||||
if Finished and self.Repeat == "One":
|
||||
return self.Index
|
||||
if self.Index + 1 < len(self.Paths):
|
||||
return self.Index + 1
|
||||
return 0 if self.Repeat != "Off" or not Finished else None
|
||||
|
||||
def Previous(self) -> Optional[int]:
|
||||
if not self.Paths:
|
||||
return None
|
||||
if self.Index > 0:
|
||||
return self.Index - 1
|
||||
return len(self.Paths) - 1 if self.Repeat == "All" else 0
|
||||
|
||||
# --- Editing ------------------------------------------------------------
|
||||
|
||||
def PlayNext(self, Paths: list[str]) -> None:
|
||||
At = self.Index + 1 if self.Index >= 0 else 0
|
||||
self.Paths[At:At] = Paths
|
||||
self.Unshuffled.extend(Paths)
|
||||
if self.Index < 0:
|
||||
self.Index = 0
|
||||
|
||||
def Append(self, Paths: list[str]) -> None:
|
||||
self.Paths.extend(Paths)
|
||||
self.Unshuffled.extend(Paths)
|
||||
if self.Index < 0 and self.Paths:
|
||||
self.Index = 0
|
||||
|
||||
def Remove(self, At: int) -> None:
|
||||
Removed = self.Paths.pop(At)
|
||||
if Removed in self.Unshuffled:
|
||||
self.Unshuffled.remove(Removed)
|
||||
if At < self.Index or (At == self.Index and self.Index >= len(self.Paths)):
|
||||
self.Index -= 1
|
||||
if not self.Paths:
|
||||
self.Index = -1
|
||||
|
||||
def Move(self, Old: int, New: int) -> None:
|
||||
Item = self.Paths.pop(Old)
|
||||
self.Paths.insert(New, Item)
|
||||
if Old == self.Index:
|
||||
self.Index = New
|
||||
elif Old < self.Index <= New:
|
||||
self.Index -= 1
|
||||
elif New <= self.Index < Old:
|
||||
self.Index += 1
|
||||
|
||||
def Clear(self) -> None:
|
||||
self.Paths, self.Unshuffled, self.Index = [], [], -1
|
||||
|
||||
|
||||
@dataclass
|
||||
class Session:
|
||||
Queue: PlayQueue = field(default_factory=PlayQueue)
|
||||
Position: int = 0 # milliseconds into the current track
|
||||
View: str = "Songs"
|
||||
|
||||
def Save(self) -> None:
|
||||
Document = tomlkit.document()
|
||||
Document["View"] = self.View
|
||||
Document["Position"] = int(self.Position)
|
||||
Document["Index"] = self.Queue.Index
|
||||
Document["Shuffle"] = self.Queue.Shuffle
|
||||
Document["Repeat"] = self.Queue.Repeat
|
||||
for Key, Paths in (("Paths", self.Queue.Paths), ("Unshuffled", self.Queue.Unshuffled)):
|
||||
Document[Key] = tomlkit.array()
|
||||
Document[Key].multiline(True)
|
||||
Document[Key].extend(Paths)
|
||||
ConfigDir.mkdir(parents=True, exist_ok=True)
|
||||
SessionPath.write_text(tomlkit.dumps(Document), encoding="utf-8")
|
||||
|
||||
|
||||
def LoadSession() -> Session:
|
||||
try:
|
||||
Data = tomlkit.parse(SessionPath.read_text(encoding="utf-8")).unwrap()
|
||||
except (OSError, tomlkit.exceptions.TOMLKitError):
|
||||
return Session()
|
||||
Paths = [str(Item) for Item in Data.get("Paths", []) if isinstance(Item, str)]
|
||||
Index = Data.get("Index", -1)
|
||||
Queue = PlayQueue(
|
||||
Paths=Paths,
|
||||
Index=Index if isinstance(Index, int) and 0 <= Index < len(Paths) else (0 if Paths else -1),
|
||||
Shuffle=bool(Data.get("Shuffle", False)),
|
||||
Repeat=Data.get("Repeat") if Data.get("Repeat") in RepeatModes else "Off",
|
||||
Unshuffled=[str(Item) for Item in Data.get("Unshuffled", []) if isinstance(Item, str)],
|
||||
)
|
||||
return Session(Queue=Queue, Position=int(Data.get("Position", 0) or 0), View=str(Data.get("View", "Songs")))
|
||||
Reference in new issue
Block a user