A command-line tool that generates Tidal playlists with recommended tracks using Last.fm similarity data or Google Gemini AI, seeded from your favorite tracks or a specific song.
-
Clone the repository:
git clone https://github.com/Certezalito/tidal-discovery-engine.git && cd tidal-discovery-engine
-
Create a virtual environment and install dependencies:
uv venv && uv pip install -e .
-
Create a
.envfile in the project root with your API keys:LASTFM_API_KEY=your_lastfm_api_key GEMINI_API_KEY=your_gemini_api_key GEMINI_MODEL= GEMINI_FALLBACK_MODEL=- Get a Last.fm API key from the Last.fm API account page.
- Get a Gemini API key from Google AI Studio. Required only if you use
--gemini. GEMINI_MODELis optional. Resolution order: exported environment variable →.envvalue → built-in default.GEMINI_FALLBACK_MODELis optional. Used only when the primary model is unavailable or not found.
-
Run the script once interactively to authenticate with Tidal:
uv run python -m src.cli.main --playlist-name "Test"This creates a
tidal_session.jsonfile in the project root, which is reused for all future non-interactive runs.
The CLI supports three modes of operation. Each mode can be combined with --shuffle to change recommendation behavior, and with --folder to organize playlists into a Tidal folder.
Quick start — generate a playlist from your Tidal favorites using Last.fm recommendations:
uv run python -m src.cli.main --playlist-name "TDE {date}"Selects random tracks from your Tidal favorites and finds similar music through Last.fm.
Without --shuffle — returns the top similar tracks per seed:
uv run python -m src.cli.main --num-tidal-tracks 5 --num-similar-tracks 10 --playlist-name "TDE {date}" --folder "Tidal Discovery Engine"Expected outcome: A playlist with up to 50 tracks (5 seeds × 10 similar each) of popular similar music.
With --shuffle — fetches a large pool (up to 1000 per seed) and randomly selects from it for deeper cuts:
uv run python -m src.cli.main --shuffle --num-tidal-tracks 5 --num-similar-tracks 10 --playlist-name "TDE {date}" --folder "Tidal Discovery Engine"Expected outcome: A playlist with up to 50 tracks randomly drawn from a much larger pool, producing more varied and unexpected recommendations.
Uses Google Gemini AI instead of Last.fm to generate recommendations from your Tidal favorites.
Without --shuffle — generates popular, highly relevant suggestions:
uv run python -m src.cli.main --gemini --playlist-name "TDE Gemini Hits" --folder "Tidal Discovery Engine"Expected outcome: A playlist of AI-curated tracks that are well-known and closely related to your favorites.
With --shuffle — generates deep cuts, underground, and lesser-known tracks:
uv run python -m src.cli.main --gemini --shuffle --playlist-name "TDE Gemini Underground" --folder "Tidal Discovery Engine"Expected outcome: A playlist of lesser-known, underground tracks selected by AI for a more adventurous listening experience.
Runtime model override — temporarily use a different Gemini model:
export GEMINI_MODEL=gemini-2.0-flash
uv run python -m src.cli.main --gemini --playlist-name "TDE Gemini Override" --folder "Tidal Discovery Engine"Model configuration notes:
- Missing or blank
GEMINI_MODELlogs one warning per run and uses the built-in default model. - Fallback is attempted only when the primary model is unavailable or not found and
GEMINI_FALLBACK_MODELis configured. - Auth, quota, and permission failures do not trigger fallback.
Generates a playlist based on one specific song. Both --artist and --track must be provided together.
Last.fm path:
uv run python -m src.cli.main --artist "Lost Tribe" --track "Gamemaster" --num-similar-tracks 1000 --playlist-name "Gamemaster Vibes" --folder "Tidal Discovery Engine"Expected outcome: A playlist of up to 1000 tracks similar to "Gamemaster" by Lost Tribe, sourced via Last.fm.
Gemini path:
uv run python -m src.cli.main --artist "Lost Tribe" --track "Gamemaster" --gemini --num-similar-tracks 20 --playlist-name "Gamemaster Gemini" --folder "Tidal Discovery Engine"Expected outcome: A playlist of 20 AI-recommended tracks based on "Gamemaster" by Lost Tribe.
Gemini deep cuts path:
uv run python -m src.cli.main --artist "Lost Tribe" --track "Gamemaster" --gemini --shuffle --num-similar-tracks 20 --playlist-name "Gamemaster Gemini Deep Cuts" --folder "Tidal Discovery Engine"Expected outcome: A playlist of 20 underground, lesser-known tracks inspired by "Gamemaster" by Lost Tribe.
Mode 3 notes:
--artistand--trackmust be provided together. Providing only one produces an error.--num-similar-tracksmust be a positive integer.- If the Gemini model is unavailable or not found in Mode 3, the CLI falls back to Last.fm for the same seed.
- Auth, quota, and permission Gemini failures do not trigger fallback and return actionable errors.
- Tidal tracks that cannot be resolved are skipped with a warning showing the skipped count and a preview of up to 5 names.
- If zero tracks can be inserted after resolution, the run fails.
| Option | Description | Default | Required |
|---|---|---|---|
--num-tidal-tracks |
Number of random favorite tracks to select as seeds. Used in Mode 1 and Mode 2. | 10 |
No |
--num-similar-tracks |
Number of similar tracks to retrieve per seed. Used in all modes. | 5 |
No |
--gemini |
Use Gemini AI for recommendations instead of Last.fm. Activates Mode 2, or Mode 3 when combined with --artist/--track. Requires GEMINI_API_KEY. |
False |
No |
--shuffle |
Changes recommendation behavior. Last.fm: fetches a large pool and randomly selects. Gemini: requests deep cuts and underground tracks. Used in all modes. | False |
No |
--artist |
Artist name for single-seed mode (Mode 3). Must be used with --track. |
— | No |
--track |
Track title for single-seed mode (Mode 3). Must be used with --artist. |
— | No |
--playlist-name |
Name for the new Tidal playlist. Use {date} to insert the current date (YYYYMMDD format). |
— | Yes |
--folder |
Tidal folder to place the playlist in. Created automatically if it does not exist. | — | No |
Constraints:
--artistand--trackmust be provided together. Providing only one produces an error.--num-similar-tracksmust be a positive integer.--geminirequires theGEMINI_API_KEYenvironment variable to be set.
Symptoms: The CLI logs an error mentioning the model name and a "not found" or "unavailable" failure category.
Explanation: The configured GEMINI_MODEL (or the built-in default) does not exist or is temporarily unavailable in the Gemini API. This is distinct from auth or quota errors — it means the specific model cannot be reached.
Corrective action:
- Check the value of
GEMINI_MODELin your.envfile or exported environment variable. Verify the model name is valid and currently available. - If you have
GEMINI_FALLBACK_MODELconfigured, the CLI will automatically attempt to use it. Verify that the fallback model name is also valid. - If neither model works, remove the
--geminiflag to use Last.fm recommendations instead. - In Mode 3, the CLI automatically falls back to Last.fm when the Gemini model is unavailable — no manual action is needed.
Symptoms: The CLI exits with an error: "Both --artist and --track are required together for single-seed mode."
Explanation: Mode 3 requires both --artist and --track to identify the seed song. Providing only one of them is invalid.
Corrective action: Provide both options together:
uv run python -m src.cli.main --artist "Lost Tribe" --track "Gamemaster" --num-similar-tracks 20 --playlist-name "Seed Playlist"Symptoms: The CLI logs an error with a failure category of "auth", "permission", or "quota" — typically from the Gemini API or Tidal session.
Explanation: These errors indicate your API key is invalid, expired, or has exceeded its usage quota. Auth and quota errors do not trigger the Gemini fallback mechanism — they are treated as unrecoverable for the current run.
Corrective action:
-
Gemini auth/quota: Verify
GEMINI_API_KEYin your.envfile is correct and active. Check your Google AI Studio dashboard for quota status. -
Tidal session expired: Delete
tidal_session.jsonand run the script interactively once to re-authenticate:rm tidal_session.json uv run python -m src.cli.main --playlist-name "Re-auth" -
Retry the original command after resolving the credential or quota issue.
You can schedule the script to run periodically using cron on Unix-like systems or Task Scheduler on Windows.
cron is a task scheduler for Unix-like systems. To set up a daily job:
-
Open your crontab file:
crontab -e
-
Add a schedule line. Replace
/path/to/tidal-discovery-enginewith the absolute path to your project folder (runpwdfrom your project directory to get it):0 8 * * * cd /path/to/tidal-discovery-engine && /home/username/.local/bin/uv run python -m src.cli.main --num-tidal-tracks 5 --num-similar-tracks 10 --playlist-name "TDE {date}"
This example runs the script every day at 8:00 AM.
-
Save and close the file. The new schedule is active immediately.
- Open Task Scheduler.
- Create a new task.
- Set a trigger for when you want the task to run.
- Set the action to "Start a program".
- Set the program/script to your Python executable inside the
.venvfolder (e.g.,C:\path\to\tidal-discovery-engine\.venv\Scripts\python.exe). - Set the arguments to
-m src.cli.main --playlist-name "Daily Discovery". - Set the "Start in" directory to the root of the project (e.g.,
C:\path\to\tidal-discovery-engine).
This project was developed using a unique, AI-driven workflow inside Visual Studio Code, leveraging the Spec Kit methodology. The entire process, from initial idea to final implementation, was guided by a series of structured prompts and automated scripts.
The primary tools used were:
- GitHub Copilot: Used as the core AI assistant for generating and refining code.
- Gemini 2.5/3.0 Pro: Integrated for advanced reasoning and to help guide the development process. Followup branches could be any AI available within GitHub Copilot
- Spec Kit: A set of prompts and scripts that enforce a rigorous, specification-driven development process. This included generating a constitution, a detailed specification, a project plan, and a task list before any code was written.
This approach ensured that the project was well-defined, robust, and implemented efficiently, with the AI agents handling the heavy lifting of code generation and iteration.
-
APIs
- Last.fm API - Used for finding similar tracks.
- Tidal API - Used for accessing user favorites and creating playlists.
-
Libraries