Skip to main content

smclipy

simple music cli py is a Python script designed to streamline downloading, tagging, and organizing music from YouTube and SoundCloud. It allows you to batch-download audio, manually set metadata (including multiple artists, album, and cover art).

Features

  • Batch Downloading: Queue up as many YouTube or SoundCloud URLs as you want before processing. Recognizes youtube.com watch/shorts/live/embed links, youtu.be links, and soundcloud.com tracks. Unrecognizable pastes are warned about and duplicate URLs are collapsed.
  • Interactive Tagging: For each track you set the Title, Album, and Artists (with autocompletion). The title defaults from the video's title, and the artists from the channel/uploader — corrected against your known authors (ignoring case and spaces).
  • MusicBrainz Re-tagging (tag): List every song already in your library and look it up on MusicBrainz (free, no API key) to fix title, artists, album, release date, genre, track number, album artist, and cover art — reviewing each candidate and the exact diff before applying. Narrow the run with --search TEXT (title, album, or artist) or --specific FIELD (autocomplete an existing title, album, or artist).
  • Source Finding (findsource): Search YouTube and SoundCloud for songs already in your library and record the matching URL in the tracking database. Providers are tried in the order set by source_preferences (default: YouTube, then SoundCloud); by default you pick from the candidates, while --auto saves the best plausible result. A result only counts when it names the song's artist (uploader or an Artist - Title feat. Artist2 title) or repeats its title, so unrelated videos that merely share a duration are skipped. Narrow the run with --search TEXT or --specific FIELD, use --force to replace an existing source, and combine --auto --all for a headless run.
  • Playlists (playlist): Build playlists of your library songs (create/rename/delete/list/show), add/remove/move songs interactively, sort by metadata, and export them to .m3u, .m3u-link (one URL per line with artist/title labels), or .json — or import .m3u/.m3u8/.m3u-link/.json playlists by resolving the entries against your library (UUID first, then each path relative to the file, your music folder, or a unique matching filename). Playlists are stored in smclipy.db and reference songs by their tracking UUID, so renaming or relocating a song never breaks its playlists. When a JSON import leaves songs unmatched, it reports how many are missing and points to smclipy download --file, which downloads each missing song from its recorded source_url and applies the playlist metadata automatically (--auto also skips the cover-crop question). Use smclipy findsource --file playlist.json to search for and fill missing source URLs, and smclipy modify --file playlist.json to edit the exported entries by hand.
  • Video Info Preview: Before tagging, the video's title, artist/s, and the first description_max_lines lines of its description are shown for context.
  • Cover Art Cropping: Crop cover art to a perfect 1:1 square ratio during tagging, with a terminal preview of the art.
  • Cover Rescan (crop): Re-extracts covers from your existing audio files, detects pillarboxed art, lets you crop it to 1:1, and re-embeds it into the matching files. Images you decline to crop are remembered so you're not asked twice.
  • Manual Editing (modify): Edit the tags of any library song by hand — title, artists, album, release date, genre, album artist, track number, cover art, and source URL. Pick a range to bulk-apply one set of values (e.g., the songs of an album) or a single number to edit one song; a blank answer keeps a field, and typing /clear empties it. The source URL is tracking metadata stored in smclipy.db (not written into the audio file). Narrow the list first with --search TEXT (matches title, album, or any artist) or --specific FIELD (autocomplete an existing title, album, or artist), then pick among the matches.
  • Name Deduplication (dedupe): Find artist and album names that differ only in capitalization or spacing — Artist, artist, a r t i s t — pick the spelling to keep for each group, and smclipy rewrites the tags, renames the artist-title files and their cover images, and updates the tracking database. Every group is reviewed before anything is written; use --artists, --albums, or --dry-run to narrow or preview the run.
  • Interrupt & Retry Friendly: Queues are saved as you paste them — pressing Ctrl+C saves what you've entered, queues survive interruptions, and download detects an unfinished queue and offers to resume. Videos that fail (e.g., HTTP errors) are kept pending for a later retry, with up to max_download_attempts total attempts per URL, after which they're marked permanently failed and reported; already-processed ones are never re-downloaded.
  • Overwrite Protection: Asks before overwriting an existing audio file with the same artist-title name, with a per-track [i/N] progress line while processing. A choice to keep your existing copy is remembered, so that track isn't re-asked on a later resume. In headless --no-prompt mode every prompt is skipped (title, album, artists, cover-crop, overwrite), so an existing file is simply kept and skipped.
  • Clean Cleanup: Uses a .temp directory (inside your music folder's smclipy subfolder) during the download and tagging process to keep your main library clean.

Requirements

  • Python 3.12+ (handled automatically if you use uv).
  • FFmpeg — required by yt-dlp to extract the audio to your chosen format and embed the cover art.
  • Deno — required by yt-dlp (2025.11+) to solve YouTube's JavaScript challenges during extraction. Without it, downloads may fail or have limited format availability.

Installation

pip install smclipy

Configuration

smclipy relies on a configuration file to know where to organize your files.

On the first run of download, crop, tag, findsource, modify, dedupe, restore, update, stats, or any playlist action, a default config file is created at ~/.config/smclipy/config.json and the program exits so you can edit it to your liking. The -d/--directories and -v/--version options work immediately using defaults, so you can preview where everything will live before a config exists. To store the config somewhere else, set the SMCLIPY_CONFIG environment variable to your preferred path (respects $XDG_CONFIG_HOME). If a key is missing from your existing config, it is re-added with its default value on the next command (except -d/--directories, which never rewrites the file).

{
  "name": "smclipy",
  "path_to_music_folder": "./Music",
  "description_max_lines": 5,
  "write_album_if_same_as_title": false,
  "clean_unwanted_tags": false,
  "audio_format": "mp3",
  "max_download_attempts": 3,
  "max_backups": -1,
  "cookies": "",
  "cookies_from_browser": "",
  "source_preferences": ["youtube", "soundcloud"],
  "tag_fields": ["title", "artists", "album", "date", "genre", "album_artist", "track_number", "cover"]
}

Config Breakdown

  • name: The name of the smclipy state folder (smclipy) created inside your music folder to hold the tracking database, covers, backups, and temp files. Song files themselves are saved directly in the music folder (or its subfolders).
  • path_to_music_folder: The base directory where your music library lives (default is ./Music, relative to wherever you run the command from; a leading ~ is expanded).
  • description_max_lines: How many lines of the video description to show while tagging (default is 5).
  • write_album_if_same_as_title: When true, the tag command writes the album even if it equals the song title. When false (default), an album that matches the title is left empty.
  • clean_unwanted_tags: When true, MP3 downloads strip every tag except title, artists, album, cover art, and the tracking UUID before embedding new tags (FLAC, M4A, OGG, and OPUS encode their own minimal tag sets, so there is nothing extra to strip there). When false (default), any tags the source provided (genre, date, lyrics, comments, ...) are kept untouched; the tag command still overwrites the fields you confirm.
  • audio_format: The format new downloads are saved in. Valid values are mp3 (default), m4a, flac, opus, and ogg (Ogg Vorbis). Existing files in other supported formats are still recognized and re-tagged, so changing this never orphans your library.
  • max_download_attempts: How many times download tries a URL before giving up on it permanently. After this many failed attempts the URL is marked failed, reported at the start of the next run, and not re-queued again (default is 3).
  • max_backups: How many tag snapshots to keep per song in smclipy/backups/<uuid>/. The default -1 keeps every snapshot; a positive number keeps only the newest N and deletes older snapshots (and their saved cover art) whenever a new one is recorded. 0 and values below -1 are invalid and fall back to -1 with a warning. Pre-crop cover copies in backups/covers/ are not affected.
  • cookies: Path to a Netscape-format cookies.txt file passed to yt-dlp. Needed for age-restricted or sign-in-required videos (see the yt-dlp cookie guide). ~ is expanded; a missing or unreadable file is ignored with a warning. Leave empty to disable.
  • cookies_from_browser: Browser name to extract cookies from (e.g. firefox, chrome) — must be one of the browsers yt-dlp supports, otherwise it is ignored with a warning. Lower priority than cookies — if both are set, the cookies file wins. Leave empty to disable.
  • source_preferences: The order in which findsource searches providers for each song. Valid values are youtube (alias yt) and soundcloud (alias sc); unknown or duplicate entries are ignored, and an empty/invalid list falls back to ["youtube", "soundcloud"]. The first provider that returns results wins, so put your preferred source first.
  • tag_fields: Which metadata fields the tag command may apply to your library songs. Valid values are title, artists, album, date, genre, album_artist, track_number, and cover. Genres come from MusicBrainz and, when several are listed, are written most-voted first and joined with ; (e.g. progressive rock; art rock; rock).

Usage

Run it from your music library's parent directory:

smclipy <command>

You can also invoke it as a module: python -m smclipy <command>. Note that download, crop, tag, findsource, modify, dedupe, restore, and the interactive playlist actions (add, remove, move) require an interactive terminal, with four headless exceptions: download --batch FILE --no-prompt, download --file FILE --auto (or --no-prompt), tag --auto --all, and findsource --auto --all. tag --specific and findsource --specific always need a terminal (they autocomplete existing values), even alongside --auto --all; --search does not break the headless mode (tag --auto --all --search TEXT, findsource --auto --all --search TEXT). update, the JSON modes, and the remaining playlist actions never require a terminal, so they can be scripted or scheduled.

Commands

  • smclipy download — Batch download and tag songs from YouTube or SoundCloud. Pass --batch FILE to read URLs from a file and --no-prompt to accept the default title, album, and artists, which together make it safe for cron/scripts. Pass --file FILE with a v2 playlist JSON to download only its missing songs and apply each entry's title, artists, album, date, genre, album artist, and track number automatically; --auto is a synonym for --no-prompt that also skips the cover-crop question. Downloaded songs are not added to the playlist automatically — re-run smclipy playlist import afterwards.
  • smclipy crop — Re-extract covers from your audio files, find pillarboxed art, crop to 1:1 interactively, and re-embed into the matching files.
  • smclipy tag — Retag songs already in your library using MusicBrainz metadata. Songs you already tagged (recorded in the tracking database) are automatically skipped with a warning, so re-running over all, a range, or a single number only processes what's new. Songs imported from the older text-file tracking format are not treated as already tagged, so they are offered on the first run.
    • smclipy tag --auto (-a) — Full-auto: after you pick the song range, the top MusicBrainz match is applied to each selected song automatically with no further prompts, writing every enabled tag_fields value (including the title). Shows a per-song progress line instead of the match picker and confirm dialog.
    • smclipy tag --semi (-s) — Semi-auto: the top match is picked automatically per song, but you still review and confirm each change set (checkbox dialog) before it is applied.
    • smclipy tag --all — Skip the range prompt and select every untagged song. Combine with --auto (smclipy tag --auto --all) to retag the whole library without an interactive terminal.
    • smclipy tag --json (-j) — Print a machine-readable JSON report to stdout (per-song outcome, recording used, fields written) while human progress goes to stderr. Requires --auto --all; use it for a scriptable headless retag report. Exits with status 1 when any song crashed, even though the report is still printed.
    • smclipy tag --skip-not-found — Skip songs whose last MusicBrainz lookup found no match instead of retrying them. Run without it to retry those songs.
    • smclipy tag --force — Re-process songs that were already tagged by a previous tag run instead of skipping them. Combine with --all (or --auto --all) to retag the whole library. A song whose current tags already match MusicBrainz is reported as already matching rather than rewritten.
    • Transient lookup failures are retried automatically: if the MusicBrainz search fails, nothing is applied and the song stays untagged; if the genre or cover-art request fails (network error, rate limit, server error), the fields that did resolve are still written but the song stays untagged, so the next tag run retries it. A release that genuinely has no cover art is final and is not retried.
    • smclipy tag --max-age AGE — Only process songs added to the library within the last AGE (e.g. 30m, 24h, 7d). Useful with --auto --all to retag only recent downloads.
    • smclipy tag --search TEXT — Only consider songs whose title, album, or any artist contains TEXT (case-insensitive). Combine with --auto --all for a headless retag of the matches.
    • smclipy tag --specific FIELD — Autocomplete the library's existing title, album, or artists values, pick one, and only retag songs that match it. Requires an interactive terminal. --search and --specific are mutually exclusive.
  • smclipy findsource — Search for download sources of songs already in your library (or, with --file, the entries of a v2 playlist JSON) and save the matching URL in the tracking database (source_url). Providers are tried in the order from source_preferences (default youtube, then soundcloud); the first provider with results wins unless --all-providers is set. Candidates are ranked so the most plausible result is first: a matching length (within two seconds of the library song), then artist similarity, then provider preference, then the provider's own relevance order. Artist names are taken from the uploader, the prefix of an Artist - Title title, and feat./ft./featuring/, with credits, and are compared against every artist on the song (exact or fuzzy, ignoring case, punctuation, and common - Topic/VEVO/Official/Music suffixes). A candidate is only accepted when it names the song's artist or repeats its title; unrelated results are ignored even when their duration matches. By default you pick from the ranked candidates (or skip the song), with unmatched results shown in red and Skip preselected when nothing matches; auto mode skips the song when no candidate matches. Songs that already have a source are skipped. Nothing is downloaded and song tags are not changed (the library scan may still add a tracking UUID to a file that has none yet).
    • Interactive mode shows the library song's length in the header (e.g. === Title - Artist (1:40) ===) and lists the candidates in ranked order. Candidates whose length is within two seconds are highlighted in bold green with a ★ ... [same length] marker.
    • smclipy findsource --auto (-a) — Full-auto: after you pick the song range, the best-ranked plausible result is saved with no candidate picker; if no result names the artist or repeats the title, the song is skipped and recorded as rejected.
    • smclipy findsource --all-providers — With --auto, search every provider in source_preferences instead of stopping at the first one that returns results, then rank the combined candidates. Useful when the preferred provider does not carry a song or has a poor match; it can roughly double the number of searches per song. Requires --auto.
    • smclipy findsource --all — Skip the range prompt and select every song. Combine with --auto (smclipy findsource --auto --all) to run without an interactive terminal.
    • smclipy findsource --file FILE (-f) — Search the entries of a smclipy v2 playlist JSON instead of the library. Every entry is listed for selection; existing source_url values are kept unless --force. Found URLs are written back to the playlist atomically, and for entries already downloaded/tracked the URL is saved in the database too. Entries that are not in the library only update the JSON. When the DB already knows a URL for a downloaded entry, it is copied into the playlist without a search.
    • smclipy findsource --json (-j) — Print a machine-readable JSON report to stdout (per-song outcome, provider, and URL) while human progress goes to stderr. Requires --auto --all; exits with status 1 when any song crashed.
    • smclipy findsource --force — Search songs that already have a recorded source URL and replace it when a new source is found. If the search returns the same URL again, the song is reported as unchanged. Without --force, already-sourced songs are skipped (and reported separately).
    • smclipy findsource --skip-skipped — Skip songs whose last search was skipped (you chose Skip), found no match, or was rejected (no artist/title match), instead of searching them again. Useful with --auto --all so repeated runs only work on songs that still need a source. Transient outcomes (unavailable, errors) are never skipped this way; run without the flag to retry everything.
    • smclipy findsource --search TEXT — Only search songs whose title, album, or any artist contains TEXT (case-insensitive). Combine with --auto --all for a headless run.
    • smclipy findsource --specific FIELD — Autocomplete the library's existing title, album, or artists values, pick one, and only search songs that match it. Requires an interactive terminal. --search and --specific are mutually exclusive.
    • Provider failures (network errors, rate limits) are warned about and the next configured provider is tried. If no candidate is found and every provider failed, the song is reported as unavailable; if some providers failed and others returned no results — or returned only results that did not match — it is also unavailable because the search was incomplete. The outcome of every search is recorded in the tracking database (source_status); skipped, not_found, and rejected are what --skip-skipped filters, while unavailable and per-song errors stay retryable. A provider that simply has no results falls through to the next provider.
  • smclipy modify — Pick songs from the library list (numbers, ranges, or all) and edit their tags by hand, or use --file to edit one audio file or the entries of a v2 playlist JSON: title, artists, album, release date, genre, album artist, track number, cover art, and source URL. You choose which fields to touch in a checkbox dialog, then type one value per field that's applied to every selected song — so a range bulk-edits an album and a single number edits just that song. A blank answer keeps the field, typing /clear empties it (e.g., to remove a bad album). For cover art, give a path to an image file to embed it, or /clear to remove the current image. Source URL is database-only tracking metadata, not an audio tag, so editing it never rewrites the file and it is not included in restore snapshots.
    • smclipy modify --search TEXT (-s) — Only consider songs whose title, album, or any artist contains TEXT (case-insensitive), then pick among the matches as usual.
    • smclipy modify --specific FIELD — Autocomplete the library's existing title, album, or artists values, pick one, and only consider songs that match it. --search and --specific are mutually exclusive.
    • smclipy modify --file FILE (-f) — Edit a single audio file directly (inside the music folder it is tracked first; outside it tags are still written, with a warning), or edit the entries of a v2 playlist JSON. Playlist mode supports every field except cover art and changes only the JSON file; source URL stays a JSON field there. --reset-mb and cover editing apply only to audio files.
    • smclipy modify --reset-mb — Also forget the MusicBrainz match of every selected song, so a later smclipy tag run offers it again. This happens even when you leave every field unchanged, so it also works to retry a song that was tagged without cover art.
  • smclipy dedupe — Find artist and album names whose only difference is capitalization or spacing (for example Artist, artist, and a r t i s t). Each group gets a single-choice dialog to pick the spelling to keep; the program then rewrites the tags, renames the affected artist-title audio files and cover images, and updates the tracking database. Nothing is written until every group has been reviewed.
    • smclipy dedupe --artists / --albums — Only look for artist or only for album spellings (default: both).
    • smclipy dedupe --dry-run — Print the planned tag, filename, cover, and database changes without writing anything.
  • smclipy restore — Browse one list of restore points, newest first, and undo a mistaken tag, modify, crop, or dedupe change. Every mutation records a backup of the song's previous tags and cover art first; each row names the song, the operation that followed the backup, when it was taken, and the values it would restore, and a full before/after preview (including cover changes) is shown before anything is written. The restore itself is backed up too. Filename renames made by dedupe are not undone. Also handy after a bad tag --auto run caught in time.
    • smclipy restore --all — Show every restore point instead of only the newest 25.
    • smclipy restore --limit N — Show at most N restore points (default: 25).
  • smclipy playlist — Create, rename, delete, list, show, fill, reorder, sort, and import/export playlists of your library songs. Every action is documented below in the CLI reference. list and show accept --json (-j) for machine-readable output; export rescans the library first so paths are current (use --no-scan to skip the rescan).
  • smclipy update — Rescan the music folder and sync the tracking database: new files get registered, renames are recorded, and vanished files are marked missing. Prints a summary of what changed, or a JSON report with --json; safe to run anytime, including from scripts or cron.
  • smclipy stats — Summarize the library: total songs, albums, and artists, total reproduction (playback) time, and the albums and artists with the most songs. Rescans first so the numbers are current; use --no-scan to report the database as-is, --top N to change how many albums/artists are listed (default 10), and --json for a machine-readable report. Safe to run from scripts or cron.
  • smclipy -d / --directories — Print the directories and files smclipy uses (config, music, library, temp, covers, backups, database) and exit; it cannot be combined with a command.
  • smclipy -v / --version — Print the version and exit.

MusicBrainz attribution: the tag command searches MusicBrainz, a community-maintained music encyclopedia run by the MetaBrainz Foundation. MusicBrainz core data is released as CC0; supplementary data and the MusicBrainz documentation are licensed under CC BY-NC-SA 3.0. See MusicBrainz's data license page for the full breakdown. Cover art is served by the Cover Art Archive, a joint project of the Internet Archive and MusicBrainz; the images are copyrighted by their respective copyright holders.

Download flow

  1. Queue URLs: Paste your URLs one by one (enter an empty line to finish).
  2. Resume or restart: If a previous run left an unfinished queue, you're asked whether to resume from where it stopped or start a fresh queue.
  3. Tagging Flow (Per Track):
    • The track is downloaded to Music/smclipy/.temp.
    • If a cover image is found, it's previewed; if it isn't already 1:1 you're prompted to crop it.
    • A preview shows the video's title, artist/s, and its description (truncated to description_max_lines lines).
    • Enter the Title of the track (prefilled with the video's title).
    • Enter the Album (prefilled when the video provides one).
    • Enter the Artist/s (prefilled from the channel name; if it matches an author already known — ignoring case and spaces — your existing spelling is kept). To tag multiple artists, separate them using a backslash \ or a comma (e.g., Artist 1\Artist 2\Artist 3 or Artist 1, Artist 2). A name that itself contains a comma (or a backslash) must escape it, e.g. Earth\, Wind & Fire, so it stays one artist.
  4. Save / Skip: Once tagged, the file is moved to your music folder as artist-title.<audio_format> (e.g., Rick Astley-Never Gonna Give You Up.mp3). If a file with that name already exists, you're asked whether to overwrite it or keep your existing copy; keeping it is remembered so you aren't asked about that track again. Overwriting a tracked song keeps its existing tracking UUID, so the database row is updated in place and no phantom duplicate is created. Successfully processed videos are recorded so they're skipped on resume; failed videos stay pending and are reported at the end so you can retry them later — once max_download_attempts total attempts have been made, a dead URL is reported as permanently failed instead of being re-queued.

Folder Structure Example

After running the script and tagging a few songs, your output directory will look something like this:

📁 ./Music
├── 🎵 artist1-title1.mp3
├── 🎵 artist1-title2.mp3
├── 🎵 artist2-title1.mp3
├── 🎵 artist3-title1.mp3
└── 📁 smclipy
    ├── 📁 .temp                       <-- Temp files during processing
    ├── 📁 covers                      <-- Cover art extracted from your audio files
    ├── 📁 backups                     <-- Tag snapshots used by `smclipy restore`
    └── 📄 smclipy.db                  <-- Tracking database (songs, history, queue, authors)

Snapshots & undo: before every tag, modify, crop, or dedupe change, smclipy records the song's current tags (and cover art) as a snapshot in smclipy/backups/<uuid>/; set max_backups to a positive number to keep only the newest N snapshots per song (the default -1 keeps all of them). A mistaken tag --auto run, a bad manual edit, a wrongly-cropped cover, or a mis-merged artist/album name can therefore be rolled back at any time with smclipy restore, which lists every backup newest first with the values it would restore, shows a before/after preview (including cover changes), and then rewrites the file's tags (and — where a backup contains cover art — re-embeds it) and re-syncs the database; filename renames made by dedupe are not undone. Every write to the audio files is atomic (a sibling temp file renamed over the original), so an interrupted run never leaves a half-written song, and leftovers are swept by the next scan. Original cover files are also copied into smclipy/backups/covers/ before a crop, so a false-positive crop is never permanently destructive.

Subfolders are scanned too: update, tag, findsource, modify, dedupe, and crop no longer look only at files in the root of the music folder — albums organized into subdirectories are registered and tracked the same way (paths are recorded relative to the music folder). The smclipy state folder, its subtrees (.temp, covers, backups) and any hidden directories (.stfolder, .stversions, etc.) are skipped, so scanning never mistakes the script's own data for music. Songs in hidden folders are intentionally not scanned.

Tracker: every song is identified by a UUID stored in its tags, so renaming a file keeps it tracked (the rename is recorded). The database keeps the download URL, MusicBrainz match status (tagged / skipped / not found), cover status (not pillarbox / false positive / cropped), and an append-only event history per song, all inspectable directly in smclipy.db.

Re-process a song: to re-tag a song that was already tagged, or be offered a cover you previously declined, reset its MusicBrainz status with smclipy modify --reset-mb or edit smclipy.db directly (or delete the row to forget it entirely). To re-process every already-tagged song in bulk without resetting status, run smclipy tag --force (add --all for the whole library). A song that is missing from the music folder is marked missing but keeps its history; put the file back and it re-appears under the same tracking ID.

Upgrading: the old text-file tracking (authors.txt, tagged_files.txt, the queue files, and cropping_tool_false_positives.txt) is imported once into smclipy.db on the first run of a version that has the database, after which those files are ignored. The database schema is versioned, so future releases upgrade it in place without losing history. Legacy rows are imported as untagged, so the first tag run offers every existing library song for MusicBrainz metadata.

Syncthing & other synced shares: if your music folder is shared between devices, the smclipy state that lives inside it (smclipy.db, .temp, covers, backups) should not sync — two devices writing the SQLite database independently with last-writer-wins whole-file copies will corrupt it, and half-downloaded temp files replicate mid-write. An .stignore template is provided in the repo (stignore.example): copy it to .stignore in the shared folder's root (or un-ignore just the backups/ directory if you want restores mirrored).

Development

Clone the repo and run it from the project root with uv (set SMCLIPY_CONFIG to use your existing config.json):

uv run smclipy <command>

Check your changes with the same tooling used in CI:

uv run pytest          # tests
uv run ruff check      # lint
uv run ruff format --check  # format
uv run mypy            # type checks

Roadmap

See roadmap.md for planned work: async/concurrent downloads, a configurable library folder structure, and a full TUI upgrade.

CLI reference

The exact --help output of the CLI and of every command. This is always current: run any command with --help (or smclipy <cmd> <action> --help for playlist actions) to see the live version.

smclipy

usage: smclipy [-h] [-v] [-d] command ...

Download, tag, and organize music from YouTube and SoundCloud.

positional arguments:
  command
    download         Batch download songs from YouTube or SoundCloud and tag
                     them.
    crop             Crop pillarboxed cover images and re-embed them.
    tag              Retag library songs using MusicBrainz metadata.
    findsource       Find and record download sources for library songs.
    modify           Manually edit the tags of one or more library songs.
    dedupe           Merge differently formatted artist and album names.
    restore          Undo tag changes from recorded backups.
    playlist         Create and manage library playlists, and import/export
                     them.
    update           Rescan the library and sync the tracking database.
    stats            Report library totals: songs, albums, artists, playtime.

options:
  -h, --help         show this help message and exit
  -v, --version      Print the version and exit.
  -d, --directories  Print the directories smclipy uses and exit (cannot be
                     combined with a command).

examples:
  smclipy download   Batch download songs and tag them interactively
  smclipy download --batch urls.txt --no-prompt  Headless batch download
  smclipy download --file playlist.json --auto  Fetch missing songs
  smclipy tag        Retag library songs using MusicBrainz metadata
  smclipy tag --auto Full-auto retag: top match applied to each song
  smclipy tag --semi Auto-pick the top match, confirm each change
  smclipy tag --auto --all  Headless retag of every untagged song
  smclipy tag --auto --all --json  Same, with a JSON report on stdout
  smclipy tag --search TEXT  Only retag songs matching TEXT
  smclipy tag --specific album  Autocomplete an album, then retag it
  smclipy findsource  Find and record sources, picking candidates
  smclipy findsource --auto --all  Headless source search for all songs
  smclipy findsource --auto --all-providers  Rank across all providers
  smclipy findsource --skip-skipped  Skip songs skipped before
  smclipy findsource --search TEXT  Only search songs matching TEXT
  smclipy findsource --specific artists  Search one artist's songs
  smclipy findsource --file playlist.json  Fill missing playlist URLs
  smclipy modify     Manually edit the tags of one or more songs
  smclipy modify --search TEXT  Find songs by text, then edit them
  smclipy modify --specific artists  Autocomplete an artist, then edit
  smclipy modify --file playlist.json  Edit exported playlist entries
  smclipy dedupe     Merge differently styled artist/album names
  smclipy dedupe --albums  Only merge album spellings
  smclipy dedupe --dry-run  Preview a merge without writing
  smclipy restore    Roll back a song to an earlier tag backup
  smclipy restore --all  Show every recorded restore point
  smclipy restore --limit 5  Show only the newest 5 restore points
  smclipy crop       Crop pillarboxed cover images and re-embed them
  smclipy update     Rescan the library and sync the tracking database
  smclipy update --json  Machine-readable scan report on stdout
  smclipy stats      Totals, playtime, top albums and artists
  smclipy stats --top 5 --json  Machine-readable stats report
  smclipy playlist create Chill  Create an empty playlist
  smclipy playlist add Chill     Interactively add library songs to it
  smclipy playlist export Chill -f m3u  Export it as an .m3u file
  smclipy playlist export Chill -f m3u-link  Export it as URLs, one per line
  smclipy playlist import mix.m3u       Import a playlist from a file
  smclipy playlist show Chill --json    Print its songs as JSON
  smclipy -d         Print the directories smclipy uses
  smclipy -v         Print the version and exit

smclipy playlist

usage: smclipy playlist [-h] action ...

Create, rename, delete, list, and show playlists of your tracked library
songs. Playlists live in the tracking database and reference songs by UUID, so
renames and relocations never break them. Songs can be added, removed, moved,
or sorted by metadata, and playlists can be exported to .m3u, .m3u-link (one
source URL per line with artist/title labels), or .json and imported back.

positional arguments:
  action
    create    Create a new playlist.
    rename    Rename a playlist.
    delete    Delete a playlist and its entries.
    list      List all playlists and their song counts.
    show      Show the songs in a playlist.
    add       Interactively add library songs to a playlist.
    remove    Interactively remove songs from a playlist.
    move      Interactively move a song to a new position.
    sort      Sort a playlist's songs by metadata.
    export    Export a playlist to .m3u, .m3u-link, or .json.
    import    Import a playlist from .m3u or .json.

options:
  -h, --help  show this help message and exit

smclipy download

usage: smclipy download [-h] [-b FILE | -f FILE] [--no-prompt] [-a]

Batch download songs from YouTube or SoundCloud, then interactively tag each
one with a title, artist, and album before saving it. Use --file to fetch the
missing songs from a v2 playlist JSON and apply the playlist metadata
automatically.

options:
  -h, --help            show this help message and exit
  -b FILE, --batch FILE
                        Read URLs from FILE (one per line) instead of
                        prompting, so download can run without an interactive
                        terminal.
  -f FILE, --file FILE  Download the missing songs from a smclipy v2 playlist
                        JSON. Songs already in the library are skipped; each
                        entry's title, artists, album, date, genre, album
                        artist, and track number are applied without prompting
                        when present.
  --no-prompt           Accept the default title, album, and artists for each
                        download without asking (useful with --batch). A file
                        that already exists with the same artist-title name is
                        kept and skipped without prompting.
  -a, --auto            Synonym for --no-prompt: skip all prompts, including
                        the cover crop question. Useful with --file.

smclipy update

usage: smclipy update [-h] [-j]

Reconcile the tracking database with the current state of the music folder:
register new songs, record renames, and mark files that vanished as missing.
Does not require an interactive terminal, so it can be run from scripts or
cron.

options:
  -h, --help  show this help message and exit
  -j, --json  Print a machine-readable JSON report to stdout.

smclipy stats

usage: smclipy stats [-h] [-j] [--top N] [--no-scan]

Summarize the library: total songs, albums, and artists, total reproduction
time, and the albums and artists with the most songs. Rescans the library
first so the numbers are current; use --no-scan to report the database as-is.
Does not require an interactive terminal, so it can be run from scripts or
cron.

options:
  -h, --help  show this help message and exit
  -j, --json  Print a machine-readable JSON report to stdout.
  --top N     How many albums and artists to list (default: 10).
  --no-scan   Skip the library rescan and report the tracking database as-is.

smclipy tag

usage: smclipy tag [-h] [-a | -s] [--all] [--force] [-j] [--skip-not-found]
                   [--max-age AGE] [--search TEXT | --specific FIELD]

List every song in the library, fetch matching metadata from MusicBrainz, and
interactively review and apply title, artist, album, release date, genre,
track number, album artist, and cover art changes.

options:
  -h, --help        show this help message and exit
  -a, --auto        Fully automatic: pick the top match for each selected song
                    and apply the configured tag_fields without any further
                    prompts (only the initial range selection).
  -s, --semi        Semi-automatic: pick the top match for each selected song
                    but confirm each change set (checkbox dialog) before
                    applying.
  --all             Tag every untagged song in the library instead of asking
                    for a range. Combine with --auto to run without an
                    interactive terminal. Use --force to include songs that
                    were already tagged.
  --force           Re-tag songs that were already tagged by a previous tag
                    run instead of skipping them.
  -j, --json        Print a machine-readable JSON report to stdout while human
                    messages go to stderr. Requires --auto --all (headless
                    retag). Exits with status 1 when any song crashed.
  --skip-not-found  Skip songs whose last MusicBrainz lookup found no match
                    instead of retrying them. Run without it to retry those
                    songs.
  --max-age AGE     Only process songs added to the library within the last
                    AGE (e.g. '30m', '24h', '7d'). Useful with --auto --all to
                    retag only recent downloads.
  --search TEXT     Only process songs whose title, album, or any artist
                    contains TEXT (case-insensitive). Combine with --auto
                    --all for a headless retag of the matches.
  --specific FIELD  Autocomplete the library's existing FIELD values (title,
                    album, or artists) and only process songs that match the
                    chosen value. Needs an interactive terminal.

Metadata is provided by MusicBrainz (core data is CC0, supplementary data and
docs are CC BY-NC-SA 3.0; https://musicbrainz.org ), and cover art is served
by the Cover Art Archive (coverartarchive.org).

smclipy findsource

usage: smclipy findsource [-h] [-f FILE] [-a] [--all] [-j] [--force]
                          [--skip-skipped] [--all-providers]
                          [--search TEXT | --specific FIELD]

Search the providers listed in the config's 'source_preferences' (default:
YouTube, then SoundCloud) for each selected song (or playlist entry with
--file) and save the matching URL in the tracking database. Candidates are
ranked by matching duration, then artist similarity, then provider preference.
A candidate is only accepted when it names the song's artist (in the uploader
or an 'Artist - Title feat. Artist2' title) or repeats the song title;
unmatched results are skipped. The first provider with results wins unless
--all-providers is set; interactive mode shows the ranked list and --auto
saves the best plausible one. Each processed song is announced as `[i/n] ===
Title - Artist (mm:ss) ===`; skipped songs are followed by a `Skipped:
<reason>` line (already sourced, no candidates, provider failure, no
artist/title match, or no source selected). With --file, URLs are written back
to the playlist and to the database for already-downloaded entries.

