You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

141 lines
4.6 KiB
Python

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