'use strict'; const path = require('path'); const fs = require('fs'); /* eslint-disable no-param-reassign */ /** * copyToWorkDir * Copies the source file to the worker's local workDir before processing. * * Use this plugin BEFORE any transcode/extract plugin when: * - The source file lives on a network share / mapped drive * - You want the worker to encode from a local copy to avoid I/O bottleneck * - The network path is slow or unreliable during long encodes * * Flow: * 1. Copies source → workDir/ (streams, no RAM load) * 2. Updates outputFileObj._id to the local copy path * 3. Downstream plugins operate on the local copy * 4. Tdarr replaces the original with the final output as usual */ const details = () => ({ name: 'Copy Source to Work Dir', description: 'Copies the source file into the worker local workDir before processing. ' + 'Use before transcode plugins when the source is on a slow network share ' + 'to avoid I/O bottleneck during encode. ' + 'The local copy is used by all subsequent plugins in this flow.', style: { borderColor: '#607D8B' }, tags: 'utility', isStartPlugin: false, pType: '', requiresVersion: '2.11.01', sidebarPosition: 0, icon: 'faCopy', inputs: [ { label: 'Skip if already local', name: 'skipIfLocal', type: 'boolean', defaultValue: 'true', inputUI: { type: 'dropdown', options: ['true', 'false'], }, tooltip: 'If true, skip the copy when the source file is already inside workDir ' + 'or on the same drive as workDir (avoids double-copy on mapped nodes).', }, ], outputs: [ { number: 1, tooltip: 'File is ready in workDir — continue to next plugin' }, ], }); // ─── Plugin ─────────────────────────────────────────────────────────────────── const plugin = async (args) => { const { jobLog, inputFileObj, variables, workDir } = args; const inputFile = inputFileObj._id; const ext = path.extname(inputFile); const baseName = path.basename(inputFile, ext); const localCopy = path.join(workDir, `${baseName}${ext}`); jobLog(`[copyToWorkDir] Source : ${inputFile}\n`); jobLog(`[copyToWorkDir] WorkDir: ${workDir}\n`); // ── Skip only if file is already located inside workDir ────────────────── const skipIfLocal = (args.inputs.skipIfLocal || 'true') === 'true'; if (skipIfLocal && inputFile.startsWith(workDir)) { jobLog('[copyToWorkDir] Source file is already inside workDir — skipping copy.\n'); return { outputNumber: 1, outputFileObj: inputFileObj, variables }; } // ── Preserve original library paths for downstream plugins ────────────── if (!variables.originalLibraryFile) { variables.originalLibraryFile = inputFile; variables.originalLibraryDir = path.dirname(inputFile); } // ── Copy using streams (no RAM spike for large files) ──────────────────── const sourceSize = fs.statSync(inputFile).size; const sourceSizeMb = (sourceSize / 1024 / 1024).toFixed(1); jobLog(`[copyToWorkDir] Copying ${sourceSizeMb} MB to: ${localCopy}\n`); const startMs = Date.now(); let copiedBytes = 0; let nextProgressPercent = 10; await new Promise((resolve, reject) => { const rd = fs.createReadStream(inputFile); const wr = fs.createWriteStream(localCopy); rd.on('error', reject); wr.on('error', reject); wr.on('finish', resolve); rd.on('data', (chunk) => { copiedBytes += chunk.length; if (sourceSize <= 0) return; const percent = Math.floor((copiedBytes / sourceSize) * 100); if (percent < nextProgressPercent) return; const elapsedSeconds = Math.max((Date.now() - startMs) / 1000, 0.1); const copiedMb = (copiedBytes / 1024 / 1024).toFixed(1); const speedMbPerSecond = (copiedBytes / 1024 / 1024 / elapsedSeconds).toFixed(1); jobLog(`[copyToWorkDir] Progress: ${Math.min(percent, 100)}% (${copiedMb}/${sourceSizeMb} MB, ${speedMbPerSecond} MB/s)\n`); nextProgressPercent = (Math.floor(percent / 10) + 1) * 10; }); rd.pipe(wr); }); const elapsed = ((Date.now() - startMs) / 1000).toFixed(1); const sizeMb = (fs.statSync(localCopy).size / 1024 / 1024).toFixed(1); jobLog(`[copyToWorkDir] Done: ${sizeMb} MB copied in ${elapsed}s\n`); // Return updated inputFileObj pointing to the local copy return { outputNumber: 1, outputFileObj: { ...inputFileObj, _id: localCopy }, variables, }; }; module.exports.details = details; module.exports.plugin = plugin;