Skip to content

Latest commit

 

History

232 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

AudioProgram

A Windows desktop app that takes a Shazam export, downloads the songs, cleans up the filenames, tags the files, and files them into your music library. Written in Python with a PyQt6 interface.

CI License: MIT Python 3.11

Why I built it

I use Shazam to catch songs I hear, and all it gives you back is a CSV file. Getting those songs into my library meant downloading each one by hand, deleting the junk out of the filename, and typing the artist and title into a tag editor. That is fine for one song and miserable for two hundred. So I wrote the whole thing as one app.

It is not a general purpose music manager. It does one job properly. A Shazam export goes in, a clean and tagged library comes out.

Warning: this tool renames and re-tags MP3 files. Back up your library before you use it the first time.

What it does

  • Downloads the songs in a Shazam CSV export straight from YouTube, using yt-dlp and FFmpeg.
  • Keeps a manual My List for tracks that never went through Shazam.
  • Strips junk out of filenames, things like [Official Video] and site watermarks.
  • Writes ID3 tags in a batch: artist, album artist, and title.
  • Downloads land in a staging folder first so you can look at them before they move into your library.
  • Lets you review staged files before the move. You can fix the artist or title, pick a different source video, or download it again.
  • Runs the whole pipeline with one button (download, clean and tag, move), or runs any step on its own.
  • Queues failed downloads and retries them on the next run.
  • Scans the library for duplicates, untagged files, suspicious names, and stray files that are not MP3s.
  • Undoes the last batch of renames and moves.

Engineering notes

The parts I would want to talk about in a code review.

Nothing writes to the library until you say so. Downloads and cleaning happen in a staging folder, and moving to the library is its own separate step. A bad download or a bad tag never reaches the real library, and the code treats the two folders as different things everywhere. It never mixes them.

Undo is built on an append only ledger. Every rename and move gets written to Config/history.jsonl as it happens, so Undo Last Batch replays real recorded operations instead of guessing what probably happened. See undo_last_batch in Core/tag_cleaner.py:331.

Failures are loud, never silent. This is the rule I care most about here. A read or a parse that turns a failure into an absence is the worst bug shape in a program that moves files around, because the caller writes the empty result back and destroys real data. An unreadable music folder must not look like an empty one. A queue file that will not decode must not be rewritten without the lines it could not read. I did a sweep for this pattern and it turned up six real bugs, all fixed. Even a fallback that cannot fail, like decoding a CSV as latin-1, announces itself, because otherwise nothing downstream can tell it apart from a clean success.

Every file replacement is atomic, and it retries. Writes go to a temp file and then get swapped in with os.replace. On Windows a replace is denied outright while anything else holds a handle on the target, and an antivirus scanner or Explorer preview will do exactly that for a moment, so the swap retries briefly instead of failing the whole run. See atomic_replace in Core/utils.py:15.

The GUI never blocks and the worker threads never touch Qt. Long jobs run in daemon threads through run_task in Core/main.py. Those threads talk to the interface only through a signal bridge, _Bridge in Core/ui.py:41, so every widget update happens back on the GUI thread where Qt requires it. Cancel is cooperative. It stops between tracks and lets the track in flight finish.

The modules stay decoupled on purpose. input_handler.py and ui.py are not allowed to import each other, and everything the user sees goes through one print_output callback instead of scattered print calls. That is what makes the logic testable without a GUI.

Dependencies are pinned exactly. The versions in requirements.txt came off a venv that was actually tested. yt-dlp especially breaks when YouTube changes, so updating it means retesting, not pip install -U.

The test suite is pure logic. Over two hundred tests, no GUI, no network, no FFmpeg, and they never touch my real config file. That is why they can run in CI on a clean Linux box in a few seconds.

Project layout

/Audio_Program/
│
├── Core/                # All the application code
│    ├── main.py         # GUI window and task orchestration
│    ├── downloader.py   # yt-dlp downloads, retry queue, My List, ledger
│    ├── tag_cleaner.py  # filename cleaner, ID3 tags, CSV parser, move to library, undo
│    ├── input_handler.py
│    ├── ui.py           # widgets, dialogs, thread safe signal bridge
│    └── utils.py        # config persistence, logging, history and ledger
│
├── Config/              # Created and managed by the app. All gitignored.
├── Setup_Scripts/       # install_setup.bat and run_audio_program.bat
├── tests/               # pytest suite
├── docs/                # open items and notes
└── requirements.txt

Config/ holds user_config.txt, the failed download queue, My List, the operation history, the download ledger, and the last run's errors. None of it is committed.

Install

