I got tired of babysitting renders. The moment I finished a screen recording, I had to remember to open the terminal, run the pipeline script, wait for it to finish, then trigger the upload. Three steps too many. So I built a watched folder that handles the entire chain: drop a file in, walk away, find it published on YouTube an hour later.
How Watched Folder Pipelines Work
The concept is simple. A filesystem watcher monitors a specific directory for new files. When a file lands -- whether you drag it there, save it from OBS, or copy it from another machine -- the watcher fires an event. That event kicks off your processing pipeline: transcription, script generation, rendering, and upload. No human in the loop after the initial drop.
On Linux, you have two solid options for filesystem watching: inotifywait from inotify-tools and Node's chokidar library. I prefer chokidar because it integrates cleanly with the rest of the JavaScript-based pipeline and handles edge cases like partial writes (large files that take time to copy).
Setting Up the Watcher
import chokidar from 'chokidar';
import { processRecording } from './pipeline';
const WATCH_DIR = '/home/user/recordings/inbox';
const watcher = chokidar.watch(WATCH_DIR, {
awaitWriteFinish: {
stabilityThreshold: 3000,
pollInterval: 500
},
ignored: /(^|[/\])../
});
watcher.on('add', async (filePath) => {
console.log(`New recording detected: ${filePath}`);
await processRecording(filePath);
});
The awaitWriteFinish option is critical. Without it, the watcher fires the moment the file appears, while it is still being written. The stability threshold waits until the file size stops changing for 3 seconds before triggering processing.
The Processing Chain
Once the watcher detects a stable file, the pipeline runs in sequence:
- OCR extraction and git diff analysis on the screen recording
- Script generation via Claude API using the extracted context
- Voice synthesis with the cloned voice model
- FFmpeg compositing -- overlaying narration, adding transitions, inserting title cards
- Thumbnail generation from a key frame
- YouTube upload via the Data API
Each step writes its output to a staging directory alongside the original file. If any step fails, the file gets moved to a /failed directory with an error log, so you can inspect and retry later.
Handling Multiple Files in the Queue
If you drop five recordings at once, you do not want five parallel FFmpeg processes fighting for CPU and RAM. Use a simple queue:
const queue: string[] = [];
let processing = false;
async function processNext() {
if (processing || queue.length === 0) return;
processing = true;
const file = queue.shift()!;
try {
await processRecording(file);
} catch (err) {
moveToFailed(file, err);
}
processing = false;
processNext();
}
watcher.on('add', (filePath) => {
queue.push(filePath);
processNext();
});
This processes files one at a time in FIFO order. For machines with enough resources, you can allow 2-3 concurrent renders, but sequential is safer for most setups.
Practical Considerations
A few things I learned the hard way:
- File naming matters. Encode the intended video title and category in the filename (e.g.,
react-hooks-tutorial__coding.mp4). The pipeline can parse this instead of guessing. - Disk space monitoring. Video files are large. Add a check that pauses the watcher when free space drops below a threshold.
- Notification on completion. Send a webhook or desktop notification when a video finishes uploading so you know it worked.
- PM2 for persistence. Run the watcher as a PM2 process so it survives reboots and logs are captured.
VidNo's local-first architecture makes this pattern especially clean. Because the entire pipeline runs on your machine -- OCR, Claude scripting, voice cloning, FFmpeg, upload -- there is no external API rate limit bottleneck between steps. The watched folder just orchestrates what is already a self-contained process. Drop and forget.