flacfetch 0.15.3__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (70) hide show
  1. flacfetch-0.15.3/ARCHITECTURE.md +97 -0
  2. flacfetch-0.15.3/LICENSE +22 -0
  3. flacfetch-0.15.3/MANIFEST.in +11 -0
  4. flacfetch-0.15.3/PKG-INFO +324 -0
  5. flacfetch-0.15.3/PLAN.md +118 -0
  6. flacfetch-0.15.3/README.md +252 -0
  7. flacfetch-0.15.3/flacfetch/__init__.py +30 -0
  8. flacfetch-0.15.3/flacfetch/api/__init__.py +17 -0
  9. flacfetch-0.15.3/flacfetch/api/auth.py +44 -0
  10. flacfetch-0.15.3/flacfetch/api/main.py +207 -0
  11. flacfetch-0.15.3/flacfetch/api/models.py +247 -0
  12. flacfetch-0.15.3/flacfetch/api/routes/__init__.py +19 -0
  13. flacfetch-0.15.3/flacfetch/api/routes/config.py +319 -0
  14. flacfetch-0.15.3/flacfetch/api/routes/credentials.py +66 -0
  15. flacfetch-0.15.3/flacfetch/api/routes/download.py +294 -0
  16. flacfetch-0.15.3/flacfetch/api/routes/health.py +288 -0
  17. flacfetch-0.15.3/flacfetch/api/routes/search.py +153 -0
  18. flacfetch-0.15.3/flacfetch/api/routes/torrents.py +227 -0
  19. flacfetch-0.15.3/flacfetch/api/services/__init__.py +36 -0
  20. flacfetch-0.15.3/flacfetch/api/services/credential_check.py +497 -0
  21. flacfetch-0.15.3/flacfetch/api/services/disk_manager.py +216 -0
  22. flacfetch-0.15.3/flacfetch/api/services/download_manager.py +562 -0
  23. flacfetch-0.15.3/flacfetch/api/services/search_cache.py +279 -0
  24. flacfetch-0.15.3/flacfetch/core/categorize.py +292 -0
  25. flacfetch-0.15.3/flacfetch/core/interfaces.py +68 -0
  26. flacfetch-0.15.3/flacfetch/core/log.py +17 -0
  27. flacfetch-0.15.3/flacfetch/core/manager.py +479 -0
  28. flacfetch-0.15.3/flacfetch/core/matching.py +53 -0
  29. flacfetch-0.15.3/flacfetch/core/models.py +319 -0
  30. flacfetch-0.15.3/flacfetch/downloaders/spotify.py +419 -0
  31. flacfetch-0.15.3/flacfetch/downloaders/torrent.py +356 -0
  32. flacfetch-0.15.3/flacfetch/downloaders/youtube.py +123 -0
  33. flacfetch-0.15.3/flacfetch/interface/cli.py +1680 -0
  34. flacfetch-0.15.3/flacfetch/interface/cli_remote.py +1585 -0
  35. flacfetch-0.15.3/flacfetch/providers/ops.py +484 -0
  36. flacfetch-0.15.3/flacfetch/providers/red.py +485 -0
  37. flacfetch-0.15.3/flacfetch/providers/spotify.py +251 -0
  38. flacfetch-0.15.3/flacfetch/providers/youtube.py +165 -0
  39. flacfetch-0.15.3/flacfetch.egg-info/PKG-INFO +324 -0
  40. flacfetch-0.15.3/flacfetch.egg-info/SOURCES.txt +68 -0
  41. flacfetch-0.15.3/flacfetch.egg-info/dependency_links.txt +1 -0
  42. flacfetch-0.15.3/flacfetch.egg-info/entry_points.txt +3 -0
  43. flacfetch-0.15.3/flacfetch.egg-info/requires.txt +48 -0
  44. flacfetch-0.15.3/flacfetch.egg-info/top_level.txt +1 -0
  45. flacfetch-0.15.3/pyproject.toml +161 -0
  46. flacfetch-0.15.3/requirements.txt +4 -0
  47. flacfetch-0.15.3/setup.cfg +4 -0
  48. flacfetch-0.15.3/setup.py +36 -0
  49. flacfetch-0.15.3/tests/test_api_auth.py +96 -0
  50. flacfetch-0.15.3/tests/test_api_config.py +315 -0
  51. flacfetch-0.15.3/tests/test_api_health_ytdlp.py +140 -0
  52. flacfetch-0.15.3/tests/test_api_integration.py +82 -0
  53. flacfetch-0.15.3/tests/test_api_models.py +267 -0
  54. flacfetch-0.15.3/tests/test_api_routes.py +152 -0
  55. flacfetch-0.15.3/tests/test_api_services.py +334 -0
  56. flacfetch-0.15.3/tests/test_cli_cookies.py +335 -0
  57. flacfetch-0.15.3/tests/test_cli_remote.py +159 -0
  58. flacfetch-0.15.3/tests/test_core.py +31 -0
  59. flacfetch-0.15.3/tests/test_logging.py +33 -0
  60. flacfetch-0.15.3/tests/test_manager.py +662 -0
  61. flacfetch-0.15.3/tests/test_matching.py +29 -0
  62. flacfetch-0.15.3/tests/test_models.py +186 -0
  63. flacfetch-0.15.3/tests/test_ops.py +207 -0
  64. flacfetch-0.15.3/tests/test_provider_errors.py +232 -0
  65. flacfetch-0.15.3/tests/test_red.py +226 -0
  66. flacfetch-0.15.3/tests/test_spotify.py +491 -0
  67. flacfetch-0.15.3/tests/test_torrent_downloader.py +152 -0
  68. flacfetch-0.15.3/tests/test_transcode_hf_noise.py +106 -0
  69. flacfetch-0.15.3/tests/test_youtube.py +214 -0
  70. flacfetch-0.15.3/tests/test_youtube_cookies.py +239 -0