options:
  -h, --help            show this help message and exit
  -f FILE, --file FILE  Search the entries of a smclipy v2 playlist JSON
                        instead of the library. Existing source_url values are
                        kept unless --force; found URLs are written back to
                        the playlist and, for entries already
                        downloaded/tracked, to the database too.
  -a, --auto            Fully automatic: save the best-ranked plausible search
                        result for each selected song without showing the
                        candidate picker.
  --all                 Search every song in the library (or every entry with
                        --file) instead of asking for a range. Combine with
                        --auto to run without an interactive terminal.
  -j, --json            Print a machine-readable JSON report to stdout while
                        human messages go to stderr. Requires --auto --all
                        (headless search). Exits with status 1 when any song
                        crashed.
  --force               Search songs that already have a recorded source URL
                        and replace it when a new source is found.
  --skip-skipped        Skip songs that were skipped, had no match, or were
                        rejected on a previous findsource run instead of
                        searching them again. Run without this flag to retry
                        them.
  --all-providers       With --auto, search every provider in
                        'source_preferences' instead of stopping at the first
                        one that returns results. Candidates are ranked by
                        matching duration, then artist similarity, then
                        provider order.
  --search TEXT         Only process songs whose title, album, or any artist
                        contains TEXT (case-insensitive). Combine with --auto
                        --all for a headless search of the matches.
  --specific FIELD      Autocomplete the library's existing FIELD values
                        (title, album, or artists) and only process songs that
                        match the chosen value. Needs an interactive terminal.

