refrain 0.4.6__tar.gz → 0.5.2__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {refrain-0.4.6 → refrain-0.5.2}/.github/ISSUE_TEMPLATE/bug_report.yml +1 -1
- {refrain-0.4.6 → refrain-0.5.2}/.github/workflows/security.yml +1 -1
- {refrain-0.4.6 → refrain-0.5.2}/.gitignore +0 -5
- {refrain-0.4.6 → refrain-0.5.2}/CHANGELOG.md +196 -1
- {refrain-0.4.6 → refrain-0.5.2}/CONTRIBUTING.md +13 -2
- {refrain-0.4.6 → refrain-0.5.2}/Makefile +8 -1
- {refrain-0.4.6 → refrain-0.5.2}/PKG-INFO +65 -6
- {refrain-0.4.6 → refrain-0.5.2}/PRIVACY.md +5 -1
- {refrain-0.4.6 → refrain-0.5.2}/README.md +64 -5
- {refrain-0.4.6 → refrain-0.5.2}/ROADMAP.md +33 -4
- {refrain-0.4.6 → refrain-0.5.2}/docs/architecture.md +149 -25
- {refrain-0.4.6 → refrain-0.5.2}/docs/bluetooth.md +6 -1
- {refrain-0.4.6 → refrain-0.5.2}/docs/faq.md +70 -13
- {refrain-0.4.6 → refrain-0.5.2}/docs/lastfm.md +16 -1
- {refrain-0.4.6 → refrain-0.5.2}/docs/screenshots/README.md +19 -6
- refrain-0.5.2/docs/screenshots/demo-cover.png +0 -0
- refrain-0.5.2/docs/screenshots/discord-rpc.png +0 -0
- refrain-0.5.2/docs/screenshots/history.png +0 -0
- refrain-0.5.2/docs/screenshots/legal.png +0 -0
- refrain-0.5.2/docs/screenshots/live-log.png +0 -0
- refrain-0.5.2/docs/screenshots/notification.png +0 -0
- refrain-0.5.2/docs/screenshots/settings-advanced.png +0 -0
- refrain-0.5.2/docs/screenshots/settings-general.png +0 -0
- refrain-0.5.2/docs/screenshots/settings-history.png +0 -0
- refrain-0.5.2/docs/screenshots/settings-lastfm.png +0 -0
- refrain-0.5.2/docs/screenshots/settings-sources.png +0 -0
- refrain-0.5.2/docs/screenshots/settings-updates.png +0 -0
- refrain-0.5.2/docs/screenshots/tray-menu.png +0 -0
- refrain-0.5.2/docs/screenshots/update-dialog.png +0 -0
- {refrain-0.4.6 → refrain-0.5.2}/docs/test-matrix.md +40 -4
- {refrain-0.4.6 → refrain-0.5.2}/packaging/README.md +37 -18
- {refrain-0.4.6 → refrain-0.5.2}/packaging/appimage/AppImageBuilder.yml +1 -1
- {refrain-0.4.6 → refrain-0.5.2}/packaging/aur/refrain/.SRCINFO +2 -2
- {refrain-0.4.6 → refrain-0.5.2}/packaging/aur/refrain/PKGBUILD +2 -2
- {refrain-0.4.6 → refrain-0.5.2}/packaging/aur/refrain-git/.SRCINFO +1 -1
- {refrain-0.4.6 → refrain-0.5.2}/packaging/aur/refrain-git/PKGBUILD +1 -1
- {refrain-0.4.6 → refrain-0.5.2}/packaging/flatpak/io.github.Rockykln.Refrain.desktop +1 -1
- {refrain-0.4.6 → refrain-0.5.2}/packaging/flatpak/io.github.Rockykln.Refrain.metainfo.xml +23 -0
- {refrain-0.4.6 → refrain-0.5.2}/packaging/flatpak/io.github.Rockykln.Refrain.yaml +2 -2
- {refrain-0.4.6 → refrain-0.5.2}/pyproject.toml +3 -1
- refrain-0.5.2/src/refrain/__init__.py +1 -0
- {refrain-0.4.6 → refrain-0.5.2}/src/refrain/app.py +124 -3
- {refrain-0.4.6 → refrain-0.5.2}/src/refrain/assets/refrain.desktop +1 -1
- {refrain-0.4.6 → refrain-0.5.2}/src/refrain/autostart.py +31 -1
- refrain-0.5.2/src/refrain/browser.py +125 -0
- {refrain-0.4.6 → refrain-0.5.2}/src/refrain/config.py +26 -0
- refrain-0.5.2/src/refrain/cover_art.py +353 -0
- {refrain-0.4.6 → refrain-0.5.2}/src/refrain/daemon.py +193 -24
- refrain-0.5.2/src/refrain/history.py +733 -0
- refrain-0.5.2/src/refrain/i18n/refrain_de.qm +0 -0
- {refrain-0.4.6 → refrain-0.5.2}/src/refrain/i18n/refrain_de.ts +403 -152
- refrain-0.5.2/src/refrain/i18n/refrain_en.qm +0 -0
- refrain-0.5.2/src/refrain/i18n/refrain_en.ts +47 -0
- refrain-0.5.2/src/refrain/i18n/refrain_es.qm +0 -0
- {refrain-0.4.6 → refrain-0.5.2}/src/refrain/i18n/refrain_es.ts +403 -152
- refrain-0.5.2/src/refrain/i18n/refrain_fr.qm +0 -0
- {refrain-0.4.6 → refrain-0.5.2}/src/refrain/i18n/refrain_fr.ts +403 -152
- {refrain-0.4.6 → refrain-0.5.2}/src/refrain/i18n/refrain_it.qm +0 -0
- {refrain-0.4.6 → refrain-0.5.2}/src/refrain/i18n/refrain_it.ts +403 -152
- {refrain-0.4.6 → refrain-0.5.2}/src/refrain/i18n/refrain_ja.qm +0 -0
- {refrain-0.4.6 → refrain-0.5.2}/src/refrain/i18n/refrain_ja.ts +398 -152
- refrain-0.5.2/src/refrain/i18n/refrain_pl.qm +0 -0
- {refrain-0.4.6 → refrain-0.5.2}/src/refrain/i18n/refrain_pl.ts +408 -152
- refrain-0.5.2/src/refrain/i18n/refrain_pt.qm +0 -0
- {refrain-0.4.6 → refrain-0.5.2}/src/refrain/i18n/refrain_pt.ts +403 -152
- {refrain-0.4.6 → refrain-0.5.2}/src/refrain/i18n/refrain_ru.qm +0 -0
- {refrain-0.4.6 → refrain-0.5.2}/src/refrain/i18n/refrain_ru.ts +408 -152
- {refrain-0.4.6 → refrain-0.5.2}/src/refrain/i18n/refrain_zh_CN.qm +0 -0
- {refrain-0.4.6 → refrain-0.5.2}/src/refrain/i18n/refrain_zh_CN.ts +398 -152
- {refrain-0.4.6 → refrain-0.5.2}/src/refrain/scrobble.py +328 -22
- {refrain-0.4.6 → refrain-0.5.2}/src/refrain/scrobble_queue.py +25 -1
- refrain-0.5.2/src/refrain/song_lengths.py +144 -0
- {refrain-0.4.6 → refrain-0.5.2}/src/refrain/sources/base.py +8 -0
- {refrain-0.4.6 → refrain-0.5.2}/src/refrain/sources/bluetooth.py +111 -4
- {refrain-0.4.6 → refrain-0.5.2}/src/refrain/sources/mpris.py +111 -14
- {refrain-0.4.6 → refrain-0.5.2}/src/refrain/sources/mpris_server.py +84 -11
- {refrain-0.4.6 → refrain-0.5.2}/src/refrain/timing.py +152 -11
- refrain-0.5.2/src/refrain/ui/history_window.py +1003 -0
- {refrain-0.4.6 → refrain-0.5.2}/src/refrain/ui/settings_window.py +125 -4
- {refrain-0.4.6 → refrain-0.5.2}/src/refrain/ui/tray.py +38 -1
- refrain-0.5.2/tests/conftest.py +69 -0
- {refrain-0.4.6 → refrain-0.5.2}/tests/test_autostart.py +66 -0
- refrain-0.5.2/tests/test_bluetooth_apps.py +70 -0
- refrain-0.5.2/tests/test_browser.py +57 -0
- refrain-0.5.2/tests/test_button_url.py +40 -0
- refrain-0.5.2/tests/test_cover_art.py +251 -0
- refrain-0.5.2/tests/test_crash_log.py +76 -0
- refrain-0.5.2/tests/test_desktop_entries.py +60 -0
- refrain-0.5.2/tests/test_history.py +555 -0
- refrain-0.5.2/tests/test_history_window.py +413 -0
- refrain-0.5.2/tests/test_mpris_controls.py +57 -0
- {refrain-0.4.6 → refrain-0.5.2}/tests/test_mpris_dispatch.py +138 -1
- {refrain-0.4.6 → refrain-0.5.2}/tests/test_position_resolver.py +223 -8
- refrain-0.5.2/tests/test_scrobbler.py +567 -0
- refrain-0.5.2/tests/test_settings_github_icon.py +62 -0
- refrain-0.5.2/tests/test_settings_history.py +130 -0
- refrain-0.5.2/tests/test_song_lengths.py +164 -0
- refrain-0.5.2/tests/test_translations.py +81 -0
- {refrain-0.4.6 → refrain-0.5.2}/tests/test_tray_init.py +40 -0
- refrain-0.4.6/docs/screenshots/discord-rpc.png +0 -0
- refrain-0.4.6/docs/screenshots/legal.png +0 -0
- refrain-0.4.6/docs/screenshots/live-log.png +0 -0
- refrain-0.4.6/docs/screenshots/notification.png +0 -0
- refrain-0.4.6/docs/screenshots/settings-advanced.png +0 -0
- refrain-0.4.6/docs/screenshots/settings-general.png +0 -0
- refrain-0.4.6/docs/screenshots/settings-lastfm.png +0 -0
- refrain-0.4.6/docs/screenshots/settings-sources.png +0 -0
- refrain-0.4.6/docs/screenshots/settings-updates.png +0 -0
- refrain-0.4.6/docs/screenshots/tray-menu.png +0 -0
- refrain-0.4.6/docs/screenshots/update-dialog.png +0 -0
- refrain-0.4.6/src/refrain/__init__.py +0 -1
- refrain-0.4.6/src/refrain/cover_art.py +0 -222
- refrain-0.4.6/src/refrain/i18n/refrain_de.qm +0 -0
- refrain-0.4.6/src/refrain/i18n/refrain_es.qm +0 -0
- refrain-0.4.6/src/refrain/i18n/refrain_fr.qm +0 -0
- refrain-0.4.6/src/refrain/i18n/refrain_pl.qm +0 -0
- refrain-0.4.6/src/refrain/i18n/refrain_pt.qm +0 -0
- refrain-0.4.6/tests/conftest.py +0 -37
- refrain-0.4.6/tests/test_cover_art.py +0 -151
- refrain-0.4.6/tests/test_scrobbler.py +0 -267
- {refrain-0.4.6 → refrain-0.5.2}/.editorconfig +0 -0
- {refrain-0.4.6 → refrain-0.5.2}/.gitattributes +0 -0
- {refrain-0.4.6 → refrain-0.5.2}/.github/FUNDING.yml +0 -0
- {refrain-0.4.6 → refrain-0.5.2}/.github/ISSUE_TEMPLATE/config.yml +0 -0
- {refrain-0.4.6 → refrain-0.5.2}/.github/ISSUE_TEMPLATE/feature_request.yml +0 -0
- {refrain-0.4.6 → refrain-0.5.2}/.github/codeql/codeql-config.yml +0 -0
- {refrain-0.4.6 → refrain-0.5.2}/.github/dependabot.yml +0 -0
- {refrain-0.4.6 → refrain-0.5.2}/.github/pull_request_template.md +0 -0
- {refrain-0.4.6 → refrain-0.5.2}/.github/workflows/codeql.yml +0 -0
- {refrain-0.4.6 → refrain-0.5.2}/.github/workflows/release.yml +0 -0
- {refrain-0.4.6 → refrain-0.5.2}/.github/workflows/tests.yml +0 -0
- {refrain-0.4.6 → refrain-0.5.2}/CODE_OF_CONDUCT.md +0 -0
- {refrain-0.4.6 → refrain-0.5.2}/LEGAL.md +0 -0
- {refrain-0.4.6 → refrain-0.5.2}/LICENSE +0 -0
- {refrain-0.4.6 → refrain-0.5.2}/SECURITY.md +0 -0
- {refrain-0.4.6 → refrain-0.5.2}/docs/screenshots/welcome.png +0 -0
- {refrain-0.4.6 → refrain-0.5.2}/packaging/flatpak/python-deps.json +0 -0
- {refrain-0.4.6 → refrain-0.5.2}/requirements-dev.txt +0 -0
- {refrain-0.4.6 → refrain-0.5.2}/requirements.txt +0 -0
- {refrain-0.4.6 → refrain-0.5.2}/src/refrain/__main__.py +0 -0
- {refrain-0.4.6 → refrain-0.5.2}/src/refrain/assets/icons/github-mark.svg +0 -0
- {refrain-0.4.6 → refrain-0.5.2}/src/refrain/assets/icons/menu-quit.svg +0 -0
- {refrain-0.4.6 → refrain-0.5.2}/src/refrain/assets/icons/menu-update.svg +0 -0
- {refrain-0.4.6 → refrain-0.5.2}/src/refrain/assets/icons/refrain.png +0 -0
- {refrain-0.4.6 → refrain-0.5.2}/src/refrain/assets/icons/refrain.svg +0 -0
- {refrain-0.4.6 → refrain-0.5.2}/src/refrain/assets/icons/tray-paused-dark.svg +0 -0
- {refrain-0.4.6 → refrain-0.5.2}/src/refrain/assets/icons/tray-paused.svg +0 -0
- {refrain-0.4.6 → refrain-0.5.2}/src/refrain/assets/icons/tray-playing-dark.svg +0 -0
- {refrain-0.4.6 → refrain-0.5.2}/src/refrain/assets/icons/tray-playing.svg +0 -0
- {refrain-0.4.6 → refrain-0.5.2}/src/refrain/assets/icons/tray-stopped-dark.svg +0 -0
- {refrain-0.4.6 → refrain-0.5.2}/src/refrain/assets/icons/tray-stopped.svg +0 -0
- {refrain-0.4.6 → refrain-0.5.2}/src/refrain/cover_fetcher.py +0 -0
- {refrain-0.4.6 → refrain-0.5.2}/src/refrain/discord_app.py +0 -0
- {refrain-0.4.6 → refrain-0.5.2}/src/refrain/discord_rpc.py +0 -0
- {refrain-0.4.6 → refrain-0.5.2}/src/refrain/logging_setup.py +0 -0
- {refrain-0.4.6 → refrain-0.5.2}/src/refrain/paths.py +0 -0
- {refrain-0.4.6 → refrain-0.5.2}/src/refrain/secrets_store.py +0 -0
- {refrain-0.4.6 → refrain-0.5.2}/src/refrain/single_instance.py +0 -0
- {refrain-0.4.6 → refrain-0.5.2}/src/refrain/sources/__init__.py +0 -0
- {refrain-0.4.6 → refrain-0.5.2}/src/refrain/startup_check.py +0 -0
- {refrain-0.4.6 → refrain-0.5.2}/src/refrain/ui/__init__.py +0 -0
- {refrain-0.4.6 → refrain-0.5.2}/src/refrain/ui/cursors.py +0 -0
- {refrain-0.4.6 → refrain-0.5.2}/src/refrain/ui/legal_dialog.py +0 -0
- {refrain-0.4.6 → refrain-0.5.2}/src/refrain/ui/log_window.py +0 -0
- {refrain-0.4.6 → refrain-0.5.2}/src/refrain/ui/update_dialog.py +0 -0
- {refrain-0.4.6 → refrain-0.5.2}/src/refrain/ui/welcome_dialog.py +0 -0
- {refrain-0.4.6 → refrain-0.5.2}/src/refrain/uninstall.py +0 -0
- {refrain-0.4.6 → refrain-0.5.2}/src/refrain/updater.py +0 -0
- {refrain-0.4.6 → refrain-0.5.2}/tests/__init__.py +0 -0
- {refrain-0.4.6 → refrain-0.5.2}/tests/test_config.py +0 -0
- {refrain-0.4.6 → refrain-0.5.2}/tests/test_config_preserve.py +0 -0
- {refrain-0.4.6 → refrain-0.5.2}/tests/test_cover_fetcher.py +0 -0
- {refrain-0.4.6 → refrain-0.5.2}/tests/test_daemon_album_format.py +0 -0
- {refrain-0.4.6 → refrain-0.5.2}/tests/test_daemon_idle.py +0 -0
- {refrain-0.4.6 → refrain-0.5.2}/tests/test_dbus_dispatch.py +0 -0
- {refrain-0.4.6 → refrain-0.5.2}/tests/test_discord_app.py +0 -0
- {refrain-0.4.6 → refrain-0.5.2}/tests/test_discord_ipc_scan.py +0 -0
- {refrain-0.4.6 → refrain-0.5.2}/tests/test_discord_listening.py +0 -0
- {refrain-0.4.6 → refrain-0.5.2}/tests/test_discord_multi_client.py +0 -0
- {refrain-0.4.6 → refrain-0.5.2}/tests/test_discord_rpc.py +0 -0
- {refrain-0.4.6 → refrain-0.5.2}/tests/test_imports.py +0 -0
- {refrain-0.4.6 → refrain-0.5.2}/tests/test_legal_notice.py +0 -0
- {refrain-0.4.6 → refrain-0.5.2}/tests/test_notify_argv.py +0 -0
- {refrain-0.4.6 → refrain-0.5.2}/tests/test_paths.py +0 -0
- {refrain-0.4.6 → refrain-0.5.2}/tests/test_qt_plugin_path.py +0 -0
- {refrain-0.4.6 → refrain-0.5.2}/tests/test_scrobble.py +0 -0
- {refrain-0.4.6 → refrain-0.5.2}/tests/test_scrobble_queue.py +0 -0
- {refrain-0.4.6 → refrain-0.5.2}/tests/test_secrets_store.py +0 -0
- {refrain-0.4.6 → refrain-0.5.2}/tests/test_settings_lastfm.py +0 -0
- {refrain-0.4.6 → refrain-0.5.2}/tests/test_settings_reset.py +0 -0
- {refrain-0.4.6 → refrain-0.5.2}/tests/test_source_selection.py +0 -0
- {refrain-0.4.6 → refrain-0.5.2}/tests/test_sources_base.py +0 -0
- {refrain-0.4.6 → refrain-0.5.2}/tests/test_startup_check.py +0 -0
- {refrain-0.4.6 → refrain-0.5.2}/tests/test_timing.py +0 -0
- {refrain-0.4.6 → refrain-0.5.2}/tests/test_ui_cursors.py +0 -0
- {refrain-0.4.6 → refrain-0.5.2}/tests/test_ui_locale.py +0 -0
- {refrain-0.4.6 → refrain-0.5.2}/tests/test_uninstall.py +0 -0
- {refrain-0.4.6 → refrain-0.5.2}/tests/test_update_dialog_body.py +0 -0
- {refrain-0.4.6 → refrain-0.5.2}/tests/test_updater.py +0 -0
- {refrain-0.4.6 → refrain-0.5.2}/tests/test_welcome_dialog_growth.py +0 -0
|
@@ -78,7 +78,7 @@ jobs:
|
|
|
78
78
|
fetch-depth: 0
|
|
79
79
|
|
|
80
80
|
- name: Run TruffleHog
|
|
81
|
-
uses: trufflesecurity/trufflehog@
|
|
81
|
+
uses: trufflesecurity/trufflehog@363923b901c911a9164f50b6c423f47c15372b1c # v3.97.4
|
|
82
82
|
with:
|
|
83
83
|
# --only-verified: a secret only counts as flagged when TruffleHog
|
|
84
84
|
# successfully validates it against the live service. Drops the
|
|
@@ -7,6 +7,199 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
## [0.5.2] - 2026-09-18
|
|
11
|
+
|
|
12
|
+
A release about the gaps. Songs the iTunes catalog doesn't know — about
|
|
13
|
+
one in ten here — were never scrobbled from the browser and had no end
|
|
14
|
+
time on Discord; Refrain now measures their length from whole plays. A
|
|
15
|
+
stream or video playing over Bluetooth no longer passes for a song,
|
|
16
|
+
Discord no longer shows nothing when the tab's address has a space in
|
|
17
|
+
it, and Last.fm neither scrobbles a long song twice nor loses one around
|
|
18
|
+
a restart, an invalid session or an account switch.
|
|
19
|
+
|
|
20
|
+
### Fixed
|
|
21
|
+
|
|
22
|
+
- **The desktop entries named invalid categories.** `Audio` requires
|
|
23
|
+
`AudioVideo`, which was missing — `desktop-file-validate` reports that
|
|
24
|
+
as an error set to become fatal — and `Audio` next to `Network` put
|
|
25
|
+
Refrain in two main categories, so a menu could list it twice. The
|
|
26
|
+
menu entry, the Flatpak entry and the autostart entry now all say
|
|
27
|
+
`AudioVideo;Audio;Music;`, and an autostart entry written by an
|
|
28
|
+
earlier version gets the new categories on the next start. Nothing
|
|
29
|
+
else in it changes — not the launcher it points at, and not a switch
|
|
30
|
+
the desktop set to keep Refrain from starting with the session.
|
|
31
|
+
- **The GitHub logo in the Settings footer was hard to see on a dark
|
|
32
|
+
theme.** Qt draws the logo's `currentColor` as black whatever the
|
|
33
|
+
theme; it now takes the theme's text colour, like the version beside
|
|
34
|
+
it, and follows a theme change while the window is open.
|
|
35
|
+
- **A long song could be scrobbled twice after a restart.** If the first
|
|
36
|
+
poll after starting saw no song yet, the play saved on quit was thrown
|
|
37
|
+
away, the song counted again from zero and was queued a second time —
|
|
38
|
+
or, not having counted yet, was never scrobbled at all. Quitting
|
|
39
|
+
before that first poll also deleted the saved play.
|
|
40
|
+
- **Pausing on an iPad could split the play in two.** The iPad reports
|
|
41
|
+
0:00 for a moment when paused; that was passed to the history and to
|
|
42
|
+
Last.fm as the player's position, which read as the song starting over
|
|
43
|
+
— a second history entry, and on songs over eight minutes a second
|
|
44
|
+
scrobble.
|
|
45
|
+
- **The elapsed time could run ahead or fall behind for a whole song.**
|
|
46
|
+
A stream position that stalled and then caught up was taken for a
|
|
47
|
+
seek; a single poll that saw no song made a browser source's next
|
|
48
|
+
segment look like the song's start; and an album that arrived a few
|
|
49
|
+
seconds after the title restarted the clock at 0:00.
|
|
50
|
+
- **Scrobbles were lost while the Last.fm session was invalid.** The log
|
|
51
|
+
promised they would submit after reconnecting, but songs played in the
|
|
52
|
+
meantime were never queued. They are now.
|
|
53
|
+
- **Queued scrobbles could go to the wrong account.** After connecting a
|
|
54
|
+
different Last.fm account, plays still in the offline queue were sent
|
|
55
|
+
to it; they now stay with the account they were heard under.
|
|
56
|
+
- **Quitting could wait on Last.fm for up to ten seconds**, and the
|
|
57
|
+
scrobble queue was written readable by other users — it is owner-only
|
|
58
|
+
now, like the history.
|
|
59
|
+
- **Other apps couldn't read what Refrain was playing when the title had
|
|
60
|
+
an umlaut** — or any other letter beyond ASCII. Refrain's MPRIS track
|
|
61
|
+
ID kept it, which a D-Bus object path doesn't allow, and every read of
|
|
62
|
+
the metadata failed.
|
|
63
|
+
- **Songs the iTunes catalog doesn't know were never scrobbled from the
|
|
64
|
+
browser.** Since 0.5.1 the media segments plasma reports no longer pass
|
|
65
|
+
for a song's length, and without a catalog match that left none at all —
|
|
66
|
+
no scrobble, no end time on Discord, and nothing in the history under
|
|
67
|
+
four minutes. Refrain now measures such a song itself: a play it watched
|
|
68
|
+
from the start to an end the player reached on its own is as long as the
|
|
69
|
+
song, and two plays that agree settle it. Kept under a hash of the
|
|
70
|
+
title, artist and album, so the file holds no song titles.
|
|
71
|
+
- **Discord showed nothing while a song without a catalog match played
|
|
72
|
+
from Apple Music's search page.** Plasma reports the tab's address with
|
|
73
|
+
plain spaces in it, the "Listen on Apple Music" button took it as its
|
|
74
|
+
link, and Discord refused the whole activity over the malformed link.
|
|
75
|
+
The link is encoded now, and one that still isn't valid drops only the
|
|
76
|
+
button.
|
|
77
|
+
- **A stream or video playing over Bluetooth passed for a song.** A Twitch
|
|
78
|
+
stream on an iPad went to Discord and, four minutes in, into the history
|
|
79
|
+
under its channel's name. Refrain now reads which app is playing and
|
|
80
|
+
takes music apps only; an app it doesn't know counts when its track has
|
|
81
|
+
a length, which a live stream doesn't.
|
|
82
|
+
- **A browser tab with "Apple Music" anywhere in its title counted as the
|
|
83
|
+
Apple Music tab**, so a video about Apple Music could decide whether
|
|
84
|
+
Refrain showed the music as playing or paused.
|
|
85
|
+
|
|
86
|
+
## [0.5.1] - 2026-09-15
|
|
87
|
+
|
|
88
|
+
A release about what you played. A new *Recently played* window lists the
|
|
89
|
+
last songs Refrain saw, and every play is now one row and one scrobble —
|
|
90
|
+
on repeat, across a restart of Refrain, or through a crash. Plasma's
|
|
91
|
+
browser integration turned out to report Apple Music's looping artwork
|
|
92
|
+
video rather than the music; Refrain now asks the Apple Music tab itself
|
|
93
|
+
whether it is playing, which also makes Play work again after a pause.
|
|
94
|
+
|
|
95
|
+
Also: the crash behind Refrain simply vanishing, Plasma's Stop starting
|
|
96
|
+
playback, Settings → Apply restarting a song's Last.fm count, and covers
|
|
97
|
+
and lengths for far more songs. (0.5.0 was never released.)
|
|
98
|
+
|
|
99
|
+
### Added
|
|
100
|
+
|
|
101
|
+
- **Recently played.** A new window, opened from the tray menu or from
|
|
102
|
+
Settings → History, lists the last songs Refrain saw: cover, title,
|
|
103
|
+
artist and album, the time and day each one started, where it came
|
|
104
|
+
from — Apple Music in which browser, or which Bluetooth device — its
|
|
105
|
+
length, and whether it went to Last.fm. Every song is a link to its
|
|
106
|
+
Apple Music page — or, where Refrain never learned the page, to an
|
|
107
|
+
Apple Music search for it — and opens in the browser it played in
|
|
108
|
+
while that browser is still running; a right-click takes a single song
|
|
109
|
+
out of the list. A song shows up the moment it
|
|
110
|
+
starts playing and stays once it counts as listened: half its length
|
|
111
|
+
or four minutes, the rule Last.fm uses, so skipping through a playlist
|
|
112
|
+
leaves nothing behind. The list lives on this machine only
|
|
113
|
+
(`$XDG_STATE_HOME/refrain/history.json`, owner-only), which is why it
|
|
114
|
+
keeps running with the privacy mode set to *Off*. Settings → History
|
|
115
|
+
turns it off entirely — deleting the stored list — and sets how many
|
|
116
|
+
songs to keep, from 10 to 100; 30 by default. The song playing when
|
|
117
|
+
Refrain stops — or crashes — is saved with how much of it was heard
|
|
118
|
+
and where it was, and carries on as the same row if it is still on,
|
|
119
|
+
further along, when Refrain comes back. A song heard through and
|
|
120
|
+
played again is listed once per play.
|
|
121
|
+
|
|
122
|
+
### Fixed
|
|
123
|
+
|
|
124
|
+
- **After a restart mid-song, the elapsed time fell back every ten
|
|
125
|
+
seconds.** Plasma's browser integration reports the media segment the
|
|
126
|
+
page has buffered — position 0 to 10 s, over and over — and at a
|
|
127
|
+
constant segment length nothing gave it away. A source length under
|
|
128
|
+
30 s on a song the catalog knows to be longer is now never taken as the
|
|
129
|
+
song's position: the time counts on from the song's start, or stays
|
|
130
|
+
hidden when Refrain came up in the middle of it.
|
|
131
|
+
- **Paused in the browser, Apple Music still counted as playing — and
|
|
132
|
+
Play didn't start it again.** KDE's browser integration takes the
|
|
133
|
+
song's title from the page but its state from whatever video plays on
|
|
134
|
+
it: Apple Music's looping artwork. That kept "playing" through a pause,
|
|
135
|
+
so the time ran on, the history and Last.fm counted the pause as
|
|
136
|
+
listened, and its Play/Pause toggle could only pause. The browser's own
|
|
137
|
+
entry for the Apple Music tab now decides playing or paused, and gets
|
|
138
|
+
Play/Pause first.
|
|
139
|
+
- **Settings → Apply started the song's Last.fm count over.** Apply
|
|
140
|
+
sends the whole configuration, and the Scrobbler dropped the play in
|
|
141
|
+
progress on every one of them — for a change on any tab. Applied near
|
|
142
|
+
the end of a song, the song was never scrobbled. The play in progress
|
|
143
|
+
is now only dropped when the Last.fm account itself changes.
|
|
144
|
+
- **Restarting Refrain mid-song could scrobble the song twice.** A play
|
|
145
|
+
that counted was queued on quit, and after the restart the Scrobbler
|
|
146
|
+
counted the same song again from zero — a long song got a second
|
|
147
|
+
scrobble for what was heard afterwards, and a shorter one was
|
|
148
|
+
scrobbled under the time of the restart. The play in progress is now
|
|
149
|
+
kept across a restart (`scrobble_current.json`, like the history's):
|
|
150
|
+
the same play carries on, under the time it began, and is never
|
|
151
|
+
queued twice. One that counted before a crash is still scrobbled.
|
|
152
|
+
- **"Apple Music – Webplayer" could pass for a song.** With no song
|
|
153
|
+
loaded, KDE's browser integration reports the page's title as the
|
|
154
|
+
track — no artist — and calls it playing. A title like that is no
|
|
155
|
+
longer taken for a song, so it can't reach Discord or the history.
|
|
156
|
+
- **A song on repeat was scrobbled only once, and lost its time.**
|
|
157
|
+
Last.fm counts every play, but the Scrobbler saw one long play for as
|
|
158
|
+
long as the title stayed the same. A song that has counted and begins
|
|
159
|
+
again is now a new play, with a scrobble of its own. The player shows
|
|
160
|
+
it: an ordinary one jumps back to zero; Apple Music in the browser,
|
|
161
|
+
through Plasma, reports the song's own length at position zero for a
|
|
162
|
+
moment; a phone over Bluetooth set to repeat-one kept counting its
|
|
163
|
+
position on across the loop (5:13 into a 3:36 song), which hid the
|
|
164
|
+
elapsed time after the first play — it now counts from the start of
|
|
165
|
+
each loop.
|
|
166
|
+
|
|
167
|
+
- **The tray icon flashed to "paused" at every song change.** Apple
|
|
168
|
+
Music's web player reports a pause for a moment between two songs.
|
|
169
|
+
The icon and the Play/Pause entry now only follow a pause that lasts
|
|
170
|
+
two seconds; going back to playing still shows at once.
|
|
171
|
+
- **Plasma's "Stop" started the music when it was paused.** Right-click
|
|
172
|
+
Refrain in the task manager and Plasma offers its media controls;
|
|
173
|
+
Play, Pause and Stop all toggled playback blindly, so each one did the
|
|
174
|
+
opposite of what it said whenever the music was already in the state
|
|
175
|
+
it asked for. They now only act when that gets them there.
|
|
176
|
+
- **Refrain could die out of nowhere.** The daemon thread polled the
|
|
177
|
+
players over the same D-Bus connection the main thread dispatches for
|
|
178
|
+
the published MPRIS player, and dbus-glib keeps that connection's
|
|
179
|
+
bookkeeping without locks. Two threads on it at once corrupted the
|
|
180
|
+
heap, and the process aborted with `malloc(): unaligned tcache chunk
|
|
181
|
+
detected` — the "Refrain was simply gone" that the log shows as a
|
|
182
|
+
start without a stop before it. The sources now keep private
|
|
183
|
+
connections of their own, the MPRIS player is only touched from the
|
|
184
|
+
thread that dispatches it, and Plasma's play/pause/skip requests are
|
|
185
|
+
handed to the daemon thread instead of running the sources from the
|
|
186
|
+
main one. Should a crash still happen, `crash.log` next to the log
|
|
187
|
+
now holds every thread's Python stack.
|
|
188
|
+
- **About one song in ten had no cover and no length.** The catalog
|
|
189
|
+
lookup asked the US store only, with the title as given — "feat."
|
|
190
|
+
credits, remaster tags and all — and took the first result without
|
|
191
|
+
checking it. German releases missing from the US store, songs
|
|
192
|
+
crediting several artists and titles with a "(feat. …)" found
|
|
193
|
+
nothing; others got a stranger's cover (a Chinese ballad on a German
|
|
194
|
+
rap track). The lookup now asks the store of the desktop's own
|
|
195
|
+
country first, strips those tags, falls back to the first of several
|
|
196
|
+
artists, and only takes a result whose artist *and* title match. A
|
|
197
|
+
lookup that fails because iTunes can't be reached is retried a minute
|
|
198
|
+
later instead of leaving the song without a cover for the whole
|
|
199
|
+
session, a song the catalog doesn't have is asked about again after
|
|
200
|
+
three days instead of never, and covers cached by the old lookup are
|
|
201
|
+
looked up once more.
|
|
202
|
+
|
|
10
203
|
## [0.4.6] - 2026-08-26
|
|
11
204
|
|
|
12
205
|
A release about the clock. Apple Music's web player and Plasma's browser
|
|
@@ -1576,7 +1769,9 @@ with a proper, installable Linux app.
|
|
|
1576
1769
|
pip-audit, trufflehog, release), Dependabot, issue + PR templates,
|
|
1577
1770
|
`CONTRIBUTING.md`, `CODE_OF_CONDUCT.md`, `SECURITY.md`.
|
|
1578
1771
|
|
|
1579
|
-
[Unreleased]: https://github.com/Rockykln/refrain/compare/v0.
|
|
1772
|
+
[Unreleased]: https://github.com/Rockykln/refrain/compare/v0.5.2...HEAD
|
|
1773
|
+
[0.5.2]: https://github.com/Rockykln/refrain/compare/v0.5.1...v0.5.2
|
|
1774
|
+
[0.5.1]: https://github.com/Rockykln/refrain/compare/v0.4.6...v0.5.1
|
|
1580
1775
|
[0.4.6]: https://github.com/Rockykln/refrain/compare/v0.4.5...v0.4.6
|
|
1581
1776
|
[0.4.5]: https://github.com/Rockykln/refrain/compare/v0.4.4...v0.4.5
|
|
1582
1777
|
[0.4.4]: https://github.com/Rockykln/refrain/compare/v0.4.3...v0.4.4
|
|
@@ -124,8 +124,19 @@ descriptions instead.
|
|
|
124
124
|
|
|
125
125
|
User-visible strings live wrapped in `tr()` calls; translations are
|
|
126
126
|
kept in `src/refrain/i18n/refrain_<lang>.ts` (Qt Linguist format).
|
|
127
|
-
English is the source language
|
|
128
|
-
|
|
127
|
+
English is the source language; every shipped language is complete.
|
|
128
|
+
|
|
129
|
+
Strings that contain a number use Qt's plural forms —
|
|
130
|
+
`self.tr("Last %n song(s)", "", count)` — never a sentence assembled
|
|
131
|
+
around the number, because Russian and Polish change the noun's ending
|
|
132
|
+
with it. Two things follow:
|
|
133
|
+
|
|
134
|
+
- Only `self.tr(…)` with the count as the third argument is extracted
|
|
135
|
+
as a plural. `QCoreApplication.translate(…, n)` isn't, and neither is
|
|
136
|
+
any call whose arguments contain `//`.
|
|
137
|
+
- `refrain_en.ts` holds only the English plural forms (`lupdate
|
|
138
|
+
-pluralonly`, which `make i18n-update` runs for it) — without it
|
|
139
|
+
English would read "Last 12 song(s)".
|
|
129
140
|
|
|
130
141
|
To add a new language end-to-end:
|
|
131
142
|
|
|
@@ -54,11 +54,18 @@ i18n:
|
|
|
54
54
|
|
|
55
55
|
# Refresh the .ts source-string list from the current Python sources,
|
|
56
56
|
# preserving existing translations. Use after wrapping new strings in tr().
|
|
57
|
+
# English is the source language, so refrain_en.ts holds only the plural
|
|
58
|
+
# forms of the %n strings (-pluralonly) — without it English would read
|
|
59
|
+
# "Last 12 song(s)".
|
|
57
60
|
i18n-update:
|
|
58
61
|
@"$(LUPDATE)" -no-obsolete \
|
|
59
62
|
src/refrain/app.py \
|
|
60
63
|
src/refrain/ui/*.py \
|
|
61
|
-
-ts $(wildcard src/refrain/i18n/refrain_*.ts)
|
|
64
|
+
-ts $(filter-out %_en.ts,$(wildcard src/refrain/i18n/refrain_*.ts))
|
|
65
|
+
@"$(LUPDATE)" -no-obsolete -pluralonly \
|
|
66
|
+
src/refrain/app.py \
|
|
67
|
+
src/refrain/ui/*.py \
|
|
68
|
+
-ts src/refrain/i18n/refrain_en.ts
|
|
62
69
|
@$(MAKE) i18n
|
|
63
70
|
|
|
64
71
|
# Remove anything that pytest, ruff, hatch, pip, or coverage might leave
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: refrain
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.5.2
|
|
4
4
|
Summary: Discord Rich Presence for Apple Music on Linux
|
|
5
5
|
Project-URL: Homepage, https://github.com/Rockykln/refrain
|
|
6
6
|
Project-URL: Repository, https://github.com/Rockykln/refrain
|
|
@@ -58,6 +58,8 @@ streaming from your phone over Bluetooth.
|
|
|
58
58
|
- Optionally **scrobbles to Last.fm** alongside Discord (opt-in, with a
|
|
59
59
|
crash-safe offline queue).
|
|
60
60
|
- Lives in your **system tray** with Play/Pause/Next/Previous controls.
|
|
61
|
+
- Keeps a **recently played** list — cover, time, source — of the last
|
|
62
|
+
30 songs by default, on your machine only.
|
|
61
63
|
- Provides a **settings window** (PySide6) for everything users typically want
|
|
62
64
|
to tweak — privacy mode, sources, autostart, Bluetooth device picker.
|
|
63
65
|
|
|
@@ -212,6 +214,12 @@ scrobble_now_playing = true # also send the ephemeral "now playing" indic
|
|
|
212
214
|
# NOTE: the Last.fm shared secret and session key are credentials and
|
|
213
215
|
# are deliberately NOT stored here. They live in your OS keyring
|
|
214
216
|
# (KWallet / GNOME Keyring), encrypted at rest — see "Last.fm" below.
|
|
217
|
+
|
|
218
|
+
[history]
|
|
219
|
+
enabled = true # the "Recently played" list; false also deletes what it stored
|
|
220
|
+
max_entries = 30 # songs kept, 1–100 (Settings offers 10, 20, 30, 50, 75, 100)
|
|
221
|
+
window_width = 0 # the history window's size when last closed; 0 = default
|
|
222
|
+
window_height = 0
|
|
215
223
|
```
|
|
216
224
|
|
|
217
225
|
Per-source `client_id_*` fields let Apple Music render under one Discord
|
|
@@ -224,7 +232,10 @@ another (with a generic Bluetooth glyph). Empty falls back to the default
|
|
|
224
232
|
Refrain publishes itself as `org.mpris.MediaPlayer2.refrain` on the session
|
|
225
233
|
bus, so KDE Plasma's panel media-controls applet (and KDE Connect, GNOME
|
|
226
234
|
Shell, Mako, …) drive the same Play/Pause/Next/Previous as the tray and
|
|
227
|
-
render the same track Discord renders.
|
|
235
|
+
render the same track Discord renders. Plasma also offers these controls
|
|
236
|
+
when you right-click Refrain in the task manager, including a *Stop*
|
|
237
|
+
that it adds for every player — Apple Music has no stop, so there it
|
|
238
|
+
pauses, and does nothing when the music is already paused.
|
|
228
239
|
|
|
229
240
|
## Tray
|
|
230
241
|
|
|
@@ -248,6 +259,7 @@ no unicode-glyph prefixes.
|
|
|
248
259
|
| Play / Pause | Toggle on the active source (label follows playback state)|
|
|
249
260
|
| Next | Skip forward on the active source |
|
|
250
261
|
| Update available — vX.Y.Z | Only visible when a newer release exists |
|
|
262
|
+
| Recently played… | Open the history window (hidden while the history is off) |
|
|
251
263
|
| Settings… | Open the settings window |
|
|
252
264
|
| Live log… | Open the live-log window |
|
|
253
265
|
| Restart Refrain | Cleanly stop and re-launch (release D-Bus name + RPC, exec the same binary) |
|
|
@@ -284,26 +296,65 @@ the background.
|
|
|
284
296
|
<img src="docs/screenshots/settings-lastfm.png" alt="Settings — Last.fm" width="420"/>
|
|
285
297
|
<br/><sub>Opt-in scrobbling, API key + secret, connect / disconnect an account</sub>
|
|
286
298
|
</td>
|
|
299
|
+
<td align="center">
|
|
300
|
+
<b>History</b><br/>
|
|
301
|
+
<img src="docs/screenshots/settings-history.png" alt="Settings — History" width="420"/>
|
|
302
|
+
<br/><sub>Recently played on or off, how many songs to keep</sub>
|
|
303
|
+
</td>
|
|
304
|
+
</tr>
|
|
305
|
+
<tr>
|
|
287
306
|
<td align="center">
|
|
288
307
|
<b>Updates</b><br/>
|
|
289
308
|
<img src="docs/screenshots/settings-updates.png" alt="Settings — Updates" width="420"/>
|
|
290
309
|
<br/><sub>Auto-check, last-checked, manual <i>Check for updates now</i></sub>
|
|
291
310
|
</td>
|
|
292
|
-
</tr>
|
|
293
|
-
<tr>
|
|
294
311
|
<td align="center">
|
|
295
312
|
<b>Advanced</b><br/>
|
|
296
313
|
<img src="docs/screenshots/settings-advanced.png" alt="Settings — Advanced" width="420"/>
|
|
297
314
|
<br/><sub>Poll interval, notification delay, cover cache size, language, log level, restart, reset, uninstall</sub>
|
|
298
315
|
</td>
|
|
316
|
+
</tr>
|
|
317
|
+
<tr>
|
|
299
318
|
<td align="center">
|
|
300
319
|
<b>Legal</b><br/>
|
|
301
320
|
<img src="docs/screenshots/legal.png" alt="Legal notice" width="420"/>
|
|
302
321
|
<br/><sub>Licence, trademark and affiliation notices — the <i>Legal</i> button in the footer</sub>
|
|
303
322
|
</td>
|
|
323
|
+
<td></td>
|
|
304
324
|
</tr>
|
|
305
325
|
</table>
|
|
306
326
|
|
|
327
|
+
## Recently played
|
|
328
|
+
|
|
329
|
+
<p align="center">
|
|
330
|
+
<img src="docs/screenshots/history.png" alt="Recently played" width="520"/>
|
|
331
|
+
</p>
|
|
332
|
+
|
|
333
|
+
*Tray → Recently played…* lists the last songs Refrain saw — 30 by
|
|
334
|
+
default, up to 100 — with cover, length, when each started, where it
|
|
335
|
+
came from (which browser, which Bluetooth device) and whether it went to
|
|
336
|
+
Last.fm. A song shows up the moment it plays and stays once it counts
|
|
337
|
+
as listened: half its length or four minutes, the rule Last.fm uses, so
|
|
338
|
+
skipping through a playlist leaves nothing behind. Restarting Refrain
|
|
339
|
+
mid-song doesn't lose or duplicate it; a song heard through and played
|
|
340
|
+
again is listed twice, once per play.
|
|
341
|
+
|
|
342
|
+
- Click a song to open it in Apple Music — in the browser it played in,
|
|
343
|
+
while that one is still open; a search when the song's page isn't
|
|
344
|
+
known.
|
|
345
|
+
- Right-click to copy artist and title or to remove the song from the
|
|
346
|
+
list; *Clear history…* empties it.
|
|
347
|
+
- From ten songs on there's a search over title, artist and album —
|
|
348
|
+
blind to case and accents, found words marked — and with more than
|
|
349
|
+
one source a filter by source. <kbd>Ctrl</kbd>+<kbd>F</kbd> jumps into
|
|
350
|
+
the search, <kbd>Esc</kbd> clears it.
|
|
351
|
+
- The window opens at the size you last left it.
|
|
352
|
+
|
|
353
|
+
The list stays on your machine (`history.json`, readable only by you)
|
|
354
|
+
and is never sent anywhere, so the privacy mode doesn't affect it.
|
|
355
|
+
*Settings → History* turns it off — which deletes the file — or changes
|
|
356
|
+
how many songs it keeps.
|
|
357
|
+
|
|
307
358
|
## Notifications
|
|
308
359
|
|
|
309
360
|
When a track changes, Refrain fires a desktop notification with the album
|
|
@@ -355,7 +406,9 @@ A track is scrobbled once you've played at least half of it, or four
|
|
|
355
406
|
minutes (Last.fm's rule), and only if it's longer than 30 s. Scrobbles
|
|
356
407
|
are queued to disk the instant they qualify, so being offline, a
|
|
357
408
|
Last.fm outage, or quitting mid-song never loses them — they submit on
|
|
358
|
-
the next opportunity.
|
|
409
|
+
the next opportunity. Restarting Refrain mid-song carries the play on
|
|
410
|
+
instead of counting it again, and a song on repeat is scrobbled once per
|
|
411
|
+
play. Privacy mode `Off` silences scrobbling too.
|
|
359
412
|
|
|
360
413
|
Full walkthrough + troubleshooting: [`docs/lastfm.md`](docs/lastfm.md).
|
|
361
414
|
|
|
@@ -387,7 +440,11 @@ entry + icon.)
|
|
|
387
440
|
|---------------|---------------------------------------------|
|
|
388
441
|
| Config | `$XDG_CONFIG_HOME/refrain/config.toml` |
|
|
389
442
|
| Scrobble queue| `$XDG_STATE_HOME/refrain/scrobble_queue.jsonl` |
|
|
443
|
+
| Scrobble in progress | `$XDG_STATE_HOME/refrain/scrobble_current.json` |
|
|
444
|
+
| Recently played | `$XDG_STATE_HOME/refrain/history.json` |
|
|
445
|
+
| Measured song lengths | `$XDG_STATE_HOME/refrain/song_lengths.txt` |
|
|
390
446
|
| Logs | `$XDG_STATE_HOME/refrain/refrain.log` (rotates) |
|
|
447
|
+
| Crash stacks | `$XDG_STATE_HOME/refrain/crash.log` (written only if Refrain crashes) |
|
|
391
448
|
| Cover cache | `$XDG_CACHE_HOME/refrain/covers/*.txt` |
|
|
392
449
|
| Autostart | `$XDG_CONFIG_HOME/autostart/refrain.desktop` (when enabled) |
|
|
393
450
|
|
|
@@ -420,7 +477,9 @@ directly to that provider:
|
|
|
420
477
|
your **OS keyring** (encrypted at rest), never in `config.toml`.
|
|
421
478
|
|
|
422
479
|
`Privacy → Off` is the global kill switch (no Discord status, no
|
|
423
|
-
scrobbling) while keeping the tray + controls running.
|
|
480
|
+
scrobbling) while keeping the tray + controls running. The *Recently
|
|
481
|
+
played* list never leaves your machine, so it has its own switch in
|
|
482
|
+
*Settings → History* instead.
|
|
424
483
|
|
|
425
484
|
Full data-flow, retention and erasure details — written to GDPR
|
|
426
485
|
transparency expectations — are in [`PRIVACY.md`](PRIVACY.md).
|
|
@@ -37,8 +37,12 @@ receiving service's own privacy policy and your account there.
|
|
|
37
37
|
|---|---|---|
|
|
38
38
|
| Now-playing metadata (title, artist, album, position) read from your media player via D-Bus / BlueZ AVRCP | Drive the tray, notifications, the published MPRIS player | In memory; reflected in the tray |
|
|
39
39
|
| Listening history lines (track changes, scrobble events) | Diagnostics / live log | `$XDG_STATE_HOME/refrain/refrain.log` — rotating, ≤ 4 × 1 MB, plaintext, local only |
|
|
40
|
+
| Start stamps, and Python stack traces if Refrain ever crashes | Diagnosing crashes inside Qt or D-Bus | `$XDG_STATE_HOME/refrain/crash.log` — ≤ 256 KB, then started afresh; written **owner-only (`0600`)**; local only |
|
|
40
41
|
| Album-cover images + iTunes URL/duration cache | Avoid re-fetching covers | `$XDG_CACHE_HOME/refrain/` — capped (default 200), oldest pruned |
|
|
41
|
-
| Pending scrobbles (artist/track/album/timestamp) | Survive offline / restart until submitted to Last.fm | `$XDG_STATE_HOME/refrain/scrobble_queue.jsonl` — capped at 1000, `0600`
|
|
42
|
+
| Pending scrobbles (artist/track/album/timestamp, and the Last.fm username they were heard under) | Survive offline / restart until submitted to Last.fm — and never to another account | `$XDG_STATE_HOME/refrain/scrobble_queue.jsonl` — capped at 1000, written **owner-only (`0600`)**, removed once submitted |
|
|
43
|
+
| The song being scrobbled right now (artist/track/album, when it began, how much of it was heard, where the player had it) | Carry that play on across a restart of Refrain instead of counting it — or scrobbling it — twice | `$XDG_STATE_HOME/refrain/scrobble_current.json` — one song, written **owner-only (`0600`)**, removed when the song ends; only while Last.fm scrobbling is on |
|
|
44
|
+
| Recently played (title, artist, album, cover URL, source + browser/device name, start time, length, whether it was scrobbled) | The *Recently played* window | `$XDG_STATE_HOME/refrain/history.json` — the last 30 songs by default (10–100), written **owner-only (`0600`)**; *Settings → History* turns it off and deletes the file |
|
|
45
|
+
| How long a song ran, under a hash of its title, artist and album | Give songs the iTunes catalog doesn't know a length, so they can still be scrobbled | `$XDG_STATE_HOME/refrain/song_lengths.txt` — at most 1000 songs, written **owner-only (`0600`)**; holds **no titles**, only hashes and seconds |
|
|
42
46
|
| Preferences + the *public* Discord/Last.fm application IDs | Your settings | `$XDG_CONFIG_HOME/refrain/config.toml` — written **owner-only (`0600`)**; contains **no secrets** |
|
|
43
47
|
| Last.fm **shared secret** and **session token** | Authenticate scrobbling | **OS keyring** (KWallet / GNOME Keyring), encrypted at rest; or, only if no keyring exists, a `0600` owner-only `secrets.json`. **Never** in `config.toml`; never logged |
|
|
44
48
|
|
|
@@ -24,6 +24,8 @@ streaming from your phone over Bluetooth.
|
|
|
24
24
|
- Optionally **scrobbles to Last.fm** alongside Discord (opt-in, with a
|
|
25
25
|
crash-safe offline queue).
|
|
26
26
|
- Lives in your **system tray** with Play/Pause/Next/Previous controls.
|
|
27
|
+
- Keeps a **recently played** list — cover, time, source — of the last
|
|
28
|
+
30 songs by default, on your machine only.
|
|
27
29
|
- Provides a **settings window** (PySide6) for everything users typically want
|
|
28
30
|
to tweak — privacy mode, sources, autostart, Bluetooth device picker.
|
|
29
31
|
|
|
@@ -178,6 +180,12 @@ scrobble_now_playing = true # also send the ephemeral "now playing" indic
|
|
|
178
180
|
# NOTE: the Last.fm shared secret and session key are credentials and
|
|
179
181
|
# are deliberately NOT stored here. They live in your OS keyring
|
|
180
182
|
# (KWallet / GNOME Keyring), encrypted at rest — see "Last.fm" below.
|
|
183
|
+
|
|
184
|
+
[history]
|
|
185
|
+
enabled = true # the "Recently played" list; false also deletes what it stored
|
|
186
|
+
max_entries = 30 # songs kept, 1–100 (Settings offers 10, 20, 30, 50, 75, 100)
|
|
187
|
+
window_width = 0 # the history window's size when last closed; 0 = default
|
|
188
|
+
window_height = 0
|
|
181
189
|
```
|
|
182
190
|
|
|
183
191
|
Per-source `client_id_*` fields let Apple Music render under one Discord
|
|
@@ -190,7 +198,10 @@ another (with a generic Bluetooth glyph). Empty falls back to the default
|
|
|
190
198
|
Refrain publishes itself as `org.mpris.MediaPlayer2.refrain` on the session
|
|
191
199
|
bus, so KDE Plasma's panel media-controls applet (and KDE Connect, GNOME
|
|
192
200
|
Shell, Mako, …) drive the same Play/Pause/Next/Previous as the tray and
|
|
193
|
-
render the same track Discord renders.
|
|
201
|
+
render the same track Discord renders. Plasma also offers these controls
|
|
202
|
+
when you right-click Refrain in the task manager, including a *Stop*
|
|
203
|
+
that it adds for every player — Apple Music has no stop, so there it
|
|
204
|
+
pauses, and does nothing when the music is already paused.
|
|
194
205
|
|
|
195
206
|
## Tray
|
|
196
207
|
|
|
@@ -214,6 +225,7 @@ no unicode-glyph prefixes.
|
|
|
214
225
|
| Play / Pause | Toggle on the active source (label follows playback state)|
|
|
215
226
|
| Next | Skip forward on the active source |
|
|
216
227
|
| Update available — vX.Y.Z | Only visible when a newer release exists |
|
|
228
|
+
| Recently played… | Open the history window (hidden while the history is off) |
|
|
217
229
|
| Settings… | Open the settings window |
|
|
218
230
|
| Live log… | Open the live-log window |
|
|
219
231
|
| Restart Refrain | Cleanly stop and re-launch (release D-Bus name + RPC, exec the same binary) |
|
|
@@ -250,26 +262,65 @@ the background.
|
|
|
250
262
|
<img src="docs/screenshots/settings-lastfm.png" alt="Settings — Last.fm" width="420"/>
|
|
251
263
|
<br/><sub>Opt-in scrobbling, API key + secret, connect / disconnect an account</sub>
|
|
252
264
|
</td>
|
|
265
|
+
<td align="center">
|
|
266
|
+
<b>History</b><br/>
|
|
267
|
+
<img src="docs/screenshots/settings-history.png" alt="Settings — History" width="420"/>
|
|
268
|
+
<br/><sub>Recently played on or off, how many songs to keep</sub>
|
|
269
|
+
</td>
|
|
270
|
+
</tr>
|
|
271
|
+
<tr>
|
|
253
272
|
<td align="center">
|
|
254
273
|
<b>Updates</b><br/>
|
|
255
274
|
<img src="docs/screenshots/settings-updates.png" alt="Settings — Updates" width="420"/>
|
|
256
275
|
<br/><sub>Auto-check, last-checked, manual <i>Check for updates now</i></sub>
|
|
257
276
|
</td>
|
|
258
|
-
</tr>
|
|
259
|
-
<tr>
|
|
260
277
|
<td align="center">
|
|
261
278
|
<b>Advanced</b><br/>
|
|
262
279
|
<img src="docs/screenshots/settings-advanced.png" alt="Settings — Advanced" width="420"/>
|
|
263
280
|
<br/><sub>Poll interval, notification delay, cover cache size, language, log level, restart, reset, uninstall</sub>
|
|
264
281
|
</td>
|
|
282
|
+
</tr>
|
|
283
|
+
<tr>
|
|
265
284
|
<td align="center">
|
|
266
285
|
<b>Legal</b><br/>
|
|
267
286
|
<img src="docs/screenshots/legal.png" alt="Legal notice" width="420"/>
|
|
268
287
|
<br/><sub>Licence, trademark and affiliation notices — the <i>Legal</i> button in the footer</sub>
|
|
269
288
|
</td>
|
|
289
|
+
<td></td>
|
|
270
290
|
</tr>
|
|
271
291
|
</table>
|
|
272
292
|
|
|
293
|
+
## Recently played
|
|
294
|
+
|
|
295
|
+
<p align="center">
|
|
296
|
+
<img src="docs/screenshots/history.png" alt="Recently played" width="520"/>
|
|
297
|
+
</p>
|
|
298
|
+
|
|
299
|
+
*Tray → Recently played…* lists the last songs Refrain saw — 30 by
|
|
300
|
+
default, up to 100 — with cover, length, when each started, where it
|
|
301
|
+
came from (which browser, which Bluetooth device) and whether it went to
|
|
302
|
+
Last.fm. A song shows up the moment it plays and stays once it counts
|
|
303
|
+
as listened: half its length or four minutes, the rule Last.fm uses, so
|
|
304
|
+
skipping through a playlist leaves nothing behind. Restarting Refrain
|
|
305
|
+
mid-song doesn't lose or duplicate it; a song heard through and played
|
|
306
|
+
again is listed twice, once per play.
|
|
307
|
+
|
|
308
|
+
- Click a song to open it in Apple Music — in the browser it played in,
|
|
309
|
+
while that one is still open; a search when the song's page isn't
|
|
310
|
+
known.
|
|
311
|
+
- Right-click to copy artist and title or to remove the song from the
|
|
312
|
+
list; *Clear history…* empties it.
|
|
313
|
+
- From ten songs on there's a search over title, artist and album —
|
|
314
|
+
blind to case and accents, found words marked — and with more than
|
|
315
|
+
one source a filter by source. <kbd>Ctrl</kbd>+<kbd>F</kbd> jumps into
|
|
316
|
+
the search, <kbd>Esc</kbd> clears it.
|
|
317
|
+
- The window opens at the size you last left it.
|
|
318
|
+
|
|
319
|
+
The list stays on your machine (`history.json`, readable only by you)
|
|
320
|
+
and is never sent anywhere, so the privacy mode doesn't affect it.
|
|
321
|
+
*Settings → History* turns it off — which deletes the file — or changes
|
|
322
|
+
how many songs it keeps.
|
|
323
|
+
|
|
273
324
|
## Notifications
|
|
274
325
|
|
|
275
326
|
When a track changes, Refrain fires a desktop notification with the album
|
|
@@ -321,7 +372,9 @@ A track is scrobbled once you've played at least half of it, or four
|
|
|
321
372
|
minutes (Last.fm's rule), and only if it's longer than 30 s. Scrobbles
|
|
322
373
|
are queued to disk the instant they qualify, so being offline, a
|
|
323
374
|
Last.fm outage, or quitting mid-song never loses them — they submit on
|
|
324
|
-
the next opportunity.
|
|
375
|
+
the next opportunity. Restarting Refrain mid-song carries the play on
|
|
376
|
+
instead of counting it again, and a song on repeat is scrobbled once per
|
|
377
|
+
play. Privacy mode `Off` silences scrobbling too.
|
|
325
378
|
|
|
326
379
|
Full walkthrough + troubleshooting: [`docs/lastfm.md`](docs/lastfm.md).
|
|
327
380
|
|
|
@@ -353,7 +406,11 @@ entry + icon.)
|
|
|
353
406
|
|---------------|---------------------------------------------|
|
|
354
407
|
| Config | `$XDG_CONFIG_HOME/refrain/config.toml` |
|
|
355
408
|
| Scrobble queue| `$XDG_STATE_HOME/refrain/scrobble_queue.jsonl` |
|
|
409
|
+
| Scrobble in progress | `$XDG_STATE_HOME/refrain/scrobble_current.json` |
|
|
410
|
+
| Recently played | `$XDG_STATE_HOME/refrain/history.json` |
|
|
411
|
+
| Measured song lengths | `$XDG_STATE_HOME/refrain/song_lengths.txt` |
|
|
356
412
|
| Logs | `$XDG_STATE_HOME/refrain/refrain.log` (rotates) |
|
|
413
|
+
| Crash stacks | `$XDG_STATE_HOME/refrain/crash.log` (written only if Refrain crashes) |
|
|
357
414
|
| Cover cache | `$XDG_CACHE_HOME/refrain/covers/*.txt` |
|
|
358
415
|
| Autostart | `$XDG_CONFIG_HOME/autostart/refrain.desktop` (when enabled) |
|
|
359
416
|
|
|
@@ -386,7 +443,9 @@ directly to that provider:
|
|
|
386
443
|
your **OS keyring** (encrypted at rest), never in `config.toml`.
|
|
387
444
|
|
|
388
445
|
`Privacy → Off` is the global kill switch (no Discord status, no
|
|
389
|
-
scrobbling) while keeping the tray + controls running.
|
|
446
|
+
scrobbling) while keeping the tray + controls running. The *Recently
|
|
447
|
+
played* list never leaves your machine, so it has its own switch in
|
|
448
|
+
*Settings → History* instead.
|
|
390
449
|
|
|
391
450
|
Full data-flow, retention and erasure details — written to GDPR
|
|
392
451
|
transparency expectations — are in [`PRIVACY.md`](PRIVACY.md).
|
|
@@ -536,16 +536,45 @@ were tracked internally as "v0.2.8" but never separately tagged.
|
|
|
536
536
|
connected"; the Connect button no longer treats an incomplete
|
|
537
537
|
leftover as Disconnect. +13 tests (pure + offscreen).
|
|
538
538
|
|
|
539
|
+
## Done — v0.5.1
|
|
540
|
+
|
|
541
|
+
- **Recently played.** A history window (tray → *Recently played…*)
|
|
542
|
+
with cover, length, start time, source and Last.fm state per song;
|
|
543
|
+
Last.fm's rule for what counts; a click opens the song in Apple
|
|
544
|
+
Music in the browser that played it; search and a source filter;
|
|
545
|
+
remove single songs; survives restarts and crashes mid-song without
|
|
546
|
+
losing or duplicating the song. Own settings tab: on/off (off
|
|
547
|
+
deletes) and 10–100 songs.
|
|
548
|
+
- **The crash behind "Refrain was simply gone".** Two threads shared
|
|
549
|
+
one dbus-glib connection; the daemon's sources now keep private
|
|
550
|
+
connections, the published MPRIS player is only touched from its
|
|
551
|
+
dispatching thread, and `crash.log` records every thread's Python
|
|
552
|
+
stack if anything still crashes.
|
|
553
|
+
- **Cover and length for far more songs.** The catalog lookup asks the
|
|
554
|
+
local store first, strips "feat." and remaster tags, falls back to
|
|
555
|
+
the first artist, checks artist *and* title, retries network
|
|
556
|
+
failures and lets misses expire.
|
|
557
|
+
- **Plasma's Stop no longer starts playback**, and Play/Pause only act
|
|
558
|
+
towards the state they ask for.
|
|
559
|
+
- **One play, one row, one scrobble.** A song on repeat counts once per
|
|
560
|
+
play — in the browser, over Bluetooth, and on any player that jumps
|
|
561
|
+
back to the start. A restart or a crash mid-song carries the play on,
|
|
562
|
+
for the history and Last.fm alike (`scrobble_current.json`), and
|
|
563
|
+
Settings → Apply no longer starts a song's Last.fm count over.
|
|
564
|
+
- **Pausing in the browser is seen as a pause.** Plasma's browser
|
|
565
|
+
integration follows Apple Music's looping artwork video, not the
|
|
566
|
+
music; the tab's own MPRIS entry now decides playing or paused and
|
|
567
|
+
takes Play/Pause. Its segment positions are no longer shown, which
|
|
568
|
+
stops the elapsed time falling back every ten seconds.
|
|
569
|
+
- **Every shipped translation complete**, plural forms included
|
|
570
|
+
(English now carries its own plural catalog).
|
|
571
|
+
|
|
539
572
|
## Up next — v0.5.x
|
|
540
573
|
|
|
541
574
|
- **Stable-release AUR build** that doesn't rely on the GitHub
|
|
542
575
|
release tarball — switch to a `git`-source PKGBUILD pinned to the
|
|
543
576
|
signed tag, so AUR users get the exact same commit the release
|
|
544
577
|
workflow ships.
|
|
545
|
-
- **Complete the translation catalogs** for the newly-wrapped
|
|
546
|
-
strings: regenerate the `.ts` / `.qm` files and bring every
|
|
547
|
-
shipped language back to 100% coverage via community PRs (the
|
|
548
|
-
code-side `tr()` wrapping landed in v0.3.0).
|
|
549
578
|
|
|
550
579
|
## Maybe — v0.5+
|
|
551
580
|
|