initial commit
first commit of ffmpegwrapper.
This commit is contained in:
commit
5541e2a07c
3
ffmpegwrapper/__init__.py
Normal file
3
ffmpegwrapper/__init__.py
Normal file
|
|
@ -0,0 +1,3 @@
|
||||||
|
# -*- coding: utf8 -*-
|
||||||
|
|
||||||
|
from ffmpegwrapper.ffmpeg import FFmpeg, Input, Output
|
||||||
24
ffmpegwrapper/codec.py
Normal file
24
ffmpegwrapper/codec.py
Normal file
|
|
@ -0,0 +1,24 @@
|
||||||
|
# -*- coding: utf8 -*-
|
||||||
|
|
||||||
|
from itertools import chain
|
||||||
|
|
||||||
|
from .options import CombinedOptions
|
||||||
|
|
||||||
|
|
||||||
|
class Codec(CombinedOptions):
|
||||||
|
|
||||||
|
def __init__(self, name, *args):
|
||||||
|
self.name = name
|
||||||
|
CombinedOptions.__init__(self, *args)
|
||||||
|
|
||||||
|
|
||||||
|
class VideoCodec(Codec):
|
||||||
|
|
||||||
|
def __iter__(self):
|
||||||
|
return chain(['-vcodec', self.name], Codec.__iter__(self))
|
||||||
|
|
||||||
|
|
||||||
|
class AudioCodec(Codec):
|
||||||
|
|
||||||
|
def __iter__(self):
|
||||||
|
return chain(['-acodec', self.name], Codec.__iter__(self))
|
||||||
48
ffmpegwrapper/ffmpeg.py
Normal file
48
ffmpegwrapper/ffmpeg.py
Normal file
|
|
@ -0,0 +1,48 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
"""
|
||||||
|
FFmpeg
|
||||||
|
|
||||||
|
Your entrypoint for every Task you want to do with FFmpeg
|
||||||
|
|
||||||
|
:copyright: (c) 2011 by Mathias Koehler.
|
||||||
|
:license: BSD, see LICENSE for more details.
|
||||||
|
"""
|
||||||
|
|
||||||
|
from subprocess import Popen, PIPE
|
||||||
|
from itertools import chain
|
||||||
|
|
||||||
|
from .options import CombinedOptions, Options
|
||||||
|
|
||||||
|
|
||||||
|
class Input(CombinedOptions):
|
||||||
|
|
||||||
|
def __init__(self, file, *args):
|
||||||
|
self.file = file
|
||||||
|
CombinedOptions.__init__(self, *args)
|
||||||
|
|
||||||
|
def __iter__(self):
|
||||||
|
return chain(CombinedOptions.__iter__(self), ['-i', self.file])
|
||||||
|
|
||||||
|
|
||||||
|
class Output(CombinedOptions):
|
||||||
|
|
||||||
|
def __init__(self, file, *args):
|
||||||
|
self.file = file
|
||||||
|
CombinedOptions.__init__(self, *args)
|
||||||
|
|
||||||
|
def __iter__(self):
|
||||||
|
return chain(CombinedOptions.__iter__(self), [self.file])
|
||||||
|
|
||||||
|
|
||||||
|
class FFmpeg(CombinedOptions):
|
||||||
|
|
||||||
|
def __init__(self, binary="ffmpeg", *args):
|
||||||
|
self.binary = binary
|
||||||
|
CombinedOptions.__init__(self, *args)
|
||||||
|
|
||||||
|
def run(self):
|
||||||
|
return Popen(self, executable=self.binary, stdin=PIPE,
|
||||||
|
stdout=PIPE, stderr=PIPE)
|
||||||
|
|
||||||
|
def add_option(self, key, value):
|
||||||
|
self._list.insert(0, Options({key: value}))
|
||||||
195
ffmpegwrapper/filter.py
Normal file
195
ffmpegwrapper/filter.py
Normal file
|
|
@ -0,0 +1,195 @@
|
||||||
|
# -*- coding: utf8 -*-
|
||||||
|
|
||||||
|
from itertools import chain, islice
|
||||||
|
|
||||||
|
from .options import CombinedOptions
|
||||||
|
|
||||||
|
|
||||||
|
class CombinedFilter(CombinedOptions):
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return ",".join(CombinedFilter.__iter__(self))
|
||||||
|
|
||||||
|
def __iter__(self):
|
||||||
|
i = CombinedOptions.__iter__
|
||||||
|
keys = islice(i(self), 0, None, 2)
|
||||||
|
values = islice(i(self), 1, None, 2)
|
||||||
|
|
||||||
|
for key, value in zip(keys, values):
|
||||||
|
if value:
|
||||||
|
yield "=".join([key, str(value)])
|
||||||
|
else:
|
||||||
|
yield key
|
||||||
|
|
||||||
|
class VideoFilter(CombinedFilter):
|
||||||
|
|
||||||
|
def blackframe(self, amount, threshold):
|
||||||
|
filter = self._format_parameter(amount, threshold)
|
||||||
|
self.add_option('blackframe', filter)
|
||||||
|
return self
|
||||||
|
|
||||||
|
def copy(self):
|
||||||
|
self.add_option('copy', None)
|
||||||
|
return self
|
||||||
|
|
||||||
|
def crop(self, out_w, out_h=None, x=None, y=None):
|
||||||
|
filter = self._format_parameter(out_w, out_h, x, y)
|
||||||
|
self.add_option('crop', filter)
|
||||||
|
return self
|
||||||
|
|
||||||
|
def cropdetect(self, limit=None, round=None, reset=None):
|
||||||
|
filter = self._format_parameter(limit, round, reset)
|
||||||
|
self.add_option('cropdetect', filter)
|
||||||
|
return self
|
||||||
|
|
||||||
|
def drawbox(self, x, y, width, height, color):
|
||||||
|
filter = self._format_parameter(x, y, width, height, color)
|
||||||
|
self.add_option('drawbox', filter)
|
||||||
|
return self
|
||||||
|
|
||||||
|
def drawtext(self, **kwargs):
|
||||||
|
filter = self._format_keyword_parameter(**kwargs)
|
||||||
|
self.add_option('drawtext', filter)
|
||||||
|
return self
|
||||||
|
|
||||||
|
def fade(self, type, start, number):
|
||||||
|
filter = self._format_parameter(type, start, number)
|
||||||
|
self.add_option('fade', filter)
|
||||||
|
return self
|
||||||
|
|
||||||
|
def fieldorder(self, type):
|
||||||
|
if str(type) not in ['0', '1', 'bff', 'tff']:
|
||||||
|
raise ValueError('Invalid Option for fieldorder. '
|
||||||
|
'Read FFmpeg manual!')
|
||||||
|
self.add_option('fieldorder', type)
|
||||||
|
return self
|
||||||
|
|
||||||
|
def fifo(self):
|
||||||
|
self.add_option('fifo', None)
|
||||||
|
return self
|
||||||
|
|
||||||
|
def format(self, *args):
|
||||||
|
filter = self._format_parameter(*args)
|
||||||
|
self.add_option('format', filter)
|
||||||
|
return self
|
||||||
|
|
||||||
|
def freior(self, name, **kwargs):
|
||||||
|
filter = self._format_keyword_parameter(name, **kwargs)
|
||||||
|
self.add_option('frei0r', filter)
|
||||||
|
return self
|
||||||
|
|
||||||
|
def gradfun(self, strength='', radius=''):
|
||||||
|
filter = self._format_parameter(strength, radius)
|
||||||
|
self.add_option('gradfun', filter)
|
||||||
|
return self
|
||||||
|
|
||||||
|
def hflip(self):
|
||||||
|
self.add_option('hflip', None)
|
||||||
|
return self
|
||||||
|
|
||||||
|
def hqdn3d(self, luma_sp='', chroma_sp='', luma_tmp='', chroma_tmp=''):
|
||||||
|
filter = self._format_parameter(
|
||||||
|
luma_sp, chroma_sp, luma_tmp, chroma_tmp)
|
||||||
|
self.add_option('hqdn3d', filter)
|
||||||
|
return self
|
||||||
|
|
||||||
|
def mp(self, **kwargs):
|
||||||
|
filter = self._format_keyword_parameter(**kwargs)
|
||||||
|
self.add_option('mp', filter)
|
||||||
|
return self
|
||||||
|
|
||||||
|
def negate(self):
|
||||||
|
self.add_option('negate', 1)
|
||||||
|
return self
|
||||||
|
|
||||||
|
def noformat(self, *args):
|
||||||
|
filter = self._format_parameter(*args)
|
||||||
|
self.add_option('noformat', filter)
|
||||||
|
return self
|
||||||
|
|
||||||
|
def null(self):
|
||||||
|
self.add_option('null', None)
|
||||||
|
return self
|
||||||
|
|
||||||
|
def overlay(self, x, y):
|
||||||
|
filter = self._format_parameter(x, y)
|
||||||
|
self.add_option('overlay', filter)
|
||||||
|
return self
|
||||||
|
|
||||||
|
def pad(self, width, height, x, y, color):
|
||||||
|
filter = self._format_parameter(width, height, x, y, color)
|
||||||
|
self.add_option('pad', filter)
|
||||||
|
return self
|
||||||
|
|
||||||
|
def scale(self, width=-1, height=-1):
|
||||||
|
filter = self._format_parameter(width, height)
|
||||||
|
self.add_option('scale', filter)
|
||||||
|
return self
|
||||||
|
|
||||||
|
def select(self, expression):
|
||||||
|
self.add_option('select', expression)
|
||||||
|
return self
|
||||||
|
|
||||||
|
def setdar(self, aspect):
|
||||||
|
self.add_option('setdar', aspect)
|
||||||
|
return self
|
||||||
|
|
||||||
|
def setpts(self, expression):
|
||||||
|
self.add_option('setpts', expression)
|
||||||
|
return self
|
||||||
|
|
||||||
|
def setsar(self, aspect):
|
||||||
|
self.add_option('setsar', aspect)
|
||||||
|
return self
|
||||||
|
|
||||||
|
def settb(self, expression):
|
||||||
|
self.add_option('settb', expression)
|
||||||
|
return self
|
||||||
|
|
||||||
|
def showinfo(self, **kwargs):
|
||||||
|
filter = self._format_keyword_parameter(**kwargs)
|
||||||
|
self.add_option('showinfo', filter)
|
||||||
|
return self
|
||||||
|
|
||||||
|
def slicify(self, height=16):
|
||||||
|
self.add_option("slicify", height)
|
||||||
|
return self
|
||||||
|
|
||||||
|
def transpose(self, type):
|
||||||
|
if str(type) not in ['0', '1','2', '3']:
|
||||||
|
raise ValueError('Invalid Option for transpose. '
|
||||||
|
'Read FFmpeg manual')
|
||||||
|
self.add_option('transpose', type)
|
||||||
|
return self
|
||||||
|
|
||||||
|
def vflip(self):
|
||||||
|
self.add_option('vflip', None)
|
||||||
|
return self
|
||||||
|
|
||||||
|
def yadif(self, mode=0, parity=-1):
|
||||||
|
filter = self._format_parameter(mode, parity)
|
||||||
|
self.add_option('yadif', filter)
|
||||||
|
return self
|
||||||
|
|
||||||
|
def _format_parameter(self, *args):
|
||||||
|
parameter = filter(lambda x: x is not None, args)
|
||||||
|
return ':'.join(map(str, parameter))
|
||||||
|
|
||||||
|
def _format_keyword_parameter(self, **kwargs):
|
||||||
|
parameter_list = []
|
||||||
|
for key, value in kwargs:
|
||||||
|
try:
|
||||||
|
parameter_list.append("=".join([key, value]))
|
||||||
|
except TypeError:
|
||||||
|
values = ":".join(value)
|
||||||
|
parameter_list.append("=".join([key, values]))
|
||||||
|
return ":".join(parameter_list)
|
||||||
|
|
||||||
|
def __iter__(self):
|
||||||
|
return chain(['-vf', CombinedFilter.__str__(self)])
|
||||||
|
|
||||||
|
|
||||||
|
class AudioFilter(CombinedFilter):
|
||||||
|
|
||||||
|
def __iter__(self):
|
||||||
|
return chain(['-af', CombinedFilter.__str__(self)])
|
||||||
69
ffmpegwrapper/options.py
Normal file
69
ffmpegwrapper/options.py
Normal file
|
|
@ -0,0 +1,69 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
from itertools import chain
|
||||||
|
|
||||||
|
|
||||||
|
class Options(dict):
|
||||||
|
|
||||||
|
def __init__(self, *args, **kwargs):
|
||||||
|
dict.__init__(self, *args, **kwargs)
|
||||||
|
|
||||||
|
def __getitem__(self, key):
|
||||||
|
if key in self:
|
||||||
|
return dict.__getitem__(self, key)
|
||||||
|
|
||||||
|
def __iter__(self):
|
||||||
|
return self.iteritems()
|
||||||
|
|
||||||
|
def iteritems(self):
|
||||||
|
for option, value in self.items():
|
||||||
|
yield option
|
||||||
|
if value:
|
||||||
|
yield value
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
return "<{cls} {opts}>".format(opts=list(self),
|
||||||
|
cls=self.__class__.__name__)
|
||||||
|
|
||||||
|
|
||||||
|
class CombinedOptions(object):
|
||||||
|
|
||||||
|
def __init__(self, *args):
|
||||||
|
self._list = list(args)
|
||||||
|
|
||||||
|
def __add__(self, other):
|
||||||
|
self.append(other)
|
||||||
|
|
||||||
|
def append(self, item):
|
||||||
|
self._list.append(item)
|
||||||
|
|
||||||
|
def insert(self, item):
|
||||||
|
self._list.insert(item)
|
||||||
|
|
||||||
|
def pop(self):
|
||||||
|
return self._list.pop()
|
||||||
|
|
||||||
|
def remove(self, item):
|
||||||
|
self._list.remove()
|
||||||
|
|
||||||
|
def count(self, items):
|
||||||
|
return self._list.count()
|
||||||
|
|
||||||
|
def index(self, item):
|
||||||
|
return self._list.index(item)
|
||||||
|
|
||||||
|
def add_option(self, key, value):
|
||||||
|
self._list.append(Options({key: value}))
|
||||||
|
|
||||||
|
@property
|
||||||
|
def option_containers(self):
|
||||||
|
return self._list
|
||||||
|
|
||||||
|
def __iter__(self):
|
||||||
|
for item in self._list:
|
||||||
|
for option in item:
|
||||||
|
yield option
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
return "<{cls} {opts}>".format(opts=list(self),
|
||||||
|
cls=self.__class__.__name__)
|
||||||
69
test.py
Normal file
69
test.py
Normal file
|
|
@ -0,0 +1,69 @@
|
||||||
|
# -*- coding: utf8 -*-
|
||||||
|
|
||||||
|
import unittest
|
||||||
|
|
||||||
|
from ffmpegwrapper import FFmpeg, Input, Output
|
||||||
|
from ffmpegwrapper.codec import VideoCodec, AudioCodec
|
||||||
|
from ffmpegwrapper.filter import VideoFilter
|
||||||
|
from ffmpegwrapper.options import Options
|
||||||
|
|
||||||
|
|
||||||
|
class FFmpegTestCase(unittest.TestCase):
|
||||||
|
|
||||||
|
def test_input_interface(self):
|
||||||
|
input = Input('/old')
|
||||||
|
self.assertEqual(list(input), ['-i', '/old'])
|
||||||
|
self.assertEqual(input.file, '/old')
|
||||||
|
|
||||||
|
option = Options({'-vf': 'x11grab'})
|
||||||
|
input.append(option)
|
||||||
|
self.assertEqual(list(input), ['-vf', 'x11grab', '-i', '/old'])
|
||||||
|
self.assertEqual(input.pop(), option)
|
||||||
|
|
||||||
|
input.add_option('-vf', 'x11grab')
|
||||||
|
self.assertEqual(input.option_containers, [option])
|
||||||
|
|
||||||
|
def test_output_interface(self):
|
||||||
|
output = Output('/new')
|
||||||
|
self.assertEqual(list(output), ['/new'])
|
||||||
|
self.assertEqual(output.file, '/new')
|
||||||
|
|
||||||
|
option = Options({'-vcodec': 'libx264'})
|
||||||
|
output.append(option)
|
||||||
|
self.assertEqual(list(output), ['-vcodec', 'libx264', '/new'])
|
||||||
|
self.assertEqual(output.pop(), option)
|
||||||
|
|
||||||
|
output.add_option('-vcodec', 'libx264')
|
||||||
|
self.assertEqual(output.option_containers, [option])
|
||||||
|
|
||||||
|
def test_codec_interface(self):
|
||||||
|
codec = VideoCodec('libx264')
|
||||||
|
self.assertEqual(list(codec), ['-vcodec', 'libx264'])
|
||||||
|
|
||||||
|
codec = AudioCodec('ac3')
|
||||||
|
self.assertEqual(list(codec), ['-acodec', 'ac3'])
|
||||||
|
|
||||||
|
def test_filter_interface(self):
|
||||||
|
filter = VideoFilter()
|
||||||
|
filter.blackframe(1, 2).crop(792)
|
||||||
|
self.assertEqual(list(filter), ['-vf',
|
||||||
|
'blackframe=1:2,crop=792'])
|
||||||
|
|
||||||
|
output = Output('/new', filter)
|
||||||
|
self.assertEqual(list(output), ['-vf',
|
||||||
|
'blackframe=1:2,crop=792', '/new'])
|
||||||
|
|
||||||
|
def test_ffmpeg_interface(self):
|
||||||
|
input = Input('/old')
|
||||||
|
output = Output('/new')
|
||||||
|
|
||||||
|
ffmpeg = FFmpeg('ffmpeg', input, output)
|
||||||
|
self.assertEqual(list(ffmpeg), ['-i', '/old', '/new'])
|
||||||
|
|
||||||
|
|
||||||
|
class VideoFilter(unittest.TestCase):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
unittest.main()
|
||||||
Loading…
Reference in a new issue