rework filter chain to embed processes and inputs
This commit is contained in:
parent
a16d211be7
commit
46abaf0a70
|
@ -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()
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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]
|
||||
)]
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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)]
|
||||
)]
|
||||
|
|
|
@ -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):
|
||||
|
|
Loading…
Reference in New Issue