video downloads: split transfer folder into Movies / TV / YouTube libraries
One shared download (input) folder feeds three separate library (output) folders — the engine routes a finished download to the one matching its type, so YouTube never lands in your real TV library (own Plex/Jellyfin library + agent). Replaces the single transfer_path with movies_path / tv_path / youtube_path (legacy transfer_path migrates into Movies on first read). Settings UI: shared Download folder + 🎬 Movies / 📺 TV / ▶️ YouTube library inputs. Tests updated incl. the migration. ruff + balance clean.
This commit is contained in:
parent
86fde7d62c
commit
89bedaf140
4 changed files with 45 additions and 21 deletions
|
|
@ -7,8 +7,12 @@ never share a folder or collide. The actual download fulfillment engine (wishlis
|
|||
config the Settings → Downloads tab edits.
|
||||
|
||||
Keys persisted here (all under video.db):
|
||||
- ``download_path`` : input folder a video download lands in
|
||||
- ``transfer_path`` : output folder finished video files move to (video library)
|
||||
- ``download_path`` : input folder a video download lands in (shared by all types)
|
||||
- ``movies_path`` : Movies library (finished movies move here)
|
||||
- ``tv_path`` : TV Shows library
|
||||
- ``youtube_path`` : YouTube library (kept separate from real TV)
|
||||
The engine routes a finished download to the path matching its type. (Legacy single
|
||||
``transfer_path`` is migrated into ``movies_path`` on first read.)
|
||||
|
||||
Connection settings that are genuinely SHARED with music (the slskd instance, the
|
||||
torrent/usenet clients, Prowlarr indexers) are NOT stored here — those live in the
|
||||
|
|
@ -24,8 +28,9 @@ from utils.logging_config import get_logger
|
|||
|
||||
logger = get_logger("video_api.downloads")
|
||||
|
||||
# Video-specific path keys (vs. the shared connection settings).
|
||||
_PATH_KEYS = ("download_path", "transfer_path")
|
||||
# Video-specific path keys (vs. the shared connection settings): one shared input
|
||||
# folder + a separate library folder per content type.
|
||||
_PATH_KEYS = ("download_path", "movies_path", "tv_path", "youtube_path")
|
||||
|
||||
# slskd CONNECTION settings genuinely SHARED with music — one slskd instance serves
|
||||
# both sides, so these live in the app-wide config_manager (soulseek.*), NOT video.db.
|
||||
|
|
@ -52,6 +57,8 @@ def register_routes(bp):
|
|||
from core.video.download_config import load as load_source
|
||||
db = get_video_db()
|
||||
out = {k: db.get_setting(k) or "" for k in _PATH_KEYS}
|
||||
if not out["movies_path"]: # migrate the legacy single transfer folder → Movies
|
||||
out["movies_path"] = db.get_setting("transfer_path") or ""
|
||||
out.update(load_source(db)) # download_mode + hybrid_order
|
||||
return jsonify(out)
|
||||
|
||||
|
|
|
|||
|
|
@ -366,18 +366,24 @@ def test_downloads_config_save_load(tmp_path):
|
|||
app.register_blueprint(videoapi.create_video_blueprint(), url_prefix="/api/video")
|
||||
client = app.test_client()
|
||||
try:
|
||||
# Defaults: empty folders, soulseek mode.
|
||||
# Defaults: empty folders, soulseek mode. One shared input + per-type libraries.
|
||||
assert client.get("/api/video/downloads/config").get_json() == {
|
||||
"download_path": "", "transfer_path": "",
|
||||
"download_path": "", "movies_path": "", "tv_path": "", "youtube_path": "",
|
||||
"download_mode": "soulseek", "hybrid_order": ["soulseek"]}
|
||||
# Round-trips + persists to video.db (separate from any music config).
|
||||
client.post("/api/video/downloads/config",
|
||||
json={"download_path": " /mnt/v/dl ", "transfer_path": "/mnt/v/lib",
|
||||
json={"download_path": " /mnt/v/dl ", "movies_path": "/media/movies",
|
||||
"tv_path": "/media/tv", "youtube_path": "/media/yt",
|
||||
"download_mode": "hybrid", "hybrid_order": ["torrent", "usenet"]})
|
||||
assert client.get("/api/video/downloads/config").get_json() == {
|
||||
"download_path": "/mnt/v/dl", "transfer_path": "/mnt/v/lib", # trimmed
|
||||
"download_path": "/mnt/v/dl", "movies_path": "/media/movies", # trimmed
|
||||
"tv_path": "/media/tv", "youtube_path": "/media/yt",
|
||||
"download_mode": "hybrid", "hybrid_order": ["torrent", "usenet"]}
|
||||
assert db.get_setting("download_path") == "/mnt/v/dl"
|
||||
# Legacy single transfer_path migrates into Movies when movies_path is unset.
|
||||
db.set_setting("movies_path", "")
|
||||
db.set_setting("transfer_path", "/old/lib")
|
||||
assert client.get("/api/video/downloads/config").get_json()["movies_path"] == "/old/lib"
|
||||
finally:
|
||||
videoapi._video_db = None
|
||||
|
||||
|
|
|
|||
|
|
@ -6017,15 +6017,24 @@
|
|||
<div class="settings-group" data-stg="downloads" data-video-only>
|
||||
<h3>Video Download Folders</h3>
|
||||
<div class="setting-help-text">
|
||||
Where the video side lands downloads and where it moves finished files — SEPARATE from your Music paths.
|
||||
One shared download folder (where slskd / torrent drop files), then a separate library folder per type — finished files are sorted to the right one automatically. SEPARATE from your Music paths.
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>Download Folder (Input):</label>
|
||||
<label>Download Folder (Input — shared):</label>
|
||||
<input type="text" id="video-download-path" placeholder="./video_downloads">
|
||||
<div class="setting-help-text">Where downloads land. Point this at the same folder as your slskd / torrent client.</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>Transfer Folder (Output — Video Library):</label>
|
||||
<input type="text" id="video-transfer-path" placeholder="./VideoLibrary">
|
||||
<label>🎬 Movies Library:</label>
|
||||
<input type="text" id="video-movies-path" placeholder="/media/movies">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>📺 TV Shows Library:</label>
|
||||
<input type="text" id="video-tv-path" placeholder="/media/tv">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>▶️ YouTube Library:</label>
|
||||
<input type="text" id="video-youtube-path" placeholder="/media/youtube">
|
||||
</div>
|
||||
</div>
|
||||
<div class="settings-group" data-stg="downloads" data-video-only>
|
||||
|
|
|
|||
|
|
@ -281,10 +281,11 @@
|
|||
.then(function (r) { return r.ok ? r.json() : null; })
|
||||
.then(function (d) {
|
||||
if (!d) return;
|
||||
var dl = document.getElementById('video-download-path');
|
||||
if (dl && d.download_path != null) dl.value = d.download_path;
|
||||
var tr = document.getElementById('video-transfer-path');
|
||||
if (tr && d.transfer_path != null) tr.value = d.transfer_path;
|
||||
var setP = function (id, v) { var el = document.getElementById(id); if (el && v != null) el.value = v; };
|
||||
setP('video-download-path', d.download_path);
|
||||
setP('video-movies-path', d.movies_path);
|
||||
setP('video-tv-path', d.tv_path);
|
||||
setP('video-youtube-path', d.youtube_path);
|
||||
_videoMode = d.download_mode || 'soulseek';
|
||||
_videoHybrid = (d.hybrid_order && d.hybrid_order.length) ? d.hybrid_order : ['soulseek'];
|
||||
var ms = document.getElementById('video-download-mode');
|
||||
|
|
@ -296,13 +297,14 @@
|
|||
}
|
||||
|
||||
function saveDownloads(silent) {
|
||||
var dl = document.getElementById('video-download-path');
|
||||
var tr = document.getElementById('video-transfer-path');
|
||||
var val = function (id) { var el = document.getElementById(id); return el ? el.value : ''; };
|
||||
return fetch(DOWNLOADS_URL, {
|
||||
method: 'POST', headers: { 'Content-Type': 'application/json', 'Accept': 'application/json' },
|
||||
body: JSON.stringify({
|
||||
download_path: dl ? dl.value : '',
|
||||
transfer_path: tr ? tr.value : '',
|
||||
download_path: val('video-download-path'),
|
||||
movies_path: val('video-movies-path'),
|
||||
tv_path: val('video-tv-path'),
|
||||
youtube_path: val('video-youtube-path'),
|
||||
download_mode: _videoMode,
|
||||
hybrid_order: _videoHybrid,
|
||||
})
|
||||
|
|
@ -440,7 +442,7 @@
|
|||
});
|
||||
}
|
||||
// Folder inputs save on change too.
|
||||
['video-download-path', 'video-transfer-path'].forEach(function (id) {
|
||||
['video-download-path', 'video-movies-path', 'video-tv-path', 'video-youtube-path'].forEach(function (id) {
|
||||
var el = document.getElementById(id);
|
||||
if (el && !el._vdWired) { el._vdWired = true; el.addEventListener('change', function () { saveDownloads(true); }); }
|
||||
});
|
||||
|
|
|
|||
Loading…
Reference in a new issue