cut detection based on fps of source material

This commit is contained in:
j 2026-01-14 01:46:55 +00:00
commit 4e5a84e96a
3 changed files with 115 additions and 22 deletions

View file

@ -0,0 +1,89 @@
import subprocess
import numpy as np
import ox
from django.config import settings
from .utils import AspectRatio
def _get_distance(data0, data1):
diff = data0.astype(np.float32) - data1.astype(np.float32)
per_pixel_distance = np.linalg.norm(diff, axis=2)
total_distance = per_pixel_distance.sum()
num_pixels = data0.shape[0] * data0.shape[1]
max_distance = num_pixels * np.sqrt(3 * (255 ** 2))
return total_distance / max_distance
def detect_cuts(path, seconds=True):
depth = 3
info = ox.avinfo(path)
dar = AspectRatio(info['video'][0]['display_aspect_ratio'])
fps = AspectRatio(info['video'][0]['framerate'])
height = 96
width = int(dar * height)
width += width % 2
nbytes = depth * width * height
bufsize = nbytes + 100
cmd = [
settings.FFMPEG,
'-hide_banner',
'-loglevel', 'error',
'-i', path,
'-threads', '4',
'-f', 'rawvideo',
'-pix_fmt', 'rgb24',
'-vcodec', 'rawvideo',
'-vf', 'scale=%d:%d' % (width, height),
'-aspect', '%d:%d' % (width, height),
'-'
]
#print(' '.join(cmd))
p = subprocess.Popen(cmd,
bufsize=bufsize,
stdout=subprocess.PIPE,
close_fds=True)
first = True
previous_frame = None
cuts = []
detect_cuts = True
short_cut = None
cut_frames = []
frame_i = 0
previous_distance = 0
while True:
data = p.stdout.read(nbytes)
if len(data) != nbytes:
if first:
raise IOError("ERROR: could not open file %s" % path)
else:
break
else:
first = False
frame = np.frombuffer(data, dtype=np.uint8).reshape((height, width, depth))
frame_data = np.asarray(frame)
if frame_i == 0:
is_cut = False
previous_distance = 0
else:
if short_cut and frame_i - short_cut > 2:
cuts.append(short_cut)
short_cut = None
distance = _get_distance(previous_frame_data, frame_data)
is_cut = distance > 0.1 or (distance > 0.2 and abs(distance - previous_distance) > 0.2)
if is_cut:
if frame_i - (0 if not cuts else short_cut or cuts[-1]) < 3:
is_cut = False
short_cut = frame_i
else:
cuts.append(frame_i)
previous_distance = distance
previous_frame_data = frame_data
frame_i += 1
if seconds:
return [float('%0.3f' % float(c/fps)) for c in cuts]
else:
return cuts

View file

@ -23,6 +23,7 @@ import pillow_avif
from pillow_heif import register_heif_opener
from .chop import Chop, make_keyframe_index
from .utils import AspectRatio
register_heif_opener()
@ -33,28 +34,6 @@ img_extension = 'jpg'
MAX_DISTANCE = math.sqrt(3 * pow(255, 2))
class AspectRatio(fractions.Fraction):
def __new__(cls, numerator, denominator=None):
if not denominator:
ratio = list(map(int, numerator.split(':')))
if len(ratio) == 1:
ratio.append(1)
numerator = ratio[0]
denominator = ratio[1]
# if its close enough to the common aspect ratios rather use that
if abs(numerator/denominator - 4/3) < 0.03:
numerator = 4
denominator = 3
elif abs(numerator/denominator - 16/9) < 0.02:
numerator = 16
denominator = 9
return super(AspectRatio, cls).__new__(cls, numerator, denominator)
@property
def ratio(self):
return "%d:%d" % (self.numerator, self.denominator)
def supported_formats():
if not find_executable(settings.FFMPEG):
return None

25
pandora/archive/utils.py Normal file
View file

@ -0,0 +1,25 @@
import fractions
import math
class AspectRatio(fractions.Fraction):
def __new__(cls, numerator, denominator=None):
if not denominator:
ratio = list(map(int, numerator.split(':')))
if len(ratio) == 1:
ratio.append(1)
numerator = ratio[0]
denominator = ratio[1]
# if its close enough to the common aspect ratios rather use that
if abs(numerator/denominator - 4/3) < 0.03:
numerator = 4
denominator = 3
elif abs(numerator/denominator - 16/9) < 0.02:
numerator = 16
denominator = 9
return super(AspectRatio, cls).__new__(cls, numerator, denominator)
@property
def ratio(self):
return "%d:%d" % (self.numerator, self.denominator)