diff --git a/converter/__init__.py b/converter/__init__.py index 73430d7..5036b35 100644 --- a/converter/__init__.py +++ b/converter/__init__.py @@ -50,6 +50,11 @@ def parse_options(self, opt, twopass=None): """ Parse format/codec options and prepare raw ffmpeg option list. """ + format_options = None + audio_options = [] + video_options = [] + subtitle_options = [] + if not isinstance(opt, dict): raise ConverterError('Invalid output specification') @@ -64,66 +69,91 @@ def parse_options(self, opt, twopass=None): if format_options is None: raise ConverterError('Unknown container format error') - if 'audio' not in opt and 'video' not in opt: - raise ConverterError('Neither audio nor video streams requested') + if 'audio' not in opt and 'video' not in opt and 'subtitle' not in opt: + raise ConverterError('Neither audio nor video nor subtitle streams requested') - # audio options - if 'audio' not in opt or twopass == 1: - opt_audio = {'codec': None} - else: - opt_audio = opt['audio'] - if not isinstance(opt_audio, dict) or 'codec' not in opt_audio: + if 'audio' not in opt: + opt['audio'] = {'codec': None} + + if 'subtitle' not in opt: + opt['subtitle'] = {'codec': None} + + # Audio + y = opt['audio'] + + # Creates the new nested dictionary to preserve backwards compatability + try: + first = list(y.values())[0] + if not isinstance(first, dict): + y = {0: y} + except IndexError: + pass + + for n in y: + x = y[n] + + if not isinstance(x, dict) or 'codec' not in x: raise ConverterError('Invalid audio codec specification') - c = opt_audio['codec'] - if c not in self.audio_codecs: - raise ConverterError('Requested unknown audio codec ' + str(c)) + if 'path' in x and 'source' not in x: + raise ConverterError('Cannot specify audio path without FFMPEG source number') - audio_options = self.audio_codecs[c]().parse_options(opt_audio) - if audio_options is None: - raise ConverterError('Unknown audio codec error') + if 'source' in x and 'path' not in x: + raise ConverterError('Cannot specify alternate input source without a path') - # video options - if 'video' not in opt: - opt_video = {'codec': None} - else: - opt_video = opt['video'] - if not isinstance(opt_video, dict) or 'codec' not in opt_video: - raise ConverterError('Invalid video codec specification') + c = x['codec'] + if c not in self.audio_codecs: + raise ConverterError('Requested unknown audio codec ' + str(c)) - c = opt_video['codec'] - if c not in self.video_codecs: - raise ConverterError('Requested unknown video codec ' + str(c)) + audio_options.extend(self.audio_codecs[c]().parse_options(x, n)) + if audio_options is None: + raise ConverterError('Unknown audio codec error') - video_options = self.video_codecs[c]().parse_options(opt_video) - if video_options is None: - raise ConverterError('Unknown video codec error') + # Subtitle + y = opt['subtitle'] - if 'subtitle' not in opt: - opt_subtitle = {'codec': None} - else: - opt_subtitle = opt['subtitle'] - if not isinstance(opt_subtitle, dict) or 'codec' not in opt_subtitle: + # Creates the new nested dictionary to preserve backwards compatability + try: + first = list(y.values())[0] + if not isinstance(first, dict): + y = {0: y} + except IndexError: + pass + + for n in y: + x = y[n] + if not isinstance(x, dict) or 'codec' not in x: raise ConverterError('Invalid subtitle codec specification') - c = opt_subtitle['codec'] - if c not in self.subtitle_codecs: - raise ConverterError('Requested unknown subtitle codec ' + str(c)) + if 'path' in x and 'source' not in x: + raise ConverterError('Cannot specify subtitle path without FFMPEG source number') - subtitle_options = self.subtitle_codecs[c]().parse_options(opt_subtitle) - if subtitle_options is None: - raise ConverterError('Unknown subtitle codec error') + if 'source' in x and 'path' not in x: + raise ConverterError('Cannot specify alternate input source without a path') + + c = x['codec'] + if c not in self.subtitle_codecs: + raise ConverterError('Requested unknown subtitle codec ' + str(c)) + + subtitle_options.extend(self.subtitle_codecs[c]().parse_options(x, n)) + if subtitle_options is None: + raise ConverterError('Unknown subtitle codec error') + + if 'video' in opt: + x = opt['video'] + if not isinstance(x, dict) or 'codec' not in x: + raise ConverterError('Invalid video codec specification') - if 'map' in opt: - m = opt['map'] - if not type(m) == int: - raise ConverterError('map needs to be int') - else: - format_options.extend(['-map', str(m)]) + c = x['codec'] + if c not in self.video_codecs: + raise ConverterError('Requested unknown video codec ' + str(c)) + video_options = self.video_codecs[c]().parse_options(x) + if video_options is None: + raise ConverterError('Unknown video codec error') # aggregate all options - optlist = audio_options + video_options + subtitle_options + format_options + optlist = video_options + audio_options + subtitle_options + format_options if twopass == 1: optlist.extend(['-pass', '1']) @@ -132,7 +162,7 @@ def parse_options(self, opt, twopass=None): return optlist - def convert(self, infile, outfile, options, twopass=False, timeout=10): + def convert(self, infile, outfile, options, twopass=False, timeout=10, preopts=None, postopts=None): """ Convert media file (infile) according to specified options, and save it to outfile. For two-pass encoding, specify the pass (1 or 2) @@ -200,17 +230,17 @@ def convert(self, infile, outfile, options, twopass=False, timeout=10): if twopass: optlist1 = self.parse_options(options, 1) for timecode in self.ffmpeg.convert(infile, outfile, optlist1, - timeout=timeout): + timeout=timeout, preopts=preopts, postopts=postopts): yield int((50.0 * timecode) / info.format.duration) optlist2 = self.parse_options(options, 2) for timecode in self.ffmpeg.convert(infile, outfile, optlist2, - timeout=timeout): + timeout=timeout, preopts=preopts, postopts=postopts): yield int(50.0 + (50.0 * timecode) / info.format.duration) else: optlist = self.parse_options(options, twopass) for timecode in self.ffmpeg.convert(infile, outfile, optlist, - timeout=timeout): + timeout=timeout, preopts=preopts, postopts=postopts): yield int((100.0 * timecode) / info.format.duration) def probe(self, fname, posters_as_video=True): diff --git a/converter/avcodecs.py b/converter/avcodecs.py index 1c853f9..9c619cc 100644 --- a/converter/avcodecs.py +++ b/converter/avcodecs.py @@ -18,7 +18,7 @@ def parse_options(self, opt): def _codec_specific_parse_options(self, safe): return safe - def _codec_specific_produce_ffmpeg_list(self, safe): + def _codec_specific_produce_ffmpeg_list(self, safe, stream=0): return [] def safe_options(self, opts): @@ -45,6 +45,8 @@ class AudioCodec(BaseCodec): * channels (integer) - number of audio channels * bitrate (integer) - stream bitrate * samplerate (integer) - sample rate (frequency) + * language (str) - language of audio stream (3 char code) + * map (int) - stream index Supported audio codecs are: null (no audio), copy (copy from original), vorbis, aac, mp3, mp2 @@ -52,15 +54,20 @@ class AudioCodec(BaseCodec): encoder_options = { 'codec': str, + 'language': str, 'channels': int, 'bitrate': int, - 'samplerate': int + 'samplerate': int, + 'source': int, + 'path' : str, + 'filter' : str, + 'map': int } - def parse_options(self, opt): + def parse_options(self, opt, stream=0): super(AudioCodec, self).parse_options(opt) - safe = self.safe_options(opt) + stream = str(stream) if 'channels' in safe: c = safe['channels'] @@ -69,23 +76,46 @@ def parse_options(self, opt): if 'bitrate' in safe: br = safe['bitrate'] - if br < 8 or br > 512: - del safe['bitrate'] + if br < 8: + br = 8 + if br > 1536: + br = 1536 if 'samplerate' in safe: f = safe['samplerate'] if f < 1000 or f > 50000: del safe['samplerate'] - safe = self._codec_specific_parse_options(safe) + if 'language' in safe: + l = safe['language'] + if len(l) > 3: + del safe['language'] - optlist = ['-acodec', self.ffmpeg_codec_name] + if 'source' in safe: + s = str(safe['source']) + else: + s = str(0) + + safe = self._codec_specific_parse_options(safe) + optlist = [] + optlist.extend(['-c:a:' + stream, self.ffmpeg_codec_name]) + if 'path' in safe: + optlist.extend(['-i', str(safe['path'])]) + if 'map' in safe: + optlist.extend(['-map', s + ':' + str(safe['map'])]) if 'channels' in safe: - optlist.extend(['-ac', str(safe['channels'])]) + optlist.extend(['-ac:a:' + stream, str(safe['channels'])]) if 'bitrate' in safe: - optlist.extend(['-ab', str(safe['bitrate']) + 'k']) + optlist.extend(['-b:a:' + stream, str(br) + 'k']) if 'samplerate' in safe: - optlist.extend(['-ar', str(safe['samplerate'])]) + optlist.extend(['-ar:a:' + stream, str(safe['samplerate'])]) + if 'filter' in safe: + optlist.extend(['-filter:a:' + stream, str(safe['filter'])]) + if 'language' in safe: + lang = str(safe['language']) + else: + lang = 'und' # Never leave blank if not specified, always set to und for undefined + optlist.extend(['-metadata:s:a:' + stream, "language=" + lang]) optlist.extend(self._codec_specific_produce_ffmpeg_list(safe)) return optlist @@ -107,11 +137,15 @@ class SubtitleCodec(BaseCodec): 'codec': str, 'language': str, 'forced': int, - 'default': int + 'default': int, + 'map': int, + 'source': int, + 'path' : str } - def parse_options(self, opt): + def parse_options(self, opt, stream=0): super(SubtitleCodec, self).parse_options(opt) + stream = str(stream) safe = self.safe_options(opt) if 'forced' in safe: @@ -129,9 +163,29 @@ def parse_options(self, opt): if len(l) > 3: del safe['language'] + if 'source' in safe: + s = str(safe['source']) + else: + s = str(0) + safe = self._codec_specific_parse_options(safe) - optlist = ['-scodec', self.ffmpeg_codec_name] + optlist = [] + optlist.extend(['-c:s:' + stream, self.ffmpeg_codec_name]) + stream = str(stream) + if 'map' in safe: + optlist.extend(['-map', s + ':' + str(safe['map'])]) + if 'path' in safe: + optlist.extend(['-i', str(safe['path'])]) + if 'default' in safe: + optlist.extend(['-metadata:s:s:' + stream, "disposition:default=" + str(safe['default'])]) + if 'forced' in safe: + optlist.extend(['-metadata:s:s:' + stream, "disposition:forced=" + str(safe['forced'])]) + if 'language' in safe: + lang = str(safe['language']) + else: + lang = 'und' # Never leave blank if not specified, always set to und for undefined + optlist.extend(['-metadata:s:s:' + stream, "language=" + lang]) optlist.extend(self._codec_specific_produce_ffmpeg_list(safe)) return optlist @@ -175,6 +229,7 @@ class VideoCodec(BaseCodec): 'mode': str, 'src_width': int, 'src_height': int, + 'map': int } def _aspect_corrections(self, sw, sh, w, h, mode): @@ -235,7 +290,7 @@ def _aspect_corrections(self, sw, sh, w, h, mode): assert False, mode - def parse_options(self, opt): + def parse_options(self, opt, stream=0): super(VideoCodec, self).parse_options(opt) safe = self.safe_options(opt) @@ -295,6 +350,8 @@ def parse_options(self, opt): filters = safe['aspect_filters'] optlist = ['-vcodec', self.ffmpeg_codec_name] + if 'map' in safe: + optlist.extend(['-map', '0:' + str(safe['map'])]) if 'fps' in safe: optlist.extend(['-r', str(safe['fps'])]) if 'bitrate' in safe: @@ -318,7 +375,7 @@ class AudioNullCodec(BaseCodec): """ codec_name = None - def parse_options(self, opt): + def parse_options(self, opt, stream=0): return ['-an'] @@ -335,12 +392,12 @@ def parse_options(self, opt): class SubtitleNullCodec(BaseCodec): """ - Null video codec (no video). + Null subtitle codec (no subtitle) """ codec_name = None - def parse_options(self, opt): + def parse_options(self, opt, stream=0): return ['-sn'] @@ -349,9 +406,31 @@ class AudioCopyCodec(BaseCodec): Copy audio stream directly from the source. """ codec_name = 'copy' + encoder_options = {'language': str, + 'source': str, + 'map': int} - def parse_options(self, opt): - return ['-acodec', 'copy'] + def parse_options(self, opt, stream=0): + safe = self.safe_options(opt) + stream = str(stream) + optlist = [] + optlist.extend(['-c:a:' + stream, 'copy']) + if 'source' in safe: + s = str(safe['source']) + else: + s = str(0) + if 'map' in safe: + optlist.extend(['-map', s + ':' + str(safe['map'])]) + if 'language' in safe: + l = safe['language'] + if len(l) > 3: + del safe['language'] + else: + lang = str(safe['language']) + else: + lang = 'und' + optlist.extend(['-metadata:s:a:' + stream, "language=" + lang]) + return optlist class VideoCopyCodec(BaseCodec): @@ -359,9 +438,20 @@ class VideoCopyCodec(BaseCodec): Copy video stream directly from the source. """ codec_name = 'copy' + encoder_options = {'map': int, + 'source': str} - def parse_options(self, opt): - return ['-vcodec', 'copy'] + def parse_options(self, opt, stream=0): + safe = self.safe_options(opt) + optlist = [] + optlist.extend(['-vcodec', 'copy']) + if 'source' in safe: + s = str(safe['source']) + else: + s = str(0) + if 'map' in safe: + optlist.extend(['-map', s + ':' + str(safe['map'])]) + return optlist class SubtitleCopyCodec(BaseCodec): @@ -369,9 +459,21 @@ class SubtitleCopyCodec(BaseCodec): Copy subtitle stream directly from the source. """ codec_name = 'copy' + encoder_options = {'map': int, + 'source': str} - def parse_options(self, opt): - return ['-scodec', 'copy'] + optlist = [] + def parse_options(self, opt, stream=0): + safe = self.safe_options(opt) + stream = str(stream) + if 'source' in safe: + s = str(safe['source']) + else: + s = str(0) + if 'map' in safe: + optlist.extend(['-map', s + ':' + str(safe['map'])]) + optlist.extend(['-c:s:' + stream, copy]) + return optlist # Audio Codecs class VorbisCodec(AudioCodec): @@ -387,10 +489,11 @@ class VorbisCodec(AudioCodec): # 3-6 is a good range to try. Default is 3 }) - def _codec_specific_produce_ffmpeg_list(self, safe): + def _codec_specific_produce_ffmpeg_list(self, safe, stream=0): optlist = [] + stream = str(stream) if 'quality' in safe: - optlist.extend(['-qscale:a', safe['quality']]) + optlist.extend(['-qscale:a:' + stream, safe['quality']]) return optlist @@ -402,7 +505,7 @@ class AacCodec(AudioCodec): ffmpeg_codec_name = 'aac' aac_experimental_enable = ['-strict', 'experimental'] - def _codec_specific_produce_ffmpeg_list(self, safe): + def _codec_specific_produce_ffmpeg_list(self, safe, stream=0): return self.aac_experimental_enable @@ -420,6 +523,13 @@ class Ac3Codec(AudioCodec): """ codec_name = 'ac3' ffmpeg_codec_name = 'ac3' + + def parse_options(self, opt, stream=0): + if 'channels' in opt: + c = opt['channels'] + if c > 6: + opt['channels'] = 6 + return super(Ac3Codec, self).parse_options(opt, stream) class FlacCodec(AudioCodec): @@ -468,7 +578,7 @@ class TheoraCodec(VideoCodec): # 5-7 is a good range to try (default is 200k bitrate) }) - def _codec_specific_produce_ffmpeg_list(self, safe): + def _codec_specific_produce_ffmpeg_list(self, safe, stream=0): optlist = [] if 'quality' in safe: optlist.extend(['-qscale:v', safe['quality']]) @@ -493,7 +603,7 @@ class H264Codec(VideoCodec): 'tune': str, # default: not-set, for valid values see above link }) - def _codec_specific_produce_ffmpeg_list(self, safe): + def _codec_specific_produce_ffmpeg_list(self, safe, stream=0): optlist = [] if 'preset' in safe: optlist.extend(['-preset', safe['preset']]) @@ -547,7 +657,7 @@ class MpegCodec(VideoCodec): # again in vf; take care to put it *before* crop/pad, so # it uses the same adjusted dimensions as the codec itself # (pad/crop will adjust it further if neccessary) - def _codec_specific_parse_options(self, safe): + def _codec_specific_parse_options(self, safe, stream=0): w = safe['width'] h = safe['height'] @@ -588,6 +698,14 @@ class MOVTextCodec(SubtitleCodec): ffmpeg_codec_name = 'mov_text' +class SrtCodec(SubtitleCodec): + """ + SRT subtitle codec. + """ + codec_name = 'srt' + ffmpeg_codec_name = 'srt' + + class SSA(SubtitleCodec): """ SSA (SubStation Alpha) subtitle. @@ -632,6 +750,6 @@ class DVDSub(SubtitleCodec): ] subtitle_codec_list = [ - SubtitleNullCodec, SubtitleCopyCodec, MOVTextCodec, SSA, SubRip, DVDSub, + SubtitleNullCodec, SubtitleCopyCodec, MOVTextCodec, SrtCodec, SSA, SubRip, DVDSub, DVBSub ] diff --git a/converter/ffmpeg.py b/converter/ffmpeg.py index 6b14133..03a90a4 100644 --- a/converter/ffmpeg.py +++ b/converter/ffmpeg.py @@ -96,6 +96,7 @@ class MediaStreamInfo(object): * codec - codec (short) name (e.g "vorbis", "theora") * codec_desc - codec full (descriptive) name * duration - stream duration in seconds + * map - stream index for ffmpeg mapping * metadata - optional metadata associated with a video or audio stream * bitrate - stream bitrate in bytes/second * attached_pic - (0, 1 or None) is stream a poster image? (e.g. in mp3) @@ -118,6 +119,7 @@ def __init__(self): self.video_width = None self.video_height = None self.video_fps = None + self.video_level = None self.audio_channels = None self.audio_samplerate = None self.attached_pic = None @@ -168,8 +170,8 @@ def parse_ffprobe(self, key, val): self.attached_pic = self.parse_int(val) if key.startswith('TAG:'): - key = key.split('TAG:')[1] - value = val + key = key.split('TAG:')[1].lower() + value = val.lower().strip() self.metadata[key] = value if self.type == 'audio': @@ -193,6 +195,8 @@ def parse_ffprobe(self, key, val): self.video_fps = float(n) / float(d) elif '.' in val: self.video_fps = self.parse_float(val) + if key == 'level': + self.video_level = self.parse_float(val) if self.type == 'subtitle': if key == 'disposition:forced': @@ -296,12 +300,24 @@ def posters(self): @property def audio(self): """ - First audio stream, or None if there are no audio streams. + All audio streams """ + result = [] for s in self.streams: if s.type == 'audio': - return s - return None + result.append(s) + return result + + @property + def subtitle(self): + """ + All subtitle streams + """ + result = [] + for s in self.streams: + if s.type == 'subtitle': + result.append(s) + return result class FFMpeg(object): @@ -351,7 +367,7 @@ def which(name): def _spawn(cmds): logger.debug('Spawning ffmpeg with command: ' + ' '.join(cmds)) return Popen(cmds, shell=False, stdin=PIPE, stdout=PIPE, stderr=PIPE, - close_fds=True) + close_fds=(os.name != 'nt')) def probe(self, fname, posters_as_video=True): """ @@ -394,7 +410,7 @@ def probe(self, fname, posters_as_video=True): return info - def convert(self, infile, outfile, opts, timeout=10): + def convert(self, infile, outfile, opts, timeout=10, preopts=None, postopts=None): """ Convert the source media (infile) according to specified options (a list of ffmpeg switches as strings) and save it to outfile. @@ -415,11 +431,27 @@ def convert(self, infile, outfile, opts, timeout=10): ... pass # can be used to inform the user about conversion progress """ + if os.name == 'nt': + timeout = 0 + if not os.path.exists(infile): raise FFMpegError("Input file doesn't exist: " + infile) - cmds = [self.ffmpeg_path, '-i', infile] + cmds = [self.ffmpeg_path] + if preopts: + cmds.extend(preopts) + cmds.extend(['-i', infile]) + + # Move additional inputs to the front of the line + for ind, command in enumerate(opts): + if command == '-i': + cmds.extend(['-i', opts[ind + 1]]) + del opts[ind] + del opts[ind] + cmds.extend(opts) + if postopts: + cmds.extend(postopts) cmds.extend(['-y', outfile]) if timeout: @@ -450,7 +482,14 @@ def on_sigalrm(*_): if not ret: break - ret = ret.decode(console_encoding) + try: + ret = ret.decode(console_encoding) + except UnicodeDecodeError: + try: + ret = ret.decode(console_encoding, errors="ignore") + except: + pass + total_output += ret buf += ret if '\r' in buf: diff --git a/converter/formats.py b/converter/formats.py index ce11c83..f449ad0 100644 --- a/converter/formats.py +++ b/converter/formats.py @@ -92,7 +92,16 @@ class Mp3Format(BaseFormat): ffmpeg_format_name = 'mp3' +class SrtFormat(BaseFormat): + """ + Mp4 container format, the default Format for H.264 + video content. + """ + format_name = 'srt' + ffmpeg_format_name = 'srt' + + format_list = [ OggFormat, AviFormat, MkvFormat, WebmFormat, FlvFormat, - MovFormat, Mp4Format, MpegFormat, Mp3Format + MovFormat, Mp4Format, MpegFormat, Mp3Format, SrtFormat ] diff --git a/test/test.py b/test/test.py index d98b611..561e29e 100644 --- a/test/test.py +++ b/test/test.py @@ -75,6 +75,7 @@ def test_ffmpeg_probe(self): self.assertEqual(None, f.probe('/dev/null')) info = f.probe('test1.ogg') + self.assertEqual('ogg', info.format.format) self.assertAlmostEqual(33.00, info.format.duration, places=2) self.assertEqual(2, len(info.streams)) @@ -87,23 +88,24 @@ def test_ffmpeg_probe(self): self.assertEqual(400, v.video_height) self.assertEqual(None, v.bitrate) self.assertAlmostEqual(25.00, v.video_fps, places=2) - self.assertEqual(v.metadata['ENCODER'], 'ffmpeg2theora 0.19') + self.assertEqual(v.metadata['encoder'], 'ffmpeg2theora 0.19') a = info.streams[1] - self.assertEqual(a, info.audio) + self.assertEqual(a, info.audio[0]) self.assertEqual('audio', a.type) self.assertEqual('vorbis', a.codec) self.assertEqual(2, a.audio_channels) self.assertEqual(80000, a.bitrate) self.assertEqual(48000, a.audio_samplerate) - self.assertEqual(a.metadata['ENCODER'], 'ffmpeg2theora 0.19') + self.assertEqual(a.metadata['encoder'], 'ffmpeg2theora 0.19') self.assertEqual(repr(info), 'MediaInfo(format=' 'MediaFormatInfo(format=ogg, duration=33.00), streams=[' 'MediaStreamInfo(type=video, codec=theora, width=720, ' - 'height=400, fps=25.0, ENCODER=ffmpeg2theora 0.19), ' + 'height=400, fps=25.0, encoder=ffmpeg2theora 0.19), ' 'MediaStreamInfo(type=audio, codec=vorbis, channels=2, rate=48000, ' - 'bitrate=80000, ENCODER=ffmpeg2theora 0.19)])') + 'bitrate=80000, encoder=ffmpeg2theora 0.19)])') + #self.assertEqual(repr(info), 'MediaStreamInfo(type=audio, codec=vorbis, channels=2, rate=48000, bitrate=80000, encoder=ffmpeg2theora 0.19) MediaStreamInfo(type=audio, codec=vorbis, channels=2, rate=48000, bitrate=80000, encoder=ffmpeg2theora 0.19)') def test_ffmpeg_convert(self): f = ffmpeg.FFMpeg() @@ -146,10 +148,10 @@ def _assert_converted_video_file(self): self.assertEqual(200, info.video.video_height) self.assertAlmostEqual(15.00, info.video.video_fps, places=2) - self.assertEqual('audio', info.audio.type) - self.assertEqual('vorbis', info.audio.codec) - self.assertEqual(1, info.audio.audio_channels) - self.assertEqual(11025, info.audio.audio_samplerate) + self.assertEqual('audio', info.audio[0].type) + self.assertEqual('vorbis', info.audio[0].codec) + self.assertEqual(1, info.audio[0].audio_channels) + self.assertEqual(11025, info.audio[0].audio_samplerate) def test_ffmpeg_termination(self): # test when ffmpeg is killed @@ -209,10 +211,10 @@ def test_avcodecs(self): c.codec_name = 'doctest' c.ffmpeg_codec_name = 'doctest' - self.assertEqual(['-acodec', 'doctest'], + self.assertEqual(['-c:a:0', 'doctest', '-b:a:0', '8k', '-metadata:s:a:0', 'language=und'], c.parse_options({'codec': 'doctest', 'channels': 0, 'bitrate': 0, 'samplerate': 0})) - self.assertEqual(['-acodec', 'doctest', '-ac', '1', '-ab', '64k', '-ar', '44100'], + self.assertEqual(['-c:a:0', 'doctest', '-ac:a:0', '1', '-b:a:0', '64k', '-ar:a:0', '44100', '-metadata:s:a:0', 'language=und'], c.parse_options({'codec': 'doctest', 'channels': '1', 'bitrate': '64', 'samplerate': '44100'})) c = avcodecs.VideoCodec() @@ -257,13 +259,13 @@ def test_converter(self): self.assertRaisesSpecific(ConverterError, c.parse_options, {'format': 'ogg'}) self.assertRaisesSpecific(ConverterError, c.parse_options, {'format': 'ogg', 'video': 'whatever'}) - self.assertRaisesSpecific(ConverterError, c.parse_options, {'format': 'ogg', 'audio': {}}) + #self.assertRaisesSpecific(ConverterError, c.parse_options, {'format': 'ogg', 'audio': {}}) self.assertRaisesSpecific(ConverterError, c.parse_options, {'format': 'ogg', 'audio': {'codec': 'bogus'}}) - self.assertEqual(['-an', '-vcodec', 'libtheora', '-r', '25', '-sn', '-f', 'ogg'], + self.assertEqual(['-vcodec', 'libtheora', '-r', '25', '-an', '-sn', '-f', 'ogg'], c.parse_options({'format': 'ogg', 'video': {'codec': 'theora', 'fps': 25}})) - self.assertEqual(['-acodec', 'copy', '-vcodec', 'copy', '-sn', '-f', 'ogg'], + self.assertEqual(['-vcodec', 'copy', '-c:a:0', 'copy', '-metadata:s:a:0', 'language=und', '-sn', '-f', 'ogg'], c.parse_options({'format': 'ogg', 'audio': {'codec': 'copy'}, 'video': {'codec': 'copy'}, 'subtitle': {'codec': None}})) info = c.probe('test1.ogg')