smclipy modify

usage: smclipy modify [-h] [-f FILE] [-s TEXT | --specific FIELD] [--reset-mb]

Pick songs from your library (numbers or ranges) and edit their tags by hand:
title, artists, album, release date, genre, album artist, track number, cover
art, and source URL. One set of values is applied to every selected song, so a
range bulk-edits an album and a single number edits just that song. A blank
answer keeps the field, typing /clear empties it. Source URL is stored only in
the tracking database (not in the audio file) and is not part of restore
snapshots. Use --file to edit one audio file directly or the entries of a v2
playlist JSON (playlist source URLs stay in the file).

options:
  -h, --help            show this help message and exit
  -f FILE, --file FILE  Edit one audio file directly, or edit the entries of a
                        smclipy v2 playlist JSON. Playlist entries are changed
                        only in the JSON; source_url stays a JSON field there.
  -s TEXT, --search TEXT
                        Only consider songs whose title, album, or any artist
                        contains TEXT (case-insensitive) before asking which
                        ones to edit.
  --specific FIELD      Autocomplete the library's existing FIELD values
                        (title, album, or artists) and only consider songs
                        that match the chosen value.
  --reset-mb            Forget the MusicBrainz match of every modified song so
                        the `tag` command offers it again.

smclipy dedupe

