modularize output

This commit is contained in:
jb-alvarado 2020-06-07 20:08:54 +02:00
parent 9af5a67530
commit fbfb9a7712
9 changed files with 404 additions and 179 deletions

View File

@ -19,15 +19,9 @@
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
import os import os
from subprocess import PIPE, Popen from pydoc import locate
from threading import Thread
from ffplayout.folder import GetSourceFromFolder, MediaStore, MediaWatcher from ffplayout.utils import _playout, validate_ffmpeg_libs
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)
try: try:
if os.name != 'posix': if os.name != 'posix':
@ -36,9 +30,6 @@ try:
except ImportError: except ImportError:
print('colorama import failed, no colored console output on windows...') print('colorama import failed, no colored console output on windows...')
_WINDOWS = os.name == 'nt'
COPY_BUFSIZE = 1024 * 1024 if _WINDOWS else 65424
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
# main functions # main functions
@ -49,105 +40,15 @@ def main():
pipe ffmpeg pre-process to final ffmpeg post-process, pipe ffmpeg pre-process to final ffmpeg post-process,
or play with ffplay or play with ffplay
""" """
year = get_date(False).split('-')[0]
overlay = []
ff_pre_settings = [ for output in os.listdir('ffplayout/output'):
'-pix_fmt', 'yuv420p', '-r', str(_pre_comp.fps), if os.path.isfile(os.path.join('ffplayout/output', output)) \
'-c:v', 'mpeg2video', '-intra', and output != '__init__.py':
'-b:v', '{}k'.format(_pre_comp.v_bitrate), mode = os.path.splitext(output)[0]
'-minrate', '{}k'.format(_pre_comp.v_bitrate), if mode == _playout.mode:
'-maxrate', '{}k'.format(_pre_comp.v_bitrate), output = locate('ffplayout.output.{}.output'.format(mode))
'-bufsize', '{}k'.format(_pre_comp.v_bufsize)
] + pre_audio_codec() + ['-f', 'mpegts', '-']
if _text.add_text: output()
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()
if __name__ == '__main__': if __name__ == '__main__':

View File

@ -31,16 +31,18 @@ logging:
log_level: "DEBUG" log_level: "DEBUG"
ffmpeg_level: "ERROR" ffmpeg_level: "ERROR"
pre_compress: pre_process:
helptext: Settings for the pre-compression. All clips get prepared in that way, 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 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 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', 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 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 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 the logo position. With 'use_loudnorm' you can activate single pass EBU R128
loudness normalization. 'loud_*' can adjust the loudnorm filter. [Output is loudness normalization. 'loud_*' can adjust the loudnorm filter. 'output_count'
always progressive!] 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 width: 1024
height: 576 height: 576
aspect: 1.778 aspect: 1.778
@ -54,6 +56,7 @@ pre_compress:
loud_I: -18 loud_I: -18
loud_TP: -1.5 loud_TP: -1.5
loud_LRA: 11 loud_LRA: 11
output_count: 1
playlist: playlist:
helptext: Set 'playlist_mode' to 'False' if you want to play clips from the [STORAGE] 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. 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'. 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. 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 add_text: False
over_pre: False
bind_address: "127.0.0.1:5555" bind_address: "127.0.0.1:5555"
fontfile: "/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf" fontfile: "/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf"
out: out:
helptext: The final playout post compression. Set the settings to your needs. helptext: The final playout compression. Set the settings to your needs.
'preview' works only on a desktop system with ffplay!! Set it to 'True', if 'mode' has the standard options 'desktop', 'hls', 'stream'. Self made outputs
you need it. can be define, by adding script in output folder with an 'output' function inside.
preview: False mode: 'stream'
service_name: "Live Stream" service_name: "Live Stream"
service_provider: "example.org" service_provider: "example.org"
post_ffmpeg_param: >- ffmpeg_param: >-
-c:v libx264 -c:v libx264
-crf 23 -crf 23
-x264-params keyint=50:min-keyint=25:scenecut=-1 -x264-params keyint=50:min-keyint=25:scenecut=-1
@ -108,5 +114,4 @@ out:
-ar 44100 -ar 44100
-b:a 128k -b:a 128k
-flags +global_header -flags +global_header
-f flv -f flv "rtmp://localhost/live/stream"
out_addr: "rtmp://localhost/live/stream"

