cut detection based on fps of source material
This commit is contained in:
parent
af09508a87
commit
4e5a84e96a
3 changed files with 115 additions and 22 deletions
89
pandora/archive/cutdetection.py
Normal file
89
pandora/archive/cutdetection.py
Normal 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
|
||||||
|
|
||||||
|
|
@ -23,6 +23,7 @@ import pillow_avif
|
||||||
from pillow_heif import register_heif_opener
|
from pillow_heif import register_heif_opener
|
||||||
|
|
||||||
from .chop import Chop, make_keyframe_index
|
from .chop import Chop, make_keyframe_index
|
||||||
|
from .utils import AspectRatio
|
||||||
|
|
||||||
|
|
||||||
register_heif_opener()
|
register_heif_opener()
|
||||||
|
|
@ -33,28 +34,6 @@ img_extension = 'jpg'
|
||||||
MAX_DISTANCE = math.sqrt(3 * pow(255, 2))
|
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():
|
def supported_formats():
|
||||||
if not find_executable(settings.FFMPEG):
|
if not find_executable(settings.FFMPEG):
|
||||||
return None
|
return None
|
||||||
|
|
|
||||||
25
pandora/archive/utils.py
Normal file
25
pandora/archive/utils.py
Normal 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)
|
||||||
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue