|
1 # |
|
2 # Copyright (c) 2008-2015 Thierry Florac <tflorac AT ulthar.net> |
|
3 # All Rights Reserved. |
|
4 # |
|
5 # This software is subject to the provisions of the Zope Public License, |
|
6 # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. |
|
7 # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED |
|
8 # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED |
|
9 # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS |
|
10 # FOR A PARTICULAR PURPOSE. |
|
11 # |
|
12 |
|
13 # import standard library |
|
14 import json |
|
15 import logging |
|
16 logger = logging.getLogger('PyAMS (media)') |
|
17 |
|
18 import mimetypes |
|
19 import re |
|
20 import tempfile |
|
21 |
|
22 from os.path import dirname, basename |
|
23 from os import sep, remove |
|
24 from subprocess import Popen, PIPE |
|
25 |
|
26 # import interfaces |
|
27 from pyams_file.interfaces import IFile |
|
28 |
|
29 # import packages |
|
30 from pyams_file.file import get_magic_content_type |
|
31 from pyams_media.ffexception import FFException |
|
32 |
|
33 |
|
34 __all__ = ['FFmpeg', 'FFVideoEffect', 'FFAudioEffect'] |
|
35 |
|
36 |
|
37 INPUT_BLOCK_SIZE = 1024 ** 2 |
|
38 |
|
39 |
|
40 class FFmpeg(object): |
|
41 """ |
|
42 FFmpeg Wrapper |
|
43 """ |
|
44 |
|
45 # thanks to pyxcoder http://code.google.com/p/pyxcoder for |
|
46 # the main idea |
|
47 re_mainline = re.compile("^\s*Input #(\d+?), (.*?), from \'(.*?)\':$") |
|
48 re_infoline = re.compile("^\s*Duration: (.*?), start: 0\.000000, bitrate: (\d+?) kb\/s$") |
|
49 re_videoline = re.compile("^\s*Stream #(\d+:\d+?)\(?([A-Za-z]*)\)?: Video: (.*?), (.*?), (.*?), (.*?)$") |
|
50 re_audioline = re.compile("^\s*Stream #(\d+:\d+?)\(?([A-Za-z]*)\)?: Audio: (.*?), (\d+?) Hz, (.*?), (.*?), (\d+?) kb\/s$") |
|
51 |
|
52 def __init__(self, cmd="ffmpeg"): |
|
53 self.__ffmpeg__ = cmd |
|
54 |
|
55 def __exec__(self, *args): |
|
56 """Build and execute a command line""" |
|
57 cmdline = [self.__ffmpeg__] |
|
58 if self.__ffmpeg__ == 'ffmpeg': |
|
59 cmdline.append('-y') |
|
60 use_stdin = None |
|
61 for arg in args: |
|
62 if IFile.providedBy(arg): |
|
63 if len(args) == 2: |
|
64 # FFmpeg can't get media info from an input pipe |
|
65 # We have to write media content to temporary file |
|
66 suffix = '.tmp' |
|
67 content_type = get_magic_content_type(arg.data) |
|
68 if content_type: |
|
69 suffix = mimetypes.guess_extension(content_type) or suffix |
|
70 output = tempfile.NamedTemporaryFile(prefix='media_', suffix=suffix) |
|
71 output.write(arg.data) |
|
72 output.file.flush() |
|
73 cmdline.append(output.name) |
|
74 else: |
|
75 use_stdin = arg |
|
76 cmdline.append('-') |
|
77 elif hasattr(arg, 'read'): # StringIO or any file like object |
|
78 if len(args) == 2: |
|
79 # FFmpeg can't get media info from an input pipe |
|
80 # We have to write media content to temporary file |
|
81 arg.reset() |
|
82 content_type = get_magic_content_type(arg.read(4096)) |
|
83 suffix = mimetypes.guess_extension(content_type) if content_type else '.tmp' |
|
84 output = tempfile.NamedTemporaryFile(prefix='media_', suffix=suffix) |
|
85 try: |
|
86 arg.reset() |
|
87 except: |
|
88 pass |
|
89 data = arg.read(INPUT_BLOCK_SIZE) |
|
90 while data: |
|
91 output.write(data) |
|
92 data = arg.read(INPUT_BLOCK_SIZE) |
|
93 output.file.flush() |
|
94 cmdline.append(output.name) |
|
95 else: |
|
96 use_stdin = arg |
|
97 cmdline.append('-') |
|
98 else: |
|
99 cmdline.append(arg) |
|
100 logger.debug("Running FFmpeg command line: {0}".format(cmdline)) |
|
101 p = Popen(cmdline, stdin=PIPE, stdout=PIPE, stderr=PIPE) |
|
102 if use_stdin is not None: |
|
103 if IFile.providedBy(use_stdin): |
|
104 return p.communicate(use_stdin.data) |
|
105 else: |
|
106 use_stdin.reset() |
|
107 return p.communicate(use_stdin.read()) |
|
108 else: |
|
109 return p.communicate() |
|
110 |
|
111 def render(self, effectchain, output): |
|
112 """Create a new file by chaining audio/video effects""" |
|
113 inputs = [] |
|
114 cmds = [[]] |
|
115 outputs = [] |
|
116 # we want to operate on more objects that use the same file |
|
117 # source, So, we have to split the effect chain in various |
|
118 # intermediate jobs, then rebuild all |
|
119 for index, effect in enumerate(effectchain): |
|
120 if index == 1 and not effect in inputs: |
|
121 inputs.append(effect) |
|
122 cmds[len(cmds)-1].append(effect) |
|
123 else: |
|
124 outputs.append("%s%s%s-%s" % (dirname(output), sep, len(cmds), basename(output))) |
|
125 cmds.append([]) |
|
126 input = [] |
|
127 # prcessing intermediate outputs |
|
128 for index, output in enumerate(outputs): |
|
129 cmd = ["-y", ] |
|
130 cmd.extend(inputs[index].cmdline()) |
|
131 cmd.append(output) |
|
132 self.__exec__(*cmd) |
|
133 # procesing final output |
|
134 cmd = ["-y", ] |
|
135 for index, output in enumerate(outputs): |
|
136 doc = FFEffect(output) |
|
137 if index == 0 and inputs[index].offset(): |
|
138 doc.offset(inputs[index].offset()) |
|
139 cmd.extend(doc.cmdline()) |
|
140 cmd.append(output) |
|
141 self.__exec__(*cmd) |
|
142 # removing intermediate outputs |
|
143 for tmp in outputs: |
|
144 remove(tmp) |
|
145 |
|
146 def info(self, input): |
|
147 """Retrieve file information parsing command output""" |
|
148 metadata = [] |
|
149 if IFile.providedBy(input) or isinstance(input, str) or hasattr(input, 'read'): |
|
150 input = [input, ] |
|
151 for i in range(0, len(input) * 2, 2): |
|
152 input.insert(i, "-i") |
|
153 if self.__ffmpeg__ == 'ffprobe': |
|
154 input.extend(['-show_streams', '-print_format', 'json']) |
|
155 probe = self.__exec__(*input)[0] # stdout |
|
156 metadata.extend(json.loads(probe.decode()).get('streams', [])) |
|
157 else: |
|
158 lines = self.__exec__(*input)[1] # stderr |
|
159 for line in lines.split(b'\n'): |
|
160 if isinstance(line, bytes): |
|
161 try: |
|
162 line = line.decode() |
|
163 except UnicodeDecodeError: |
|
164 logger.debug("Unicode decode error: {0}".format(line)) |
|
165 continue |
|
166 if FFmpeg.re_mainline.match(line): |
|
167 clip, vtype, filename = FFmpeg.re_mainline.match(line).groups() |
|
168 metadata.append({"vtype": vtype, "filename": filename, "video": [], "audio": []}) |
|
169 elif FFmpeg.re_infoline.match(line): |
|
170 current = len(metadata) - 1 |
|
171 metadata[current]["duration"], metadata[current]["bitrate"] = FFmpeg.re_infoline.match(line).groups() |
|
172 elif FFmpeg.re_audioline.match(line): |
|
173 clip, lang, codec, freq, chan, freqbit, bitrate = FFmpeg.re_audioline.match(line).groups() |
|
174 audiostream = {"codec": codec, "lang": lang, "freq": freq, "chan": chan, "freqbit": freqbit, "bitrate": bitrate} |
|
175 metadata[len(metadata) - 1]["audio"].append(audiostream) |
|
176 elif FFmpeg.re_videoline.match(line): |
|
177 clip, lang, codec, pix_fmt, size, framerate = FFmpeg.re_videoline.match(line).groups() |
|
178 size = size.split(" ") |
|
179 videostream = {"codec": codec, "lang": lang, "pix_fmt": pix_fmt, "size": size, "framerate": framerate} |
|
180 metadata[len(metadata) - 1]["video"].append(videostream) |
|
181 return metadata |
|
182 |
|
183 |
|
184 class FFEffect: |
|
185 """ |
|
186 effect for a specified input file |
|
187 each "set" method has an unset_* method |
|
188 to clear the effect of the former (e.g. |
|
189 crop() and unset_crop() ), and a general |
|
190 unset() method |
|
191 """ |
|
192 |
|
193 def __init__(self, inputfile, **args): |
|
194 self.__file__ = inputfile |
|
195 for opt in args.keys(): |
|
196 if opt not in ["b", "vframes", "r", "s", "aspect", "croptop", |
|
197 "cropbottom", "cropleft", "cropright", "padtop", |
|
198 "padbottom", "padleft", "padright", "padcolor", |
|
199 "vn", "bt", "maxrate", "minrate", "bufsize", |
|
200 "vcodec", "sameq", "pass", "newvideo", "pix_fmt", |
|
201 "sws_flag", "g", "intra", "vdt", "qscale", |
|
202 "qmin", "qmax", "qdiff", "qblur", "qcomp", "lmin", |
|
203 "lmax", "mblmin", "mblmax", "rc_init_cplx", |
|
204 "b_qfactor", "i_qfactor", "b_qoffset", |
|
205 "i_qoffset", "rc_eq", "rc_override", "me_method", |
|
206 "dct_algo", "idct_algo", "er", "ec", "bf", "mbd", |
|
207 "4mv", "part", "bug", "strict", "aic", "umv", |
|
208 "deinterlace", "ilme", "psnr", "vhook", "top", |
|
209 "dc", "vtag", "vbsf", "aframes", "ar", "ab", "ac", |
|
210 "an", "acodec", "newaudio", "alang", "t", |
|
211 "itsoffset", "ss", "dframes"]: |
|
212 raise FFException("Error parsing option: %s" % opt) |
|
213 self.__effects__ = args |
|
214 self.__default__ = self.__effects__.copy() |
|
215 |
|
216 def cmdline(self): |
|
217 """ return a list of arguments """ |
|
218 cmd = ["-i", self.__file__] |
|
219 for opt, value in self.__effects__.items(): |
|
220 cmd.append("-%s" % opt) |
|
221 if value is not True: |
|
222 cmd.append("%s" % value) |
|
223 return cmd |
|
224 |
|
225 def get_output(self, format=None, target='-', get_stderr=False): |
|
226 if (format is None) and hasattr(self, '__metadata__'): |
|
227 format = self.__metadata__.get('vtype') |
|
228 stdout, stderr = FFmpeg().__exec__(*self.cmdline() + ['-f', format, target]) |
|
229 return (stdout, stderr) if get_stderr else stdout |
|
230 |
|
231 def restore(self): |
|
232 """ |
|
233 restore initial settings |
|
234 """ |
|
235 self.__effects__ = self.__default__.copy() |
|
236 |
|
237 def unset(self): |
|
238 """ |
|
239 clear settings |
|
240 """ |
|
241 self.__effects__ = {} |
|
242 |
|
243 def duration(self, t=None): |
|
244 """ restrict transcode sequence to duration specified """ |
|
245 if t: |
|
246 self.__effects__["t"] = float(t) |
|
247 return self.__effects__.get("t") |
|
248 |
|
249 def unset_duration(self): |
|
250 del self.__effects__["duration"] |
|
251 |
|
252 def seek(self, ss=None): |
|
253 """ seek to time position in seconds """ |
|
254 if ss: |
|
255 self.__effects__["ss"] = float(ss) |
|
256 return self.__effects__.get("ss") |
|
257 |
|
258 def unset_seek(self): |
|
259 del self.__effects__["ss"] |
|
260 |
|
261 def offset(self, itsoffset=None): |
|
262 """ Set the input time offset in seconds """ |
|
263 if itsoffset: |
|
264 self.__effects__["itsoffset"] = itsoffset |
|
265 return self.__effects__.get("itsoffset") |
|
266 |
|
267 def unset_offset(self): |
|
268 del self.__effects__["itsoffset"] |
|
269 |
|
270 def dframes(self, dframes=None): |
|
271 """ number of data frames to record """ |
|
272 if dframes: |
|
273 self.__effects__["dframes"] = dframes |
|
274 return self.__effects__.get("dframes") |
|
275 |
|
276 def unset_dframes(self): |
|
277 del self.__effects__["dframes"] |
|
278 |
|
279 |
|
280 class FFVideoEffect(FFEffect): |
|
281 """ |
|
282 video effect |
|
283 """ |
|
284 |
|
285 def __init__(self, inputfile=None, **args): |
|
286 FFEffect.__init__(self, inputfile, **args) |
|
287 |
|
288 def bitrate(self, b=None): |
|
289 """ set video bitrate """ |
|
290 if b: |
|
291 self.__effects__["b"] = "%sk" % int(b) |
|
292 return self.__effects__.get("b") |
|
293 |
|
294 def unset_bitrate(self): |
|
295 del self.__effects__["b"] |
|
296 |
|
297 def vframes(self, vframes=None): |
|
298 """ set number of video frames to record """ |
|
299 if vframes: |
|
300 self.__effects__["vframes"] = int(vframes) |
|
301 return self.__effects__.get("vframes") |
|
302 |
|
303 def unset_vframes(self): |
|
304 del self.__effects__["vframes"] |
|
305 |
|
306 def rate(self, r=None): |
|
307 """ set frame rate """ |
|
308 if r: |
|
309 self.__effects__["r"] = int(r) |
|
310 return self.__effects__.get("r") |
|
311 |
|
312 def unset_rate(self): |
|
313 del self.__effects__["r"] |
|
314 |
|
315 def size(self, s=None): |
|
316 """ set frame size """ |
|
317 if s in ["sqcif", "qcif", "cif", "4cif", "qqvga", "qvga", "vga", "svga", |
|
318 "xga", "uxga", "qxga", "sxga", "qsxga", "hsxga", "wvga", "wxga", |
|
319 "wsxga", "wuxga", "wqxga", "wqsxga", "wquxga", "whsxga", |
|
320 "whuxga", "cga", "ega", "hd480", "hd720", "hd1080"]: |
|
321 self.__effects__["s"] = s |
|
322 elif s: |
|
323 wh = s.split("x") |
|
324 if len(wh) == 2 and int(wh[0]) and int(wh[1]): |
|
325 self.__effects__["s"] = s |
|
326 else: |
|
327 raise FFException("Error parsing option: size") |
|
328 return self.__effects__.get("s") |
|
329 |
|
330 def unset_size(self): |
|
331 del self.__effects__["s"] |
|
332 |
|
333 def aspect(self, aspect=None): |
|
334 """ set aspect ratio """ |
|
335 if aspect: |
|
336 self.__effects__["aspect"] = aspect |
|
337 return self.__effects__.get("aspect") |
|
338 |
|
339 def unset_aspect(self): |
|
340 del self.__effects__["aspect"] |
|
341 |
|
342 def crop(self, top=0, bottom=0, left=0, right=0): |
|
343 """ set the crop size """ |
|
344 if top % 2: |
|
345 top = top - 1 |
|
346 if bottom % 2: |
|
347 bottom = bottom - 1 |
|
348 if left % 2: |
|
349 left = left - 1 |
|
350 if right % 2: |
|
351 right = right - 1 |
|
352 if top: |
|
353 self.__effects__["croptop"] = top |
|
354 if bottom: |
|
355 self.__effects__["cropbottom"] = bottom |
|
356 if left: |
|
357 self.__effects__["cropleft"] = left |
|
358 if right: |
|
359 self.__effects__["cropright"] = right |
|
360 return self.__effects__.get("croptop"), self.__effects__.get("cropbottom"), self.__effects__.get("cropleft"), self.__effects__.get("cropright") |
|
361 |
|
362 def unset_crop(self): |
|
363 del self.__effects__["croptop"] |
|
364 del self.__effects__["cropbottom"] |
|
365 del self.__effects__["cropleft"] |
|
366 del self.__effects__["cropright"] |
|
367 |
|
368 def pad(self, top=0, bottom=0, left=0, right=0, color="000000"): |
|
369 """ set the pad band size and color as hex value """ |
|
370 if top: |
|
371 self.__effects__["padtop"] = top |
|
372 if bottom: |
|
373 self.__effects__["padbottom"] = bottom |
|
374 if left: |
|
375 self.__effects__["padleft"] = left |
|
376 if right: |
|
377 self.__effects__["padright"] = right |
|
378 if color: |
|
379 self.__effects__["padcolor"] = color |
|
380 return self.__effects__.get("padtop"), self.__effects__.get("padbottom"), self.__effects__.get("padleft"), self.__effects__.get("padright"), self.__effects__.get("padcolor") |
|
381 |
|
382 def unset_pad(self): |
|
383 del self.__effects__["padtop"] |
|
384 del self.__effects__["padbottom"] |
|
385 del self.__effects__["padleft"] |
|
386 del self.__effects__["padright"] |
|
387 |
|
388 def vn(self): |
|
389 """ disable video recording """ |
|
390 self.__effects__["vn"] = True |
|
391 |
|
392 def unset_vn(self): |
|
393 del self.__effects__["vn"] |
|
394 |
|
395 def bitratetolerance(self, bt=None): |
|
396 """ set bitrate tolerance """ |
|
397 if bt: |
|
398 self.__effects__["bt"] = "%sk" % int(bt) |
|
399 return self.__effects__.get("bt") |
|
400 |
|
401 def unset_bitratetolerance(self): |
|
402 del self.__effects__["bt"] |
|
403 |
|
404 def bitraterange(self, minrate=None, maxrate=None): |
|
405 """ set min/max bitrate (bit/s) """ |
|
406 if minrate or maxrate and not self.__effects__["bufsize"]: |
|
407 self.__effects__["bufsize"] = 4096 |
|
408 if minrate: |
|
409 self.__effects__["minrate"] = minrate |
|
410 if maxrate: |
|
411 self.__effects__["maxrate"] = maxrate |
|
412 |
|
413 return self.__effects__.get("minrate"), self.__effects__.get("maxrate") |
|
414 |
|
415 def unset_bitraterange(self): |
|
416 del self.__effects__["maxrate"] |
|
417 del self.__effects__["minrate"] |
|
418 |
|
419 def bufsize(self, bufsize=4096): |
|
420 """ set buffer size (bits) """ |
|
421 self.__effects__["bufsize"] = int(bufsize) |
|
422 return self.__effects__["bufsize"] |
|
423 |
|
424 def unset_bufsize(self): |
|
425 del self.__effects__["bufsize"] |
|
426 |
|
427 def vcodec(self, vcodec="copy"): |
|
428 """ set video codec """ |
|
429 self.__effects__["vcodec"] = vcodec |
|
430 return self.__effects__["vcodec"] |
|
431 |
|
432 def unset_vcodec(self): |
|
433 del self.__effects__["vcodec"] |
|
434 |
|
435 def sameq(self): |
|
436 """ use same video quality as source """ |
|
437 self.__effects__["sameq"] = True |
|
438 |
|
439 def unset_sameq(self): |
|
440 del self.__effects__["sameq"] |
|
441 |
|
442 def passenc(self, p=1): |
|
443 """ select pass number (1 or 2)""" |
|
444 self.__effects__["pass"] = (int(p) % 3 + 1) % 2 + 1 #!!! |
|
445 return self.__effects__["pass"] |
|
446 |
|
447 def unset_passenc(self): |
|
448 del self.__effects__["pass"] |
|
449 |
|
450 def pixelformat(self, p=None): |
|
451 """ set pixelformat """ |
|
452 if p: |
|
453 self.__effects__["pix_fmt"] = p |
|
454 return self.__effects__.get("pix_fmt") |
|
455 |
|
456 def unset_pixelformat(self): |
|
457 del self.__effects__["pix_fmt"] |
|
458 |
|
459 #TODO: sws_flag |
|
460 |
|
461 def picturesize(self, gop=None): |
|
462 """ set of group pictures size """ |
|
463 if gop: |
|
464 self.__effects__["gop"] = int(gop) |
|
465 return self.__effects__.get("gop") |
|
466 |
|
467 def unset_picturesize(self): |
|
468 del self.__effects__["gop"] |
|
469 |
|
470 def intra(self): |
|
471 """ use only intra frames """ |
|
472 self.__effects__["intra"] = True |
|
473 |
|
474 def unset_intra(self): |
|
475 del self.__effects__["intra"] |
|
476 |
|
477 def vdthreshold(self, vdt=None): |
|
478 """ discard threshold """ |
|
479 if vdt: |
|
480 self.__effects__["vdt"] = int(vdt) |
|
481 return self.__effects__.get("vdt") |
|
482 |
|
483 def unset_vdthreshold(self): |
|
484 del self.__effects__["vdt"] |
|
485 |
|
486 def quantizerscale(self, qscale=None): |
|
487 """ Fixed quantizer scale """ |
|
488 if qscale: |
|
489 self.__effects__["qscale"] = int(qscale) |
|
490 return self.__effects__.get("qscale") |
|
491 |
|
492 def unset_quantizerscale(self): |
|
493 del self.__effects__["qscale"] |
|
494 |
|
495 def quantizerrange(self, qmin=None, qmax=None, qdiff=None): |
|
496 """ define min/max quantizer scale """ |
|
497 if qdiff: |
|
498 self.__effects__["qdiff"] = int(qdiff) |
|
499 else: |
|
500 if qmin: |
|
501 self.__effects__["qmin"] = int(qmin) |
|
502 if qmax: |
|
503 self.__effects__["qmax"] = int(qmax) |
|
504 return self.__effects__.get("qmin"), self.__effects__.get("qmax"), self.__effects__.get("qdiff"), |
|
505 |
|
506 def unset_quantizerrange(self): |
|
507 del self.__effects__["qdiff"] |
|
508 |
|
509 def quantizerblur(self, qblur=None): |
|
510 """ video quantizer scale blur """ |
|
511 if qblur: |
|
512 self.__effects__["qblur"] = float(qblur) |
|
513 return self.__effects__.get("qblur") |
|
514 |
|
515 def unset_quantizerblur(self): |
|
516 del self.__effects__["qblur"] |
|
517 |
|
518 def quantizercompression(self, qcomp=0.5): |
|
519 """ video quantizer scale compression """ |
|
520 self.__effects__["qcomp"] = float(qcomp) |
|
521 return self.__effects__["qcomp"] |
|
522 |
|
523 def unset_quantizercompression(self): |
|
524 del self.__effects__["qcomp"] |
|
525 |
|
526 def lagrangefactor(self, lmin=None, lmax=None): |
|
527 """ min/max lagrange factor """ |
|
528 if lmin: |
|
529 self.__effects__["lmin"] = int(lmin) |
|
530 if lmax: |
|
531 self.__effects__["lmax"] = int(lmax) |
|
532 return self.__effects__.get("lmin"), self.__effects__.get("lmax") |
|
533 |
|
534 def unset_lagrangefactor(self): |
|
535 del self.__effects__["lmin"] |
|
536 del self.__effects__["lmax"] |
|
537 |
|
538 def macroblock(self, mblmin=None, mblmax=None): |
|
539 """ min/max macroblock scale """ |
|
540 if mblmin: |
|
541 self.__effects__["mblmin"] = int(mblmin) |
|
542 if mblmax: |
|
543 self.__effects__["mblmax"] = int(mblmax) |
|
544 return self.__effects__.get("mblmin"), self.__effects__.get("mblmax") |
|
545 |
|
546 def unset_macroblock(self): |
|
547 del self.__effects__["mblmin"] |
|
548 del self.__effects__["mblmax"] |
|
549 |
|
550 #TODO: read man pages ! |
|
551 |
|
552 |
|
553 class FFAudioEffect(FFEffect): |
|
554 """ |
|
555 Audio effect |
|
556 """ |
|
557 |
|
558 def __init__(self, inputfile, **args): |
|
559 FFEffect.__init__(self, inputfile, **args) |
|
560 |
|
561 def aframes(self, aframes=None): |
|
562 """ set number of audio frames to record """ |
|
563 if aframes: |
|
564 self.__effects__["aframes"] = int(aframes) |
|
565 return self.__effects__.get("aframes") |
|
566 |
|
567 def unset_aframes(self): |
|
568 del self.__effects__["aframes"] |
|
569 |
|
570 def audiosampling(self, ar=44100): |
|
571 """ set audio sampling frequency (Hz)""" |
|
572 self.__effects__["ar"] = int(ar) |
|
573 return self.__effects__["ar"] |
|
574 |
|
575 def unset_audiosampling(self): |
|
576 del self.__effects__["ar"] |
|
577 |
|
578 def audiobitrate(self, ab=64): |
|
579 """ set audio bitrate (kbit/s)""" |
|
580 self.__effects__["ab"] = int(ab) |
|
581 return self.__effects__["ab"] |
|
582 |
|
583 def unset_audiobitrate(self): |
|
584 del self.__effects__["ab"] |
|
585 |
|
586 def audiochannels(self, ac=1): |
|
587 """ set number of audio channels """ |
|
588 self.__effects__["ac"] = int(ac) |
|
589 return self.__effects__["ac"] |
|
590 |
|
591 def unset_audiochannels(self): |
|
592 del self.__effects__["ac"] |
|
593 |
|
594 def audiorecording(self): |
|
595 """ disable audio recording """ |
|
596 self.__effects__["an"] = True |
|
597 |
|
598 def unset_audiorecording(self): |
|
599 del self.__effects__["an"] |
|
600 |
|
601 def acodec(self, acodec="copy"): |
|
602 """ select audio codec """ |
|
603 self.__effects__["acodec"] = acodec |
|
604 return self.__effects__["acodec"] |
|
605 |
|
606 def unset_acodec(self): |
|
607 del self.__effects__["acodec"] |
|
608 |
|
609 def newaudio(self): |
|
610 """ add new audio track """ |
|
611 self.__effects__["newaudio"] = True |
|
612 |
|
613 def unset_newaudio(self): |
|
614 del self.__effects__["newaudio"] |