Modularize play mode, so it is possible to create a custom video source generator.

This commit is contained in:
jb-alvarado 2022-01-26 12:03:59 +01:00
parent fa66320fe0
commit ae427b948b
9 changed files with 89 additions and 113 deletions

View File

@ -19,15 +19,14 @@
This module plays the compressed output directly on the desktop.
"""
from importlib import import_module
from platform import system
from subprocess import PIPE, Popen
from threading import Thread
from ..folder import GetSourceFromFolder, MediaStore, MediaWatcher
from ..playlist import GetSourceFromPlaylist
from ..utils import (ff_proc, ffmpeg_stderr_reader, log, lower_third,
messenger, playlist, pre, pre_audio_codec, stdin_args,
sync_op, terminate_processes)
messenger, play, pre, pre_audio_codec, sync_op,
terminate_processes)
COPY_BUFSIZE = 1024 * 1024 if system() == 'Windows' else 65424
@ -72,20 +71,11 @@ def output():
enc_err_thread.daemon = True
enc_err_thread.start()
if playlist.mode and not stdin_args.folder:
watcher = None
get_source = GetSourceFromPlaylist()
else:
messenger.info('Start folder mode')
media = MediaStore()
watcher = MediaWatcher(media)
get_source = GetSourceFromFolder(media)
Iter = import_module(f'ffplayout.player.{play.mode}').GetSourceIter
get_source = Iter()
try:
for node in get_source.next():
if watcher is not None:
watcher.current_clip = node.get('source')
messenger.info(
f'Play for {node["out"] - node["seek"]:.2f} '
f'seconds: {node.get("source")}')
@ -119,15 +109,15 @@ def output():
messenger.debug(f'node: "{node}"')
messenger.debug(f'dec_cmd: "{dec_cmd}"')
messenger.debug(79 * '-')
terminate_processes(watcher)
terminate_processes(getattr(get_source, 'stop', None))
except SystemExit:
messenger.info('Got close command')
terminate_processes(watcher)
terminate_processes(getattr(get_source, 'stop', None))
except KeyboardInterrupt:
messenger.warning('Program terminated')
terminate_processes(watcher)
terminate_processes(getattr(get_source, 'stop', None))
# close encoder when nothing is to do anymore
if ff_proc.encoder.poll() is None:

View File

@ -16,19 +16,30 @@
# ------------------------------------------------------------------------------
"""
This module write the files compression directly to a hls (m3u8) playlist.
This module write the files compression directly to a hls (m3u8) playlist,
without pre- and post-processing.
Example config:
out:
stream_output: >-
-flags +cgop
-f hls
-hls_time 6
-hls_list_size 600
-hls_flags append_list+delete_segments+omit_endlist+program_date_time
-hls_segment_filename /var/www/srs/live/stream-%09d.ts /var/www/srs/live/stream.m3u8
"""
import re
from importlib import import_module
from pathlib import Path
from subprocess import PIPE, Popen
from threading import Thread
from ..folder import GetSourceFromFolder, MediaStore, MediaWatcher
from ..playlist import GetSourceFromPlaylist
from ..utils import (ff_proc, ffmpeg_stderr_reader, get_date, log, messenger,
playlist, playout, stdin_args, sync_op,
terminate_processes)
play, playout, sync_op, terminate_processes)
def clean_ts():
@ -38,7 +49,7 @@ def clean_ts():
then it checks if files on hard drive are older then this first *.ts
and if so delete them
"""
m3u8_files = [p for p in playout.hls_output if 'm3u8' in p]
m3u8_files = [p for p in playout.stream_output if 'm3u8' in p]
for m3u8_file in m3u8_files:
messenger.debug(f'cleanup *.ts files from: "{m3u8_file}"')
@ -67,20 +78,11 @@ def output():
sync_op.realtime = True
try:
if playlist.mode and not stdin_args.folder:
watcher = None
get_source = GetSourceFromPlaylist()
else:
messenger.info('Start folder mode')
media = MediaStore()
watcher = MediaWatcher(media)
get_source = GetSourceFromFolder(media)
Iter = import_module(f'ffplayout.player.{play.mode}').GetSourceIter
get_source = Iter()
try:
for node in get_source.next():
if watcher is not None:
watcher.current_clip = node.get('source')
messenger.info(f'Play: {node.get("source")}')
cmd = [
@ -90,7 +92,7 @@ def output():
'-metadata', f'service_name={playout.name}',
'-metadata', f'service_provider={playout.provider}',
'-metadata', f'year={year}'
] + playout.ffmpeg_param + playout.hls_output
] + playout.ffmpeg_param + playout.stream_output
messenger.debug(f'Encoder CMD: "{" ".join(cmd)}"')
@ -109,15 +111,15 @@ def output():
except BrokenPipeError:
messenger.error('Broken Pipe!')
terminate_processes(watcher)
terminate_processes(getattr(get_source, 'stop', None))
except SystemExit:
messenger.info('Got close command')
terminate_processes(watcher)
terminate_processes(getattr(get_source, 'stop', None))
except KeyboardInterrupt:
messenger.warning('Program terminated')
terminate_processes(watcher)
terminate_processes(getattr(get_source, 'stop', None))
# close encoder when nothing is to do anymore
if ff_proc.encoder.poll() is None:

