Skip to content

Commit

Permalink
Always define pixfmt for timelapse in video filter chain
Browse files Browse the repository at this point in the history
Apparently having a chain AND the pix_fmt parameter
produces issues with higher resolutions.

Should fix #1317
  • Loading branch information
foosel committed Jan 17, 2017
1 parent 4c971a9 commit 6bd788a
Show file tree
Hide file tree
Showing 2 changed files with 78 additions and 29 deletions.
41 changes: 12 additions & 29 deletions src/octoprint/timelapse.py
Expand Up @@ -809,17 +809,10 @@ def _render(self):

@classmethod
def _create_ffmpeg_command_string(cls, ffmpeg, fps, bitrate, threads, input, output, hflip=False, vflip=False,
rotate=False, watermark=None):
rotate=False, watermark=None, pixfmt="yuv420p"):
"""
Create ffmpeg command string based on input parameters.
Examples:
>>> TimelapseRenderJob._create_ffmpeg_command_string("/path/to/ffmpeg", 25, "10000k", 1, "/path/to/input/files_%d.jpg", "/path/to/output.mpg")
'/path/to/ffmpeg -framerate 25 -loglevel error -i "/path/to/input/files_%d.jpg" -vcodec mpeg2video -threads 1 -pix_fmt yuv420p -r 25 -y -b 10000k -f vob "/path/to/output.mpg"'
>>> TimelapseRenderJob._create_ffmpeg_command_string("/path/to/ffmpeg", 25, "10000k", 1, "/path/to/input/files_%d.jpg", "/path/to/output.mpg", hflip=True)
'/path/to/ffmpeg -framerate 25 -loglevel error -i "/path/to/input/files_%d.jpg" -vcodec mpeg2video -threads 1 -pix_fmt yuv420p -r 25 -y -b 10000k -f vob -vf \\'[in] hflip [out]\\' "/path/to/output.mpg"'
Arguments:
ffmpeg (str): Path to ffmpeg
fps (int): Frames per second for output
Expand All @@ -831,16 +824,19 @@ def _create_ffmpeg_command_string(cls, ffmpeg, fps, bitrate, threads, input, out
vflip (bool): Perform vertical flip on input material.
rotate (bool): Perform 90° CCW rotation on input material.
watermark (str): Path to watermark to apply to lower left corner.
pixfmt (str): Pixel format to use for output. Default of yuv420p should usually fit the bill.
Returns:
(str): Prepared command string to render `input` to `output` using ffmpeg.
"""

### See unit tests in test/timelapse/test_timelapse_renderjob.py

logger = logging.getLogger(__name__)

command = [
ffmpeg, '-framerate', str(fps), '-loglevel', 'error', '-i', '"{}"'.format(input), '-vcodec', 'mpeg2video',
'-threads', str(threads), '-pix_fmt', 'yuv420p', '-r', "25", '-y', '-b', str(bitrate),
'-threads', str(threads), '-r', "25", '-y', '-b', str(bitrate),
'-f', 'vob']