View File

@ -21,13 +21,25 @@ import math
import os import os
import re import re
from .utils import _pre_comp from .utils import _pre, _text
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
# building filters, # building filters,
# when is needed add individuell filters to match output format # 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): def deinterlace_filter(probe):
""" """
when material is interlaced, when material is interlaced,
@ -50,15 +62,15 @@ def pad_filter(probe):
filter_chain = [] filter_chain = []
if not math.isclose(probe.video[0]['aspect'], if not math.isclose(probe.video[0]['aspect'],
_pre_comp.aspect, abs_tol=0.03): _pre.aspect, abs_tol=0.03):
if probe.video[0]['aspect'] < _pre_comp.aspect: if probe.video[0]['aspect'] < _pre.aspect:
filter_chain.append( filter_chain.append(
'pad=ih*{}/{}/sar:ih:(ow-iw)/2:(oh-ih)/2'.format(_pre_comp.w, 'pad=ih*{}/{}/sar:ih:(ow-iw)/2:(oh-ih)/2'.format(_pre.w,
_pre_comp.h)) _pre.h))
elif probe.video[0]['aspect'] > _pre_comp.aspect: elif probe.video[0]['aspect'] > _pre.aspect:
filter_chain.append( filter_chain.append(
'pad=iw:iw*{}/{}/sar:(ow-iw)/2:(oh-ih)/2'.format(_pre_comp.h, 'pad=iw:iw*{}/{}/sar:(ow-iw)/2:(oh-ih)/2'.format(_pre.h,
_pre_comp.w)) _pre.w))
return filter_chain return filter_chain
@ -69,8 +81,8 @@ def fps_filter(probe):
""" """
filter_chain = [] filter_chain = []
if probe.video[0]['fps'] != _pre_comp.fps: if probe.video[0]['fps'] != _pre.fps:
filter_chain.append('fps={}'.format(_pre_comp.fps)) filter_chain.append('fps={}'.format(_pre.fps))
return filter_chain return filter_chain
@ -82,13 +94,13 @@ def scale_filter(probe):
""" """
filter_chain = [] filter_chain = []
if int(probe.video[0]['width']) != _pre_comp.w or \ if int(probe.video[0]['width']) != _pre.w or \
int(probe.video[0]['height']) != _pre_comp.h: int(probe.video[0]['height']) != _pre.h:
filter_chain.append('scale={}:{}'.format(_pre_comp.w, _pre_comp.h)) filter_chain.append('scale={}:{}'.format(_pre.w, _pre.h))
if not math.isclose(probe.video[0]['aspect'], if not math.isclose(probe.video[0]['aspect'],
_pre_comp.aspect, abs_tol=0.03): _pre.aspect, abs_tol=0.03):
filter_chain.append('setdar=dar={}'.format(_pre_comp.aspect)) filter_chain.append('setdar=dar={}'.format(_pre.aspect))
return filter_chain 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 ad is comming next fade logo out,
when clip before was an ad fade logo in when clip before was an ad fade logo in
""" """
logo_filter = '[v]null[logo]' logo_filter = '[v]null'
scale_filter = '' 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 = [] logo_chain = []
if _pre_comp.logo_scale and \ if _pre.logo_scale and \
re.match(r'\d+:-?\d+', _pre_comp.logo_scale): re.match(r'\d+:-?\d+', _pre.logo_scale):
scale_filter = 'scale={},'.format(_pre_comp.logo_scale) scale_filter = 'scale={},'.format(_pre.logo_scale)
logo_extras = 'format=rgba,{}colorchannelmixer=aa={}'.format( 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' loop = 'loop=loop=-1:size=1:start=0'
logo_chain.append( logo_chain.append(
'movie={},{},{}'.format(_pre_comp.logo, loop, logo_extras)) 'movie={},{},{}'.format(_pre.logo, loop, logo_extras))
if ad_last: if ad_last:
logo_chain.append('fade=in:st=0:d=1.0:alpha=1') logo_chain.append('fade=in:st=0:d=1.0:alpha=1')
if ad_next: if ad_next:
logo_chain.append('fade=out:st={}:d=1.0:alpha=1'.format( logo_chain.append('fade=out:st={}:d=1.0:alpha=1'.format(
duration - 1)) duration - 1))
logo_filter = '{}[l];[v][l]{}:shortest=1[logo]'.format( logo_filter = '{}[l];[v][l]{}:shortest=1'.format(
','.join(logo_chain), _pre_comp.logo_filter) ','.join(logo_chain), _pre.logo_filter)
return logo_filter return logo_filter
@ -161,9 +173,9 @@ def add_loudnorm(probe):
""" """
loud_filter = [] loud_filter = []
if probe.audio and _pre_comp.add_loudnorm: if probe.audio and _pre.add_loudnorm:
loud_filter = [('loudnorm=I={}:TP={}:LRA={}').format( 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 return loud_filter
@ -175,7 +187,7 @@ def extend_audio(probe, duration):
pad_filter = [] pad_filter = []
if probe.audio and 'duration' in probe.audio[0] and \ 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)) pad_filter.append('apad=whole_dur={}'.format(duration))
return pad_filter return pad_filter
@ -189,25 +201,45 @@ def extend_video(probe, duration, target_duration):
if 'duration' in probe.video[0] and \ if 'duration' in probe.video[0] and \
target_duration < duration > float( 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( pad_filter.append('tpad=stop_mode=add:stop_duration={}'.format(
duration - float(probe.video[0]['duration']))) duration - float(probe.video[0]['duration'])))
return pad_filter 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): def build_filtergraph(duration, seek, out, ad, ad_last, ad_next, probe, msg):
""" """
build final filter graph, with video and audio chain build final filter graph, with video and audio chain
""" """
video_chain = [] video_chain = []
audio_chain = [] audio_chain = []
video_map = ['-map', '[logo]']
if out > duration: if out > duration:
seek = 0 seek = 0
if probe.video[0]: if probe.video[0]:
video_chain += text_filter()
video_chain += deinterlace_filter(probe) video_chain += deinterlace_filter(probe)
video_chain += pad_filter(probe) video_chain += pad_filter(probe)
video_chain += fps_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]' video_filter = 'null[v]'
logo_filter = overlay_filter(out - seek, ad, ad_last, ad_next) logo_filter = overlay_filter(out - seek, ad, ad_last, ad_next)
v_split = split_filter('v')
video_map = ['-map', '[vout1]']
video_filter = [ video_filter = [
'-filter_complex', '[0:v]{};{}'.format( '-filter_complex', '[0:v]{};{}{}'.format(
video_filter, logo_filter)] video_filter, logo_filter, v_split)]
if audio_chain: a_split = split_filter('a')
audio_filter = [ audio_map = ['-map', '[aout1]']
'-filter_complex', '{}[a]'.format(','.join(audio_chain))] audio_filter = [
audio_map = ['-map', '[a]'] '-filter_complex', '{}{}'.format(','.join(audio_chain), a_split)]
else:
audio_filter = []
audio_map = ['-map', '0:a']
if probe.video[0]: if probe.video[0]:
return video_filter + audio_filter + video_map + audio_map return video_filter + audio_filter + video_map + audio_map

