-
-
Notifications
You must be signed in to change notification settings - Fork 0
feat: CLI with config file and env var support #9
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,20 @@ | ||
| FROM python:3.12-slim AS builder | ||
|
|
||
| WORKDIR /app | ||
| COPY pyproject.toml . | ||
| COPY src/ src/ | ||
|
|
||
| RUN pip install --no-cache-dir --prefix=/install . | ||
|
|
||
| FROM python:3.12-slim | ||
|
|
||
| WORKDIR /app | ||
| COPY --from=builder /install /usr/local | ||
|
|
||
| RUN useradd --create-home appuser | ||
| USER appuser | ||
|
|
||
| RUN mkdir -p /app/music /app/downloads /app/organized | ||
|
|
||
| ENTRYPOINT ["slskd-transform"] | ||
| CMD ["search"] | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -8,94 +8,179 @@ | |
|
|
||
| <p align="center"> | ||
| <a href="https://github.com/GeiserX/slskd-transform/blob/main/LICENSE"><img src="https://img.shields.io/github/license/GeiserX/slskd-transform?style=flat-square&color=FF6F00" alt="License"></a> | ||
| <a href="https://www.python.org/"><img src="https://img.shields.io/badge/python-3.8%2B-blue?style=flat-square&logo=python&logoColor=white" alt="Python 3.8+"></a> | ||
| <a href="https://www.python.org/"><img src="https://img.shields.io/badge/python-3.10%2B-blue?style=flat-square&logo=python&logoColor=white" alt="Python 3.10+"></a> | ||
| <a href="https://github.com/slskd/slskd"><img src="https://img.shields.io/badge/requires-slskd-1A1A2E?style=flat-square" alt="Requires slskd"></a> | ||
| <a href="https://github.com/GeiserX/slskd-transform/stargazers"><img src="https://img.shields.io/github/stars/GeiserX/slskd-transform?style=flat-square&color=FFD54F" alt="Stars"></a> | ||
| <a href="https://codecov.io/gh/GeiserX/slskd-transform"><img src="https://img.shields.io/codecov/c/github/GeiserX/slskd-transform?style=flat-square" alt="Coverage"></a> | ||
| </p> | ||
|
|
||
| --- | ||
|
|
||
| **slskd-transform** is a Python tool that scans your local music library, searches the [Soulseek](https://www.slsknet.org/) network through [slskd](https://github.com/slskd/slskd) for matching FLAC versions of each track, and automatically enqueues them for download. It matches songs by **audio duration** rather than filenames alone, ensuring you get the correct track every time. Songs that cannot be found are reported in a CSV file for manual follow-up. | ||
| **slskd-transform** scans your local music library, searches the [Soulseek](https://www.slsknet.org/) network through [slskd](https://github.com/slskd/slskd) for matching FLAC versions of each track, and automatically enqueues them for download. It matches songs by **audio duration** rather than filenames alone, ensuring you get the correct track every time. | ||
|
|
||
| A companion script handles post-download organization, renaming all downloaded FLACs into a clean `Artist - Title.flac` structure using embedded metadata. | ||
| A companion `rename` command handles post-download organization, renaming all downloaded FLACs into a clean `Artist - Title.flac` structure using embedded metadata. | ||
|
|
||
| ## Features | ||
|
|
||
| - **Duration-based matching** -- Compares local track duration against search results with a configurable tolerance (default: 15 seconds), avoiding mismatches from inconsistent naming. | ||
| - **Multi-threaded search** -- Distributes searches across multiple threads (default: 5) for faster processing of large libraries. | ||
| - **Duration-based matching** -- Compares local track duration against search results with a configurable tolerance (default: 15 seconds). | ||
| - **Recursive scanning** -- Point it at your existing music library with `--recursive`, no need to flatten files first. | ||
| - **Multi-threaded search** -- Distributes searches across multiple threads (default: 5) for faster processing. | ||
| - **Flexible configuration** -- Config file, environment variables, or CLI flags. No code editing required. | ||
| - **Automatic enqueue** -- Matched FLAC files are enqueued for download directly through the slskd API. | ||
| - **CSV reporting** -- Tracks that could not be found are written to `unfound_songs.csv` for later review. | ||
| - **Metadata-based renaming** -- The `rename-files.py` script reads FLAC tags and renames files to `Artist - Title.flac`, sanitizing any invalid characters. | ||
| - **CSV reporting** -- Tracks that could not be found are written to `unfound_songs.csv`. | ||
| - **Metadata-based renaming** -- Reads FLAC tags and renames files to `Artist - Title.flac`. | ||
| - **Docker support** -- Run alongside slskd in the same compose stack. | ||
|
|
||
| ## Prerequisites | ||
|
|
||
| - **Python 3.8+** | ||
| - **[slskd](https://github.com/slskd/slskd)** running and accessible (by default at `http://127.0.0.1:5030`) | ||
| - **Python 3.10+** | ||
| - **[slskd](https://github.com/slskd/slskd)** running and accessible | ||
| - A valid slskd **API key** (configured in slskd's settings) | ||
| - A local directory containing the lossy music files you want to upgrade | ||
|
|
||
| ## Installation | ||
|
|
||
| ```bash | ||
| pip install git+https://github.com/GeiserX/slskd-transform.git | ||
| ``` | ||
|
|
||
| Or for development: | ||
|
|
||
| ```bash | ||
| git clone https://github.com/GeiserX/slskd-transform.git | ||
| cd slskd-transform | ||
| pip install -r requirements.txt | ||
| pip install -e ".[dev]" | ||
| ``` | ||
|
|
||
| ## Usage | ||
| ## Quick Start | ||
|
|
||
| ### Step 1 -- Search and enqueue FLAC downloads | ||
| ```bash | ||
| # Set your API key (or put it in config.yml) | ||
| export SLSKD_API_KEY="your-api-key-here" | ||
|
|
||
| # Search for FLAC versions of all files in ./music | ||
| slskd-transform search | ||
|
|
||
| 1. Place your lossy music files (MP3, AAC, OGG, etc.) in a `music/` directory inside the project root. | ||
| 2. Open `main.py` and set your slskd connection details: | ||
| # Search recursively in your existing library | ||
| slskd-transform search --music-dir /path/to/library --recursive | ||
|
|
||
| ```python | ||
| slskd = slskd_api.SlskdClient( | ||
| host="http://127.0.0.1:5030", | ||
| api_key="YOUR_API_KEY", | ||
| verify_ssl=False | ||
| ) | ||
| # Rename downloaded FLACs using metadata | ||
| slskd-transform rename --source-dir /path/to/downloads --dest-dir /path/to/organized | ||
| ``` | ||
|
|
||
| 3. Run the search: | ||
| ## Configuration | ||
|
|
||
| slskd-transform loads configuration from multiple sources with this priority: | ||
|
|
||
| ```bash | ||
| python main.py | ||
| ``` | ||
| CLI flags > Environment variables > Config file > Defaults | ||
| ``` | ||
|
Comment on lines
+74
to
76
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Add language identifiers to fenced code blocks. Line 74, Line 121, Line 138, and Line 147 use unlabeled fenced blocks (MD040). Add a language (for example Also applies to: 121-123, 138-139, 147-148 🧰 Tools🪛 markdownlint-cli2 (0.22.1)[warning] 74-74: Fenced code blocks should have a language specified (MD040, fenced-code-language) 🤖 Prompt for AI Agents |
||
|
|
||
| The script will search Soulseek for a FLAC version of each local track, match by duration, and enqueue any matches for download. Any songs that could not be found will be saved to `unfound_songs.csv`. | ||
| ### Config File | ||
|
|
||
| ### Step 2 -- Rename downloaded FLACs | ||
| Create `config.yml` in your working directory or `~/.config/slskd-transform/config.yml`: | ||
|
|
||
| Once downloads are complete, use the renaming script to organize them: | ||
| ```yaml | ||
| # slskd connection | ||
| host: "http://127.0.0.1:5030" | ||
| api_key: "your-api-key" | ||
| verify_ssl: false | ||
|
|
||
| 1. Open `rename-files.py` and set the source and destination directories: | ||
| # Search settings | ||
| music_dir: "./music" | ||
| duration_tolerance: 15 | ||
| num_threads: 5 | ||
| search_timeout: 60 | ||
| format: "flac" | ||
| recursive: false | ||
|
|
||
| ```python | ||
| source_directory = '/path/to/slskd/downloads' | ||
| destination_directory = '/path/to/organized/music' | ||
| # Rename settings | ||
| source_dir: "./downloads" | ||
| destination_dir: "./organized" | ||
| ``` | ||
|
|
||
| 2. Run the script: | ||
| ### Environment Variables | ||
|
|
||
| All settings can be configured via `SLSKD_` prefixed environment variables: | ||
|
|
||
| | Variable | Description | Default | | ||
| |----------|-------------|---------| | ||
| | `SLSKD_HOST` | slskd instance URL | `http://127.0.0.1:5030` | | ||
| | `SLSKD_API_KEY` | slskd API key | -- | | ||
| | `SLSKD_VERIFY_SSL` | Enable SSL verification | `false` | | ||
| | `SLSKD_MUSIC_DIR` | Source directory with lossy files | `./music` | | ||
| | `SLSKD_DURATION_TOLERANCE` | Max duration difference (seconds) | `15` | | ||
| | `SLSKD_NUM_THREADS` | Concurrent search threads | `5` | | ||
| | `SLSKD_SEARCH_TIMEOUT` | Wait time for search results (seconds) | `60` | | ||
| | `SLSKD_FORMAT` | Target format to search for | `flac` | | ||
| | `SLSKD_RECURSIVE` | Scan directories recursively | `false` | | ||
| | `SLSKD_SOURCE_DIR` | Download directory for rename | `./downloads` | | ||
| | `SLSKD_DESTINATION_DIR` | Output directory for rename | `./organized` | | ||
|
|
||
| ### CLI Reference | ||
|
|
||
| ```bash | ||
| python rename-files.py | ||
| ``` | ||
| slskd-transform [OPTIONS] COMMAND [ARGS]... | ||
|
|
||
| Options: | ||
| -c, --config PATH Path to config.yml | ||
| --host TEXT slskd host URL | ||
| --api-key TEXT slskd API key | ||
| --no-verify-ssl Disable SSL verification | ||
| -t, --threads INTEGER Number of search threads | ||
| --help Show help | ||
|
|
||
| Commands: | ||
| search Search Soulseek for lossless versions and enqueue downloads | ||
| rename Rename downloaded FLACs using metadata | ||
| ``` | ||
|
|
||
| All `.flac` files in the source directory (including subdirectories) will be renamed to `Artist - Title.flac` and moved to the destination. | ||
| **search options:** | ||
| ``` | ||
| -m, --music-dir PATH Directory with lossy source files | ||
| -r, --recursive Scan music directory recursively | ||
| -f, --format TEXT Target format (default: flac) | ||
| --tolerance INTEGER Duration match tolerance in seconds | ||
| --timeout INTEGER Seconds to wait for search results | ||
| ``` | ||
|
|
||
| ## Configuration | ||
| **rename options:** | ||
| ``` | ||
| -s, --source-dir PATH Directory where slskd downloads land | ||
| -d, --dest-dir PATH Destination for renamed files | ||
| ``` | ||
|
|
||
| | Parameter | Location | Default | Description | | ||
| |---|---|---|---| | ||
| | `host` | `main.py` | `http://127.0.0.1:5030` | slskd instance URL | | ||
| | `api_key` | `main.py` | -- | Your slskd API key | | ||
| | `MUSIC_DIR` | `main.py` | `./music` | Directory containing lossy source files | | ||
| | `duration_tolerance` | `main.py` | `15` (seconds) | Maximum duration difference for a match | | ||
| | `num_threads` | `main.py` | `5` | Number of concurrent search threads | | ||
| | `source_directory` | `rename-files.py` | -- | Where slskd downloads land | | ||
| | `destination_directory` | `rename-files.py` | -- | Where renamed FLACs are moved | | ||
| ## Docker | ||
|
|
||
| ```bash | ||
| docker run --rm \ | ||
| -e SLSKD_HOST=http://slskd:5030 \ | ||
| -e SLSKD_API_KEY=your-key \ | ||
| -v /path/to/lossy:/app/music:ro \ | ||
| -v /path/to/downloads:/app/downloads \ | ||
| ghcr.io/geiserx/slskd-transform:2.0.0 search --recursive | ||
| ``` | ||
|
|
||
| Or in a compose stack alongside slskd: | ||
|
|
||
| ```yaml | ||
| services: | ||
| slskd: | ||
| image: slskd/slskd:0.21.4 | ||
| ports: | ||
| - "5030:5030" | ||
| volumes: | ||
| - ./slskd-data:/app | ||
|
|
||
| slskd-transform: | ||
| image: ghcr.io/geiserx/slskd-transform:2.0.0 | ||
| environment: | ||
| SLSKD_HOST: http://slskd:5030 | ||
| SLSKD_API_KEY: your-key | ||
| volumes: | ||
| - /path/to/lossy:/app/music:ro | ||
| - ./slskd-data/downloads:/app/downloads | ||
| command: ["search", "--recursive"] | ||
| ``` | ||
|
|
||
| ## How It Works | ||
|
|
||
|
|
@@ -110,16 +195,10 @@ Local library Soulseek network Your disk | |
| │ │ │ | ||
| └──── no match ──> unfound_songs.csv │ | ||
| v | ||
| rename-files.py | ||
| slskd-transform rename | ||
| Artist - Title.flac | ||
| ``` | ||
|
|
||
| 1. **Scan** -- `main.py` reads every file in the `music/` directory using [mutagen](https://mutagen.readthedocs.io/) to extract the audio duration. | ||
| 2. **Search** -- For each track, a Soulseek search is issued via the slskd API with the query `"<song name> flac"`. Searches run in parallel across multiple threads with staggered starts to avoid flooding the network. | ||
| 3. **Match** -- Each search result is compared by duration. The first result within the tolerance window is selected. | ||
| 4. **Enqueue** -- Matched files are enqueued for download through slskd. Failed or unmatched tracks are logged. | ||
| 5. **Rename** -- After downloading, `rename-files.py` walks the download directory, reads FLAC metadata, and moves files into a flat structure with clean filenames. | ||
|
|
||
| ## Related Music Tools | ||
|
|
||
| | Project | Description | | ||
|
|
@@ -128,7 +207,6 @@ Local library Soulseek network Your disk | |
| | [audio-transcode-watcher](https://github.com/GeiserX/audio-transcode-watcher) | Automated multi-format audio transcoding with lyrics fetching | | ||
| | [jellyfin-encoder](https://github.com/GeiserX/jellyfin-encoder) | Automatic 720p HEVC/AV1 transcoding for Jellyfin | | ||
|
|
||
|
|
||
| ## License | ||
|
|
||
| This project is licensed under the [MIT License](LICENSE). | ||
| This project is licensed under the [GPL-3.0 License](LICENSE). | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,19 @@ | ||
| # slskd-transform configuration | ||
| # All fields are optional — defaults shown below | ||
|
|
||
| # slskd connection | ||
| host: "http://127.0.0.1:5030" | ||
| api_key: "" # REQUIRED (or set SLSKD_API_KEY env var) | ||
| verify_ssl: false | ||
|
|
||
| # Search settings | ||
| music_dir: "./music" # Where your lossy source files live | ||
| duration_tolerance: 15 # Seconds of acceptable duration difference | ||
| num_threads: 5 # Concurrent search threads | ||
| search_timeout: 60 # Seconds to wait for search results | ||
| format: "flac" # Target format (appended to search query) | ||
| recursive: false # Scan music_dir recursively | ||
|
|
||
| # Rename settings | ||
| source_dir: "./downloads" # Where slskd puts completed downloads | ||
| destination_dir: "./organized" # Where renamed FLACs are moved to |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Critical: Permission denied—appuser cannot create directories under root-owned /app.
WORKDIR /app(line 11) creates/appowned by root. After switching toappuser(line 15), theRUN mkdir(line 17) fails because appuser lacks write permissions to/app.🐛 Proposed fix: create directories before switching user
📝 Committable suggestion
🤖 Prompt for AI Agents