Improve video thumbnailer by skipping early black frames.
This commit is contained in:
parent
19a0322901
commit
ec8644dded
1 changed files with 32 additions and 18 deletions
|
@ -3,7 +3,9 @@ This file provides functions which are used in various places throughout the
|
|||
codebase but don't deserve to be methods of any class.
|
||||
'''
|
||||
import bs4
|
||||
import io
|
||||
import datetime
|
||||
import kkroening_ffmpeg
|
||||
import hashlib
|
||||
import os
|
||||
import PIL.Image
|
||||
|
@ -18,6 +20,9 @@ from voussoirkit import imagetools
|
|||
from voussoirkit import pathclass
|
||||
from voussoirkit import stringtools
|
||||
from voussoirkit import timetools
|
||||
from voussoirkit import vlogging
|
||||
|
||||
log = vlogging.get_logger(__name__)
|
||||
|
||||
from . import constants
|
||||
from . import exceptions
|
||||
|
@ -255,13 +260,23 @@ def generate_image_thumbnail(*args, trusted_file=False, **kwargs) -> PIL.Image:
|
|||
finally:
|
||||
PIL.Image.MAX_IMAGE_PIXELS = _max_pixels
|
||||
|
||||
def image_is_mostly_black(image):
|
||||
tiny = image.copy()
|
||||
tiny.thumbnail((64, 64))
|
||||
pixels = list(tiny.getdata())
|
||||
black_count = 0
|
||||
if tiny.mode == 'RGB':
|
||||
black_count = sum(1 for pixel in pixels if sum(pixel) <= 24)
|
||||
|
||||
return (black_count / len(pixels)) > 0.5
|
||||
|
||||
def generate_video_thumbnail(filepath, width, height, **special) -> PIL.Image:
|
||||
if not os.path.isfile(filepath):
|
||||
raise FileNotFoundError(filepath)
|
||||
file = pathclass.Path(filepath)
|
||||
file.assert_is_file()
|
||||
probe = constants.ffmpeg.probe(filepath)
|
||||
|
||||
if not probe or not probe.video:
|
||||
return False
|
||||
return None
|
||||
|
||||
size = imagetools.fit_into_bounds(
|
||||
image_width=probe.video.video_width,
|
||||
|
@ -269,26 +284,25 @@ def generate_video_thumbnail(filepath, width, height, **special) -> PIL.Image:
|
|||
frame_width=width,
|
||||
frame_height=height,
|
||||
)
|
||||
size = '%dx%d' % size
|
||||
duration = probe.video.duration
|
||||
|
||||
if 'timestamp' in special:
|
||||
timestamp = special['timestamp']
|
||||
elif duration < 3:
|
||||
timestamp = 0
|
||||
timestamp_choices = [special['timestamp']]
|
||||
else:
|
||||
timestamp = 2
|
||||
timestamp_choices = list(range(0, int(duration), 3))
|
||||
|
||||
image = None
|
||||
for this_time in timestamp_choices:
|
||||
log.debug('Attempting video thumbnail at t=%d', this_time)
|
||||
command = kkroening_ffmpeg.input(file.absolute_path, ss=this_time)
|
||||
command = command.filter('scale', size[0], size[1])
|
||||
command = command.output('pipe:', vcodec='bmp', format='image2pipe', vframes=1)
|
||||
(out, trash) = command.run(capture_stdout=True, capture_stderr=True)
|
||||
bio = io.BytesIO(out)
|
||||
image = PIL.Image.open(bio)
|
||||
if not image_is_mostly_black(image):
|
||||
break
|
||||
|
||||
outfile = tempfile.NamedTemporaryFile(suffix='.jpg', delete=False)
|
||||
constants.ffmpeg.thumbnail(
|
||||
filepath,
|
||||
outfile=outfile.name,
|
||||
quality=2,
|
||||
size=size,
|
||||
time=timestamp,
|
||||
)
|
||||
outfile.close()
|
||||
image = PIL.Image.open(outfile.name)
|
||||
return image
|
||||
|
||||
def get_mimetype(extension) -> typing.Optional[str]:
|
||||
|
|
Loading…
Reference in a new issue