myLastFmPlayer is a (Linux) desktop application for collecting a user's loved tracks from Last.fm, resolving them via YouTube, downloading audio, and playing tracks locally. Written in Python with PyQt6. The full workflow is implemented and in active daily use — this is a working product, not a proof of concept.
Author: Marcel Petrick mail@marcelpetrick.it
License: GPLv3 or later. See LICENSE.
Note: projected is generated with AI.
Current version: 0.0.136 — work in progress (WIP), but past MVP and actively used
Click the preview to open the full recording.
This project uses a SemVer-style base version:
MAJOR.MINOR.PATCH
The version is written without leading zero padding. The first version was 0.0.1.
The single source of truth is my_lastfm_player/version.py; Python package metadata reads
the same __version__ value through pyproject.toml.
MAJOR: incompatible or breaking changes.MINOR: backwards-compatible feature additions.PATCH: fixes, documentation, tooling, and other incremental changes.
For this project, every future commit should increase the PATCH number unless the change intentionally requires a MINOR or MAJOR bump.
Built packages can show a build suffix in user-facing locations such as the startup
line and window title. The suffix is the first six digits of the git commit hash,
generated at package build time into my_lastfm_player/_build_info.py. Source-tree
development runs show only the base version when that generated metadata is absent.
- Linux x86_64
- Python 3.12 or newer
venvsupport for Pythonyt-dlpffmpeg
On Manjaro:
sudo pacman -S yt-dlp ffmpegCreate the virtual environment:
python3 -m venv .venvActivate it:
source .venv/bin/activateInstall the app in editable mode:
python -m pip install --upgrade pip
python -m pip install -e .Run the app:
my-lastfm-playerAlternatively:
python -m my_lastfm_playerThe app ships with Last.fm desktop application credentials so loved-track fetching and scrobbling can work without every user registering a separate Last.fm API account:
- API key:
d36dce7154716e08a1d2907b7badadf7 - Shared secret:
ed22747b03cabe49ab93f7215afc06fc
These are application credentials, not a user's Last.fm password or session key. Public loved-track fetching uses the API key only. Last.fm's desktop authentication flow for scrobbling also requires the shared secret to sign the app's requests, while the per-user session key is created only after the user approves access in the browser. For desktop apps, these app credentials cannot be kept confidential from users of the binary or source; larger open-source music clients such as Strawberry follow the same practical model by shipping a shared Last.fm API key for all users.
Advanced users and downstream packages can override the bundled credentials without patching source:
LASTFM_API_KEY=your_key LASTFM_API_SECRET=your_secret my-lastfm-playerStart the application, enter a Last.fm username, and press Fetch. The app loads that user's public loved-track list from the Last.fm Web API, stores it locally, resolves the tracks through yt-dlp, and starts downloading playable MP3 files into the local downloads directory.
The normal workflow is:
- Enter a Last.fm username.
- Press Fetch.
- Wait while the loved-track list is fetched and shown in the table.
- The app automatically starts YouTube lookup for the fetched tracks.
- The app automatically starts the download queue for resolved tracks.
- Select a downloaded track and press Play.
flowchart TD
A["Enter Last.fm username"] --> B["Press Fetch"]
B --> C["Fetch public loved-track list from Last.fm"]
C --> D["Show tracks in the table"]
D --> E["Resolve tracks through yt-dlp search"]
E --> F["Queue resolved tracks for download"]
F --> G["Download MP3 files"]
G --> H["Select downloaded track"]
H --> I["Press Play"]
I --> J["Play local MP3"]
If you press Play on a track that is not downloaded yet, the app prepares that selection first. It prioritizes the selected track, resolves its YouTube URL if needed, downloads only that track first, and then starts playback when the local file is ready.
flowchart TD
A["Select track"] --> B["Press Play"]
B --> C{"Already downloaded?"}
C -->|yes| D["Play local MP3"]
C -->|no| E{"YouTube URL known?"}
E -->|no| F["Priority lookup for selected track"]
E -->|yes| G["Priority download for selected track"]
F --> G
G --> H["Store downloaded MP3 path"]
H --> D
Progress and errors are shown in the status bar at the bottom of the window and in the feedback area. The terminal also prints detailed logging when the app is started from localPipeline.sh or from a shell.
========== Local Pipeline Summary ==========
Virtualenv : PASS .venv is available
Dependencies : PASS Editable install with dev dependencies completed
Ruff : PASS 0 violations
Pylint : PASS 10.00/10 (100%)
Translations : PASS de: 214 strings, 0 untranslated; hr: 214 strings, 0 untranslated; ...
Docs : PASS required docs present
Sphinx : PASS HTML built with 0 warnings
Tests+Coverage : PASS 99.06%; 450 passed, 1 skipped in 3.44s
Open Docs : PASS Sphinx index.html was handed to firefox
Open Coverage : PASS htmlcov/index.html was handed to firefox
Clean Build : PASS Stale package artifacts removed
Package Build : PASS Successfully built source and wheel distributions
Wheel : PASS my_lastfm_player-<version>-py3-none-any.whl
Wheel Install : PASS Built wheel installed into .venv
Import Check : PASS <version>[+commit]
Launch App : PASS my-lastfm-player was started once
============================================
By default, downloaded MP3 files are stored here:
~/.local/share/myLastFmPlayer/downloads/
Per-user track lists are stored as JSON files here:
~/.local/share/myLastFmPlayer/tracks/
The shared download cache is stored here:
~/.local/share/myLastFmPlayer/download-cache.json
The shared YouTube lookup cache is stored here:
~/.local/share/myLastFmPlayer/lookup-cache.json
If XDG_DATA_HOME is set, the base directory changes to:
$XDG_DATA_HOME/myLastFmPlayer/
For example, with XDG_DATA_HOME=/tmp/app-data, downloads are stored in:
/tmp/app-data/myLastFmPlayer/downloads/
Install development dependencies and run the full local build, lint, documentation, test, coverage, package, install verification sequence, and then start the installed application once:
./localPipeline.shThe pipeline uses .venv, creates it when missing, installs the project with development dependencies, runs Ruff, Pylint (10.00/10 required), checks translations (0 untranslated strings required), checks required documentation, builds Sphinx documentation into docs/_build/html, runs pytest with coverage, opens the generated HTML reports when possible, builds the package, installs the built wheel, verifies the package can be imported, and then starts my-lastfm-player like a user would. Without --noRun, the app is started once and the launch stage is marked successful after the process is handed off.
localPipeline.sh is the canonical local build workflow. It records each stage as PASS, FAIL, SKIP, or WARN and prints the complete summary at the end, so a developer can see the build state in one place.
flowchart TD
A["Start ./localPipeline.sh"] --> B{"--noRun provided?"}
B -->|yes| C["Set RUN_APP=false"]
B -->|no| D["Keep RUN_APP=true"]
C --> E{"Is .venv/bin/python executable?"}
D --> E
E -->|no| F["Create .venv with python3 -m venv"]
E -->|yes| G["Use existing .venv"]
F --> H["Install project in editable mode with dev dependencies"]
G --> H
H --> I["Ruff lint check"]
I --> J["Pylint static analysis"]
J --> K["Check Qt translation completeness"]
K --> L["Documentation check"]
L --> M["Build Sphinx docs with warnings as errors"]
M --> N["Pytest with coverage and HTML report"]
N --> O["Open Sphinx and coverage HTML when possible"]
O --> P["Remove old build artifacts"]
P --> Q["Build source/wheel distributions"]
Q --> R["Find built wheel in dist/"]
R --> S["Force reinstall built wheel without dependencies"]
S --> T["Import package and print version"]
T --> U{"RUN_APP=true?"}
U -->|yes| V["Start installed my-lastfm-player once"]
U -->|no| W["Skip GUI startup"]
V --> X["Print stage summary and exit"]
W --> X
The workflow phases are:
- Argument handling: accepts
--noRunand--report-dir PATH; any other argument stops the pipeline with usage help. - Environment preparation: creates
.venvonly when.venv/bin/pythonis missing, otherwise reuses the existing virtual environment. - Dependency installation: runs
python -m pip install -e ".[dev]"so the app and development tools come from the same environment. - Quality gates: runs Ruff (0 violations), Pylint (10.00/10), translations (0 untranslated strings across all locales), required documentation checks, Sphinx documentation with warnings as errors, and pytest with configured coverage reporting (99% minimum).
- Report opening: prints the Sphinx and coverage HTML paths and tries to open them with
MY_LASTFM_PLAYER_REPORT_BROWSERwhen set, otherwisefirefox, otherwisexdg-open, otherwiseopen; a failed auto-open is reported asWARN, not as a failed build. - Package build: removes stale package
build/,dist/, and egg-info output before runningpython -m build; generated Sphinx HTML indocs/_build/htmlis kept usable after the package build. - Install verification: installs the freshly built wheel and imports
my_lastfm_playerto confirm the packaged application exposes its version. - Runtime launch: starts
my-lastfm-playeronce unless--noRunwas provided; quitting the app does not make the pipeline reopen it. - Final summary: prints a stage-by-stage table so the developer can see which parts passed, failed, were skipped, or only produced warnings.
To run every check without launching the GUI at the end:
./localPipeline.sh --noRunTo preserve the machine-readable test and coverage results, stage logs, environment metadata, and final summary:
./localPipeline.sh --noRun --report-dir release-artifacts/rawThe manual GitHub Release workflow uses these reports to publish seven described ZIP archives: packages, Sphinx documentation, C4 architecture, pytest results, coverage, static analysis, and the complete pipeline trace. The raw wheel and source distribution remain separate release assets for direct installation and packaging use.
After the pipeline completes, open the HTML coverage report at:
htmlcov/index.htmlAfter the pipeline completes, open the Sphinx documentation at:
docs/_build/html/index.htmlThe normal pipeline does not require internet access. To include the live Last.fm end-to-end test for the hardwired user first, run:
MY_LASTFM_PLAYER_RUN_LASTFM_E2E=1 ./localPipeline.sh --noRunThat test fetches all loved-track API pages from Last.fm for first and prints the tracks during the test run.
The UI is prepared for Qt Linguist translations. English is the source/default language, and .ts files are available for Croatian, German, Mandarin, and Ukrainian in:
my_lastfm_player/translations/
Regenerate the Qt translation source files after changing user-visible strings:
tools/update_translations.shAfter editing the .ts files with Qt Linguist or another Qt-compatible translation tool, compile runtime .qm files:
tools/compile_translations.sh
