migrate to yaml config file
This commit is contained in:
parent
6c4bb61bf7
commit
472b11965e
147
ffplayout.py
147
ffplayout.py
@ -18,7 +18,6 @@
|
||||
|
||||
# ------------------------------------------------------------------------------
|
||||
|
||||
import configparser
|
||||
import glob
|
||||
import json
|
||||
import logging
|
||||
@ -33,6 +32,7 @@ import ssl
|
||||
import sys
|
||||
import tempfile
|
||||
import time
|
||||
import yaml
|
||||
from argparse import ArgumentParser
|
||||
from datetime import date, datetime, timedelta
|
||||
from email.mime.multipart import MIMEMultipart
|
||||
@ -142,36 +142,52 @@ _WINDOWS = os.name == 'nt'
|
||||
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():
|
||||
"""
|
||||
this function can reload most settings from configuration file,
|
||||
the change does not take effect immediately, but with the after next file,
|
||||
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:
|
||||
cfg.read(stdin_args.config)
|
||||
elif os.path.isfile('/etc/ffplayout/ffplayout.conf'):
|
||||
cfg.read('/etc/ffplayout/ffplayout.conf')
|
||||
cfg = read_config(stdin_args.config)
|
||||
elif os.path.isfile('/etc/ffplayout/ffplayout.yml'):
|
||||
cfg = read_config('/etc/ffplayout/ffplayout.yml')
|
||||
else:
|
||||
cfg.read('ffplayout.conf')
|
||||
cfg = read_config('ffplayout.yml')
|
||||
|
||||
if stdin_args.start:
|
||||
p_start = str_to_sec(stdin_args.start)
|
||||
else:
|
||||
p_start = str_to_sec(cfg.get('PLAYLIST', 'day_start'))
|
||||
p_start = str_to_sec(cfg['playlist']['day_start'])
|
||||
|
||||
if not p_start:
|
||||
p_start = get_time('full_sec')
|
||||
@ -179,65 +195,61 @@ def load_config():
|
||||
if stdin_args.length:
|
||||
p_length = str_to_sec(stdin_args.length)
|
||||
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.threshold = cfg.getfloat('GENERAL', 'stop_threshold')
|
||||
_general.stop = cfg['general']['stop_on_error']
|
||||
_general.threshold = cfg['general']['stop_threshold']
|
||||
|
||||
_mail.subject = cfg.get('MAIL', 'subject')
|
||||
_mail.server = cfg.get('MAIL', 'smpt_server')
|
||||
_mail.port = cfg.getint('MAIL', 'smpt_port')
|
||||
_mail.s_addr = cfg.get('MAIL', 'sender_addr')
|
||||
_mail.s_pass = cfg.get('MAIL', 'sender_pass')
|
||||
_mail.recip = cfg.get('MAIL', 'recipient')
|
||||
_mail.level = cfg.get('MAIL', 'mail_level')
|
||||
_mail.subject = cfg['mail']['subject']
|
||||
_mail.server = cfg['mail']['smpt_server']
|
||||
_mail.port = cfg['mail']['smpt_port']
|
||||
_mail.s_addr = cfg['mail']['sender_addr']
|
||||
_mail.s_pass = cfg['mail']['sender_pass']
|
||||
_mail.recip = cfg['mail']['recipient']
|
||||
_mail.level = cfg['mail']['mail_level']
|
||||
|
||||
_pre_comp.add_logo = cfg.getboolean('PRE_COMPRESS', 'add_logo')
|
||||
_pre_comp.logo = cfg.get('PRE_COMPRESS', 'logo')
|
||||
_pre_comp.opacity = cfg.get('PRE_COMPRESS', 'logo_opacity')
|
||||
_pre_comp.logo_filter = cfg.get('PRE_COMPRESS', 'logo_filter')
|
||||
_pre_comp.add_loudnorm = cfg.getboolean('PRE_COMPRESS', 'add_loudnorm')
|
||||
_pre_comp.loud_i = cfg.getfloat('PRE_COMPRESS', 'loud_I')
|
||||
_pre_comp.loud_tp = cfg.getfloat('PRE_COMPRESS', 'loud_TP')
|
||||
_pre_comp.loud_lra = cfg.getfloat('PRE_COMPRESS', 'loud_LRA')
|
||||
_pre_comp.add_logo = cfg['pre_compress']['add_logo']
|
||||
_pre_comp.logo = cfg['pre_compress']['logo']
|
||||
_pre_comp.opacity = cfg['pre_compress']['logo_opacity']
|
||||
_pre_comp.logo_filter = cfg['pre_compress']['logo_filter']
|
||||
_pre_comp.add_loudnorm = cfg['pre_compress']['add_loudnorm']
|
||||
_pre_comp.loud_i = cfg['pre_compress']['loud_I']
|
||||
_pre_comp.loud_tp = cfg['pre_compress']['loud_TP']
|
||||
_pre_comp.loud_lra = cfg['pre_compress']['loud_LRA']
|
||||
|
||||
_playlist.mode = cfg.getboolean('PLAYLIST', 'playlist_mode')
|
||||
_playlist.path = cfg.get('PLAYLIST', 'path')
|
||||
_playlist.mode = cfg['playlist']['playlist_mode']
|
||||
_playlist.path = cfg['playlist']['path']
|
||||
_playlist.start = p_start
|
||||
_playlist.length = p_length
|
||||
|
||||
_storage.path = cfg.get('STORAGE', 'path')
|
||||
_storage.filler = cfg.get('STORAGE', 'filler_clip')
|
||||
_storage.extensions = json.loads(cfg.get('STORAGE', 'extensions'))
|
||||
_storage.shuffle = cfg.getboolean('STORAGE', 'shuffle')
|
||||
_storage.path = cfg['storage']['path']
|
||||
_storage.filler = cfg['storage']['filler_clip']
|
||||
_storage.extensions = cfg['storage']['extensions']
|
||||
_storage.shuffle = cfg['storage']['shuffle']
|
||||
|
||||
_text.add_text = cfg.getboolean('TEXT', 'add_text')
|
||||
_text.address = cfg.get('TEXT', 'bind_address')
|
||||
_text.fontfile = cfg.get('TEXT', 'fontfile')
|
||||
_text.add_text = cfg['text']['add_text']
|
||||
_text.address = cfg['text']['bind_address']
|
||||
_text.fontfile = cfg['text']['fontfile']
|
||||
|
||||
if _init.load:
|
||||
_log.to_file = cfg.getboolean('LOGGING', 'log_to_file')
|
||||
_log.path = cfg.get('LOGGING', 'log_path')
|
||||
_log.level = cfg.get('LOGGING', 'log_level')
|
||||
_log.ff_level = cfg.get('LOGGING', 'ffmpeg_level')
|
||||
_log.to_file = cfg['logging']['log_to_file']
|
||||
_log.path = cfg['logging']['log_path']
|
||||
_log.level = cfg['logging']['log_level']
|
||||
_log.ff_level = cfg['logging']['ffmpeg_level']
|
||||
|
||||
_pre_comp.w = cfg.getint('PRE_COMPRESS', 'width')
|
||||
_pre_comp.h = cfg.getint('PRE_COMPRESS', 'height')
|
||||
_pre_comp.aspect = cfg.getfloat('PRE_COMPRESS', 'aspect')
|
||||
_pre_comp.fps = cfg.getint('PRE_COMPRESS', 'fps')
|
||||
_pre_comp.v_bitrate = cfg.getint('PRE_COMPRESS', 'width') * 50
|
||||
_pre_comp.v_bufsize = cfg.getint('PRE_COMPRESS', 'width') * 50 / 2
|
||||
_pre_comp.w = cfg['pre_compress']['width']
|
||||
_pre_comp.h = cfg['pre_compress']['height']
|
||||
_pre_comp.aspect = cfg['pre_compress']['aspect']
|
||||
_pre_comp.fps = cfg['pre_compress']['fps']
|
||||
_pre_comp.v_bitrate = cfg['pre_compress']['width'] * 50
|
||||
_pre_comp.v_bufsize = cfg['pre_compress']['width'] * 50 / 2
|
||||
|
||||
_playout.preview = cfg.getboolean('OUT', 'preview')
|
||||
_playout.name = cfg.get('OUT', 'service_name')
|
||||
_playout.provider = cfg.get('OUT', 'service_provider')
|
||||
_playout.out_addr = cfg.get('OUT', 'out_addr')
|
||||
_playout.post_comp_video = json.loads(
|
||||
cfg.get('OUT', 'post_comp_video'))
|
||||
_playout.post_comp_audio = json.loads(
|
||||
cfg.get('OUT', 'post_comp_audio'))
|
||||
_playout.post_comp_extra = json.loads(
|
||||
cfg.get('OUT', 'post_comp_extra'))
|
||||
_playout.preview = cfg['out']['preview']
|
||||
_playout.name = cfg['out']['service_name']
|
||||
_playout.provider = cfg['out']['service_provider']
|
||||
_playout.post_comp_param = dict_to_list(
|
||||
cfg['out']['post_ffmpeg_param'])
|
||||
_playout.out_addr = cfg['out']['out_addr']
|
||||
|
||||
_init.load = False
|
||||
|
||||
@ -1606,12 +1618,11 @@ def main():
|
||||
_ff.encoder = Popen([
|
||||
'ffmpeg', '-v', _log.ff_level.lower(), '-hide_banner',
|
||||
'-nostats', '-re', '-thread_queue_size', '256',
|
||||
'-i', 'pipe:0'] + overlay + _playout.post_comp_video
|
||||
+ _playout.post_comp_audio + [
|
||||
'-i', 'pipe:0'] + overlay + [
|
||||
'-metadata', 'service_name=' + _playout.name,
|
||||
'-metadata', 'service_provider=' + _playout.provider,
|
||||
'-metadata', 'year={}'.format(year)
|
||||
] + _playout.post_comp_extra + [_playout.out_addr],
|
||||
] + _playout.post_comp_param + [_playout.out_addr],
|
||||
stdin=PIPE, stderr=PIPE)
|
||||
|
||||
enc_err_thread = Thread(target=ffmpeg_stderr_reader,
|
||||
|
144
ffplayout.yml
Normal file
144
ffplayout.yml
Normal 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"
|
Loading…
x
Reference in New Issue
Block a user