The backfill_formatting.py script applies transcript formatting/cleanup to existing videos that were transcribed before the formatting feature was added or with an older formatting version.
- Idempotency: Automatically skips transcripts already formatted with the current version and configuration
- Dry-run mode: Preview changes without modifying the database
- Flexible filtering: Process specific videos, channels, or jobs
- Safe batching: Configurable batch sizes with transaction-per-batch for safe interruption
- Resume capability: Can be stopped and restarted without data loss
- Force reprocessing: Option to reprocess all transcripts regardless of version
- Progress tracking: Detailed structured logging with metrics
- Error handling: Gracefully handles per-video errors without stopping the batch
- Database migration
003_transcript_cleanupmust be applied - Formatting module enabled in settings (
CLEANUP_ENABLED=true) - Database access via
DATABASE_URLenvironment variable
Process all unformatted transcripts in batches:
python scripts/backfill_formatting.py --batch 100 --until-emptyPreview changes without committing to the database:
python scripts/backfill_formatting.py --dry-run --batch 10Process only specific video UUIDs:
python scripts/backfill_formatting.py --video-ids UUID1,UUID2,UUID3Process all videos from a specific channel:
python scripts/backfill_formatting.py --channel-name "My Channel" --batch 50 --until-emptyProcess all videos from a specific job:
python scripts/backfill_formatting.py --job-id JOB_UUID --batch 20Reprocess all transcripts regardless of version:
python scripts/backfill_formatting.py --force --until-empty --batch 100| Option | Description | Default |
|---|---|---|
--batch |
Number of videos to process per batch | 10 |
--until-empty |
Continue processing until no more videos need formatting | false |
--max-iterations |
Maximum number of iterations (safety cap with --until-empty) | unlimited |
--dry-run |
Preview changes without committing | false |
--force |
Force reprocessing even if already formatted | false |
--video-ids |
Comma-separated list of video UUIDs to process | all |
--channel-name |
Filter to videos from a specific channel | all |
--job-id |
Filter to videos from a specific job UUID | all |
The script uses a semantic version number (FORMATTING_VERSION) and a configuration hash to determine if a transcript needs reprocessing:
- Never formatted: Transcripts without
cleanup_configare processed - Version changed: Transcripts with an older version are reprocessed
- Config changed: Transcripts with a different configuration hash are reprocessed
- Already current: Transcripts matching current version and config are skipped
- Load configuration: Reads current formatting settings from
app/settings.py - Compute config hash: Creates a hash of the configuration for change detection
- Select batch: Queries database for videos needing formatting based on filters
- Check each video: Determines if transcript needs processing
- Apply formatting: Runs formatter on segments and updates database
- Update metadata: Records version, config hash, and timestamp in
transcripts.cleanup_config - Repeat: Continues to next batch if
--until-emptyis enabled
The script updates two tables:
segments table:
text_cleaned: Stores the formatted textcleanup_applied: Set totruewhen formatted
transcripts table:
cleanup_config: JSON with version, config hash, config, and timestampis_cleaned: Set totruewhen formatted
Process in small batches, manually controlling when to continue:
# Process first batch
python scripts/backfill_formatting.py --batch 50
# Check results, then process next batch
python scripts/backfill_formatting.py --batch 50
# Repeat as neededProcess all videos with a safety limit:
python scripts/backfill_formatting.py --until-empty --batch 100 --max-iterations 50Force reprocessing when you've changed formatting settings:
python scripts/backfill_formatting.py --force --until-empty --batch 100Preview changes on a few test videos before full rollout:
# Dry run on test videos
python scripts/backfill_formatting.py --dry-run --video-ids TEST_UUID1,TEST_UUID2
# If satisfied, process for real
python scripts/backfill_formatting.py --video-ids TEST_UUID1,TEST_UUID2The script outputs structured JSON logs (or text logs depending on LOG_FORMAT setting) with:
- Batch progress (iteration, videos processed, errors, skipped)
- Per-video results (segments processed, updated, status)
- Final summary (total processed, errors, skipped, iterations)
Key metrics reported:
processed: Videos successfully formattedskipped: Videos already formatted or filtered outerrors: Videos that failed formattingiterations: Number of batches processedsegments_processed: Total segments formatted per videosegments_updated: Total segments updated in database per video
Cause: All videos already formatted with current version
Solution: Use --force to reprocess, or change formatting configuration
Cause: Fewer videos than batch size were found, triggering early completion
Solution: This is normal behavior when approaching completion. Use --until-empty to continue until truly empty.
Cause: DATABASE_URL not set or database not accessible
Solution: Ensure .env file has correct DATABASE_URL or set environment variable
Cause: Corrupted segments or unexpected data format Solution: Check logs for specific video IDs with errors; script continues processing other videos
Cause: Configuration contains non-serializable objects
Solution: Ensure formatter.config contains only JSON-serializable types (strings, numbers, booleans, lists, dicts)
- Small (10-50): For testing or when resources are limited
- Medium (50-100): Good balance for most use cases
- Large (100-500): For bulk processing with adequate resources
Depends on:
- Number of segments per video
- Complexity of formatting operations enabled
- Database and disk I/O performance
- Available CPU cores (formatting is CPU-bound)
Note: The following performance estimates are approximate and were observed in a specific test environment (e.g., 8-core CPU, SSD storage, PostgreSQL on local network, 1-5 minute videos). Actual performance will vary significantly depending on your hardware, database performance, video length, and formatting complexity.
Typical rates:
- ~5-10 videos per minute with default settings
- ~100-200 segments per second
- Memory: Minimal (<100MB for script, more for database connections)
- CPU: Moderate (formatting operations are CPU-intensive)
- Disk I/O: Low (only reading segments and updating database)
- Database: One transaction per video for safety
Each video is processed in its own database transaction, ensuring:
- Partial batches can be rolled back
- Script can be interrupted without data corruption
- Failed videos don't affect other videos in the batch
Running the script multiple times on the same videos:
- Does not duplicate work
- Does not corrupt data
- Only processes videos that need updating
Errors in one video don't stop the batch:
- Error is logged with details
- Script continues to next video
- Summary includes error count
- Always dry-run first: Use
--dry-runto preview changes before actual processing - Start with small batches: Test with
--batch 10before scaling up - Test on specific videos: Use
--video-idsto test on a few known videos first - Monitor logs: Watch for errors or unexpected behavior
- Use max-iterations: Set
--max-iterationswhen using--until-emptyas a safety cap - Schedule during off-peak: Run large backfills when system load is low
- Check database size: Ensure adequate disk space for cleaned text storage
- cleanup-examples.md - Examples of formatting transformations
- cleanup-profiles.md - Pre-configured formatting profiles
- cleanup-quick-reference.md - Quick reference for settings
- advanced-features.md - Overview of advanced features
For existing deployments:
- Deploy code: Update to version with backfill script
- Run migration: Apply
003_transcript_cleanupmigration - Dry run: Test on a few videos with
--dry-run - Pilot: Process a small channel or job
- Monitor: Check results and performance
- Scale: Gradually increase batch size
- Full backfill: Process remaining videos with
--until-empty
For issues or questions:
- Check script logs for detailed error messages
- Review this documentation
- Check GitHub issues for similar problems
- Open a new issue with logs and reproduction steps