2017-02-04 00:08:12 +00:00
|
|
|
from typing import List
|
2016-06-01 23:15:26 +00:00
|
|
|
import logging
|
2016-04-30 21:17:08 +00:00
|
|
|
import json
|
2016-06-01 23:15:26 +00:00
|
|
|
import shlex
|
2016-04-09 19:41:10 +00:00
|
|
|
import subprocess
|
2016-06-01 23:34:06 +00:00
|
|
|
import math
|
2016-04-09 19:41:10 +00:00
|
|
|
from szurubooru import errors
|
2016-06-01 23:15:26 +00:00
|
|
|
from szurubooru.func import mime, util
|
|
|
|
|
2016-08-14 12:22:53 +00:00
|
|
|
|
2016-06-01 23:15:26 +00:00
|
|
|
logger = logging.getLogger(__name__)
|
2016-04-09 19:41:10 +00:00
|
|
|
|
2016-08-14 12:22:53 +00:00
|
|
|
|
2016-10-22 12:43:52 +00:00
|
|
|
class Image:
|
2017-02-04 00:08:12 +00:00
|
|
|
def __init__(self, content: bytes) -> None:
|
2016-04-09 19:41:10 +00:00
|
|
|
self.content = content
|
2016-04-30 21:17:08 +00:00
|
|
|
self._reload_info()
|
|
|
|
|
|
|
|
@property
|
2017-02-04 00:08:12 +00:00
|
|
|
def width(self) -> int:
|
2016-04-30 21:17:08 +00:00
|
|
|
return self.info['streams'][0]['width']
|
|
|
|
|
|
|
|
@property
|
2017-02-04 00:08:12 +00:00
|
|
|
def height(self) -> int:
|
2016-04-30 21:17:08 +00:00
|
|
|
return self.info['streams'][0]['height']
|
|
|
|
|
|
|
|
@property
|
2017-02-04 00:08:12 +00:00
|
|
|
def frames(self) -> int:
|
2016-04-30 21:17:08 +00:00
|
|
|
return self.info['streams'][0]['nb_read_frames']
|
2016-04-09 19:41:10 +00:00
|
|
|
|
2017-02-04 00:08:12 +00:00
|
|
|
def resize_fill(self, width: int, height: int) -> None:
|
2018-03-08 06:41:24 +00:00
|
|
|
width_greater = self.width > self.height
|
|
|
|
width, height = (-1, height) if width_greater else (width, -1)
|
|
|
|
|
2016-06-01 23:34:06 +00:00
|
|
|
cli = [
|
2016-05-20 19:34:02 +00:00
|
|
|
'-i', '{path}',
|
2016-04-09 19:41:10 +00:00
|
|
|
'-f', 'image2',
|
2018-03-08 06:41:24 +00:00
|
|
|
'-filter:v', "scale='{width}:{height}'".format(
|
|
|
|
width=width, height=height),
|
2017-01-25 16:12:06 +00:00
|
|
|
'-map', '0:v:0',
|
2016-04-09 19:41:10 +00:00
|
|
|
'-vframes', '1',
|
|
|
|
'-vcodec', 'png',
|
|
|
|
'-',
|
2016-06-01 23:34:06 +00:00
|
|
|
]
|
|
|
|
if 'duration' in self.info['format'] \
|
|
|
|
and self.info['format']['format_name'] != 'swf':
|
2016-08-14 12:22:53 +00:00
|
|
|
duration = float(self.info['format']['duration'])
|
|
|
|
if duration > 3:
|
|
|
|
cli = [
|
|
|
|
'-ss',
|
|
|
|
'%d' % math.floor(duration * 0.3),
|
|
|
|
] + cli
|
2018-03-08 06:41:24 +00:00
|
|
|
content = self._execute(cli, ignore_error_if_data=True)
|
2017-05-03 10:09:18 +00:00
|
|
|
if not content:
|
|
|
|
raise errors.ProcessingError('Error while resizing image.')
|
|
|
|
self.content = content
|
2016-04-30 21:17:08 +00:00
|
|
|
self._reload_info()
|
2016-04-09 19:41:10 +00:00
|
|
|
|
2017-02-04 00:08:12 +00:00
|
|
|
def to_png(self) -> bytes:
|
2016-04-09 19:41:10 +00:00
|
|
|
return self._execute([
|
2016-05-20 19:34:02 +00:00
|
|
|
'-i', '{path}',
|
2016-04-09 19:41:10 +00:00
|
|
|
'-f', 'image2',
|
2017-01-25 16:12:06 +00:00
|
|
|
'-map', '0:v:0',
|
2016-04-09 19:41:10 +00:00
|
|
|
'-vframes', '1',
|
2016-06-02 09:52:54 +00:00
|
|
|
'-vcodec', 'png',
|
2016-04-09 19:41:10 +00:00
|
|
|
'-',
|
|
|
|
])
|
|
|
|
|
2017-02-04 00:08:12 +00:00
|
|
|
def to_jpeg(self) -> bytes:
|
2016-06-02 09:52:54 +00:00
|
|
|
return self._execute([
|
|
|
|
'-f', 'lavfi',
|
|
|
|
'-i', 'color=white:s=%dx%d' % (self.width, self.height),
|
|
|
|
'-i', '{path}',
|
|
|
|
'-f', 'image2',
|
|
|
|
'-filter_complex', 'overlay',
|
2017-01-25 16:12:06 +00:00
|
|
|
'-map', '0:v:0',
|
2016-06-02 09:52:54 +00:00
|
|
|
'-vframes', '1',
|
|
|
|
'-vcodec', 'mjpeg',
|
|
|
|
'-',
|
|
|
|
])
|
2016-04-09 19:41:10 +00:00
|
|
|
|
2018-03-08 06:41:24 +00:00
|
|
|
def _execute(
|
|
|
|
self,
|
|
|
|
cli: List[str],
|
|
|
|
program: str = 'ffmpeg',
|
|
|
|
ignore_error_if_data: bool = False) -> bytes:
|
2016-06-01 23:15:26 +00:00
|
|
|
extension = mime.get_extension(mime.get_mime_type(self.content))
|
|
|
|
assert extension
|
|
|
|
with util.create_temp_file(suffix='.' + extension) as handle:
|
2016-05-20 19:34:02 +00:00
|
|
|
handle.write(self.content)
|
|
|
|
handle.flush()
|
|
|
|
cli = [program, '-loglevel', '24'] + cli
|
|
|
|
cli = [part.format(path=handle.name) for part in cli]
|
|
|
|
proc = subprocess.Popen(
|
|
|
|
cli,
|
|
|
|
stdout=subprocess.PIPE,
|
|
|
|
stdin=subprocess.PIPE,
|
|
|
|
stderr=subprocess.PIPE)
|
|
|
|
out, err = proc.communicate(input=self.content)
|
|
|
|
if proc.returncode != 0:
|
2016-06-01 23:15:26 +00:00
|
|
|
logger.warning(
|
|
|
|
'Failed to execute ffmpeg command (cli=%r, err=%r)',
|
|
|
|
' '.join(shlex.quote(arg) for arg in cli),
|
|
|
|
err)
|
2018-03-08 06:41:24 +00:00
|
|
|
if ((len(out) > 0 and not ignore_error_if_data)
|
|
|
|
or len(out) == 0):
|
|
|
|
raise errors.ProcessingError(
|
|
|
|
'Error while processing image.\n'
|
|
|
|
+ err.decode('utf-8'))
|
2016-05-20 19:34:02 +00:00
|
|
|
return out
|
2016-04-30 21:17:08 +00:00
|
|
|
|
2017-02-04 00:08:12 +00:00
|
|
|
def _reload_info(self) -> None:
|
2016-04-30 21:17:08 +00:00
|
|
|
self.info = json.loads(self._execute([
|
2016-06-01 23:15:26 +00:00
|
|
|
'-i', '{path}',
|
2016-04-30 21:17:08 +00:00
|
|
|
'-of', 'json',
|
|
|
|
'-select_streams', 'v',
|
2016-06-01 23:34:06 +00:00
|
|
|
'-show_format',
|
2016-04-30 21:17:08 +00:00
|
|
|
'-show_streams',
|
|
|
|
], program='ffprobe').decode('utf-8'))
|
2016-06-01 23:34:06 +00:00
|
|
|
assert 'format' in self.info
|
2016-04-30 21:17:08 +00:00
|
|
|
assert 'streams' in self.info
|
2017-01-25 16:12:06 +00:00
|
|
|
if len(self.info['streams']) < 1:
|
|
|
|
logger.warning('The video contains no video streams.')
|
2017-02-03 20:42:15 +00:00
|
|
|
raise errors.ProcessingError(
|
|
|
|
'The video contains no video streams.')
|