usage: smclipy dedupe [-h] [--artists] [--albums] [--dry-run]

Find artist and album names that differ only by case or spacing (for example
'Artist', 'artist', and 'a r t i s t'), pick the spelling to keep for each
group, then rewrite the tags, the artist-title filenames, matching cover
images, and the tracking database. Nothing is written until every group has
been reviewed.

options:
  -h, --help  show this help message and exit
  --artists   Only look for artist names (default: artists and albums).
  --albums    Only look for album names (default: artists and albums).
  --dry-run   Show the planned tag, filename, cover, and database changes
              without writing anything.

smclipy restore

usage: smclipy restore [-h] [--all] [--limit N]

Browse a single list of restore points, newest first, and roll songs back to
an earlier backup. Each row names the song, the operation that followed the
backup, when it was taken, and the values it would restore. A backup of each
song's tags (and cover art) is recorded automatically before every `tag`,
`modify`, `crop`, and `dedupe` change, so a mistaken MusicBrainz match or
manual edit can be undone here. The restore itself is also backed up, so
undoing an undo is possible.

options:
  -h, --help  show this help message and exit
  --all       Show every restore point instead of only the newest 25.
  --limit N   Show at most N restore points (default: 25).

smclipy crop

usage: smclipy crop [-h]

Scan saved cover images for pillarboxing, crop them to a 1:1 ratio
interactively, and re-embed each result into its matching audio file.

