migrate to yaml config file

This commit is contained in:
Jonathan Baecker 2020-02-03 12:07:21 +01:00
parent 6c4bb61bf7
commit 472b11965e
2 changed files with 223 additions and 68 deletions

View File

@ -18,7 +18,6 @@
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
import configparser
import glob import glob
import json import json
import logging import logging
@ -33,6 +32,7 @@ import ssl
import sys import sys
import tempfile import tempfile
import time import time
import yaml
from argparse import ArgumentParser from argparse import ArgumentParser
from datetime import date, datetime, timedelta from datetime import date, datetime, timedelta
from email.mime.multipart import MIMEMultipart from email.mime.multipart import MIMEMultipart
@ -142,36 +142,52 @@ _WINDOWS = os.name == 'nt'
COPY_BUFSIZE = 1024 * 1024 if _WINDOWS else 64 * 1024 COPY_BUFSIZE = 1024 * 1024 if _WINDOWS else 64 * 1024
def str_to_sec(s):
if s in ['now', '', None, 'none']:
return None
else:
s = s.split(':')
try:
return float(s[0]) * 3600 + float(s[1]) * 60 + float(s[2])
except ValueError:
print('Wrong time format!')
sys.exit(1)
def read_config(path):
with open(path, 'r') as config_file:
return yaml.safe_load(config_file)
def dict_to_list(d):
li = []
for key, value in d.items():
if value:
li += ['-{}'.format(key), str(value)]
else:
li += ['-{}'.format(key)]
return li
def load_config(): def load_config():
""" """
this function can reload most settings from configuration file, this function can reload most settings from configuration file,
the change does not take effect immediately, but with the after next file, the change does not take effect immediately, but with the after next file,
some settings cannot be changed - like resolution, aspect, or output some settings cannot be changed - like resolution, aspect, or output
""" """
cfg = configparser.ConfigParser()
def str_to_sec(s):
if s in ['now', '', None, 'none']:
return None
else:
s = s.split(':')
try:
return float(s[0]) * 3600 + float(s[1]) * 60 + float(s[2])
except ValueError:
print('Wrong time format!')
sys.exit(1)
if stdin_args.config: if stdin_args.config:
cfg.read(stdin_args.config) cfg = read_config(stdin_args.config)
elif os.path.isfile('/etc/ffplayout/ffplayout.conf'): elif os.path.isfile('/etc/ffplayout/ffplayout.yml'):
cfg.read('/etc/ffplayout/ffplayout.conf') cfg = read_config('/etc/ffplayout/ffplayout.yml')
else: else:
cfg.read('ffplayout.conf') cfg = read_config('ffplayout.yml')
if stdin_args.start: if stdin_args.start:
p_start = str_to_sec(stdin_args.start) p_start = str_to_sec(stdin_args.start)
else: else:
p_start = str_to_sec(cfg.get('PLAYLIST', 'day_start')) p_start = str_to_sec(cfg['playlist']['day_start'])
if not p_start: if not p_start:
p_start = get_time('full_sec') p_start = get_time('full_sec')
@ -179,65 +195,61 @@ def load_config():
if stdin_args.length: if stdin_args.length:
p_length = str_to_sec(stdin_args.length) p_length = str_to_sec(stdin_args.length)
else: else:
p_length = str_to_sec(cfg.get('PLAYLIST', 'length')) p_length = str_to_sec(cfg['playlist']['length'])
_general.stop = cfg.getboolean('GENERAL', 'stop_on_error') _general.stop = cfg['general']['stop_on_error']
_general.threshold = cfg.getfloat('GENERAL', 'stop_threshold') _general.threshold = cfg['general']['stop_threshold']
_mail.subject = cfg.get('MAIL', 'subject') _mail.subject = cfg['mail']['subject']
_mail.server = cfg.get('MAIL', 'smpt_server') _mail.server = cfg['mail']['smpt_server']
_mail.port = cfg.getint('MAIL', 'smpt_port') _mail.port = cfg['mail']['smpt_port']
_mail.s_addr = cfg.get('MAIL', 'sender_addr') _mail.s_addr = cfg['mail']['sender_addr']
_mail.s_pass = cfg.get('MAIL', 'sender_pass') _mail.s_pass = cfg['mail']['sender_pass']
_mail.recip = cfg.get('MAIL', 'recipient') _mail.recip = cfg['mail']['recipient']
_mail.level = cfg.get('MAIL', 'mail_level') _mail.level = cfg['mail']['mail_level']
_pre_comp.add_logo = cfg.getboolean('PRE_COMPRESS', 'add_logo') _pre_comp.add_logo = cfg['pre_compress']['add_logo']
_pre_comp.logo = cfg.get('PRE_COMPRESS', 'logo') _pre_comp.logo = cfg['pre_compress']['logo']
_pre_comp.opacity = cfg.get('PRE_COMPRESS', 'logo_opacity') _pre_comp.opacity = cfg['pre_compress']['logo_opacity']
_pre_comp.logo_filter = cfg.get('PRE_COMPRESS', 'logo_filter') _pre_comp.logo_filter = cfg['pre_compress']['logo_filter']
_pre_comp.add_loudnorm = cfg.getboolean('PRE_COMPRESS', 'add_loudnorm') _pre_comp.add_loudnorm = cfg['pre_compress']['add_loudnorm']
_pre_comp.loud_i = cfg.getfloat('PRE_COMPRESS', 'loud_I') _pre_comp.loud_i = cfg['pre_compress']['loud_I']
_pre_comp.loud_tp = cfg.getfloat('PRE_COMPRESS', 'loud_TP') _pre_comp.loud_tp = cfg['pre_compress']['loud_TP']
_pre_comp.loud_lra = cfg.getfloat('PRE_COMPRESS', 'loud_LRA') _pre_comp.loud_lra = cfg['pre_compress']['loud_LRA']
_playlist.mode = cfg.getboolean('PLAYLIST', 'playlist_mode') _playlist.mode = cfg['playlist']['playlist_mode']
_playlist.path = cfg.get('PLAYLIST', 'path') _playlist.path = cfg['playlist']['path']
_playlist.start = p_start _playlist.start = p_start
_playlist.length = p_length _playlist.length = p_length
_storage.path = cfg.get('STORAGE', 'path') _storage.path = cfg['storage']['path']
_storage.filler = cfg.get('STORAGE', 'filler_clip') _storage.filler = cfg['storage']['filler_clip']
_storage.extensions = json.loads(cfg.get('STORAGE', 'extensions')) _storage.extensions = cfg['storage']['extensions']
_storage.shuffle = cfg.getboolean('STORAGE', 'shuffle') _storage.shuffle = cfg['storage']['shuffle']
_text.add_text = cfg.getboolean('TEXT', 'add_text') _text.add_text = cfg['text']['add_text']
_text.address = cfg.get('TEXT', 'bind_address') _text.address = cfg['text']['bind_address']
_text.fontfile = cfg.get('TEXT', 'fontfile') _text.fontfile = cfg['text']['fontfile']
if _init.load: if _init.load:
_log.to_file = cfg.getboolean('LOGGING', 'log_to_file') _log.to_file = cfg['logging']['log_to_file']
_log.path = cfg.get('LOGGING', 'log_path') _log.path = cfg['logging']['log_path']
_log.level = cfg.get('LOGGING', 'log_level') _log.level = cfg['logging']['log_level']
_log.ff_level = cfg.get('LOGGING', 'ffmpeg_level') _log.ff_level = cfg['logging']['ffmpeg_level']
_pre_comp.w = cfg.getint('PRE_COMPRESS', 'width') _pre_comp.w = cfg['pre_compress']['width']
_pre_comp.h = cfg.getint('PRE_COMPRESS', 'height') _pre_comp.h = cfg['pre_compress']['height']
_pre_comp.aspect = cfg.getfloat('PRE_COMPRESS', 'aspect') _pre_comp.aspect = cfg['pre_compress']['aspect']
_pre_comp.fps = cfg.getint('PRE_COMPRESS', 'fps') _pre_comp.fps = cfg['pre_compress']['fps']
_pre_comp.v_bitrate = cfg.getint('PRE_COMPRESS', 'width') * 50 _pre_comp.v_bitrate = cfg['pre_compress']['width'] * 50
_pre_comp.v_bufsize = cfg.getint('PRE_COMPRESS', 'width') * 50 / 2 _pre_comp.v_bufsize = cfg['pre_compress']['width'] * 50 / 2
_playout.preview = cfg.getboolean('OUT', 'preview') _playout.preview = cfg['out']['preview']
_playout.name = cfg.get('OUT', 'service_name') _playout.name = cfg['out']['service_name']
_playout.provider = cfg.get('OUT', 'service_provider') _playout.provider = cfg['out']['service_provider']
_playout.out_addr = cfg.get('OUT', 'out_addr') _playout.post_comp_param = dict_to_list(
_playout.post_comp_video = json.loads( cfg['out']['post_ffmpeg_param'])
cfg.get('OUT', 'post_comp_video')) _playout.out_addr = cfg['out']['out_addr']
_playout.post_comp_audio = json.loads(
cfg.get('OUT', 'post_comp_audio'))
_playout.post_comp_extra = json.loads(
cfg.get('OUT', 'post_comp_extra'))
_init.load = False _init.load = False
@ -1606,12 +1618,11 @@ def main():
_ff.encoder = Popen([ _ff.encoder = Popen([
'ffmpeg', '-v', _log.ff_level.lower(), '-hide_banner', 'ffmpeg', '-v', _log.ff_level.lower(), '-hide_banner',
'-nostats', '-re', '-thread_queue_size', '256', '-nostats', '-re', '-thread_queue_size', '256',
'-i', 'pipe:0'] + overlay + _playout.post_comp_video '-i', 'pipe:0'] + overlay + [
+ _playout.post_comp_audio + [
'-metadata', 'service_name=' + _playout.name, '-metadata', 'service_name=' + _playout.name,
'-metadata', 'service_provider=' + _playout.provider, '-metadata', 'service_provider=' + _playout.provider,
'-metadata', 'year={}'.format(year) '-metadata', 'year={}'.format(year)
] + _playout.post_comp_extra + [_playout.out_addr], ] + _playout.post_comp_param + [_playout.out_addr],
stdin=PIPE, stderr=PIPE) stdin=PIPE, stderr=PIPE)
enc_err_thread = Thread(target=ffmpeg_stderr_reader, enc_err_thread = Thread(target=ffmpeg_stderr_reader,

144
ffplayout.yml Normal file
View File

@ -0,0 +1,144 @@
# 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/>.
# ------------------------------------------------------------------------------
# sometimes it can happen, that a file is corrupt but still playable,
# this can produce an streaming error over all following files
# the only way in this case is, to stop ffplayout and start it again
# here we only say it can stop, the starting process is in your hand
# best way is a systemd serivce on linux
# stop_threshold: stop ffplayout, if it is async in time above this value
general:
stop_on_error: True
stop_threshold: 11
# send error messages to email address, like:
# missing playlist
# unvalid json format
# missing clip path
# leave recipient blank, if you don't need this
# mail_level can be: WARNING, ERROR
mail:
subject: "Playout Error"
smpt_server: "mail.example.org"
smpt_port: 587
sender_addr: "ffplayout@example.org"
sender_pass: "12345"
recipient:
mail_level: "ERROR"
# Logging to file
# if log_to_file = False > log to console
# path to /var/log/ only if you run this program as deamon
# log_level can be: DEBUG, INFO, WARNING, ERROR
# ffmpeg_level can be: INFO, WARNING, ERROR
logging:
log_to_file: True
log_path: "/var/log/ffplayout/"
log_level: "DEBUG"
ffmpeg_level: "ERROR"
# output settings for the pre-compression
# all clips get prepared in that way,
# so the input for the final compression is unique
# aspect mus be a float number
# logo is only used if the path exist
# with logo_opacity logo can make transparent
# with logo_filter = overlay=W-w-12:12 you can modify the logo position
# with use_loudnorm you can activate single pass EBU R128 loudness normalization
# loud_* can adjust the loudnorm filter
# INFO: output is progressive!
pre_compress:
width: 1024
height: 576
aspect: 1.778
fps: 25
add_logo: True
logo: "docs/logo.png"
logo_opacity: 0.7
logo_filter: "overlay=W-w-12:12"
add_loudnorm: False
loud_I: -18
loud_TP: -1.5
loud_LRA: 11
# playlist settings
# set playlist_mode to False if you want to play clips from the [STORAGE] section
# put only the root path here, for example: "/playlists"
# subfolders are readed by the script
# subfolders needs this structur:
# "/playlists/2018/01" (/playlists/year/month)
# day_start means at which time the playlist should start
# leave day_start blank when playlist should always start at the begin
# length represent the target length from playlist, when is blank real length will not consider
playlist:
playlist_mode: True
path: "/playlists"
day_start: "5:59:25"
length: "24:00:00"
# play ordered or ramdomly files from path
# filler_path are for the GUI only at the moment
# filler_clip is for fill the end to reach 24 hours, it will loop when is necessary
# extensions: search only files with this extension, can be a list
# set shuffle to True to pick files randomly
storage:
path: "/mediaStorage"
filler_path: "/mediaStorage/filler/filler-clips"
filler_clip: "/mediaStorage/filler/filler.mp4"
extensions:
- "*.mp4"
- "*.mkv"
shuffle: True
# overlay text in combination with messenger: https://github.com/ffplayout/messenger
# 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
text:
add_text: True
bind_address: "tcp://127.0.0.1:5555"
fontfile: "/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf"
# the final playout post compression
# set the settings to your needs
# preview works only on a desktop system with ffplay!! Set it to True, if you need it
out:
preview: False
service_name: "Live Stream"
service_provider: "example.org"
post_ffmpeg_param:
c:v: "libx264"
crf: "23"
x264-params: "keyint=50:min-keyint=25:scenecut=-1"
maxrate: "1300k"
bufsize: "2600k"
preset: "medium"
profile:v: "Main"
level: "3.1"
c:a: "aac"
ar: "44100"
b:a: "128k"
flags: +global_header
f: "flv"
out_addr: "rtmp://localhost/live/stream"