View File

@ -15,7 +15,9 @@
# You should have received a copy of the GNU General Public License
# along with ffplayout. If not, see <http://www.gnu.org/licenses/>.
# ------------------------------------------------------------------------------
# -----------------------------------------------------------------------------
from importlib import import_module
from platform import system
from queue import Queue
from subprocess import PIPE, Popen
@ -23,11 +25,9 @@ from threading import Thread
from time import sleep
from ..filters.default import overlay_filter
from ..folder import GetSourceFromFolder, MediaStore, MediaWatcher
from ..playlist import GetSourceFromPlaylist
from ..utils import (ff_proc, ffmpeg_stderr_reader, get_date, get_time, ingest,
log, lower_third, messenger, playlist, playout, pre,
pre_audio_codec, stdin_args, sync_op, terminate_processes)
log, lower_third, messenger, play, playout, pre,
pre_audio_codec, sync_op, terminate_processes)
COPY_BUFSIZE = 1024 * 1024 if system() == 'Windows' else 65424
@ -71,7 +71,7 @@ def check_time(node, get_source):
clip_length = node['out'] - node['seek']
clip_end = current_time + clip_length
if playlist.mode and not stdin_args.folder and clip_end > current_time:
if play.mode == 'playlist' and clip_end > current_time:
get_source.first = True
@ -131,20 +131,11 @@ def output():
enc_err_thread.daemon = True
enc_err_thread.start()
if playlist.mode and not stdin_args.folder:
watcher = None
get_source = GetSourceFromPlaylist()
else:
messenger.info('Start folder mode')
media = MediaStore()
watcher = MediaWatcher(media)
get_source = GetSourceFromFolder(media)
Iter = import_module(f'ffplayout.player.{play.mode}').GetSourceIter
get_source = Iter()
try:
for node in get_source.next():
if watcher is not None:
watcher.current_clip = node.get('source')
messenger.info(f'Play: {node.get("source")}')
dec_cmd = [
@ -187,18 +178,18 @@ def output():
messenger.debug(f'node: "{node}"')
messenger.debug(f'dec_cmd: "{dec_cmd}"')
messenger.debug(79 * '-')
terminate_processes(watcher)
terminate_processes(getattr(get_source, 'stop', None))
except SystemExit:
messenger.info('Got close command')
terminate_processes(watcher)
terminate_processes(getattr(get_source, 'stop', None))
if ff_proc.live and ff_proc.live.poll() is None:
ff_proc.live.terminate()
except KeyboardInterrupt:
messenger.warning('Program terminated')
terminate_processes(watcher)
terminate_processes(getattr(get_source, 'stop', None))
if ff_proc.live and ff_proc.live.poll() is None:
ff_proc.live.terminate()

View File

@ -19,15 +19,13 @@
This module streams to -f null, so it is only for debugging.
"""
from importlib import import_module
from platform import system
from subprocess import PIPE, Popen
from threading import Thread
from ..folder import GetSourceFromFolder, MediaStore, MediaWatcher
from ..playlist import GetSourceFromPlaylist
from ..utils import (ff_proc, ffmpeg_stderr_reader, log, messenger, playlist,
playout, pre, pre_audio_codec, stdin_args,
terminate_processes)
from ..utils import (ff_proc, ffmpeg_stderr_reader, log, messenger, play,
playout, pre, pre_audio_codec, terminate_processes)
COPY_BUFSIZE = 1024 * 1024 if system() == 'Windows' else 65424
@ -38,9 +36,11 @@ def output():
like rtmp, rtp, svt, etc.
"""
messenger.info(f'Stream to null output, only usefull for debugging...')
ff_pre_settings = [
'-pix_fmt', 'yuv420p', '-r', str(pre.fps),
'-c:v', 'mpeg2video', '-intra',
'-c:v', 'mpeg2video', '-g', '1',
'-b:v', f'{pre.v_bitrate}k',
'-minrate', f'{pre.v_bitrate}k',
'-maxrate', f'{pre.v_bitrate}k',
@ -62,20 +62,11 @@ def output():
enc_err_thread.daemon = True
enc_err_thread.start()
if playlist.mode and not stdin_args.folder:
watcher = None
get_source = GetSourceFromPlaylist()
else:
messenger.info('Start folder mode')
media = MediaStore()
watcher = MediaWatcher(media)
get_source = GetSourceFromFolder(media)
Iter = import_module(f'ffplayout.player.{play.mode}').GetSourceIter
get_source = Iter()
try:
for node in get_source.next():
if watcher is not None:
watcher.current_clip = node.get('source')
messenger.info(
f'Play for {node["out"] - node["seek"]:.2f} '
f'seconds: {node.get("source")}')
@ -103,15 +94,15 @@ def output():
except BrokenPipeError:
messenger.error('Broken Pipe!')
terminate_processes(watcher)
terminate_processes(getattr(get_source, 'stop', None))
except SystemExit:
messenger.info('Got close command')
terminate_processes(watcher)
terminate_processes(getattr(get_source, 'stop', None))
except KeyboardInterrupt:
messenger.warning('Program terminated')
terminate_processes(watcher)
terminate_processes(getattr(get_source, 'stop', None))
# close encoder when nothing is to do anymore
if ff_proc.encoder.poll() is None:

View File

@ -19,15 +19,14 @@
This module streams the files out to a remote target.
"""
from importlib import import_module
from platform import system
from subprocess import PIPE, Popen
from threading import Thread
from ..folder import GetSourceFromFolder, MediaStore, MediaWatcher
from ..playlist import GetSourceFromPlaylist
from ..utils import (ff_proc, ffmpeg_stderr_reader, get_date, log, lower_third,
messenger, playlist, playout, pre, pre_audio_codec,
stdin_args, sync_op, terminate_processes)
messenger, play, playout, pre, pre_audio_codec, sync_op,
terminate_processes)
COPY_BUFSIZE = 1024 * 1024 if system() == 'Windows' else 65424
@ -80,20 +79,11 @@ def output():
enc_err_thread.daemon = True
enc_err_thread.start()
if playlist.mode and not stdin_args.folder:
watcher = None
get_source = GetSourceFromPlaylist()
else:
messenger.info('Start folder mode')
media = MediaStore()
watcher = MediaWatcher(media)
get_source = GetSourceFromFolder(media)
Iter = import_module(f'ffplayout.player.{play.mode}').GetSourceIter
get_source = Iter()
try:
for node in get_source.next():
if watcher is not None:
watcher.current_clip = node.get('source')
messenger.info(f'Play: {node.get("source")}')
dec_cmd = [
@ -125,15 +115,15 @@ def output():
messenger.debug(f'node: "{node}"')
messenger.debug(f'dec_cmd: "{dec_cmd}"')
messenger.debug(79 * '-')
terminate_processes(watcher)
terminate_processes(getattr(get_source, 'stop', None))
except SystemExit:
messenger.info('Got close command')
terminate_processes(watcher)
terminate_processes(getattr(get_source, 'stop', None))
except KeyboardInterrupt:
messenger.warning('Program terminated')
terminate_processes(watcher)
terminate_processes(getattr(get_source, 'stop', None))
# close encoder when nothing is to do anymore
if ff_proc.encoder.poll() is None:

View File

@ -0,0 +1,7 @@
Here you have the possibility to add you own player module. Defaults are: playing a playlist, or the content of a folder.
If you need your own module, create a python file with the desire name. Inside it need a generator class with the name: **GetSourceIter**.
Check **folder.py** and **playlist.py** to get an idea how it needs to work.
After creating the custom module, set in config **play: -> mode:** the file name of your module without extension.

View File

View File

@ -27,8 +27,8 @@ from pathlib import Path
from watchdog.events import PatternMatchingEventHandler
from watchdog.observers import Observer
from .filters.default import build_filtergraph
from .utils import (MediaProbe, ff_proc, get_float, messenger, stdin_args,
from ..filters.default import build_filtergraph
from ..utils import (MediaProbe, ff_proc, get_float, messenger, stdin_args,
storage)
# ------------------------------------------------------------------------------
@ -163,13 +163,14 @@ class MediaWatcher:
self.observer.join()
class GetSourceFromFolder:
class GetSourceIter:
"""
give next clip, depending on shuffle mode
"""
def __init__(self, media):
self._media = media
def __init__(self):
self.media = MediaStore()
self.watcher = MediaWatcher(self.media)
self.last_played = []
self.index = 0
@ -179,28 +180,31 @@ class GetSourceFromFolder:
self.node_last = None
self.node_next = None
def stop(self):
self.watcher.stop()
def next(self):
"""
generator for getting always a new file
"""
while True:
while self.index < len(self._media.store):
while self.index < len(self.media.store):
if self.node_next:
self.node = deepcopy(self.node_next)
self.probe = deepcopy(self.next_probe)
else:
self.probe.load(self._media.store[self.index])
self.probe.load(self.media.store[self.index])
duration = get_float(self.probe.format.get('duration'), 0)
self.node = {
'in': 0,
'seek': 0,
'out': duration,
'duration': duration,
'source': self._media.store[self.index],
'source': self.media.store[self.index],
'probe': self.probe
}
if self.index < len(self._media.store) - 1:
self.next_probe.load(self._media.store[self.index + 1])
if self.index < len(self.media.store) - 1:
self.next_probe.load(self.media.store[self.index + 1])
next_duration = get_float(
self.next_probe.format.get('duration'), 0)
self.node_next = {
@ -208,17 +212,18 @@ class GetSourceFromFolder:
'seek': 0,
'out': next_duration,
'duration': next_duration,
'source': self._media.store[self.index + 1],
'source': self.media.store[self.index + 1],
'probe': self.next_probe
}
else:
self._media.rand()
self.media.rand()
self.node_next = None
self.node['src_cmd'] = ['-i', self._media.store[self.index]]
self.node['src_cmd'] = ['-i', self.media.store[self.index]]
self.node['filter'] = build_filtergraph(
self.node, self.node_last, self.node_next)
self.watcher.current_clip = self.node.get('source')
yield self.node
self.index += 1
self.node_last = deepcopy(self.node)

View File

@ -29,8 +29,8 @@ from threading import Thread
import requests
from .filters.default import build_filtergraph
from .utils import (MediaProbe, check_sync, get_date, get_delta,
from ..filters.default import build_filtergraph
from ..utils import (MediaProbe, check_sync, get_date, get_delta,
get_float, get_time, messenger, playlist, sec_to_time,
src_or_dummy, stdin_args, storage, sync_op, valid_json)
@ -255,7 +255,7 @@ class PlaylistReader:
self.error = True
class GetSourceFromPlaylist:
class GetSourceIter:
"""
read values from json playlist,
get current clip in time,