options:
  -h, --help  show this help message and exit

smclipy playlist create

usage: smclipy playlist create [-h] [--description DESCRIPTION] name

Create a new, empty playlist.

positional arguments:
  name                  Name of the playlist.

options:
  -h, --help            show this help message and exit
  --description DESCRIPTION
                        Optional description of the playlist.

smclipy playlist rename

usage: smclipy playlist rename [-h] name new_name

Rename an existing playlist.

positional arguments:
  name        Current name of the playlist.
  new_name    New name for the playlist.

options:
  -h, --help  show this help message and exit

smclipy playlist delete

usage: smclipy playlist delete [-h] name

Delete a playlist and remove all of its entries.

positional arguments:
  name        Name of the playlist to delete.

options:
  -h, --help  show this help message and exit

smclipy playlist list

usage: smclipy playlist list [-h] [-j]

List every playlist with its song count.

options:
  -h, --help  show this help message and exit
  -j, --json  Print the list as JSON.

smclipy playlist show

usage: smclipy playlist show [-h] [-j] name

List the songs in a playlist in order.

positional arguments:
  name        Name of the playlist to show.

options:
  -h, --help  show this help message and exit
  -j, --json  Print the songs as JSON.

smclipy playlist add

usage: smclipy playlist add [-h] name

Pick songs from your library to append to a playlist.

