flacfetch 0.3.0__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 (33) hide show
  1. flacfetch-0.3.0/ARCHITECTURE.md +88 -0
  2. flacfetch-0.3.0/LICENSE +22 -0
  3. flacfetch-0.3.0/MANIFEST.in +11 -0
  4. flacfetch-0.3.0/PKG-INFO +227 -0
  5. flacfetch-0.3.0/PLAN.md +117 -0
  6. flacfetch-0.3.0/README.md +187 -0
  7. flacfetch-0.3.0/flacfetch/__init__.py +6 -0
  8. flacfetch-0.3.0/flacfetch/core/interfaces.py +54 -0
  9. flacfetch-0.3.0/flacfetch/core/log.py +17 -0
  10. flacfetch-0.3.0/flacfetch/core/manager.py +240 -0
  11. flacfetch-0.3.0/flacfetch/core/matching.py +53 -0
  12. flacfetch-0.3.0/flacfetch/core/models.py +181 -0
  13. flacfetch-0.3.0/flacfetch/downloaders/torrent.py +330 -0
  14. flacfetch-0.3.0/flacfetch/downloaders/youtube.py +50 -0
  15. flacfetch-0.3.0/flacfetch/interface/cli.py +490 -0
  16. flacfetch-0.3.0/flacfetch/providers/ops.py +408 -0
  17. flacfetch-0.3.0/flacfetch/providers/redacted.py +407 -0
  18. flacfetch-0.3.0/flacfetch/providers/youtube.py +141 -0
  19. flacfetch-0.3.0/flacfetch.egg-info/PKG-INFO +227 -0
  20. flacfetch-0.3.0/flacfetch.egg-info/SOURCES.txt +31 -0
  21. flacfetch-0.3.0/flacfetch.egg-info/dependency_links.txt +1 -0
  22. flacfetch-0.3.0/flacfetch.egg-info/entry_points.txt +2 -0
  23. flacfetch-0.3.0/flacfetch.egg-info/requires.txt +11 -0
  24. flacfetch-0.3.0/flacfetch.egg-info/top_level.txt +1 -0
  25. flacfetch-0.3.0/pyproject.toml +117 -0
  26. flacfetch-0.3.0/requirements.txt +4 -0
  27. flacfetch-0.3.0/setup.cfg +4 -0
  28. flacfetch-0.3.0/setup.py +36 -0
  29. flacfetch-0.3.0/tests/test_core.py +31 -0
  30. flacfetch-0.3.0/tests/test_manager.py +199 -0
  31. flacfetch-0.3.0/tests/test_matching.py +29 -0
  32. flacfetch-0.3.0/tests/test_ops.py +163 -0
  33. flacfetch-0.3.0/tests/test_redacted.py +162 -0
