Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
39 changes: 36 additions & 3 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,26 @@ The format is inspired by Keep a Changelog, and this project uses semantic versi

### Added

- Nothing yet.

### Changed

- Nothing yet.

### Fixed

- Nothing yet.


## [1.2.0] - 2026-09-02

### Added

- Added optional radio stream recording to the Textual TUI and GTK4 desktop GUI.
- Added FFmpeg-based stream-copy recording with Matroska audio output and no transcoding.
- Added profile-scoped recording metadata persistence in SQLite, including station,
timestamps, logical duration, file size, status and output path.
- Added safe, unique recording filenames under the FluxTuner data directory.
- Added automatic Linux system tray integration for the GTK frontend using
StatusNotifierItem and DBusMenu over Gio/D-Bus, without a new runtime dependency.
- Added tray actions for restoring the main window, showing the current station,
Expand All @@ -18,16 +38,29 @@ The format is inspired by Keep a Changelog, and this project uses semantic versi

### Changed

- GTK closing behavior now hides the main window and keeps playback running
when a compatible Linux StatusNotifierWatcher is available.
- Recording and playback are independent: a selected station can be recorded without
being played, and playback changes do not interrupt an active recording.
- FFmpeg recording input is paced in real time so live/HLS sources are not consumed
faster than wall-clock time.
- Active recordings are finalized cleanly when the TUI or GTK application exits.
- GTK closing behavior now hides the main window and keeps playback and recording
running when a compatible Linux StatusNotifierWatcher is available.
- GTK falls back to the traditional close-and-exit behavior when tray registration is unavailable.

### Fixed

- Nothing yet.
- Fixed live streams that could produce substantially more recorded audio than the
user-requested wall-clock recording time.

### Documentation

- Documented local recording requirements, storage location and current TUI/GTK scope.

### Internal

- Added recording lifecycle, FFmpeg backend, persistence, path-generation, TUI and GTK
recording coverage.
- Validated stream-copy recording with MP3, AAC, Opus and HLS sources.
- Added isolated Linux tray backend coverage, packaged-icon checks and GTK close-to-tray lifecycle coverage.
- Tray lifetime uses `GApplication.hold()` / `release()` and a dedicated D-Bus connection so notifier removal is clean and does not affect GTK's shared bus.

Expand Down
4 changes: 4 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ Run the Web/server mode on your own infrastructure and keep accounts, favorites,
- Switch built-in TUI themes with live preview.
- Run the default Textual TUI, GTK4 desktop GUI, legacy numbered CLI or browser-based web/server mode.
- On Linux, keep the GTK GUI available from a StatusNotifierItem-compatible system tray while the main window is hidden.
- Record the currently selected station from the TUI or GTK GUI with FFmpeg stream copy, independently from playback.
- Use Web/server accounts with first-run admin setup, pending account requests, authenticated profiles, CSRF-protected mutations, dashboard metrics and admin user management.
- Store library data in a local SQLite database, with XDG-style config, data and cache locations.

Expand Down Expand Up @@ -96,6 +97,7 @@ Run FluxTuner on your own server and access your radio library from any modern b

- Python 3.11+
- `mpv` recommended, `ffmpeg` / `ffplay` as broad fallback, or optional lightweight `mpg123` / `ogg123` backends
- `ffmpeg` is required for local stream recording
- Optional GUI dependencies: GTK4 and PyGObject

### Run the Web platform with Docker Compose
Expand Down Expand Up @@ -203,6 +205,8 @@ Then launch the GUI mode using the documented FluxTuner GUI option.

On Linux, FluxTuner GTK automatically registers a StatusNotifierItem when the desktop session provides a compatible watcher. When available, closing the window hides FluxTuner while playback continues; use the tray icon to restore the window, stop playback or quit the application. No additional Python dependency is required. If no compatible tray watcher is available, FluxTuner keeps the normal close-window behavior.

Local recording is available from the Textual TUI and GTK GUI when `ffmpeg` is available. Recording is independent from playback and writes Matroska audio (`.mka`) files under `~/.local/share/fluxtuner/recordings/` using stream copy, preserving the source codec without transcoding. The Web/server interface does not expose recording in this release.

This method is useful for testing a release quickly. For regular use, prefer the packaged installation methods when available.

