Umbenennung: Audiofy heißt jetzt Playwish

Name, Seitentitel, Dienste (playwish-web, playwish-controller), Dienst-Benutzer
(playwish), Pfade (/opt/playwish, /usr/local/src/playwish), Vorlagen und
Repository-Adresse. „Audiofy“ war bereits von mehreren Produkten belegt.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-17 20:27:59 +01:00
co-authored by Claude Opus 5.5
parent ca8db176ac
commit fc15f2b933
44 changed files with 218 additions and 218 deletions
+5 -5
View File
@@ -1,5 +1,5 @@
#!/usr/bin/env python3
"""Audiofy – einmaliger Spotify-Login (Authorization Code Flow).
"""Playwish – einmaliger Spotify-Login (Authorization Code Flow).
Holt einen Refresh-Token für das eigene Spotify-Konto und schreibt ihn in die
.env-Datei. Funktioniert ohne Browser auf dem Gerät selbst:
@@ -11,8 +11,8 @@ Holt einen Refresh-Token für das eigene Spotify-Konto und schreibt ihn in die
und hier einfügen.
Aufruf:
python3 auth.py --env /opt/audiofy/config/.env # Player (Wiedergabe steuern)
python3 auth.py --profil server --env /opt/audiofy/.env # Server (nur Suche)
python3 auth.py --env /opt/playwish/config/.env # Player (Wiedergabe steuern)
python3 auth.py --profil server --env /opt/playwish/.env # Server (nur Suche)
Benötigte Einträge in der .env: SPOTIFY_CLIENT_ID, SPOTIFY_CLIENT_SECRET,
optional SPOTIFY_REDIRECT_URI. Nur Python-Standardbibliothek, kein venv nötig.
@@ -75,7 +75,7 @@ def env_setzen(pfad: Path, key: str, wert: str) -> None:
inhalt = "\n".join(neu) + "\n"
if not os.access(pfad.parent, os.W_OK):
# z. B. Server: /opt/audiofy gehört root, nur die .env selbst dem Dienst-Benutzer
# z. B. Server: /opt/playwish gehört root, nur die .env selbst dem Dienst-Benutzer
fd = os.open(pfad, os.O_WRONLY | os.O_TRUNC)
with os.fdopen(fd, "w") as f:
f.write(inhalt)
@@ -112,7 +112,7 @@ def me_abfragen(access_token: str) -> dict:
def main() -> None:
parser = argparse.ArgumentParser(description="Audiofy – Spotify-Login, schreibt den Refresh-Token in die .env")
parser = argparse.ArgumentParser(description="Playwish – Spotify-Login, schreibt den Refresh-Token in die .env")
parser.add_argument("--profil", choices=SCOPES, default="player", help="Scope-Satz (Standard: player)")
parser.add_argument("--env", type=Path, default=Path(__file__).resolve().parent / ".env",
help="Pfad zur .env (Standard: neben dem Skript)")
+11 -11
View File
@@ -1,8 +1,8 @@
#!/usr/bin/env python3
"""Audiofy – Controller (Player).
"""Playwish – Controller (Player).
Spielt zufällige Titel aus der Grund-Playlist auf dem Spotify-Connect-Gerät
"Audiofy" und schiebt nach jeweils N-1 Playlist-Titeln einen Wunsch ein.
"Playwish" und schiebt nach jeweils N-1 Playlist-Titeln einen Wunsch ein.
Spotify bekommt immer nur den jeweils nächsten Titel (POST /me/player/queue,
ca. 15 s vor Titelende); die Reihenfolge verwaltet ausschließlich der Controller.
@@ -14,7 +14,7 @@ Ist der Server nicht erreichbar, läuft einfach die Grund-Playlist weiter.
Bedienung über die Spotify-App bleibt möglich: Fremde Titel werden erkannt und
geduldet, eine Pause durch den Besitzer (App oder Admin-Bereich) wird respektiert.
Aufruf: .venv/bin/python controller.py --env /opt/audiofy/config/.env
Aufruf: .venv/bin/python controller.py --env /opt/playwish/config/.env
"""
import argparse
@@ -38,7 +38,7 @@ from auth import env_lesen, env_setzen
from spotify_client import SpotifyClient, SpotifyFehler
from server_client import ServerVerbindung, Wunsch
log = logging.getLogger("audiofy.controller")
log = logging.getLogger("playwish.controller")
ORDNER = Path(__file__).resolve().parent
POLL_S = 3 # Abfrageintervall /me/player und Server
@@ -289,7 +289,7 @@ class Controller:
return None
def starten(self) -> None:
"""Wiedergabe auf dem Gerät "Audiofy" (neu) starten."""
"""Wiedergabe auf dem Gerät "Playwish" (neu) starten."""
gid = self.geraet_id()
if not gid:
# Warnung beim ersten Mal, danach höchstens einmal pro Minute
@@ -547,7 +547,7 @@ class Controller:
log.info("Als Nächstes eingereiht: %s [%s]", eintrag.uri, eintrag.quelle)
return
# --- es läuft nichts (auf Audiofy) ---
# --- es läuft nichts (auf Playwish) ---
if self.bt_pause:
# librespot pausiert bei Audiofehlern selbst – nach Neuverbindung fortsetzen
if self.lautsprecher_bereit():
@@ -558,13 +558,13 @@ class Controller:
log.info("Lautsprecher wieder da – Wiedergabe fortgesetzt")
return
if vorher is None:
# Controller-Start: pausierter Titel auf Audiofy = Besitzer-Pause, sonst starten
# Controller-Start: pausierter Titel auf Playwish = Besitzer-Pause, sonst starten
if st and unser and item and fortschritt >= PAUSE_MIN_MS:
if not self._bt_pruefen_jetzt():
log.warning("Audiofy pausiert und Lautsprecher getrennt – geht nach Neuverbindung weiter")
log.warning("Playwish pausiert und Lautsprecher getrennt – geht nach Neuverbindung weiter")
self.bt_pause = True
else:
log.info("Audiofy ist pausiert – warte, bis der Besitzer fortsetzt")
log.info("Playwish ist pausiert – warte, bis der Besitzer fortsetzt")
self.besitzer_pause = True
else:
self.neustart = True
@@ -708,7 +708,7 @@ class Controller:
def main() -> None:
parser = argparse.ArgumentParser(description="Audiofy-Controller")
parser = argparse.ArgumentParser(description="Playwish-Controller")
parser.add_argument("--env", type=Path, default=ORDNER / ".env")
args = parser.parse_args()
@@ -741,7 +741,7 @@ def main() -> None:
Playlist(sp, env.get("SPOTIFY_PLAYLIST_ID", ""), int(env.get("PLAYLIST_REPEAT_PERCENT") or 50)),
server,
n=int(env.get("WISH_EVERY_N") or 3),
geraetename=env.get("DEVICE_NAME") or "Audiofy",
geraetename=env.get("DEVICE_NAME") or "Playwish",
bt_mac=env.get("BT_SPEAKER_MAC") or None,
zustand_pfad=args.env.parent / "zustand.json",
env_pfad=args.env,
+2 -2
View File
@@ -1,4 +1,4 @@
"""Audiofy – Bluetooth-Lautsprecher suchen, koppeln und verbinden (bluetoothctl, ohne sudo).
"""Playwish – Bluetooth-Lautsprecher suchen, koppeln und verbinden (bluetoothctl, ohne sudo).
Gekoppelt wird immer MIT Agent in derselben bluetoothctl-Sitzung – sonst speichert
BlueZ den Schlüssel nicht dauerhaft ("Bonded: no"), und der Lautsprecher lässt sich
@@ -11,7 +11,7 @@ import shutil
import subprocess
import time
log = logging.getLogger("audiofy.lautsprecher")
log = logging.getLogger("playwish.lautsprecher")
MAC_RE = re.compile(r"^([0-9A-F]{2}:){5}[0-9A-F]{2}$")
SUCHDAUER_S = 15
+2 -2
View File
@@ -1,4 +1,4 @@
"""Audiofy – Verbindung Player → Server (Player-API mit Bearer-Token, nur ausgehend).
"""Playwish – Verbindung Player → Server (Player-API mit Bearer-Token, nur ausgehend).
Alle Aufrufe fangen Fehler selbst ab: Ist der Server nicht erreichbar, liefern sie
None/False, und der Controller spielt einfach die Grund-Playlist weiter. Im
@@ -11,7 +11,7 @@ from dataclasses import dataclass
import httpx
log = logging.getLogger("audiofy.server")
log = logging.getLogger("playwish.server")
OFFLINE_RETRY_S = 15
TIMEOUT_S = 5
+2 -2
View File
@@ -1,4 +1,4 @@
"""Audiofy – schlanker Spotify-Web-API-Client für den Controller (httpx).
"""Playwish – schlanker Spotify-Web-API-Client für den Controller (httpx).
Erneuert den Access-Token selbst per Refresh-Token. Fehler werden als
SpotifyFehler geworfen (bei 429 mit retry_after); Netzwerkfehler kommen als
@@ -12,7 +12,7 @@ from collections.abc import Callable
import httpx
log = logging.getLogger("audiofy.spotify")
log = logging.getLogger("playwish.spotify")
API = "https://api.spotify.com/v1"
TOKEN_URL = "https://accounts.spotify.com/api/token"
+1 -1
View File
@@ -93,7 +93,7 @@ def test_ein_titel_bleibt_immer_frei():
def test_zustand_speichert_zuletzt(tmp_path):
datei = tmp_path / "zustand.json"
neu = lambda: Controller(sp=None, playlist=Playlist(FakeSpotify(TITEL), "liste", 50), server=None, # noqa: E731
n=3, geraetename="Audiofy", bt_mac=None, zustand_pfad=datei)
n=3, geraetename="Playwish", bt_mac=None, zustand_pfad=datei)
c = neu()
c.playlist.gespielt(TITEL[3])
c.playlist.gespielt(TITEL[4])
+1 -1
View File
@@ -42,7 +42,7 @@ class FakeServer:
def controller(sp, server):
c = Controller(sp=sp, playlist=Playlist(sp, "liste"), server=server, n=1, geraetename="Audiofy", bt_mac=None)
c = Controller(sp=sp, playlist=Playlist(sp, "liste"), server=server, n=1, geraetename="Playwish", bt_mac=None)
return c