@@ -0,0 +1,88 @@
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
+ Redacted[RedactedProvider]
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**: `RedactedProvider` 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
+ * **Redacted**: "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**: (Redacted) Album > Single > EP.
62
+ 4. **Health**: Seeders (Redacted) / Views (YouTube - implicitly handled via display).
63
+ 5. **Quality**: Lossless > High Bitrate.
64
+ 6. **Year (Contextual)**:
65
+ * *Redacted*: **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
+ ## 3. Implementation Details
76
+
77
+ ### Redacted Provider
78
+ * **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.
79
+ * **Lossless Filter**: Hard-coded to only return `FLAC` results to ensure archival quality from trackers.
80
+
81
+ ### YouTube Provider
82
+ * **Topic Search**: Appends "topic" to search queries to surface auto-generated "Art Tracks" (high quality, static image) which are preferred over user uploads.
83
+ * **URL Handling**: Constructs `youtu.be` short links for easy sharing/checking.
84
+
85
+ ## 4. Future Improvements
86
+ * **Metadata Tagging**: Auto-tag downloaded files using MusicBrainz/Discogs.
87
+ * **Spectral Analysis**: Integrate `ffmpeg` or `sox` to verify frequency cutoffs post-download automatically.
88
+
@@ -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,227 @@
1
+ Metadata-Version: 2.4
2
+ Name: flacfetch
3
+ Version: 0.3.0
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,redacted,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: dev
30
+ Requires-Dist: pytest>=7.0.0; extra == "dev"
31
+ Requires-Dist: pytest-mock>=3.10.0; extra == "dev"
32
+ Requires-Dist: pytest-cov>=4.0.0; extra == "dev"
33
+ Requires-Dist: ruff>=0.1.0; extra == "dev"
34
+ Requires-Dist: mypy>=1.0.0; extra == "dev"
35
+ Requires-Dist: types-requests>=2.28.0; extra == "dev"
36
+ Dynamic: author
37
+ Dynamic: home-page
38
+ Dynamic: license-file
39
+ Dynamic: requires-python
40
+
41
+ # flacfetch
42
+
43
+ [![PyPI version](https://badge.fury.io/py/flacfetch.svg)](https://pypi.org/project/flacfetch/)
44
+ [![Python Version](https://img.shields.io/pypi/pyversions/flacfetch.svg)](https://pypi.org/project/flacfetch/)
45
+ [![Tests](https://github.com/nomadkaraoke/flacfetch/workflows/Tests/badge.svg)](https://github.com/nomadkaraoke/flacfetch/actions/workflows/test.yml)
46
+ [![codecov](https://codecov.io/gh/nomadkaraoke/flacfetch/branch/main/graph/badge.svg)](https://codecov.io/gh/nomadkaraoke/flacfetch)
47
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
48
+
49
+ **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.
50
+
51
+ ## Features
52
+
53
+ - **Precise Track Search**:
54
+ - **Private Music Trackers**: Redacted and OPS (API integration). Uses advanced file list filtering to find specific songs within album torrents, downloading only the required track.
55
+ - **Public Sources**: YouTube (via `yt-dlp`).
56
+ - **Smart Prioritization**:
57
+ - **Official Sources**: Automatically prioritizes "Topic" channels and "Official Audio" on YouTube.
58
+ - **Quality Heuristics**:
59
+ - **Trackers (Redacted/OPS)**: Prioritizes Lossless (FLAC) and healthy torrents (Seeders). Matches filename exactly to your query.
60
+ - **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).
61
+ - **Flexible Interaction**:
62
+ - **Interactive Mode**: Present search results to the user for manual selection with rich, color-coded metadata (Seeders, Views, Duration).
63
+ - **Automatic Mode**: Automatically select the highest ranked release.
64
+ - **Smart Downloading**:
65
+ - **Selective BitTorrent**: Uses Transmission daemon to download *only* the specific file matching your search query from larger album torrents (saving bandwidth).
66
+ - **Direct Downloads**: Handles HTTP/Stream downloads for public sources.
67
+
68
+ ## Requirements
69
+
70
+ - Python 3.10+
71
+ - `requests`
72
+ - `yt-dlp`
73
+ - `transmission-rpc`
74
+ - **Transmission** (daemon) - *Required for BitTorrent downloads* (Optional if only using YouTube)
75
+
76
+ ### Installing Transmission
77
+
78
+ Transmission is a lightweight, cross-platform BitTorrent client with RPC support.
79
+
80
+ - **Ubuntu/Debian**: `sudo apt install transmission-daemon`
81
+ - **macOS**: `brew install transmission-cli`
82
+ - **Windows**: Download from [transmissionbt.com](https://transmissionbt.com)
83
+
84
+ flacfetch will automatically start the transmission daemon if it's not running.
85
+
86
+ ## Installation
87
+
88
+ ### From PyPI (Recommended)
89
+
90
+ ```bash
91
+ pip install flacfetch
92
+ ```
93
+
94
+ ### From Source
95
+
96
+ ```bash
97
+ git clone https://github.com/nomadkaraoke/flacfetch.git
98
+ cd flacfetch
99
+ pip install .
100
+ ```
101
+
102
+ ### Development Installation
103
+
104
+ ```bash
105
+ git clone https://github.com/nomadkaraoke/flacfetch.git
106
+ cd flacfetch
107
+ pip install -e ".[dev]"
108
+ ```
109
+
110
+ ## Usage
111
+
112
+ ### CLI Usage
113
+
114
+ **Standard Search (Artist - Title)**
115
+ ```bash
116
+ flacfetch "Seether - Tonight"
117
+ ```
118
+
119
+ **Explicit Arguments (Recommended for precision)**
120
+ ```bash
121
+ flacfetch --artist "Seether" --title "Tonight"
122
+ ```
123
+
124
+ **Auto-download Highest Quality**
125
+ ```bash
126
+ flacfetch --auto --artist "Seether" --title "Tonight"
127
+ ```
128
+
129
+ **Output Options**
130
+ ```bash
131
+ # Specify output directory
132
+ flacfetch --artist "Seether" --title "Tonight" -o ~/Music
133
+
134
+ # Auto-rename to "ARTIST - TITLE.ext"
135
+ flacfetch --artist "Seether" --title "Tonight" --rename
136
+
137
+ # Specify exact filename
138
+ flacfetch --artist "Seether" --title "Tonight" --filename "my_song"
139
+
140
+ # Combine options
141
+ flacfetch --artist "Seether" --title "Tonight" -o ~/Music --rename
142
+ ```
143
+
144
+ **Verbose Logging**
145
+ ```bash
146
+ flacfetch -v "Seether - Tonight"
147
+ ```
148
+
149
+ **Configuration**
150
+
151
+ To use private music trackers, you must provide an API Key:
152
+ ```bash
153
+ # Redacted
154
+ export REDACTED_API_KEY="your_api_key_here"
155
+ # OR
156
+ flacfetch "..." --redacted-key "your_key"
157
+
158
+ # OPS
159
+ export OPS_API_KEY="your_api_key_here"
160
+ # OR
161
+ flacfetch "..." --ops-key "your_key"
162
+ ```
163
+
164
+ **Provider Priority**
165
+
166
+ When multiple providers are configured, flacfetch searches them in priority order. By default: **Redacted > OPS > YouTube**
167
+
168
+ This means Redacted is searched first, and only if it returns no results will OPS be searched, then YouTube. This is useful for conserving buffer on trackers with stricter limits.
169
+
170
+ ```bash
171
+ # Use default priority (Redacted > OPS > YouTube)
172
+ export REDACTED_API_KEY="..."
173
+ export OPS_API_KEY="..."
174
+ flacfetch "Artist" "Title" --auto
175
+
176
+ # Custom priority
177
+ flacfetch "Artist" "Title" --provider-priority "OPS,Redacted,YouTube"
178
+
179
+ # Or via environment variable
180
+ export FLACFETCH_PROVIDER_PRIORITY="OPS,Redacted,YouTube"
181
+ flacfetch "Artist" "Title" --auto
182
+
183
+ # Disable fallback (only search highest priority provider)
184
+ flacfetch "Artist" "Title" --auto --no-fallback
185
+ ```
186
+
187
+ ### Library Usage
188
+
189
+ ```python
190
+ from flacfetch.core.manager import FetchManager
191
+ from flacfetch.core.models import TrackQuery
192
+ from flacfetch.providers.redacted import RedactedProvider
193
+ from flacfetch.providers.ops import OPSProvider
194
+
195
+ manager = FetchManager()
196
+ manager.add_provider(RedactedProvider(api_key="..."))
197
+ manager.add_provider(OPSProvider(api_key="..."))
198
+
199
+ # Search for a specific track
200
+ results = manager.search(TrackQuery(artist="Seether", title="Tonight"))
201
+ best = manager.select_best(results)
202
+
203
+ if best:
204
+ # Download returns the path to the downloaded file
205
+ file_path = manager.download(
206
+ best,
207
+ output_path="./downloads",
208
+ output_filename="Seether - Tonight" # Optional: custom filename
209
+ )
210
+ print(f"Downloaded to: {file_path}")
211
+ ```
212
+
213
+ ## Architecture & Design
214
+
215
+ See [ARCHITECTURE.md](ARCHITECTURE.md) for detailed architecture, design choices, and implementation learnings.
216
+
217
+ ## Contributing
218
+
219
+ Contributions are welcome! Please see [CONTRIBUTING.md](CONTRIBUTING.md) for guidelines.
220
+
221
+ ## Legal Disclaimer
222
+
223
+ 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.
224
+
225
+ ## License
226
+
227
+ MIT License - see [LICENSE](LICENSE) file for details.
@@ -0,0 +1,117 @@
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., `RedactedProvider`, `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
+ Redacted
37
+ Bandcamp
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
+ - **RedactedProvider**:
53
+ - Uses `ajax.php?action=browse` with `artistname` and `filelist` parameters to find torrents containing the specific track.
54
+ - Parses the `fileList` field in the response to identify the specific file within the torrent that matches the requested song.
55
+ - Prioritizes "Album" releases (Type 1) and original release years when presenting options.
56
+ - **PublicProviders**: Wrappers around `yt-dlp` or scraping logic for Bandcamp/Soundcloud.
57
+
58
+ ### 2.2 Quality & Selection Logic
59
+ - **Quality Class**: Implements `__lt__`, `__eq__` to allow sorting.
60
+ - Hierarchy: `FLAC 24bit` > `FLAC 16bit` > `MP3 320` > `AAC` > `Other`.
61
+ - **Auto-Selection**: `FetchManager` sorts results by `Quality` and picks the top.
62
+ - **Interactive Selection**: `FetchManager` delegates to `InteractionHandler`.
63
+ - **CLI Implementation**: Prints list to stdout, reads index from stdin.
64
+ - **Library Implementation**: Accepts a callable/hook passed during initialization or method call.
65
+
66
+ ### 2.3 Downloader System
67
+ - **TorrentDownloader**: Uses `libtorrent`.
68
+ - **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).
69
+ - Manages session, adds magnet/torrent file, monitors progress, alerts on completion.
70
+ - **HttpDownloader**: Standard HTTP GET or `yt-dlp` process.
71
+
72
+ ## 3. Implementation Plan
73
+
74
+ ### Phase 1: Core & Interfaces
75
+ - Define `TrackQuery`, `Release`, `Quality` data classes.
76
+ - Define `Provider` and `Downloader` abstract base classes (ABCs).
77
+ - Implement `Quality` comparison logic.
78
+
79
+ ### Phase 2: Redacted Provider Integration
80
+ - Implement authentication (API Key support).
81
+ - Implement `RedactedProvider.search` using specific `artistname` and `filelist` filtering.
82
+ - Parse `fileList` string (format: `filename{{{size}}}|||...`) to find target track.
83
+ - Parse results into `Release` objects, including `target_file` metadata.
84
+
85
+ ### Phase 3: BitTorrent Integration
86
+ - Set up `libtorrent` session management.
87
+ - Implement `TorrentDownloader` with selective file downloading support.
88
+ - Ensure protocol compatibility.
89
+
90
+ ### Phase 4: Public Providers
91
+ - Implement `YoutubeProvider` using `yt-dlp`.
92
+ - Implement `HttpDownloader`.
93
+
94
+ ### Phase 5: CLI & Library API
95
+ - Build `FetchManager` to tie it all together.
96
+ - Implement CLI with separated `--artist` and `--title` arguments for precision.
97
+ - Implement `ConsoleInteractionHandler` (CLI) and `CallbackInteractionHandler` (Library).
98
+
99
+ ## 4. Testing Strategy
100
+
101
+ ### 4.1 Unit Testing (`pytest`)
102
+ - **Domain Logic**: Test `Quality` sorting and `FetchManager` flow (mocking providers).
103
+ - **Providers**: Test API response parsing using mocked JSON responses (recorded from actual API calls or synthesized based on docs).
104
+ - **Downloaders**: Mock `libtorrent` session and file system operations.
105
+
106
+ ### 4.2 Integration Testing
107
+ - Test the "Search -> Select -> Download" pipeline with a "MockProvider" and "MockDownloader" to ensure the architecture holds together without hitting real external services.
108
+
109
+ ### 4.3 End-to-End Testing (Manual/Staging)
110
+ - Run against real APIs (limited) to verify contract assumptions.
111
+
112
+ ## 5. Technology Stack
113
+ - **Language**: Python 3.10+
114
+ - **HTTP Client**: `httpx` (async support) or `requests`.
115
+ - **Torrent**: `python-libtorrent` (via system package or pip).
116
+ - **CLI**: `argparse` (stdlib).
117
+ - **Testing**: `pytest`, `pytest-mock`.
@@ -0,0 +1,187 @@
1
+ # flacfetch
2
+
3
+ [![PyPI version](https://badge.fury.io/py/flacfetch.svg)](https://pypi.org/project/flacfetch/)
4
+ [![Python Version](https://img.shields.io/pypi/pyversions/flacfetch.svg)](https://pypi.org/project/flacfetch/)
5
+ [![Tests](https://github.com/nomadkaraoke/flacfetch/workflows/Tests/badge.svg)](https://github.com/nomadkaraoke/flacfetch/actions/workflows/test.yml)
6
+ [![codecov](https://codecov.io/gh/nomadkaraoke/flacfetch/branch/main/graph/badge.svg)](https://codecov.io/gh/nomadkaraoke/flacfetch)
7
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
8
+
9
+ **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.
10
+
11
+ ## Features
12
+
13
+ - **Precise Track Search**:
14
+ - **Private Music Trackers**: Redacted and OPS (API integration). Uses advanced file list filtering to find specific songs within album torrents, downloading only the required track.
15
+ - **Public Sources**: YouTube (via `yt-dlp`).
16
+ - **Smart Prioritization**:
17
+ - **Official Sources**: Automatically prioritizes "Topic" channels and "Official Audio" on YouTube.
18
+ - **Quality Heuristics**:
19
+ - **Trackers (Redacted/OPS)**: Prioritizes Lossless (FLAC) and healthy torrents (Seeders). Matches filename exactly to your query.
20
+ - **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).
21
+ - **Flexible Interaction**:
22
+ - **Interactive Mode**: Present search results to the user for manual selection with rich, color-coded metadata (Seeders, Views, Duration).
23
+ - **Automatic Mode**: Automatically select the highest ranked release.
24
+ - **Smart Downloading**:
25
+ - **Selective BitTorrent**: Uses Transmission daemon to download *only* the specific file matching your search query from larger album torrents (saving bandwidth).
26
+ - **Direct Downloads**: Handles HTTP/Stream downloads for public sources.
27
+
28
+ ## Requirements
29
+
30
+ - Python 3.10+
31
+ - `requests`
32
+ - `yt-dlp`
33
+ - `transmission-rpc`
34
+ - **Transmission** (daemon) - *Required for BitTorrent downloads* (Optional if only using YouTube)
35
+
36
+ ### Installing Transmission
37
+
38
+ Transmission is a lightweight, cross-platform BitTorrent client with RPC support.
39
+
40
+ - **Ubuntu/Debian**: `sudo apt install transmission-daemon`
41
+ - **macOS**: `brew install transmission-cli`
42
+ - **Windows**: Download from [transmissionbt.com](https://transmissionbt.com)
43
+
44
+ flacfetch will automatically start the transmission daemon if it's not running.
45
+
46
+ ## Installation
47
+
48
+ ### From PyPI (Recommended)
49
+
50
+ ```bash
51
+ pip install flacfetch
52
+ ```
53
+
54
+ ### From Source
55
+
56
+ ```bash
57
+ git clone https://github.com/nomadkaraoke/flacfetch.git
58
+ cd flacfetch
59
+ pip install .
60
+ ```
61
+
62
+ ### Development Installation
63
+
64
+ ```bash
65
+ git clone https://github.com/nomadkaraoke/flacfetch.git
66
+ cd flacfetch
67
+ pip install -e ".[dev]"
68
+ ```
69
+
70
+ ## Usage
71
+
72
+ ### CLI Usage
73
+
74
+ **Standard Search (Artist - Title)**
75
+ ```bash
76
+ flacfetch "Seether - Tonight"
77
+ ```
78
+
79
+ **Explicit Arguments (Recommended for precision)**
80
+ ```bash
81
+ flacfetch --artist "Seether" --title "Tonight"
82
+ ```
83
+
84
+ **Auto-download Highest Quality**
85
+ ```bash
86
+ flacfetch --auto --artist "Seether" --title "Tonight"
87
+ ```
88
+
89
+ **Output Options**
90
+ ```bash
91
+ # Specify output directory
92
+ flacfetch --artist "Seether" --title "Tonight" -o ~/Music
93
+
94
+ # Auto-rename to "ARTIST - TITLE.ext"
95
+ flacfetch --artist "Seether" --title "Tonight" --rename
96
+
97
+ # Specify exact filename
98
+ flacfetch --artist "Seether" --title "Tonight" --filename "my_song"
99
+
100
+ # Combine options
101
+ flacfetch --artist "Seether" --title "Tonight" -o ~/Music --rename
102
+ ```
103
+
104
+ **Verbose Logging**
105
+ ```bash
106
+ flacfetch -v "Seether - Tonight"
107
+ ```
108
+
109
+ **Configuration**
110
+
111
+ To use private music trackers, you must provide an API Key:
112
+ ```bash
113
+ # Redacted
114
+ export REDACTED_API_KEY="your_api_key_here"
115
+ # OR
116
+ flacfetch "..." --redacted-key "your_key"
117
+
118
+ # OPS
119
+ export OPS_API_KEY="your_api_key_here"
120
+ # OR
121
+ flacfetch "..." --ops-key "your_key"
122
+ ```
123
+
124
+ **Provider Priority**
125
+
126
+ When multiple providers are configured, flacfetch searches them in priority order. By default: **Redacted > OPS > YouTube**
127
+
128
+ This means Redacted is searched first, and only if it returns no results will OPS be searched, then YouTube. This is useful for conserving buffer on trackers with stricter limits.
129
+
130
+ ```bash
131
+ # Use default priority (Redacted > OPS > YouTube)
132
+ export REDACTED_API_KEY="..."
133
+ export OPS_API_KEY="..."
134
+ flacfetch "Artist" "Title" --auto
135
+
136
+ # Custom priority
137
+ flacfetch "Artist" "Title" --provider-priority "OPS,Redacted,YouTube"
138
+
139
+ # Or via environment variable
140
+ export FLACFETCH_PROVIDER_PRIORITY="OPS,Redacted,YouTube"
141
+ flacfetch "Artist" "Title" --auto
142
+
143
+ # Disable fallback (only search highest priority provider)
144
+ flacfetch "Artist" "Title" --auto --no-fallback
145
+ ```
146
+
147
+ ### Library Usage
148
+
149
+ ```python
150
+ from flacfetch.core.manager import FetchManager
151
+ from flacfetch.core.models import TrackQuery
152
+ from flacfetch.providers.redacted import RedactedProvider
153
+ from flacfetch.providers.ops import OPSProvider
154
+
155
+ manager = FetchManager()
156
+ manager.add_provider(RedactedProvider(api_key="..."))
157
+ manager.add_provider(OPSProvider(api_key="..."))
158
+
159
+ # Search for a specific track
160
+ results = manager.search(TrackQuery(artist="Seether", title="Tonight"))
161
+ best = manager.select_best(results)
162
+
163
+ if best:
164
+ # Download returns the path to the downloaded file
165
+ file_path = manager.download(
166
+ best,
167
+ output_path="./downloads",
168
+ output_filename="Seether - Tonight" # Optional: custom filename
169
+ )
170
+ print(f"Downloaded to: {file_path}")
171
+ ```
172
+
173
+ ## Architecture & Design
174
+
175
+ See [ARCHITECTURE.md](ARCHITECTURE.md) for detailed architecture, design choices, and implementation learnings.
176
+
177
+ ## Contributing
178
+
179
+ Contributions are welcome! Please see [CONTRIBUTING.md](CONTRIBUTING.md) for guidelines.
180
+
181
+ ## Legal Disclaimer
182
+
183
+ 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.
184
+
185
+ ## License
186
+
187
+ MIT License - see [LICENSE](LICENSE) file for details.
@@ -0,0 +1,6 @@
1
+ """flacfetch - Search and download high-quality audio from multiple sources."""
2
+
3
+ __version__ = "0.3.0"
4
+ __author__ = "Andrew Beveridge"
5
+ __email__ = "andrew@beveridge.uk"
6
+