View File

104
ffplayout/output/desktop.py Normal file
View File

@ -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()

72
ffplayout/output/hls.py Normal file
View File

@ -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()

111
ffplayout/output/stream.py Normal file
View File

@ -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()

View File

@ -224,7 +224,7 @@ class GetSourceFromPlaylist:
if self.clip_nodes is None: if self.clip_nodes is None:
self.eof_handling( 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 yield self.src_cmd + self.filtergraph
continue continue

View File

@ -113,7 +113,7 @@ def get_time(time_format):
_general = SimpleNamespace() _general = SimpleNamespace()
_mail = SimpleNamespace() _mail = SimpleNamespace()
_log = SimpleNamespace() _log = SimpleNamespace()
_pre_comp = SimpleNamespace() _pre = SimpleNamespace()
_playlist = SimpleNamespace() _playlist = SimpleNamespace()
_storage = SimpleNamespace() _storage = SimpleNamespace()
_text = SimpleNamespace() _text = SimpleNamespace()
@ -178,15 +178,16 @@ def load_config():
_mail.recip = cfg['mail']['recipient'] _mail.recip = cfg['mail']['recipient']
_mail.level = cfg['mail']['mail_level'] _mail.level = cfg['mail']['mail_level']
_pre_comp.add_logo = cfg['pre_compress']['add_logo'] _pre.add_logo = cfg['pre_process']['add_logo']
_pre_comp.logo = cfg['pre_compress']['logo'] _pre.logo = cfg['pre_process']['logo']
_pre_comp.logo_scale = cfg['pre_compress']['logo_scale'] _pre.logo_scale = cfg['pre_process']['logo_scale']
_pre_comp.logo_filter = cfg['pre_compress']['logo_filter'] _pre.logo_filter = cfg['pre_process']['logo_filter']
_pre_comp.logo_opacity = cfg['pre_compress']['logo_opacity'] _pre.logo_opacity = cfg['pre_process']['logo_opacity']
_pre_comp.add_loudnorm = cfg['pre_compress']['add_loudnorm'] _pre.add_loudnorm = cfg['pre_process']['add_loudnorm']
_pre_comp.loud_i = cfg['pre_compress']['loud_I'] _pre.loud_i = cfg['pre_process']['loud_I']
_pre_comp.loud_tp = cfg['pre_compress']['loud_TP'] _pre.loud_tp = cfg['pre_process']['loud_TP']
_pre_comp.loud_lra = cfg['pre_compress']['loud_LRA'] _pre.loud_lra = cfg['pre_process']['loud_LRA']
_pre.output_count = cfg['pre_process']['output_count']
_playlist.mode = cfg['playlist']['playlist_mode'] _playlist.mode = cfg['playlist']['playlist_mode']
_playlist.path = cfg['playlist']['path'] _playlist.path = cfg['playlist']['path']
@ -199,6 +200,7 @@ def load_config():
_storage.shuffle = cfg['storage']['shuffle'] _storage.shuffle = cfg['storage']['shuffle']
_text.add_text = cfg['text']['add_text'] _text.add_text = cfg['text']['add_text']
_text.over_pre = cfg['text']['over_pre']
_text.address = cfg['text']['bind_address'] _text.address = cfg['text']['bind_address']
_text.fontfile = cfg['text']['fontfile'] _text.fontfile = cfg['text']['fontfile']
@ -209,18 +211,17 @@ def load_config():
_log.level = cfg['logging']['log_level'] _log.level = cfg['logging']['log_level']
_log.ff_level = cfg['logging']['ffmpeg_level'] _log.ff_level = cfg['logging']['ffmpeg_level']
_pre_comp.w = cfg['pre_compress']['width'] _pre.w = cfg['pre_process']['width']
_pre_comp.h = cfg['pre_compress']['height'] _pre.h = cfg['pre_process']['height']
_pre_comp.aspect = cfg['pre_compress']['aspect'] _pre.aspect = cfg['pre_process']['aspect']
_pre_comp.fps = cfg['pre_compress']['fps'] _pre.fps = cfg['pre_process']['fps']
_pre_comp.v_bitrate = cfg['pre_compress']['width'] * 50 _pre.v_bitrate = cfg['pre_process']['width'] * 50
_pre_comp.v_bufsize = cfg['pre_compress']['width'] * 50 / 2 _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.name = cfg['out']['service_name']
_playout.provider = cfg['out']['service_provider'] _playout.provider = cfg['out']['service_provider']
_playout.post_comp_param = cfg['out']['post_ffmpeg_param'].split(' ') _playout.ffmpeg_param = cfg['out']['ffmpeg_param'].split(' ')
_playout.out_addr = cfg['out']['out_addr']
_init.load = False _init.load = False
@ -782,7 +783,7 @@ def gen_dummy(duration):
return [ return [
'-f', 'lavfi', '-i', '-f', 'lavfi', '-i',
'color=c={}:s={}x{}:d={}:r={},format=pix_fmts=yuv420p'.format( '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( '-f', 'lavfi', '-i', 'anoisesrc=d={}:c=pink:r=48000:a=0.05'.format(
duration) duration)
@ -989,7 +990,7 @@ def pre_audio_codec():
s302m has higher quality, but is experimental s302m has higher quality, but is experimental
and works not well together with the loudnorm filter 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' acodec = 'libtwolame' if 'libtwolame' in FF_LIBS['libs'] else 'mp2'
return ['-c:a', acodec, '-b:a', '384k', '-ar', '48000', '-ac', '2'] return ['-c:a', acodec, '-b:a', '384k', '-ar', '48000', '-ac', '2']
else: else: