Initial commit: Momentry Core v0.1
- Rust-based digital asset management system - Video analysis: ASR, OCR, YOLO, Face, Pose - RAG capabilities with Qdrant vector database - Multi-database support: PostgreSQL, Redis, MongoDB - Monitoring system with launchd plists - n8n workflow automation integration
This commit is contained in:
97
scripts/thumbnail_extractor.py
Normal file
97
scripts/thumbnail_extractor.py
Normal file
@@ -0,0 +1,97 @@
|
||||
#!/opt/homebrew/bin/python3.11
|
||||
import sys
|
||||
import os
|
||||
import subprocess
|
||||
import json
|
||||
import argparse
|
||||
|
||||
|
||||
def get_duration(video_path):
|
||||
result = subprocess.run(
|
||||
["ffprobe", "-v", "quiet", "-print_format", "json", "-show_format", video_path],
|
||||
capture_output=True,
|
||||
text=True,
|
||||
)
|
||||
data = json.loads(result.stdout)
|
||||
return float(data["format"]["duration"])
|
||||
|
||||
|
||||
def extract_thumbnails(video_path, uuid, output_dir, count=6):
|
||||
"""Extract evenly-spaced thumbnails from video."""
|
||||
|
||||
if not os.path.exists(video_path):
|
||||
print(f"Error: Video file not found: {video_path}", file=sys.stderr)
|
||||
return None
|
||||
|
||||
duration = get_duration(video_path)
|
||||
print(f"Video duration: {duration:.2f}s", file=sys.stderr)
|
||||
|
||||
# Output directory
|
||||
thumb_dir = os.path.join(output_dir, uuid)
|
||||
os.makedirs(thumb_dir, exist_ok=True)
|
||||
|
||||
# Calculate timestamps (skip first 10%, skip last 10%)
|
||||
start_ts = duration * 0.1
|
||||
end_ts = duration * 0.9
|
||||
interval = (end_ts - start_ts) / (count - 1) if count > 1 else 0
|
||||
|
||||
extracted = []
|
||||
for i in range(count):
|
||||
if count == 1:
|
||||
ts = duration / 2
|
||||
else:
|
||||
ts = start_ts + (interval * i)
|
||||
|
||||
output_file = os.path.join(thumb_dir, f"thumb_{i:03d}.jpg")
|
||||
result = subprocess.run(
|
||||
[
|
||||
"ffmpeg",
|
||||
"-y",
|
||||
"-ss",
|
||||
str(ts),
|
||||
"-i",
|
||||
video_path,
|
||||
"-vframes",
|
||||
"1",
|
||||
"-q:v",
|
||||
"2",
|
||||
"-vf",
|
||||
"scale=320:-1",
|
||||
output_file,
|
||||
],
|
||||
capture_output=True,
|
||||
)
|
||||
|
||||
if result.returncode == 0 and os.path.exists(output_file):
|
||||
extracted.append(output_file)
|
||||
print(f" Extracted: {output_file} at {ts:.1f}s", file=sys.stderr)
|
||||
else:
|
||||
print(f" Failed to extract frame at {ts:.1f}s", file=sys.stderr)
|
||||
|
||||
return extracted
|
||||
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(
|
||||
description="Extract keyframe thumbnails from video"
|
||||
)
|
||||
parser.add_argument("video_path", help="Path to video file")
|
||||
parser.add_argument("uuid", help="Video UUID")
|
||||
parser.add_argument("-o", "--output", default="thumbnails", help="Output directory")
|
||||
parser.add_argument(
|
||||
"-c", "--count", type=int, default=6, help="Number of thumbnails"
|
||||
)
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
result = extract_thumbnails(args.video_path, args.uuid, args.output, args.count)
|
||||
|
||||
if result:
|
||||
print(json.dumps({"uuid": args.uuid, "count": len(result), "files": result}))
|
||||
else:
|
||||
print(json.dumps({"error": "Failed to extract thumbnails"}))
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Reference in New Issue
Block a user