@@ -0,0 +1,97 @@
1
+ # Architecture & Design
2
+
3
+ This document details the architectural decisions, component design, and key learnings from the development of **flacfetch**.
4
+
5
+ ## 1. High-Level Architecture
6
+
7
+ The system follows **Clean Architecture** principles to decouple core logic from external providers and interfaces.
8
+
9
+ ```mermaid
10
+ graph TD
11
+ CLI[CLI Adapter] --> Core
12
+ Lib[Library User] --> Core
13
+
14
+ subgraph Core
15
+ FM[FetchManager]
16
+ Models[Release, TrackQuery, Quality]
17
+ Interfaces[Provider, Downloader]
18
+ end
19
+
20
+ FM --> Providers
21
+ FM --> Downloaders
22
+
23
+ subgraph Providers
24
+ RED[REDProvider]
25
+ YouTube[YoutubeProvider]
26
+ end
27
+
28
+ subgraph Downloaders
29
+ LibTorrent[TorrentDownloader]
30
+ YtDlp[YoutubeDownloader]
31
+ end
32
+ ```
33
+
34
+ ### Core Components
35
+
36
+ * **FetchManager**: The central orchestrator. It aggregates results from registered providers, applies sorting/prioritization logic, and delegates downloading.
37
+ * **Models**:
38
+ * `Release`: Unified representation of a search result. Abstracts away differences between a Torrent and a YouTube video. Contains metadata (Year, Label, Views) and download info.
39
+ * `Quality`: Value object representing format (FLAC/Opus/AAC), bitrate, and source media. Implements comparison logic (`__lt__`) for sorting.
40
+ * **Interfaces**:
41
+ * `Provider`: Abstract base class for search sources.
42
+ * `Downloader`: Abstract base class for download mechanisms.
43
+
44
+ ## 2. Key Design Choices
45
+
46
+ ### 2.1. Selective BitTorrent Downloading
47
+ **Challenge**: Private trackers usually organize content by **Album**, but users often want a single **Track**. Downloading a 500MB FLAC album for one 30MB song is inefficient.
48
+ **Solution**:
49
+ * **Search**: `REDProvider` uses the `filelist` API parameter to find torrents containing the specific track title.
50
+ * **Matching**: It parses the file list string (`filename{{{size}}}|||...`) to identify the exact target file index.
51
+ * **Download**: `TorrentDownloader` uses `libtorrent`'s `prioritize_files` API. It sets the target file priority to `7` (High) and all others to `0` (Skip), downloading only the necessary chunks.
52
+
53
+ ### 2.2. Hybrid Prioritization Logic
54
+ **Challenge**: "Best" means different things for different sources.
55
+ * **RED**: "Best" = Original Release (Oldest), Lossless, Healthy (Seeders).
56
+ * **YouTube**: "Best" = Modern Codec (Newest), Official Source (Topic Channel), High Bitrate.
57
+ **Solution**:
58
+ The `FetchManager` implements a weighted sort key:
59
+ 1. **Match Score**: Does the filename exactly match the query? (Crucial for filtering junk).
60
+ 2. **Official Score**: (YouTube only) Is it a "Topic" channel or "Official Audio"? (Heavily boosted).
61
+ 3. **Release Type**: (RED) Album > Single > EP.
62
+ 4. **Health**: Seeders (RED) / Views (YouTube - implicitly handled via display).
63
+ 5. **Quality**: Lossless > High Bitrate.
64
+ 6. **Year (Contextual)**:
65
+ * *RED*: **Oldest First** (Prefer original pressings).
66
+ * *YouTube*: **Newest First** (Prefer modern Opus uploads over legacy 2011 AAC uploads).
67
+
68
+ ### 2.3. YouTube Quality & Reliability
69
+ **Learnings**:
70
+ * **Metadata vs Reality**: YouTube metadata (via `yt-dlp`) can be misleading. Older videos might list "AAC" but provide very low bitrate (48kbps) streams even if `itag` suggests higher potential.
71
+ * **Bitrate Guessing**: Estimating bitrate from file size is dangerous for video containers. We switched to relying strictly on `abr` (Audio Bitrate) or known `itag` mapping (e.g., 251 -> Opus 130k).
72
+ * **Proxy for Quality**: Since accurate bitrate is hard to guarantee without downloading, we use **Upload Year** as a strong proxy. Videos uploaded post-2015 (and especially post-2020) almost always offer high-quality Opus streams. Pre-2015 uploads are often legacy AAC with lower fidelity.
73
+ * **Visuals**: The CLI color-codes the Year (Green > 2020, Red < 2015) instead of showing potentially inaccurate bitrate numbers, empowering the user to choose based on "Freshness".
74
+
75
+ ### 2.4. Security: No Hardcoded URLs
76
+ **Design Decision**: Tracker API base URLs are **never** stored in the source code. Both `REDProvider` and `OPSProvider` require a `base_url` parameter that must be provided at runtime (typically via environment variables).
77
+
78
+ **Rationale**:
79
+ 1. **Privacy**: Private trackers prefer their URLs not be publicly indexed.
80
+ 2. **Safety**: Ensures test suites cannot accidentally hit real tracker APIs without proper mocking.
81
+ 3. **Flexibility**: Allows easy switching between different tracker instances if needed.
82
+
83
+ ## 3. Implementation Details
84
+
85
+ ### RED Provider
86
+ * **Lazy Loading**: Fetching file lists for *every* search result is slow. We implemented a default search limit (20 groups) to prevent rate-limiting while still finding the best match.
87
+ * **Lossless Filter**: Hard-coded to only return `FLAC` results to ensure archival quality from trackers.
88
+ * **Base URL Required**: The `base_url` constructor parameter is mandatory; if not provided, an error is raised.
89
+
90
+ ### YouTube Provider
91
+ * **Topic Search**: Appends "topic" to search queries to surface auto-generated "Art Tracks" (high quality, static image) which are preferred over user uploads.
92
+ * **URL Handling**: Constructs `youtu.be` short links for easy sharing/checking.
93
+
94
+ ## 4. Future Improvements
95
+ * **Metadata Tagging**: Auto-tag downloaded files using MusicBrainz/Discogs.
96
+ * **Spectral Analysis**: Integrate `ffmpeg` or `sox` to verify frequency cutoffs post-download automatically.
97
+
@@ -0,0 +1,22 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2024 FlacFetch Contributors
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
22
+
@@ -0,0 +1,11 @@
1
+ include LICENSE
2
+ include README.md
3
+ include ARCHITECTURE.md
4
+ include PLAN.md
5
+ include pyproject.toml
6
+ include requirements.txt
7
+ recursive-include flacfetch *.py
8
+ recursive-include tests *.py
9
+ prune venv
10
+ prune .github
11
+ prune api_docs
@@ -0,0 +1,324 @@
1
+ Metadata-Version: 2.4
2
+ Name: flacfetch
3
+ Version: 0.15.3
4
+ Summary: Search and download high-quality audio from multiple sources
5
+ Home-page: https://github.com/yourusername/flacfetch
6
+ Author: Your Name
7
+ Author-email: Andrew Beveridge <andrew@beveridge.uk>
8
+ License-Expression: MIT
9
+ Project-URL: Homepage, https://github.com/nomadkaraoke/flacfetch
10
+ Project-URL: Repository, https://github.com/nomadkaraoke/flacfetch
11
+ Project-URL: Issues, https://github.com/nomadkaraoke/flacfetch/issues
12
+ Keywords: audio,music,flac,download,youtube,torrent
13
+ Classifier: Development Status :: 4 - Beta
14
+ Classifier: Intended Audience :: End Users/Desktop
15
+ Classifier: Operating System :: OS Independent
16
+ Classifier: Programming Language :: Python :: 3
17
+ Classifier: Programming Language :: Python :: 3.10
18
+ Classifier: Programming Language :: Python :: 3.11
19
+ Classifier: Programming Language :: Python :: 3.12
20
+ Classifier: Programming Language :: Python :: 3.13
21
+ Classifier: Topic :: Multimedia :: Sound/Audio
22
+ Classifier: Environment :: Console
23
+ Requires-Python: >=3.8
24
+ Description-Content-Type: text/markdown
25
+ License-File: LICENSE
26
+ Requires-Dist: requests>=2.28.0
27
+ Requires-Dist: yt-dlp>=2023.0.0
28
+ Requires-Dist: transmission-rpc>=7.0.0
29
+ Provides-Extra: remote
30
+ Requires-Dist: httpx>=0.28.0; extra == "remote"
31
+ Requires-Dist: playwright>=1.40.0; extra == "remote"
32
+ Provides-Extra: api
33
+ Requires-Dist: fastapi>=0.115.0; extra == "api"
34
+ Requires-Dist: uvicorn[standard]>=0.24.0; extra == "api"
35
+ Requires-Dist: pydantic>=2.0.0; extra == "api"
36
+ Requires-Dist: httpx>=0.28.0; extra == "api"
37
+ Requires-Dist: google-cloud-storage>=2.13.0; extra == "api"
38
+ Requires-Dist: google-cloud-secret-manager>=2.16.0; extra == "api"
39
+ Provides-Extra: spotify
40
+ Requires-Dist: spotipy>=2.25.1; extra == "spotify"
41
+ Provides-Extra: ejs
42
+ Requires-Dist: yt-dlp-ejs>=0.3.0; extra == "ejs"
43
+ Provides-Extra: dev
44
+ Requires-Dist: pytest>=7.0.0; extra == "dev"
45
+ Requires-Dist: pytest-mock>=3.10.0; extra == "dev"
46
+ Requires-Dist: pytest-cov>=4.0.0; extra == "dev"
47
+ Requires-Dist: pytest-asyncio>=0.23.0; extra == "dev"
48
+ Requires-Dist: ruff>=0.1.0; extra == "dev"
49
+ Requires-Dist: mypy>=1.0.0; extra == "dev"
50
+ Requires-Dist: types-requests>=2.28.0; extra == "dev"
51
+ Provides-Extra: all
52
+ Requires-Dist: httpx>=0.28.0; extra == "all"
53
+ Requires-Dist: playwright>=1.40.0; extra == "all"
54
+ Requires-Dist: fastapi>=0.115.0; extra == "all"
55
+ Requires-Dist: uvicorn[standard]>=0.24.0; extra == "all"
56
+ Requires-Dist: pydantic>=2.0.0; extra == "all"
57
+ Requires-Dist: google-cloud-storage>=2.13.0; extra == "all"
58
+ Requires-Dist: google-cloud-secret-manager>=2.16.0; extra == "all"
59
+ Requires-Dist: spotipy>=2.25.1; extra == "all"
60
+ Requires-Dist: yt-dlp-ejs>=0.3.0; extra == "all"
61
+ Requires-Dist: pytest>=7.0.0; extra == "all"
62
+ Requires-Dist: pytest-mock>=3.10.0; extra == "all"
63
+ Requires-Dist: pytest-cov>=4.0.0; extra == "all"
64
+ Requires-Dist: pytest-asyncio>=0.23.0; extra == "all"
65
+ Requires-Dist: ruff>=0.1.0; extra == "all"
66
+ Requires-Dist: mypy>=1.0.0; extra == "all"
67
+ Requires-Dist: types-requests>=2.28.0; extra == "all"
68
+ Dynamic: author
69
+ Dynamic: home-page
70
+ Dynamic: license-file
71
+ Dynamic: requires-python
72
+
73
+ # flacfetch
74
+
75
+ [![PyPI version](https://badge.fury.io/py/flacfetch.svg)](https://pypi.org/project/flacfetch/)
76
+ [![Python Version](https://img.shields.io/pypi/pyversions/flacfetch.svg)](https://pypi.org/project/flacfetch/)
77
+ [![Tests](https://github.com/nomadkaraoke/flacfetch/workflows/Tests/badge.svg)](https://github.com/nomadkaraoke/flacfetch/actions/workflows/test.yml)
78
+ [![codecov](https://codecov.io/gh/nomadkaraoke/flacfetch/branch/main/graph/badge.svg)](https://codecov.io/gh/nomadkaraoke/flacfetch)
79
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
80
+
81
+ **flacfetch** is a Python tool designed to search for and download high-quality audio files from various sources. It is optimized for finding specific tracks (songs) across both private music trackers and public sources, with intelligent prioritization of "Official" and "Original" releases.
82
+
83
+ ## Features
84
+
85
+ - **Precise Track Search**:
86
+ - **Private Music Trackers**: RED and OPS (API integration). Uses advanced file list filtering to find specific songs within album torrents, downloading only the required track.
87
+ - **Streaming Services**: Spotify (via `librespot`, requires Premium account) - CD Quality FLAC (44.1kHz/16-bit).
88
+ - **Public Sources**: YouTube (via `yt-dlp`).
89
+ - **Smart Prioritization**:
90
+ - **Official Sources**: Automatically prioritizes "Topic" channels and "Official Audio" on YouTube. Spotify results are always from official sources.
91
+ - **Quality Heuristics**:
92
+ - **Trackers (RED/OPS)**: Prioritizes Lossless (FLAC) and healthy torrents (Seeders). Matches filename exactly to your query.
93
+ - **Spotify**: CD-quality FLAC (44.1kHz/16-bit) via librespot capture. Prioritizes by popularity.
94
+ - **YouTube**: Prioritizes newer uploads (Opus codec) over legacy uploads (AAC). Color-codes upload years to help you spot modern, high-quality streams (Green: 2020+, Yellow: 2015-2019, Red: <2015).
95
+ - **Flexible Interaction**:
96
+ - **Interactive Mode**: Present search results to the user for manual selection with rich, color-coded metadata (Seeders, Views, Duration).
97
+ - **Automatic Mode**: Automatically select the highest ranked release.
98
+ - **Smart Downloading**:
99
+ - **Selective BitTorrent**: Uses Transmission daemon to download *only* the specific file matching your search query from larger album torrents (saving bandwidth).
100
+ - **Direct Downloads**: Handles HTTP/Stream downloads for public sources.
101
+
102
+ ## Requirements
103
+
104
+ - Python 3.10+
105
+ - `requests`
106
+ - `yt-dlp`
107
+ - `transmission-rpc`
108
+ - **Transmission** (daemon) - *Required for BitTorrent downloads* (Optional if only using YouTube)
109
+
110
+ ### Installing Transmission
111
+
112
+ Transmission is a lightweight, cross-platform BitTorrent client with RPC support.
113
+
114
+ - **Ubuntu/Debian**: `sudo apt install transmission-daemon`
115
+ - **macOS**: `brew install transmission-cli`
116
+ - **Windows**: Download from [transmissionbt.com](https://transmissionbt.com)
117
+
118
+ flacfetch will automatically start the transmission daemon if it's not running.
119
+
120
+ ## Installation
121
+
122
+ ### From PyPI (Recommended)
123
+
124
+ ```bash
125
+ pip install flacfetch
126
+ ```
127
+
128
+ ### From Source
129
+
130
+ ```bash
131
+ git clone https://github.com/nomadkaraoke/flacfetch.git
132
+ cd flacfetch
133
+ pip install .
134
+ ```
135
+
136
+ ### Development Installation
137
+
138
+ ```bash
139
+ git clone https://github.com/nomadkaraoke/flacfetch.git
140
+ cd flacfetch
141
+ pip install -e ".[dev]"
142
+ ```
143
+
144
+ ## Usage
145
+
146
+ ### CLI Usage
147
+
148
+ **Standard Search (Artist - Title)**
149
+ ```bash
150
+ flacfetch "Seether" "Tonight"
151
+ ```
152
+
153
+ **Explicit Arguments (Recommended for precision)**
154
+ ```bash
155
+ flacfetch --artist "Seether" --title "Tonight"
156
+ ```
157
+
158
+ **Auto-download Highest Quality**
159
+ ```bash
160
+ flacfetch --auto --artist "Seether" --title "Tonight"
161
+ ```
162
+
163
+ **Output Options**
164
+ ```bash
165
+ # Specify output directory
166
+ flacfetch --artist "Seether" --title "Tonight" -o ~/Music
167
+
168
+ # Auto-rename to "ARTIST - TITLE.ext"
169
+ flacfetch --artist "Seether" --title "Tonight" --rename
170
+
171
+ # Specify exact filename
172
+ flacfetch --artist "Seether" --title "Tonight" --filename "my_song"
173
+
174
+ # Combine options
175
+ flacfetch --artist "Seether" --title "Tonight" -o ~/Music --rename
176
+ ```
177
+
178
+ **Verbose Logging**
179
+ ```bash
180
+ flacfetch -v "Seether" "Tonight"
181
+ ```
182
+
183
+ **Configuration**
184
+
185
+ To use private music trackers, you must provide both an API Key and API URL:
186
+ ```bash
187
+ # RED
188
+ export RED_API_KEY="your_api_key_here"
189
+ export RED_API_URL="your_tracker_url_here"
190
+ # OR
191
+ flacfetch "..." --red-key "your_key" --red-url "your_url"
192
+
193
+ # OPS
194
+ export OPS_API_KEY="your_api_key_here"
195
+ export OPS_API_URL="your_tracker_url_here"
196
+ # OR
197
+ flacfetch "..." --ops-key "your_key" --ops-url "your_url"
198
+ ```
199
+
200
+ **Spotify Configuration** (Optional - requires Premium account)
201
+
202
+ Spotify provides CD-quality audio (44.1kHz/16-bit) captured via librespot and converted to FLAC. This uses the official Spotify Web API for authentication (OAuth) and librespot for audio capture.
203
+
204
+ **Prerequisites:**
205
+ - Spotify Premium account
206
+ - `librespot` binary: `brew install librespot` or `cargo install librespot`
207
+ - `ffmpeg` for audio conversion
208
+
209
+ **Setup:**
210
+ ```bash
211
+ # 1. Install Spotify extra dependencies
212
+ pip install flacfetch[spotify]
213
+
214
+ # 2. Create a Spotify Developer App
215
+ # Go to: https://developer.spotify.com/dashboard
216
+ # Click "Create App"
217
+ # Set redirect URI to: http://127.0.0.1:8888/callback
218
+ # Note your Client ID and Client Secret
219
+
220
+ # 3. Set environment variables
221
+ export SPOTIPY_CLIENT_ID='your-client-id'
222
+ export SPOTIPY_CLIENT_SECRET='your-client-secret'
223
+ export SPOTIPY_REDIRECT_URI='http://127.0.0.1:8888/callback'
224
+
225
+ # 4. First run will open browser for OAuth login (token cached automatically)
226
+ flacfetch "Artist" "Title"
227
+
228
+ # Disable Spotify if needed
229
+ flacfetch "Artist" "Title" --no-spotify
230
+ ```
231
+
232
+ **How it works:**
233
+ 1. Uses Spotify Web API (via spotipy) for search and playback control
234
+ 2. Starts librespot as a Spotify Connect device with OAuth token
235
+ 3. Triggers playback via Web API, captures raw PCM via pipe backend
236
+ 4. Converts PCM to FLAC using ffmpeg
237
+
238
+ **Note:** The redirect URI must use `127.0.0.1` (not `localhost`) as per Spotify's updated security requirements.
239
+
240
+ **Provider Priority**
241
+
242
+ When multiple providers are configured, flacfetch searches them in priority order. By default: **RED > OPS > Spotify > YouTube**
243
+
244
+ This means RED is searched first, and only if it returns no results will OPS be searched, then Spotify, then YouTube. This prioritizes lossless sources first, then high-quality streaming.
245
+
246
+ ```bash
247
+ # Use default priority (RED > OPS > Spotify > YouTube)
248
+ export RED_API_KEY="..."
249
+ export RED_API_URL="..."
250
+ export OPS_API_KEY="..."
251
+ export OPS_API_URL="..."
252
+ flacfetch "Artist" "Title" --auto
253
+
254
+ # Custom priority (e.g., prefer Spotify over trackers)
255
+ flacfetch "Artist" "Title" --provider-priority "Spotify,RED,OPS,YouTube"
256
+
257
+ # Or via environment variable
258
+ export FLACFETCH_PROVIDER_PRIORITY="OPS,RED,Spotify,YouTube"
259
+ flacfetch "Artist" "Title" --auto
260
+
261
+ # Disable fallback (only search highest priority provider)
262
+ flacfetch "Artist" "Title" --auto --no-fallback
263
+ ```
264
+
265
+ ### Library Usage
266
+
267
+ **Quick Example:**
268
+
269
+ ```python
270
+ from flacfetch.core.manager import FetchManager
271
+ from flacfetch.core.models import TrackQuery
272
+ from flacfetch.providers.red import REDProvider
273
+ from flacfetch.providers.ops import OPSProvider
274
+ from flacfetch.providers.spotify import SpotifyProvider # Optional
275
+ from flacfetch.downloaders.spotify import SpotifyDownloader # Optional
276
+
277
+ manager = FetchManager()
278
+ manager.add_provider(REDProvider(api_key="...", base_url="..."))
279
+ manager.add_provider(OPSProvider(api_key="...", base_url="..."))
280
+
281
+ # Spotify (requires SPOTIPY_CLIENT_ID, SPOTIPY_CLIENT_SECRET, SPOTIPY_REDIRECT_URI env vars)
282
+ spotify_provider = SpotifyProvider()
283
+ manager.add_provider(spotify_provider)
284
+ manager.register_downloader("Spotify", SpotifyDownloader(provider=spotify_provider))
285
+
286
+ # Search for a specific track
287
+ results = manager.search(TrackQuery(artist="Seether", title="Tonight"))
288
+ best = manager.select_best(results)
289
+
290
+ if best:
291
+ # Download returns the path to the downloaded file
292
+ file_path = manager.download(
293
+ best,
294
+ output_path="./downloads",
295
+ output_filename="Seether - Tonight" # Optional: custom filename
296
+ )
297
+ print(f"Downloaded to: {file_path}")
298
+ ```
299
+
300
+ **For comprehensive library documentation**, including:
301
+ - Complete API reference for all classes and methods
302
+ - Data models and type hints
303
+ - Provider configuration options
304
+ - Advanced usage patterns (filtering, custom sorting, batch processing)
305
+ - Error handling best practices
306
+ - 5+ detailed examples
307
+
308
+ See **[LIBRARY.md](LIBRARY.md)** for full library API documentation.
309
+
310
+ ## Architecture & Design
311
+
312
+ See [ARCHITECTURE.md](ARCHITECTURE.md) for detailed architecture, design choices, and implementation learnings.
313
+
314
+ ## Contributing
315
+
316
+ Contributions are welcome! Please see [CONTRIBUTING.md](CONTRIBUTING.md) for guidelines.
317
+
318
+ ## Legal Disclaimer
319
+
320
+ This tool is intended for use with content to which you have legal access. Users are responsible for complying with all applicable laws and terms of service for the supported providers.
321
+
322
+ ## License
323
+
324
+ MIT License - see [LICENSE](LICENSE) file for details.
@@ -0,0 +1,118 @@
1
+ # Architecture & Implementation Plan
2
+
3
+ ## 1. High-Level Architecture
4
+
5
+ The system will be designed using **Clean Architecture** principles to separate concerns between the core logic, external interfaces (CLI/Library), and infrastructure (APIs/Downloaders).
6
+
7
+ ### Core Components
8
+
9
+ 1. **Orchestrator (`FetchManager`)**: The main entry point that coordinates searching, selection, and downloading.
10
+ 2. **Domain Models**:
11
+ - `TrackQuery`: Input data (Artist, Title).
12
+ - `Release`: Represents a finding (Source, Quality, Metadata, Download Link/Magnet, **Target File**).
13
+ - `Quality`: Value object for audio quality (Format, Bitrate, Source Media) with comparison logic.
14
+ 3. **Interfaces (Abstract Base Classes)**:
15
+ - `Provider`: Interface for searching (e.g., `REDProvider`, `OPSProvider`, `YoutubeProvider`).
16
+ - `Downloader`: Interface for retrieving content (e.g., `TorrentDownloader`, `HttpDownloader`).
17
+ - `UserInterface`: Interface for interaction (handling selection prompts).
18
+
19
+ ### Component Diagram
20
+
21
+ ```mermaid
22
+ graph TD
23
+ CLI[CLI Adapter] --> Core
24
+ Lib[Library User] --> Core
25
+
26
+ subgraph Core
27
+ FM[FetchManager]
28
+ QS[QualitySorter]
29
+ Handler[InteractionHandler]
30
+ end
31
+
32
+ FM --> Providers
33
+ FM --> Downloaders
34
+
35
+ subgraph Providers
36
+ RED
37
+ OPS
38
+ YouTube
39
+ end
40
+
41
+ subgraph Downloaders
42
+ LibTorrent
43
+ YtDlp
44
+ DirectHttp
45
+ end
46
+ ```
47
+
48
+ ## 2. Detailed Design
49
+
50
+ ### 2.1 Provider System
51
+ Each provider implements a `search(query: TrackQuery) -> List[Release]` method.
52
+ - **REDProvider / OPSProvider**:
53
+ - Uses `ajax.php?action=browse` with `artistname` and `filelist` parameters to find torrents containing the specific track.
54
+ - Requires `base_url` parameter (set via environment variable) in addition to API key.
55
+ - Parses the `fileList` field in the response to identify the specific file within the torrent that matches the requested song.
56
+ - Prioritizes "Album" releases (Type 1) and original release years when presenting options.
57
+ - **PublicProviders**: Wrappers around `yt-dlp` or scraping logic for YouTube.
58
+
59
+ ### 2.2 Quality & Selection Logic
60
+ - **Quality Class**: Implements `__lt__`, `__eq__` to allow sorting.
61
+ - Hierarchy: `FLAC 24bit` > `FLAC 16bit` > `MP3 320` > `AAC` > `Other`.
62
+ - **Auto-Selection**: `FetchManager` sorts results by `Quality` and picks the top.
63
+ - **Interactive Selection**: `FetchManager` delegates to `InteractionHandler`.
64
+ - **CLI Implementation**: Prints list to stdout, reads index from stdin.
65
+ - **Library Implementation**: Accepts a callable/hook passed during initialization or method call.
66
+
67
+ ### 2.3 Downloader System
68
+ - **TorrentDownloader**: Uses `libtorrent`.
69
+ - **Selective Downloading**: Uses the `target_file` information from the `Release` to set file priorities. All files are set to priority 0 (skip) except the target song file, which is set to priority 7 (high).
70
+ - Manages session, adds magnet/torrent file, monitors progress, alerts on completion.
71
+ - **HttpDownloader**: Standard HTTP GET or `yt-dlp` process.
72
+
73
+ ## 3. Implementation Plan
74
+
75
+ ### Phase 1: Core & Interfaces
76
+ - Define `TrackQuery`, `Release`, `Quality` data classes.
77
+ - Define `Provider` and `Downloader` abstract base classes (ABCs).
78
+ - Implement `Quality` comparison logic.
79
+
80
+ ### Phase 2: Private Tracker Provider Integration
81
+ - Implement authentication (API Key + Base URL support via environment variables).
82
+ - Implement `REDProvider.search` and `OPSProvider.search` using specific `artistname` and `filelist` filtering.
83
+ - Parse `fileList` string (format: `filename{{{size}}}|||...`) to find target track.
84
+ - Parse results into `Release` objects, including `target_file` metadata.
85
+
86
+ ### Phase 3: BitTorrent Integration
87
+ - Set up `libtorrent` session management.
88
+ - Implement `TorrentDownloader` with selective file downloading support.
89
+ - Ensure protocol compatibility.
90
+
91
+ ### Phase 4: Public Providers
92
+ - Implement `YoutubeProvider` using `yt-dlp`.
93
+ - Implement `HttpDownloader`.
94
+
95
+ ### Phase 5: CLI & Library API
96
+ - Build `FetchManager` to tie it all together.
97
+ - Implement CLI with separated `--artist` and `--title` arguments for precision.
98
+ - Implement `ConsoleInteractionHandler` (CLI) and `CallbackInteractionHandler` (Library).
99
+
100
+ ## 4. Testing Strategy
101
+
102
+ ### 4.1 Unit Testing (`pytest`)
103
+ - **Domain Logic**: Test `Quality` sorting and `FetchManager` flow (mocking providers).
104
+ - **Providers**: Test API response parsing using mocked JSON responses (recorded from actual API calls or synthesized based on docs).
105
+ - **Downloaders**: Mock `libtorrent` session and file system operations.
106
+
107
+ ### 4.2 Integration Testing
108
+ - Test the "Search -> Select -> Download" pipeline with a "MockProvider" and "MockDownloader" to ensure the architecture holds together without hitting real external services.
109
+
110
+ ### 4.3 End-to-End Testing (Manual/Staging)
111
+ - Run against real APIs (limited) to verify contract assumptions.
112
+
113
+ ## 5. Technology Stack
114
+ - **Language**: Python 3.10+
115
+ - **HTTP Client**: `httpx` (async support) or `requests`.
116
+ - **Torrent**: `python-libtorrent` (via system package or pip).
117
+ - **CLI**: `argparse` (stdlib).
118
+ - **Testing**: `pytest`, `pytest-mock`.