|
|
|
|
import ffmpeg
|
|
|
|
|
import subprocess
|
|
|
|
|
import json
|
|
|
|
|
import os
|
|
|
|
|
from collections import Counter
|
|
|
|
|
|
|
|
|
|
def is_av1(filepath):
|
|
|
|
|
"""Check if a video file is already AV1-encoded."""
|
|
|
|
|
try:
|
|
|
|
|
probe = ffmpeg.probe(filepath)
|
|
|
|
|
for stream in probe['streams']:
|
|
|
|
|
if stream['codec_type'] == 'video' and 'codec_name' in stream:
|
|
|
|
|
if stream['codec_name'] == 'av1':
|
|
|
|
|
return True
|
|
|
|
|
except ffmpeg.Error as e:
|
|
|
|
|
print(f"Error probing {filepath}: {e}")
|
|
|
|
|
return False
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def get_fps(filepath):
|
|
|
|
|
"""Get the frames per second (FPS) of the input video using ffmpeg.probe."""
|
|
|
|
|
try:
|
|
|
|
|
probe = ffmpeg.probe(filepath)
|
|
|
|
|
video_stream = next((s for s in probe['streams'] if s['codec_type'] == 'video'), None)
|
|
|
|
|
if video_stream and 'r_frame_rate' in video_stream:
|
|
|
|
|
fps_str = video_stream['r_frame_rate'] # e.g. "30/1", "25/1"
|
|
|
|
|
num, den = map(int, fps_str.split('/'))
|
|
|
|
|
return num / den
|
|
|
|
|
except ffmpeg.Error as e:
|
|
|
|
|
print(f"Error getting FPS for {filepath}: {e}")
|
|
|
|
|
return None
|
|
|
|
|
|
|
|
|
|
def get_video_info(filepath):
|
|
|
|
|
"""
|
|
|
|
|
Returns dict:
|
|
|
|
|
{ 'width': int, 'height': int, 'bitrate': int, 'fps': float }
|
|
|
|
|
- bitrate is Kbps (rounded down)
|
|
|
|
|
- uses stream bit_rate, else format bit_rate, else computed
|
|
|
|
|
"""
|
|
|
|
|
cmd = [
|
|
|
|
|
"ffprobe","-v","error",
|
|
|
|
|
"-select_streams","v:0",
|
|
|
|
|
"-show_entries","stream=width,height,bit_rate,r_frame_rate",
|
|
|
|
|
"-show_entries","format=bit_rate,duration",
|
|
|
|
|
"-of","json", filepath
|
|
|
|
|
]
|
|
|
|
|
r = subprocess.run(cmd, capture_output=True, text=True)
|
|
|
|
|
if r.returncode:
|
|
|
|
|
return {"width": 0, "height": 0, "bitrate": 0, "fps": 0.0}
|
|
|
|
|
|
|
|
|
|
try:
|
|
|
|
|
d = json.loads(r.stdout or "{}")
|
|
|
|
|
s = (d.get("streams") or [{}])[0]
|
|
|
|
|
f = d.get("format") or {}
|
|
|
|
|
|
|
|
|
|
width = int(s.get("width") or 0)
|
|
|
|
|
height = int(s.get("height") or 0)
|
|
|
|
|
|
|
|
|
|
# fps (r_frame_rate like "30000/1001")
|
|
|
|
|
fps = 0.0
|
|
|
|
|
rfr = s.get("r_frame_rate")
|
|
|
|
|
if rfr and rfr != "0/0":
|
|
|
|
|
try:
|
|
|
|
|
num, den = rfr.split("/")
|
|
|
|
|
num = float(num); den = float(den)
|
|
|
|
|
fps = (num/den) if den else 0.0
|
|
|
|
|
except Exception:
|
|
|
|
|
pass
|
|
|
|
|
|
|
|
|
|
# bitrate in bps → prefer stream, fallback to format, else compute
|
|
|
|
|
br_bps = s.get("bit_rate") or f.get("bit_rate")
|
|
|
|
|
if not br_bps:
|
|
|
|
|
try:
|
|
|
|
|
dur = float(f.get("duration") or 0)
|
|
|
|
|
if dur > 0:
|
|
|
|
|
br_bps = int(os.path.getsize(filepath) * 8 / dur)
|
|
|
|
|
except Exception:
|
|
|
|
|
br_bps = 0
|
|
|
|
|
br_kbps = int(int(br_bps or 0) / 1000)
|
|
|
|
|
|
|
|
|
|
return {"width": width, "height": height, "bitrate": br_kbps, "fps": fps}
|
|
|
|
|
except Exception:
|
|
|
|
|
return {"width": 0, "height": 0, "bitrate": 0, "fps": 0.0}
|
|
|
|
|
|
|
|
|
|
def get_common_resolution(group):
|
|
|
|
|
"""Most common (w,h) across the group's videos. Fallback 1280x720."""
|
|
|
|
|
resolutions = []
|
|
|
|
|
for v in group:
|
|
|
|
|
info = get_video_info(v["filepath"])
|
|
|
|
|
w, h = info.get("width"), info.get("height")
|
|
|
|
|
if w and h:
|
|
|
|
|
resolutions.append((w, h))
|
|
|
|
|
if not resolutions:
|
|
|
|
|
return (1280, 720)
|
|
|
|
|
return Counter(resolutions).most_common(1)[0][0]
|
|
|
|
|
|
|
|
|
|
def get_target_resolution(group):
|
|
|
|
|
"""
|
|
|
|
|
Choose (w,h) whose videos have the highest *total duration*.
|
|
|
|
|
Tie-breakers: higher count, then larger area. Fallback 1280x720.
|
|
|
|
|
"""
|
|
|
|
|
totals = {} # (w,h) -> total duration
|
|
|
|
|
counts = {} # (w,h) -> number of files
|
|
|
|
|
|
|
|
|
|
for v in group:
|
|
|
|
|
info = get_video_info(v["filepath"])
|
|
|
|
|
w, h = info.get("width"), info.get("height")
|
|
|
|
|
if not (w and h):
|
|
|
|
|
continue
|
|
|
|
|
|
|
|
|
|
# Prefer DB duration if present, else probe info['duration'], else 0
|
|
|
|
|
dur = v.get("duration", info.get("duration", 0))
|
|
|
|
|
try:
|
|
|
|
|
dur = float(dur)
|
|
|
|
|
except (TypeError, ValueError):
|
|
|
|
|
dur = 0.0
|
|
|
|
|
|
|
|
|
|
key = (w, h)
|
|
|
|
|
totals[key] = totals.get(key, 0.0) + dur
|
|
|
|
|
counts[key] = counts.get(key, 0) + 1
|
|
|
|
|
|
|
|
|
|
if not totals:
|
|
|
|
|
return (1280, 720)
|
|
|
|
|
|
|
|
|
|
def sort_key(item):
|
|
|
|
|
(w, h), total = item
|
|
|
|
|
cnt = counts[(w, h)]
|
|
|
|
|
area = (w or 0) * (h or 0)
|
|
|
|
|
return (total, cnt, area)
|
|
|
|
|
|
|
|
|
|
best_resolution = max(totals.items(), key=sort_key)[0]
|
|
|
|
|
return best_resolution
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def get_target_bitrate(width, height):
|
|
|
|
|
"""Your existing function to choose a bitrate based on resolution."""
|
|
|
|
|
resolutions = {(854, 480): 1000,(1280, 720): 1500,(1920, 1080): 3000,(2560, 1440): 5000,(3840, 2160): 12000}
|
|
|
|
|
for res, bitrate in resolutions.items():
|
|
|
|
|
if width <= res[0] and height <= res[1]:
|
|
|
|
|
return bitrate
|
|
|
|
|
return 2500
|