rework filter chain to embed processes and inputs

This commit is contained in:
Shiz 2020-05-18 18:50:50 +02:00
parent a16d211be7
commit 46abaf0a70
7 changed files with 83 additions and 34 deletions

View File

@ -1,3 +1,6 @@
import threading
def filter_escape(s):
return (str(s)
.replace(':', '\\:')
@ -5,12 +8,27 @@ def filter_escape(s):
.replace(';', '\\;')
)
class FFmpegInput:
def __init__(self, name, **options):
self.name = name
self.options = options
def format(self):
args = []
for (k, v) in self.options.items():
args.append('-' + k)
if v is not None:
args.append(str(v))
args.append('-i')
args.append(self.name)
return args
class FFmpegFilter:
def __init__(self, name, *, ins=[], outs=[], **args):
def __init__(self, name, *, ins=None, outs=None, **args):
self.name = name
self.args = args
self.ins = ins
self.outs = outs
self.ins = ins or []
self.outs = outs or []
def __repr__(self):
return '{}({}, {}, ins={!r}, outs={!r})'.format(
@ -31,8 +49,10 @@ class FFmpegFilter:
)
class FFmpegChain:
def __init__(self, *filters):
def __init__(self, *filters, inputs=None, runners=None):
self.filters = list(filters)
self.inputs = inputs or []
self.runners = runners or []
def append(self, f):
self.filters.append(f)
@ -51,9 +71,20 @@ class FFmpegChain:
def __repr__(self):
return '{}({})'.format(self.__class__.__name__, ', '.join(repr(f) for f in self.filters))
def __str__(self):
def format_inputs(self):
args = []
for input in self.inputs:
args.extend(input.format())
return args
def format_filters(self):
return ', '.join(str(f) for f in self.filters)
def run(self):
for r in self.runners:
t = threading.Thread(target=r, daemon=True)
t.start()
class FFmpegGraph:
def __init__(self, *chains):
self.chains = list(chains)
@ -75,5 +106,20 @@ class FFmpegGraph:
def __repr__(self):
return '{}({})'.format(self.__class__.__name__, ', '.join(repr(c) for c in self.chains))
def __str__(self):
return '; '.join(str(c) for c in self.chains)
def format(self):
args = []
in_no = 0
for c in self.chains:
args.extend(c.format_inputs())
for i in range(len(c.inputs)):
c.filters[0].ins.append(str(in_no))
in_no += 1
args.append('-filter_complex')
args.append('; '.join(c.format_filters() for c in self.chains))
return args
def run(self):
for c in self.chains:
c.run()

View File

@ -53,8 +53,8 @@ class OBSScene:
def to_ffmpeg(self, scene=None):
scene = scene or self.current_scene
runners, chains = self.sources[scene].to_ffmpeg(self)
chains = self.sources[scene].to_ffmpeg(self)
r = FFmpegGraph()
r.extend(chains)
return runners, r
return r

View File

@ -65,6 +65,7 @@ class OBSBrowserSource(OBSSource):
fifo_path = os.path.join(self.tempdir, 'frame.bin')
fifo = os.mkfifo(fifo_path)
runner = functools.partial(capture_page, url=self.url, outfile=fifo_path, dims=(self.width, self.height))
return [runner], [FFmpegChain(
FFmpegFilter('movie', filename=fifo_path, loop=0)
return [FFmpegChain(
inputs=[FFmpegInput(fifo_path, f='image2pipe', framerate=runner.rate, follow=1, probesize=8192)],
runners=[runner]
)]

View File

@ -28,18 +28,17 @@ class OBSSceneSource(OBSSource):
def to_ffmpeg(self, scene):
prefix = identifiery(self.name)
i = 1
i = 0
graph = [FFmpegChain(
FFmpegFilter('color', c='black', s='{}x{}'.format(*scene.dimensions), outs=[prefix + str(i)])
FFmpegFilter('color', c='black', s='{}x{}'.format(*scene.dimensions), outs=[prefix + str(0)])
)]
runners = []
i += 1
for item in self.items:
if not item['visible']:
continue
rs, chains = scene.sources[item['name']].to_ffmpeg(scene)
runners.extend(rs)
chains = scene.sources[item['name']].to_ffmpeg(scene)
c = chains[-1]
if item['scale']['x'] != 1.0 or item['scale']['y'] != 1.0:
@ -58,15 +57,19 @@ class OBSSceneSource(OBSSource):
c.append(FFmpegFilter('rotate',
angle=deg2rad(item['rot'])
))
c.append(FFmpegFilter('overlay',
eval='init',
x=item['pos']['x'],
y=item['pos']['y'],
ins=[prefix + str(i)],
outs=[prefix + str(i + 1)])
)
if not c[-1].outs:
c[-1].outs.append(prefix + 'Out' + str(i))
chains.append(FFmpegChain(
FFmpegFilter('overlay',
eval='init',
x=item['pos']['x'],
y=item['pos']['y'],
ins=[prefix + str(i - 1), c[-1].outs[-1]],
outs=[prefix + str(i)]
)
))
i += 1
graph.extend(chains)
return runners, graph
return graph

View File

@ -13,7 +13,7 @@ class TextSource(OBSSource):
self.shadow_color = '#000000'
def to_ffmpeg(self, scene):
return [], [FFmpegChain(
return [FFmpegChain(
FFmpegFilter('drawtext',
expansion='none',
fontcolor=self.color,

View File

@ -1,4 +1,4 @@
from ..filters import FFmpegChain, FFmpegFilter
from ..filters import FFmpegInput, FFmpegChain, FFmpegFilter
from .base import OBSSource
@ -12,9 +12,7 @@ class ImageSource(OBSSource):
self.infile = data['settings']['file']
def to_ffmpeg(self, scene):
return [], [FFmpegChain(
FFmpegFilter('movie', filename=self.infile)
)]
return [FFmpegChain(inputs=[FFmpegInput(self.infile)])]
@OBSSource.type('ffmpeg_source')
class FFmpegSource(OBSSource):
@ -28,7 +26,7 @@ class FFmpegSource(OBSSource):
self.loop = data['settings']['looping']
def to_ffmpeg(self, scene):
return [], [FFmpegChain(
FFmpegFilter('movie', filename=self.infile, loop=0 if self.loop else 1),
FFmpegFilter('realtime')
return [FFmpegChain(
FFmpegFilter('realtime'),
inputs=[FFmpegInput(self.infile, re=None)]
)]

View File

@ -9,7 +9,7 @@ def make_twitch_chat(name, nickname, token, channel, x=200, y=25, addr='tcp://lo
nick_id = 'drawtext@twitch-{}-nicks'.format(name)
chat_id = 'drawtext@twitch-{}-messages'.format(name)
runner = TwitchChatUpdater(nickname=nickname, token=token, channel=channel, nick_target=nick_id, chat_target=chat_id)
return [runner.run], [FFmpegChain(
return [FFmpegChain(
FFmpegFilter(nick_id,
text='Loading',
expansion='none',
@ -24,7 +24,8 @@ def make_twitch_chat(name, nickname, token, channel, x=200, y=25, addr='tcp://lo
y=y,
**chat_args
),
FFmpegFilter('zmq')
FFmpegFilter('zmq'),
runners=[runner.run]
)]
def zmq_escape(v):