### Launch modes
Expand Down
65 changes: 56 additions & 9 deletions SMOKE_TEST.md
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,9 @@ Expected:
- minimum bitrate filter works
- playback starts with selected station
- playback stops cleanly
- if `ffmpeg` is available, `R` starts/stops recording of the selected station
- recording continues when playback starts, stops or changes station
- the completed `.mka` is playable and has approximately the requested wall-clock duration
- data usage updates while streaming

Optional explicit backend tests:
Expand All @@ -129,7 +132,12 @@ Expected:
- backend is displayed in the side panel
- playback starts with selected station
- playback stops cleanly
- closing the window stops playback
- if `ffmpeg` is available, Record / Stop recording records the selected station
- recording continues independently while playback starts, stops or changes station
- with a compatible StatusNotifierWatcher, closing the window hides GTK and keeps playback and recording running
- restoring the window from the tray returns to the same application state
- actual Quit finalizes an active recording and stops playback cleanly
- without a compatible tray watcher, closing the window performs the traditional application shutdown
- data usage updates while streaming
- favorites controls work
- tag playlist controls work
Expand All @@ -143,7 +151,45 @@ python -m fluxtuner --gui --player ffplay

---

# 7. Live metadata
# 7. Recording persistence

After a short TUI or GTK recording, inspect the latest recording row:

```bash
sqlite3 ~/.local/share/fluxtuner/fluxtuner.db \
'SELECT id, profile_id, station_name, duration_seconds, file_size, status, file_path
FROM recordings
ORDER BY id DESC
LIMIT 1;'
```

Expected:

- `status` is `completed`
- `duration_seconds` is close to the measured wall-clock recording time
- `file_size` is greater than zero
- `file_path` points inside the FluxTuner data directory

Inspect the media file with:

```bash
ffprobe -v error \
-show_entries format=start_time,duration,size \
-show_entries stream=codec_name,start_time,duration \
-of default=noprint_wrappers=1 \
/path/to/recording.mka
```

Expected:

- the file is a valid Matroska audio recording
- stream copy preserves the source codec
- media duration is reasonably close to the requested recording duration
- a live/HLS source is not recorded substantially faster than wall clock

---

# 8. Live metadata

Run a known stream that exposes ICY metadata.

Expand Down Expand Up @@ -177,7 +223,7 @@ Notes:

---

# 8. Web/server mode
# 9. Web/server mode

Use an isolated data directory so the smoke test does not touch your regular
FluxTuner library:
Expand Down Expand Up @@ -242,7 +288,7 @@ UI checks:

---

# 9. macOS GTK notes
# 10. macOS GTK notes

Install dependencies:

Expand Down Expand Up @@ -284,7 +330,7 @@ python -m fluxtuner --gui

---

# 10. Linux notes
# 11. Linux notes

## CRUX

Expand Down Expand Up @@ -313,7 +359,7 @@ sudo dnf install mpv ffmpeg python3-gobject gtk4

---

# 11. Documentation checks
# 12. Documentation checks