Run Setup_Scripts\install_setup.bat. It finds a Python 3.11 through 3.14 interpreter, installs FFmpeg with winget (warn only, since it may already be there), creates .venv, installs the exact pins from requirements.txt, and starts the app.

If you would rather do it yourself:

winget install Gyan.FFmpeg
py -3.11 -m venv .venv
.venv\Scripts\python.exe -m pip install -r requirements.txt

Shazam CSV: export your library from shazam.com under My Library, download the CSV, and point at it in Settings. The CSV is optional. My List works without one.

Run

Double click Setup_Scripts\run_audio_program.bat, or from the project root:

.venv\Scripts\pythonw.exe Core\main.py

Use the venv interpreter. A system wide python will not have PyQt6.

Using it

On the first run, open Settings and set the Library Folder, the Staging Folder, and the CSV File Path if you are downloading from Shazam.

Actions

  • Run Full Pipeline downloads, cleans and tags, then moves, in that order.
  • Run Shazam Downloader downloads the CSV entries that are newer than the last scanned date. Anything already in your library or staging folder is skipped, and it asks before starting a large batch.
  • Clean & Tag MP3 Files renames and tags whatever is sitting in staging, which is useful for files you added yourself.
  • Download My List downloads the tracks in Config/my_list.txt.
  • Review Staged Files lets you go through staged tracks before they move. Fix the artist or title, download it again, or pick a different source.
  • Move Staged Files to Library is the last step. It is also offered automatically after a download or clean run.
  • Cancel stops the run between tracks. The track in progress finishes first.

Tools

  • Add Song puts an artist and title straight into My List.
  • My List opens Config/my_list.txt. One track per line, tab separated.
  • Failed List opens Config/failed_downloads.txt. Failed tracks retry on their own each run, and after three attempts they are held. Delete a line to drop the track for good, or set its attempt count back to 0 to try again.
  • Open Staging and Open Library open those folders in Explorer.
  • Undo Last Batch reverses the most recent set of renames and moves.
  • Library Health scans the library for duplicates, untagged files, suspicious names, and stray files that are not MP3s.

Settings

  • Library Folder, Staging Folder, and CSV File Path.
  • Song Tags List and Web Tags List, which are the junk strings stripped out of filenames.
  • Re-scan Date lets you move the last scanned date back to redownload from an earlier point. Use YYYY-MM-DD or YYYY-MM-DDTHH:MM:SS. Fractions of a second are accepted and kept.
  • Loudness Normalize on or off, applied while downloading.

Configuration

Config/user_config.txt is written by the app. Edit it through the GUI.

library_folder=C:/Users/You/Music
staging_folder=C:/Users/You/Music_Staging
csv_path=C:/Users/You/Desktop/shazam_library.csv
song_tags=(Lyrics)|(Official Video)
web_tags=[ytmp3.page] |yt5s.io -
last_scanned_date=2026-01-01T00:00:00
normalize_audio=0
ffmpeg_path=

ffmpeg_path is optional. Set it only if FFmpeg is not on your PATH.

Config/failed_downloads.txt and Config/my_list.txt are tab separated, one track per line, and # starts a comment. If a line is malformed the app tells you and leaves the file alone instead of quietly rewriting it without that line.

Testing

.venv\Scripts\python.exe -m pytest tests/ -q

Pure logic only. No GUI, no network, no FFmpeg. The tests never touch your real user_config.txt.

Dependencies

  • PyQt6 for the interface
  • eyed3 for ID3 tagging
  • yt-dlp for downloading audio from YouTube
  • FFmpeg for MP3 conversion, installed on the system and on PATH

Python 3.11 through 3.14 is supported by the setup script. The pinned dependencies are verified on 3.11. Newer versions are not blocked if you launch the app yourself.

Privacy

During setup, pip and winget talk to their package sources. During a search or a download, the artist and title queries go out through yt-dlp to YouTube, along with the video, media, and thumbnail requests that follow. The app itself has no telemetry and no update check. Your CSV, logs, configuration, queues, and library files stay on your machine.

It is worth knowing where your listening history lives locally:

  • Logs are at %LOCALAPPDATA%\AudioProgram\logs\app.log, rotating at 1 MB across three files. They contain track names, local file paths, and error details.
  • The Config folder sits next to the app inside the project folder. It is gitignored, but it holds track names and local paths, so keep that in mind before you share the folder or a checkout of the repo.

License

MIT. See LICENSE.

About

A Windows desktop app that turns a Shazam export into a clean, tagged MP3 library. It downloads the songs, fixes the filenames, writes the ID3 tags, and files everything away. Built with Python and PyQt6.

Topics

Resources

Stars

1 star

Watchers

1 watching

Forks

Releases

Packages

Contributors

Languages