Overview
Camera Workflow is designed to be 100% safe and idempotent . The recovery system ensures you can safely resume conversions after interruptions (crashes, Ctrl+C, power loss) without data loss or duplicate work.
Idempotent : Running the same conversion command multiple times produces identical results, safely skipping already-converted files.
Core Safety Guarantees
1. Original Preservation
Originals are preserved by default (--keep-originals=true):
Source files never modified during conversion
Deletion requires explicit opt-out (--keep-originals=false)
Triple verification before any deletion occurs
2. Atomic Conversions
All conversions use temporary files with atomic rename:
// Convert to .tmp file first
tempPath := outputPath + ".tmp"
ffmpeg - i input . mov - c : v hevc output . mp4 . tmp
// Verify integrity
VerifyOutputFile ( tempPath )
// Atomic rename (never overwrites partially-written files)
os . Rename ( tempPath , outputPath )
Interruptions at any point leave either:
No output file (conversion never started)
.tmp file (conversion incomplete, auto-cleaned on next run)
Complete valid file (conversion succeeded)
3. Processing Markers
Track in-flight conversions with .processing files (security.go:211-231):
PID:12345
Started:2024-08-15T14:30:22Z
File:/dest/2024/08-August/2024-08-15/videos/vacation.mp4
Purpose :
Identify abandoned conversions from crashed processes
Enable cleanup of orphaned .tmp files
Prevent concurrent conversions of the same file
Recovery Mechanisms
Corruption Detection (security.go:187-209)
Before skipping existing files, verify integrity:
func ( s * SecurityChecker ) IsFileCorrupted ( filePath , fileType string ) bool {
// Check file exists and is not empty
info , err := os . Stat ( filePath )
if err != nil || info . Size () == 0 {
return true
}
// Verify file integrity based on type
switch fileType {
case "photo" :
return s . verifyImageIntegrity ( filePath ) != nil // Uses ImageMagick identify
case "video" :
return s . verifyVideoIntegrity ( filePath ) != nil // Uses ffprobe
}
}
Per-File Type Verification :
Images (security.go:107-114): magick identify <file> - detects truncated/corrupted images
Videos (security.go:116-125): ffprobe <file> - validates container and streams
Abandoned File Cleanup (security.go:282-319)
Run automatically before each conversion batch:
func ( s * SecurityChecker ) CleanupAbandonedFiles ( dir string ) error {
// Remove orphaned .tmp files from interrupted conversions
// Remove .processing markers from dead processes
// Log cleanup actions
}
Detection Logic (security.go:256-280):
func ( s * SecurityChecker ) isMarkerAbandoned ( markerPath string ) bool {
// Read PID from .processing file
content , _ := os . ReadFile ( markerPath )
pid := extractPID ( content )
// Check if process still exists
return ! processExists ( pid )
}
Idempotent Conversion Flow
Every conversion follows this recovery-aware sequence (video.go:174-191):
// 1. Check if already converted and valid
if _ , err := os . Stat ( outputPath ); err == nil {
if ! c . security . IsFileCorrupted ( outputPath , "video" ) {
// Valid file exists, skip
c . logger . Info ( "already exists and valid, skipping" )
c . stats . skippedFiles ++
return nil
} else {
// Corrupted file detected, remove and re-convert
c . logger . Warn ( "corrupted file detected, re-converting" )
os . Remove ( outputPath )
c . stats . recoveredFiles ++
}
}
// 2. Create processing marker
c . security . CreateProcessingMarker ( outputPath )
defer c . security . RemoveProcessingMarker ( outputPath )
// 3. Convert to .tmp file
// 4. Verify integrity
// 5. Atomic rename
Recovery Workflow
When resuming after interruption:
Step 1: Startup Cleanup
media-converter /source /dest
Before processing files:
Scans destination for .processing markers
Checks if PIDs are still running
Removes abandoned markers and .tmp files
Logs cleanup actions
Example Output :
🔒 Security Check: Found 2 abandoned conversions from previous run
🔒 Cleanup: Removed vacation.mp4.tmp (incomplete conversion)
🔒 Cleanup: Removed beach.mp4.processing (dead process 12345)
Step 2: Integrity Verification
For each file in source:
Check if output exists in destination
If exists, verify integrity with external tools
If valid → skip
If corrupted → remove and re-convert
If missing → convert
Example Output :
📹 vacation.mov → 2024-08-15_vacation.mp4 (already exists and valid, skipping)
📹 beach.mov → 2024-08-15_beach.mp4 (corrupted file detected, re-converting)
📹 sunset.mov → 2024-08-15_sunset.mp4 (converting...)
Step 3: Safe Re-conversion
Corrupted files are automatically re-converted:
Original source file remains untouched
Corrupted output removed before re-conversion
recoveredFiles counter incremented
Standard conversion flow proceeds
Created at conversion start (security.go:211-222):
File : <output_path>.processing
Content :
PID:12345
Started:2024-08-15T14:30:22Z
File:/dest/2024/08-August/2024-08-15/videos/vacation.mp4
Lifecycle :
Created: When conversion starts (before FFmpeg/ImageMagick)
Removed: When conversion completes successfully
Orphaned: If process crashes/killed before completion
Cleaned: On next run by CleanupAbandonedFiles()
Do not manually delete .processing files while conversions are running. This can lead to duplicate concurrent conversions of the same file.
Integrity Verification
Output Verification (security.go:51-105)
Called after every conversion before finalizing:
func ( s * SecurityChecker ) VerifyOutputFile (
inputPath , outputPath , fileType , outputFormat string ,
) error {
// 1. Check file exists
// 2. Check file is not empty
// 3. Verify minimum size ratio
// 4. Run format-specific integrity check
}
Minimum Size Ratios
Prevents accepting files that are suspiciously small (config.go:82-84):
Default minimum size ratio (0.5% of original)
min_output_size_ratio_avif
AVIF minimum size ratio (0.1% of original) - highly compressed format
min_output_size_ratio_webp
WebP minimum size ratio (0.3% of original)
Validation (security.go:74-94):
minSize := int64 ( float64 ( inputSize ) * ratio )
if outputSize < minSize {
os . Remove ( outputPath ) // Clean up suspicious file
return fmt . Errorf ( "output file too small ( %d < %d bytes, ratio %.3f for %s )" ,
outputSize , minSize , ratio , outputFormat )
}
Images (security.go:107-114):
magick identify /path/to/output.avif
# Exit code 0 = valid, non-zero = corrupted
Videos (security.go:116-125):
ffprobe /path/to/output.mp4
# Exit code 0 = valid, non-zero = corrupted
Failures trigger automatic cleanup:
if err := cmd . Run (); err != nil {
os . Remove ( outputPath ) // Clean up corrupted file
return fmt . Errorf ( "file is corrupted: %s " , outputPath )
}
Safe Deletion
When --keep-originals=false, triple verification before deletion (security.go:127-145):
func ( s * SecurityChecker ) SafeDelete ( filePath , outputPath string ) error {
// 1. Verify output file exists
outputInfo , err := os . Stat ( outputPath )
if err != nil {
return fmt . Errorf ( "cannot verify output file before deletion: %w " , err )
}
// 2. Ensure output is not suspiciously small
if outputInfo . Size () < 1000 {
return fmt . Errorf ( "deletion cancelled for safety: output file too small" )
}
// 3. Perform deletion
if err := os . Remove ( filePath ); err != nil {
return fmt . Errorf ( "failed to delete original file: %w " , err )
}
return nil
}
Deletion failures are logged as warnings, not errors. The conversion is still considered successful if the output file is valid.
Configuration
Timeout Settings
Prevent hung conversions from blocking progress:
Photo conversion timeout in seconds (5 minutes)
Video conversion timeout in seconds (30 minutes)
Usage :
# Increase timeout for 4K videos
media-converter /source /dest --timeout-video 3600
Implementation (video.go:226-228):
ctx , cancel := context . WithTimeout ( context . Background (), c . config . ConversionTimeoutVideo )
defer cancel ()
cmd := exec . CommandContext ( ctx , "ffmpeg" , ... )
Skip Existing Files
Disable integrity verification for faster re-runs (not recommended):
# DANGER: Skips corruption detection
media-converter /source /dest --skip-integrity-check
There is no --skip-integrity-check flag by design. Integrity verification is mandatory to ensure recovery system reliability.
Monitoring Recovery
The converter tracks recovery statistics:
type ConversionStats struct {
skippedFiles int // Already converted and valid
recoveredFiles int // Re-converted due to corruption
// ...
}
Final Report :
✅ Conversion complete!
Total files processed: 150
Successfully converted: 42
Skipped (already converted): 105
Recovered (re-converted corrupted): 3
Failed: 0
Best Practices
1. Regular Backups
While the recovery system is robust, maintain source backups:
# Always keep originals during first conversion
media-converter /source /dest --keep-originals
# Verify destination integrity before deleting sources
media-converter /source /dest --verify-checksum
2. Test Recovery
Verify recovery behavior before production use:
# Start conversion
media-converter /source /dest
# Interrupt with Ctrl+C after a few files
# Resume - should skip completed files and cleanup .tmp files
media-converter /source /dest
3. Monitor Corruption
Watch for recovered file counts:
# High recovery count may indicate:
# - Storage issues (failing disk)
# - Insufficient resources (OOM during conversion)
# - Software bugs
4. Tune Timeouts
Adjust based on content characteristics:
# Large 4K video library
timeout_video : 3600 # 60 minutes for very large files
# RAW photos from high-resolution cameras
timeout_photo : 600 # 10 minutes for 100MP+ files
Troubleshooting
Files Repeatedly Re-converted
Symptom : Same file shows as “re-converting” on every run
Causes :
Output file consistently fails integrity check
FFmpeg/ImageMagick not in PATH
Corrupted external tool installation
Diagnosis :
# Test integrity check manually
ffprobe /dest/2024/08-August/2024-08-15/videos/vacation.mp4
magick identify /dest/2024/08-August/2024-08-15/images/photo.avif
Orphaned .processing Files
Symptom : .processing files remain after conversion completes
Causes :
Process killed with kill -9 (bypasses defer cleanup)
System crash before defer runs
Storage I/O errors during marker removal
Solution :
# Manual cleanup
find /dest -name "*.processing" -delete
# Or re-run converter (auto-cleans on startup)
media-converter /source /dest
High Skipped File Count
Symptom : Most files reported as “already exists and valid, skipping”
Expected Behavior : This is correct idempotent operation!
If Unintended :
Check if source/destination paths are correct
Verify date extraction is working (files organized by correct dates)
Use --dry-run to preview what would happen
Performance Tuning Optimize conversion speed and resource usage
Adaptive Workers Dynamic concurrency management