```bash
python -m fluxtuner --help
Expand All @@ -338,7 +384,7 @@ Review visually:

---

# 12. Pre-commit checklist
# 13. Pre-commit checklist

Before committing:

Expand All @@ -363,7 +409,7 @@ Recommended manual checks:

---

# 13. Suggested release smoke test
# 14. Suggested release smoke test

Before a release candidate, run the canonical gate and smoke-test the
generated wheel rather than only the source checkout:
Expand Down Expand Up @@ -401,5 +447,6 @@ Expected:
- no crashes
- correct backend selection
- playback works
- GUI closes cleanly
- GUI tray/close behavior matches watcher availability
- a short TUI/GTK recording finalizes cleanly when `ffmpeg` is available
- metadata appears when available
66 changes: 61 additions & 5 deletions docs/architecture.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,8 @@ The primary local library store is SQLite:

~/.local/share/fluxtuner/fluxtuner.db

The database stores normalized stations, profiles, favorites, playback history
and manual playlists.
The database stores normalized stations, profiles, favorites, playback history,
manual playlists and profile-scoped recording metadata.

FluxTuner local interfaces use profile-scoped library data. Profiles are
context-level separation inside the same FluxTuner installation. They are useful for contexts
Expand All @@ -23,15 +23,18 @@ Current model:
├── default
│ ├── favorites
│ ├── playback history
│ └── manual playlists
│ ├── manual playlists
│ └── recordings metadata
├── work
│ ├── favorites
│ ├── playback history
│ └── manual playlists
│ ├── manual playlists
│ └── recordings metadata
└── terrace
├── favorites
├── playback history
└── manual playlists
├── manual playlists
└── recordings metadata

Profile resolution order:

Expand All @@ -50,6 +53,10 @@ The Web user/account model adds ownership above profiles:
├── playback history
└── manual playlists

Recording metadata uses the same profile-scoped SQLite model for local TUI/GTK
recordings. Web/server recording is intentionally not wired into the Web user
model in this release.

FluxTuner is organized as a multi-interface platform with frontends that share core services, user data and playback backends.

For completed refactor milestones and larger internal cleanup plans, see [`docs/refactor-roadmap.md`](refactor-roadmap.md).
Expand Down Expand Up @@ -83,6 +90,13 @@ flowchart LR
Core --> Usage["Data usage tracking"]
Core --> Config["Config and XDG storage"]
Core --> Compatibility["Station compatibility"]
Core --> Recording["RecordingManager"]
Recording --> RecorderStore["SqliteRecordingStore"]
Recording --> FfmpegRecorder["FfmpegRecorder"]
FfmpegRecorder --> FFMPEG["ffmpeg"]
FFMPEG --> RecordingStreams["Online radio streams"]
FFMPEG --> RecordingFiles["XDG data recordings/*.mka"]
RecorderStore --> Library

Compatibility --> Capabilities["PlayerCapabilities"]
Capabilities --> Registry["Player registry"]
Expand Down Expand Up @@ -244,6 +258,8 @@ fluxtuner/core/
playlists.py Tag playlists and playlist persistence helpers
profiles.py Profile persistence and effective-profile resolution
public_stats.py Public activity statistics
recording.py Recording lifecycle contracts and manager
recordings.py Recording paths and SQLite persistence
search_service.py Shared station search service
stations.py Station normalization and persistence helpers
storage.py Atomic JSON writes for remaining JSON files
Expand Down Expand Up @@ -304,6 +320,44 @@ Current backends:

`mpv` and `ffplay` are treated as broadly compatible backends. `mpg123` and `ogg123` are specialized backends, so FluxTuner uses declared `PlayerCapabilities` plus station metadata to filter unsupported stations where possible.

## Recording layer

Local recording is a separate lifecycle from playback and is currently exposed by
the Textual TUI and GTK4 GUI only.

```mermaid
flowchart LR
TUI["Textual TUI"] --> Manager["RecordingManager"]
GTK["GTK4 GUI"] --> Manager

Manager --> Backend["FfmpegRecorder"]
Manager --> Store["SqliteRecordingStore"]

Backend --> FFmpeg["ffmpeg -readrate 1 -c copy"]
FFmpeg --> Stream["Online radio stream"]
FFmpeg --> Media["XDG data recordings/*.mka"]

Store --> DB["SQLite recordings table"]
DB --> Profiles["Profile-scoped recording metadata"]
```

The recording manager owns the single active recording session for an interface.
Playback and recording are intentionally independent: changing or stopping playback
does not stop an active recording.

FFmpeg records with stream copy into Matroska audio and uses real-time input pacing
so live/HLS sources are not consumed faster than wall-clock time. Logical duration
is derived from the manager's start/stop timestamps rather than container timestamps,
which can inherit timing from the source stream.

Completed recording metadata is persisted in SQLite. Media files are stored under
the FluxTuner XDG data directory in `recordings/`. Closing GTK to a compatible
system tray keeps an active recording running; actual application shutdown finalizes
the FFmpeg process and persists the completed session.

Web/server recording is outside the current local recording boundary and requires
separate storage, quota and long-running-worker design before being exposed.

## Player capabilities and station compatibility

Each backend declares static capabilities through `PlayerCapabilities`.
Expand Down Expand Up @@ -334,6 +388,7 @@ The SQLite database stores the profile-scoped library:
- favorites
- playback history
- manual playlists
- recording metadata

Favorites are the canonical saved-station library. Manual playlists store station
references and resolve them through the saved station/favorites model rather than
Expand All @@ -358,6 +413,7 @@ Other local files remain JSON-based:
```text
~/.config/fluxtuner/config.json
~/.local/share/fluxtuner/usage.json
~/.local/share/fluxtuner/recordings/*.mka
~/.cache/fluxtuner/search_cache.json
```

Expand Down
Loading