positional arguments:
  name        Name of the playlist to add to.

options:
  -h, --help  show this help message and exit

smclipy playlist remove

usage: smclipy playlist remove [-h] name

Pick songs from a playlist to remove.

positional arguments:
  name        Name of the playlist to remove from.

options:
  -h, --help  show this help message and exit

smclipy playlist move

usage: smclipy playlist move [-h] name

Pick a song in a playlist and give it a new position.

positional arguments:
  name        Name of the playlist to reorder.

options:
  -h, --help  show this help message and exit

smclipy playlist sort

usage: smclipy playlist sort [-h] [--key {title,artists,album,path,added}]
                             [--reverse]
                             name

Reorder a playlist's songs by title, artists, album, path, or the time they
were added.

positional arguments:
  name                  Name of the playlist to sort.

options:
  -h, --help            show this help message and exit
  --key {title,artists,album,path,added}
                        What to sort by (default: title).
  --reverse             Sort in descending order.

smclipy playlist export

usage: smclipy playlist export [-h] [-f {m3u,m3u-link,json}] [-o OUTPUT]
                               [--absolute] [--no-scan]
                               name

Export a playlist to an .m3u, .m3u-link, or .json file. Rescans the library
first so the paths are current; use --no-scan to export the tracking database
as-is. Paths are written relative to the exported file unless --absolute is
given. The m3u-link format writes one source URL per line instead of local paths,
each preceded by an #EXTINF line with the song's artists and title.

