feat(flow): support folder-to-folder conversion with sidecar migration and prune
Build / Lint & Test (push) Successful in 28s
Build / Lint & Test (push) Successful in 28s
This commit is contained in:
1 parent
f2e483ca1f
commit
88ecf9a847
3 files changed
+253
-62
No files matched your search
@@ -130,80 +130,163 @@ async function testCopyAndSubtitles() {
|
||||
assert(!fs.existsSync(path.join(libraryDir, 'Movie.eng.sdh.default.srt')), 'eng.sdh.default.srt was renamed');
|
||||
assert(fs.existsSync(path.join(libraryDir, 'Movie.eng.sdh.srt')), 'eng.sdh.srt exists (default stripped)');
|
||||
|
||||
// 5. Test TV Series: Multi-episode sidecar isolation & empty Season/Series directory cleanup
|
||||
const stagingRoot = path.join(tmpDir, 'staging');
|
||||
const seriesSourceDir = path.join(stagingRoot, 'Halo', 'Season 01');
|
||||
// 5. Test TV Series: Complete Folder-to-Folder Workflow (Sonarr -> Serie)
|
||||
const sonarrRoot = path.join(tmpDir, 'Sonarr');
|
||||
const seriesSourceDir = path.join(sonarrRoot, 'Halo.2022 [tvdbid-12345]', 'After.Life.S01');
|
||||
fs.mkdirSync(seriesSourceDir, { recursive: true });
|
||||
|
||||
const finalSeasonDir = path.join(tmpDir, 'final_library', 'Halo', 'Season 01');
|
||||
fs.mkdirSync(finalSeasonDir, { recursive: true });
|
||||
const serieOutputRoot = path.join(tmpDir, 'Serie');
|
||||
|
||||
// In finalSeasonDir, put Episode 1 and Episode 2 with their sidecars
|
||||
const ep1File = path.join(finalSeasonDir, 'Halo - S01E01.mkv');
|
||||
const ep2File = path.join(finalSeasonDir, 'Halo - S01E02.mkv');
|
||||
fs.writeFileSync(ep1File, Buffer.alloc(100));
|
||||
fs.writeFileSync(ep2File, Buffer.alloc(100));
|
||||
// Create Episode 1 source files in Sonarr
|
||||
const ep1SourceFile = path.join(seriesSourceDir, 'Halo.S01E01.mkv');
|
||||
const ep1SubFile = path.join(seriesSourceDir, 'Halo.S01E01.ita.forced.srt');
|
||||
const ep1ChapFile = path.join(seriesSourceDir, 'Halo.S01E01_chapters.xml');
|
||||
fs.writeFileSync(ep1SourceFile, Buffer.alloc(100));
|
||||
fs.writeFileSync(ep1SubFile, 'ep1 sub');
|
||||
fs.writeFileSync(ep1ChapFile, '<chapters ep1/>');
|
||||
|
||||
// Sidecars for Ep1 (to be normalized when Ep1 is finalized)
|
||||
fs.writeFileSync(path.join(finalSeasonDir, 'Halo - S01E01.ita.forced.srt'), 'ep1 sub');
|
||||
fs.writeFileSync(path.join(finalSeasonDir, 'Halo - S01E01_chapters.xml'), '<chapters/>');
|
||||
// Create Episode 2 source files in Sonarr (still waiting to be processed)
|
||||
const ep2SourceFile = path.join(seriesSourceDir, 'Halo.S01E02.mkv');
|
||||
const ep2SubFile = path.join(seriesSourceDir, 'Halo.S01E02.eng.forced.default.srt');
|
||||
const ep2ChapFile = path.join(seriesSourceDir, 'Halo.S01E02_chapters.xml');
|
||||
fs.writeFileSync(ep2SourceFile, Buffer.alloc(100));
|
||||
fs.writeFileSync(ep2SubFile, 'ep2 sub');
|
||||
fs.writeFileSync(ep2ChapFile, '<chapters ep2/>');
|
||||
|
||||
// Sidecars for Ep2 (should NOT be touched when Ep1 is finalized!)
|
||||
fs.writeFileSync(path.join(finalSeasonDir, 'Halo - S01E02.eng.forced.default.srt'), 'ep2 sub');
|
||||
fs.writeFileSync(path.join(finalSeasonDir, 'Halo - S01E02_chapters.xml'), '<chapters ep2/>');
|
||||
// Ep1 encoded file in worker workDir
|
||||
const ep1WorkFile = path.join(workDir, 'Halo.S01E01_encoded.mkv');
|
||||
fs.writeFileSync(ep1WorkFile, Buffer.alloc(200));
|
||||
|
||||
// In source directory, simulate that Ep2 is still waiting in staging
|
||||
fs.writeFileSync(path.join(seriesSourceDir, 'Halo - S01E02.mkv'), Buffer.alloc(100));
|
||||
// A. Run replaceOriginalFile for Ep1 with folder-to-folder
|
||||
const ep1ReplaceResult = await replacePlugin({
|
||||
inputFileObj: { _id: ep1WorkFile },
|
||||
variables: { originalLibraryFile: ep1SourceFile, originalLibraryDir: seriesSourceDir },
|
||||
workDir,
|
||||
librarySettings: { folder: sonarrRoot, output: serieOutputRoot },
|
||||
inputs: { deleteSource: 'true' },
|
||||
jobLog: () => {},
|
||||
updateWorker: () => {},
|
||||
});
|
||||
|
||||
const finalizeEp1Args = {
|
||||
assert(fs.existsSync(ep1ReplaceResult.outputFileObj._id), 'Ep1 installed in Serie destination library');
|
||||
assert(ep1ReplaceResult.outputFileObj._id.includes(path.join('Serie', 'Halo.2022 [tvdbid-12345]', 'After.Life.S01')), 'Ep1 relative season subfolder preserved');
|
||||
assert(!fs.existsSync(ep1SourceFile), 'Ep1 source video unlinked from Sonarr intake');
|
||||
assert(fs.existsSync(ep2SourceFile), 'Ep2 source video untouched in Sonarr intake');
|
||||
assert(fs.existsSync(ep1SubFile), 'Ep1 subtitle sidecar still in Sonarr before finalize');
|
||||
assert(fs.existsSync(ep2SubFile), 'Ep2 subtitle sidecar untouched in Sonarr');
|
||||
|
||||
// B. Run finalizeMedia for Ep1
|
||||
const ep1FinalDir = path.dirname(ep1ReplaceResult.outputFileObj._id);
|
||||
await finalizePlugin({
|
||||
inputFileObj: {
|
||||
_id: ep1File,
|
||||
_id: ep1ReplaceResult.outputFileObj._id,
|
||||
ffProbeData: { streams: [] },
|
||||
},
|
||||
variables: { originalLibraryDir: seriesSourceDir },
|
||||
variables: ep1ReplaceResult.variables,
|
||||
librarySettings: { folder: sonarrRoot, output: serieOutputRoot },
|
||||
inputs: { removeEmptySourceDir: 'true', removeEmptyParentSeriesDir: 'true' },
|
||||
jobLog: () => {},
|
||||
};
|
||||
});
|
||||
|
||||
await finalizePlugin(finalizeEp1Args);
|
||||
// Check Ep1 sidecars migrated and normalized in Serie
|
||||
assert(fs.existsSync(path.join(ep1FinalDir, 'Halo.S01E01.ita.forced.default.srt')), 'Ep1 subtitle moved to Serie and normalized with .default');
|
||||
assert(fs.existsSync(path.join(ep1FinalDir, 'Halo.S01E01_chapters.xml')), 'Ep1 chapters moved to Serie');
|
||||
assert(!fs.existsSync(ep1SubFile), 'Ep1 subtitle removed from Sonarr sourceDir');
|
||||
assert(!fs.existsSync(ep1ChapFile), 'Ep1 chapters removed from Sonarr sourceDir');
|
||||
|
||||
// Ep1 sidecars were processed
|
||||
assert(fs.existsSync(path.join(finalSeasonDir, 'Halo - S01E01.ita.forced.default.srt')), 'Ep1 subtitle normalized to .default');
|
||||
assert(fs.existsSync(path.join(finalSeasonDir, 'Halo - S01E01_chapters.xml')), 'Ep1 chapters intact');
|
||||
// Check Ep2 sidecars in Sonarr were completely untouched
|
||||
assert(fs.existsSync(ep2SubFile), 'Ep2 subtitle untouched in Sonarr sourceDir');
|
||||
assert(fs.existsSync(ep2ChapFile), 'Ep2 chapters untouched in Sonarr sourceDir');
|
||||
|
||||
// Ep2 sidecars were COMPLETELY UNTOUCHED!
|
||||
assert(fs.existsSync(path.join(finalSeasonDir, 'Halo - S01E02.eng.forced.default.srt')), 'Ep2 subtitle was untouched when Ep1 was finalized');
|
||||
assert(fs.existsSync(path.join(finalSeasonDir, 'Halo - S01E02_chapters.xml')), 'Ep2 chapters untouched');
|
||||
// Season folder in Sonarr preserved because Ep2 is still there
|
||||
assert(fs.existsSync(seriesSourceDir), 'Sonarr season folder preserved while Ep2 remains');
|
||||
|
||||
// Source season directory was NOT removed because Ep2 was still in staging
|
||||
assert(fs.existsSync(seriesSourceDir), 'Source Season 01 directory kept while Ep2 remains');
|
||||
// C. Now process Episode 2
|
||||
const ep2WorkFile = path.join(workDir, 'Halo.S01E02_encoded.mkv');
|
||||
fs.writeFileSync(ep2WorkFile, Buffer.alloc(200));
|
||||
|
||||
// Now simulate Ep2 finishing and removing from staging, leaving only a junk file (e.g. Thumbs.db)
|
||||
fs.unlinkSync(path.join(seriesSourceDir, 'Halo - S01E02.mkv'));
|
||||
fs.writeFileSync(path.join(seriesSourceDir, 'Thumbs.db'), 'junk');
|
||||
// Also put a junk file in parent series folder
|
||||
fs.writeFileSync(path.join(stagingRoot, 'Halo', 'desktop.ini'), 'junk');
|
||||
const ep2ReplaceResult = await replacePlugin({
|
||||
inputFileObj: { _id: ep2WorkFile },
|
||||
variables: { originalLibraryFile: ep2SourceFile, originalLibraryDir: seriesSourceDir },
|
||||
workDir,
|
||||
librarySettings: { folder: sonarrRoot, output: serieOutputRoot },
|
||||
inputs: { deleteSource: 'true' },
|
||||
jobLog: () => {},
|
||||
updateWorker: () => {},
|
||||
});
|
||||
|
||||
const finalizeEp2Args = {
|
||||
assert(fs.existsSync(ep2ReplaceResult.outputFileObj._id), 'Ep2 installed in Serie destination library');
|
||||
assert(!fs.existsSync(ep2SourceFile), 'Ep2 source video unlinked from Sonarr');
|
||||
|
||||
const ep2FinalDir = path.dirname(ep2ReplaceResult.outputFileObj._id);
|
||||
await finalizePlugin({
|
||||
inputFileObj: {
|
||||
_id: ep2File,
|
||||
_id: ep2ReplaceResult.outputFileObj._id,
|
||||
ffProbeData: { streams: [] },
|
||||
},
|
||||
variables: { originalLibraryDir: seriesSourceDir },
|
||||
variables: ep2ReplaceResult.variables,
|
||||
librarySettings: { folder: sonarrRoot, output: serieOutputRoot },
|
||||
inputs: { removeEmptySourceDir: 'true', removeEmptyParentSeriesDir: 'true' },
|
||||
jobLog: () => {},
|
||||
};
|
||||
});
|
||||
|
||||
await finalizePlugin(finalizeEp2Args);
|
||||
// Ep2 sidecars normalized in Serie (default stripped for eng)
|
||||
assert(fs.existsSync(path.join(ep2FinalDir, 'Halo.S01E02.eng.forced.srt')), 'Ep2 subtitle moved to Serie and default stripped');
|
||||
assert(!fs.existsSync(path.join(ep2FinalDir, 'Halo.S01E02.eng.forced.default.srt')), 'Ep2 old subtitle removed');
|
||||
assert(fs.existsSync(path.join(ep2FinalDir, 'Halo.S01E02_chapters.xml')), 'Ep2 chapters moved to Serie');
|
||||
|
||||
// Ep2 subtitle normalized now
|
||||
assert(fs.existsSync(path.join(finalSeasonDir, 'Halo - S01E02.eng.forced.srt')), 'Ep2 subtitle normalized when Ep2 was finalized');
|
||||
assert(!fs.existsSync(path.join(finalSeasonDir, 'Halo - S01E02.eng.forced.default.srt')), 'Ep2 default stripped');
|
||||
// Season 01 and parent Halo directory removed from Sonarr, but sonarrRoot preserved!
|
||||
assert(!fs.existsSync(seriesSourceDir), 'Empty Season 01 directory removed from Sonarr');
|
||||
assert(!fs.existsSync(path.join(sonarrRoot, 'Halo.2022 [tvdbid-12345]')), 'Empty Series parent directory removed from Sonarr');
|
||||
assert(fs.existsSync(sonarrRoot), 'Sonarr library root preserved');
|
||||
|
||||
// Season 01 and parent Halo directory removed, but stagingRoot (library root) preserved
|
||||
assert(!fs.existsSync(seriesSourceDir), 'Empty Season 01 directory removed');
|
||||
assert(!fs.existsSync(path.join(stagingRoot, 'Halo')), 'Empty Series directory removed');
|
||||
assert(fs.existsSync(stagingRoot), 'Staging root directory preserved');
|
||||
// 6. Test Movie: Complete Folder-to-Folder Workflow (Radarr -> Film)
|
||||
const radarrRoot = path.join(tmpDir, 'Radarr');
|
||||
const movieSourceDir = path.join(radarrRoot, 'Avatar.2009');
|
||||
fs.mkdirSync(movieSourceDir, { recursive: true });
|
||||
|
||||
const filmOutputRoot = path.join(tmpDir, 'Film');
|
||||
|
||||
const movieSourceFile = path.join(movieSourceDir, 'Avatar.2009.mkv');
|
||||
const movieSubFile = path.join(movieSourceDir, 'Avatar.2009.ita.forced.srt');
|
||||
const movieChapFile = path.join(movieSourceDir, 'Avatar.2009_chapters.xml');
|
||||
fs.writeFileSync(movieSourceFile, Buffer.alloc(100));
|
||||
fs.writeFileSync(movieSubFile, 'movie sub');
|
||||
fs.writeFileSync(movieChapFile, '<chapters movie/>');
|
||||
|
||||
const movieWorkFile = path.join(workDir, 'Avatar.2009_encoded.mkv');
|
||||
fs.writeFileSync(movieWorkFile, Buffer.alloc(200));
|
||||
|
||||
const movieReplaceResult = await replacePlugin({
|
||||
inputFileObj: { _id: movieWorkFile },
|
||||
variables: { originalLibraryFile: movieSourceFile, originalLibraryDir: movieSourceDir },
|
||||
workDir,
|
||||
librarySettings: { folder: radarrRoot, output: filmOutputRoot },
|
||||
inputs: { deleteSource: 'true' },
|
||||
jobLog: () => {},
|
||||
updateWorker: () => {},
|
||||
});
|
||||
|
||||
assert(fs.existsSync(movieReplaceResult.outputFileObj._id), 'Movie installed in Film library');
|
||||
assert(movieReplaceResult.outputFileObj._id.includes(path.join('Film', 'Avatar.2009')), 'Movie subfolder preserved in Film');
|
||||
assert(!fs.existsSync(movieSourceFile), 'Movie source video unlinked from Radarr');
|
||||
|
||||
const movieFinalDir = path.dirname(movieReplaceResult.outputFileObj._id);
|
||||
await finalizePlugin({
|
||||
inputFileObj: {
|
||||
_id: movieReplaceResult.outputFileObj._id,
|
||||
ffProbeData: { streams: [] },
|
||||
},
|
||||
variables: movieReplaceResult.variables,
|
||||
librarySettings: { folder: radarrRoot, output: filmOutputRoot },
|
||||
inputs: { removeEmptySourceDir: 'true', removeEmptyParentSeriesDir: 'true' },
|
||||
jobLog: () => {},
|
||||
});
|
||||
|
||||
assert(fs.existsSync(path.join(movieFinalDir, 'Avatar.2009.ita.forced.default.srt')), 'Movie subtitle moved to Film and normalized');
|
||||
assert(fs.existsSync(path.join(movieFinalDir, 'Avatar.2009_chapters.xml')), 'Movie chapters moved to Film');
|
||||
assert(!fs.existsSync(movieSourceDir), 'Empty movie directory removed from Radarr');
|
||||
assert(fs.existsSync(radarrRoot), 'Radarr library root preserved');
|
||||
|
||||
console.log('\n=== ALL COPY, MOVE, SUBTITLE & SERIES TESTS PASSED ===\n');
|
||||
} finally {
|
||||
|
||||
Reference in new issue
Block a user