# Movie Scheduler Automated movie scheduler that reads from NocoDB, generates subtitles with whisper.cpp, burns them into videos with VAAPI encoding, and streams at scheduled times. ## Quick Links - **[Production Deployment Guide](PRODUCTION.md)** - Complete guide for production setup - **[Testing Guide](TESTING.md)** - Docker-based test environment - **[Development Setup](#installation)** - Local development setup ## Features - **NocoDB Integration**: Syncs job schedules from NocoDB database - **Automatic Subtitle Generation**: Uses whisper.cpp to generate SRT subtitles - **Hardware-Accelerated Encoding**: Burns subtitles into videos using VAAPI (h264_vaapi) - **Scheduled Streaming**: Streams videos to RTMP server at scheduled times - **Robust Error Handling**: Automatic retries with exponential backoff - **Comprehensive Logging**: Tracks all operations in database log column - **Process Management**: Properly tracks and manages streaming processes ## Requirements - Python 3.7+ - `requests` library - `whisper.cpp` installed and available in PATH - `ffmpeg` with VAAPI support - VAAPI-capable hardware (AMD/Intel GPU) - NocoDB instance with scheduled jobs table ## Installation 1. Clone or download this repository 2. Install Python dependencies: ```bash pip install requests ``` 3. Ensure whisper.cpp is installed: ```bash # Follow whisper.cpp installation instructions # Make sure the binary is in your PATH which whisper.cpp ``` 4. Verify FFmpeg has VAAPI support: ```bash ffmpeg -hwaccels | grep vaapi ``` ## Configuration 1. Copy the example environment file: ```bash cp .env.example .env ``` 2. Edit `.env` and set the required variables: ```bash # Required export NOCODB_URL="https://nocodb/api/v2/tables/XXXX/records" export NOCODB_TOKEN="your_token_here" export RTMP_SERVER="rtmp://your_server/live" # Optional (defaults provided) export RAW_DIR="/root/surowe_filmy" export FINAL_DIR="/root/przygotowane_filmy" export WHISPER_MODEL="/root/models/ggml-base.bin" export VAAPI_DEVICE="/dev/dri/renderD128" export STREAM_GRACE_PERIOD_MINUTES="15" export NOCODB_SYNC_INTERVAL_SECONDS="60" export WATCHDOG_CHECK_INTERVAL_SECONDS="10" ``` 3. Load the environment variables: ```bash source .env ``` ## NocoDB Table Structure Your NocoDB table should have the following fields: - `Id` - Unique identifier (Text/Primary Key) - `Title` - Movie title matching filename (Text) - `Date` - Scheduled run time (DateTime in ISO format) The scheduler will automatically create additional columns in the local SQLite database: - `prep_at` - Preparation time (6 hours before run_at) - `prep_status` - Preparation status (pending/done/failed) - `play_status` - Streaming status (pending/done/failed) - `raw_path` - Path to source video file - `final_path` - Path to converted video with subtitles - `log` - Detailed operation log ## Usage ### Running the Scheduler ```bash # Ensure environment variables are set source .env # Run the scheduler python agent.py ``` The scheduler will: 1. Sync jobs from NocoDB every 60 seconds (configurable) 2. Check stream health every 10 seconds (configurable) 3. Prepare videos 6 hours before their scheduled time: - Find matching video file in RAW_DIR - Generate subtitles with whisper.cpp - Encode video with burned subtitles using VAAPI 4. Stream videos at their scheduled time to RTMP server ### Stopping the Scheduler Press `Ctrl+C` to gracefully stop the scheduler. It will: - Stop any active streaming process - Close the database connection - Exit cleanly ### Restart & Recovery Behavior The scheduler handles restarts, power outages, and downtime gracefully: **On Startup:** - ✅ Checks for overdue prep jobs and processes them immediately - ⚠️ Skips jobs where the streaming time has already passed - 📊 Logs recovery status (overdue/skipped jobs found) **Grace Period for Late Streaming:** - ⏰ If prep is done and streaming time is overdue by **up to 15 minutes**, will still start streaming - ⚠️ Jobs more than 15 minutes late are marked as 'skipped' - 📝 Late starts are logged with exact delay time **Recovery Scenarios:** 1. **Short Outage (prep time missed, but streaming time not reached)** - Movie scheduled for 21:00 (prep at 15:00) - System down from 14:00-16:00 - ✅ Restarts at 16:00: immediately preps the movie - ✅ Streams normally at 21:00 2. **Late Start Within Grace Period (< 15 minutes)** - Movie scheduled for 21:00 (prep completed at 15:00) - System down from 20:00-21:10 - ✅ Restarts at 21:10: starts streaming immediately (10 minutes late) - 📝 Logged: "Starting stream 10.0 minutes late" 3. **Too Late to Stream (> 15 minutes)** - Movie scheduled for 21:00 (prep completed) - System down from 20:00-21:20 - ⚠️ Restarts at 21:20: marks streaming as 'skipped' (>15 min late) - 📝 Logged: "Streaming skipped - more than 15 minutes late" 4. **Long Outage (both prep and streaming times passed, >15 min)** - Movie scheduled for 21:00 (prep at 15:00) - System down from 14:00-22:00 - ⚠️ Restarts at 22:00: marks entire job as 'skipped' - 📝 Logged: "Job skipped - streaming time already passed" 5. **Crash During Processing** - System crashes during subtitle generation or encoding - ✅ On restart: retries from the beginning with full retry logic - All operations are idempotent (safe to re-run) **Database Persistence:** - Job status stored in `scheduler.db` survives restarts - Completed preps are never re-done - Failed jobs stay marked as 'failed' (can be reset manually to 'pending' if needed) ## File Naming Place your raw video files in `RAW_DIR` with names that contain the title from NocoDB. Example: - NocoDB Title: `"The Matrix"` - Valid filenames: `The Matrix.mkv`, `the-matrix-1999.mp4`, `[1080p] The Matrix.avi` The scheduler uses glob matching to find files containing the title. ## Streaming Watchdog The scheduler includes an active watchdog that monitors streaming: **Stream Monitoring (checked every 10 seconds):** - ✅ Detects if ffmpeg stream crashes or exits unexpectedly - ✅ Automatically restarts stream at the correct playback position - ✅ Calculates elapsed time since stream start - ✅ Seeks to correct position using `ffmpeg -ss` flag - ✅ Limits restarts to 10 attempts to prevent infinite loops - ✅ Marks stream as 'done' when video completes normally - ✅ Marks stream as 'failed' after 10 failed restart attempts **How It Works:** 1. Stream starts and status set to 'streaming' (not 'done') 2. Watchdog checks every 10 seconds if stream process is running 3. If crashed: calculates elapsed time and restarts with seek 4. If completed normally (exit code 0): marks as 'done' 5. If video duration exceeded: marks as 'done' **Example:** ``` Stream starts at 21:00:00 Stream crashes at 21:05:30 (5.5 minutes in) Watchdog detects crash within 10 seconds Restarts stream with: ffmpeg -ss 330 -i video.mp4 Stream resumes from 5:30 mark ``` ## Error Handling The scheduler includes robust error handling: - **Automatic Retries**: Failed operations are retried up to 3 times with exponential backoff - **Database Logging**: All operations and errors are logged to the `log` column - **Status Tracking**: Jobs are marked as 'failed' if all retries are exhausted - **File Verification**: Checks that subtitle files and encoded videos were created successfully ## Monitoring ### Check Job Status Query the SQLite database: ```bash sqlite3 scheduler.db "SELECT nocodb_id, title, prep_status, play_status FROM jobs;" ``` ### View Job Logs ```bash sqlite3 scheduler.db "SELECT nocodb_id, title, log FROM jobs WHERE nocodb_id='YOUR_JOB_ID';" ``` ### Console Output The scheduler logs to stdout with timestamps: - INFO: Normal operations - WARNING: Retry attempts - ERROR: Failures ## Troubleshooting ### Configuration Validation Failed - Ensure all required environment variables are set - Check that NOCODB_URL is accessible - Verify NOCODB_TOKEN has proper permissions ### No Files Found Matching Title - Check that video files exist in RAW_DIR - Ensure filenames contain the NocoDB title (case-insensitive glob matching) - Review the log column in the database for exact error messages ### Subtitle Generation Failed - Verify whisper.cpp is installed: `which whisper.cpp` - Check WHISPER_MODEL path is correct - Ensure the model file exists and is readable ### Video Encoding Failed - Confirm VAAPI is available: `ffmpeg -hwaccels | grep vaapi` - Check VAAPI device exists: `ls -l /dev/dri/renderD128` - Verify subtitle file was created successfully - Review FFmpeg error output in the log column ### Streaming Failed - Test RTMP server connectivity: `ffmpeg -re -i test.mp4 -c copy -f flv rtmp://your_server/live` - Ensure final video file exists - Check network connectivity to RTMP server ## Development ### Database Schema ```sql CREATE TABLE jobs ( nocodb_id TEXT PRIMARY KEY, title TEXT, run_at TIMESTAMP, prep_at TIMESTAMP, raw_path TEXT, final_path TEXT, prep_status TEXT, -- pending/done/failed/skipped play_status TEXT, -- pending/streaming/done/failed/skipped log TEXT, stream_start_time TIMESTAMP, -- When streaming started (for restart seek) stream_retry_count INTEGER -- Number of stream restart attempts ); ``` **Status Values:** - `play_status='streaming'`: Stream is actively running - `play_status='done'`: Stream completed successfully - `play_status='failed'`: Stream failed after 10 restart attempts - `play_status='skipped'`: Too late to stream (>15 min past scheduled time) ### Customization Use environment variables to customize: - `NOCODB_SYNC_INTERVAL_SECONDS` - How often to check NocoDB for new jobs (default: 60) - `WATCHDOG_CHECK_INTERVAL_SECONDS` - How often to monitor streams (default: 10) - `STREAM_GRACE_PERIOD_MINUTES` - Late start tolerance (default: 15) Edit `agent.py` to customize: - Retry attempts and delay (agent.py:99, 203, 232) - Preparation time offset (currently 6 hours, see sync function) - Stream restart limit (agent.py:572) - Subtitle styling (agent.py:246) - Video encoding parameters (agent.py:248-250) ## License This project is provided as-is without warranty.