2018-08-27 04:57:14 -04:00
|
|
|
#!/usr/bin/env python3
|
2018-01-07 07:58:45 -05:00
|
|
|
|
|
|
|
# This file is part of ffplayout.
|
|
|
|
#
|
|
|
|
# ffplayout is free software: you can redistribute it and/or modify
|
|
|
|
# it under the terms of the GNU General Public License as published by
|
|
|
|
# the Free Software Foundation, either version 3 of the License, or
|
|
|
|
# (at your option) any later version.
|
|
|
|
#
|
|
|
|
# ffplayout is distributed in the hope that it will be useful,
|
|
|
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
|
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
|
|
# GNU General Public License for more details.
|
|
|
|
#
|
|
|
|
# You should have received a copy of the GNU General Public License
|
|
|
|
# along with ffplayout. If not, see <http://www.gnu.org/licenses/>.
|
|
|
|
|
|
|
|
# ------------------------------------------------------------------------------
|
|
|
|
|
|
|
|
|
|
|
|
import configparser
|
2019-03-06 09:06:06 -05:00
|
|
|
import json
|
2019-03-08 08:51:37 -05:00
|
|
|
import logging
|
2019-03-06 09:06:06 -05:00
|
|
|
import os
|
2018-01-07 07:58:45 -05:00
|
|
|
import smtplib
|
2018-11-14 04:47:53 -05:00
|
|
|
import socket
|
2018-01-10 09:41:56 -05:00
|
|
|
import sys
|
|
|
|
from argparse import ArgumentParser
|
2018-01-07 07:58:45 -05:00
|
|
|
from ast import literal_eval
|
2018-11-14 04:47:53 -05:00
|
|
|
from datetime import date, datetime, timedelta
|
2018-01-07 07:58:45 -05:00
|
|
|
from email.mime.multipart import MIMEMultipart
|
|
|
|
from email.mime.text import MIMEText
|
2018-11-14 04:47:53 -05:00
|
|
|
from email.utils import formatdate
|
2018-01-10 09:41:56 -05:00
|
|
|
from logging.handlers import TimedRotatingFileHandler
|
2018-01-07 07:58:45 -05:00
|
|
|
from shutil import copyfileobj
|
2019-03-08 08:51:37 -05:00
|
|
|
from subprocess import PIPE, Popen, check_output
|
2018-01-07 07:58:45 -05:00
|
|
|
from threading import Thread
|
|
|
|
from time import sleep
|
2018-01-09 11:54:50 -05:00
|
|
|
from types import SimpleNamespace
|
2018-01-07 07:58:45 -05:00
|
|
|
|
2018-01-07 16:31:06 -05:00
|
|
|
# ------------------------------------------------------------------------------
|
|
|
|
# read variables from config file
|
|
|
|
# ------------------------------------------------------------------------------
|
|
|
|
|
2018-01-07 11:56:54 -05:00
|
|
|
# read config
|
|
|
|
cfg = configparser.ConfigParser()
|
2019-03-06 09:06:06 -05:00
|
|
|
if os.path.exists("/etc/ffplayout/ffplayout.conf"):
|
2018-04-29 12:07:42 -04:00
|
|
|
cfg.read("/etc/ffplayout/ffplayout.conf")
|
|
|
|
else:
|
|
|
|
cfg.read("ffplayout.conf")
|
2018-01-07 11:56:54 -05:00
|
|
|
|
2019-03-11 07:29:47 -04:00
|
|
|
_general = SimpleNamespace(
|
|
|
|
stop=cfg.get('GENERAL', 'stop_on_error'),
|
2019-03-11 09:59:49 -04:00
|
|
|
threshold=cfg.getfloat('GENERAL', 'stop_threshold')
|
2019-03-11 07:29:47 -04:00
|
|
|
)
|
|
|
|
|
2018-01-09 11:54:50 -05:00
|
|
|
_mail = SimpleNamespace(
|
2019-03-08 08:51:37 -05:00
|
|
|
subject=cfg.get('MAIL', 'subject'),
|
2018-01-09 11:54:50 -05:00
|
|
|
server=cfg.get('MAIL', 'smpt_server'),
|
|
|
|
port=cfg.getint('MAIL', 'smpt_port'),
|
|
|
|
s_addr=cfg.get('MAIL', 'sender_addr'),
|
|
|
|
s_pass=cfg.get('MAIL', 'sender_pass'),
|
|
|
|
recip=cfg.get('MAIL', 'recipient')
|
|
|
|
)
|
|
|
|
|
2018-01-10 09:41:56 -05:00
|
|
|
_log = SimpleNamespace(
|
|
|
|
path=cfg.get('LOGGING', 'log_file'),
|
|
|
|
level=cfg.get('LOGGING', 'log_level')
|
|
|
|
)
|
|
|
|
|
2018-01-09 11:54:50 -05:00
|
|
|
_pre_comp = SimpleNamespace(
|
|
|
|
w=cfg.getint('PRE_COMPRESS', 'width'),
|
|
|
|
h=cfg.getint('PRE_COMPRESS', 'height'),
|
2018-08-13 15:32:25 -04:00
|
|
|
aspect=cfg.getfloat(
|
|
|
|
'PRE_COMPRESS', 'width') / cfg.getfloat('PRE_COMPRESS', 'height'),
|
2018-01-09 11:54:50 -05:00
|
|
|
fps=cfg.getint('PRE_COMPRESS', 'fps'),
|
2018-02-20 04:59:39 -05:00
|
|
|
v_bitrate=cfg.getint('PRE_COMPRESS', 'v_bitrate'),
|
2019-03-10 16:24:03 -04:00
|
|
|
v_bufsize=cfg.getint('PRE_COMPRESS', 'v_bitrate') / 2,
|
2019-03-04 11:54:36 -05:00
|
|
|
protocols=cfg.get('PRE_COMPRESS', 'live_protocols'),
|
2018-04-29 12:07:42 -04:00
|
|
|
copy=cfg.getboolean('PRE_COMPRESS', 'copy_mode'),
|
|
|
|
copy_settings=literal_eval(cfg.get('PRE_COMPRESS', 'ffmpeg_copy_settings'))
|
2018-01-09 11:54:50 -05:00
|
|
|
)
|
|
|
|
|
|
|
|
_playlist = SimpleNamespace(
|
|
|
|
path=cfg.get('PLAYLIST', 'playlist_path'),
|
2018-08-15 10:19:50 -04:00
|
|
|
start=cfg.getint('PLAYLIST', 'day_start'),
|
2018-04-29 12:07:42 -04:00
|
|
|
filler=cfg.get('PLAYLIST', 'filler_clip'),
|
2018-08-14 15:30:32 -04:00
|
|
|
blackclip=cfg.get('PLAYLIST', 'blackclip'),
|
2018-08-15 11:34:42 -04:00
|
|
|
shift=cfg.getint('PLAYLIST', 'time_shift'),
|
2018-08-15 06:26:43 -04:00
|
|
|
map_ext=cfg.get('PLAYLIST', 'map_extension')
|
2018-01-09 11:54:50 -05:00
|
|
|
)
|
|
|
|
|
|
|
|
_buffer = SimpleNamespace(
|
|
|
|
length=cfg.getint('BUFFER', 'buffer_length'),
|
2018-03-20 16:33:21 -04:00
|
|
|
tol=cfg.getfloat('BUFFER', 'buffer_tolerance'),
|
2018-01-09 11:54:50 -05:00
|
|
|
cli=cfg.get('BUFFER', 'buffer_cli'),
|
|
|
|
cmd=literal_eval(cfg.get('BUFFER', 'buffer_cmd'))
|
|
|
|
)
|
|
|
|
|
|
|
|
_playout = SimpleNamespace(
|
2018-11-25 08:24:47 -05:00
|
|
|
preview=cfg.getboolean('OUT', 'preview'),
|
2018-01-09 11:54:50 -05:00
|
|
|
name=cfg.get('OUT', 'service_name'),
|
|
|
|
provider=cfg.get('OUT', 'service_provider'),
|
|
|
|
out_addr=cfg.get('OUT', 'out_addr'),
|
|
|
|
post_comp_video=literal_eval(cfg.get('OUT', 'post_comp_video')),
|
|
|
|
post_comp_audio=literal_eval(cfg.get('OUT', 'post_comp_audio')),
|
2018-04-29 12:07:42 -04:00
|
|
|
post_comp_extra=literal_eval(cfg.get('OUT', 'post_comp_extra')),
|
|
|
|
post_comp_copy=literal_eval(cfg.get('OUT', 'post_comp_copy'))
|
2018-01-09 11:54:50 -05:00
|
|
|
)
|
|
|
|
|
|
|
|
# set logo filtergraph
|
2019-03-06 09:06:06 -05:00
|
|
|
if os.path.exists(cfg.get('OUT', 'logo')):
|
2018-02-19 05:12:13 -05:00
|
|
|
_playout.logo = ['-thread_queue_size', '16', '-i', cfg.get('OUT', 'logo')]
|
2018-01-09 11:54:50 -05:00
|
|
|
_playout.filter = [
|
|
|
|
'-filter_complex', '[0:v][1:v]' + cfg.get('OUT', 'logo_o') + '[o]',
|
|
|
|
'-map', '[o]', '-map', '0:a'
|
|
|
|
]
|
|
|
|
else:
|
|
|
|
_playout.logo = []
|
|
|
|
_playout.filter = []
|
2018-01-07 07:58:45 -05:00
|
|
|
|
|
|
|
|
2018-01-10 09:41:56 -05:00
|
|
|
# ------------------------------------------------------------------------------
|
|
|
|
# logging
|
|
|
|
# ------------------------------------------------------------------------------
|
|
|
|
|
|
|
|
stdin_parser = ArgumentParser(description="python and ffmpeg based playout")
|
|
|
|
stdin_parser.add_argument(
|
|
|
|
"-l", "--log", help="file to write log to (default '" + _log.path + "')"
|
|
|
|
)
|
|
|
|
|
|
|
|
# If the log file is specified on the command line then override the default
|
|
|
|
stdin_args = stdin_parser.parse_args()
|
|
|
|
if stdin_args.log:
|
|
|
|
_log.path = stdin_args.log
|
|
|
|
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
logger.setLevel(_log.level)
|
|
|
|
handler = TimedRotatingFileHandler(_log.path, when="midnight", backupCount=5)
|
2018-01-16 03:31:59 -05:00
|
|
|
formatter = logging.Formatter('[%(asctime)s] [%(levelname)s] %(message)s')
|
2018-01-10 09:41:56 -05:00
|
|
|
handler.setFormatter(formatter)
|
|
|
|
logger.addHandler(handler)
|
|
|
|
|
|
|
|
|
|
|
|
# capture stdout and sterr in the log
|
2019-03-10 16:24:03 -04:00
|
|
|
class PlayoutLogger(object):
|
2019-03-08 08:51:37 -05:00
|
|
|
def __init__(self, logger, level):
|
|
|
|
self.logger = logger
|
|
|
|
self.level = level
|
2018-01-10 09:41:56 -05:00
|
|
|
|
2019-03-08 08:51:37 -05:00
|
|
|
def write(self, message):
|
|
|
|
# Only log if there is a message (not just a new line)
|
|
|
|
if message.rstrip() != "":
|
|
|
|
self.logger.log(self.level, message.rstrip())
|
2018-01-10 09:41:56 -05:00
|
|
|
|
2019-03-08 08:51:37 -05:00
|
|
|
def flush(self):
|
|
|
|
pass
|
2018-01-10 09:41:56 -05:00
|
|
|
|
|
|
|
|
|
|
|
# Replace stdout with logging to file at INFO level
|
2019-03-10 16:24:03 -04:00
|
|
|
sys.stdout = PlayoutLogger(logger, logging.INFO)
|
2018-01-10 09:41:56 -05:00
|
|
|
# Replace stderr with logging to file at ERROR level
|
2019-03-10 16:24:03 -04:00
|
|
|
sys.stderr = PlayoutLogger(logger, logging.ERROR)
|
2018-01-10 09:41:56 -05:00
|
|
|
|
|
|
|
|
2018-01-07 07:58:45 -05:00
|
|
|
# ------------------------------------------------------------------------------
|
2018-01-09 11:54:50 -05:00
|
|
|
# global helper functions
|
2018-01-07 07:58:45 -05:00
|
|
|
# ------------------------------------------------------------------------------
|
|
|
|
|
2018-01-09 11:54:50 -05:00
|
|
|
# get time
|
|
|
|
def get_time(time_format):
|
2018-08-15 10:18:09 -04:00
|
|
|
t = datetime.today() + timedelta(seconds=_playlist.shift)
|
2018-01-09 11:54:50 -05:00
|
|
|
if time_format == 'hour':
|
|
|
|
return t.hour
|
|
|
|
elif time_format == 'full_sec':
|
2018-02-19 05:12:13 -05:00
|
|
|
sec = float(t.hour * 3600 + t.minute * 60 + t.second)
|
|
|
|
micro = float(t.microsecond) / 1000000
|
|
|
|
return sec + micro
|
2018-01-09 11:54:50 -05:00
|
|
|
else:
|
|
|
|
return t.strftime("%H:%M:%S")
|
2018-01-07 16:31:06 -05:00
|
|
|
|
2018-01-09 11:54:50 -05:00
|
|
|
|
|
|
|
# get date
|
|
|
|
def get_date(seek_day):
|
2018-08-15 10:18:09 -04:00
|
|
|
d = date.today() + timedelta(seconds=_playlist.shift)
|
2018-01-09 11:54:50 -05:00
|
|
|
if get_time('hour') < _playlist.start and seek_day:
|
2018-08-15 10:18:09 -04:00
|
|
|
yesterday = d - timedelta(1)
|
2018-01-09 11:54:50 -05:00
|
|
|
return yesterday.strftime('%Y-%m-%d')
|
|
|
|
else:
|
2018-08-15 10:18:09 -04:00
|
|
|
return d.strftime('%Y-%m-%d')
|
2018-01-07 16:31:06 -05:00
|
|
|
|
|
|
|
|
2018-01-07 07:58:45 -05:00
|
|
|
# send error messages to email addresses
|
2019-03-10 16:24:03 -04:00
|
|
|
def mailer(message, time, path):
|
2018-01-07 07:58:45 -05:00
|
|
|
if _mail.recip:
|
|
|
|
msg = MIMEMultipart()
|
|
|
|
msg['From'] = _mail.s_addr
|
|
|
|
msg['To'] = _mail.recip
|
2019-03-08 08:51:37 -05:00
|
|
|
msg['Subject'] = _mail.subject
|
2018-11-14 04:47:53 -05:00
|
|
|
msg["Date"] = formatdate(localtime=True)
|
2018-01-23 07:14:50 -05:00
|
|
|
msg.attach(MIMEText('{} {}\n{}'.format(time, message, path), 'plain'))
|
2018-01-07 07:58:45 -05:00
|
|
|
text = msg.as_string()
|
|
|
|
|
2018-11-14 04:47:53 -05:00
|
|
|
try:
|
|
|
|
server = smtplib.SMTP(_mail.server, _mail.port)
|
|
|
|
except socket.error as err:
|
|
|
|
logger.error(err)
|
|
|
|
server = None
|
|
|
|
|
|
|
|
if server is not None:
|
|
|
|
server.starttls()
|
|
|
|
try:
|
|
|
|
login = server.login(_mail.s_addr, _mail.s_pass)
|
|
|
|
except smtplib.SMTPAuthenticationError as serr:
|
|
|
|
logger.error(serr)
|
|
|
|
login = None
|
|
|
|
|
|
|
|
if login is not None:
|
|
|
|
server.sendmail(_mail.s_addr, _mail.recip, text)
|
|
|
|
server.quit()
|
|
|
|
|
2018-01-07 07:58:45 -05:00
|
|
|
|
2018-02-13 08:23:34 -05:00
|
|
|
# calculating the size for the buffer in KB
|
2018-01-07 07:58:45 -05:00
|
|
|
def calc_buffer_size():
|
2019-03-08 03:36:02 -05:00
|
|
|
# in copy mode files has normally smaller bit rate,
|
|
|
|
# so we calculate the size different
|
|
|
|
if _pre_comp.copy:
|
|
|
|
list_date = get_date(True)
|
2019-03-10 16:24:03 -04:00
|
|
|
year, month, day = list_date.split('-')
|
2019-03-08 03:36:02 -05:00
|
|
|
json_file = os.path.join(
|
|
|
|
_playlist.path, year, month, list_date + '.json')
|
|
|
|
|
2019-03-10 16:24:03 -04:00
|
|
|
if file_exist(json_file):
|
2019-03-08 03:36:02 -05:00
|
|
|
with open(json_file) as f:
|
|
|
|
clip_nodes = json.load(f)
|
|
|
|
|
|
|
|
if _playlist.map_ext:
|
|
|
|
_ext = literal_eval(_playlist.map_ext)
|
|
|
|
source = clip_nodes["program"][0]["source"].replace(
|
|
|
|
_ext[0], _ext[1])
|
|
|
|
else:
|
|
|
|
source = clip_nodes["program"][0]["source"]
|
|
|
|
|
|
|
|
cmd = [
|
|
|
|
'ffprobe', '-v', 'error', '-show_entries', 'format=bit_rate',
|
|
|
|
'-of', 'default=noprint_wrappers=1:nokey=1', source]
|
|
|
|
bite_rate = check_output(cmd).decode('utf-8')
|
|
|
|
|
|
|
|
if is_int(bite_rate):
|
|
|
|
bite_rate = int(bite_rate) / 1024
|
|
|
|
else:
|
|
|
|
bite_rate = 4000
|
|
|
|
|
|
|
|
return int(bite_rate * 0.125 * _buffer.length)
|
|
|
|
else:
|
|
|
|
return 5000
|
|
|
|
else:
|
|
|
|
return int((_pre_comp.v_bitrate * 0.125 + 281.25) * _buffer.length)
|
2018-01-07 07:58:45 -05:00
|
|
|
|
|
|
|
|
|
|
|
# check if processes a well
|
2019-03-10 16:24:03 -04:00
|
|
|
def check_process(play_thread, playout, mbuffer):
|
2018-01-07 07:58:45 -05:00
|
|
|
while True:
|
|
|
|
sleep(4)
|
2019-03-10 16:24:03 -04:00
|
|
|
if playout.poll() is not None:
|
2018-11-28 03:05:05 -05:00
|
|
|
logger.error(
|
|
|
|
'postprocess is not alive anymore, terminate ffplayout!')
|
2019-03-10 16:24:03 -04:00
|
|
|
mbuffer.terminate()
|
2018-01-07 07:58:45 -05:00
|
|
|
break
|
|
|
|
|
2018-11-27 06:35:27 -05:00
|
|
|
if not play_thread.is_alive():
|
2018-11-28 03:05:05 -05:00
|
|
|
logger.error(
|
|
|
|
'preprocess is not alive anymore, terminate ffplayout!')
|
2019-03-10 16:24:03 -04:00
|
|
|
mbuffer.terminate()
|
2018-11-27 06:35:27 -05:00
|
|
|
break
|
|
|
|
|
2018-01-07 07:58:45 -05:00
|
|
|
|
2019-03-04 11:54:36 -05:00
|
|
|
# check if input file exist,
|
2018-01-07 07:58:45 -05:00
|
|
|
# when not send email and generate blackclip
|
2019-03-10 16:24:03 -04:00
|
|
|
def file_exist(in_file):
|
2019-03-06 09:06:06 -05:00
|
|
|
if os.path.exists(in_file):
|
2018-01-09 11:54:50 -05:00
|
|
|
return True
|
2018-01-07 07:58:45 -05:00
|
|
|
else:
|
2018-01-09 11:54:50 -05:00
|
|
|
return False
|
|
|
|
|
|
|
|
|
2018-02-03 17:01:26 -05:00
|
|
|
# seek in clip and cut the end
|
2019-03-11 09:56:56 -04:00
|
|
|
# TODO: should first file be fade in?
|
|
|
|
# by logo blending we have to change this, maybe a filter gen class
|
2018-02-03 17:01:26 -05:00
|
|
|
def seek_in_cut_end(in_file, duration, seek, out):
|
2018-02-13 08:23:34 -05:00
|
|
|
if seek > 0.0:
|
2018-02-03 17:01:26 -05:00
|
|
|
inpoint = ['-ss', str(seek)]
|
2019-03-11 09:56:56 -04:00
|
|
|
fade_in_v = '[0:v]fade=in:st=0:d=0.5,'
|
|
|
|
fade_in_a = '[0:a]afade=in:st=0:d=0.5,'
|
2018-02-03 17:01:26 -05:00
|
|
|
else:
|
|
|
|
inpoint = []
|
2019-03-11 09:56:56 -04:00
|
|
|
fade_in_v = '[0:v]'
|
|
|
|
fade_in_a = '[0:a]'
|
2018-02-03 17:01:26 -05:00
|
|
|
|
|
|
|
if out < duration:
|
2019-03-04 11:54:36 -05:00
|
|
|
length = out - seek - 1.0
|
2018-02-04 14:29:17 -05:00
|
|
|
cut_end = ['-t', str(out - seek)]
|
2019-03-11 09:56:56 -04:00
|
|
|
fade_out_vid = '{}fade=out:st={}:d=1.0[v];'.format(fade_in_v, length)
|
|
|
|
fade_out_aud = '{}afade=out:st={}:d=1.0[a]'.format(fade_in_a, length)
|
2019-03-05 09:01:54 -05:00
|
|
|
end = ['-map', '[v]', '-map', '[a]']
|
2018-02-03 17:01:26 -05:00
|
|
|
else:
|
|
|
|
cut_end = []
|
2019-03-11 09:56:56 -04:00
|
|
|
fade_out_vid = 'null[v];'
|
|
|
|
fade_out_aud = '{}apad[a]'.format(fade_in_a)
|
|
|
|
end = ['-shortest', '-map', '[v]', '-map', '[a]']
|
2018-02-03 17:01:26 -05:00
|
|
|
|
2018-04-29 12:07:42 -04:00
|
|
|
if _pre_comp.copy:
|
|
|
|
return inpoint + ['-i', in_file] + cut_end
|
|
|
|
else:
|
|
|
|
return inpoint + ['-i', in_file] + cut_end + [
|
2019-03-04 11:54:36 -05:00
|
|
|
'-filter_complex', fade_out_vid + fade_out_aud
|
2019-03-05 09:01:54 -05:00
|
|
|
] + end
|
2018-01-09 11:54:50 -05:00
|
|
|
|
|
|
|
|
2018-01-22 14:43:52 -05:00
|
|
|
# generate a dummy clip, with black color and empty audiotrack
|
2018-01-09 11:54:50 -05:00
|
|
|
def gen_dummy(duration):
|
2018-04-29 12:07:42 -04:00
|
|
|
if _pre_comp.copy:
|
|
|
|
return ['-i', _playlist.blackclip]
|
|
|
|
else:
|
|
|
|
return [
|
|
|
|
'-f', 'lavfi', '-i',
|
|
|
|
'color=s={}x{}:d={}'.format(
|
|
|
|
_pre_comp.w, _pre_comp.h, duration
|
|
|
|
),
|
2019-03-05 09:01:54 -05:00
|
|
|
'-f', 'lavfi', '-i', 'anullsrc=r=48000',
|
2018-04-29 12:07:42 -04:00
|
|
|
'-shortest'
|
|
|
|
]
|
2018-01-09 11:54:50 -05:00
|
|
|
|
|
|
|
|
2018-02-19 05:12:13 -05:00
|
|
|
# when source path exist, generate input with seek and out time
|
2018-03-29 04:30:18 -04:00
|
|
|
# when path not exist, generate dummy clip
|
|
|
|
def src_or_dummy(src, duration, seek, out, dummy_len=None):
|
2019-03-04 11:54:36 -05:00
|
|
|
prefix = src.split('://')[0]
|
|
|
|
|
2019-03-08 03:36:02 -05:00
|
|
|
if _pre_comp.copy:
|
|
|
|
add_filter = []
|
|
|
|
else:
|
|
|
|
add_filter = [
|
|
|
|
'-filter_complex', '[0:a]apad[a]',
|
|
|
|
'-shortest', '-map', '0:v', '-map', '[a]']
|
|
|
|
|
2019-03-04 11:54:36 -05:00
|
|
|
# check if input is a live source
|
2019-03-11 09:56:56 -04:00
|
|
|
if prefix in _pre_comp.protocols:
|
2019-03-04 11:54:36 -05:00
|
|
|
cmd = [
|
|
|
|
'ffprobe', '-v', 'error', '-show_entries', 'format=duration',
|
|
|
|
'-of', 'default=noprint_wrappers=1:nokey=1', src]
|
2019-03-06 09:06:06 -05:00
|
|
|
live_duration = check_output(cmd)
|
2019-03-04 11:54:36 -05:00
|
|
|
|
2019-03-06 09:06:06 -05:00
|
|
|
if '404' in live_duration.decode('utf-8'):
|
2019-03-10 16:24:03 -04:00
|
|
|
mailer('Clip not exist:', get_time(None), src)
|
|
|
|
logger.error('Clip not exist: {}'.format(src))
|
2019-03-04 11:54:36 -05:00
|
|
|
if dummy_len and not _pre_comp.copy:
|
|
|
|
return gen_dummy(dummy_len)
|
|
|
|
else:
|
|
|
|
return gen_dummy(out - seek)
|
2019-03-06 09:06:06 -05:00
|
|
|
elif is_float(live_duration):
|
|
|
|
if seek > 0.0 or out < live_duration:
|
|
|
|
return seek_in_cut_end(src, live_duration, seek, out)
|
2019-03-04 11:54:36 -05:00
|
|
|
else:
|
2019-03-08 03:36:02 -05:00
|
|
|
return ['-i', src] + add_filter
|
2019-03-04 11:54:36 -05:00
|
|
|
else:
|
|
|
|
# no duration found, so we set duration to 24 hours,
|
|
|
|
# to be sure that out point will cut the lenght
|
2019-03-05 09:01:54 -05:00
|
|
|
return seek_in_cut_end(src, 86400, 0, out - seek)
|
2019-03-04 11:54:36 -05:00
|
|
|
|
2019-03-10 16:24:03 -04:00
|
|
|
elif file_exist(src):
|
2018-02-13 08:23:34 -05:00
|
|
|
if seek > 0.0 or out < duration:
|
2018-02-19 05:12:13 -05:00
|
|
|
return seek_in_cut_end(src, duration, seek, out)
|
2018-02-03 17:01:26 -05:00
|
|
|
else:
|
2019-03-08 03:53:31 -05:00
|
|
|
return ['-i', src] + add_filter
|
2018-02-13 08:23:34 -05:00
|
|
|
else:
|
2019-03-10 16:24:03 -04:00
|
|
|
mailer('Clip not exist:', get_time(None), src)
|
|
|
|
logger.error('Clip not exist: {}'.format(src))
|
2018-04-29 12:07:42 -04:00
|
|
|
if dummy_len and not _pre_comp.copy:
|
2018-03-29 04:30:18 -04:00
|
|
|
return gen_dummy(dummy_len)
|
|
|
|
else:
|
|
|
|
return gen_dummy(out - seek)
|
2018-02-13 08:23:34 -05:00
|
|
|
|
|
|
|
|
2018-02-19 09:13:24 -05:00
|
|
|
# compare clip play time with real time,
|
|
|
|
# to see if we are sync
|
|
|
|
def check_sync(begin):
|
|
|
|
time_now = get_time('full_sec')
|
2018-08-15 10:18:09 -04:00
|
|
|
start = float(_playlist.start * 3600)
|
2019-03-08 03:53:31 -05:00
|
|
|
|
|
|
|
# in copy mode buffer length can not be calculatet correctly...
|
|
|
|
if _pre_comp.copy:
|
|
|
|
tolerance = 60
|
|
|
|
else:
|
|
|
|
tolerance = _buffer.tol * 4
|
2018-02-19 09:13:24 -05:00
|
|
|
|
|
|
|
t_dist = begin - time_now
|
2018-02-20 04:59:39 -05:00
|
|
|
if 0 <= time_now < start and not begin == start:
|
2018-02-19 09:13:24 -05:00
|
|
|
t_dist -= 86400.0
|
|
|
|
|
|
|
|
# check that we are in tolerance time
|
2018-03-20 16:33:21 -04:00
|
|
|
if not _buffer.length - tolerance < t_dist < _buffer.length + tolerance:
|
2019-03-10 16:24:03 -04:00
|
|
|
mailer(
|
2018-02-19 09:13:24 -05:00
|
|
|
'Playlist is not sync!', get_time(None),
|
2019-03-11 09:56:56 -04:00
|
|
|
'{} seconds async'.format(t_dist)
|
2018-02-19 09:13:24 -05:00
|
|
|
)
|
2019-03-10 16:24:03 -04:00
|
|
|
logger.error('Playlist is {} seconds async!'.format(t_dist))
|
2018-02-19 09:13:24 -05:00
|
|
|
|
2019-03-11 07:29:47 -04:00
|
|
|
if _general.stop and t_dist > _general.threshold:
|
|
|
|
logger.error('Sync tolerance value exceeded, program is terminated')
|
|
|
|
exit()
|
|
|
|
|
2018-02-19 09:13:24 -05:00
|
|
|
|
2018-02-19 05:12:13 -05:00
|
|
|
# prepare input clip
|
|
|
|
# check begin and length from clip
|
|
|
|
# return clip only if we are in 24 hours time range
|
2018-03-20 16:33:21 -04:00
|
|
|
def gen_input(src, begin, dur, seek, out, last):
|
2018-08-15 10:18:09 -04:00
|
|
|
start = float(_playlist.start * 3600)
|
2018-03-20 16:33:21 -04:00
|
|
|
day_in_sec = 86400.0
|
|
|
|
ref_time = day_in_sec + start
|
2018-02-28 15:16:17 -05:00
|
|
|
time = get_time('full_sec')
|
2018-02-19 05:12:13 -05:00
|
|
|
|
2018-03-20 16:33:21 -04:00
|
|
|
if 0 <= time < start:
|
|
|
|
time += day_in_sec
|
2018-02-19 05:12:13 -05:00
|
|
|
|
2018-03-20 16:33:21 -04:00
|
|
|
# calculate time difference to see if we are sync
|
|
|
|
time_diff = _buffer.length + _buffer.tol + out - seek + time
|
2018-02-19 05:12:13 -05:00
|
|
|
|
2018-03-20 16:33:21 -04:00
|
|
|
if (time_diff <= ref_time or begin < day_in_sec) and not last:
|
2018-02-19 05:12:13 -05:00
|
|
|
# when we are in the 24 houre range, get the clip
|
2018-03-29 04:30:18 -04:00
|
|
|
return src_or_dummy(src, dur, seek, out, 20), None
|
2018-03-20 16:33:21 -04:00
|
|
|
elif time_diff < ref_time and last:
|
2018-02-19 05:12:13 -05:00
|
|
|
# when last clip is passed and we still have too much time left
|
2018-03-20 16:33:21 -04:00
|
|
|
# check if duration is larger then out - seek
|
|
|
|
time_diff = _buffer.length + _buffer.tol + dur + time
|
|
|
|
new_len = dur - (time_diff - ref_time)
|
|
|
|
logger.info('we are under time, new_len is: {}'.format(new_len))
|
2018-02-19 05:12:13 -05:00
|
|
|
|
2018-03-20 16:33:21 -04:00
|
|
|
if time_diff >= ref_time:
|
2018-02-19 05:12:13 -05:00
|
|
|
if src == _playlist.filler:
|
2018-03-20 16:33:21 -04:00
|
|
|
# when filler is something like a clock,
|
|
|
|
# is better to start the clip later and to play until end
|
|
|
|
src_cmd = src_or_dummy(src, dur, dur - new_len, dur)
|
2018-02-19 05:12:13 -05:00
|
|
|
else:
|
2018-03-20 16:33:21 -04:00
|
|
|
src_cmd = src_or_dummy(src, dur, 0, new_len)
|
2018-02-28 15:16:17 -05:00
|
|
|
else:
|
2018-03-20 16:33:21 -04:00
|
|
|
src_cmd = src_or_dummy(src, dur, 0, dur)
|
|
|
|
|
2019-03-10 16:24:03 -04:00
|
|
|
mailer(
|
|
|
|
'Playlist is not long enough:', get_time(None),
|
2019-03-11 09:56:56 -04:00
|
|
|
'{} seconds needed.'.format(new_len)
|
2018-03-20 16:33:21 -04:00
|
|
|
)
|
2019-03-10 16:24:03 -04:00
|
|
|
logger.error('Playlist is {} seconds to short'.format(new_len))
|
2018-03-20 16:33:21 -04:00
|
|
|
|
|
|
|
return src_cmd, new_len - dur
|
|
|
|
|
|
|
|
elif time_diff > ref_time:
|
|
|
|
new_len = out - seek - (time_diff - ref_time)
|
|
|
|
# when we over the 24 hours range, trim clip
|
|
|
|
logger.info('we are over time, new_len is: {}'.format(new_len))
|
|
|
|
|
|
|
|
if new_len > 5.0:
|
2018-02-19 05:12:13 -05:00
|
|
|
if src == _playlist.filler:
|
2018-03-20 16:33:21 -04:00
|
|
|
src_cmd = src_or_dummy(src, dur, out - new_len, out)
|
2018-02-19 05:12:13 -05:00
|
|
|
else:
|
2018-03-20 16:33:21 -04:00
|
|
|
src_cmd = src_or_dummy(src, dur, seek, new_len)
|
2018-02-19 05:12:13 -05:00
|
|
|
elif new_len > 1.0:
|
|
|
|
src_cmd = gen_dummy(new_len)
|
|
|
|
else:
|
|
|
|
src_cmd = None
|
2018-01-07 07:58:45 -05:00
|
|
|
|
2018-03-20 16:33:21 -04:00
|
|
|
return src_cmd, 0.0
|
2018-02-13 08:23:34 -05:00
|
|
|
|
|
|
|
|
|
|
|
# test if value is float
|
2019-03-06 09:06:06 -05:00
|
|
|
def is_float(value):
|
2018-02-13 08:23:34 -05:00
|
|
|
try:
|
|
|
|
float(value)
|
2019-03-06 09:06:06 -05:00
|
|
|
return True
|
2018-02-13 08:23:34 -05:00
|
|
|
except ValueError:
|
2019-03-06 09:06:06 -05:00
|
|
|
return False
|
2018-02-13 08:23:34 -05:00
|
|
|
|
|
|
|
|
2019-03-08 03:36:02 -05:00
|
|
|
# test if value is int
|
|
|
|
def is_int(value):
|
|
|
|
try:
|
|
|
|
int(value)
|
|
|
|
return True
|
|
|
|
except ValueError:
|
|
|
|
return False
|
|
|
|
|
|
|
|
|
2018-03-29 04:30:18 -04:00
|
|
|
# check last item, when it is None or a dummy clip,
|
|
|
|
# set true and seek in playlist
|
|
|
|
def check_last_item(src_cmd, last_time, last):
|
2018-11-27 06:35:27 -05:00
|
|
|
if src_cmd is None and not last:
|
2018-03-29 04:30:18 -04:00
|
|
|
first = True
|
2018-08-15 10:18:09 -04:00
|
|
|
last_time = get_time('full_sec')
|
|
|
|
if 0 <= last_time < _playlist.start * 3600:
|
2018-03-29 04:30:18 -04:00
|
|
|
last_time += 86400
|
|
|
|
|
|
|
|
elif 'lavfi' in src_cmd and not last:
|
|
|
|
first = True
|
|
|
|
last_time = get_time('full_sec') + _buffer.length + _buffer.tol
|
2018-08-15 10:18:09 -04:00
|
|
|
if 0 <= last_time < _playlist.start * 3600:
|
2018-03-29 04:30:18 -04:00
|
|
|
last_time += 86400
|
|
|
|
else:
|
|
|
|
first = False
|
|
|
|
|
|
|
|
return first, last_time
|
|
|
|
|
|
|
|
|
2019-03-06 09:06:06 -05:00
|
|
|
# check begin and length
|
|
|
|
def check_start_and_length(json_nodes, counter):
|
|
|
|
# check start time and set begin
|
|
|
|
if "begin" in json_nodes:
|
|
|
|
h, m, s = json_nodes["begin"].split(':')
|
|
|
|
if is_float(h) and is_float(m) and is_float(s):
|
|
|
|
begin = float(h) * 3600 + float(m) * 60 + float(s)
|
|
|
|
else:
|
|
|
|
begin = -100.0
|
|
|
|
else:
|
|
|
|
begin = -100.0
|
|
|
|
|
|
|
|
# check if playlist is long enough
|
|
|
|
if "length" in json_nodes:
|
|
|
|
l_h, l_m, l_s = json_nodes["length"].split(':')
|
|
|
|
if is_float(l_h) and is_float(l_m) and is_float(l_s):
|
|
|
|
length = float(l_h) * 3600 + float(l_m) * 60 + float(l_s)
|
|
|
|
|
|
|
|
start = float(_playlist.start * 3600)
|
|
|
|
|
|
|
|
total_play_time = begin + counter - start
|
|
|
|
|
|
|
|
if total_play_time < length - 5:
|
2019-03-10 16:24:03 -04:00
|
|
|
mailer(
|
2019-03-06 09:06:06 -05:00
|
|
|
'json playlist is not long enough!',
|
2019-03-11 09:56:56 -04:00
|
|
|
get_time(None), "total play time is: {}".format(
|
|
|
|
timedelta(seconds=total_play_time))
|
2019-03-06 09:06:06 -05:00
|
|
|
)
|
2019-03-10 16:24:03 -04:00
|
|
|
logger.error('Playlist is only {} long!'.format(
|
|
|
|
timedelta(seconds=total_play_time)))
|
2019-03-06 09:06:06 -05:00
|
|
|
|
|
|
|
|
|
|
|
# validate json values in new Thread
|
2018-02-13 08:23:34 -05:00
|
|
|
# and test if file path exist
|
2019-03-11 06:22:23 -04:00
|
|
|
# TODO: we need better and unique validation,
|
|
|
|
# now it is messy - the file get readed twice
|
|
|
|
# and values get multiple time evaluate
|
|
|
|
# IDEA: open one time the playlist,
|
|
|
|
# not in a thread and build from it a new clean dictionary
|
2018-02-19 05:12:13 -05:00
|
|
|
def validate_thread(clip_nodes):
|
2019-03-06 09:06:06 -05:00
|
|
|
def check_json(json_nodes):
|
2018-02-19 05:12:13 -05:00
|
|
|
error = ''
|
2019-03-06 09:06:06 -05:00
|
|
|
counter = 0
|
2018-02-13 08:23:34 -05:00
|
|
|
|
2018-03-29 04:30:18 -04:00
|
|
|
# check if all values are valid
|
2019-03-06 09:06:06 -05:00
|
|
|
for node in json_nodes["program"]:
|
2018-08-15 06:15:06 -04:00
|
|
|
if _playlist.map_ext:
|
2018-08-15 09:29:42 -04:00
|
|
|
_ext = literal_eval(_playlist.map_ext)
|
2019-03-06 09:06:06 -05:00
|
|
|
source = node["source"].replace(
|
2018-08-15 09:29:42 -04:00
|
|
|
_ext[0], _ext[1])
|
2018-08-15 06:15:06 -04:00
|
|
|
else:
|
2019-03-06 09:06:06 -05:00
|
|
|
source = node["source"]
|
2018-08-15 06:15:06 -04:00
|
|
|
|
2019-03-06 09:06:06 -05:00
|
|
|
prefix = source.split('://')[0]
|
2019-03-04 11:54:36 -05:00
|
|
|
|
2019-03-11 09:56:56 -04:00
|
|
|
if prefix in _pre_comp.protocols:
|
2019-03-04 11:54:36 -05:00
|
|
|
cmd = [
|
|
|
|
'ffprobe', '-v', 'error',
|
|
|
|
'-show_entries', 'format=duration',
|
2019-03-06 09:06:06 -05:00
|
|
|
'-of', 'default=noprint_wrappers=1:nokey=1', source]
|
2019-03-04 11:54:36 -05:00
|
|
|
output = check_output(cmd)
|
|
|
|
|
|
|
|
if '404' in output.decode('utf-8'):
|
|
|
|
a = 'Stream not exist! '
|
|
|
|
else:
|
|
|
|
a = ''
|
2019-03-10 16:24:03 -04:00
|
|
|
elif file_exist(source):
|
2018-02-19 05:12:13 -05:00
|
|
|
a = ''
|
|
|
|
else:
|
|
|
|
a = 'File not exist! '
|
2018-02-13 08:23:34 -05:00
|
|
|
|
2019-03-06 09:06:06 -05:00
|
|
|
if is_float(node["in"]) and is_float(node["out"]):
|
|
|
|
b = ''
|
|
|
|
counter += node["out"] - node["in"]
|
|
|
|
else:
|
|
|
|
b = 'Missing Value! '
|
2018-02-13 08:23:34 -05:00
|
|
|
|
2019-03-06 09:06:06 -05:00
|
|
|
c = '' if is_float(node["duration"]) else 'No DURATION Value! '
|
|
|
|
|
|
|
|
line = a + b + c
|
2018-02-19 05:12:13 -05:00
|
|
|
if line:
|
2019-03-11 06:22:23 -04:00
|
|
|
logger.error('Validation error in line: {}'.format(line))
|
2019-03-11 09:56:56 -04:00
|
|
|
error += line + 'In line: {}\n'.format(node)
|
2018-02-13 08:23:34 -05:00
|
|
|
|
2018-02-19 05:12:13 -05:00
|
|
|
if error:
|
2019-03-10 16:24:03 -04:00
|
|
|
mailer(
|
2019-03-06 09:06:06 -05:00
|
|
|
'Validation error, check json playlist, values are missing:\n',
|
2018-04-29 12:07:42 -04:00
|
|
|
get_time(None), error
|
2018-03-29 04:30:18 -04:00
|
|
|
)
|
|
|
|
|
2019-03-06 09:06:06 -05:00
|
|
|
check_start_and_length(json_nodes, counter)
|
2018-02-19 05:12:13 -05:00
|
|
|
|
2019-03-06 09:06:06 -05:00
|
|
|
validate = Thread(name='check_json', target=check_json, args=(clip_nodes,))
|
2018-02-19 05:12:13 -05:00
|
|
|
validate.daemon = True
|
|
|
|
validate.start()
|
|
|
|
|
|
|
|
|
2019-03-08 08:51:37 -05:00
|
|
|
# ------------------------------------------------------------------------------
|
|
|
|
# main functions
|
|
|
|
# ------------------------------------------------------------------------------
|
2018-03-20 16:33:21 -04:00
|
|
|
|
2019-03-08 08:51:37 -05:00
|
|
|
# read values from json playlist
|
|
|
|
class GetSourceIter:
|
|
|
|
def __init__(self):
|
|
|
|
self.last_time = None
|
|
|
|
self.last_mod_time = 0.0
|
|
|
|
self.json_file = None
|
|
|
|
self.clip_nodes = None
|
|
|
|
self.src_cmd = None
|
|
|
|
self.first = True
|
|
|
|
self.last = False
|
|
|
|
self.list_date = get_date(True)
|
|
|
|
self.dummy_len = 60
|
|
|
|
self.begin = 0
|
|
|
|
|
|
|
|
def get_playlist(self):
|
2019-03-11 04:35:56 -04:00
|
|
|
year, month, day = self.list_date.split('-')
|
2019-03-08 08:51:37 -05:00
|
|
|
self.json_file = os.path.join(
|
|
|
|
_playlist.path, year, month, self.list_date + '.json')
|
|
|
|
|
2019-03-10 16:24:03 -04:00
|
|
|
if file_exist(self.json_file):
|
2019-03-08 08:51:37 -05:00
|
|
|
# check last modification from playlist
|
|
|
|
mod_time = os.path.getmtime(self.json_file)
|
|
|
|
if mod_time > self.last_mod_time:
|
2019-03-09 14:12:20 -05:00
|
|
|
with open(self.json_file, 'r') as f:
|
2019-03-08 08:51:37 -05:00
|
|
|
self.clip_nodes = json.load(f)
|
|
|
|
|
2019-03-09 14:12:20 -05:00
|
|
|
self.last_mod_time = mod_time
|
|
|
|
logger.info('open: ' + self.json_file)
|
|
|
|
validate_thread(self.clip_nodes)
|
2019-03-08 08:51:37 -05:00
|
|
|
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
|
|
|
|
self.error_handling('Playlist not exist:')
|
2018-03-20 16:33:21 -04:00
|
|
|
|
2019-03-08 08:51:37 -05:00
|
|
|
def error_handling(self, message):
|
|
|
|
self.src_cmd = gen_dummy(self.dummy_len)
|
2018-02-19 05:12:13 -05:00
|
|
|
|
2019-03-08 08:51:37 -05:00
|
|
|
if self.last:
|
|
|
|
self.last_time = float(_playlist.start * 3600 - 5)
|
|
|
|
self.first = False
|
|
|
|
else:
|
|
|
|
self.last_time = (
|
|
|
|
get_time('full_sec') + self.dummy_len
|
|
|
|
+ _buffer.length + _buffer.tol
|
|
|
|
)
|
2018-02-19 05:12:13 -05:00
|
|
|
|
2019-03-08 08:51:37 -05:00
|
|
|
if 0 <= self.last_time < _playlist.start * 3600:
|
|
|
|
self.last_time += 86400
|
2018-01-07 07:58:45 -05:00
|
|
|
|
2019-03-08 08:51:37 -05:00
|
|
|
self.first = True
|
2018-01-07 07:58:45 -05:00
|
|
|
|
2019-03-10 16:24:03 -04:00
|
|
|
mailer(message, get_time(None), self.json_file)
|
|
|
|
logger.error('{} {}'.format(message, self.json_file))
|
2018-01-07 07:58:45 -05:00
|
|
|
|
2019-03-08 08:51:37 -05:00
|
|
|
self.begin = get_time('full_sec') + _buffer.length + _buffer.tol
|
|
|
|
self.last = False
|
|
|
|
self.dummy_len = 60
|
|
|
|
self.last_mod_time = 0.0
|
2018-01-16 03:31:59 -05:00
|
|
|
|
2019-03-08 10:41:22 -05:00
|
|
|
def next(self):
|
2019-03-08 08:51:37 -05:00
|
|
|
while True:
|
|
|
|
self.get_playlist()
|
2019-03-06 09:06:06 -05:00
|
|
|
|
2019-03-08 08:51:37 -05:00
|
|
|
if self.clip_nodes is None:
|
2019-03-08 10:41:22 -05:00
|
|
|
yield self.src_cmd
|
|
|
|
continue
|
2018-02-13 08:23:34 -05:00
|
|
|
|
2018-03-29 04:30:18 -04:00
|
|
|
# when last clip is None or a dummy,
|
|
|
|
# we have to jump to the right place in the playlist
|
2019-03-08 08:51:37 -05:00
|
|
|
self.first, self.last_time = check_last_item(
|
|
|
|
self.src_cmd, self.last_time, self.last)
|
2018-03-29 04:30:18 -04:00
|
|
|
|
2019-03-09 14:12:20 -05:00
|
|
|
# get start time from playlist
|
2019-03-08 08:51:37 -05:00
|
|
|
if "begin" in self.clip_nodes:
|
|
|
|
h, m, s = self.clip_nodes["begin"].split(':')
|
2019-03-06 09:06:06 -05:00
|
|
|
if is_float(h) and is_float(m) and is_float(s):
|
2019-03-08 08:51:37 -05:00
|
|
|
self.begin = float(h) * 3600 + float(m) * 60 + float(s)
|
2019-03-06 09:06:06 -05:00
|
|
|
else:
|
2019-03-08 08:51:37 -05:00
|
|
|
self.begin = self.last_time
|
2019-03-06 09:06:06 -05:00
|
|
|
else:
|
|
|
|
# when clip_nodes["begin"] is not set in playlist,
|
|
|
|
# start from current time
|
2019-03-08 08:51:37 -05:00
|
|
|
self.begin = get_time('full_sec')
|
2019-03-06 09:06:06 -05:00
|
|
|
|
2018-02-28 15:29:42 -05:00
|
|
|
# loop through all clips in playlist
|
2019-03-06 09:06:06 -05:00
|
|
|
# TODO: index we need for blend out logo on ad
|
2019-03-08 08:51:37 -05:00
|
|
|
for index, node in enumerate(self.clip_nodes["program"]):
|
2018-08-15 06:15:06 -04:00
|
|
|
if _playlist.map_ext:
|
2018-08-15 09:29:42 -04:00
|
|
|
_ext = literal_eval(_playlist.map_ext)
|
2019-03-08 08:51:37 -05:00
|
|
|
src = node["source"].replace(
|
2018-08-15 09:29:42 -04:00
|
|
|
_ext[0], _ext[1])
|
2018-08-15 06:15:06 -04:00
|
|
|
else:
|
2019-03-08 08:51:37 -05:00
|
|
|
src = node["source"]
|
2018-08-15 06:15:06 -04:00
|
|
|
|
2019-03-08 08:51:37 -05:00
|
|
|
seek = node["in"] if is_float(node["in"]) else 0
|
|
|
|
out = node["out"] if is_float(node["out"]) else self.dummy_len
|
|
|
|
duration = node["duration"] if \
|
|
|
|
is_float(node["duration"]) else self.dummy_len
|
2018-01-16 03:31:59 -05:00
|
|
|
|
2018-02-19 05:12:13 -05:00
|
|
|
# first time we end up here
|
2019-03-08 08:51:37 -05:00
|
|
|
if self.first and self.last_time < self.begin + duration:
|
2018-02-19 05:12:13 -05:00
|
|
|
# calculate seek time
|
2019-03-08 08:51:37 -05:00
|
|
|
seek = self.last_time - self.begin + seek
|
|
|
|
self.src_cmd, self.time_left = gen_input(
|
|
|
|
src, self.begin, duration, seek, out, False
|
2018-02-19 05:12:13 -05:00
|
|
|
)
|
2018-01-16 03:31:59 -05:00
|
|
|
|
2019-03-08 08:51:37 -05:00
|
|
|
self.first = False
|
|
|
|
self.last_time = self.begin
|
2018-02-19 05:12:13 -05:00
|
|
|
break
|
2019-03-08 08:51:37 -05:00
|
|
|
elif self.last_time and self.last_time < self.begin:
|
|
|
|
if node == self.clip_nodes["program"][-1]:
|
2019-03-09 14:12:20 -05:00
|
|
|
self.last = True
|
2018-02-19 05:12:13 -05:00
|
|
|
else:
|
2019-03-09 14:12:20 -05:00
|
|
|
self.last = False
|
2018-02-13 08:23:34 -05:00
|
|
|
|
2019-03-08 08:51:37 -05:00
|
|
|
check_sync(self.begin)
|
2018-02-13 08:23:34 -05:00
|
|
|
|
2019-03-08 08:51:37 -05:00
|
|
|
self.src_cmd, self.time_left = gen_input(
|
2019-03-09 14:12:20 -05:00
|
|
|
src, self.begin, duration, seek, out, self.last
|
2018-02-19 05:12:13 -05:00
|
|
|
)
|
|
|
|
|
2019-03-08 08:51:37 -05:00
|
|
|
if self.time_left is None:
|
2018-03-20 16:33:21 -04:00
|
|
|
# normal behavior
|
2019-03-08 08:51:37 -05:00
|
|
|
self.last_time = self.begin
|
|
|
|
elif self.time_left > 0.0:
|
2018-03-20 16:33:21 -04:00
|
|
|
# when playlist is finish and we have time left
|
2019-03-08 08:51:37 -05:00
|
|
|
self.list_date = get_date(False)
|
|
|
|
self.last_time = self.begin
|
|
|
|
self.dummy_len = self.time_left
|
2018-03-20 16:33:21 -04:00
|
|
|
|
|
|
|
else:
|
|
|
|
# when there is no time left and we are in time,
|
|
|
|
# set right values for new playlist
|
2019-03-08 08:51:37 -05:00
|
|
|
self.list_date = get_date(False)
|
|
|
|
self.last_time = float(_playlist.start * 3600 - 5)
|
|
|
|
self.last_mod_time = 0.0
|
2018-03-20 16:33:21 -04:00
|
|
|
|
2018-02-19 05:12:13 -05:00
|
|
|
break
|
2019-03-06 09:06:06 -05:00
|
|
|
|
2019-03-08 08:51:37 -05:00
|
|
|
self.begin += out - seek
|
2018-02-19 05:12:13 -05:00
|
|
|
else:
|
2019-03-06 09:06:06 -05:00
|
|
|
# when we reach currect end, stop script
|
2019-03-08 08:51:37 -05:00
|
|
|
if "begin" not in self.clip_nodes or \
|
|
|
|
"length" not in self.clip_nodes and \
|
|
|
|
self.begin < get_time('full_sec'):
|
2019-03-06 09:06:06 -05:00
|
|
|
logger.info('Playlist reach End!')
|
|
|
|
return
|
|
|
|
|
2018-02-19 05:12:13 -05:00
|
|
|
# when playlist exist but is empty, or not long enough,
|
|
|
|
# generate dummy and send log
|
2019-03-08 08:51:37 -05:00
|
|
|
self.error_handling('Playlist is not valid!')
|
2018-02-19 05:12:13 -05:00
|
|
|
|
2019-03-08 08:51:37 -05:00
|
|
|
if self.src_cmd is not None:
|
2019-03-08 10:41:22 -05:00
|
|
|
yield self.src_cmd
|
2018-01-07 07:58:45 -05:00
|
|
|
|
|
|
|
|
|
|
|
# independent thread for clip preparation
|
2019-03-08 08:51:37 -05:00
|
|
|
def play_clips(out_file, GetSourceIter):
|
|
|
|
# send current file to buffer stdin
|
2019-03-09 14:12:20 -05:00
|
|
|
iter = GetSourceIter()
|
2018-01-10 05:06:20 -05:00
|
|
|
|
2019-03-08 10:41:22 -05:00
|
|
|
for src_cmd in iter.next():
|
2018-04-29 12:07:42 -04:00
|
|
|
if _pre_comp.copy:
|
|
|
|
ff_pre_settings = _pre_comp.copy_settings
|
|
|
|
else:
|
|
|
|
ff_pre_settings = [
|
|
|
|
'-s', '{}x{}'.format(_pre_comp.w, _pre_comp.h),
|
|
|
|
'-aspect', str(_pre_comp.aspect),
|
|
|
|
'-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),
|
2019-03-10 16:24:03 -04:00
|
|
|
'-bufsize', '{}k'.format(_pre_comp.v_bufsize),
|
2019-03-05 09:01:54 -05:00
|
|
|
'-c:a', 's302m', '-strict', '-2', '-ar', '48000', '-ac', '2',
|
2018-04-29 12:07:42 -04:00
|
|
|
'-threads', '2', '-f', 'mpegts', '-'
|
|
|
|
]
|
|
|
|
|
2018-01-16 03:31:59 -05:00
|
|
|
try:
|
2019-03-08 08:51:37 -05:00
|
|
|
logger.info('play: {}'.format(src_cmd))
|
|
|
|
|
2018-03-20 16:33:21 -04:00
|
|
|
file_piper = Popen(
|
2018-01-07 07:58:45 -05:00
|
|
|
[
|
2018-02-02 01:49:43 -05:00
|
|
|
'ffmpeg', '-v', 'error', '-hide_banner', '-nostats'
|
2018-04-29 12:07:42 -04:00
|
|
|
] + src_cmd + list(ff_pre_settings),
|
2018-02-13 08:23:34 -05:00
|
|
|
stdout=PIPE,
|
|
|
|
bufsize=0
|
2018-01-07 07:58:45 -05:00
|
|
|
)
|
|
|
|
|
2018-03-20 16:33:21 -04:00
|
|
|
copyfileobj(file_piper.stdout, out_file)
|
2018-01-07 07:58:45 -05:00
|
|
|
finally:
|
2018-03-20 16:33:21 -04:00
|
|
|
file_piper.wait()
|
2018-01-07 07:58:45 -05:00
|
|
|
|
|
|
|
|
|
|
|
def main():
|
2019-03-10 16:24:03 -04:00
|
|
|
year = get_date(False).split('-')[0]
|
2018-01-07 07:58:45 -05:00
|
|
|
try:
|
|
|
|
# open a buffer for the streaming pipeline
|
|
|
|
# stdin get the files loop
|
|
|
|
# stdout pipes to ffmpeg rtmp streaming
|
|
|
|
mbuffer = Popen(
|
2018-08-13 15:32:25 -04:00
|
|
|
[_buffer.cli] + list(_buffer.cmd)
|
2019-03-11 09:56:56 -04:00
|
|
|
+ ['{}k'.format(calc_buffer_size())],
|
2018-01-07 07:58:45 -05:00
|
|
|
stdin=PIPE,
|
2018-02-13 08:23:34 -05:00
|
|
|
stdout=PIPE,
|
|
|
|
bufsize=0
|
2018-01-07 07:58:45 -05:00
|
|
|
)
|
|
|
|
try:
|
2018-11-25 08:24:47 -05:00
|
|
|
if _playout.preview:
|
|
|
|
# preview playout to player
|
2019-03-04 11:54:36 -05:00
|
|
|
playout = Popen([
|
|
|
|
'ffplay', '-v', 'error',
|
|
|
|
'-hide_banner', '-nostats', '-i', 'pipe:0'],
|
2018-11-25 13:34:18 -05:00
|
|
|
stdin=mbuffer.stdout,
|
|
|
|
bufsize=0
|
|
|
|
)
|
2018-04-29 12:07:42 -04:00
|
|
|
else:
|
2018-11-25 08:24:47 -05:00
|
|
|
# playout to rtmp
|
|
|
|
if _pre_comp.copy:
|
|
|
|
playout_pre = [
|
|
|
|
'ffmpeg', '-v', 'info', '-hide_banner', '-nostats',
|
|
|
|
'-re', '-i', 'pipe:0', '-c', 'copy'
|
|
|
|
] + _playout.post_comp_copy
|
|
|
|
else:
|
|
|
|
playout_pre = [
|
|
|
|
'ffmpeg', '-v', 'info', '-hide_banner', '-nostats',
|
|
|
|
'-re', '-thread_queue_size', '256',
|
|
|
|
'-fflags', '+igndts', '-i', 'pipe:0',
|
|
|
|
'-fflags', '+genpts'
|
|
|
|
] + _playout.logo + _playout.filter + \
|
|
|
|
_playout.post_comp_video + \
|
|
|
|
_playout.post_comp_audio
|
|
|
|
|
|
|
|
playout = Popen(
|
|
|
|
list(playout_pre)
|
|
|
|
+ [
|
|
|
|
'-metadata', 'service_name=' + _playout.name,
|
|
|
|
'-metadata', 'service_provider=' + _playout.provider,
|
|
|
|
'-metadata', 'year=' + year
|
|
|
|
] + list(_playout.post_comp_extra)
|
|
|
|
+ [
|
|
|
|
_playout.out_addr
|
|
|
|
],
|
|
|
|
stdin=mbuffer.stdout,
|
|
|
|
bufsize=0
|
|
|
|
)
|
2018-01-07 07:58:45 -05:00
|
|
|
|
|
|
|
play_thread = Thread(
|
2018-01-16 03:31:59 -05:00
|
|
|
name='play_clips', target=play_clips, args=(
|
|
|
|
mbuffer.stdin,
|
2019-03-09 14:12:20 -05:00
|
|
|
GetSourceIter,
|
2018-01-16 03:31:59 -05:00
|
|
|
)
|
2018-01-07 07:58:45 -05:00
|
|
|
)
|
|
|
|
play_thread.daemon = True
|
|
|
|
play_thread.start()
|
|
|
|
|
2019-03-10 16:24:03 -04:00
|
|
|
check_process(play_thread, playout, mbuffer)
|
2018-01-07 07:58:45 -05:00
|
|
|
finally:
|
|
|
|
playout.wait()
|
|
|
|
finally:
|
|
|
|
mbuffer.wait()
|
|
|
|
|
|
|
|
|
|
|
|
if __name__ == '__main__':
|
|
|
|
main()
|