From 0eeff235893cac1851e2d18432a01d0c637307a5 Mon Sep 17 00:00:00 2001 From: SRHTNIN Date: Fri, 9 Oct 2026 15:33:43 +0200 Subject: [PATCH] Initial commit --- .gitignore | 7 + AudioMode.py | 293 +++++++ Core.py | 346 ++++++++ Covers.py | 58 ++ Library.py | 136 +++ Main.py | 246 ++++++ Metadata.py | 259 ++++++ MusicMode.py | 811 ++++++++++++++++++ Packaging/BuildAppImage.sh | 38 + Packaging/Launch.sh | 5 + Packaging/SaraSuite/Icon.png | Bin 0 -> 1128 bytes Packaging/SaraSuite/Manifest.toml | 48 ++ Packaging/SaraSuite/Templates/Launch.sh | 10 + .../SaraSuite/Templates/SaraTune.desktop | 11 + PlayQueue.py | 154 ++++ Player.py | 211 +++++ Playlists.py | 84 ++ README.md | 56 ++ Settings.py | 312 +++++++ Transport.py | 146 ++++ Widgets.py | 91 ++ assets/Icon.ico | Bin 0 -> 5747 bytes assets/Icon.png | Bin 0 -> 1030 bytes assets/fonts/Fixedsys_Excelsior_Regular.ttf | Bin 0 -> 580724 bytes requirements.txt | 6 + 25 files changed, 3328 insertions(+) create mode 100644 .gitignore create mode 100644 AudioMode.py create mode 100644 Core.py create mode 100644 Covers.py create mode 100644 Library.py create mode 100644 Main.py create mode 100644 Metadata.py create mode 100644 MusicMode.py create mode 100755 Packaging/BuildAppImage.sh create mode 100755 Packaging/Launch.sh create mode 100644 Packaging/SaraSuite/Icon.png create mode 100644 Packaging/SaraSuite/Manifest.toml create mode 100644 Packaging/SaraSuite/Templates/Launch.sh create mode 100644 Packaging/SaraSuite/Templates/SaraTune.desktop create mode 100644 PlayQueue.py create mode 100644 Player.py create mode 100644 Playlists.py create mode 100644 README.md create mode 100644 Settings.py create mode 100644 Transport.py create mode 100644 Widgets.py create mode 100644 assets/Icon.ico create mode 100644 assets/Icon.png create mode 100644 assets/fonts/Fixedsys_Excelsior_Regular.ttf create mode 100644 requirements.txt diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..2b48d2e --- /dev/null +++ b/.gitignore @@ -0,0 +1,7 @@ +.venv/ +__pycache__/ +*.pyc +build/ +.flet/ +dist/ +*.AppImage diff --git a/AudioMode.py b/AudioMode.py new file mode 100644 index 0000000..3f78486 --- /dev/null +++ b/AudioMode.py @@ -0,0 +1,293 @@ +"""Audio mode: open a file or a folder and listen, right away. A folder +browser on the left, the playing file's cover, details and tags on the right. +Nothing is saved: no library, no queue, no history.""" + +import asyncio +import re +from pathlib import Path +from typing import Callable, Optional + +import flet as ft + +from Core import AudioExtensions, DisplayPath, Home, IsAudioFile, Note, Palette +from Covers import CoverFor +from Metadata import ReadDetails, ReadTrack +from Player import Player +from Transport import CoverImage, Transport +from Widgets import App, EmptyState, InfoTable + +BrowserWidth = 360 +ViewerCoverSize = 220 + + +def NaturalKey(Name: str) -> list: + """"Track 2" before "Track 10".""" + return [int(Part) if Part.isdigit() else Part.lower() for Part in re.split(r"(\d+)", Name)] + + +class AudioMode(ft.Column): + def __init__(self, TheApp: App, ThePlayer: Player, OnVolume: Callable[[float], None]): + super().__init__(expand=True, spacing=0) + self.TheApp = TheApp + self.ThePlayer = ThePlayer + self.Folder: Optional[Path] = None + self.Opened: list[Path] = [] # files opened together (not a folder listing) + self.Files: list[Path] = [] # the audio files shown, in order: what Previous/Next walk + self.Rows: dict[str, ft.Container] = {} + self.Current: Optional[str] = None + self.AutoAdvance = TheApp.Config.AudioAutoAdvance + + ThePlayer.OnEnded = lambda: TheApp.Page.run_task(self._Ended) + ThePlayer.OnChanged = lambda: self.Bar.Refresh() + ThePlayer.OnError = lambda Message: TheApp.Notify(f"Can't play {Path(self.Current or '').name}: {Message}", "error") + + # --- Browser --------------------------------------------------------- + self.FolderLabel = ft.Text("", color=Palette.Text, size=13, max_lines=1, overflow=ft.TextOverflow.ELLIPSIS, + expand=True, tooltip="") + self.UpButton = ft.IconButton(ft.Icons.ARROW_UPWARD, icon_color=Palette.Mauve, tooltip="Parent folder", + on_click=self._Up) + Toolbar = ft.Row([ + self.UpButton, + self.FolderLabel, + ft.IconButton(ft.Icons.AUDIO_FILE_OUTLINED, icon_color=Palette.Mauve, tooltip="Open files", + on_click=self._PickFiles), + ft.IconButton(ft.Icons.FOLDER_OPEN_OUTLINED, icon_color=Palette.Mauve, tooltip="Open folder", + on_click=self._PickFolder), + ], spacing=2) + self.List = ft.ListView(expand=True, spacing=0, item_extent=40) + Browser = ft.Container( + ft.Column([Toolbar, ft.Divider(height=1, color=Palette.Surface1), self.List], spacing=4, expand=True), + width=BrowserWidth, bgcolor=Palette.Mantle, padding=ft.Padding.only(left=6, right=6, top=6), + border=ft.Border.only(right=ft.BorderSide(1, Palette.Surface1)), + ) + + # --- Viewer ---------------------------------------------------------- + self.Viewer = ft.Container( + EmptyState("Pick a file to play it", "Click one on the left, or open files or a folder.", + ft.Icons.HEADPHONES_OUTLINED), + expand=True, padding=24, + ) + + # --- Play bar -------------------------------------------------------- + self.LoopButton = ft.IconButton(ft.Icons.REPEAT_ONE, icon_color=Palette.Overlay0, tooltip="Loop this file", + on_click=self._ToggleLoop) + self.AdvanceButton = ft.IconButton( + ft.Icons.PLAYLIST_PLAY, tooltip="Play the next file in the folder when one ends", + icon_color=Palette.Mauve if self.AutoAdvance else Palette.Overlay0, on_click=self._ToggleAdvance, + ) + self.Bar = Transport(ThePlayer, self.Previous, self.Next, OnVolume, Extras=[self.LoopButton, self.AdvanceButton]) + + self.controls = [ + ft.Row([Browser, self.Viewer], expand=True, spacing=0, vertical_alignment=ft.CrossAxisAlignment.STRETCH), + self.Bar, + ] + Start = Home / "Music" + self._ShowFolder(Start if Start.is_dir() else Home, Update=False) + + + def _Show(self, Control: ft.Control) -> None: + """Updates Control if this mode is on screen; a hidden mode's changes + go out with Main's page update when it's shown.""" + if self.Bar.Shown: + Control.update() + + # --- Browsing ----------------------------------------------------------- + + def _ShowFolder(self, Folder: Path, Update: bool = True) -> None: + try: + Children = list(Folder.iterdir()) + except OSError as Error: + self.TheApp.Notify(f"Can't open {Folder}: {Error.strerror or Error}", "error") + return + self.Folder, self.Opened = Folder, [] + Folders = sorted((C for C in Children if C.is_dir() and not C.name.startswith(".")), key=lambda C: NaturalKey(C.name)) + self.Files = sorted((C for C in Children if C.is_file() and IsAudioFile(C)), key=lambda C: NaturalKey(C.name)) + self.FolderLabel.value = DisplayPath(str(Folder)) + self.FolderLabel.tooltip = str(Folder) + self.UpButton.disabled = Folder.parent == Folder + self._FillList(Folders) + if Update: + self._Show(self) + + def _ShowOpened(self, Files: list[Path]) -> None: + self.Folder, self.Opened, self.Files = None, Files, Files + self.FolderLabel.value = f"{len(Files)} opened files" + self.FolderLabel.tooltip = "" + self.UpButton.disabled = False # goes to the first file's folder + self._FillList([]) + self._Show(self) + + def _FillList(self, Folders: list[Path]) -> None: + self.Rows = {} + Items: list[ft.Control] = [] + for Child in Folders: + Items.append(self._Row(Child, ft.Icons.FOLDER, Palette.Yellow, lambda e, C=Child: self._ShowFolder(C))) + for Child in self.Files: + Row = self._Row(Child, ft.Icons.AUDIO_FILE, Palette.Mauve, lambda e, C=Child: self.TheApp.Page.run_task(self.PlayFile, str(C))) + self.Rows[str(Child)] = Row + Items.append(Row) + if not Items: + Items.append(ft.Container(Note("No audio files or folders here."), padding=12)) + self.List.controls = Items + self._Highlight() + + def _Row(self, Item: Path, Icon, Colour: str, OnClick) -> ft.Container: + return ft.Container( + ft.Row([ + ft.Icon(Icon, color=Colour, size=18), + ft.Text(Item.name, size=13, color=Palette.Text, max_lines=1, overflow=ft.TextOverflow.ELLIPSIS, expand=True), + ], spacing=8), + padding=ft.Padding.symmetric(horizontal=8), border_radius=4, on_click=OnClick, ink=True, + tooltip=Item.name if len(Item.name) > 40 else None, + ) + + def _Highlight(self) -> None: + for PathText, Row in self.Rows.items(): + Row.bgcolor = Palette.Surface0 if PathText == self.Current else None + + def _Up(self, e) -> None: + if self.Opened: + self._ShowFolder(self.Opened[0].parent) + elif self.Folder and self.Folder.parent != self.Folder: + self._ShowFolder(self.Folder.parent) + + async def _PickFiles(self, e) -> None: + Picked = await self.TheApp.Picker.pick_files( + dialog_title="Open audio files", allow_multiple=True, file_type=ft.FilePickerFileType.CUSTOM, + allowed_extensions=sorted(Extension.lstrip(".") for Extension in AudioExtensions), + initial_directory=str(self.Folder) if self.Folder else None, + ) + Paths = [Item.path for Item in Picked or [] if Item.path] + if Paths: + await self.Open(Paths) + + async def _PickFolder(self, e) -> None: + Chosen = await self.TheApp.Picker.get_directory_path( + dialog_title="Open a folder", initial_directory=str(self.Folder) if self.Folder else None) + if Chosen: + self._ShowFolder(Path(Chosen)) + + async def Open(self, Paths: list[str]) -> None: + """Files (play the first) or a folder (show it). One file shows its + whole folder, so Next carries on through it.""" + Items = [Path(Item).expanduser().resolve() for Item in Paths] + Folders = [Item for Item in Items if Item.is_dir()] + Files = [Item for Item in Items if Item.is_file()] + if not Files: + if Folders: + self._ShowFolder(Folders[0]) + return + if len(Files) == 1: + self._ShowFolder(Files[0].parent) + if str(Files[0]) not in self.Rows: # not an audio extension we list; play it anyway + self.Files.append(Files[0]) + else: + self._ShowOpened(Files) + await self.PlayFile(str(Files[0])) + + # --- Playing ------------------------------------------------------------ + + async def PlayFile(self, FilePath: str) -> None: + Info = await asyncio.to_thread(ReadTrack, FilePath) + if not Path(FilePath).is_file(): + self.TheApp.Notify(f"{Path(FilePath).name} is gone.", "error") + return + self.Current = FilePath + await self.ThePlayer.Load(FilePath, Duration=Info.Duration) # errors arrive through OnError + self._Highlight() + self._Show(self.List) + self._ScrollTo(FilePath) + Subtitle = " - ".join(Part for Part in (Info.Artist, Info.Album) if Part) + self.Bar.ShowTrack(Info.DisplayTitle, Subtitle or Path(FilePath).parent.name, None) + await self._ShowDetails(FilePath, Info) + + def _ScrollTo(self, FilePath: str) -> None: + Keys = list(self.Rows) + if FilePath in Keys: + Offset = (len(self.List.controls) - len(Keys) + Keys.index(FilePath)) * 40 - 120 + self.TheApp.Page.run_task(self.List.scroll_to, offset=max(0, Offset), duration=200) + + async def _ShowDetails(self, FilePath: str, Info) -> None: + (Technical, Tags), Cover = await asyncio.gather( + asyncio.to_thread(ReadDetails, FilePath), asyncio.to_thread(CoverFor, FilePath, ViewerCoverSize * 2)) + if self.Current != FilePath: + return # another file was clicked meanwhile + self.Bar.ShowTrack(self.Bar.Title.value, self.Bar.Subtitle.value, Cover) + Heading = [ + ft.Text(Info.DisplayTitle, size=24, color=Palette.Mauve, selectable=True), + ft.Text(Info.DisplayArtist if Info.Artist or Info.AlbumArtist else "", size=16, color=Palette.Text), + ft.Text(" - ".join(Part for Part in (Info.Album, Info.Year) if Part), size=14, color=Palette.Overlay0), + ft.Text(str(Path(FilePath).parent), size=12, color=Palette.Overlay0, selectable=True), + ] + Sections: list[ft.Control] = [ + ft.Row([CoverImage(Cover, ViewerCoverSize, 6), ft.Column(Heading, spacing=6, expand=True)], + spacing=24, vertical_alignment=ft.CrossAxisAlignment.START), + ft.Divider(height=24, color=Palette.Surface1), + ft.Text("Details", color=Palette.Mauve, size=16), + InfoTable(Technical), + ] + if Tags: + Sections += [ + ft.Divider(height=24, color=Palette.Surface1), + ft.Text("Tags", color=Palette.Mauve, size=16), + InfoTable(Tags, LabelWidth=160), + ] + self.Viewer.content = ft.Column(Sections, scroll=ft.ScrollMode.AUTO, expand=True, spacing=8) + self._Show(self.Viewer) + + def _Neighbour(self, Step: int) -> Optional[str]: + Paths = [str(Item) for Item in self.Files] + if not Paths: + return None + if self.Current not in Paths: + return Paths[0] + At = Paths.index(self.Current) + Step + return Paths[At] if 0 <= At < len(Paths) else None + + async def Next(self) -> None: + Target = self._Neighbour(1) + if Target: + await self.PlayFile(Target) + + async def Previous(self) -> None: + if self.ThePlayer.Position > 3000: + await self.ThePlayer.Seek(0) + return + Target = self._Neighbour(-1) + if Target: + await self.PlayFile(Target) + + async def _Ended(self) -> None: + Target = self._Neighbour(1) if self.AutoAdvance else None + if Target: + await self.PlayFile(Target) + else: + self.Bar.Refresh() + + def _ToggleLoop(self, e) -> None: + self.ThePlayer.Loop = not self.ThePlayer.Loop + self.LoopButton.icon_color = Palette.Mauve if self.ThePlayer.Loop else Palette.Overlay0 + self._Show(self.LoopButton) + + def _ToggleAdvance(self, e) -> None: + self.AutoAdvance = not self.AutoAdvance + self.AdvanceButton.icon_color = Palette.Mauve if self.AutoAdvance else Palette.Overlay0 + self._Show(self.AdvanceButton) + + # --- Keys --------------------------------------------------------------- + + async def HandleKey(self, e: ft.KeyboardEvent) -> None: + """Space: play/pause. Left/right: seek. Up/down: previous/next file.""" + Step = self.TheApp.Config.SeekSeconds * 1000 + if e.key == " ": + await self.ThePlayer.Toggle() + elif e.key == "Arrow Left": + await self.ThePlayer.Seek(self.ThePlayer.Position - Step) + self.Bar.Refresh() + elif e.key == "Arrow Right": + await self.ThePlayer.Seek(self.ThePlayer.Position + Step) + self.Bar.Refresh() + elif e.key == "Arrow Up": + await self.Previous() + elif e.key == "Arrow Down": + await self.Next() diff --git a/Core.py b/Core.py new file mode 100644 index 0000000..5634775 --- /dev/null +++ b/Core.py @@ -0,0 +1,346 @@ +"""Shared app-wide constants: paths, palette, theme, SaraTune's own config. + +The palette, theming and shared settings mirror SaraSuite's and SaraFem's +Core.py and read the same ~/.config/SaraSuite files, so a theme picked for +one Sara program looks the same in all of them. +""" + +import os +import sys +from dataclasses import asdict, dataclass, field +from pathlib import Path + +import flet as ft +import tomlkit + +AppName = "SaraTune" +# Bundled in assets/fonts and always registered, so it works as FontFamily +# without being installed. +BundledFontFamily = "Fixedsys Excelsior" +ActiveFontFamily = BundledFontFamily # set by ApplyAppearance, read by BuildTheme + +IsWindows = sys.platform == "win32" + +# Each system's own per-user folders, like SaraSuite's: ~/.config and +# ~/.local/share on Linux, %APPDATA% and %LOCALAPPDATA% on Windows. +Home = Path.home() +if IsWindows: + ConfigHome = Path(os.environ.get("APPDATA") or Home / "AppData" / "Roaming") + DataHome = Path(os.environ.get("LOCALAPPDATA") or Home / "AppData" / "Local") +else: + ConfigHome = Path(os.environ.get("XDG_CONFIG_HOME") or Home / ".config") + DataHome = Path(os.environ.get("XDG_DATA_HOME") or Home / ".local" / "share") + +SharedConfigDir = ConfigHome / "SaraSuite" +ThemesDir = SharedConfigDir / "Themes" +SharedConfigPath = SharedConfigDir / "Config.toml" +ConfigDir = SharedConfigDir / AppName +ConfigPath = ConfigDir / "Config.toml" +PlaylistsDir = ConfigDir / "Playlists" # one .toml per playlist +SessionPath = ConfigDir / "Session.toml" # Music mode's queue and position +CacheDir = (DataHome if IsWindows else Path(os.environ.get("XDG_CACHE_HOME") or Home / ".cache")) / "SaraSuite" / AppName +LibraryCachePath = CacheDir / "Library.toml" # tags of every scanned file, keyed by path + +# What both modes list. Playback itself goes through the Flet client's +# audioplayers plugin (GStreamer on Linux, Media Foundation on Windows), so +# what actually plays depends on the system's codecs. +AudioExtensions = { + ".mp3", ".flac", ".ogg", ".oga", ".opus", ".wav", ".m4a", ".aac", ".aif", ".aiff", ".wma", ".mka", ".webm", +} + +# Inside a PyInstaller build, bundled files are unpacked to sys._MEIPASS. +BundleDir = Path(getattr(sys, "_MEIPASS", Path(__file__).resolve().parent)) +AssetsDir = BundleDir / "assets" + +# Themes SaraFem seeds into ThemesDir. Only other themes travel with an +# export as full palettes (same rule as SaraFem's ExportSettings). +BuiltinThemeNames = {"CatppuccinMocha", "Forest", "Gruvbox"} + + +class Palette: + """Live values for the active theme (see ApplyTheme). Same attribute names + and role mapping as SaraSuite's and SaraFem's Palette.""" + + Mauve = "#cba6f7" + Red = "#f38ba8" + Peach = "#fab387" + Yellow = "#f9e2af" + Green = "#a6e3a1" + Blue = "#89b4fa" + Text = "#cdd6f4" + White = "#cdd6f4" + Overlay0 = "#6c7086" + Surface1 = "#45475a" + Surface0 = "#313244" + Base = "#1e1e2e" + Mantle = "#181825" + Crust = "#11111b" + + +_ThemeRoleMap = { + "Background": "Base", "Surface": "Mantle", "SurfaceAlt": "Surface0", "Border": "Surface1", + "Text": "Text", "TextMuted": "Overlay0", "Black": "Crust", "White": "White", + "Red": "Red", "Orange": "Peach", "Yellow": "Yellow", "Green": "Green", + "Blue": "Blue", "Purple": "Mauve", +} + + +def ApplyTheme(ThemeName: str) -> None: + """Mutate Palette in place from ~/.config/SaraSuite/Themes/.toml. + Keeps the built-in Catppuccin Mocha values if the file is missing. Must + run before any control reads Palette.X.""" + ThemeFile = ThemesDir / f"{ThemeName}.toml" + if not ThemeFile.exists(): + return + try: + RoleValues = tomlkit.parse(ThemeFile.read_text()).unwrap().get("Palette", {}) + except tomlkit.exceptions.TOMLKitError: + return + for RoleName, AttrName in _ThemeRoleMap.items(): + if RoleName in RoleValues: + setattr(Palette, AttrName, RoleValues[RoleName]) + + +def AvailableThemes() -> list[str]: + Names = {ThemePath.stem for ThemePath in ThemesDir.glob("*.toml")} if ThemesDir.is_dir() else set() + Names.add("CatppuccinMocha") + return sorted(Names) + + +@dataclass +class AppConfig: + # True: Theme, FontFamily and HideWindowTitleBar come from the shared + # ~/.config/SaraSuite/Config.toml. False: SaraTune's own values below. + UseSharedSettings: bool = True + Theme: str = "CatppuccinMocha" + FontFamily: str = BundledFontFamily + HideWindowTitleBar: bool = False + StartMode: str = "Music" # "Audio" or "Music"; opening files always starts in Audio + # Music mode's library: every audio file below these folders. + MusicFolders: list[str] = field(default_factory=lambda: [str(Home / "Music")]) + ScanOnStart: bool = True # look for new and changed files at every start + Volume: float = 1.0 # shared by both modes, saved when changed + # Audio mode: seek step for the arrow keys, and whether a finished file + # moves on to the next one in its folder. + SeekSeconds: int = 5 + AudioAutoAdvance: bool = False + # Music mode: keep the queue and position between runs. + RememberSession: bool = True + + def Save(self) -> None: + ConfigDir.mkdir(parents=True, exist_ok=True) + ConfigPath.write_text(tomlkit.dumps(asdict(self))) + + +def LoadConfig() -> AppConfig: + if not ConfigPath.exists(): + Config = AppConfig() + Config.Save() + return Config + try: + Data = tomlkit.parse(ConfigPath.read_text()).unwrap() + except tomlkit.exceptions.TOMLKitError: + return AppConfig() + KnownFields = set(AppConfig.__dataclass_fields__) + return AppConfig(**{Key: Value for Key, Value in Data.items() if Key in KnownFields}) + + +@dataclass +class SharedSettings: + """~/.config/SaraSuite/Config.toml, shared by every SaraSuite program. + Same schema and fallback rules as SaraSuite's and SaraFem's.""" + + Theme: str = "CatppuccinMocha" + FontFamily: str = BundledFontFamily + HideWindowTitleBar: bool = False + + def Save(self) -> None: + SharedConfigDir.mkdir(parents=True, exist_ok=True) + SharedConfigPath.write_text(tomlkit.dumps(asdict(self))) + + +def _OwnSettings(Config: AppConfig) -> SharedSettings: + return SharedSettings(Config.Theme, Config.FontFamily, Config.HideWindowTitleBar) + + +def LoadSharedSettings(Config: AppConfig) -> SharedSettings: + """The shared file, created from SaraTune's own values if missing.""" + if not SharedConfigPath.exists(): + Shared = _OwnSettings(Config) + Shared.Save() + return Shared + Result = _OwnSettings(Config) # fallback for keys the file doesn't have + try: + Data = tomlkit.parse(SharedConfigPath.read_text()).unwrap() + except tomlkit.exceptions.TOMLKitError: + return Result + for Key in SharedSettings.__dataclass_fields__: + if Key in Data: + setattr(Result, Key, Data[Key]) + return Result + + +def EffectiveSettings(Config: AppConfig) -> SharedSettings: + return LoadSharedSettings(Config) if Config.UseSharedSettings else _OwnSettings(Config) + + +def ExportSettings(Config: AppConfig, DestPath: Path) -> None: + """One portable file in the same format as SaraFem's export: the shared + settings under [SaraSuite] (with a custom theme's palette as ThemeData) + and SaraTune's own under [SaraTune]. Other programs ignore [SaraTune], + and SaraTune ignores their sections.""" + Shared = LoadSharedSettings(Config) + SharedData = asdict(Shared) + if Shared.Theme not in BuiltinThemeNames: + ThemeFile = ThemesDir / f"{Shared.Theme}.toml" + if ThemeFile.exists(): + try: + SharedData["ThemeData"] = tomlkit.parse(ThemeFile.read_text()).unwrap().get("Palette", {}) + except tomlkit.exceptions.TOMLKitError: + pass + DestPath.write_text(tomlkit.dumps({"SaraSuite": SharedData, AppName: asdict(Config)})) + + +def ImportSettings(Config: AppConfig, SourcePath: Path) -> None: + """Applies [SaraSuite] to the shared file and [SaraTune] onto Config, key + by key: keys the file doesn't mention stay as they are, unknown sections + and keys are skipped. Raises TOMLKitError on an unreadable file.""" + Data = tomlkit.parse(SourcePath.read_text()).unwrap() + + SharedData = Data.get("SaraSuite") + if isinstance(SharedData, dict): + ThemeData = SharedData.pop("ThemeData", None) + Merged = asdict(LoadSharedSettings(Config)) + Merged.update({Key: Value for Key, Value in SharedData.items() if Key in SharedSettings.__dataclass_fields__}) + ImportedTheme = Merged.get("Theme") + if isinstance(ThemeData, dict) and ImportedTheme and ImportedTheme not in BuiltinThemeNames: + ThemesDir.mkdir(parents=True, exist_ok=True) + (ThemesDir / f"{ImportedTheme}.toml").write_text(tomlkit.dumps({"Palette": ThemeData})) + SharedSettings(**Merged).Save() + + OwnData = Data.get(AppName) + if isinstance(OwnData, dict): + for Key, Value in OwnData.items(): + if Key in AppConfig.__dataclass_fields__: + setattr(Config, Key, Value) + Config.Save() + + +def ApplyAppearance(Settings: SharedSettings) -> None: + """Theme colours and font; must run before anything reads Palette.X.""" + global ActiveFontFamily + ApplyTheme(Settings.Theme) + ActiveFontFamily = Settings.FontFamily or BundledFontFamily + + +def ChildEnvironment() -> dict[str, str]: + """The environment for child processes (xdg-open, fc-list). + A PyInstaller build points LD_LIBRARY_PATH at its own bundled libraries; + system tools need the original back.""" + Environment = dict(os.environ) + if getattr(sys, "frozen", False): + Original = Environment.pop("LD_LIBRARY_PATH_ORIG", None) + if Original is not None: + Environment["LD_LIBRARY_PATH"] = Original + else: + Environment.pop("LD_LIBRARY_PATH", None) + for Key in ("PYTHONHOME", "PYTHONPATH"): + Environment.pop(Key, None) + if IsWindows: + Environment["PYTHONUTF8"] = "1" # every Sara program reads and writes UTF-8 + return Environment + + +def BuildTheme() -> ft.Theme: + ColorScheme = ft.ColorScheme( + primary=Palette.Mauve, + on_primary=Palette.Crust, + primary_container=Palette.Surface0, + on_primary_container=Palette.Mauve, + secondary=Palette.Mauve, + on_secondary=Palette.Crust, + secondary_container=Palette.Surface0, + on_secondary_container=Palette.Mauve, + surface=Palette.Base, + on_surface=Palette.Text, + on_surface_variant=Palette.Overlay0, + surface_container=Palette.Mantle, + surface_container_low=Palette.Mantle, + surface_container_high=Palette.Surface0, + surface_container_highest=Palette.Surface1, + outline=Palette.Surface1, + outline_variant=Palette.Surface1, + error=Palette.Red, + on_error=Palette.Crust, + ) + return ft.Theme( + font_family=ActiveFontFamily, + color_scheme=ColorScheme, + use_material3=True, + scaffold_bgcolor=Palette.Base, + canvas_color=Palette.Base, + dialog_theme=ft.DialogTheme( + bgcolor=Palette.Mantle, + shape=ft.RoundedRectangleBorder(radius=8, side=ft.BorderSide(1, Palette.Mauve)), + title_text_style=ft.TextStyle(color=Palette.Mauve, size=18, font_family=ActiveFontFamily), + ), + ) + + +def BlendHex(BaseHex: str, AccentHex: str, Ratio: float) -> str: + """Solid colour mixing Base and Accent (0 = pure Base, 1 = pure Accent), + for tinted-but-still-dark backgrounds. Same as SaraFem's.""" + BaseRGB = tuple(int(BaseHex.lstrip("#")[i : i + 2], 16) for i in (0, 2, 4)) + AccentRGB = tuple(int(AccentHex.lstrip("#")[i : i + 2], 16) for i in (0, 2, 4)) + Mixed = (round(B + (A - B) * Ratio) for B, A in zip(BaseRGB, AccentRGB)) + return "#" + "".join(f"{C:02x}" for C in Mixed) + + +def NotificationStyle(Kind: str) -> tuple[str, str]: + """(Background, AccentColor) for a toast, from the theme's own colours like + SaraFem's: "success" green, "error" red, plus "warning" yellow and "info" + mauve for plain notes.""" + Accent = {"success": Palette.Green, "error": Palette.Red, "warning": Palette.Yellow}.get(Kind, Palette.Mauve) + return BlendHex(Palette.Mantle, Accent, 0.12), Accent + + +def ChipButtonStyle(Accent: str = "") -> ft.ButtonStyle: + """SaraView's button look: SurfaceAlt fill, 1px accent border, small radius.""" + return ft.ButtonStyle( + color=Palette.Text, + bgcolor=Palette.Surface0, + side=ft.BorderSide(1, Accent or Palette.Mauve), + shape=ft.RoundedRectangleBorder(radius=4), + ) + + +def ChipButton(Text: str, OnClick, Icon=None, Accent: str = "", Disabled: bool = False) -> ft.OutlinedButton: + return ft.OutlinedButton(content=Text, icon=Icon, style=ChipButtonStyle(Accent), on_click=OnClick, disabled=Disabled) + + +def Note(Text: str, Size: int = 12) -> ft.Text: + return ft.Text(Text, color=Palette.Overlay0, size=Size) + + +def Heading(Text: str) -> ft.Text: + return ft.Text(Text, color=Palette.Mauve, size=20) + + +def DisplayPath(PathText: str) -> str: + """/home/sarah/Music -> ~/Music, for labels.""" + HomeText = str(Home) + if PathText == HomeText or PathText.startswith(HomeText + os.sep): + return "~" + PathText[len(HomeText):] + return PathText + + +def FormatTime(Milliseconds: float) -> str: + """3:07, or 1:02:45 for an hour or more.""" + Seconds = max(0, int(Milliseconds // 1000)) + Hours, Seconds = divmod(Seconds, 3600) + Minutes, Seconds = divmod(Seconds, 60) + return f"{Hours}:{Minutes:02}:{Seconds:02}" if Hours else f"{Minutes}:{Seconds:02}" + + +def IsAudioFile(PathValue: Path) -> bool: + return PathValue.suffix.lower() in AudioExtensions and not PathValue.name.startswith(".") diff --git a/Covers.py b/Covers.py new file mode 100644 index 0000000..6827b05 --- /dev/null +++ b/Covers.py @@ -0,0 +1,58 @@ +"""Small versions of cover art. Embedded covers can be several megabytes, +and everything shown goes through Flet's connection, so the album grid and +play bar get thumbnails, cached in ~/.cache/SaraSuite/SaraTune/Covers/.""" + +import hashlib +import io +from pathlib import Path +from typing import Optional + +from PIL import Image, UnidentifiedImageError + +from Core import CacheDir +from Metadata import ReadCover + +CoversDir = CacheDir / "Covers" +_NoCover = b"" # remembered in the memory cache so a coverless album isn't read again + + +def Thumbnail(Data: bytes, Size: int) -> Optional[bytes]: + try: + with Image.open(io.BytesIO(Data)) as Picture: + Picture = Picture.convert("RGB") + Picture.thumbnail((Size, Size), Image.LANCZOS) + Output = io.BytesIO() + Picture.save(Output, "JPEG", quality=88) + return Output.getvalue() + except (UnidentifiedImageError, OSError, ValueError, Image.DecompressionBombError): + return None + + +_Memory: dict[tuple[str, int], bytes] = {} + + +def CoverFor(FilePath: str, Size: int) -> Optional[bytes]: + """FilePath's cover at most Size pixels wide. Blocking (run it in a + thread). Cached on disk by path, size and the file's mtime.""" + Key = (FilePath, Size) + if Key in _Memory: + return _Memory[Key] or None + try: + Stamp = Path(FilePath).stat().st_mtime_ns + except OSError: + return None + Name = hashlib.sha1(f"{FilePath}|{Size}|{Stamp}".encode("utf-8", "surrogateescape")).hexdigest() + CachePath = CoversDir / f"{Name}.jpg" + if CachePath.exists(): + Data = CachePath.read_bytes() + else: + Original = ReadCover(FilePath) + Data = Thumbnail(Original, Size) if Original else None + if Data: + try: + CoversDir.mkdir(parents=True, exist_ok=True) + CachePath.write_bytes(Data) + except OSError: + pass + _Memory[Key] = Data or _NoCover + return Data diff --git a/Library.py b/Library.py new file mode 100644 index 0000000..12514df --- /dev/null +++ b/Library.py @@ -0,0 +1,136 @@ +"""Music mode's library: every audio file below the configured folders, with +their tags. The tags are cached in ~/.cache/SaraSuite/SaraTune/Library.toml +so a start only reads files that are new or changed since the last scan.""" + +import json +import os +import tomllib +from collections import defaultdict +from pathlib import Path +from typing import Callable, Optional + +from Core import IsAudioFile, LibraryCachePath +from Metadata import Track, ReadTrack + +CacheVersion = 1 + + +def _TomlValue(Value) -> str: + # JSON's string escapes are valid TOML basic-string escapes. + if isinstance(Value, str): + return json.dumps(Value, ensure_ascii=False) + return repr(Value) + + +class Library: + def __init__(self): + self.Tracks: dict[str, Track] = {} + + # --- Cache -------------------------------------------------------------- + + def LoadCache(self) -> None: + try: + Data = tomllib.loads(LibraryCachePath.read_text(encoding="utf-8")) + except (OSError, tomllib.TOMLDecodeError): + return + if Data.get("Version") != CacheVersion: + return + self.Tracks = {} + for Entry in Data.get("Tracks", []): + if "Path" in Entry: + self.Tracks[Entry["Path"]] = Track.FromDict(Entry) + + def SaveCache(self) -> None: + # Written by hand: tomlkit takes seconds for a few thousand tables. + Lines = [f"Version = {CacheVersion}", ""] + for Item in sorted(self.Tracks.values(), key=lambda T: T.Path): + Lines.append("[[Tracks]]") + Lines += [f"{Key} = {_TomlValue(Value)}" for Key, Value in Item.ToDict().items()] + Lines.append("") + LibraryCachePath.parent.mkdir(parents=True, exist_ok=True) + Temporary = LibraryCachePath.with_suffix(".new") + Temporary.write_text("\n".join(Lines), encoding="utf-8") + Temporary.replace(LibraryCachePath) + + def ClearCache(self) -> None: + self.Tracks = {} + LibraryCachePath.unlink(missing_ok=True) + + # --- Scanning ----------------------------------------------------------- + + def Scan(self, Folders: list[str], OnProgress: Optional[Callable[[int], None]] = None) -> tuple[int, int]: + """Reads new and changed files, forgets ones that are gone. Blocking + (run it in a thread). Returns (added or changed, removed).""" + Found: dict[str, float] = {} + for Folder in Folders: + Root = Path(Folder).expanduser() + if not Root.is_dir(): + continue + for DirPath, DirNames, FileNames in os.walk(Root, followlinks=True): + DirNames[:] = [Name for Name in DirNames if not Name.startswith(".")] + for Name in FileNames: + FilePath = Path(DirPath) / Name + if IsAudioFile(FilePath): + try: + Found[str(FilePath)] = FilePath.stat().st_mtime + except OSError: + pass + Changed = 0 + for Index, (FilePath, Modified) in enumerate(Found.items()): + Known = self.Tracks.get(FilePath) + if Known is None or Known.Modified != Modified: + self.Tracks[FilePath] = ReadTrack(FilePath) + Changed += 1 + if OnProgress and Changed % 25 == 0: + OnProgress(Index + 1) + Gone = [FilePath for FilePath in self.Tracks if FilePath not in Found] + for FilePath in Gone: + del self.Tracks[FilePath] + if Changed or Gone: + self.SaveCache() + return Changed, len(Gone) + + # --- Views -------------------------------------------------------------- + + def Get(self, FilePath: str) -> Track: + """The library's Track, or one read on the spot for files outside it + (playlists may point anywhere).""" + return self.Tracks.get(FilePath) or ReadTrack(FilePath) + + def Songs(self) -> list[Track]: + return sorted(self.Tracks.values(), key=SongKey) + + def Albums(self) -> list[list[Track]]: + """Each album's tracks in disc/track order; albums by artist, then name.""" + Groups: dict[tuple[str, str], list[Track]] = defaultdict(list) + for Item in self.Tracks.values(): + if Item.Album: + Groups[Item.AlbumKey].append(Item) + return [sorted(Tracks, key=AlbumOrderKey) for _, Tracks in sorted(Groups.items())] + + def Artists(self) -> list[tuple[str, list[Track]]]: + Groups: dict[str, list[Track]] = defaultdict(list) + Names: dict[str, str] = {} + for Item in self.Tracks.values(): + Name = Item.AlbumArtist or Item.Artist or "Unknown artist" + Groups[Name.lower()].append(Item) + Names.setdefault(Name.lower(), Name) + return [(Names[Key], sorted(Groups[Key], key=ArtistOrderKey)) for Key in sorted(Groups)] + + +def SongKey(Item: Track) -> tuple: + return Item.DisplayArtist.lower(), Item.DisplayAlbum.lower(), Item.DiscNumber, Item.TrackNumber, Item.DisplayTitle.lower() + + +def AlbumOrderKey(Item: Track) -> tuple: + return Item.DiscNumber, Item.TrackNumber, Item.DisplayTitle.lower() + + +def ArtistOrderKey(Item: Track) -> tuple: + return Item.Year, Item.DisplayAlbum.lower(), Item.DiscNumber, Item.TrackNumber, Item.DisplayTitle.lower() + + +def Matches(Item: Track, Query: str) -> bool: + """Every word of the search has to appear in the title, artist, album or genre.""" + Haystack = " ".join((Item.DisplayTitle, Item.Artist, Item.AlbumArtist, Item.Album, Item.Genre)).lower() + return all(Word in Haystack for Word in Query.lower().split()) diff --git a/Main.py b/Main.py new file mode 100644 index 0000000..e7fba7a --- /dev/null +++ b/Main.py @@ -0,0 +1,246 @@ +"""SaraTune: a music player with two modes. + +- Audio: open a file or folder and listen right away; shows the file's + details and tags. Nothing is saved. +- Music: a library of your music folders, a queue, and saved playlists. + +Files given on the command line (e.g. "Open with SaraTune") open in Audio mode.""" + +import ctypes +import os +import shutil +import sys + +import flet as ft + +from AudioMode import AudioMode +from Core import ( + AppName, ApplyAppearance, AssetsDir, BuildTheme, BundledFontFamily, ChipButtonStyle, DataHome, EffectiveSettings, + IsWindows, LoadConfig, Palette, +) +from MusicMode import MusicMode +from Player import Player +from Settings import ShowSettingsDialog +from Transport import Ticker +from Widgets import App + +Modes = [("Audio", ft.Icons.HEADPHONES_OUTLINED), ("Music", ft.Icons.LIBRARY_MUSIC_OUTLINED)] + +# When running as an AppImage, SaraTune can put itself in the app menu (like SaraSuite). +_AppImage = os.environ.get("APPIMAGE", "") +_MenuEntry = DataHome / "applications" / "SaraTune.desktop" +_MenuIcon = DataHome / "SaraTune" / "SaraTune.png" +MimeTypes = "audio/mpeg;audio/flac;audio/x-flac;audio/ogg;audio/x-vorbis+ogg;audio/opus;audio/x-opus+ogg;" \ + "audio/wav;audio/x-wav;audio/mp4;audio/x-m4a;audio/aac;audio/x-aiff;audio/x-ms-wma;audio/x-matroska;" \ + "audio/webm;" + +# The Flet client's audio plugin is only in its "full" build, which on Linux +# needs the system's libmpv (Fedora: mpv-libs). Windows' full client brings +# everything itself. +def _HasMpv() -> bool: + """Loads the exact library the client links against (like the client + will), so LD_LIBRARY_PATH and odd install paths count too.""" + try: + ctypes.CDLL("libmpv.so.2") + return True + except OSError: + return False + + +NeedsMpv = sys.platform.startswith("linux") and not _HasMpv() + + +def _SetMenuEntry(Enabled: bool) -> None: + if not Enabled: + _MenuEntry.unlink(missing_ok=True) + _MenuIcon.unlink(missing_ok=True) + return + _MenuIcon.parent.mkdir(parents=True, exist_ok=True) + shutil.copy(AssetsDir / "Icon.png", _MenuIcon) + _MenuEntry.parent.mkdir(parents=True, exist_ok=True) + _MenuEntry.write_text( + "[Desktop Entry]\nType=Application\nName=SaraTune\n" + "Comment=Play audio files and your music library\n" + f"Exec={_AppImage} %F\nIcon={_MenuIcon}\nTerminal=false\n" + f"Categories=AudioVideo;Audio;Player;\nMimeType={MimeTypes}\nStartupNotify=true\n" + ) + + +def _SetUpPage(Page: ft.Page, Appearance) -> None: + Page.title = AppName + Page.theme_mode = ft.ThemeMode.DARK + Page.theme = BuildTheme() + Page.dark_theme = BuildTheme() + Page.fonts = {BundledFontFamily: "fonts/Fixedsys_Excelsior_Regular.ttf"} + Page.window.title_bar_hidden = Appearance.HideWindowTitleBar + Page.padding = 0 + Page.bgcolor = Palette.Base + if IsWindows: + Page.window.icon = str(AssetsDir / "Icon.ico") # taskbar/title bar (Linux: the desktop entry) + + +def MissingMpv(Page: ft.Page) -> None: + """Shown instead of the player when libmpv isn't installed.""" + ApplyAppearance(EffectiveSettings(LoadConfig())) + _SetUpPage(Page, EffectiveSettings(LoadConfig())) + Page.window.width, Page.window.height = 620, 360 + Command = "sudo dnf install mpv-libs" + Page.add(ft.Container(ft.Column([ + ft.Text(AppName, size=24, color=Palette.Mauve), + ft.Text("SaraTune needs libmpv to play audio.", size=16, color=Palette.Text), + ft.Text("Flet's audio player is part of its full desktop client, which links against the system's " + "libmpv. Install it, then start SaraTune again:", color=Palette.Overlay0), + ft.Container(ft.Text(Command, selectable=True, color=Palette.Green), bgcolor=Palette.Mantle, padding=10, + border_radius=4, border=ft.Border.all(1, Palette.Surface1)), + ft.Text("(On other distributions the package is usually called libmpv2 or mpv-libs.)", size=12, + color=Palette.Overlay0), + ], spacing=12), padding=24)) + + +def Main(Page: ft.Page) -> None: + Config = LoadConfig() + Appearance = EffectiveSettings(Config) + ApplyAppearance(Appearance) # before anything reads Palette.X + _SetUpPage(Page, Appearance) + Page.window.width = 1180 + Page.window.height = 760 + Page.window.min_width = 820 + Page.window.min_height = 520 + + TheApp = App(Page, Config) + + def SetVolume(Value: float) -> None: + Config.Volume = round(Value, 3) + for Each in Players.values(): + Each.SetVolume(Value) + for Name, Mode in ModeViews.items(): + if Mode is not ModeViews[State["Mode"]]: + Mode.Bar.SetVolume(Value) + + Players = {Name: Player(Page, Config.Volume) for Name, _ in Modes} + ModeViews = { + "Audio": AudioMode(TheApp, Players["Audio"], SetVolume), + "Music": MusicMode(TheApp, Players["Music"], SetVolume), + } + # One sound at a time: starting one mode's player pauses the other's. + for Name, ThePlayer in Players.items(): + Others = [Other for OtherName, Other in Players.items() if OtherName != Name] + ThePlayer.OnStarted = lambda Others=Others: [Page.run_task(Other.Pause) for Other in Others if Other.Playing] + + Body = ft.Container(expand=True) + State = {"Mode": ""} + + def SwitchMode(Name: str) -> None: + if Name not in ModeViews: + return + State["Mode"] = Name + for ModeName, Mode in ModeViews.items(): + Mode.Bar.Shown = ModeName == Name + Switcher.selected = [Name] + Body.content = ModeViews[Name] + Page.update() + ModeViews[Name].Bar.Refresh() + + def OpenInAudio(Paths: list[str]) -> None: + SwitchMode("Audio") + Page.run_task(ModeViews["Audio"].Open, Paths) + + TheApp.SwitchMode = SwitchMode + TheApp.OpenInAudio = OpenInAudio + + Switcher = ft.SegmentedButton( + segments=[ft.Segment(value=Name, label=Name, icon=Icon) for Name, Icon in Modes], + selected=["Music"], + show_selected_icon=False, + style=ft.ButtonStyle( + color={ft.ControlState.SELECTED: Palette.Crust, ft.ControlState.DEFAULT: Palette.Text}, + bgcolor={ft.ControlState.SELECTED: Palette.Mauve, ft.ControlState.DEFAULT: Palette.Surface0}, + side=ft.BorderSide(1, Palette.Mauve), + shape=ft.RoundedRectangleBorder(radius=4), + ), + on_change=lambda e: SwitchMode(e.control.selected[0]) if e.control.selected else None, + ) + + def ToggleMenuEntry(e) -> None: + _SetMenuEntry(not _MenuEntry.exists()) + MenuButton.content = "Remove from app menu" if _MenuEntry.exists() else "Add to app menu" + MenuButton.update() + + MenuButton = ft.OutlinedButton( + content="Remove from app menu" if _MenuEntry.exists() else "Add to app menu", + style=ChipButtonStyle(), + visible=bool(_AppImage), + on_click=ToggleMenuEntry, + ) + + def AfterSettings(FoldersChanged: bool) -> None: + if FoldersChanged: + ModeViews["Music"].StartScan() + + def ClearCache() -> None: + Music = ModeViews["Music"] + Music.Library.ClearCache() + Music.ShowView("Songs") + Music.StartScan() + + SettingsButton = ft.IconButton( + icon=ft.Icons.SETTINGS_OUTLINED, + icon_color=Palette.Mauve, + tooltip="Settings", + on_click=lambda e: ShowSettingsDialog(TheApp, AfterSettings, ClearCache), + ) + + async def OnKey(e: ft.KeyboardEvent) -> None: + # Not while a dialog is open (its text fields take the keys), and + # only in Audio mode: Music mode has a search field. + if any(Dialog.open and not isinstance(Dialog, ft.SnackBar) for Dialog in Page._dialogs.controls): + return + if State["Mode"] == "Audio" and not (e.ctrl or e.alt or e.meta): + await ModeViews["Audio"].HandleKey(e) + + Page.on_keyboard_event = OnKey + + async def OnWindowEvent(e: ft.WindowEvent) -> None: + if e.type == ft.WindowEventType.CLOSE: + ModeViews["Music"].SaveSession() + Config.Save() # the volume + await Page.window.destroy() + + Page.window.prevent_close = True + Page.window.on_event = OnWindowEvent + + Page.add(ft.Column([ + ft.Container( + content=ft.Row([ + ft.Text(AppName, size=24, color=Palette.Mauve), + ft.Container(width=16), + Switcher, + ft.Container(expand=True), + MenuButton, + SettingsButton, + ]), + padding=ft.Padding.symmetric(horizontal=16, vertical=8), + bgcolor=Palette.Mantle, + border=ft.Border.only(bottom=ft.BorderSide(1, Palette.Surface1)), + ), + Body, + ], expand=True, spacing=0)) + + Files = [Argument for Argument in sys.argv[1:] if not Argument.startswith("-")] + if Files: + SwitchMode("Audio") + Page.run_task(ModeViews["Audio"].Open, Files) + else: + SwitchMode(Config.StartMode if Config.StartMode in ModeViews else "Music") + Page.run_task(ModeViews["Music"].Started) + Page.run_task(Ticker, [Mode.Bar for Mode in ModeViews.values()]) + + +if __name__ == "__main__": + if NeedsMpv: + os.environ["FLET_DESKTOP_FLAVOR"] = "light" + os.environ.pop("FLET_VIEW_PATH", None) # Launch.sh's client copy is the full one + ft.run(MissingMpv, assets_dir=str(AssetsDir)) + else: + os.environ.setdefault("FLET_DESKTOP_FLAVOR", "full") + ft.run(Main, assets_dir=str(AssetsDir)) diff --git a/Metadata.py b/Metadata.py new file mode 100644 index 0000000..129ef31 --- /dev/null +++ b/Metadata.py @@ -0,0 +1,259 @@ +"""Reading audio files with mutagen: the tags Music mode sorts by, the full +details Audio mode shows, and cover art (embedded, or a cover image next to +the file). Never writes to the files.""" + +import base64 +from dataclasses import asdict, dataclass +from pathlib import Path +from typing import Optional + +import mutagen +from mutagen.flac import Picture + +# Images next to a file that count as its cover, best first. +CoverNames = ["cover", "folder", "front", "album", "albumart", "albumartsmall"] +CoverExtensions = [".jpg", ".jpeg", ".png", ".webp", ".gif", ".bmp"] + + +@dataclass +class Track: + Path: str + Title: str = "" + Artist: str = "" + Album: str = "" + AlbumArtist: str = "" + Genre: str = "" + Year: str = "" + TrackNumber: int = 0 + DiscNumber: int = 0 + Duration: int = 0 # milliseconds + Modified: float = 0 # the file's mtime when it was read, so a rescan can skip it + + @property + def DisplayTitle(self) -> str: + return self.Title or Path(self.Path).stem + + @property + def DisplayArtist(self) -> str: + return self.Artist or self.AlbumArtist or "Unknown artist" + + @property + def DisplayAlbum(self) -> str: + return self.Album or "Unknown album" + + @property + def AlbumKey(self) -> tuple[str, str]: + """Albums are told apart by album artist too, so two "Greatest Hits" stay two.""" + return (self.AlbumArtist or self.Artist or "").lower(), (self.Album or "").lower() + + def ToDict(self) -> dict: + return {Key: Value for Key, Value in asdict(self).items() if Value not in ("", 0) or Key == "Path"} + + @classmethod + def FromDict(cls, Data: dict) -> "Track": + Known = cls.__dataclass_fields__ + return cls(**{Key: Value for Key, Value in Data.items() if Key in Known}) + + +def _First(Tags, *Keys: str) -> str: + for Key in Keys: + try: + Value = Tags.get(Key) + except (KeyError, ValueError): + continue + if Value: + if isinstance(Value, list): + Value = Value[0] + return str(Value).strip() + return "" + + +def _Number(Text: str) -> int: + """"3/12" -> 3.""" + try: + return int(Text.split("/")[0].strip() or 0) + except ValueError: + return 0 + + +def _Open(FilePath: str, Easy: bool): + try: + return mutagen.File(FilePath, easy=Easy) + except Exception: # mutagen raises many kinds of errors for broken files + return None + + +def ReadTrack(FilePath: str) -> Track: + """The tags Music mode uses. A file mutagen can't read still becomes a + Track (titled after its file name), so it can be played.""" + Result = Track(Path=FilePath) + try: + Result.Modified = Path(FilePath).stat().st_mtime + except OSError: + pass + File = _Open(FilePath, Easy=True) + if File is None: + return Result + if File.info is not None and getattr(File.info, "length", None): + Result.Duration = int(File.info.length * 1000) + Tags = File.tags or {} + Result.Title = _First(Tags, "title") + Result.Artist = _First(Tags, "artist") + Result.Album = _First(Tags, "album") + Result.AlbumArtist = _First(Tags, "albumartist", "album artist", "performer") + Result.Genre = _First(Tags, "genre") + Result.Year = _First(Tags, "date", "year", "originaldate")[:4] + Result.TrackNumber = _Number(_First(Tags, "tracknumber", "track")) + Result.DiscNumber = _Number(_First(Tags, "discnumber", "disc")) + return Result + + +def ReadDetails(FilePath: str) -> tuple[list[tuple[str, str]], list[tuple[str, str]]]: + """(technical info, every tag) as label/value rows for Audio mode.""" + PathValue = Path(FilePath) + Info: list[tuple[str, str]] = [] + try: + Size = PathValue.stat().st_size + Info.append(("Size", _FormatSize(Size))) + except OSError: + pass + Info.append(("Format", PathValue.suffix.lstrip(".").upper() or "?")) + Tags: list[tuple[str, str]] = [] + File = _Open(FilePath, Easy=False) + if File is None: + Info.append(("Note", "mutagen couldn't read this file's details")) + return Info, Tags + + Stream = File.info + Codec = getattr(Stream, "codec_description", "") or getattr(Stream, "codec", "") + if Codec: + Info.append(("Codec", str(Codec))) + if getattr(Stream, "length", None): + from Core import FormatTime + Info.append(("Length", FormatTime(Stream.length * 1000))) + if getattr(Stream, "bitrate", None): + Mode = getattr(Stream, "bitrate_mode", None) + ModeText = {1: " CBR", 2: " VBR", 3: " ABR"}.get(int(Mode), "") if Mode is not None else "" + Info.append(("Bitrate", f"{round(Stream.bitrate / 1000)} kbps{ModeText}")) + if getattr(Stream, "sample_rate", None): + Info.append(("Sample rate", f"{Stream.sample_rate / 1000:g} kHz")) + if getattr(Stream, "bits_per_sample", None): + Info.append(("Bit depth", f"{Stream.bits_per_sample} bit")) + if getattr(Stream, "channels", None): + Info.append(("Channels", {1: "Mono", 2: "Stereo"}.get(Stream.channels, str(Stream.channels)))) + + for Key, Value in _AllTags(File): + Tags.append((Key, Value)) + return Info, Tags + + +# Readable names for ID3 frames and MP4 atoms; Vorbis comments (FLAC, Ogg, +# Opus) already have names, and only get title-cased. +TagNames = { + "TIT2": "Title", "TPE1": "Artist", "TPE2": "Album artist", "TALB": "Album", "TRCK": "Track", "TPOS": "Disc", + "TDRC": "Date", "TYER": "Year", "TCON": "Genre", "TCOM": "Composer", "TBPM": "BPM", "TSSE": "Encoder", + "TENC": "Encoded by", "TCOP": "Copyright", "TPUB": "Publisher", "TLAN": "Language", "COMM": "Comment", + "USLT": "Lyrics", "TIT1": "Grouping", "TIT3": "Subtitle", "TKEY": "Key", "TMOO": "Mood", "TSRC": "ISRC", + "TDOR": "Original date", "TOPE": "Original artist", "TEXT": "Lyricist", "TLEN": "Length (ms)", + "\xa9nam": "Title", "\xa9ART": "Artist", "aART": "Album artist", "\xa9alb": "Album", "trkn": "Track", + "disk": "Disc", "\xa9day": "Date", "\xa9gen": "Genre", "\xa9wrt": "Composer", "\xa9too": "Encoder", + "\xa9cmt": "Comment", "\xa9lyr": "Lyrics", "cprt": "Copyright", "tmpo": "BPM", "\xa9grp": "Grouping", + "tracknumber": "Track", "discnumber": "Disc", "albumartist": "Album artist", "tracktotal": "Total tracks", + "totaltracks": "Total tracks", "disctotal": "Total discs", "totaldiscs": "Total discs", +} + + +def _TagName(Key: str) -> str: + if Key.startswith("TXXX:"): # ID3 user text frames read "TXXX:Name" + return Key[5:] + if Key.startswith("----:"): # MP4 freeform atoms read "----:com.apple.iTunes:Name" + return Key.rsplit(":", 1)[-1] + Base = Key.split(":")[0] # COMM frames read "COMM::eng" + if Base in TagNames: + return TagNames[Base] + return Base.replace("_", " ").capitalize() if Base.islower() else Base + + +def _AllTags(File) -> list[tuple[str, str]]: + """Readable key/value pairs from any tag format, without the binary ones.""" + Rows: list[tuple[str, str]] = [] + if not File.tags: + return Rows + try: + Items = list(File.tags.items()) + except (AttributeError, TypeError): + return Rows + for Key, Value in Items: + KeyText = str(Key) + if KeyText.startswith(("APIC", "covr", "metadata_block_picture", "PRIV", "GEOB", "MCDI")): + continue + if hasattr(Value, "text"): # ID3 frames + Value = Value.text + if isinstance(Value, list): + # MP4 track/disc numbers are (number, total) pairs. + Value = ", ".join("/".join(str(N) for N in Part if N) if isinstance(Part, tuple) else + Part.decode("utf-8", "replace") if isinstance(Part, bytes) else str(Part) for Part in Value) + Text = str(Value) + if not Text or len(Text) > 400 or "\x00" in Text: + continue + Rows.append((_TagName(KeyText), Text)) + return Rows + + +def ReadCover(FilePath: str) -> Optional[bytes]: + """The embedded front cover (or the first picture), else an image file + named like cover.jpg/folder.png in the same folder.""" + File = _Open(FilePath, Easy=False) + Embedded = _EmbeddedCover(File) if File is not None else None + if Embedded: + return Embedded + return FolderCover(Path(FilePath).parent) + + +def _EmbeddedCover(File) -> Optional[bytes]: + try: + Pictures = getattr(File, "pictures", None) # FLAC + if Pictures: + Front = [P for P in Pictures if P.type == 3] + return (Front or Pictures)[0].data + Tags = File.tags + if Tags is None: + return None + if hasattr(Tags, "getall"): # ID3 + Frames = Tags.getall("APIC") + if Frames: + Front = [F for F in Frames if F.type == 3] + return (Front or Frames)[0].data + if "covr" in Tags: # MP4 + return bytes(Tags["covr"][0]) + if "metadata_block_picture" in Tags: # Ogg Vorbis/Opus + for Encoded in Tags["metadata_block_picture"]: + return Picture(base64.b64decode(Encoded)).data + except Exception: + return None + return None + + +def FolderCover(Folder: Path) -> Optional[bytes]: + try: + Files = {Child.name.lower(): Child for Child in Folder.iterdir() if Child.is_file()} + except OSError: + return None + for Name in CoverNames: + for Extension in CoverExtensions: + Found = Files.get(Name + Extension) + if Found: + try: + return Found.read_bytes() + except OSError: + return None + return None + + +def _FormatSize(Size: int) -> str: + Value = float(Size) + for Unit in ("B", "KB", "MB", "GB"): + if Value < 1024 or Unit == "GB": + return f"{Value:.0f} {Unit}" if Unit == "B" else f"{Value:.1f} {Unit}" + Value /= 1024 + return f"{Size} B" diff --git a/MusicMode.py b/MusicMode.py new file mode 100644 index 0000000..a66bfad --- /dev/null +++ b/MusicMode.py @@ -0,0 +1,811 @@ +"""Music mode: a library of the music folders (songs, albums, artists), a +queue with shuffle and repeat, and saved playlists. The queue and position +are kept between runs (Session.toml), and the library's tags are cached.""" + +import asyncio +import random +from pathlib import Path +from typing import Callable, Optional + +import flet as ft + +from Core import ChipButton, DisplayPath, FormatTime, Home, IsAudioFile, Note, Palette +from Covers import CoverFor +from Library import Library, Matches +from Metadata import Track +from Player import Player +from Playlists import ExportM3U, LoadPlaylists, NameTaken, Playlist, ReadM3U +from PlayQueue import RestartAfterMs, Session, LoadSession +from Transport import CoverImage, Transport +from Widgets import App, EmptyState + +SidebarWidth = 220 +RowHeight = 44 +PageSize = 400 # rows shown at once; "Show more" adds another page +AlbumCardWidth = 168 +AlbumCoverSize = 150 +SaveEverySeconds = 15 + +LibraryViews = [ + ("Songs", ft.Icons.MUSIC_NOTE_OUTLINED), + ("Albums", ft.Icons.ALBUM_OUTLINED), + ("Artists", ft.Icons.PERSON_OUTLINE), + ("Queue", ft.Icons.QUEUE_MUSIC), +] + + +class MusicMode(ft.Column): + def __init__(self, TheApp: App, ThePlayer: Player, OnVolume: Callable[[float], None]): + super().__init__(expand=True, spacing=0) + self.TheApp = TheApp + self.Config = TheApp.Config + self.ThePlayer = ThePlayer + self.Library = Library() + self.Library.LoadCache() + self.Session = LoadSession() if self.Config.RememberSession else Session() + self.Queue = self.Session.Queue + self.Playlists: list[Playlist] = LoadPlaylists() + self.Scanning = False + # What the open view shows, for Play all / clicking a row. + self.View = "" + self.ViewArgument: object = None + self.Shown: list[Track] = [] + self.RowsByPath: dict[str, list[ft.Container]] = {} + + ThePlayer.OnEnded = lambda: TheApp.Page.run_task(self._Ended) + ThePlayer.OnChanged = self._PlayerChanged + ThePlayer.OnError = lambda Message: TheApp.Notify( + f"Can't play {Path(ThePlayer.Source or '').name}: {Message}", "error") + + # --- Sidebar --------------------------------------------------------- + self.NavTiles: dict[str, ft.Container] = {} + self.PlaylistColumn = ft.Column(spacing=0) + self.ScanNote = Note("") + self.SidebarList = ft.Column(scroll=ft.ScrollMode.AUTO, expand=True, spacing=0) + Sidebar = ft.Container( + ft.Column([ + self.SidebarList, + ft.Divider(height=1, color=Palette.Surface1), + ft.Row([ + ft.Container(self.ScanNote, expand=True), + ft.IconButton(ft.Icons.REFRESH, icon_color=Palette.Mauve, icon_size=18, + tooltip="Look for new and changed files", on_click=lambda e: self.StartScan()), + ]), + ], spacing=4, expand=True), + width=SidebarWidth, bgcolor=Palette.Mantle, padding=ft.Padding.only(left=6, right=6, top=6), + border=ft.Border.only(right=ft.BorderSide(1, Palette.Surface1)), + ) + + # --- Main area ------------------------------------------------------- + self.Heading = ft.Text("", size=22, color=Palette.Mauve, max_lines=1, overflow=ft.TextOverflow.ELLIPSIS) + self.SubHeading = Note("") + self.Search = ft.TextField( + hint_text="Search", prefix_icon=ft.Icons.SEARCH, dense=True, width=260, + on_change=lambda e: self._Rebuild(), on_submit=lambda e: self._Rebuild(), + ) + self.Actions = ft.Row(spacing=6) + Header = ft.Container( + ft.Row([ + ft.Column([self.Heading, self.SubHeading], spacing=0, expand=True, tight=True), + self.Actions, + self.Search, + ], vertical_alignment=ft.CrossAxisAlignment.CENTER), + padding=ft.Padding.only(left=20, right=16, top=12, bottom=8), + ) + self.Body = ft.Container(expand=True, padding=ft.Padding.symmetric(horizontal=12)) + + # --- Play bar -------------------------------------------------------- + self.ShuffleButton = ft.IconButton(ft.Icons.SHUFFLE, tooltip="Shuffle", on_click=self._ToggleShuffle) + self.RepeatButton = ft.IconButton(ft.Icons.REPEAT, tooltip="Repeat", on_click=self._CycleRepeat) + self._ShowModes() + self.Bar = Transport(ThePlayer, self.Previous, self.Next, OnVolume, + Extras=[self.ShuffleButton, self.RepeatButton], + OnInfoClick=lambda e: self.ShowView("Queue")) + + self.controls = [ + ft.Row([ + Sidebar, + ft.Column([Header, self.Body], expand=True, spacing=0), + ], expand=True, spacing=0, vertical_alignment=ft.CrossAxisAlignment.STRETCH), + self.Bar, + ] + self._BuildSidebar() + + + def _Show(self, Control: ft.Control) -> None: + """Updates Control if this mode is on screen; a hidden mode's changes + go out with Main's page update when it's shown.""" + if self.Bar.Shown: + Control.update() + + # --- Start / stop ------------------------------------------------------- + + async def Started(self) -> None: + """Once the page is up: the saved view and track (paused), the scan, autosave.""" + View = self.Session.View + self.ShowView(View if View in dict(LibraryViews) else "Songs") + if self.Queue.Current: + await self._LoadCurrent(Play=False, Start=self.Session.Position) + if self.Config.ScanOnStart or not self.Library.Tracks: + self.StartScan() + self.TheApp.Page.run_task(self._AutoSave) + + async def _AutoSave(self) -> None: + while True: + await asyncio.sleep(SaveEverySeconds) + if self.ThePlayer.Playing: + self.SaveSession() + + def SaveSession(self) -> None: + if not self.Config.RememberSession: + return + self.Session.Position = self.ThePlayer.Position if self.ThePlayer.Source else 0 + self.Session.View = self.View if self.View in dict(LibraryViews) else "Songs" + try: + self.Session.Save() + except OSError: + pass + + # --- Scanning ----------------------------------------------------------- + + def StartScan(self) -> None: + if self.Scanning: + return + self.TheApp.Page.run_task(self._Scan) + + async def _Scan(self) -> None: + self.Scanning = True + self.ScanNote.value = "Scanning..." + self._Show(self.ScanNote) + Folders = [Folder for Folder in self.Config.MusicFolders if Folder.strip()] + Loop = asyncio.get_running_loop() + + def Progress(Count: int) -> None: + def Show() -> None: + self.ScanNote.value = f"Scanning... {Count} files" + self._Show(self.ScanNote) + Loop.call_soon_threadsafe(Show) + + try: + Changed, Removed = await asyncio.to_thread(self.Library.Scan, Folders, Progress) + except OSError as Error: + self.TheApp.Notify(f"Scanning failed: {Error}", "error") + Changed = Removed = 0 + self.Scanning = False + self._ShowCount() + if Changed or Removed: + self._Rebuild() + if not Folders or not any(Path(Folder).expanduser().is_dir() for Folder in Folders): + self.TheApp.Notify("None of the music folders exist. Pick them in Settings > Music.", "warning") + + def _ShowCount(self) -> None: + Count = len(self.Library.Tracks) + self.ScanNote.value = f"{Count} song{'s' if Count != 1 else ''}" + self.ScanNote.tooltip = "\n".join(DisplayPath(str(Path(F).expanduser())) for F in self.Config.MusicFolders) + self._Show(self.ScanNote) + + # --- Sidebar ------------------------------------------------------------ + + def _NavTile(self, Key: str, Label: str, Icon, OnClick) -> ft.Container: + Tile = ft.Container( + ft.Row([ft.Icon(Icon, size=18, color=Palette.Mauve), + ft.Text(Label, size=14, color=Palette.Text, max_lines=1, overflow=ft.TextOverflow.ELLIPSIS, expand=True)], + spacing=10), + padding=ft.Padding.symmetric(horizontal=10, vertical=8), border_radius=4, ink=True, on_click=OnClick, + ) + self.NavTiles[Key] = Tile + return Tile + + def _BuildSidebar(self) -> None: + self.NavTiles = {} + Items: list[ft.Control] = [ft.Container(Note("Library"), padding=ft.Padding.only(left=10, top=4, bottom=4))] + for Name, Icon in LibraryViews: + Items.append(self._NavTile(Name, Name, Icon, lambda e, N=Name: self.ShowView(N))) + Items.append(ft.Container( + ft.Row([ + Note("Playlists"), + ft.Container(expand=True), + ft.IconButton(ft.Icons.FILE_OPEN_OUTLINED, icon_size=16, icon_color=Palette.Mauve, + tooltip="Import an .m3u playlist", on_click=self._ImportM3U), + ft.IconButton(ft.Icons.ADD, icon_size=18, icon_color=Palette.Mauve, tooltip="New playlist", + on_click=lambda e: self._NewPlaylist()), + ], spacing=0), + padding=ft.Padding.only(left=10, top=12), + )) + for Item in self.Playlists: + Items.append(self._NavTile(f"Playlist:{Item.Name}", Item.Name, ft.Icons.PLAYLIST_PLAY, + lambda e, N=Item.Name: self.ShowView("Playlist", N))) + if not self.Playlists: + Items.append(ft.Container(Note("None yet. Make one with +, or from the queue."), + padding=ft.Padding.only(left=10, right=6))) + self.SidebarList.controls = Items + self._MarkNav() + + def _MarkNav(self) -> None: + Selected = f"Playlist:{self.ViewArgument}" if self.View == "Playlist" else self.View + if self.View in ("Album", "Artist"): + Selected = self.View + "s" + for Key, Tile in self.NavTiles.items(): + Tile.bgcolor = Palette.Surface0 if Key == Selected else None + + # --- Views -------------------------------------------------------------- + + def ShowView(self, View: str, Argument: object = None, KeepSearch: bool = False) -> None: + if not KeepSearch: + self.Search.value = "" + self.View, self.ViewArgument = View, Argument + self._MarkNav() + self._Rebuild() + + def _Rebuild(self) -> None: + Builders = { + "Songs": self._SongsView, "Albums": self._AlbumsView, "Artists": self._ArtistsView, + "Queue": self._QueueView, "Playlist": self._PlaylistView, "Album": self._AlbumView, + "Artist": self._ArtistView, + } + self.Actions.controls = [] + self.Search.visible = self.View in ("Songs", "Albums", "Artists") + Builders.get(self.View, self._SongsView)() + self._Show(self) + + def _SetHeading(self, Title: str, Sub: str) -> None: + self.Heading.value = Title + self.SubHeading.value = Sub + + def _PlayButtons(self, Tracks: list[Track]) -> list[ft.Control]: + Paths = [Item.Path for Item in Tracks] + return [ + ChipButton("Play", lambda e: self.TheApp.Page.run_task(self.PlayList, Paths, 0), ft.Icons.PLAY_ARROW, + Disabled=not Paths), + ChipButton("Shuffle", lambda e: self.TheApp.Page.run_task(self.PlayList, Paths, -1), ft.Icons.SHUFFLE, + Disabled=not Paths), + ft.PopupMenuButton(icon=ft.Icons.MORE_HORIZ, icon_color=Palette.Mauve, items=[ + ft.PopupMenuItem(content="Add all to queue", on_click=lambda e: self._Enqueue(Paths)), + ft.PopupMenuItem(content="Add all to a playlist...", on_click=lambda e: self._AddToPlaylist(Paths)), + ]), + ] + + def _Filtered(self, Tracks: list[Track]) -> list[Track]: + Query = (self.Search.value or "").strip() + return [Item for Item in Tracks if Matches(Item, Query)] if Query else Tracks + + def _SongsView(self) -> None: + Tracks = self._Filtered(self.Library.Songs()) + self._SetHeading("Songs", f"{len(Tracks)} songs" + (f" matching \"{self.Search.value.strip()}\"" + if (self.Search.value or "").strip() else "")) + self.Actions.controls = self._PlayButtons(Tracks) + if not self.Library.Tracks: + self.Body.content = EmptyState( + "No music yet" if not self.Scanning else "Scanning your music...", + "SaraTune lists the audio files in " + ", ".join(DisplayPath(F) for F in self.Config.MusicFolders) + + ". Change the folders in Settings > Music.") + return + self.Body.content = self._TrackList(Tracks) + + def _AlbumsView(self) -> None: + Query = (self.Search.value or "").strip() + Albums = [Album for Album in self.Library.Albums() if not Query or any(Matches(Item, Query) for Item in Album)] + self._SetHeading("Albums", f"{len(Albums)} albums") + if not Albums: + self.Body.content = EmptyState("No albums", "Albums come from the files' album tags.", ft.Icons.ALBUM_OUTLINED) + return + Cards: list[ft.Control] = [] + Pending: list[tuple[str, ft.Container]] = [] + for Album in Albums: + First = Album[0] + CoverBox = ft.Container(CoverImage(None, AlbumCoverSize, 6)) + Pending.append((First.Path, CoverBox)) + Cards.append(ft.Container( + ft.Column([ + CoverBox, + ft.Text(First.DisplayAlbum, size=13, color=Palette.Text, max_lines=1, overflow=ft.TextOverflow.ELLIPSIS), + ft.Text(First.AlbumArtist or First.DisplayArtist, size=12, color=Palette.Overlay0, max_lines=1, + overflow=ft.TextOverflow.ELLIPSIS), + ], spacing=4, tight=True), + padding=8, border_radius=6, ink=True, tooltip=f"{First.DisplayAlbum}\n{len(Album)} songs", + on_click=lambda e, Key=First.AlbumKey: self.ShowView("Album", Key), + )) + self.Body.content = ft.GridView(Cards, max_extent=AlbumCardWidth, child_aspect_ratio=0.78, spacing=4, + run_spacing=4, expand=True) + self.TheApp.Page.run_task(self._LoadCovers, Pending, self.Body.content) + + async def _LoadCovers(self, Pending: list[tuple[str, ft.Container]], Grid: ft.Control) -> None: + for Index, (FilePath, Box) in enumerate(Pending): + if self.Body.content is not Grid: + return # left the albums view + Data = await asyncio.to_thread(CoverFor, FilePath, AlbumCoverSize * 2) + if Data: + Box.content = CoverImage(Data, AlbumCoverSize, 6) + if Index % 12 == 11 or Index == len(Pending) - 1: + self._Show(Grid) + + def _AlbumTracks(self, Key) -> list[Track]: + for Album in self.Library.Albums(): + if Album[0].AlbumKey == Key: + return Album + return [] + + def _AlbumView(self) -> None: + Tracks = self._AlbumTracks(self.ViewArgument) + if not Tracks: + self.ShowView("Albums") + return + First = Tracks[0] + Length = sum(Item.Duration for Item in Tracks) + self._SetHeading(First.DisplayAlbum, " - ".join(Part for Part in ( + First.AlbumArtist or First.DisplayArtist, First.Year, f"{len(Tracks)} songs", FormatTime(Length)) if Part)) + self.Actions.controls = [ChipButton("Albums", lambda e: self.ShowView("Albums"), ft.Icons.ARROW_BACK)] + \ + self._PlayButtons(Tracks) + self.Body.content = self._TrackList(Tracks, Numbered=True) + + def _ArtistsView(self) -> None: + Query = (self.Search.value or "").strip().lower() + Artists = [(Name, Tracks) for Name, Tracks in self.Library.Artists() if not Query or Query in Name.lower()] + self._SetHeading("Artists", f"{len(Artists)} artists") + if not Artists: + self.Body.content = EmptyState("No artists", "", ft.Icons.PERSON_OUTLINE) + return + Rows = [] + for Name, Tracks in Artists: + Albums = len({Item.AlbumKey for Item in Tracks if Item.Album}) + Rows.append(ft.Container( + ft.Row([ + ft.Icon(ft.Icons.PERSON, color=Palette.Mauve, size=18), + ft.Text(Name, size=14, color=Palette.Text, expand=True, max_lines=1, overflow=ft.TextOverflow.ELLIPSIS), + Note(f"{Albums} album{'s' if Albums != 1 else ''}, {len(Tracks)} song{'s' if len(Tracks) != 1 else ''}"), + ], spacing=10), + padding=ft.Padding.symmetric(horizontal=10), height=RowHeight, border_radius=4, ink=True, + on_click=lambda e, N=Name: self.ShowView("Artist", N), + )) + self.Body.content = ft.ListView(Rows, item_extent=RowHeight, expand=True) + + def _ArtistView(self) -> None: + Tracks = next((T for Name, T in self.Library.Artists() if Name == self.ViewArgument), []) + self._SetHeading(str(self.ViewArgument), f"{len(Tracks)} songs") + self.Actions.controls = [ChipButton("Artists", lambda e: self.ShowView("Artists"), ft.Icons.ARROW_BACK)] + \ + self._PlayButtons(Tracks) + self.Body.content = self._TrackList(Tracks) + + def _QueueView(self) -> None: + Tracks = [self.Library.Get(PathText) for PathText in self.Queue.Paths] + Left = sum(Item.Duration for Item in Tracks[max(self.Queue.Index, 0):]) + self._SetHeading("Queue", f"{len(Tracks)} songs" + (f", {FormatTime(Left)} left" if Tracks else "")) + self.Actions.controls = [ + ChipButton("Save as playlist", lambda e: self._QueueToPlaylist(), ft.Icons.PLAYLIST_ADD, + Disabled=not Tracks), + ChipButton("Clear", lambda e: self._ClearQueue(), ft.Icons.CLEAR_ALL, Accent=Palette.Red, + Disabled=not Tracks), + ] + if not Tracks: + self.Body.content = EmptyState("The queue is empty", "Play something from the library or a playlist.", + ft.Icons.QUEUE_MUSIC) + return + self.Body.content = self._TrackList(Tracks, Context="Queue") + + def _Playlist(self, Name) -> Optional[Playlist]: + return next((Item for Item in self.Playlists if Item.Name == Name), None) + + def _PlaylistView(self) -> None: + Item = self._Playlist(self.ViewArgument) + if Item is None: + self.ShowView("Songs") + return + Tracks = [self.Library.Get(PathText) for PathText in Item.Tracks] + self._SetHeading(Item.Name, f"{len(Tracks)} songs, {FormatTime(sum(T.Duration for T in Tracks))}") + self.Actions.controls = self._PlayButtons(Tracks)[:2] + [ + ft.PopupMenuButton(icon=ft.Icons.MORE_HORIZ, icon_color=Palette.Mauve, items=[ + ft.PopupMenuItem(content="Add files...", icon=ft.Icons.ADD, on_click=self._AddFilesToPlaylist), + ft.PopupMenuItem(content="Add all to queue", icon=ft.Icons.QUEUE, + on_click=lambda e: self._Enqueue(list(Item.Tracks))), + ft.PopupMenuItem(content="Rename...", icon=ft.Icons.EDIT_OUTLINED, on_click=lambda e: self._RenamePlaylist(Item)), + ft.PopupMenuItem(content="Export as .m3u...", icon=ft.Icons.SAVE_ALT, on_click=lambda e: self.TheApp.Page.run_task(self._ExportM3U, Item)), + ft.PopupMenuItem(content="Delete", icon=ft.Icons.DELETE_OUTLINE, on_click=lambda e: self._DeletePlaylist(Item)), + ]), + ] + if not Tracks: + self.Body.content = EmptyState("This playlist is empty", + "Add songs with the ... button on any song, or Add files above.", + ft.Icons.PLAYLIST_ADD) + return + self.Body.content = self._TrackList(Tracks, Context="Playlist", Numbered=True) + + # --- Track rows --------------------------------------------------------- + + def _TrackList(self, Tracks: list[Track], Context: str = "Library", Numbered: bool = False) -> ft.Control: + """Library rows play the list from there; Queue rows jump in the queue; + Queue and Playlist rows can be dragged into another order.""" + self.Shown = Tracks + self.RowsByPath = {} + Reorderable = Context in ("Queue", "Playlist") + Header = ft.Container(ft.Row([ + ft.Container(width=36), + ft.Text("Title", size=12, color=Palette.Overlay0, expand=4), + ft.Text("Artist", size=12, color=Palette.Overlay0, expand=3), + ft.Text("Album", size=12, color=Palette.Overlay0, expand=3), + ft.Container(ft.Icon(ft.Icons.SCHEDULE, size=14, color=Palette.Overlay0), width=56), + ft.Container(width=40 + (24 if Reorderable else 0)), + ], spacing=8), padding=ft.Padding.symmetric(horizontal=8)) + + # Reorderable lists need all rows (dragging works on the full list); + # the library pages through long ones. + Limit = len(Tracks) if Reorderable else PageSize + Rows = [self._TrackRow(Item, Index, Context, Numbered) for Index, Item in enumerate(Tracks[:Limit])] + if Reorderable: + List = ft.ReorderableListView(Rows, expand=True, show_default_drag_handles=False, + on_reorder=lambda e: self._Reordered(Context, e.old_index, e.new_index)) + else: + List = ft.ListView(Rows, expand=True, item_extent=RowHeight) + if len(Tracks) > Limit: + self._AddMoreButton(List, Tracks, Limit, Context, Numbered) + self._MarkPlaying() + return ft.Column([Header, ft.Divider(height=1, color=Palette.Surface1), List], expand=True, spacing=0) + + def _AddMoreButton(self, List: ft.ListView, Tracks: list[Track], Shown: int, Context: str, Numbered: bool) -> None: + def More(e) -> None: + List.controls.pop() + Next = min(len(Tracks), Shown + PageSize) + List.controls += [self._TrackRow(Item, Index, Context, Numbered) + for Index, Item in enumerate(Tracks[Shown:Next], start=Shown)] + if Next < len(Tracks): + self._AddMoreButton(List, Tracks, Next, Context, Numbered) + self._MarkPlaying() + self._Show(List) + + List.controls.append(ft.Container( + ChipButton(f"Show more ({len(Tracks) - Shown} left)", More), alignment=ft.Alignment.CENTER, height=RowHeight)) + + def _TrackRow(self, Item: Track, Index: int, Context: str, Numbered: bool) -> ft.Control: + Lead = ft.Text(str(Item.TrackNumber or Index + 1) if Numbered else "", size=12, color=Palette.Overlay0, + width=36, text_align=ft.TextAlign.CENTER) + Menu = [ + ft.PopupMenuItem(content="Play next", icon=ft.Icons.PLAYLIST_PLAY, on_click=lambda e: self._PlayNext([Item.Path])), + ft.PopupMenuItem(content="Add to queue", icon=ft.Icons.QUEUE, on_click=lambda e: self._Enqueue([Item.Path])), + ft.PopupMenuItem(content="Add to a playlist...", icon=ft.Icons.PLAYLIST_ADD, + on_click=lambda e: self._AddToPlaylist([Item.Path])), + ] + if Item.Album: + Menu.append(ft.PopupMenuItem(content="Go to album", icon=ft.Icons.ALBUM_OUTLINED, + on_click=lambda e: self.ShowView("Album", Item.AlbumKey))) + Menu.append(ft.PopupMenuItem(content="Open in Audio mode", icon=ft.Icons.HEADPHONES_OUTLINED, + on_click=lambda e: self.TheApp.OpenInAudio([Item.Path]))) + if Context == "Queue": + Menu.append(ft.PopupMenuItem(content="Remove from queue", icon=ft.Icons.REMOVE_CIRCLE_OUTLINE, + on_click=lambda e: self._RemoveFromQueue(Index))) + elif Context == "Playlist": + Menu.append(ft.PopupMenuItem(content="Remove from playlist", icon=ft.Icons.REMOVE_CIRCLE_OUTLINE, + on_click=lambda e: self._RemoveFromPlaylist(Index))) + Missing = Context != "Library" and not Path(Item.Path).is_file() + TextColour = Palette.Overlay0 if Missing else Palette.Text + Cells: list[ft.Control] = [ + Lead, + ft.Text(Item.DisplayTitle + (" (missing)" if Missing else ""), size=14, color=TextColour, expand=4, + max_lines=1, overflow=ft.TextOverflow.ELLIPSIS), + ft.Text(Item.DisplayArtist, size=13, color=Palette.Overlay0, expand=3, max_lines=1, + overflow=ft.TextOverflow.ELLIPSIS), + ft.Text(Item.DisplayAlbum if Item.Album else "", size=13, color=Palette.Overlay0, expand=3, max_lines=1, + overflow=ft.TextOverflow.ELLIPSIS), + ft.Text(FormatTime(Item.Duration) if Item.Duration else "", size=12, color=Palette.Overlay0, width=56), + ft.PopupMenuButton(icon=ft.Icons.MORE_VERT, icon_color=Palette.Overlay0, icon_size=18, items=Menu), + ] + if Context in ("Queue", "Playlist"): + Cells.append(ft.ReorderableDragHandle(ft.Icon(ft.Icons.DRAG_INDICATOR, color=Palette.Overlay0, size=18), + mouse_cursor=ft.MouseCursor.GRAB)) + Row = ft.Container( + ft.Row(Cells, spacing=8), height=RowHeight, padding=ft.Padding.symmetric(horizontal=8), border_radius=4, + ink=True, on_click=lambda e: self.TheApp.Page.run_task(self._RowClicked, Context, Index), + data=(Context, Index), + ) + self.RowsByPath.setdefault(Item.Path, []).append(Row) + return Row + + def _MarkPlaying(self) -> None: + """Highlights the current track's rows (in the queue: only its own entry).""" + Current = self.Queue.Current if self.ThePlayer.Source == self.Queue.Current else None + for PathText, Rows in self.RowsByPath.items(): + for Row in Rows: + Context, Index = Row.data + IsCurrent = PathText == Current and (Context != "Queue" or Index == self.Queue.Index) + Row.bgcolor = Palette.Surface0 if IsCurrent else None + Lead = Row.content.controls[0] + if IsCurrent: + Row.content.controls[0] = ft.Container( + ft.Icon(ft.Icons.VOLUME_UP if self.ThePlayer.Playing else ft.Icons.PAUSE, size=16, + color=Palette.Mauve), width=36, data=Lead) + elif isinstance(Lead, ft.Container) and isinstance(Lead.data, ft.Text): + Row.content.controls[0] = Lead.data + + async def _RowClicked(self, Context: str, Index: int) -> None: + if Context == "Queue": + self.Queue.Index = Index + await self._LoadCurrent() + else: + await self.PlayList([Item.Path for Item in self.Shown], Index) + + # --- Playing ------------------------------------------------------------ + + async def PlayList(self, Paths: list[str], Start: int) -> None: + """Replaces the queue with Paths and plays from Start (-1: shuffled).""" + if not Paths: + return + if Start < 0: + self.Queue.Shuffle = True + self.Queue.Set(Paths, random.randrange(len(Paths))) + self._ShowModes() + else: + self.Queue.Set(Paths, Start) + await self._LoadCurrent() + + async def _LoadCurrent(self, Play: bool = True, Start: int = 0) -> None: + """Loads the queue's current track, skipping ones whose file is gone.""" + Tries = len(self.Queue.Paths) + while self.Queue.Current and Tries > 0: + Item = self.Library.Get(self.Queue.Current) + if await self.ThePlayer.Load(Item.Path, Start=Start, Play=Play, Duration=Item.Duration): + self._ShowNowPlaying(Item) + self.SaveSession() + return + Tries -= 1 + Next = self.Queue.Next() + if Next is None or Next == self.Queue.Index: + break + self.Queue.Index, Start = Next, 0 + self.TheApp.Notify("Couldn't play any song in the queue (missing or unplayable files).", "error") + + def _ShowNowPlaying(self, Item: Track) -> None: + Subtitle = " - ".join(Part for Part in (Item.DisplayArtist, Item.Album) if Part) + self.Bar.ShowTrack(Item.DisplayTitle, Subtitle, None) + self.TheApp.Page.run_task(self._LoadBarCover, Item.Path) + if self.View == "Queue": + self._Rebuild() + else: + self._RefreshRows() + + async def _LoadBarCover(self, FilePath: str) -> None: + Data = await asyncio.to_thread(CoverFor, FilePath, 112) + if self.ThePlayer.Source == FilePath: + self.Bar.ShowTrack(self.Bar.Title.value, self.Bar.Subtitle.value, Data) + + def _RefreshRows(self) -> None: + self._MarkPlaying() + self._Show(self.Body) + + def _PlayerChanged(self) -> None: + self.Bar.Refresh() + if not self.ThePlayer.Playing: + self.SaveSession() + self._RefreshRows() + + async def Next(self) -> None: + Target = self.Queue.Next() + if Target is not None: + self.Queue.Index = Target + await self._LoadCurrent() + + async def Previous(self) -> None: + if self.ThePlayer.Position > RestartAfterMs: + await self.ThePlayer.Seek(0) + return + Target = self.Queue.Previous() + if Target is not None: + self.Queue.Index = Target + await self._LoadCurrent() + + async def _Ended(self) -> None: + Target = self.Queue.Next(Finished=True) + if Target is None: + self.SaveSession() + self.Bar.Refresh() + return + self.Queue.Index = Target + await self._LoadCurrent() + + def _ShowModes(self) -> None: + self.ShuffleButton.icon_color = Palette.Mauve if self.Queue.Shuffle else Palette.Overlay0 + self.RepeatButton.icon = ft.Icons.REPEAT_ONE if self.Queue.Repeat == "One" else ft.Icons.REPEAT + self.RepeatButton.icon_color = Palette.Overlay0 if self.Queue.Repeat == "Off" else Palette.Mauve + self.RepeatButton.tooltip = f"Repeat: {self.Queue.Repeat.lower()}" + + def _ToggleShuffle(self, e) -> None: + self.Queue.SetShuffle(not self.Queue.Shuffle) + self._ShowModes() + self._Show(self.ShuffleButton) + self.SaveSession() + if self.View == "Queue": + self._Rebuild() + + def _CycleRepeat(self, e) -> None: + self.Queue.CycleRepeat() + self._ShowModes() + self._Show(self.RepeatButton) + self.SaveSession() + + # --- Queue editing ------------------------------------------------------ + + def _AfterQueueEdit(self, Message: str = "") -> None: + self.SaveSession() + if self.View == "Queue": + self._Rebuild() + if Message: + self.TheApp.Notify(Message, "success") + + def _Plural(self, Count: int) -> str: + return f"{Count} song{'s' if Count != 1 else ''}" + + def _PlayNext(self, Paths: list[str]) -> None: + Empty = not self.Queue.Paths + self.Queue.PlayNext(Paths) + self._AfterQueueEdit(f"{self._Plural(len(Paths))} will play next.") + if Empty: + self.TheApp.Page.run_task(self._LoadCurrent) + + def _Enqueue(self, Paths: list[str]) -> None: + Empty = not self.Queue.Paths + self.Queue.Append(Paths) + self._AfterQueueEdit(f"Added {self._Plural(len(Paths))} to the queue.") + if Empty: + self.TheApp.Page.run_task(self._LoadCurrent) + + def _RemoveFromQueue(self, Index: int) -> None: + WasCurrent = Index == self.Queue.Index + self.Queue.Remove(Index) + self._AfterQueueEdit() + if WasCurrent: + if self.Queue.Current: + self.TheApp.Page.run_task(self._LoadCurrent, self.ThePlayer.Playing) + else: + self.TheApp.Page.run_task(self.ThePlayer.Stop) + + def _ClearQueue(self) -> None: + self.Queue.Clear() + self.TheApp.Page.run_task(self.ThePlayer.Stop) + self.Bar.ShowTrack("Nothing playing", "", None) + self._AfterQueueEdit() + + def _Reordered(self, Context: str, Old: Optional[int], New: Optional[int]) -> None: + if Old is None or New is None or Old == New: + return + if Context == "Queue": + self.Queue.Move(Old, New) + self.SaveSession() + else: + Item = self._Playlist(self.ViewArgument) + if Item is None: + return + Item.Tracks.insert(New, Item.Tracks.pop(Old)) + self._SavePlaylist(Item) + self._Rebuild() # row indices changed + + # --- Playlists ---------------------------------------------------------- + + def _SavePlaylist(self, Item: Playlist) -> bool: + try: + Item.Save() + return True + except OSError as Error: + self.TheApp.Notify(f"Couldn't save {Item.Name}: {Error}", "error") + return False + + def _ReloadPlaylists(self) -> None: + self.Playlists = LoadPlaylists() + self._BuildSidebar() + self._Show(self.SidebarList) + + def _NewPlaylist(self, Paths: Optional[list[str]] = None) -> None: + def Make(Name: str) -> None: + if NameTaken(Name): + self.TheApp.Notify(f"There's already a playlist called {Name}.", "warning") + return + Item = Playlist(Name, list(Paths or [])) + if self._SavePlaylist(Item): + self._ReloadPlaylists() + if Paths: + self.TheApp.Notify(f"Made {Name} with {self._Plural(len(Paths))}.", "success") + else: + self.ShowView("Playlist", Name) + + self.TheApp.Prompt("New playlist", "Name", Make, OkText="Make") + + def _QueueToPlaylist(self) -> None: + self._NewPlaylist(list(self.Queue.Paths)) + + def _AddToPlaylist(self, Paths: list[str]) -> None: + if not Paths: + return + + def Add(Item: Playlist) -> None: + self.TheApp.Close() + Item.Tracks.extend(Paths) + if self._SavePlaylist(Item): + self.TheApp.Notify(f"Added {self._Plural(len(Paths))} to {Item.Name}.", "success") + if self.View == "Playlist" and self.ViewArgument == Item.Name: + self._Rebuild() + + def New(e) -> None: + self.TheApp.Close() + self._NewPlaylist(Paths) + + Choices: list[ft.Control] = [ + ft.ListTile(leading=ft.Icon(ft.Icons.PLAYLIST_PLAY, color=Palette.Mauve), title=Item.Name, + subtitle=self._Plural(len(Item.Tracks)), on_click=lambda e, I=Item: Add(I)) + for Item in self.Playlists + ] + Choices.append(ft.ListTile(leading=ft.Icon(ft.Icons.ADD, color=Palette.Mauve), title="New playlist...", + on_click=New)) + self.TheApp.ShowDialog(f"Add {self._Plural(len(Paths))} to", + ft.Column(Choices, tight=True, scroll=ft.ScrollMode.AUTO, height=min(360, 64 * len(Choices))), + [ChipButton("Cancel", lambda e: self.TheApp.Close())], Width=360) + + def _RemoveFromPlaylist(self, Index: int) -> None: + Item = self._Playlist(self.ViewArgument) + if Item is not None and 0 <= Index < len(Item.Tracks): + Item.Tracks.pop(Index) + self._SavePlaylist(Item) + self._Rebuild() + + def _RenamePlaylist(self, Item: Playlist) -> None: + def Rename(Name: str) -> None: + if Name == Item.Name: + return + if NameTaken(Name, Except=Item.Name): + self.TheApp.Notify(f"There's already a playlist called {Name}.", "warning") + return + OldFile = Item.FilePath + Item.Name = Name + if self._SavePlaylist(Item): + if OldFile != Item.FilePath: + OldFile.unlink(missing_ok=True) + self._ReloadPlaylists() + self.ShowView("Playlist", Name) + + self.TheApp.Prompt("Rename playlist", "Name", Rename, Value=Item.Name, OkText="Rename") + + def _DeletePlaylist(self, Item: Playlist) -> None: + def Delete() -> None: + Item.Delete() + self._ReloadPlaylists() + self.ShowView("Songs") + self.TheApp.Notify(f"Deleted {Item.Name}.", "success") + + self.TheApp.Confirm("Delete playlist", f"Delete {Item.Name}? The songs themselves stay where they are.", Delete) + + async def _AddFilesToPlaylist(self, e) -> None: + Item = self._Playlist(self.ViewArgument) + if Item is None: + return + Picked = await self.TheApp.Picker.pick_files(dialog_title=f"Add to {Item.Name}", allow_multiple=True) + Paths = [P.path for P in Picked or [] if P.path and IsAudioFile(Path(P.path))] + if Paths: + Item.Tracks.extend(Paths) + if self._SavePlaylist(Item): + self._Rebuild() + + async def _ExportM3U(self, Item: Playlist) -> None: + Destination = await self.TheApp.Picker.save_file( + dialog_title="Export playlist", file_name=f"{Item.Name}.m3u8", initial_directory=str(Home), + allowed_extensions=["m3u8", "m3u"], file_type=ft.FilePickerFileType.CUSTOM) + if not Destination: + return + try: + ExportM3U(Item, Path(Destination)) + except OSError as Error: + self.TheApp.Notify(f"Couldn't export: {Error}", "error") + return + self.TheApp.Notify(f"Exported {Item.Name}.", "success") + + async def _ImportM3U(self, e) -> None: + Picked = await self.TheApp.Picker.pick_files( + dialog_title="Import playlist", file_type=ft.FilePickerFileType.CUSTOM, allowed_extensions=["m3u", "m3u8"]) + if not Picked or not Picked[0].path: + return + try: + Item = ReadM3U(Path(Picked[0].path)) + except OSError as Error: + self.TheApp.Notify(f"Couldn't read it: {Error}", "error") + return + Base, Number = Item.Name, 2 + while NameTaken(Item.Name): + Item.Name, Number = f"{Base} {Number}", Number + 1 + if self._SavePlaylist(Item): + self._ReloadPlaylists() + self.ShowView("Playlist", Item.Name) + self.TheApp.Notify(f"Imported {Item.Name} ({self._Plural(len(Item.Tracks))}).", "success") diff --git a/Packaging/BuildAppImage.sh b/Packaging/BuildAppImage.sh new file mode 100755 index 0000000..53bc75b --- /dev/null +++ b/Packaging/BuildAppImage.sh @@ -0,0 +1,38 @@ +#!/bin/bash +# Builds dist/SaraTune-x86_64.AppImage: a PyInstaller one-file build (with +# the font and icon bundled) wrapped by appimagetool, the same way SaraSuite's +# and SaraFem's AppImages are made. Needs appimagetool on PATH. +set -euo pipefail +cd "$(dirname "$(readlink -f "$0")")/.." + +[ -d .venv ] || python3 -m venv .venv +.venv/bin/pip install --quiet --disable-pip-version-check -r requirements.txt pyinstaller + +# --collect-data flet: without it ft.Icons crashes when frozen. +.venv/bin/pyinstaller --noconfirm --clean --log-level WARN \ + --onefile --windowed --name SaraTune --collect-data flet \ + --add-data "$PWD/assets:assets" \ + --distpath build/PyInstaller/dist --workpath build/PyInstaller/work --specpath build/PyInstaller \ + "$PWD/Main.py" + +AppDir=build/AppDir +rm -rf "$AppDir" +mkdir -p "$AppDir/usr/bin" +cp build/PyInstaller/dist/SaraTune "$AppDir/usr/bin/SaraTune" +ln -s usr/bin/SaraTune "$AppDir/AppRun" +cp assets/Icon.png "$AppDir/SaraTune.png" +ln -s SaraTune.png "$AppDir/.DirIcon" +cat > "$AppDir/SaraTune.desktop" << DESKTOP +[Desktop Entry] +Type=Application +Name=SaraTune +Comment=Play audio files and your music library +Exec=SaraTune %F +Icon=SaraTune +Terminal=false +Categories=AudioVideo;Audio;Player; +DESKTOP + +mkdir -p dist +ARCH=x86_64 appimagetool --no-appstream "$AppDir" dist/SaraTune-x86_64.AppImage +echo "Built dist/SaraTune-x86_64.AppImage" diff --git a/Packaging/Launch.sh b/Packaging/Launch.sh new file mode 100755 index 0000000..f81f2a1 --- /dev/null +++ b/Packaging/Launch.sh @@ -0,0 +1,5 @@ +#!/bin/sh +# Runs SaraTune from source: Packaging/Launch.sh [files...] +cd "$(dirname "$(readlink -f "$0")")/.." || exit 1 +[ -d .venv ] || { python3 -m venv .venv && .venv/bin/pip install -r requirements.txt; } +exec .venv/bin/python Main.py "$@" diff --git a/Packaging/SaraSuite/Icon.png b/Packaging/SaraSuite/Icon.png new file mode 100644 index 0000000000000000000000000000000000000000..f4b9eb9702fdb8ad2926ece71daf82794f9f4215 GIT binary patch literal 1128 zcmeAS@N?(olHy`uVBq!ia0y~yU~~Xs4mP03*$GQmF)*;`d%8G=RNQ)dS252hP@?VP z^fud&u)vF5R+DD+tx07q6^&(-JIS=o%W|hLs zQp3cIrkX?Hz|ogGg;_Q*qMHL&xFor}Y0ABafxou@wcjTPGOA`@{q{?W(h3iRB^($S zSpkL0MAkqBoFo&g6nClt4 z8OUUWK0+*ocPuY^V(%{DE{~t`&-%>!JRfC~p$E z^)9@P0qia`GX)$PEI5m}PM>o`LinPm&2qK6B*q3<&mEN1NLw~kokv!gCpcy^5;ZpUFVdQ I&MBb@0Hw|N8vp 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"))) diff --git a/Player.py b/Player.py new file mode 100644 index 0000000..9773593 --- /dev/null +++ b/Player.py @@ -0,0 +1,211 @@ +"""One audio player. Each mode has its own, so switching modes never loses +what the other was playing; Main pauses one when the other starts. + +Every track gets a fresh flet_audio.Audio service: changing an existing +one's src only works the first time (later play calls never answer), and +sources are file:// URIs, since plain paths are read as URLs and break on +names with # or %. Events from a replaced service are ignored. + +The Flet client only reports the position about once a second, so the +position is kept here and moved along with the clock in between.""" + +import asyncio +import time +from pathlib import Path +from typing import Callable, Optional + +import flet as ft +import flet_audio as fta + +CallTimeout = 8 # seconds; a call the client never answers shouldn't hang the app + + +class Player: + def __init__(self, Page: ft.Page, Volume: float): + self.Page = Page + self.Volume = Volume + self.Audio: Optional[fta.Audio] = None + self.Source: Optional[str] = None + self.Playing = False + self.Duration = 0 # milliseconds; 0 until known + self._Position = 0 + self._PositionAt = time.monotonic() + self._Ready = asyncio.Event() # the current service has loaded its source + self.Loop = False + # Set by the owner. OnEnded: the track ran out by itself. + self.OnStarted: Callable[[], None] = lambda: None + self.OnEnded: Callable[[], None] = lambda: None + self.OnChanged: Callable[[], None] = lambda: None # playing/paused, duration, source + self.OnError: Callable[[str], None] = lambda Message: None + + # --- Position ----------------------------------------------------------- + + @property + def Position(self) -> int: + if not self.Playing: + return self._Position + Moved = self._Position + (time.monotonic() - self._PositionAt) * 1000 + return int(min(Moved, self.Duration) if self.Duration else Moved) + + def _SetPosition(self, Milliseconds: int) -> None: + self._Position = max(0, int(Milliseconds)) + self._PositionAt = time.monotonic() + + # --- Events from the client --------------------------------------------- + + def _StateChanged(self, Audio: fta.Audio, e: fta.AudioStateChangeEvent) -> None: + if Audio is not self.Audio: + return + if e.state == fta.AudioState.PLAYING: + if not self.Playing: + self._SetPosition(self._Position) + self.Playing = True + self.OnStarted() + elif e.state == fta.AudioState.COMPLETED: + self.Playing = False + self._SetPosition(0) + if self.Loop: + self.Page.run_task(self.Play, 0) + else: + self.OnEnded() + elif e.state in (fta.AudioState.PAUSED, fta.AudioState.STOPPED): + if self.Playing: + self._SetPosition(self.Position) + self.Playing = False + self.OnChanged() + + def _PositionChanged(self, Audio: fta.Audio, e: fta.AudioPositionChangeEvent) -> None: + if Audio is self.Audio and self.Playing: + self._SetPosition(e.position) + + def _DurationChanged(self, Audio: fta.Audio, e: fta.AudioDurationChangeEvent) -> None: + if Audio is not self.Audio: + return + Duration = e.duration + Value = int(Duration.in_milliseconds if isinstance(Duration, ft.Duration) else Duration or 0) + if Value > 0: + self.Duration = Value + self._Ready.set() + self.OnChanged() + + def _Loaded(self, Audio: fta.Audio) -> None: + if Audio is self.Audio: + self._Ready.set() + + # --- Control ------------------------------------------------------------ + + async def _Call(self, Method, *Arguments) -> bool: + try: + await asyncio.wait_for(Method(*Arguments), CallTimeout) + return True + except (asyncio.TimeoutError, RuntimeError) as Error: + # e.g. GStreamer can't decode it: the client raises a PlatformException. + Text = str(Error) + self.OnError("The player didn't answer." if isinstance(Error, asyncio.TimeoutError) else + Text.split(",")[1].strip() if "PlatformException" in Text and "," in Text else Text) + return False + + async def _Unload(self) -> None: + Old, self.Audio = self.Audio, None + if Old is None: + return + if Old in self.Page.services: + self.Page.services.remove(Old) + self.Page.update() # disposes the old service and its native player + + async def Load(self, FilePath: str, Start: int = 0, Play: bool = True, Duration: int = 0) -> bool: + """Switches to FilePath and (if Play) starts at Start (ms). Duration is + a hint (e.g. from the tags) until the client reports the real one. + False when the file doesn't exist or can't be played.""" + if not Path(FilePath).is_file(): + return False + await self._Unload() + self.Source = FilePath + self.Duration = Duration + self.Playing = False + self._SetPosition(Start) + self._Ready = asyncio.Event() + Audio = fta.Audio( + src=Path(FilePath).absolute().as_uri(), + volume=self.Volume, + release_mode=fta.ReleaseMode.STOP, + ) + # Handlers know which service they belong to, so a replaced one's late events are dropped. + Audio.on_state_change = lambda e: self._StateChanged(Audio, e) + Audio.on_position_change = lambda e: self._PositionChanged(Audio, e) + Audio.on_duration_change = lambda e: self._DurationChanged(Audio, e) + Audio.on_loaded = lambda e: self._Loaded(Audio) + self.Audio = Audio + self.Page.services.append(Audio) + self.Page.update() + self.OnChanged() + if Play: + return await self.Play(Start) + return True + + async def _WaitReady(self) -> None: + try: + await asyncio.wait_for(self._Ready.wait(), 3) + except asyncio.TimeoutError: + pass # play anyway; it answers with an error if the file is bad + + async def Play(self, Start: Optional[int] = None) -> bool: + """From Start (ms), or from where it was.""" + if not self.Source: + return False + if self.Audio is None: # stopped: load it again + return await self.Load(self.Source, self._Position if Start is None else Start, Duration=self.Duration) + Audio = self.Audio + await self._WaitReady() + Position = self._Position if Start is None else Start + self._SetPosition(Position) + if Position > 0: + # play()'s own position isn't reliable before the first play: start, then seek. + if not await self._Call(Audio.resume): + return False + if not await self._Call(Audio.seek, ft.Duration(milliseconds=int(Position))): + return False + self._SetPosition(Position) # it played from 0 until the seek landed + return True + return await self._Call(Audio.play, ft.Duration(milliseconds=0)) + + async def Pause(self) -> None: + if self.Playing and self.Audio is not None: + self._SetPosition(self.Position) + self.Playing = False + await self._Call(self.Audio.pause) + self.OnChanged() + + async def Toggle(self) -> None: + if self.Playing: + await self.Pause() + else: + # A finished track (position at the end) starts over. + AtEnd = self.Duration and self._Position >= self.Duration - 250 + await self.Play(0 if AtEnd else None) + + async def Seek(self, Milliseconds: int) -> None: + if not self.Source: + return + Milliseconds = max(0, min(int(Milliseconds), self.Duration or int(Milliseconds))) + self._SetPosition(Milliseconds) + if self.Playing and self.Audio is not None: + await self._Call(self.Audio.seek, ft.Duration(milliseconds=Milliseconds)) + else: + # Paused or not started: remember it, Play starts from there. + self.OnChanged() + + async def Stop(self) -> None: + """Back to nothing loaded.""" + await self._Unload() + self.Source = None + self.Playing = False + self.Duration = 0 + self._SetPosition(0) + self.OnChanged() + + def SetVolume(self, Volume: float) -> None: + self.Volume = max(0.0, min(1.0, Volume)) + if self.Audio is not None: + self.Audio.volume = self.Volume + self.Audio.update() diff --git a/Playlists.py b/Playlists.py new file mode 100644 index 0000000..d7ebf09 --- /dev/null +++ b/Playlists.py @@ -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) diff --git a/README.md b/README.md new file mode 100644 index 0000000..a5abf2b --- /dev/null +++ b/README.md @@ -0,0 +1,56 @@ +# SaraTune + +A music player with two modes, switched in the header: + +- **Audio**: open a file or a folder and listen right away. A folder browser on the left; the + playing file's cover, details (format, codec, bitrate, sample rate, ...) and every tag on the + right. Nothing is saved: no library, no queue, no history. Files opened with SaraTune (e.g. + "Open with" in a file manager, or `Main.py song.flac`) always open here. + Keys: space play/pause, left/right seek, up/down previous/next file. The play bar can loop the + file or carry on to the next one in the folder. +- **Music**: a library of your music folders (Songs, Albums, Artists, with search), a queue with + shuffle and repeat, and saved playlists. The queue and position are kept between runs. + +Each mode has its own player, so switching modes doesn't lose your place; starting one pauses +the other. + +## Where things live + +| Path | What | +| --- | --- | +| `~/.config/SaraSuite/SaraTune/Config.toml` | settings (Settings > General / Music) | +| `~/.config/SaraSuite/SaraTune/Playlists/.toml` | one file per playlist: `Name` and `Tracks` (paths, in order) | +| `~/.config/SaraSuite/SaraTune/Session.toml` | Music mode's queue, position, shuffle and repeat | +| `~/.cache/SaraSuite/SaraTune/` | the library's tag cache and cover thumbnails (safe to delete) | + +On Windows: `%APPDATA%\SaraSuite\SaraTune\` and `%LOCALAPPDATA%\SaraSuite\SaraTune\`. + +Playlists can be imported from and exported to `.m3u`/`.m3u8` (the buttons in the sidebar and on a +playlist's page). Like every SaraSuite program, the theme, font and title bar come from the shared +`~/.config/SaraSuite/Config.toml` unless "uses these shared settings" is off. + +## Playback + +Playback goes through `flet-audio` (audioplayers), which is only in Flet's **full** desktop +client; SaraTune asks for it (`FLET_DESKTOP_FLAVOR=full`). On Linux that client links against the +system's libmpv and decodes with GStreamer: + +``` +sudo dnf install mpv-libs +``` + +Without libmpv, SaraTune shows a page saying so instead of failing to start. What plays depends on +the system's codecs (GStreamer on Linux, Media Foundation on Windows); mp3, flac, ogg, opus, wav +and m4a are tested on Fedora. + +Each track gets a fresh player service, and files are passed as `file://` URIs: changing an +existing service's source only works once, and plain paths break on names with `#` or `%` +(see `Player.py`). + +## Running and building + +- From source: `Packaging/Launch.sh [files...]` (makes `.venv` on the first run). +- Through SaraSuite: the manifest in `Packaging/SaraSuite/` is kept in sync with + `SaraSuite/Programs/SaraTune/`. +- AppImage: `Packaging/BuildAppImage.sh` builds `dist/SaraTune-x86_64.AppImage` (same recipe as + SaraSort's and SaraFem's; needs `appimagetool`). diff --git a/Settings.py b/Settings.py new file mode 100644 index 0000000..af57821 --- /dev/null +++ b/Settings.py @@ -0,0 +1,312 @@ +"""Settings dialog: the shared SaraSuite settings (with export/import), +SaraTune's own, and Music mode's library. Laid out like SaraFem's and +SaraSuite's Settings (a category list on the left, the chosen category on the +right).""" + +import os +import subprocess +from pathlib import Path +from typing import Callable + +import flet as ft +import tomlkit + +from Core import ( + AppConfig, AvailableThemes, BundledFontFamily, ChildEnvironment, ChipButtonStyle, ExportSettings, Home, + ImportSettings, IsWindows, LoadSharedSettings, Palette, PlaylistsDir, SharedSettings, +) +from Widgets import App + +DialogWidth = 700 +DialogHeight = 520 +CategoryListWidth = 150 +RestartNote = "Theme, font and title bar changes apply the next time SaraTune starts." +Modes = ["Music", "Audio"] + + +def _Note(Text: str) -> ft.Text: + return ft.Text(Text, color=Palette.Overlay0, size=12) + + +def _WindowsFonts() -> set[str]: + """Font families from the system and per-user Fonts registry keys + ("Arial Bold (TrueType)" -> "Arial").""" + import re + import winreg + + Families: set[str] = set() + Key = r"SOFTWARE\Microsoft\Windows NT\CurrentVersion\Fonts" + for Root in (winreg.HKEY_LOCAL_MACHINE, winreg.HKEY_CURRENT_USER): + try: + with winreg.OpenKey(Root, Key) as Fonts: + Index = 0 + while True: + try: + Name = winreg.EnumValue(Fonts, Index)[0] + except OSError: + break + Index += 1 + Name = re.sub(r"\s*\(.*\)$", "", Name) + Name = re.sub(r"\s+(Bold|Italic|Light|Semibold|SemiBold|Black|Regular|Medium|Thin)\b.*$", "", Name) + if Name and "&" not in Name: + Families.add(Name.strip()) + except OSError: + continue + return Families + + +def _Fonts() -> list[str]: + """Installed font families (fontconfig, or the registry on Windows), with + the bundled font first.""" + if IsWindows: + Families = _WindowsFonts() + Families.discard(BundledFontFamily) + return [BundledFontFamily] + sorted(Families, key=str.lower) + try: + Result = subprocess.run( + ["fc-list", ":", "family"], capture_output=True, text=True, timeout=5, env=ChildEnvironment(), + ) + Families = {Line.split(",")[0].strip() for Line in Result.stdout.splitlines() if Line.strip()} + except (OSError, subprocess.SubprocessError): + Families = set() + Families.discard(BundledFontFamily) + return [BundledFontFamily] + sorted(Families, key=str.lower) + + +def _NumberField(Label: str, Value: int) -> ft.TextField: + return ft.TextField(label=Label, value=str(Value), keyboard_type=ft.KeyboardType.NUMBER) + + +def _ReadInt(Field: ft.TextField, Fallback: int, Minimum: int) -> int: + try: + return max(Minimum, int(Field.value)) + except (TypeError, ValueError): + return Fallback # keep the previous value rather than crash on bad input + + +def ShowSettingsDialog(TheApp: App, OnSaved: Callable[[bool], None], OnClearCache: Callable[[], None]) -> None: + """OnSaved(FoldersChanged) runs after saving; OnClearCache empties the + library cache (Music mode rescans).""" + Page = TheApp.Page + Config: AppConfig = TheApp.Config + Themes = AvailableThemes() + Fonts = _Fonts() + + def ThemePicker(Value: str) -> ft.Dropdown: + Names = Themes if Value in Themes else [Value] + Themes + return ft.Dropdown(value=Value, label="Theme", options=[ft.DropdownOption(key=Name, text=Name) for Name in Names]) + + def FontPicker(Value: str) -> ft.Dropdown: + # Type to filter the long list. + Names = Fonts if Value in Fonts else [Value] + Fonts + return ft.Dropdown( + value=Value, label="Font", editable=True, enable_filter=True, menu_height=320, + options=[ft.DropdownOption(key=Name, text=Name) for Name in Names], + ) + + # SaraSuite: ~/.config/SaraSuite/Config.toml, read by every SaraSuite program. + Shared = LoadSharedSettings(Config) + UseSharedSwitch = ft.Switch(label="SaraTune uses these shared settings", value=Config.UseSharedSettings) + SharedTheme = ThemePicker(Shared.Theme) + SharedFont = FontPicker(Shared.FontFamily) + SharedTitleBar = ft.Switch(label="Hide window title bar", value=Shared.HideWindowTitleBar) + SettingsFileField = ft.TextField(value=str(Home / "SaraTune-Settings.toml"), label="Settings file path") + + # SaraTune's own appearance, only used with shared settings off. + OwnTheme = ThemePicker(Config.Theme) + OwnFont = FontPicker(Config.FontFamily) + OwnTitleBar = ft.Switch(label="Hide window title bar", value=Config.HideWindowTitleBar) + OwnAppearance = ft.Column( + [_Note("SaraTune's own appearance, used when shared settings are off"), OwnTheme, OwnFont, OwnTitleBar], + spacing=12, + disabled=Config.UseSharedSettings, + opacity=0.5 if Config.UseSharedSettings else 1, + ) + + def OnUseSharedChanged(e) -> None: + OwnAppearance.disabled = UseSharedSwitch.value + OwnAppearance.opacity = 0.5 if UseSharedSwitch.value else 1 + OwnAppearance.update() + + UseSharedSwitch.on_change = OnUseSharedChanged + + StartMode = ft.Dropdown( + label="Start in", value=Config.StartMode if Config.StartMode in Modes else "Music", + options=[ft.DropdownOption(key=Name, text=f"{Name} mode") for Name in Modes], + ) + SeekField = _NumberField("Arrow keys seek by (seconds, Audio mode)", Config.SeekSeconds) + AdvanceSwitch = ft.Switch(label="Audio mode plays the next file in the folder when one ends", + value=Config.AudioAutoAdvance) + + # Music: one folder per line, plus a Browse button that adds a line. + FoldersField = ft.TextField(label="Music folders (one per line)", value="\n".join(Config.MusicFolders), + multiline=True, min_lines=3, max_lines=6, expand=True) + + async def AddFolder(e) -> None: + Chosen = await TheApp.Picker.get_directory_path(dialog_title="Add a music folder") + if Chosen: + Lines = [Line for Line in (FoldersField.value or "").splitlines() if Line.strip()] + if Chosen not in Lines: + FoldersField.value = "\n".join(Lines + [Chosen]) + FoldersField.update() + + ScanSwitch = ft.Switch(label="Look for new and changed files every start", value=Config.ScanOnStart) + SessionSwitch = ft.Switch(label="Remember the queue and position between runs", value=Config.RememberSession) + + def ClearCache(e) -> None: + Page.pop_dialog() + OnClearCache() + + def OnExport(e) -> None: + PathText = (SettingsFileField.value or "").strip() + if not PathText: + return + try: + ExportSettings(Config, Path(PathText).expanduser()) + except OSError as Error: + TheApp.Notify(f"Couldn't export settings: {Error}", "error") + return + TheApp.Notify("Settings exported.", "success") + + def OnImport(e) -> None: + PathText = (SettingsFileField.value or "").strip() + if not PathText: + return + OldFolders = list(Config.MusicFolders) + try: + ImportSettings(Config, Path(PathText).expanduser()) + except (OSError, tomlkit.exceptions.TOMLKitError) as Error: + TheApp.Notify(f"Couldn't import settings: {Error}", "error") + return + # The open dialog still shows the pre-import values; closing it + # rather than leaving a stale Save that would overwrite the import. + Page.pop_dialog() + OnSaved(Config.MusicFolders != OldFolders) + TheApp.Notify("Settings imported. Reopen Settings to see them; restart for Theme/Font/Title bar.", "success") + + Categories: dict[str, ft.Control] = { + "SaraSuite": ft.Column( + [ + _Note("Shared by every SaraSuite program (~/.config/SaraSuite/Config.toml). " + "Other programs pick changes up the next time they start."), + UseSharedSwitch, + ft.Divider(height=1, color=Palette.Surface1), + SharedTheme, + SharedFont, + SharedTitleBar, + ft.Divider(height=1, color=Palette.Surface1), + _Note("Export/import settings as one file: this tab's shared settings plus everything else " + "in Settings. Same format as SaraFem's."), + SettingsFileField, + ft.Row([ + ft.OutlinedButton(content="Export", style=ChipButtonStyle(), on_click=OnExport), + ft.OutlinedButton(content="Import", style=ChipButtonStyle(), on_click=OnImport), + ]), + ], + spacing=12, + scroll=ft.ScrollMode.AUTO, + ), + "General": ft.Column( + [ + StartMode, + _Note("Opening files with SaraTune (e.g. from a file manager) always starts in Audio mode."), + SeekField, + AdvanceSwitch, + ft.Divider(height=1, color=Palette.Surface1), + OwnAppearance, + ], + spacing=12, + scroll=ft.ScrollMode.AUTO, + ), + "Music": ft.Column( + [ + _Note("Music mode's library is every audio file below these folders."), + ft.Row([FoldersField, ft.IconButton(ft.Icons.CREATE_NEW_FOLDER_OUTLINED, tooltip="Add a folder", + on_click=AddFolder)], + vertical_alignment=ft.CrossAxisAlignment.START), + ScanSwitch, + SessionSwitch, + ft.Divider(height=1, color=Palette.Surface1), + _Note("Playlists are kept in ~/.config/SaraSuite/SaraTune/Playlists/, one TOML file each. " + "The library's tags and cover thumbnails are only a cache."), + ft.Row([ + ft.OutlinedButton(content="Open playlists folder", style=ChipButtonStyle(), + on_click=lambda e: (PlaylistsDir.mkdir(parents=True, exist_ok=True), + TheApp.OpenFolder(str(PlaylistsDir)))), + ft.OutlinedButton(content="Clear cache and rescan", style=ChipButtonStyle(Palette.Yellow), + on_click=ClearCache), + ], wrap=True), + ], + spacing=12, + scroll=ft.ScrollMode.AUTO, + ), + } + + DetailArea = ft.Container(Categories["General"], expand=True, padding=ft.Padding.only(left=12)) + CategoryTiles: dict[str, ft.ListTile] = {} + + def SelectCategory(Name: str) -> None: + for CategoryName, Tile in CategoryTiles.items(): + Tile.selected = CategoryName == Name + DetailArea.content = Categories[Name] + DetailArea.update() + CategoryList.update() + + for Name in Categories: + CategoryTiles[Name] = ft.ListTile( + title=Name, + selected=Name == "General", + selected_tile_color=Palette.Surface1, + selected_color=Palette.Mauve, + on_click=lambda e, n=Name: SelectCategory(n), + ) + CategoryList = ft.Column(list(CategoryTiles.values()), spacing=0, scroll=ft.ScrollMode.AUTO) + + def Save(e) -> None: + OldFolders = list(Config.MusicFolders) + Config.UseSharedSettings = UseSharedSwitch.value + Config.Theme = OwnTheme.value or Config.Theme + Config.FontFamily = OwnFont.value or Config.FontFamily + Config.HideWindowTitleBar = OwnTitleBar.value + Config.StartMode = StartMode.value or Config.StartMode + Config.SeekSeconds = _ReadInt(SeekField, Config.SeekSeconds, 1) + Config.AudioAutoAdvance = AdvanceSwitch.value + Config.MusicFolders = [os.path.expanduser(Line.strip()) for Line in (FoldersField.value or "").splitlines() + if Line.strip()] + Config.ScanOnStart = ScanSwitch.value + Config.RememberSession = SessionSwitch.value + Config.Save() + SharedSettings( + Theme=SharedTheme.value, + FontFamily=SharedFont.value or Shared.FontFamily, + HideWindowTitleBar=SharedTitleBar.value, + ).Save() + Page.pop_dialog() + OnSaved(Config.MusicFolders != OldFolders) + TheApp.Notify("Settings saved.", "success") + + Page.show_dialog(ft.AlertDialog( + title="Settings", + content=ft.Column( + [ + ft.Row( + [ + ft.Container( + CategoryList, width=CategoryListWidth, bgcolor=Palette.Surface0, border_radius=4, padding=4, + ), + DetailArea, + ], + expand=True, + vertical_alignment=ft.CrossAxisAlignment.STRETCH, + ), + ft.Divider(height=1, color=Palette.Surface1), + _Note(RestartNote), + ], + width=DialogWidth, + height=DialogHeight, + ), + actions=[ + ft.OutlinedButton(content="Cancel", style=ChipButtonStyle(), on_click=lambda e: Page.pop_dialog()), + ft.OutlinedButton(content="Save", style=ChipButtonStyle(), on_click=Save), + ], + )) diff --git a/Transport.py b/Transport.py new file mode 100644 index 0000000..0fb614a --- /dev/null +++ b/Transport.py @@ -0,0 +1,146 @@ +"""The play bar at the bottom of both modes: what's playing, previous / play / +next, the seek bar and volume. Each mode adds its own toggles (loop, shuffle, +repeat) through Extras.""" + +import asyncio +from typing import Awaitable, Callable, Optional + +import flet as ft + +from Core import FormatTime, Palette +from Player import Player + +CoverSize = 52 + + +def CoverImage(Data: Optional[bytes], Size: int, Radius: int = 4) -> ft.Control: + """A cover, or a pixel-style note on the theme's colours when there's none.""" + if Data: + return ft.Image(src=Data, width=Size, height=Size, fit=ft.BoxFit.COVER, + border_radius=Radius, filter_quality=ft.FilterQuality.MEDIUM, gapless_playback=True) + return ft.Container( + ft.Icon(ft.Icons.MUSIC_NOTE, color=Palette.Mauve, size=Size * 0.5), + width=Size, height=Size, bgcolor=Palette.Surface0, border_radius=Radius, + border=ft.Border.all(1, Palette.Surface1), alignment=ft.Alignment.CENTER, + ) + + +class Transport(ft.Container): + def __init__(self, ThePlayer: Player, OnPrevious: Callable[[], Awaitable[None]], + OnNext: Callable[[], Awaitable[None]], OnVolume: Callable[[float], None], + Extras: Optional[list[ft.Control]] = None, OnInfoClick: Optional[Callable] = None): + super().__init__( + bgcolor=Palette.Mantle, + border=ft.Border.only(top=ft.BorderSide(1, Palette.Surface1)), + padding=ft.Padding.symmetric(horizontal=12, vertical=8), + ) + self.ThePlayer = ThePlayer + self._Dragging = False + self.Shown = False # set by Main: only the shown mode's bar is on the page + + self.Cover = ft.Container(CoverImage(None, CoverSize)) + self.Title = ft.Text("Nothing playing", color=Palette.Text, size=15, max_lines=1, + overflow=ft.TextOverflow.ELLIPSIS) + self.Subtitle = ft.Text("", color=Palette.Overlay0, size=12, max_lines=1, overflow=ft.TextOverflow.ELLIPSIS) + Info = ft.Container( + ft.Row([self.Cover, ft.Column([self.Title, self.Subtitle], spacing=2, expand=True, tight=True)], + spacing=10), + width=280, on_click=OnInfoClick, ink=OnInfoClick is not None, border_radius=4, + ) + + async def Previous(e) -> None: + await OnPrevious() + + async def Next(e) -> None: + await OnNext() + + async def Toggle(e) -> None: + await ThePlayer.Toggle() + + self.PlayButton = ft.IconButton( + ft.Icons.PLAY_ARROW_ROUNDED, icon_size=34, icon_color=Palette.Crust, bgcolor=Palette.Mauve, + tooltip="Play / pause (space)", on_click=Toggle, + ) + Buttons = ft.Row([ + ft.IconButton(ft.Icons.SKIP_PREVIOUS_ROUNDED, icon_color=Palette.Text, tooltip="Previous", on_click=Previous), + self.PlayButton, + ft.IconButton(ft.Icons.SKIP_NEXT_ROUNDED, icon_color=Palette.Text, tooltip="Next", on_click=Next), + ], spacing=4) + + self.Elapsed = ft.Text("0:00", color=Palette.Overlay0, size=12, width=52, text_align=ft.TextAlign.RIGHT) + self.Total = ft.Text("0:00", color=Palette.Overlay0, size=12, width=52) + self.Seek = ft.Slider( + min=0, max=1, value=0, expand=True, active_color=Palette.Mauve, inactive_color=Palette.Surface1, + thumb_color=Palette.Mauve, on_change_start=self._DragStart, on_change=self._Dragged, + on_change_end=self._DragEnd, + ) + + def VolumeChanged(e) -> None: + OnVolume(e.control.value) + self.VolumeIcon.icon = self._VolumeIconFor(e.control.value) + self.VolumeIcon.update() + + self.VolumeIcon = ft.Icon(self._VolumeIconFor(ThePlayer.Volume), color=Palette.Overlay0, size=20) + self.Volume = ft.Slider(min=0, max=1, value=ThePlayer.Volume, width=120, active_color=Palette.Mauve, + inactive_color=Palette.Surface1, thumb_color=Palette.Mauve, on_change=VolumeChanged) + + self.content = ft.Row([ + Info, + Buttons, + self.Elapsed, self.Seek, self.Total, + *(Extras or []), + self.VolumeIcon, self.Volume, + ], spacing=8, vertical_alignment=ft.CrossAxisAlignment.CENTER) + + @staticmethod + def _VolumeIconFor(Value: float): + return ft.Icons.VOLUME_OFF if Value <= 0 else ft.Icons.VOLUME_DOWN if Value < 0.5 else ft.Icons.VOLUME_UP + + def SetVolume(self, Value: float) -> None: + """The other mode's bar changed the shared volume.""" + self.Volume.value = Value + self.VolumeIcon.icon = self._VolumeIconFor(Value) + + def ShowTrack(self, Title: str, Subtitle: str, Cover: Optional[bytes]) -> None: + self.Title.value = Title + self.Subtitle.value = Subtitle + self.Cover.content = CoverImage(Cover, CoverSize) + if self.Shown: + self.update() + + # --- Seek bar ----------------------------------------------------------- + + def _DragStart(self, e) -> None: + self._Dragging = True + + def _Dragged(self, e) -> None: + self.Elapsed.value = FormatTime(e.control.value) + self.Elapsed.update() + + async def _DragEnd(self, e) -> None: + self._Dragging = False + await self.ThePlayer.Seek(int(e.control.value)) + + def Refresh(self) -> None: + """Play button, times and seek bar from the player; called by a + ticker a few times a second while playing.""" + P = self.ThePlayer + self.PlayButton.icon = ft.Icons.PAUSE_ROUNDED if P.Playing else ft.Icons.PLAY_ARROW_ROUNDED + Duration = max(P.Duration, 1) + self.Seek.max = Duration + self.Seek.disabled = not P.Source + self.Total.value = FormatTime(P.Duration) + if not self._Dragging: + self.Seek.value = min(P.Position, Duration) + self.Elapsed.value = FormatTime(P.Position) + if self.Shown: + self.update() + + +async def Ticker(Bars: list[Transport], Interval: float = 0.25) -> None: + """Keeps the playing bars moving. Runs for the app's lifetime.""" + while True: + for Bar in Bars: + if Bar.ThePlayer.Playing and Bar.Shown: + Bar.Refresh() + await asyncio.sleep(Interval) diff --git a/Widgets.py b/Widgets.py new file mode 100644 index 0000000..ea4a8ff --- /dev/null +++ b/Widgets.py @@ -0,0 +1,91 @@ +"""What both modes share: the App context (config, notifications, dialogs, +the file picker, switching modes) and small reusable controls.""" + +import os +import subprocess +from typing import Callable + +import flet as ft + +from Core import AppConfig, ChildEnvironment, ChipButton, IsWindows, Note, NotificationStyle, Palette + + +class App: + def __init__(self, Page: ft.Page, Config: AppConfig): + self.Page = Page + self.Config = Config + self.Picker = ft.FilePicker() # registers itself with the page + # Set by Main. + self.SwitchMode: Callable[[str], None] = lambda Name: None + self.OpenInAudio: Callable[[list[str]], None] = lambda Paths: None + + def Notify(self, Text: str, Kind: str = "info") -> None: + Background, Accent = NotificationStyle(Kind) + self.Page.show_dialog(ft.SnackBar( + content=ft.Text(Text, color=Accent), + bgcolor=Background, + shape=ft.RoundedRectangleBorder(radius=4, side=ft.BorderSide(1, Accent)), + open=True, + )) + + def ShowDialog(self, Title: str, Content: ft.Control, Actions: list[ft.Control], Width: int = 480) -> ft.AlertDialog: + Dialog = ft.AlertDialog(title=Title, content=ft.Container(Content, width=Width), actions=Actions) + self.Page.show_dialog(Dialog) + return Dialog + + def Close(self) -> None: + self.Page.pop_dialog() + + def Confirm(self, Title: str, Text: str, OnYes: Callable[[], None], YesText: str = "Delete") -> None: + def Yes(e) -> None: + self.Close() + OnYes() + + self.ShowDialog(Title, ft.Text(Text), [ + ChipButton("Cancel", lambda e: self.Close()), + ChipButton(YesText, Yes, Accent=Palette.Red), + ], Width=420) + + def Prompt(self, Title: str, Label: str, OnOk: Callable[[str], None], Value: str = "", OkText: str = "OK") -> None: + """Asks for one line of text; OnOk gets it stripped (never empty).""" + Field = ft.TextField(label=Label, value=Value, autofocus=True) + + def Ok(e) -> None: + Text = (Field.value or "").strip() + if Text: + self.Close() + OnOk(Text) + + Field.on_submit = Ok + self.ShowDialog(Title, Field, [ChipButton("Cancel", lambda e: self.Close()), ChipButton(OkText, Ok)], Width=380) + + def OpenFolder(self, PathText: str) -> None: + try: + if IsWindows: + os.startfile(PathText) # Explorer + else: + subprocess.Popen(["xdg-open", PathText], env=ChildEnvironment(), start_new_session=True) + except OSError as Error: + self.Notify(f"Couldn't open {PathText}: {Error}", "error") + + +def EmptyState(Text: str, Detail: str = "", Icon=ft.Icons.MUSIC_OFF_OUTLINED) -> ft.Control: + return ft.Container( + ft.Column([ + ft.Icon(Icon, color=Palette.Surface1, size=56), + ft.Text(Text, color=Palette.Overlay0, size=16), + Note(Detail), + ], horizontal_alignment=ft.CrossAxisAlignment.CENTER, tight=True, spacing=8), + alignment=ft.Alignment.CENTER, expand=True, + ) + + +def InfoTable(Rows: list[tuple[str, str]], LabelWidth: int = 110) -> ft.Column: + """Label/value rows; values are selectable so tags can be copied.""" + return ft.Column([ + ft.Row([ + ft.Text(Label, color=Palette.Overlay0, size=13, width=LabelWidth), + ft.Text(Value, color=Palette.Text, size=13, selectable=True, expand=True), + ], vertical_alignment=ft.CrossAxisAlignment.START) + for Label, Value in Rows + ], spacing=4) diff --git a/assets/Icon.ico b/assets/Icon.ico new file mode 100644 index 0000000000000000000000000000000000000000..0343f8c775a52515e443941f05025e8c7b413c1d GIT binary patch literal 5747 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 literal 0 HcmV?d00001 diff --git a/assets/Icon.png b/assets/Icon.png new file mode 100644 index 0000000000000000000000000000000000000000..2ccd352915c62602ce3d424a9d69a0d3ba245423 GIT binary patch literal 1030 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.0,<2.0 +flet-desktop>=1.0,<2.0 # the desktop client; otherwise Flet fetches it on first launch and quits once +flet-audio>=1.0,<2.0 # playback; needs Flet's "full" desktop client (Main.py picks it) +mutagen # tags, details and cover art +pillow # cover thumbnails +tomlkit