127 lines
3.8 KiB
Python
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)
|