Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
157de5d
Use cookies.txt for yt-dlp.
firetech Jul 22, 2026
6caf45b
Make service .socket unit more failsafe.
firetech Jul 22, 2026
c03d607
Styling tweaks.
firetech Jul 22, 2026
9b7ee77
Minor cleanup of Chromecast scanning (-C) output.
firetech Jul 23, 2026
eff0b36
Format code with black & isort, fix pyright issues.
firetech Jul 23, 2026
4937f61
Introduce strict pyright type checking.
firetech Jul 23, 2026
045f390
Add project meta files.
firetech Jul 23, 2026
565d071
Fetch and show artist and title for queued files instead of filename …
firetech Jul 23, 2026
692e13f
Improved type checking in websocket input handling.
firetech Jul 23, 2026
036e666
Add optional "best-effort" support for Spotify links.
firetech Jul 24, 2026
2861d12
Show used python binary in install-service.sh.
firetech Jul 24, 2026
3f54d24
Include yt-dlp EJS dependencies.
firetech Jul 24, 2026
0b84861
Add indicator of upload handling in progress.
firetech Jul 24, 2026
0de754e
Less intrusive upload status indication.
firetech Jul 24, 2026
a04de75
Fix race conditions for upload status by switching to fetch() API.
firetech Jul 26, 2026
92e2b04
Improve parameter description when running --help.
firetech Jul 30, 2026
01eb64d
Change from TypedDict to Pydantic objects for storing data.
firetech Jul 30, 2026
cabf506
Add option to persist state between executions.
firetech Jul 30, 2026
8ed2c6d
Grab and display track duration (if available).
firetech Jul 30, 2026
8feb792
Clean up fallback handling, add PopLove[1] as a third type of fallback.
firetech Jul 30, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
*.mp3
/cookies.txt
__pycache__
1 change: 1 addition & 0 deletions .python-version
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
3.12
15 changes: 15 additions & 0 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
{
"[python]": {
"editor.defaultFormatter": "ms-python.black-formatter",
"editor.formatOnSave": true,
"editor.codeActionsOnSave": {
"source.organizeImports": "explicit"
},
},
"isort.args": [
"--profile",
"black"
],
"python.analysis.diagnosticsSource": "Pylance",
"python-envs.defaultEnvManager": "ms-python.python:venv"
}
59 changes: 59 additions & 0 deletions _types.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
# pyright: strict

from typing import Literal, TypeAlias

import pydantic as pyd

#############
# Responses #
#############


class AddressResponse(pyd.BaseModel):
type: Literal["address"] = "address"
address: str | None


class ListContentResponseEntry(pyd.BaseModel):
id: str
title: str
duration: float | None
nick: str | None
address: str | None
prio: int

model_config = {"extra": "ignore"}


class ListContentResponse(pyd.BaseModel):
type: Literal["list"] = "list"
position: float
list: list[ListContentResponseEntry]


class ListFallbackResponse(pyd.BaseModel):
type: Literal["fallback"] = "fallback"
description: str


ListResponse: TypeAlias = ListContentResponse | ListFallbackResponse


class ProgressResponse(pyd.BaseModel):
type: Literal["progress"] = "progress"
position: float


Response: TypeAlias = AddressResponse | ListResponse | ProgressResponse

############
# Requests #
############


class CancelRequest(pyd.BaseModel):
type: Literal["cancel"] = "cancel"
id: str


Request: TypeAlias = CancelRequest
27 changes: 18 additions & 9 deletions connections.py
Original file line number Diff line number Diff line change
@@ -1,30 +1,39 @@
from threading import Lock
import json
# pyright: strict

import threading

import tornado.ioloop
import tornado.websocket

import _types


class Connections:
def __init__(self, ioloop):
self.lock = Lock()
def __init__(self, ioloop: tornado.ioloop.IOLoop):
self.lock = threading.Lock()
self._ioloop = ioloop
self._clients = []
self._clients: list[tornado.websocket.WebSocketHandler] = []

def add_connection(self, handler):
def add_connection(self, handler: tornado.websocket.WebSocketHandler):
self.lock.acquire()
try:
self._clients.append(handler)
finally:
self.lock.release()

def close_connection(self, handler):
def close_connection(self, handler: tornado.websocket.WebSocketHandler):
self.lock.acquire()
try:
self._clients.remove(handler)
finally:
self.lock.release()

def message_clients(self, message):
def message_clients(self, message: _types.Response):
self.lock.acquire()
try:
for client in self._clients:
self._ioloop.add_callback(client.write_message, json.dumps(message))
self._ioloop.add_callback( # pyright: ignore[reportUnknownMemberType]
client.write_message, message.model_dump_json()
)
finally:
self.lock.release()
Loading