127 lines
3.8 KiB
Python

#!/usr/bin/env python3
"""
Scene Detection Service
A Flask API that wraps PySceneDetect to find hard cuts in videos.
Returns cut timestamps for use in clip boundary snapping.
"""
import os
from flask import Flask, request, jsonify
from scenedetect import open_video, SceneManager
from scenedetect.detectors import ContentDetector, AdaptiveDetector
app = Flask(__name__)
# Configuration from environment
CONTENT_THRESHOLD = float(os.getenv('SCENE_CONTENT_THRESHOLD', '27.0'))
MIN_SCENE_LEN = int(os.getenv('SCENE_MIN_LENGTH', '15')) # minimum scene length in frames
@app.route('/health', methods=['GET'])
def health():
"""Health check endpoint"""
return jsonify({
'status': 'ok',
'content_threshold': CONTENT_THRESHOLD,
'min_scene_len': MIN_SCENE_LEN
})
@app.route('/detect', methods=['POST'])
def detect():
"""
Detect scene cuts in a video.
Expects JSON body with:
- video_path: path to video file (must be accessible from container)
- threshold: optional override for content detector threshold
- detector: 'content' (default) or 'adaptive'
Returns JSON with:
- cuts: array of cut timestamps in seconds
- scene_count: number of scenes detected
- duration: video duration in seconds
"""
data = request.get_json()
if not data or 'video_path' not in data:
return jsonify({'error': 'video_path is required'}), 400
video_path = data['video_path']
threshold = data.get('threshold', CONTENT_THRESHOLD)
detector_type = data.get('detector', 'content')
# Verify file exists
if not os.path.exists(video_path):
return jsonify({'error': f'Video file not found: {video_path}'}), 404
try:
# Open video
video = open_video(video_path)
scene_manager = SceneManager()
# Add detector based on type
if detector_type == 'adaptive':
scene_manager.add_detector(AdaptiveDetector())
else:
scene_manager.add_detector(
ContentDetector(threshold=threshold, min_scene_len=MIN_SCENE_LEN)
)
# Detect scenes
scene_manager.detect_scenes(video, show_progress=False)
scene_list = scene_manager.get_scene_list()
# Get video duration
duration = video.duration.get_seconds()
# Extract cut timestamps (scene boundaries)
# Scene list contains (start_time, end_time) tuples
# We want the unique timestamps where cuts occur
cuts = [0.0] # Always include video start
for scene in scene_list:
start_sec = scene[0].get_seconds()
end_sec = scene[1].get_seconds()
# Add start of each scene (which is end of previous)
if start_sec not in cuts and start_sec > 0:
cuts.append(start_sec)
# Add video end
if duration not in cuts:
cuts.append(duration)
# Sort and deduplicate
cuts = sorted(list(set(cuts)))
return jsonify({
'cuts': cuts,
'scene_count': len(scene_list),
'duration': duration,
'detector': detector_type,
'threshold': threshold
})
except Exception as e:
return jsonify({'error': str(e)}), 500
@app.route('/info', methods=['GET'])
def info():
"""Get service configuration"""
return jsonify({
'service': 'scene-detection',
'library': 'PySceneDetect',
'detectors': ['content', 'adaptive'],
'default_threshold': CONTENT_THRESHOLD,
'min_scene_len': MIN_SCENE_LEN
})
if __name__ == '__main__':
print(f"Scene Detection Service starting...")
print(f"Content threshold: {CONTENT_THRESHOLD}")
print(f"Min scene length: {MIN_SCENE_LEN} frames")
app.run(host='0.0.0.0', port=5004, debug=False)