Skip to content

Commit a045f75

Browse files
committed
updates and bug fixes
1 parent b7a4841 commit a045f75

21 files changed

Lines changed: 2985 additions & 308 deletions

.DS_Store

8 KB
Binary file not shown.

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,3 +22,4 @@ target
2222

2323
# Claude Code local project state — not meant to be shared
2424
.claude/
25+
docs

ARCHITECTURE.md

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,16 +4,20 @@ Spritz implements DLNA/UPnP AV directly instead of wrapping an existing library.
44

55
## Discovery (SSDP)
66

7-
Spritz sends `ssdp:alive` announcements to `239.255.255.250:1900` on startup, responds to `M-SEARCH` requests (honoring the client's `MX` delay per UPnP 1.0 §1.2.3), and sends `ssdp:byebye` on exit. Announcements repeat every 3 minutes, and each NT is sent three times with small gaps to survive datagram loss on WiFi.
7+
Spritz sends `ssdp:alive` announcements to `239.255.255.250:1900` (IPv4) and `[FF02::C]:1900` (IPv6) on startup, responds to `M-SEARCH` requests (honoring the client's `MX` delay per UPnP 1.0 §1.2.3), and sends `ssdp:byebye` on exit. Announcements repeat every 3 minutes, and each NT is sent three times with small gaps to survive datagram loss on WiFi. `LOCATION` URLs use `[ipv6]:port` when answering an IPv6 search.
88

99
## Device description
1010

11-
`GET /upnp/description.xml` returns a `MediaServer:1` description advertising the ContentDirectory and ConnectionManager services. The `<dlna:X_DLNADOC>DMS-1.50</dlna:X_DLNADOC>` tag marks it as a DLNA DMS, which strict clients (tvOS Infuse, SenPlayer) require.
11+
`GET /upnp/description.xml` returns a `MediaServer:1` description advertising ContentDirectory, ConnectionManager, and Microsoft `X_MS_MediaReceiverRegistrar` (Xbox). The `<dlna:X_DLNADOC>DMS-1.50</dlna:X_DLNADOC>` tag marks it as a DLNA DMS, which strict clients (tvOS Infuse, SenPlayer) require.
1212

1313
## Browse (SOAP)
1414

15-
`POST /upnp/control/contentdirectory` handles `Browse`, `GetSystemUpdateID`, `GetSearchCapabilities`, and `GetSortCapabilities`. The root has three children: `V` (Videos, flat), `A` (Music, flat), and `F` (By folder, recursive). Empty containers are hidden. `<res>` tags include `size=` and DLNA.ORG flags (`OP=01` byte-seek plus standard streaming flags); file responses set `transferMode.dlna.org: Streaming` and `contentFeatures.dlna.org` so Infuse will play them.
15+
`POST /upnp/control/contentdirectory` handles `Browse`, `Search`, `GetSystemUpdateID`, `GetSearchCapabilities`, and `GetSortCapabilities`. The root has three children: `V` (Videos, flat), `A` (Music, flat), and `F` (By folder, recursive). Empty containers are hidden. `<res>` tags include `size=`, `duration=` when the container header can be parsed, `DLNA.ORG_PN` for common profiles, and DLNA.ORG flags (`OP=01` byte-seek plus standard streaming flags). Matching sidecar subtitles (`.srt` / `.vtt` / `.ass`) are extra `<res>` URLs. Sidecar covers (`cover.jpg` / same-stem `.jpg`) appear as `<upnp:albumArtURI>` pointing at `/art/{index}`. File responses set `transferMode.dlna.org: Streaming` and `contentFeatures.dlna.org` so Infuse will play them.
16+
17+
A `SUBSCRIBE` to an event URL is answered with a SID and an immediate HTTP `NOTIFY` carrying the current state variables (`SystemUpdateID` stays `1` because the library is scanned once at start).
18+
19+
`GET /upnp/icon.png` is a 48×48 PNG listed in `iconList` on the device description.
1620

1721
## File serving
1822

19-
Each source directory is mounted at `/m/{index}/` and served over HTTP with range support via `tower-http`'s `ServeDir`.
23+
Each source directory is mounted at `/m/{index}/` and served over HTTP with range support via `tower-http`'s `ServeFile`. Requests that leave the tree, follow a symlink, or use an unknown extension return 404. Sidecar subtitles sharing a stem with an indexed file are reachable so clients can fetch the extra `<res>` URLs. Album art is served at `/art/{index}` from `cover.jpg` / `folder.jpg` / a same-stem image next to the file.

CHANGELOG.md

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,20 @@ All notable changes to this project will be documented in this file.
44

55
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
66
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7+
## [Unreleased]
8+
9+
### Added
10+
11+
- `--bind` and `--name` so a multi-homed host can pick an interface and a DLNA friendly name
12+
- ContentDirectory `Search` (title contains + video/audio class)
13+
- 48×48 PNG device icon at `GET /upnp/icon.png`
14+
- Sidecar subtitles (`.srt` / `.vtt` / `.ass`) served over HTTP and advertised as extra DIDL `<res>` tags
15+
- Access log (`info`) for `/m/`, `/upnp/`, and `/art/` requests
16+
- DIDL `duration` and `DLNA.ORG_PN` from file headers (MP4/MKV/WAV/FLAC) plus sidecar album art
17+
- UPnP event `NOTIFY` after `SUBSCRIBE` (ContentDirectory, ConnectionManager, MediaReceiverRegistrar)
18+
- Xbox `X_MS_MediaReceiverRegistrar` service (`IsAuthorized` / `IsValidated` always succeed)
19+
- IPv6 SSDP (`[FF02::C]:1900`) and dual-stack HTTP when `--bind` is unspecified
20+
721
## [0.1.4] - 2026-04-22
822

923
### Added
@@ -27,12 +41,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
2741

2842
### Added
2943

30-
- Add audio support and restructure DIDL hierarchy for Infuse tvOS
44+
- Cross-platform release binaries via cargo-dist
3145

3246
## [0.1.1] - 2026-04-22
3347

3448
### Added
3549

36-
- Add audio support and restructure DIDL hierarchy for Infuse tvOS
50+
- Initial public release automation (release-plz)
3751

3852
<!-- release-plz inserts new version sections below this line -->

Cargo.lock

Lines changed: 90 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

README.md

Lines changed: 26 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,9 @@ DLNA clients — such as modern TVs, Apple TV (via Infuse or VLC), PS5, Xbox, Ko
3030
- Serves one or more folders (recursively — subdirectories are indexed automatically) from a single instance
3131
- Presents three browse views at the root: `Videos`, `Music`, and `By folder` (the on-disk structure)
3232
- Supports video and audio formats (MP4/MKV/AVI/MOV/… and MP3/FLAC/OGG/…)
33-
- Implements `ContentDirectory:1` and `ConnectionManager:1` for DLNA Browse
33+
- Implements `ContentDirectory:1` and `ConnectionManager:1` for DLNA Browse and Search
34+
- Advertises sidecar subtitles (`.srt` / `.vtt` / `.ass`) next to matching media files
35+
- Advertises duration, DLNA profile names, and sidecar album art (`cover.jpg` / matching stem)
3436
- Exposes an M3U playlist at `/spritz` for VLC, Infuse, and similar players
3537
- Sends `ssdp:byebye` on Ctrl+C so clients drop it immediately
3638

@@ -80,6 +82,8 @@ Arguments:
8082
8183
Options:
8284
-p, --port <PORT> Port to listen on [default: 8080]
85+
--bind <ADDR> Address to bind [default: 0.0.0.0]
86+
-n, --name <NAME> Friendly name shown to DLNA clients [default: Spritz Media Server]
8387
-h, --help Print help
8488
```
8589

@@ -97,6 +101,9 @@ spritz /mnt/nas/movies /mnt/nas/tv /mnt/nas/kids
97101

98102
# Use a different port
99103
spritz --port 9000 /media/videos
104+
105+
# Bind a specific interface and set the name TVs display
106+
spritz --bind 192.168.1.10 --name "Living Room" /media/videos
100107
```
101108

102109
## Connecting a client
@@ -142,22 +149,35 @@ Point it at `http://<your-spritz-server-ip>:8080/spritz`.
142149
| `.wma` | `audio/x-ms-wma`|
143150
| `.aiff`, `.aif` | `audio/aiff` |
144151

152+
**Subtitles** (sidecar files next to a video/audio item; not listed as their own library entries)
153+
154+
| Extension | MIME type |
155+
|----------------|---------------|
156+
| `.srt` | `text/srt` |
157+
| `.vtt` | `text/vtt` |
158+
| `.ass`, `.ssa` | `text/x-ssa` |
159+
145160
## Compatibility
146161

147162
| Device | Status | Notes |
148163
|---------------------------------------|--------|--------------------------------------------------------------------|
149164
| Samsung (Tizen) | Works | Requires `<dc:date>` on each DIDL item — included |
150-
| LG (webOS) | Works | Shows an "unknown device" icon (no icon endpoint yet) |
165+
| LG (webOS) | Works | Device icon served at `/upnp/icon.png` |
151166
| Sony / Bravia | Works | Strict about `Content-Type: text/xml; charset="utf-8"` — handled |
152167
| Apple TV — Infuse (tvOS, iOS, iPadOS) | Works | Requires the full DIDL treatment for tvOS playback |
153168
| Apple TV — VLC (tvOS, iOS, iPadOS) | Works | tvOS VLC sometimes misses SSDP; add the M3U URL manually |
169+
| Xbox | Works | Advertises Microsoft `MediaReceiverRegistrar` |
154170

155171
## Troubleshooting
156172

157173
DLNA is fiddly by nature, especially in combination with certain devices and operating systems (looking at you, Apple TV).
158174

159175
If your client can't find Spritz, check your firewall rules first (on both the server and client side, but typically the server side): SSDP needs UDP 1900 open, and HTTP needs your serving port (8080 by default).
160176

177+
On a VPN, Docker, or multi-homed machine, discovery may succeed while playback URLs point at the wrong address. Infuse and VLC using the M3U URL (`/spritz`) pick up the `Host` header; SSDP `LOCATION` tries to reply with the interface on the same subnet as the client.
178+
179+
IPv6-only LANs: SSDP also joins `[FF02::C]:1900`. When `--bind` is the default unspecified address, HTTP tries a dual-stack socket so IPv6 clients can fetch `LOCATION`. Link-local (`fe80::`) addresses are not advertised.
180+
161181
On Apple TV, Infuse tends to work better than VLC. If you're using VLC and can't find the share, you can bypass discovery entirely by pasting the M3U URL into VLC: `Media → Open Network Stream → http://192.168.X.X:8080/spritz`. If that plays, the server is fine and the issue is discovery.
162182

163183
Restart VLC once Spritz is already running — VLC only scans at startup and on NOTIFY packets.
@@ -186,10 +206,10 @@ New-NetFirewallRule -DisplayName "Spritz SSDP" `
186206

187207
Spritz implements DLNA/UPnP AV directly instead of wrapping an existing library. At a glance:
188208

189-
- **Discovery (SSDP).** Sends `ssdp:alive` on startup, responds to `M-SEARCH`, and sends `ssdp:byebye` on exit.
190-
- **Device description.** `GET /upnp/description.xml` returns a `MediaServer:1` description advertising ContentDirectory and ConnectionManager.
191-
- **Browse (SOAP).** `POST /upnp/control/contentdirectory` handles `Browse` and related actions, exposing three root containers: Videos (flat), Music (flat), and By folder (recursive).
192-
- **File serving.** Each source directory is mounted at `/m/{index}/` and served over HTTP with range support.
209+
- **Discovery (SSDP).** Sends `ssdp:alive` on IPv4 and IPv6 on startup, responds to `M-SEARCH`, and sends `ssdp:byebye` on exit.
210+
- **Device description.** `GET /upnp/description.xml` returns a `MediaServer:1` description advertising ContentDirectory, ConnectionManager, and Xbox MediaReceiverRegistrar.
211+
- **Browse / Search (SOAP).** `POST /upnp/control/contentdirectory` handles `Browse`, `Search`, and related actions, exposing three root containers: Videos (flat), Music (flat), and By folder (recursive).
212+
- **File serving.** Each source directory is mounted at `/m/{index}/` and served over HTTP with range support. Only media, sidecar-subtitle, and album-art extensions are reachable; directory listings, other files, and symlinks are not.
193213

194214
See [ARCHITECTURE.md](ARCHITECTURE.md) for the full protocol walkthrough.
195215

api/Cargo.toml

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,10 +11,12 @@ authors.workspace = true
1111
anyhow = "1.0.102"
1212
axum = "0.8.8"
1313
dlna = { version = "0.1.4", path = "../dlna" }
14-
uuid = { version = "1", features = ["v4"] }
14+
uuid = { version = "1", features = ["v5"] }
15+
hostname = "0.4"
1516
spritz-core = { version = "0.1.4", path = "../core" }
1617
local-ip-address = "0.6.11"
1718
tokio = { version = "1.51.1", features = ["full"] }
19+
socket2 = "0.6"
1820
tower = "0.5"
1921
tower-http = { version = "0.6.8", features = ["fs", "set-header"] }
2022
tracing = "0.1"

0 commit comments

Comments
 (0)