diff --git a/ffmpegwrapper/__init__.py b/ffmpegwrapper/__init__.py index 8bf1324..cc5ed46 100644 --- a/ffmpegwrapper/__init__.py +++ b/ffmpegwrapper/__init__.py @@ -1,3 +1,5 @@ # -*- coding: utf8 -*- -from ffmpegwrapper.ffmpeg import FFmpeg, Input, Output +from .ffmpeg import FFmpeg, Input, Output +from .codec import VideoCodec, AudioCodec, NO_AUDIO, NO_VIDEO +from .filter import VideoFilter, AudioFilter diff --git a/ffmpegwrapper/codec.py b/ffmpegwrapper/codec.py index 391037c..5bdc017 100644 --- a/ffmpegwrapper/codec.py +++ b/ffmpegwrapper/codec.py @@ -5,6 +5,10 @@ from itertools import chain from .options import CombinedOptions +NO_AUDIO = ['-an'] +NO_VIDEO = ['-vn'] + + class Codec(CombinedOptions): def __init__(self, name, *args): @@ -14,11 +18,95 @@ class Codec(CombinedOptions): class VideoCodec(Codec): + def bitrate(self, bitrate): + self.add_option('-b', str(bitrate)) + return self + + def frames(self, number): + self.add_option('-vframes', str(number)) + return self + + def fps(self, fps): + self.add_option('-r', str(fps)) + return self + + def size(self, x, y): + filter = "{x}x{y}".format(x, y) + self.add_option('-s', filter) + return self + + def aspect(self, x, y): + filter = self._format_parameter(x, y) + self.add_option('-aspect', filter) + return self + + def bitrate_tolerance(self, tolerance): + self.add_option('-bt', str(tolerance)) + return self + + def max_bitrate(self, rate): + self.add_option('-maxrate', str(rate)) + return self + + def min_bitrate(self, rate): + self.add_option('-minrate', str(rate)) + return self + + def buffer_size(self, size): + self.add_option('-bufsize', str(size)) + return self + + def pass_number(self, number): + self.add_option('-pass', str(number)) + return self + + def language(self, lang): + self.add_option('-vlang', str(lang)) + return self + + def same_quality(self): + self.add_option('-sameq', None) + return self + + def preset(self, preset): + self.add_option('-vpre', str(preset)) + return self + def __iter__(self): return chain(['-vcodec', self.name], Codec.__iter__(self)) class AudioCodec(Codec): + def frames(self, number): + self.add_option('-aframes', str(number)) + return self + + def frequence(self, freq): + self.add_option('-ar', str(freq)) + return self + + def bitrate(self, rate): + self.add_option('-ab', str(rate)) + return self + + def quality(self, number): + self.add_option('-aq', str(number)) + return self + + def channels(self, number): + self.add_option('-ac', str(number)) + return self + + def language(self, lang): + self.add_option('-alang', str(lang)) + return self + + def preset(self, preset): + self.add_option('-apre', str(preset)) + return self + def __iter__(self): return chain(['-acodec', self.name], Codec.__iter__(self)) + + diff --git a/ffmpegwrapper/ffmpeg.py b/ffmpegwrapper/ffmpeg.py index fc54cb7..f45b890 100644 --- a/ffmpegwrapper/ffmpeg.py +++ b/ffmpegwrapper/ffmpeg.py @@ -8,14 +8,19 @@ :license: BSD, see LICENSE for more details. """ -from subprocess import Popen, PIPE + +import os + +from fcntl import fcntl, F_SETFL, F_GETFL +from select import select +from subprocess import Popen, PIPE, STDOUT from itertools import chain from .options import CombinedOptions, Options - + class Input(CombinedOptions): - + def __init__(self, file, *args): self.file = file CombinedOptions.__init__(self, *args) @@ -25,11 +30,15 @@ class Input(CombinedOptions): class Output(CombinedOptions): - + def __init__(self, file, *args): self.file = file CombinedOptions.__init__(self, *args) + def overwrite(self): + self.add_option('-y', None) + return self + def __iter__(self): return chain(CombinedOptions.__iter__(self), [self.file]) @@ -41,8 +50,17 @@ class FFmpeg(CombinedOptions): CombinedOptions.__init__(self, *args) def run(self): - return Popen(self, executable=self.binary, stdin=PIPE, - stdout=PIPE, stderr=PIPE) + self.pipe = Popen(self, executable=self.binary, + stderr=PIPE) + fcntl(self.pipe.stderr.fileno(), F_SETFL, + fcntl(self.pipe.stderr.fileno(), F_GETFL) | os.O_NONBLOCK) + return self + + def wait_for_data(self): + while True: + ready = select([self.pipe.stderr.fileno()], [], [])[0] + if ready: + return True def add_option(self, key, value): self._list.insert(0, Options({key: value})) diff --git a/ffmpegwrapper/filter.py b/ffmpegwrapper/filter.py index db9fc7d..6d56fd8 100644 --- a/ffmpegwrapper/filter.py +++ b/ffmpegwrapper/filter.py @@ -13,7 +13,7 @@ class CombinedFilter(CombinedOptions): def __iter__(self): for key, value in CombinedOptions.iteritems(self): if value is not None: - yield "=".join([key, str(value)]) + yield "=".join([key, str(value)]) else: yield key @@ -160,7 +160,7 @@ class VideoFilter(CombinedFilter): filter = self._format_parameter(*args) self.add_option('unsharp', filter) return self - + def vflip(self): self.add_option('vflip', None) return self diff --git a/ffmpegwrapper/options.py b/ffmpegwrapper/options.py index 883b271..232f526 100644 --- a/ffmpegwrapper/options.py +++ b/ffmpegwrapper/options.py @@ -2,7 +2,7 @@ class Options(dict): - + def __init__(self, *args, **kwargs): dict.__init__(self, *args, **kwargs) @@ -35,7 +35,7 @@ class CombinedOptions(object): def append(self, item): self._list.append(item) - + def insert(self, item): self._list.insert(item) @@ -67,7 +67,7 @@ class CombinedOptions(object): for option in self._list: for item in option.iteritems(): yield item - + def _format_parameter(self, *args): parameter = filter(lambda x: x is not None, args) return ':'.join(map(str, parameter)) diff --git a/setup.py b/setup.py old mode 100644 new mode 100755 index e69de29..f201970 --- a/setup.py +++ b/setup.py @@ -0,0 +1,14 @@ +#!/usr/bin/env python +# -*- coding: utf8 -*- + +try: + from setuptools import setup +except ImportError: + from distutils.core import setup + + +setup( + name="ffmpegwrapper", + version="0.1-dev", + packages=['ffmpegwrapper'] +) diff --git a/test.py b/test.py index 9c66ecf..23ad3e8 100644 --- a/test.py +++ b/test.py @@ -2,9 +2,8 @@ import unittest -from ffmpegwrapper import FFmpeg, Input, Output -from ffmpegwrapper.codec import VideoCodec, AudioCodec -from ffmpegwrapper.filter import VideoFilter +from ffmpegwrapper import FFmpeg, Input, Output, \ + VideoCodec, AudioCodec, VideoFilter from ffmpegwrapper.options import Options @@ -46,12 +45,11 @@ class FFmpegTestCase(unittest.TestCase): def test_filter_interface(self): filter = VideoFilter() filter.blackframe(1, 2).crop(792) - self.assertEqual(list(filter), ['-vf', - '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']) + self.assertEqual( + list(output), ['-vf', 'blackframe=1:2,crop=792', '/new']) def test_ffmpeg_interface(self): input = Input('/old') @@ -210,5 +208,113 @@ class VideoFilterTestCase(unittest.TestCase): self.prefix('yadif=0:-1')) +class VideoCodecTestCase(unittest.TestCase): + + def setUp(self): + self.codec = VideoCodec('libx264') + + def prefix(self, *args): + return ['-vcodec', 'libx264'] + list(args) + + def test_bitrate(self): + self.codec.bitrate('300k') + self.assertEqual(list(self.codec), + self.prefix('-b', '300k')) + + def test_frames(self): + self.codec.frames(100) + self.assertEqual(list(self.codec), + self.prefix('-vframes', '100')) + + def test_fps(self): + self.codec.fps(24) + self.assertEqual(list(self.codec), + self.prefix('-r', '24')) + + def test_aspect(self): + self.codec.aspect(16, 9) + self.assertEqual(list(self.codec), + self.prefix('-aspect', '16:9')) + + def test_bitrate_tolerance(self): + self.codec.bitrate_tolerance(10) + self.assertEqual(list(self.codec), + self.prefix('-bt', '10')) + + def test_max_bitrate(self): + self.codec.max_bitrate('100k') + self.assertEqual(list(self.codec), + self.prefix('-maxrate', '100k')) + + def test_min_bitrate(self): + self.codec.min_bitrate('50k') + self.assertEqual(list(self.codec), + self.prefix('-minrate', '50k')) + + def test_buffer_size(self): + self.codec.buffer_size('20k') + self.assertEqual(list(self.codec), + self.prefix('-bufsize', '20k')) + + def test_pass_number(self): + self.codec.pass_number(2) + self.assertEqual(list(self.codec), + self.prefix('-pass', '2')) + + def test_language(self): + self.codec.language('DEU') + self.assertEqual(list(self.codec), + self.prefix('-vlang', 'DEU')) + + def test_same_quality(self): + self.codec.same_quality() + self.assertEqual(list(self.codec), + self.prefix('-sameq')) + + def test_preset(self): + self.codec.preset('max') + self.assertEqual(list(self.codec), + self.prefix('-vpre', 'max')) + + +class AudioTestCase(unittest.TestCase): + + def setUp(self): + self.codec = AudioCodec('AC3') + + def prefix(self, *args): + return ['-acodec', 'AC3'] + list(args) + + def test_frames(self): + self.codec.frames(100) + self.assertEqual(list(self.codec), + self.prefix('-aframes', '100')) + + def test_frequence(self): + self.codec.frequence(48000) + self.assertEqual(list(self.codec), + self.prefix('-ar', '48000')) + + def test_bitrate(self): + self.codec.bitrate('320k') + self.assertEqual(list(self.codec), + self.prefix('-ab', '320k')) + + def test_quality(self): + self.codec.quality(8) + self.assertEqual(list(self.codec), + self.prefix('-aq', '8')) + + def test_language(self): + self.codec.language('DEU') + self.assertEqual(list(self.codec), + self.prefix('-alang', 'DEU')) + + def test_preset(self): + self.codec.preset('max') + self.assertEqual(list(self.codec), + self.prefix('-apre', 'max')) + + if __name__ == '__main__': unittest.main()