Merge pull request #51 from ffplayout/master

merge current master
This commit is contained in:
jb-alvarado 2020-05-08 12:42:24 +02:00 committed by GitHub
commit 2739da8917
8 changed files with 117 additions and 144 deletions

View File

@ -33,9 +33,9 @@ install:
if [ ! -f "/etc/ffplayout/ffplayout.yml" ]; then \
install -m 644 -o $(USER) -g $(USER) ffplayout.yml /etc/ffplayout/; \
fi
if [ -d "/etc/systemd/system" ] && [ ! -f "/etc/systemd/system/ffplayout.service" ]; then \
install -m 644 docs/ffplayout.service /etc/systemd/system/; \
sed -i "s/root/$(USER)/g" "/etc/systemd/system/ffplayout.service"; \
if [ -d "/etc/systemd/system" ] && [ ! -f "/etc/systemd/system/ffplayout-engine.service" ]; then \
install -m 644 docs/ffplayout-engine.service /etc/systemd/system/; \
sed -i "s/root/$(USER)/g" "/etc/systemd/system/ffplayout-engine.service"; \
fi
@echo ""
@echo "-------------------------------------------------------------------"
@ -49,7 +49,7 @@ clean:
uninstall:
rm -rf "/etc/ffplayout"
rm -rf "/var/log/ffplayout"
rm -rf "/etc/systemd/system/ffplayout.service"
rm -rf "/etc/systemd/system/ffplayout-engine.service"
if [ ! "$(CURRENT_DIR)" == "/opt/ffplayout-engine" ]; then \
rm -rf "/opt/ffplayout-engine"; \
fi

View File

