diff --git a/ffplayout.py b/ffplayout.py index 2b1199a9..10111845 100755 --- a/ffplayout.py +++ b/ffplayout.py @@ -19,15 +19,9 @@ # ------------------------------------------------------------------------------ import os -from subprocess import PIPE, Popen -from threading import Thread +from pydoc import locate -from ffplayout.folder import GetSourceFromFolder, MediaStore, MediaWatcher -from ffplayout.playlist import GetSourceFromPlaylist -from ffplayout.utils import (_ff, _log, _playlist, _playout, _pre_comp, _text, - ffmpeg_stderr_reader, get_date, messenger, - pre_audio_codec, stdin_args, terminate_processes, - validate_ffmpeg_libs) +from ffplayout.utils import _playout, validate_ffmpeg_libs try: if os.name != 'posix': @@ -36,9 +30,6 @@ try: except ImportError: print('colorama import failed, no colored console output on windows...') -_WINDOWS = os.name == 'nt' -COPY_BUFSIZE = 1024 * 1024 if _WINDOWS else 65424 - # ------------------------------------------------------------------------------ # main functions @@ -49,105 +40,15 @@ def main(): pipe ffmpeg pre-process to final ffmpeg post-process, or play with ffplay """ - year = get_date(False).split('-')[0] - overlay = [] - ff_pre_settings = [ - '-pix_fmt', 'yuv420p', '-r', str(_pre_comp.fps), - '-c:v', 'mpeg2video', '-intra', - '-b:v', '{}k'.format(_pre_comp.v_bitrate), - '-minrate', '{}k'.format(_pre_comp.v_bitrate), - '-maxrate', '{}k'.format(_pre_comp.v_bitrate), - '-bufsize', '{}k'.format(_pre_comp.v_bufsize) - ] + pre_audio_codec() + ['-f', 'mpegts', '-'] + for output in os.listdir('ffplayout/output'): + if os.path.isfile(os.path.join('ffplayout/output', output)) \ + and output != '__init__.py': + mode = os.path.splitext(output)[0] + if mode == _playout.mode: + output = locate('ffplayout.output.{}.output'.format(mode)) - if _text.add_text: - messenger.info('Using drawtext node, listening on address: {}'.format( - _text.address - )) - overlay = [ - '-vf', - "null,zmq=b=tcp\\\\://'{}',drawtext=text='':fontfile='{}'".format( - _text.address.replace(':', '\\:'), _text.fontfile) - ] - - try: - if _playout.preview or stdin_args.desktop: - # preview playout to player - _ff.encoder = Popen([ - 'ffplay', '-hide_banner', '-nostats', '-i', 'pipe:0' - ] + overlay, stderr=PIPE, stdin=PIPE, stdout=None) - else: - _ff.encoder = Popen([ - 'ffmpeg', '-v', _log.ff_level.lower(), '-hide_banner', - '-nostats', '-re', '-thread_queue_size', '256', '-i', 'pipe:0' - ] + overlay + [ - '-metadata', 'service_name=' + _playout.name, - '-metadata', 'service_provider=' + _playout.provider, - '-metadata', 'year={}'.format(year) - ] + _playout.post_comp_param + [_playout.out_addr], - stdin=PIPE, stderr=PIPE) - - enc_err_thread = Thread(target=ffmpeg_stderr_reader, - args=(_ff.encoder.stderr, False)) - enc_err_thread.daemon = True - enc_err_thread.start() - - if _playlist.mode and not stdin_args.folder: - watcher = None - get_source = GetSourceFromPlaylist() - else: - messenger.info('Start folder mode') - media = MediaStore() - watcher = MediaWatcher(media) - get_source = GetSourceFromFolder(media) - - try: - for src_cmd in get_source.next(): - messenger.debug('src_cmd: "{}"'.format(src_cmd)) - if src_cmd[0] == '-i': - current_file = src_cmd[1] - else: - current_file = src_cmd[3] - - messenger.info('Play: "{}"'.format(current_file)) - - with Popen([ - 'ffmpeg', '-v', _log.ff_level.lower(), '-hide_banner', - '-nostats'] + src_cmd + ff_pre_settings, - stdout=PIPE, stderr=PIPE) as _ff.decoder: - - dec_err_thread = Thread(target=ffmpeg_stderr_reader, - args=(_ff.decoder.stderr, True)) - dec_err_thread.daemon = True - dec_err_thread.start() - - while True: - buf = _ff.decoder.stdout.read(COPY_BUFSIZE) - if not buf: - break - _ff.encoder.stdin.write(buf) - - except BrokenPipeError: - messenger.error('Broken Pipe!') - terminate_processes(watcher) - - except SystemExit: - messenger.info('Got close command') - terminate_processes(watcher) - - except KeyboardInterrupt: - messenger.warning('Program terminated') - terminate_processes(watcher) - - # close encoder when nothing is to do anymore - if _ff.encoder.poll() is None: - _ff.encoder.terminate() - - finally: - if _ff.encoder.poll() is None: - _ff.encoder.terminate() - _ff.encoder.wait() + output() if __name__ == '__main__': diff --git a/ffplayout.yml b/ffplayout.yml index b8227814..aaf5f0cf 100644 --- a/ffplayout.yml +++ b/ffplayout.yml @@ -31,16 +31,18 @@ logging: log_level: "DEBUG" ffmpeg_level: "ERROR" -pre_compress: - helptext: Settings for the pre-compression. All clips get prepared in that way, +pre_process: + helptext: Settings for the pre_process. All clips get prepared in that way, so the input for the final compression is unique. 'aspect' must be a float number. 'logo' is only used if the path exist. 'logo_scale' scale the logo to target size, leave it blank when no scaling is needed, format is 'number:number', for example '100:-1' for proportional scaling. With 'logo_opacity' logo can become transparent. With 'logo_filter' 'overlay=W-w-12:12' you can modify the logo position. With 'use_loudnorm' you can activate single pass EBU R128 - loudness normalization. 'loud_*' can adjust the loudnorm filter. [Output is - always progressive!] + loudness normalization. 'loud_*' can adjust the loudnorm filter. 'output_count' + sets the outputs for the filtering, > 1 gives the option to use the same filters + for multiple outputs. This outputs can be taken in 'ffmpeg_param', names will be + vout2, vout3; aout2, aout2 etc. width: 1024 height: 576 aspect: 1.778 @@ -54,6 +56,7 @@ pre_compress: loud_I: -18 loud_TP: -1.5 loud_LRA: 11 + output_count: 1 playlist: helptext: Set 'playlist_mode' to 'False' if you want to play clips from the [STORAGE] @@ -84,18 +87,21 @@ text: helptext: Overlay text in combination with libzmq for remote text manipulation. On windows fontfile path need to be like this 'C\:/WINDOWS/fonts/DejaVuSans.ttf'. In a standard environment the filter drawtext node is Parsed_drawtext_2. + 'over_pre' if True text will be overlay in pre processing. Continue same text + over multiple files is in that mode not possible. add_text: False + over_pre: False bind_address: "127.0.0.1:5555" fontfile: "/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf" out: - helptext: The final playout post compression. Set the settings to your needs. - 'preview' works only on a desktop system with ffplay!! Set it to 'True', if - you need it. - preview: False + helptext: The final playout compression. Set the settings to your needs. + 'mode' has the standard options 'desktop', 'hls', 'stream'. Self made outputs + can be define, by adding script in output folder with an 'output' function inside. + mode: 'stream' service_name: "Live Stream" service_provider: "example.org" - post_ffmpeg_param: >- + ffmpeg_param: >- -c:v libx264 -crf 23 -x264-params keyint=50:min-keyint=25:scenecut=-1 @@ -108,5 +114,4 @@ out: -ar 44100 -b:a 128k -flags +global_header - -f flv - out_addr: "rtmp://localhost/live/stream" + -f flv "rtmp://localhost/live/stream" diff --git a/ffplayout/filters.py b/ffplayout/filters.py index 3a93a97a..1d057f90 100644 --- a/ffplayout/filters.py +++ b/ffplayout/filters.py @@ -21,13 +21,25 @@ import math import os import re -from .utils import _pre_comp +from .utils import _pre, _text # ------------------------------------------------------------------------------ # building filters, # when is needed add individuell filters to match output format # ------------------------------------------------------------------------------ + +def text_filter(): + filter_chain = [] + + if _text.add_text and _text.over_pre: + filter_chain = [ + "null,zmq=b=tcp\\\\://'{}',drawtext=text='':fontfile='{}'".format( + _text.address.replace(':', '\\:'), _text.fontfile)] + + return filter_chain + + def deinterlace_filter(probe): """ when material is interlaced, @@ -50,15 +62,15 @@ def pad_filter(probe): filter_chain = [] if not math.isclose(probe.video[0]['aspect'], - _pre_comp.aspect, abs_tol=0.03): - if probe.video[0]['aspect'] < _pre_comp.aspect: + _pre.aspect, abs_tol=0.03): + if probe.video[0]['aspect'] < _pre.aspect: filter_chain.append( - 'pad=ih*{}/{}/sar:ih:(ow-iw)/2:(oh-ih)/2'.format(_pre_comp.w, - _pre_comp.h)) - elif probe.video[0]['aspect'] > _pre_comp.aspect: + 'pad=ih*{}/{}/sar:ih:(ow-iw)/2:(oh-ih)/2'.format(_pre.w, + _pre.h)) + elif probe.video[0]['aspect'] > _pre.aspect: filter_chain.append( - 'pad=iw:iw*{}/{}/sar:(ow-iw)/2:(oh-ih)/2'.format(_pre_comp.h, - _pre_comp.w)) + 'pad=iw:iw*{}/{}/sar:(ow-iw)/2:(oh-ih)/2'.format(_pre.h, + _pre.w)) return filter_chain @@ -69,8 +81,8 @@ def fps_filter(probe): """ filter_chain = [] - if probe.video[0]['fps'] != _pre_comp.fps: - filter_chain.append('fps={}'.format(_pre_comp.fps)) + if probe.video[0]['fps'] != _pre.fps: + filter_chain.append('fps={}'.format(_pre.fps)) return filter_chain @@ -82,13 +94,13 @@ def scale_filter(probe): """ filter_chain = [] - if int(probe.video[0]['width']) != _pre_comp.w or \ - int(probe.video[0]['height']) != _pre_comp.h: - filter_chain.append('scale={}:{}'.format(_pre_comp.w, _pre_comp.h)) + if int(probe.video[0]['width']) != _pre.w or \ + int(probe.video[0]['height']) != _pre.h: + filter_chain.append('scale={}:{}'.format(_pre.w, _pre.h)) if not math.isclose(probe.video[0]['aspect'], - _pre_comp.aspect, abs_tol=0.03): - filter_chain.append('setdar=dar={}'.format(_pre_comp.aspect)) + _pre.aspect, abs_tol=0.03): + filter_chain.append('setdar=dar={}'.format(_pre.aspect)) return filter_chain @@ -115,27 +127,27 @@ def overlay_filter(duration, ad, ad_last, ad_next): when ad is comming next fade logo out, when clip before was an ad fade logo in """ - logo_filter = '[v]null[logo]' + logo_filter = '[v]null' scale_filter = '' - if _pre_comp.add_logo and os.path.isfile(_pre_comp.logo) and not ad: + if _pre.add_logo and os.path.isfile(_pre.logo) and not ad: logo_chain = [] - if _pre_comp.logo_scale and \ - re.match(r'\d+:-?\d+', _pre_comp.logo_scale): - scale_filter = 'scale={},'.format(_pre_comp.logo_scale) + if _pre.logo_scale and \ + re.match(r'\d+:-?\d+', _pre.logo_scale): + scale_filter = 'scale={},'.format(_pre.logo_scale) logo_extras = 'format=rgba,{}colorchannelmixer=aa={}'.format( - scale_filter, _pre_comp.logo_opacity) + scale_filter, _pre.logo_opacity) loop = 'loop=loop=-1:size=1:start=0' logo_chain.append( - 'movie={},{},{}'.format(_pre_comp.logo, loop, logo_extras)) + 'movie={},{},{}'.format(_pre.logo, loop, logo_extras)) if ad_last: logo_chain.append('fade=in:st=0:d=1.0:alpha=1') if ad_next: logo_chain.append('fade=out:st={}:d=1.0:alpha=1'.format( duration - 1)) - logo_filter = '{}[l];[v][l]{}:shortest=1[logo]'.format( - ','.join(logo_chain), _pre_comp.logo_filter) + logo_filter = '{}[l];[v][l]{}:shortest=1'.format( + ','.join(logo_chain), _pre.logo_filter) return logo_filter @@ -161,9 +173,9 @@ def add_loudnorm(probe): """ loud_filter = [] - if probe.audio and _pre_comp.add_loudnorm: + if probe.audio and _pre.add_loudnorm: loud_filter = [('loudnorm=I={}:TP={}:LRA={}').format( - _pre_comp.loud_i, _pre_comp.loud_tp, _pre_comp.loud_lra)] + _pre.loud_i, _pre.loud_tp, _pre.loud_lra)] return loud_filter @@ -175,7 +187,7 @@ def extend_audio(probe, duration): pad_filter = [] if probe.audio and 'duration' in probe.audio[0] and \ - duration > float(probe.audio[0]['duration']) + 0.3: + duration > float(probe.audio[0]['duration']) + 0.1: pad_filter.append('apad=whole_dur={}'.format(duration)) return pad_filter @@ -189,25 +201,45 @@ def extend_video(probe, duration, target_duration): if 'duration' in probe.video[0] and \ target_duration < duration > float( - probe.video[0]['duration']) + 0.3: + probe.video[0]['duration']) + 0.1: pad_filter.append('tpad=stop_mode=add:stop_duration={}'.format( duration - float(probe.video[0]['duration']))) return pad_filter +def split_filter(filter_type): + map_node = [] + filter_prefix = '' + + if filter_type == 'a': + filter_prefix = 'a' + + if _pre.output_count > 1: + for num in range(_pre.output_count): + map_node.append('[{}out{}]'.format(filter_type, num + 1)) + + filter = ',{}split={}{}'.format(filter_prefix, _pre.output_count, + ''.join(map_node)) + + else: + filter = '[{}out1]'.format(filter_type) + + return filter + + def build_filtergraph(duration, seek, out, ad, ad_last, ad_next, probe, msg): """ build final filter graph, with video and audio chain """ video_chain = [] audio_chain = [] - video_map = ['-map', '[logo]'] if out > duration: seek = 0 if probe.video[0]: + video_chain += text_filter() video_chain += deinterlace_filter(probe) video_chain += pad_filter(probe) video_chain += fps_filter(probe) @@ -229,17 +261,16 @@ def build_filtergraph(duration, seek, out, ad, ad_last, ad_next, probe, msg): video_filter = 'null[v]' logo_filter = overlay_filter(out - seek, ad, ad_last, ad_next) + v_split = split_filter('v') + video_map = ['-map', '[vout1]'] video_filter = [ - '-filter_complex', '[0:v]{};{}'.format( - video_filter, logo_filter)] + '-filter_complex', '[0:v]{};{}{}'.format( + video_filter, logo_filter, v_split)] - if audio_chain: - audio_filter = [ - '-filter_complex', '{}[a]'.format(','.join(audio_chain))] - audio_map = ['-map', '[a]'] - else: - audio_filter = [] - audio_map = ['-map', '0:a'] + a_split = split_filter('a') + audio_map = ['-map', '[aout1]'] + audio_filter = [ + '-filter_complex', '{}{}'.format(','.join(audio_chain), a_split)] if probe.video[0]: return video_filter + audio_filter + video_map + audio_map diff --git a/ffplayout/output/__init__.py b/ffplayout/output/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/ffplayout/output/desktop.py b/ffplayout/output/desktop.py new file mode 100644 index 00000000..efdc7f34 --- /dev/null +++ b/ffplayout/output/desktop.py @@ -0,0 +1,104 @@ +import os +from subprocess import PIPE, Popen +from threading import Thread + +from ffplayout.folder import GetSourceFromFolder, MediaStore, MediaWatcher +from ffplayout.playlist import GetSourceFromPlaylist +from ffplayout.utils import (_ff, _log, _playlist, _pre, _text, + ffmpeg_stderr_reader, messenger, pre_audio_codec, + stdin_args, terminate_processes) + +_WINDOWS = os.name == 'nt' +COPY_BUFSIZE = 1024 * 1024 if _WINDOWS else 65424 + + +def output(): + """ + this output is for playing on desktop with ffplay + """ + overlay = [] + + ff_pre_settings = [ + '-pix_fmt', 'yuv420p', '-r', str(_pre.fps), + '-c:v', 'mpeg2video', '-intra', + '-b:v', '{}k'.format(_pre.v_bitrate), + '-minrate', '{}k'.format(_pre.v_bitrate), + '-maxrate', '{}k'.format(_pre.v_bitrate), + '-bufsize', '{}k'.format(_pre.v_bufsize) + ] + pre_audio_codec() + ['-f', 'mpegts', '-'] + + if _text.add_text and not _text.over_pre: + messenger.info('Using drawtext node, listening on address: {}'.format( + _text.address + )) + overlay = [ + '-vf', + "null,zmq=b=tcp\\\\://'{}',drawtext=text='':fontfile='{}'".format( + _text.address.replace(':', '\\:'), _text.fontfile) + ] + + try: + _ff.encoder = Popen([ + 'ffplay', '-hide_banner', '-nostats', '-i', 'pipe:0' + ] + overlay, stderr=PIPE, stdin=PIPE, stdout=None) + + enc_err_thread = Thread(target=ffmpeg_stderr_reader, + args=(_ff.encoder.stderr, False)) + enc_err_thread.daemon = True + enc_err_thread.start() + + if _playlist.mode and not stdin_args.folder: + watcher = None + get_source = GetSourceFromPlaylist() + else: + messenger.info('Start folder mode') + media = MediaStore() + watcher = MediaWatcher(media) + get_source = GetSourceFromFolder(media) + + try: + for src_cmd in get_source.next(): + messenger.debug('src_cmd: "{}"'.format(src_cmd)) + if src_cmd[0] == '-i': + current_file = src_cmd[1] + else: + current_file = src_cmd[3] + + messenger.info('Play: "{}"'.format(current_file)) + + with Popen([ + 'ffmpeg', '-v', _log.ff_level.lower(), '-hide_banner', + '-nostats'] + src_cmd + ff_pre_settings, + stdout=PIPE, stderr=PIPE) as _ff.decoder: + + dec_err_thread = Thread(target=ffmpeg_stderr_reader, + args=(_ff.decoder.stderr, True)) + dec_err_thread.daemon = True + dec_err_thread.start() + + while True: + buf = _ff.decoder.stdout.read(COPY_BUFSIZE) + if not buf: + break + _ff.encoder.stdin.write(buf) + + except BrokenPipeError: + messenger.error('Broken Pipe!') + terminate_processes(watcher) + + except SystemExit: + messenger.info('Got close command') + terminate_processes(watcher) + + except KeyboardInterrupt: + messenger.warning('Program terminated') + terminate_processes(watcher) + + # close encoder when nothing is to do anymore + if _ff.encoder.poll() is None: + _ff.encoder.terminate() + + finally: + if _ff.encoder.poll() is None: + _ff.encoder.terminate() + _ff.encoder.wait() diff --git a/ffplayout/output/hls.py b/ffplayout/output/hls.py new file mode 100644 index 00000000..7efe4eba --- /dev/null +++ b/ffplayout/output/hls.py @@ -0,0 +1,72 @@ +from subprocess import PIPE, Popen +from threading import Thread + +from ffplayout.folder import GetSourceFromFolder, MediaStore, MediaWatcher +from ffplayout.playlist import GetSourceFromPlaylist +from ffplayout.utils import (_ff, _log, _playlist, _playout, + ffmpeg_stderr_reader, get_date, messenger, + stdin_args, terminate_processes) + + +def output(): + """ + this output is hls output, no preprocess is needed. + """ + year = get_date(False).split('-')[0] + + try: + if _playlist.mode and not stdin_args.folder: + watcher = None + get_source = GetSourceFromPlaylist() + else: + messenger.info('Start folder mode') + media = MediaStore() + watcher = MediaWatcher(media) + get_source = GetSourceFromFolder(media) + + try: + for src_cmd in get_source.next(): + messenger.debug('src_cmd: "{}"'.format(src_cmd)) + if src_cmd[0] == '-i': + current_file = src_cmd[1] + else: + current_file = src_cmd[3] + + messenger.info('Play: "{}"'.format(current_file)) + cmd = [ + 'ffmpeg', '-v', _log.ff_level.lower(), '-hide_banner', + '-nostats', '-re', '-thread_queue_size', '256' + ] + src_cmd + [ + '-metadata', 'service_name=' + _playout.name, + '-metadata', 'service_provider=' + _playout.provider, + '-metadata', 'year={}'.format(year) + ] + _playout.ffmpeg_param + + _ff.encoder = Popen(cmd, stdin=PIPE, stderr=PIPE) + + enc_thread = Thread(target=ffmpeg_stderr_reader, + args=(_ff.encoder.stderr, True)) + enc_thread.daemon = True + enc_thread.start() + enc_thread.join() + + except BrokenPipeError: + messenger.error('Broken Pipe!') + terminate_processes(watcher) + + except SystemExit: + messenger.info('Got close command') + terminate_processes(watcher) + + except KeyboardInterrupt: + messenger.warning('Program terminated') + terminate_processes(watcher) + + # close encoder when nothing is to do anymore + if _ff.encoder.poll() is None: + _ff.encoder.terminate() + + finally: + if _ff.encoder.poll() is None: + _ff.encoder.terminate() + _ff.encoder.wait() diff --git a/ffplayout/output/stream.py b/ffplayout/output/stream.py new file mode 100644 index 00000000..200aa1dc --- /dev/null +++ b/ffplayout/output/stream.py @@ -0,0 +1,111 @@ +import os +from subprocess import PIPE, Popen +from threading import Thread + +from ffplayout.folder import GetSourceFromFolder, MediaStore, MediaWatcher +from ffplayout.playlist import GetSourceFromPlaylist +from ffplayout.utils import (_ff, _log, _playlist, _playout, _pre, _text, + ffmpeg_stderr_reader, get_date, messenger, + pre_audio_codec, stdin_args, terminate_processes) + +_WINDOWS = os.name == 'nt' +COPY_BUFSIZE = 1024 * 1024 if _WINDOWS else 65424 + + +def output(): + """ + this output is for streaming to a target address, + like rtmp, rtp, svt, etc. + """ + year = get_date(False).split('-')[0] + overlay = [] + + ff_pre_settings = [ + '-pix_fmt', 'yuv420p', '-r', str(_pre.fps), + '-c:v', 'mpeg2video', '-intra', + '-b:v', '{}k'.format(_pre.v_bitrate), + '-minrate', '{}k'.format(_pre.v_bitrate), + '-maxrate', '{}k'.format(_pre.v_bitrate), + '-bufsize', '{}k'.format(_pre.v_bufsize) + ] + pre_audio_codec() + ['-f', 'mpegts', '-'] + + if _text.add_text and not _text.over_pre: + messenger.info('Using drawtext node, listening on address: {}'.format( + _text.address + )) + overlay = [ + '-vf', + "null,zmq=b=tcp\\\\://'{}',drawtext=text='':fontfile='{}'".format( + _text.address.replace(':', '\\:'), _text.fontfile) + ] + + try: + _ff.encoder = Popen([ + 'ffmpeg', '-v', _log.ff_level.lower(), '-hide_banner', + '-nostats', '-re', '-thread_queue_size', '256', '-i', 'pipe:0' + ] + overlay + [ + '-metadata', 'service_name=' + _playout.name, + '-metadata', 'service_provider=' + _playout.provider, + '-metadata', 'year={}'.format(year) + ] + _playout.ffmpeg_param, stdin=PIPE, stderr=PIPE) + + enc_err_thread = Thread(target=ffmpeg_stderr_reader, + args=(_ff.encoder.stderr, False)) + enc_err_thread.daemon = True + enc_err_thread.start() + + if _playlist.mode and not stdin_args.folder: + watcher = None + get_source = GetSourceFromPlaylist() + else: + messenger.info('Start folder mode') + media = MediaStore() + watcher = MediaWatcher(media) + get_source = GetSourceFromFolder(media) + + try: + for src_cmd in get_source.next(): + messenger.debug('src_cmd: "{}"'.format(src_cmd)) + if src_cmd[0] == '-i': + current_file = src_cmd[1] + else: + current_file = src_cmd[3] + + messenger.info('Play: "{}"'.format(current_file)) + + with Popen([ + 'ffmpeg', '-v', _log.ff_level.lower(), '-hide_banner', + '-nostats'] + src_cmd + ff_pre_settings, + stdout=PIPE, stderr=PIPE) as _ff.decoder: + + dec_err_thread = Thread(target=ffmpeg_stderr_reader, + args=(_ff.decoder.stderr, True)) + dec_err_thread.daemon = True + dec_err_thread.start() + + while True: + buf = _ff.decoder.stdout.read(COPY_BUFSIZE) + if not buf: + break + _ff.encoder.stdin.write(buf) + + except BrokenPipeError: + messenger.error('Broken Pipe!') + terminate_processes(watcher) + + except SystemExit: + messenger.info('Got close command') + terminate_processes(watcher) + + except KeyboardInterrupt: + messenger.warning('Program terminated') + terminate_processes(watcher) + + # close encoder when nothing is to do anymore + if _ff.encoder.poll() is None: + _ff.encoder.terminate() + + finally: + if _ff.encoder.poll() is None: + _ff.encoder.terminate() + _ff.encoder.wait() diff --git a/ffplayout/playlist.py b/ffplayout/playlist.py index cec5dca2..f666ace4 100644 --- a/ffplayout/playlist.py +++ b/ffplayout/playlist.py @@ -224,7 +224,7 @@ class GetSourceFromPlaylist: if self.clip_nodes is None: self.eof_handling( - 'No valid playlist:\n{}'.format(self.json_file), True, 300) + 'No valid playlist:\n{}'.format(self.json_file), True, 30) yield self.src_cmd + self.filtergraph continue diff --git a/ffplayout/utils.py b/ffplayout/utils.py index b737b9a0..143bedcc 100644 --- a/ffplayout/utils.py +++ b/ffplayout/utils.py @@ -113,7 +113,7 @@ def get_time(time_format): _general = SimpleNamespace() _mail = SimpleNamespace() _log = SimpleNamespace() -_pre_comp = SimpleNamespace() +_pre = SimpleNamespace() _playlist = SimpleNamespace() _storage = SimpleNamespace() _text = SimpleNamespace() @@ -178,15 +178,16 @@ def load_config(): _mail.recip = cfg['mail']['recipient'] _mail.level = cfg['mail']['mail_level'] - _pre_comp.add_logo = cfg['pre_compress']['add_logo'] - _pre_comp.logo = cfg['pre_compress']['logo'] - _pre_comp.logo_scale = cfg['pre_compress']['logo_scale'] - _pre_comp.logo_filter = cfg['pre_compress']['logo_filter'] - _pre_comp.logo_opacity = cfg['pre_compress']['logo_opacity'] - _pre_comp.add_loudnorm = cfg['pre_compress']['add_loudnorm'] - _pre_comp.loud_i = cfg['pre_compress']['loud_I'] - _pre_comp.loud_tp = cfg['pre_compress']['loud_TP'] - _pre_comp.loud_lra = cfg['pre_compress']['loud_LRA'] + _pre.add_logo = cfg['pre_process']['add_logo'] + _pre.logo = cfg['pre_process']['logo'] + _pre.logo_scale = cfg['pre_process']['logo_scale'] + _pre.logo_filter = cfg['pre_process']['logo_filter'] + _pre.logo_opacity = cfg['pre_process']['logo_opacity'] + _pre.add_loudnorm = cfg['pre_process']['add_loudnorm'] + _pre.loud_i = cfg['pre_process']['loud_I'] + _pre.loud_tp = cfg['pre_process']['loud_TP'] + _pre.loud_lra = cfg['pre_process']['loud_LRA'] + _pre.output_count = cfg['pre_process']['output_count'] _playlist.mode = cfg['playlist']['playlist_mode'] _playlist.path = cfg['playlist']['path'] @@ -199,6 +200,7 @@ def load_config(): _storage.shuffle = cfg['storage']['shuffle'] _text.add_text = cfg['text']['add_text'] + _text.over_pre = cfg['text']['over_pre'] _text.address = cfg['text']['bind_address'] _text.fontfile = cfg['text']['fontfile'] @@ -209,18 +211,17 @@ def load_config(): _log.level = cfg['logging']['log_level'] _log.ff_level = cfg['logging']['ffmpeg_level'] - _pre_comp.w = cfg['pre_compress']['width'] - _pre_comp.h = cfg['pre_compress']['height'] - _pre_comp.aspect = cfg['pre_compress']['aspect'] - _pre_comp.fps = cfg['pre_compress']['fps'] - _pre_comp.v_bitrate = cfg['pre_compress']['width'] * 50 - _pre_comp.v_bufsize = cfg['pre_compress']['width'] * 50 / 2 + _pre.w = cfg['pre_process']['width'] + _pre.h = cfg['pre_process']['height'] + _pre.aspect = cfg['pre_process']['aspect'] + _pre.fps = cfg['pre_process']['fps'] + _pre.v_bitrate = cfg['pre_process']['width'] * 50 + _pre.v_bufsize = cfg['pre_process']['width'] * 50 / 2 - _playout.preview = cfg['out']['preview'] + _playout.mode = cfg['out']['mode'] _playout.name = cfg['out']['service_name'] _playout.provider = cfg['out']['service_provider'] - _playout.post_comp_param = cfg['out']['post_ffmpeg_param'].split(' ') - _playout.out_addr = cfg['out']['out_addr'] + _playout.ffmpeg_param = cfg['out']['ffmpeg_param'].split(' ') _init.load = False @@ -782,7 +783,7 @@ def gen_dummy(duration): return [ '-f', 'lavfi', '-i', 'color=c={}:s={}x{}:d={}:r={},format=pix_fmts=yuv420p'.format( - color, _pre_comp.w, _pre_comp.h, duration, _pre_comp.fps + color, _pre.w, _pre.h, duration, _pre.fps ), '-f', 'lavfi', '-i', 'anoisesrc=d={}:c=pink:r=48000:a=0.05'.format( duration) @@ -989,7 +990,7 @@ def pre_audio_codec(): s302m has higher quality, but is experimental and works not well together with the loudnorm filter """ - if _pre_comp.add_loudnorm: + if _pre.add_loudnorm: acodec = 'libtwolame' if 'libtwolame' in FF_LIBS['libs'] else 'mp2' return ['-c:a', acodec, '-b:a', '384k', '-ar', '48000', '-ac', '2'] else: