settings: youtube path template field on the Library tab
surface the youtube_template (built into organization.py) on Settings -> Library Organization, next to the movie/episode templates: input + variable hints ($channel/$year/$date/$month/$day/$title/$videoid) + live preview, wired into load/collect/save/reset. backend already round-trips it (load/save normalize).
This commit is contained in:
parent
c47dccc7d7
commit
3254e8a6c4
3 changed files with 23 additions and 4 deletions
|
|
@ -28,6 +28,10 @@ def test_normalize_fills_and_validates():
|
|||
assert d["episode_template"] == "$series/$episode"
|
||||
# an invalid transfer mode falls back to the default
|
||||
assert organization.normalize({"transfer_mode": "torrent"})["transfer_mode"] == "copy"
|
||||
# youtube template is a first-class setting: defaulted + editable + blank→default
|
||||
assert organization.default_settings()["youtube_template"] == organization.DEFAULTS["youtube_template"]
|
||||
assert organization.normalize({"youtube_template": "$channel/$title"})["youtube_template"] == "$channel/$title"
|
||||
assert organization.normalize({"youtube_template": " "})["youtube_template"] == organization.DEFAULTS["youtube_template"]
|
||||
|
||||
|
||||
def test_load_save_roundtrip():
|
||||
|
|
|
|||
|
|
@ -6010,6 +6010,13 @@
|
|||
<small class="settings-hint">Example: <code id="vo-episode-preview">…</code></small>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label>YouTube Path Template:</label>
|
||||
<input type="text" id="vo-youtube-template" placeholder="$channel/Season $year/$channel - $date - $title">
|
||||
<small class="settings-hint">Followed YouTube channels organize as a Plex "TV by date" show. Variables: $channel, $year, $date (2024-03-15), $month (03), $day (15), $title, $videoid.</small>
|
||||
<small class="settings-hint">Example: <code id="vo-youtube-preview">…</code></small>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label>When a file finishes:</label>
|
||||
<select id="vo-transfer-mode" class="form-select">
|
||||
|
|
|
|||
|
|
@ -693,6 +693,8 @@
|
|||
resolution: '1080p', source: 'Bluray', codec: 'HEVC', edition: '', tmdbid: '603', imdbid: 'tt0133093' };
|
||||
var _ORG_EP_EG = { series: 'Breaking Bad', season: '01', seasonraw: '1', episode: '01', episodetitle: 'Pilot',
|
||||
year: '2008', quality: 'WEBDL-1080p', resolution: '1080p', source: 'WEBDL', codec: 'H264', tvdbid: '81189' };
|
||||
var _ORG_YT_EG = { channel: 'Veritasium', title: 'How Electricity Actually Works', year: '2024',
|
||||
date: '2024-03-15', month: '03', day: '15', videoid: 'oI_X2cMHNe0' };
|
||||
|
||||
function _orgSanitize(v) {
|
||||
return String(v == null ? '' : v).replace(/[\\/:*?"<>|\x00-\x1f]/g, '')
|
||||
|
|
@ -714,10 +716,13 @@
|
|||
function renderOrgPreview() {
|
||||
var mt = document.getElementById('vo-movie-template');
|
||||
var et = document.getElementById('vo-episode-template');
|
||||
var yt = document.getElementById('vo-youtube-template');
|
||||
var mp = document.getElementById('vo-movie-preview');
|
||||
var ep = document.getElementById('vo-episode-preview');
|
||||
var yp = document.getElementById('vo-youtube-preview');
|
||||
if (mp && mt) mp.textContent = _orgRender(mt.value || mt.placeholder, _ORG_MOVIE_EG) + '.mkv';
|
||||
if (ep && et) ep.textContent = _orgRender(et.value || et.placeholder, _ORG_EP_EG) + '.mkv';
|
||||
if (yp && yt) yp.textContent = _orgRender(yt.value || yt.placeholder, _ORG_YT_EG) + '.mp4';
|
||||
}
|
||||
function fillOrg() {
|
||||
if (!_videoOrg) return;
|
||||
|
|
@ -725,6 +730,7 @@
|
|||
var chk = function (id, v) { var el = document.getElementById(id); if (el) el.checked = !!v; };
|
||||
set('vo-movie-template', _videoOrg.movie_template || '');
|
||||
set('vo-episode-template', _videoOrg.episode_template || '');
|
||||
set('vo-youtube-template', _videoOrg.youtube_template || '');
|
||||
set('vo-transfer-mode', _videoOrg.transfer_mode || 'copy');
|
||||
chk('vo-verify', _videoOrg.verify_with_ffprobe);
|
||||
chk('vo-replace', _videoOrg.replace_existing);
|
||||
|
|
@ -747,6 +753,7 @@
|
|||
return {
|
||||
movie_template: val('vo-movie-template'),
|
||||
episode_template: val('vo-episode-template'),
|
||||
youtube_template: val('vo-youtube-template'),
|
||||
transfer_mode: val('vo-transfer-mode'),
|
||||
verify_with_ffprobe: on('vo-verify'),
|
||||
replace_existing: on('vo-replace'),
|
||||
|
|
@ -771,7 +778,7 @@
|
|||
var card = anchor.closest('.settings-group');
|
||||
if (!card || card._voWired) return;
|
||||
card._voWired = true;
|
||||
['vo-movie-template', 'vo-episode-template'].forEach(function (id) {
|
||||
['vo-movie-template', 'vo-episode-template', 'vo-youtube-template'].forEach(function (id) {
|
||||
var el = document.getElementById(id);
|
||||
if (!el) return;
|
||||
el.addEventListener('input', renderOrgPreview); // live preview while typing
|
||||
|
|
@ -788,9 +795,10 @@
|
|||
// Radarr/Sonarr defaults and echoes them back.
|
||||
fetch(ORG_URL, {
|
||||
method: 'POST', headers: { 'Content-Type': 'application/json', 'Accept': 'application/json' },
|
||||
body: JSON.stringify({ movie_template: '', episode_template: '', transfer_mode: 'copy',
|
||||
verify_with_ffprobe: true, replace_existing: true, carry_subtitles: true,
|
||||
save_artwork: false, write_nfo: false, download_subtitles: false, subtitle_langs: 'en' })
|
||||
body: JSON.stringify({ movie_template: '', episode_template: '', youtube_template: '',
|
||||
transfer_mode: 'copy', verify_with_ffprobe: true, replace_existing: true,
|
||||
carry_subtitles: true, save_artwork: false, write_nfo: false,
|
||||
download_subtitles: false, subtitle_langs: 'en' })
|
||||
}).then(function (r) { return r.ok ? r.json() : null; })
|
||||
.then(function (d) { if (d) { _videoOrg = d; fillOrg(); toast('Reset to the standard layout', 'success'); } });
|
||||
});
|
||||
|
|
|
|||
Loading…
Reference in a new issue