@ -99,17 +99,15 @@ Subfolders is read by the script and needs this structur:
```YAML
storage:
path: "/mediaStorage"
filler_path: "/mediaStorage/filler/filler-clips"
filler_clip: "/mediaStorage/filler/filler.mp4"
extensions:
- "*.mp4"
- "*.mkv"
- ".mp4"
- ".mkv"
shuffle: True
```
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, add as many as you want.
Set `shuffle` to **True** to pick files randomly.
Play ordered or ramdomly files from path, `filler_clip` is for fill the end
to reach 24 hours, it will loop when is necessary. `extensions:` search only files
with this extension, add as many as you want. Set `shuffle` to **True** to pick files randomly.
---
@ -130,20 +128,20 @@ 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"
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"
```

View File

@ -19,7 +19,7 @@ Installation
- create playlists folder, in that format: **/playlists/year/month**
- set variables in config file to your needs
- use `docs/gen_playlist_from_subfolders.sh /path/to/mp4s/` as a starting point for your playlists (path in script needs to change)
- activate service and start it: `sudo systemctl enable ffplayout && sudo systemctl start ffplayout`
- activate service and start it: `sudo systemctl enable ffplayout-engine && sudo systemctl start ffplayout-engine`
Cleanup
-----
@ -35,7 +35,7 @@ The routine with `make` build a virtual environment with all dependencies, and i
Just copy the project where you want to have it, run inside `pip3 install -r requirements.txt`. For logging you have to create the folder **ffplayout** under **/var/log/**, or adjust the settings in config. **ffplayout.yml** have to go to **/etc/ffplayout/**, or should stay in same folder.
If you want to use the systemd service, edit the service file in **docs/ffplayout.service**, copy it to **/etc/systemd/system/** and activate it with: `sudo systemctl enable ffplayout`.
If you want to use the systemd service, edit the service file in **docs/ffplayout-engine.service**, copy it to **/etc/systemd/system/** and activate it with: `sudo systemctl enable ffplayout-engine`.
Using it Without Installation
-----

View File

@ -26,7 +26,8 @@ from ffplayout.folder import GetSourceFromFolder, MediaStore, MediaWatcher
from ffplayout.playlist import GetSourceFromPlaylist
from ffplayout.utils import (_ff, _log, _playlist, _playout, _pre_comp, _text,
ffmpeg_stderr_reader, get_date, messenger,
pre_audio_codec, stdin_args, terminate_processes)
pre_audio_codec, stdin_args, terminate_processes,
validate_ffmpeg_libs)
try:
if os.name != 'posix':
@ -65,7 +66,7 @@ def main():
_text.address
))
overlay = [
'-vf', "null,zmq=b='{}',drawtext=text='':fontfile='{}'".format(
'-vf', "null,zmq=b=tcp\\\://'{}',drawtext=text='':fontfile='{}'".format(
_text.address.replace(':', '\\:'), _text.fontfile)
]
@ -149,4 +150,6 @@ def main():
if __name__ == '__main__':
# check if ffmpeg contains all codecs and filters
validate_ffmpeg_libs()
main()

View File

@ -1,71 +1,42 @@
# 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:
helptext: 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.
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:
helptext: 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 or ERROR.
subject: "Playout Error"
smpt_server: "mail.example.org"
smpt_port: 587
sender_addr: "ffplayout@example.org"
sender_pass: "12345"
sender_pass: "abc123"
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:
helptext: 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.
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:
helptext: Settings for the pre-compression. All clips get prepared in that way,
so the input for the final compression is unique. 'aspect' must be a float
number. 'logo' is only used if the path exist. With 'logo_opacity' logo can
become 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. [Output is
always progressive!]
width: 1024
height: 576
aspect: 1.778
@ -79,66 +50,58 @@ pre_compress:
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:
helptext: 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_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:
helptext: Play ordered or ramdomly files from path. '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.
path: "/mediaStorage"
filler_path: "/mediaStorage/filler/filler-clips"
filler_clip: "/mediaStorage/filler/filler.mp4"
extensions:
- "*.mp4"
- "*.mkv"
- ".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"
helptext: 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.
add_text: False
bind_address: "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:
helptext: 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.
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"
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"

View File

@ -52,7 +52,7 @@ class MediaStore:
def fill(self):
for ext in _storage.extensions:
self.store.extend(
glob.glob(os.path.join(self.folder, '**', ext),
glob.glob(os.path.join(self.folder, '**', '*{}'.format(ext)),
recursive=True))
if _storage.shuffle:

View File

@ -27,17 +27,17 @@ import smtplib
import socket
import sys
import tempfile
import yaml
from argparse import ArgumentParser
from datetime import date, datetime, timedelta
from email.mime.multipart import MIMEMultipart
from email.mime.text import MIMEText
from email.utils import formatdate
from logging.handlers import TimedRotatingFileHandler
from subprocess import CalledProcessError, check_output
from subprocess import STDOUT, CalledProcessError, check_output
from threading import Thread
from types import SimpleNamespace
import yaml
# ------------------------------------------------------------------------------
# argument parsing
@ -140,17 +140,6 @@ def read_config(path):
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,
@ -228,8 +217,7 @@ def load_config():
_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.post_comp_param = cfg['out']['post_ffmpeg_param'].split(' ')
_playout.out_addr = cfg['out']['out_addr']
_init.load = False
@ -312,8 +300,8 @@ if _log.to_file and _log.path != 'none':
log_dir = os.path.join(base_dir, 'log')
os.makedirs(log_dir, exist_ok=True)
playout_log = os.path.join(log_dir, 'ffplayout.log')
decoder_log = os.path.join(log_dir, 'ffdecoder.log')
encoder_log = os.path.join(log_dir, 'ffencoder.log')
decoder_log = os.path.join(log_dir, 'decoder.log')
encoder_log = os.path.join(log_dir, 'encoder.log')
p_format = logging.Formatter('[%(asctime)s] [%(levelname)s] %(message)s')
f_format = logging.Formatter('[%(asctime)s] %(message)s')
@ -461,11 +449,12 @@ def ffmpeg_libs():
check which external libs are compiled in ffmpeg,
for using them later
"""
cmd = ['ffmpeg', '-version']
cmd = ['ffmpeg', '-filters']
libs = []
filters = []
try:
info = check_output(cmd).decode('UTF-8')
info = check_output(cmd, stderr=STDOUT).decode('UTF-8')
except CalledProcessError as err:
messenger.error('ffmpeg - libs could not be readed!\n'
'Processing is not possible. Error:\n{}'.format(err))
@ -478,14 +467,34 @@ def ffmpeg_libs():
for cfg in configs:
if '--enable-lib' in cfg:
libs.append(cfg.replace('--enable-', ''))
break
elif re.match(r'^(?!.*=) [TSC.]+', line):
filter_list = line.split()
if len(filter_list) > 3:
filters.append(filter_list[1])
return libs
return {'libs': libs, 'filters': filters}
FF_LIBS = ffmpeg_libs()
def validate_ffmpeg_libs():
if 'libx264' not in FF_LIBS['libs']:
playout_logger.error('ffmpeg contains no libx264!')
if 'libfdk-aac' not in FF_LIBS['libs']:
playout_logger.warning(
'ffmpeg contains no libfdk-aac! No high quality aac...')
if 'libtwolame' not in FF_LIBS['libs']:
playout_logger.warning(
'ffmpeg contains no libtwolame!'
' Loudness correction use mp2 audio codec...')
if 'tpad' not in FF_LIBS['filters']:
playout_logger.error('ffmpeg contains no tpad filter!')
if 'zmq' not in FF_LIBS['filters']:
playout_logger.error(
'ffmpeg contains no zmq filter! Text messages will not work...')
# ------------------------------------------------------------------------------
# probe media infos
# ------------------------------------------------------------------------------
@ -979,7 +988,7 @@ def pre_audio_codec():
and works not well together with the loudnorm filter
"""
if _pre_comp.add_loudnorm:
acodec = 'libtwolame' if 'libtwolame' in FF_LIBS else 'mp2'
acodec = 'libtwolame' if 'libtwolame' in FF_LIBS['libs'] else 'mp2'
audio = ['-c:a', acodec, '-b:a', '384k', '-ar', '48000', '-ac', '2']
else:
audio = ['-c:a', 's302m', '-strict', '-2', '-ar', '48000', '-ac', '2']