positional arguments:
  name                  Name of the playlist to export.

options:
  -h, --help            show this help message and exit
  -f {m3u,m3u-link,json}, --format {m3u,m3u-link,json}
                        Export format (default: inferred from --output, else
                        m3u).
  -o OUTPUT, --output OUTPUT
                        Where to write the file (default: <name>.<ext> in the
                        current dir).
  --absolute            Write absolute paths.
  --no-scan             Skip the library rescan and export the tracking
                        database as-is.

smclipy playlist import

usage: smclipy playlist import [-h] [--name NAME] [--replace] file

Import an .m3u/.m3u8/.m3u-link or smclipy .json playlist. Entries are matched to your
tracked library songs by path (relative to the file, your music folder, or
absolute) or UUID; unmatched entries are skipped and reported. For .json
imports, the missing songs are counted and smclipy suggests `smclipy download
--file FILE`, which downloads them from their recorded source_url and applies
the playlist metadata automatically.

positional arguments:
  file         Path to the .m3u/.m3u8/.m3u-link/.json file.

options:
  -h, --help   show this help message and exit
  --name NAME  Playlist name (default: the file's name or the JSON name).
  --replace    Overwrite the playlist if it already exists.

The JSON export format is versioned by its smclipy_playlist field. Version 2 includes each song's full metadata, with covers deliberately excluded:

{
  "smclipy_playlist": 2,
  "name": "Chill",
  "songs": [
    {
      "uuid": "…",
      "title": "Song",
      "artists": "Artist",
      "artists_list": ["Artist"],
      "album": "Album",
      "album_artist": "Artist",
      "genres": ["pop", "rock"],
      "date": "1980-01-01",
      "track_number": "3",
      "source_url": "https://www.youtube.com/watch?v=…",
      "path": "Artist-Song.mp3"
    }
  ]
}

Import only uses uuid/path to resolve entries, so version 1 files remain fully importable. download --file requires version 2 and prefers artists_list when present so artist names containing commas survive the round-trip; it only downloads missing songs and does not modify the playlist, so re-run the import afterwards to attach them.

Metadata

Release files for smclipy 0.7.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for smclipy 0.7.0
File Size Uploaded Provenance Actions
smclipy-0.7.0.tar.gz 257.6 kB Without provenance Details for smclipy-0.7.0.tar.gz

Built distribution (wheel)

Table of built distributions (wheels) for smclipy 0.7.0
File Interpreter ABI Platform Actions
smclipy-0.7.0-py3-none-any.whl Python 3 none any Details for smclipy-0.7.0-py3-none-any.whl

Total release size: 374.0 kB

Release files / smclipy-0.7.0.tar.gz

Download URL smclipy-0.7.0.tar.gz
Size 257.6 kB
Tags Source
SHA-256 checksum
How to use checksums
0ccc58c67f5311424cb804f9afbeda9aea1fe033dfd3913e7b27d9b9a353e591
BLAKE2b-256 checksum
How to use checksums
9c538fc7f6a12b5f9b46204a8479cda1c804473bf12ee61a3f8b3f2b35270761
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via uv/0.13.6 {"installer":{"name":"uv","version":"0.13.6","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}

Release files / smclipy-0.7.0-py3-none-any.whl

Download URL smclipy-0.7.0-py3-none-any.whl
Size 116.4 kB
Tags Python 3
SHA-256 checksum
How to use checksums
d06fbd00e61901738212405c5a55fa8b223710bdf7d53438cf4acc90342f5c65
BLAKE2b-256 checksum
How to use checksums
df9becfa82d7e09e96ac96574c78f701618861c486966d21472fe4e223b845fa
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via uv/0.13.6 {"installer":{"name":"uv","version":"0.13.6","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}

Security for smclipy 0.7.0

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

No provenance found

None of the files in this release were uploaded with provenance metadata.

A note on package security

PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.

To learn more about protecting yourself from malicious actors, visit our package security documentation.

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page