From bac14b82d41de5d6b9ebfaa4f47bdc15cba8c3e2 Mon Sep 17 00:00:00 2001 From: jb-alvarado Date: Tue, 13 Feb 2018 14:23:34 +0100 Subject: [PATCH] xml validation, handle empty playlist --- README.md | 7 +-- ffplayout.conf | 4 +- ffplayout.py | 162 +++++++++++++++++++++++++++++++++++-------------- 3 files changed, 119 insertions(+), 54 deletions(-) diff --git a/README.md b/README.md index dab909aa..7464fe93 100644 --- a/README.md +++ b/README.md @@ -11,7 +11,6 @@ Features ----- - have all values in a separate config file -- try to be as simple as possible - dynamic playlist - replace missing playlist or clip with a blank clip - send emails with error message @@ -64,7 +63,5 @@ Installation TODO ----- -- better xml validation -- time sync check and correction -- check empty playlist -- check when clip or playlist got lost while playling (?) +- time sync correction +- check when clip or playlist got lost while playling (maybe not possible) diff --git a/ffplayout.conf b/ffplayout.conf index fa14939e..8c612366 100644 --- a/ffplayout.conf +++ b/ffplayout.conf @@ -58,7 +58,6 @@ a_sample = 44100 # subfolders needs this structur: # "/playlists/2018/01" (/playlists/year/month) # strings in playlist must have ampersan (&) as: & -# playlist format is smil xml # day_start means at witch hour starts the day, as integer # filler_path and filler_clip are for the GUI only at the moment @@ -76,10 +75,9 @@ day_start = 6 # without interrupt the stream # buffer_length: length in seconds of the buffer -# (this is not accurate at the moment, the real length is maybe match more) # this is the time what the playout have, to change from one clip to the next # be liberal with this value but dont exaggerate -# buffer size gets calculate with: (v_bitrate + a_bitrate) * buffer_length +# buffer size gets calculate with: (v_bitrate + a_bitrate) * 0.125 * buffer_length # 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: diff --git a/ffplayout.py b/ffplayout.py index ec5a730c..5f5b1980 100755 --- a/ffplayout.py +++ b/ffplayout.py @@ -172,7 +172,7 @@ def get_date(seek_day): # send error messages to email addresses -def send_mail(message, time, path): +def mail_or_log(message, time, path): if _mail.recip: msg = MIMEMultipart() msg['From'] = _mail.s_addr @@ -190,9 +190,9 @@ def send_mail(message, time, path): logger.error('{} {}'.format(message, path)) -# calculating the size for the buffer in bytes +# calculating the size for the buffer in KB def calc_buffer_size(): - return (_pre_comp.v_bitrate + _pre_comp.a_bitrate) * _buffer.length + return (_pre_comp.v_bitrate + _pre_comp.a_bitrate) * 0.125 * _buffer.length # check if processes a well @@ -210,13 +210,12 @@ def check_file_exist(in_file): if path.exists(in_file): return True else: - send_mail('File not exist:', get_time(None), in_file) return False # seek in clip and cut the end def seek_in_cut_end(in_file, duration, seek, out): - if seek > 0.00: + if seek > 0.0: inpoint = ['-ss', str(seek)] fade_in_vid = 'fade=in:st=0:d=0.5' fade_in_aud = 'afade=in:st=0:d=0.5' @@ -256,12 +255,12 @@ def gen_dummy(duration): # prepare input clip def prepare_input(src, duration, seek, out): if check_file_exist(src): - if seek > 0.00 or out < duration: + if seek > 0.0 or out < duration: src_cmd = seek_in_cut_end(src, duration, seek, out) else: src_cmd = ['-i', src] else: - if seek > 0.00: + if seek > 0.0: duration = duration - seek if out < duration: duration = duration - out @@ -272,24 +271,82 @@ def prepare_input(src, duration, seek, out): # last clip can be a filler # so we get the IN point and calculate the new duration # if the new duration is smaller then 6 sec put a blank clip -def prepare_last_clip(in_node): - src = in_node.get('src') - duration = float(in_node.get('dur')) - seek = float(in_node.get('in')) - out = float(in_node.get('out')) - tmp_dur = duration - seek +# we have to validate here to, that a last clip exists in the playlist +def prepare_last_clip(clip_nodes): + if clip_nodes: + last_node = clip_nodes[-1] + src = last_node.get('src') + begin = is_float(last_node.get('begin'), get_time('full_sec'), True) + duration = is_float(last_node.get('dur'), 300, True) + seek = is_float(last_node.get('in'), 0, True) + out = is_float(last_node.get('out'), 300, True) + first = False + last = True + else: + src = None + begin = get_time('full_sec') + duration = 300.0 + seek = 0.0 + out = duration + first = True + last = False + mail_or_log( + 'Playlist has no valid entries!', + get_time(None), get_date(True) + ) - if tmp_dur > 6.00: + 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: src_cmd = gen_dummy(tmp_dur) - elif tmp_dur > 1.00: + elif tmp_dur > 1.0: src_cmd = gen_dummy(tmp_dur) else: src_cmd = None - return src_cmd + return src_cmd, begin, first, last + + +# test if value is float +def is_float(value, text, convert): + try: + float(value) + if convert: + return float(value) + else: + return '' + except ValueError: + return text + + +# check all variables in xml playlist +# and test if file path exist +def validate_xml(xml_nodes): + error = '' + + for xml_node in xml_nodes: + if check_file_exist(xml_node.get('src')): + a = '' + else: + a = 'File not exist! ' + + b = is_float(xml_node.get('begin'), 'No Start Time! ', False) + c = is_float(xml_node.get('dur'), 'No Duration! ', False) + d = is_float(xml_node.get('in'), 'No In Value! ', False) + e = is_float(xml_node.get('out'), 'No Out Value! ', False) + + line = a + b + c + d + e + if line: + error += line + 'In line: ' + str(xml_node.attrib) + '\n' + + if error: + mail_or_log( + 'Validation error, check xml playlist, values are missing:\n', + get_time(None), error + ) # ------------------------------------------------------------------------------ @@ -301,22 +358,22 @@ def iter_src_commands(): last_time = get_time('full_sec') if 0 <= last_time < _playlist.start * 3600: last_time += 86400 - last_mod_time = 0.00 - time_diff = 0.00 - first_in = True - last_out = False - time_in_buffer = 0.0 + last_mod_time = 0.0 + time_diff = 0.0 + first = True + last = False + time_difference = 0.0 while True: # switch playlist after last clip from day befor - if last_out: - if time_in_buffer > float(_buffer.length): + if last: + if time_difference > float(_buffer.length): # wait to sync time - wait = time_in_buffer - float(_buffer.length) + wait = time_difference - float(_buffer.length) logger.info('Wait for: ' + str(wait) + ' seconds.') sleep(wait) list_date = get_date(False) - last_out = False + last = False else: list_date = get_date(True) @@ -332,23 +389,32 @@ def iter_src_commands(): last_mod_time = mod_time logger.info('open: ' + xml_path) + # validate xml values in new Thread + 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 for clip_node in clip_nodes[:-1]: src = clip_node.get('src') - begin = float(clip_node.get('begin')) - duration = float(clip_node.get('dur')) - seek = float(clip_node.get('in')) - out = float(clip_node.get('out')) + begin = is_float(clip_node.get('begin'), last_time, True) + duration = is_float(clip_node.get('dur'), 300, True) + seek = is_float(clip_node.get('in'), 0, True) + out = is_float(clip_node.get('out'), 300, True) - if first_in: + if first: # first time we end up here if last_time < begin + duration: # calculate seek time init_seek = last_time - begin + seek + time_diff src_cmd = prepare_input(src, duration, init_seek, out) - time_diff = 0.00 - first_in = False + time_diff = 0.0 + first = False last_time = begin break @@ -359,25 +425,26 @@ def iter_src_commands(): break else: # last clip in playlist + src_cmd, begin, first, last = prepare_last_clip(clip_nodes) + if last_time > 86400: - add_sec = 86400 - else: - add_sec = 0 + begin -= 86400.0 + # calculate real time in buffer - time_in_buffer = last_time - get_time('full_sec') - add_sec - begin = float(_playlist.start * 3600 - 5) - src_cmd = prepare_last_clip(clip_nodes[-1]) - last_time = begin + time_difference = begin - get_time('full_sec') + last_time = float(_playlist.start * 3600 - 5) + list_date = get_date(True) - last_mod_time = 0.00 - last_out = True + last_mod_time = 0.0 else: # when we have no playlist for the current day, # then we generate a black clip # and calculate the seek in time, for when the playlist comes back src_cmd = gen_dummy(300) last_time += 300 - last_mod_time = 0.00 + 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 @@ -388,7 +455,7 @@ def iter_src_commands(): time_val = last_time time_diff = time_val - get_time('full_sec') - first_in = True + first = True if src_cmd is not None: yield src_cmd, last_time @@ -425,7 +492,8 @@ def play_clips(out_file, iter_src_commands): '-ar', str(_pre_comp.a_sample), '-ac', '2', '-threads', '2', '-f', 'mpegts', '-' ], - stdout=PIPE + stdout=PIPE, + bufsize=0 ) copyfileobj(filePiper.stdout, out_file) @@ -443,7 +511,8 @@ def main(): [_buffer.cli] + list(_buffer.cmd) + [str(calc_buffer_size()) + 'k'], stdin=PIPE, - stdout=PIPE + stdout=PIPE, + bufsize=0 ) try: # playout to rtmp @@ -466,7 +535,8 @@ def main(): [ _playout.out_addr ], - stdin=mbuffer.stdout + stdin=mbuffer.stdout, + bufsize=0 ) play_thread = Thread(