pipe uncompressed stream, add gen_input function, clean function
This commit is contained in:
parent
bac14b82d4
commit
056009933f
@ -40,15 +40,11 @@ log_level = INFO
|
|||||||
# output settings for the pre-compression
|
# output settings for the pre-compression
|
||||||
# all clips get prepared in that way,
|
# all clips get prepared in that way,
|
||||||
# so the input for the final compression is unique
|
# so the input for the final compression is unique
|
||||||
# it produce a mpeg2 ts stream
|
# it produce a uncompressed avi stream
|
||||||
|
|
||||||
# bitrate is in kbit/s
|
|
||||||
[PRE_COMPRESS]
|
[PRE_COMPRESS]
|
||||||
width = 1024
|
width = 1024
|
||||||
height = 576
|
height = 576
|
||||||
fps = 25
|
fps = 25
|
||||||
v_bitrate = 15000
|
|
||||||
a_bitrate = 256
|
|
||||||
a_sample = 44100
|
a_sample = 44100
|
||||||
|
|
||||||
|
|
||||||
@ -60,7 +56,8 @@ a_sample = 44100
|
|||||||
# strings in playlist must have ampersan (&) as: &
|
# strings in playlist must have ampersan (&) as: &
|
||||||
|
|
||||||
# day_start means at witch hour starts the day, as integer
|
# day_start means at witch hour starts the day, as integer
|
||||||
# filler_path and filler_clip are for the GUI only at the moment
|
# filler_path are for the GUI only at the moment
|
||||||
|
# filler_clip get handle different, when a new length needs to calculate
|
||||||
[PLAYLIST]
|
[PLAYLIST]
|
||||||
playlist_path = /playlists
|
playlist_path = /playlists
|
||||||
clips_root = /ADtvMedia
|
clips_root = /ADtvMedia
|
||||||
@ -77,7 +74,10 @@ day_start = 6
|
|||||||
# buffer_length: length in seconds of the buffer
|
# buffer_length: length in seconds of the buffer
|
||||||
# this is the time what the playout have, to change from one clip to the next
|
# this is the time what the playout have, to change from one clip to the next
|
||||||
# be liberal with this value but dont exaggerate
|
# be liberal with this value but dont exaggerate
|
||||||
# buffer size gets calculate with: (v_bitrate + a_bitrate) * 0.125 * buffer_length
|
# buffer size gets calculate with:
|
||||||
|
# (width x height * 3 / 2 * fps * buffer_length / 1024) + ((a_sample * 16 * 2 * buffer_length) / 8 / 1024)
|
||||||
|
# uncompressed audio and video (yuv420p), solution is in KB
|
||||||
|
|
||||||
|
|
||||||
# buffer_cli: the prefert buffer tool, needs to be installed on your system
|
# buffer_cli: the prefert buffer tool, needs to be installed on your system
|
||||||
# buffer_cmd: need to end with the buffer size command, full command would look:
|
# buffer_cmd: need to end with the buffer size command, full command would look:
|
||||||
|
256
ffplayout.py
256
ffplayout.py
@ -66,15 +66,13 @@ _pre_comp = SimpleNamespace(
|
|||||||
aspect=cfg.getfloat('PRE_COMPRESS', 'width') /
|
aspect=cfg.getfloat('PRE_COMPRESS', 'width') /
|
||||||
cfg.getfloat('PRE_COMPRESS', 'height'),
|
cfg.getfloat('PRE_COMPRESS', 'height'),
|
||||||
fps=cfg.getint('PRE_COMPRESS', 'fps'),
|
fps=cfg.getint('PRE_COMPRESS', 'fps'),
|
||||||
v_bitrate=cfg.getint('PRE_COMPRESS', 'v_bitrate'),
|
a_sample=cfg.getint('PRE_COMPRESS', 'a_sample')
|
||||||
v_bufsize=cfg.getint('PRE_COMPRESS', 'v_bitrate'),
|
|
||||||
a_bitrate=cfg.getint('PRE_COMPRESS', 'a_bitrate'),
|
|
||||||
a_sample=cfg.getint('PRE_COMPRESS', 'a_sample'),
|
|
||||||
)
|
)
|
||||||
|
|
||||||
_playlist = SimpleNamespace(
|
_playlist = SimpleNamespace(
|
||||||
path=cfg.get('PLAYLIST', 'playlist_path'),
|
path=cfg.get('PLAYLIST', 'playlist_path'),
|
||||||
start=cfg.getint('PLAYLIST', 'day_start')
|
start=cfg.getint('PLAYLIST', 'day_start'),
|
||||||
|
filler=cfg.get('PLAYLIST', 'filler_clip')
|
||||||
)
|
)
|
||||||
|
|
||||||
_buffer = SimpleNamespace(
|
_buffer = SimpleNamespace(
|
||||||
@ -94,7 +92,7 @@ _playout = SimpleNamespace(
|
|||||||
|
|
||||||
# set logo filtergraph
|
# set logo filtergraph
|
||||||
if path.exists(cfg.get('OUT', 'logo')):
|
if path.exists(cfg.get('OUT', 'logo')):
|
||||||
_playout.logo = ['-thread_queue_size', '512', '-i', cfg.get('OUT', 'logo')]
|
_playout.logo = ['-thread_queue_size', '16', '-i', cfg.get('OUT', 'logo')]
|
||||||
_playout.filter = [
|
_playout.filter = [
|
||||||
'-filter_complex', '[0:v][1:v]' + cfg.get('OUT', 'logo_o') + '[o]',
|
'-filter_complex', '[0:v][1:v]' + cfg.get('OUT', 'logo_o') + '[o]',
|
||||||
'-map', '[o]', '-map', '0:a'
|
'-map', '[o]', '-map', '0:a'
|
||||||
@ -157,7 +155,9 @@ def get_time(time_format):
|
|||||||
if time_format == 'hour':
|
if time_format == 'hour':
|
||||||
return t.hour
|
return t.hour
|
||||||
elif time_format == 'full_sec':
|
elif time_format == 'full_sec':
|
||||||
return t.hour * 3600 + t.minute * 60 + t.second
|
sec = float(t.hour * 3600 + t.minute * 60 + t.second)
|
||||||
|
micro = float(t.microsecond) / 1000000
|
||||||
|
return sec + micro
|
||||||
else:
|
else:
|
||||||
return t.strftime("%H:%M:%S")
|
return t.strftime("%H:%M:%S")
|
||||||
|
|
||||||
@ -192,7 +192,9 @@ def mail_or_log(message, time, path):
|
|||||||
|
|
||||||
# calculating the size for the buffer in KB
|
# calculating the size for the buffer in KB
|
||||||
def calc_buffer_size():
|
def calc_buffer_size():
|
||||||
return (_pre_comp.v_bitrate + _pre_comp.a_bitrate) * 0.125 * _buffer.length
|
v_size = _pre_comp.w * _pre_comp.h * 3 / 2 * _pre_comp.fps * _buffer.length
|
||||||
|
a_size = (_pre_comp.a_sample * 16 * 2 * _buffer.length) / 8
|
||||||
|
return (v_size + a_size) / 1024
|
||||||
|
|
||||||
|
|
||||||
# check if processes a well
|
# check if processes a well
|
||||||
@ -252,62 +254,66 @@ def gen_dummy(duration):
|
|||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
# prepare input clip
|
# when source path exist, generate input with seek and out time
|
||||||
def prepare_input(src, duration, seek, out):
|
# when path not exist, get dummy clip
|
||||||
|
def src_or_dummy(src, duration, seek, out):
|
||||||
if check_file_exist(src):
|
if check_file_exist(src):
|
||||||
if seek > 0.0 or out < duration:
|
if seek > 0.0 or out < duration:
|
||||||
src_cmd = seek_in_cut_end(src, duration, seek, out)
|
return seek_in_cut_end(src, duration, seek, out)
|
||||||
else:
|
else:
|
||||||
src_cmd = ['-i', src]
|
return ['-i', src]
|
||||||
else:
|
else:
|
||||||
if seek > 0.0:
|
return gen_dummy(out - seek)
|
||||||
duration = duration - seek
|
|
||||||
if out < duration:
|
|
||||||
duration = duration - out
|
|
||||||
src_cmd = gen_dummy(duration)
|
|
||||||
return src_cmd
|
|
||||||
|
|
||||||
|
|
||||||
# last clip can be a filler
|
# prepare input clip
|
||||||
# so we get the IN point and calculate the new duration
|
# check begin and length from clip
|
||||||
# if the new duration is smaller then 6 sec put a blank clip
|
# return clip only if we are in 24 hours time range
|
||||||
# we have to validate here to, that a last clip exists in the playlist
|
def gen_input(src, begin, duration, seek, out, last):
|
||||||
def prepare_last_clip(clip_nodes):
|
test_time = 86400.0
|
||||||
if clip_nodes:
|
start_time = float(_playlist.start * 3600)
|
||||||
last_node = clip_nodes[-1]
|
|
||||||
src = last_node.get('src')
|
if begin + out - seek > test_time:
|
||||||
begin = is_float(last_node.get('begin'), get_time('full_sec'), True)
|
begin -= start_time
|
||||||
duration = is_float(last_node.get('dur'), 300, True)
|
|
||||||
seek = is_float(last_node.get('in'), 0, True)
|
playlist_length = begin + out - seek
|
||||||
out = is_float(last_node.get('out'), 300, True)
|
|
||||||
first = False
|
if playlist_length <= test_time and not last:
|
||||||
last = True
|
# when we are in the 24 houre range, get the clip
|
||||||
else:
|
return src_or_dummy(src, duration, seek, out)
|
||||||
src = None
|
elif playlist_length < test_time and last:
|
||||||
begin = get_time('full_sec')
|
# when last clip is passed and we still have too much time left
|
||||||
duration = 300.0
|
diff = test_time - playlist_length
|
||||||
seek = 0.0
|
|
||||||
out = duration
|
if duration < diff:
|
||||||
first = True
|
|
||||||
last = False
|
|
||||||
mail_or_log(
|
mail_or_log(
|
||||||
'Playlist has no valid entries!',
|
'Last clip is not long enough', get_time(None),
|
||||||
get_time(None), get_date(True)
|
str(diff - duration) + ' seconds are missing.'
|
||||||
)
|
)
|
||||||
|
src_cmd = src_or_dummy(src, duration, 0, duration)
|
||||||
tmp_dur = out - seek
|
|
||||||
|
|
||||||
if tmp_dur > 6.0:
|
|
||||||
if check_file_exist(src):
|
|
||||||
src_cmd = seek_in_cut_end(src, duration, seek, out)
|
|
||||||
else:
|
else:
|
||||||
src_cmd = gen_dummy(tmp_dur)
|
if src == _playlist.filler:
|
||||||
elif tmp_dur > 1.0:
|
src_cmd = src_or_dummy(src, duration, duration - diff, out)
|
||||||
src_cmd = gen_dummy(tmp_dur)
|
else:
|
||||||
|
src_cmd = src_or_dummy(src, duration, 0, duration - diff)
|
||||||
|
else:
|
||||||
|
# when we over the 24 hours range,
|
||||||
|
# calculate time and try to correct them
|
||||||
|
diff = playlist_length - test_time
|
||||||
|
new_len = out - seek - diff
|
||||||
|
if new_len > 6.0:
|
||||||
|
if src == _playlist.filler:
|
||||||
|
# when filler is something like a clock,
|
||||||
|
# is better to start the clip later, to play until end
|
||||||
|
src_cmd = src_or_dummy(src, duration, seek + diff, out)
|
||||||
|
else:
|
||||||
|
src_cmd = src_or_dummy(src, duration, seek, out - diff)
|
||||||
|
elif new_len > 1.0:
|
||||||
|
src_cmd = gen_dummy(new_len)
|
||||||
else:
|
else:
|
||||||
src_cmd = None
|
src_cmd = None
|
||||||
|
|
||||||
return src_cmd, begin, first, last
|
return src_cmd
|
||||||
|
|
||||||
|
|
||||||
# test if value is float
|
# test if value is float
|
||||||
@ -322,9 +328,10 @@ def is_float(value, text, convert):
|
|||||||
return text
|
return text
|
||||||
|
|
||||||
|
|
||||||
# check all variables in xml playlist
|
# validate xml values in new Thread
|
||||||
# and test if file path exist
|
# and test if file path exist
|
||||||
def validate_xml(xml_nodes):
|
def validate_thread(clip_nodes):
|
||||||
|
def check_xml(xml_nodes):
|
||||||
error = ''
|
error = ''
|
||||||
|
|
||||||
for xml_node in xml_nodes:
|
for xml_node in xml_nodes:
|
||||||
@ -348,6 +355,24 @@ def validate_xml(xml_nodes):
|
|||||||
get_time(None), error
|
get_time(None), error
|
||||||
)
|
)
|
||||||
|
|
||||||
|
validate = Thread(name='check_xml', target=check_xml, args=(clip_nodes,))
|
||||||
|
validate.daemon = True
|
||||||
|
validate.start()
|
||||||
|
|
||||||
|
|
||||||
|
def exeption(last_time, message, path):
|
||||||
|
src_cmd = gen_dummy(300)
|
||||||
|
# there is still material in the buffer,
|
||||||
|
# so we have to calculate the right seek time for the new playlist
|
||||||
|
if last_time > 86400:
|
||||||
|
last_time -= 86400
|
||||||
|
|
||||||
|
time_diff = last_time - get_time('full_sec')
|
||||||
|
last_time = get_time('full_sec')
|
||||||
|
mail_or_log(message, get_time(None), path)
|
||||||
|
|
||||||
|
return src_cmd, last_time, time_diff
|
||||||
|
|
||||||
|
|
||||||
# ------------------------------------------------------------------------------
|
# ------------------------------------------------------------------------------
|
||||||
# main functions
|
# main functions
|
||||||
@ -361,23 +386,10 @@ def iter_src_commands():
|
|||||||
last_mod_time = 0.0
|
last_mod_time = 0.0
|
||||||
time_diff = 0.0
|
time_diff = 0.0
|
||||||
first = True
|
first = True
|
||||||
last = False
|
|
||||||
time_difference = 0.0
|
|
||||||
|
|
||||||
while True:
|
|
||||||
# switch playlist after last clip from day befor
|
|
||||||
if last:
|
|
||||||
if time_difference > float(_buffer.length):
|
|
||||||
# wait to sync time
|
|
||||||
wait = time_difference - float(_buffer.length)
|
|
||||||
logger.info('Wait for: ' + str(wait) + ' seconds.')
|
|
||||||
sleep(wait)
|
|
||||||
list_date = get_date(False)
|
|
||||||
last = False
|
|
||||||
else:
|
|
||||||
list_date = get_date(True)
|
list_date = get_date(True)
|
||||||
|
|
||||||
year, month, _day = re.split('-', list_date)
|
while True:
|
||||||
|
year, month, day = re.split('-', list_date)
|
||||||
xml_path = path.join(_playlist.path, year, month, list_date + '.xml')
|
xml_path = path.join(_playlist.path, year, month, list_date + '.xml')
|
||||||
|
|
||||||
if check_file_exist(xml_path):
|
if check_file_exist(xml_path):
|
||||||
@ -388,74 +400,77 @@ def iter_src_commands():
|
|||||||
clip_nodes = xml_root.findall('body/video')
|
clip_nodes = xml_root.findall('body/video')
|
||||||
last_mod_time = mod_time
|
last_mod_time = mod_time
|
||||||
logger.info('open: ' + xml_path)
|
logger.info('open: ' + xml_path)
|
||||||
|
validate_thread(clip_nodes)
|
||||||
# validate xml values in new Thread
|
last_node = clip_nodes[-1]
|
||||||
validate_thread = Thread(
|
|
||||||
name='validate_xml', target=validate_xml, args=(
|
|
||||||
clip_nodes,
|
|
||||||
)
|
|
||||||
)
|
|
||||||
validate_thread.daemon = True
|
|
||||||
validate_thread.start()
|
|
||||||
|
|
||||||
# all clips in playlist except last one
|
# all clips in playlist except last one
|
||||||
for clip_node in clip_nodes[:-1]:
|
for clip_node in clip_nodes:
|
||||||
src = clip_node.get('src')
|
src = clip_node.get('src')
|
||||||
begin = is_float(clip_node.get('begin'), last_time, True)
|
begin = is_float(clip_node.get('begin'), last_time, True)
|
||||||
duration = is_float(clip_node.get('dur'), 300, True)
|
duration = is_float(clip_node.get('dur'), 300, True)
|
||||||
seek = is_float(clip_node.get('in'), 0, True)
|
seek = is_float(clip_node.get('in'), 0, True)
|
||||||
out = is_float(clip_node.get('out'), 300, True)
|
out = is_float(clip_node.get('out'), 300, True)
|
||||||
|
|
||||||
if first:
|
|
||||||
# first time we end up here
|
# first time we end up here
|
||||||
if last_time < begin + duration:
|
if first and last_time + time_diff < begin + duration:
|
||||||
# calculate seek time
|
# calculate seek time
|
||||||
init_seek = last_time - begin + seek + time_diff
|
seek = last_time - begin + seek + time_diff
|
||||||
src_cmd = prepare_input(src, duration, init_seek, out)
|
src_cmd = gen_input(
|
||||||
|
src, begin, duration, seek, out, False
|
||||||
|
)
|
||||||
|
|
||||||
time_diff = 0.0
|
time_diff = 0.0
|
||||||
first = False
|
first = False
|
||||||
|
|
||||||
last_time = begin
|
last_time = begin
|
||||||
break
|
break
|
||||||
else:
|
elif last_time < begin:
|
||||||
if last_time < begin:
|
if clip_node == last_node:
|
||||||
src_cmd = prepare_input(src, duration, seek, out)
|
# after last item is loaded,
|
||||||
last_time = begin
|
# set right values for new playlist
|
||||||
break
|
list_date = get_date(False)
|
||||||
else:
|
|
||||||
# last clip in playlist
|
|
||||||
src_cmd, begin, first, last = prepare_last_clip(clip_nodes)
|
|
||||||
|
|
||||||
if last_time > 86400:
|
|
||||||
begin -= 86400.0
|
|
||||||
|
|
||||||
# calculate real time in buffer
|
|
||||||
time_difference = begin - get_time('full_sec')
|
|
||||||
last_time = float(_playlist.start * 3600 - 5)
|
last_time = float(_playlist.start * 3600 - 5)
|
||||||
|
|
||||||
list_date = get_date(True)
|
|
||||||
last_mod_time = 0.0
|
last_mod_time = 0.0
|
||||||
|
last = True
|
||||||
|
else:
|
||||||
|
last_time = begin
|
||||||
|
last = False
|
||||||
|
|
||||||
|
t_dist = begin - get_time('full_sec')
|
||||||
|
if 0 <= get_time('full_sec') < _playlist.start * 3600:
|
||||||
|
t_dist -= 86400
|
||||||
|
|
||||||
|
# check that we are in tolerance time
|
||||||
|
if not _buffer.length - 8 < t_dist < _buffer.length + 8:
|
||||||
|
mail_or_log(
|
||||||
|
'Playlist is not sync!', get_time(None),
|
||||||
|
str(t_dist) + ' seconds async.'
|
||||||
|
)
|
||||||
|
|
||||||
|
src_cmd = gen_input(
|
||||||
|
src, begin, duration, seek, out, last
|
||||||
|
)
|
||||||
|
|
||||||
|
break
|
||||||
|
else:
|
||||||
|
# when playlist exist but is empty, or not long enough,
|
||||||
|
# generate dummy and send log
|
||||||
|
src_cmd, last_time, time_diff = exeption(
|
||||||
|
last_time, 'Playlist is not valid!', xml_path
|
||||||
|
)
|
||||||
|
|
||||||
|
first = True
|
||||||
|
last_mod_time = 0.0
|
||||||
|
|
||||||
else:
|
else:
|
||||||
# when we have no playlist for the current day,
|
# when we have no playlist for the current day,
|
||||||
# then we generate a black clip
|
# then we generate a black clip
|
||||||
# and calculate the seek in time, for when the playlist comes back
|
# and calculate the seek in time, for when the playlist comes back
|
||||||
src_cmd = gen_dummy(300)
|
src_cmd, last_time, time_diff = exeption(
|
||||||
last_time += 300
|
last_time, 'Playlist not exist:', xml_path
|
||||||
last_mod_time = 0.0
|
)
|
||||||
|
|
||||||
mail_or_log('Playlist not exist:', get_time(None), xml_path)
|
|
||||||
|
|
||||||
# there is still material in the buffer,
|
|
||||||
# so we have to calculate the right seek time for the new playlist
|
|
||||||
# time_diff: is the real time what we have in buffer
|
|
||||||
if last_time > 86400:
|
|
||||||
time_val = last_time - 86400
|
|
||||||
else:
|
|
||||||
time_val = last_time
|
|
||||||
|
|
||||||
time_diff = time_val - get_time('full_sec')
|
|
||||||
first = True
|
first = True
|
||||||
|
last_mod_time = 0.0
|
||||||
|
|
||||||
if src_cmd is not None:
|
if src_cmd is not None:
|
||||||
yield src_cmd, last_time
|
yield src_cmd, last_time
|
||||||
@ -483,14 +498,10 @@ def play_clips(out_file, iter_src_commands):
|
|||||||
'-aspect', str(_pre_comp.aspect),
|
'-aspect', str(_pre_comp.aspect),
|
||||||
'-pix_fmt', 'yuv420p', '-r', str(_pre_comp.fps),
|
'-pix_fmt', 'yuv420p', '-r', str(_pre_comp.fps),
|
||||||
'-af', 'apad', '-shortest',
|
'-af', 'apad', '-shortest',
|
||||||
'-c:v', 'mpeg2video', '-g', '12', '-bf', '2',
|
'-c:v', 'rawvideo',
|
||||||
'-b:v', '{}k'.format(_pre_comp.v_bitrate),
|
'-c:a', 'pcm_s16le',
|
||||||
'-minrate', '{}k'.format(_pre_comp.v_bitrate),
|
|
||||||
'-maxrate', '{}k'.format(_pre_comp.v_bitrate),
|
|
||||||
'-bufsize', '{}k'.format(_pre_comp.v_bufsize),
|
|
||||||
'-c:a', 'mp2', '-b:a', '{}k'.format(_pre_comp.a_bitrate),
|
|
||||||
'-ar', str(_pre_comp.a_sample), '-ac', '2',
|
'-ar', str(_pre_comp.a_sample), '-ac', '2',
|
||||||
'-threads', '2', '-f', 'mpegts', '-'
|
'-threads', '2', '-f', 'avi', '-'
|
||||||
],
|
],
|
||||||
stdout=PIPE,
|
stdout=PIPE,
|
||||||
bufsize=0
|
bufsize=0
|
||||||
@ -519,8 +530,7 @@ def main():
|
|||||||
playout = Popen(
|
playout = Popen(
|
||||||
[
|
[
|
||||||
'ffmpeg', '-v', 'info', '-hide_banner', '-nostats', '-re',
|
'ffmpeg', '-v', 'info', '-hide_banner', '-nostats', '-re',
|
||||||
'-fflags', '+igndts', '-thread_queue_size', '512',
|
'-thread_queue_size', '256', '-i', 'pipe:0'
|
||||||
'-i', 'pipe:0', '-fflags', '+genpts'
|
|
||||||
] +
|
] +
|
||||||
list(_playout.logo) +
|
list(_playout.logo) +
|
||||||
list(_playout.filter) +
|
list(_playout.filter) +
|
||||||
|
Loading…
Reference in New Issue
Block a user