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.comwatch/shorts/live/embed links,youtu.belinks, andsoundcloud.comtracks. 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, track number, album artist, and cover art — reviewing each candidate and the exact diff before applying. - Video Info Preview: Before tagging, the video's title, artist/s, and the first
description_max_lineslines 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 MP3s, 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. - Interrupt & Retry Friendly: Queues are saved as you paste them — pressing Ctrl+C saves what you've entered, queues survive interruptions, and
downloaddetects an unfinished queue and offers to resume. Videos that fail (e.g., HTTP errors) are kept pending for a later retry, and already-processed ones are never re-downloaded. - Overwrite Protection: Asks before overwriting an existing MP3 with the same
artist-titlename, 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. - Clean Cleanup: Uses a
.tempdirectory (inside your music folder'ssmclipysubfolder) 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 MP3 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, or tag, 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 the next time you run smclipy.
{
"name": "smclipy",
"path_to_music_folder": "./Music",
"description_max_lines": 5,
"write_album_if_same_as_title": false,
"tag_fields": ["title", "artists", "album", "date", "album_artist", "track_number", "cover"]
}
Config Breakdown
name: The master folder name (smclipy) that will be created inside your music folder to hold all the organized artists.path_to_music_folder: The base directory where your music library lives (default is./Music, relative to wherever you run the command from).description_max_lines: How many lines of the video description to show while tagging (default is 5).write_album_if_same_as_title: Whentrue, thetagcommand writes the album even if it equals the song title. Whenfalse(default), an album that matches the title is left empty.tag_fields: Which metadata fields thetagcommand may apply to your library songs. Valid values aretitle,artists,album,date,album_artist,track_number, andcover.
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, and tag require an interactive terminal.
Commands
smclipy download— Batch download and tag songs from YouTube or SoundCloud.smclipy crop— Re-extract covers from your MP3s, find pillarboxed art, crop to 1:1 interactively, and re-embed into the matching MP3s.smclipy tag— Retag songs already in your library using MusicBrainz metadata (attribution: MusicBrainz data is licensed CC BY-NC-SA 3.0). Songs you already tagged (recorded intagged_files.txt) are automatically skipped with a warning, so re-running overall, a range, or a single number only processes what's new.smclipy -d/--directories— Print the directories and files smclipy uses (config, music, library, temp, covers, authors, songs info) and exit.smclipy -v/--version— Print the version and exit.
Download flow
- Queue URLs: Paste your URLs one by one (enter an empty line to finish).
- 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.
- 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_lineslines). - 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 in
authors.txt— 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 3orArtist 1, Artist 2).
- The track is downloaded to
- Save / Skip: Once tagged, the file is moved to your music folder as
artist-title.mp3(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. 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.
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 <-- Pending/processed queues + temp files during processing
├── 📁 covers <-- Cover art extracted from your MP3s
├── 📄 authors.txt <-- Known authors, used for autocompletion
├── 📄 tagged_files.txt <-- Songs already tagged by `tag` (skipped automatically)
├── 📄 songs_info.txt <-- Log of tagged songs
├── 📄 scan_state.txt <-- Library scan state, used to skip unchanged libraries
└── 📄 cropping_tool_false_positives.txt <-- Covers you declined to crop
Re-process a song: to re-tag a song that was already tagged, remove its entry from
tagged_files.txt. To be offered a cover you previously skipped again, remove that song fromcropping_tool_false_positives.txt. Just edit these files like a normal text file (one song per line).
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, support for more formats (FLAC, M4A, Opus), an interactive library edit/re-tag mode, a configurable library folder structure, and a full TUI upgrade.
Release files for smclipy 0.3.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| smclipy-0.3.0.tar.gz | 109.9 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| smclipy-0.3.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 138.1 kB
Release files / smclipy-0.3.0.tar.gz
| Download URL | smclipy-0.3.0.tar.gz |
|---|---|
| Size | 109.9 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
9cd8aa4dbbc53ed52865a95b2a1050563268a60fc6b9d0ad89d12adeb136fd93
|
|
BLAKE2b-256 checksum How to use checksums |
66cb9ed71ef43fd05dc8e6d0b0b7ea4e1e986a3e99718b2d9083897795c0aedc
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
uv/0.12.13 {"installer":{"name":"uv","version":"0.12.13","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.3.0-py3-none-any.whl
| Download URL | smclipy-0.3.0-py3-none-any.whl |
|---|---|
| Size | 28.2 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
64433729f2f82114d850990d550caf85173f974b91834378690bd4b61d758a98
|
|
BLAKE2b-256 checksum How to use checksums |
a799a0edaae0abdf44655ac489cf6ee83b777be060723a71b8be709403c763b1
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
uv/0.12.13 {"installer":{"name":"uv","version":"0.12.13","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}
|