filter_string = cls._create_filter_string(hflip=hflip,
Expand All @@ -859,38 +855,25 @@ def _create_ffmpeg_command_string(cls, ffmpeg, fps, bitrate, threads, input, out
return " ".join(command)

@classmethod
def _create_filter_string(cls, hflip=False, vflip=False, rotate=False, watermark=None):
def _create_filter_string(cls, hflip=False, vflip=False, rotate=False, watermark=None, pixfmt="yuv420p"):
"""
Creates an ffmpeg filter string based on input parameters.
Examples:
>>> TimelapseRenderJob._create_filter_string()
>>> TimelapseRenderJob._create_filter_string(hflip=True)
'[in] hflip [out]'
>>> TimelapseRenderJob._create_filter_string(vflip=True)
'[in] vflip [out]'
>>> TimelapseRenderJob._create_filter_string(rotate=True)
'[in] transpose=2 [out]'
>>> TimelapseRenderJob._create_filter_string(vflip=True, rotate=True)
'[in] vflip,transpose=2 [out]'
>>> TimelapseRenderJob._create_filter_string(vflip=True, hflip=True, rotate=True)
'[in] hflip,vflip,transpose=2 [out]'
>>> TimelapseRenderJob._create_filter_string(watermark="/path/to/watermark.png")
'movie=/path/to/watermark.png [wm]; [in][wm] overlay=10:main_h-overlay_h-10 [out]'
>>> TimelapseRenderJob._create_filter_string(hflip=True, watermark="/path/to/watermark.png")
'[in] hflip [postprocessed]; movie=/path/to/watermark.png [wm]; [postprocessed][wm] overlay=10:main_h-overlay_h-10 [out]'
Arguments:
hflip (bool): Perform horizontal flip on input material.
vflip (bool): Perform vertical flip on input material.
rotate (bool): Perform 90° CCW rotation on input material.
watermark (str): Path to watermark to apply to lower left corner.
pixfmt (str): Pixel format to use, defaults to "yuv420p" which should usually fit the bill
Returns:
(str or None): filter string or None if no filters are required
"""
filters = []

### See unit tests in test/timelapse/test_timelapse_renderjob.py

# apply pixel format
filters = ["format={}".format(pixfmt)]

# flip video if configured
if hflip:
Expand Down
66 changes: 66 additions & 0 deletions tests/timelapse/test_timelapse_renderjob.py
@@ -0,0 +1,66 @@
# coding=utf-8
from __future__ import absolute_import

__license__ = 'GNU Affero General Public License http://www.gnu.org/licenses/agpl.html'
__copyright__ = "Copyright (C) 2016 The OctoPrint Project - Released under terms of the AGPLv3 License"

import unittest

from ddt import ddt, data, unpack

from octoprint.timelapse import TimelapseRenderJob

@ddt
class TimelapseRenderJobTest(unittest.TestCase):

@data(
(("/path/to/ffmpeg", 25, "10000k", 1, "/path/to/input/files_%d.jpg", "/path/to/output.mpg"),
dict(),
'/path/to/ffmpeg -framerate 25 -loglevel error -i "/path/to/input/files_%d.jpg" -vcodec mpeg2video -threads 1 -r 25 -y -b 10000k -f vob -vf \'[in] format=yuv420p [out]\' "/path/to/output.mpg"'),
(("/path/to/ffmpeg", 25, "10000k", 1, "/path/to/input/files_%d.jpg", "/path/to/output.mpg"),
dict(hflip=True),
'/path/to/ffmpeg -framerate 25 -loglevel error -i "/path/to/input/files_%d.jpg" -vcodec mpeg2video -threads 1 -r 25 -y -b 10000k -f vob -vf \'[in] format=yuv420p,hflip [out]\' "/path/to/output.mpg"'),
(("/path/to/ffmpeg", 25, "20000k", 4, "/path/to/input/files_%d.jpg", "/path/to/output.mpg"),
dict(rotate=True, watermark="/path/to/watermark.png"),
'/path/to/ffmpeg -framerate 25 -loglevel error -i "/path/to/input/files_%d.jpg" -vcodec mpeg2video -threads 4 -r 25 -y -b 20000k -f vob -vf \'[in] format=yuv420p,transpose=2 [postprocessed]; movie=/path/to/watermark.png [wm]; [postprocessed][wm] overlay=10:main_h-overlay_h-10 [out]\' "/path/to/output.mpg"')
)
@unpack
def test_create_ffmpeg_command_string(self, args, kwargs, expected):
actual = TimelapseRenderJob._create_ffmpeg_command_string(*args, **kwargs)
self.assertEquals(actual, expected)

@data(
(dict(),
'[in] format=yuv420p [out]'),
(dict(pixfmt="test"),
'[in] format=test [out]'),
(dict(hflip=True),
'[in] format=yuv420p,hflip [out]'),
(dict(vflip=True),
'[in] format=yuv420p,vflip [out]'),
(dict(rotate=True),
'[in] format=yuv420p,transpose=2 [out]'),
(dict(vflip=True, rotate=True),
'[in] format=yuv420p,vflip,transpose=2 [out]'),
(dict(vflip=True, hflip=True, rotate=True),
'[in] format=yuv420p,hflip,vflip,transpose=2 [out]'),
(dict(watermark="/path/to/watermark.png"),
'[in] format=yuv420p [postprocessed]; movie=/path/to/watermark.png [wm]; [postprocessed][wm] overlay=10:main_h-overlay_h-10 [out]'),
(dict(hflip=True, watermark="/path/to/watermark.png"),
'[in] format=yuv420p,hflip [postprocessed]; movie=/path/to/watermark.png [wm]; [postprocessed][wm] overlay=10:main_h-overlay_h-10 [out]'),
)
@unpack
def test_create_filter_string(self, kwargs, expected):
actual = TimelapseRenderJob._create_filter_string(**kwargs)
self.assertEquals(actual, expected)

0 comments on commit 6bd788a

Please sign in to comment.