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):
|
def filter_escape(s):
|
||||||
return (str(s)
|
return (str(s)
|
||||||
.replace(':', '\\:')
|
.replace(':', '\\:')
|
||||||
|
@ -5,12 +8,27 @@ def filter_escape(s):
|
||||||
.replace(';', '\\;')
|
.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:
|
class FFmpegFilter:
|
||||||
def __init__(self, name, *, ins=[], outs=[], **args):
|
def __init__(self, name, *, ins=None, outs=None, **args):
|
||||||
self.name = name
|
self.name = name
|
||||||
self.args = args
|
self.args = args
|
||||||
self.ins = ins
|
self.ins = ins or []
|
||||||
self.outs = outs
|
self.outs = outs or []
|
||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
return '{}({}, {}, ins={!r}, outs={!r})'.format(
|
return '{}({}, {}, ins={!r}, outs={!r})'.format(
|
||||||
|
@ -31,8 +49,10 @@ class FFmpegFilter:
|
||||||
)
|
)
|
||||||
|
|
||||||
class FFmpegChain:
|
class FFmpegChain:
|
||||||
def __init__(self, *filters):
|
def __init__(self, *filters, inputs=None, runners=None):
|
||||||
self.filters = list(filters)
|
self.filters = list(filters)
|
||||||
|
self.inputs = inputs or []
|
||||||
|
self.runners = runners or []
|
||||||
|
|
||||||
def append(self, f):
|
def append(self, f):
|
||||||
self.filters.append(f)
|
self.filters.append(f)
|
||||||
|
@ -51,9 +71,20 @@ class FFmpegChain:
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
return '{}({})'.format(self.__class__.__name__, ', '.join(repr(f) for f in self.filters))
|
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)
|
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:
|
class FFmpegGraph:
|
||||||
def __init__(self, *chains):
|
def __init__(self, *chains):
|
||||||
self.chains = list(chains)
|
self.chains = list(chains)
|
||||||
|
@ -75,5 +106,20 @@ class FFmpegGraph:
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
return '{}({})'.format(self.__class__.__name__, ', '.join(repr(c) for c in self.chains))
|
return '{}({})'.format(self.__class__.__name__, ', '.join(repr(c) for c in self.chains))
|
||||||
|
|
||||||
def __str__(self):
|
def format(self):
|
||||||
return '; '.join(str(c) for c in self.chains)
|
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):
|
def to_ffmpeg(self, scene=None):
|
||||||
scene = scene or self.current_scene
|
scene = scene or self.current_scene
|
||||||
runners, chains = self.sources[scene].to_ffmpeg(self)
|
chains = self.sources[scene].to_ffmpeg(self)
|
||||||
|
|
||||||
r = FFmpegGraph()
|
r = FFmpegGraph()
|
||||||
r.extend(chains)
|
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_path = os.path.join(self.tempdir, 'frame.bin')
|
||||||
fifo = os.mkfifo(fifo_path)
|
fifo = os.mkfifo(fifo_path)
|
||||||
runner = functools.partial(capture_page, url=self.url, outfile=fifo_path, dims=(self.width, self.height))
|
runner = functools.partial(capture_page, url=self.url, outfile=fifo_path, dims=(self.width, self.height))
|
||||||
return [runner], [FFmpegChain(
|
return [FFmpegChain(
|
||||||
FFmpegFilter('movie', filename=fifo_path, loop=0)
|
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):
|
def to_ffmpeg(self, scene):
|
||||||
prefix = identifiery(self.name)
|
prefix = identifiery(self.name)
|
||||||
i = 1
|
i = 0
|
||||||
graph = [FFmpegChain(
|
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:
|
for item in self.items:
|
||||||
if not item['visible']:
|
if not item['visible']:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
rs, chains = scene.sources[item['name']].to_ffmpeg(scene)
|
chains = scene.sources[item['name']].to_ffmpeg(scene)
|
||||||
runners.extend(rs)
|
|
||||||
c = chains[-1]
|
c = chains[-1]
|
||||||
|
|
||||||
if item['scale']['x'] != 1.0 or item['scale']['y'] != 1.0:
|
if item['scale']['x'] != 1.0 or item['scale']['y'] != 1.0:
|
||||||
|
@ -58,15 +57,19 @@ class OBSSceneSource(OBSSource):
|
||||||
c.append(FFmpegFilter('rotate',
|
c.append(FFmpegFilter('rotate',
|
||||||
angle=deg2rad(item['rot'])
|
angle=deg2rad(item['rot'])
|
||||||
))
|
))
|
||||||
c.append(FFmpegFilter('overlay',
|
if not c[-1].outs:
|
||||||
eval='init',
|
c[-1].outs.append(prefix + 'Out' + str(i))
|
||||||
x=item['pos']['x'],
|
chains.append(FFmpegChain(
|
||||||
y=item['pos']['y'],
|
FFmpegFilter('overlay',
|
||||||
ins=[prefix + str(i)],
|
eval='init',
|
||||||
outs=[prefix + str(i + 1)])
|
x=item['pos']['x'],
|
||||||
)
|
y=item['pos']['y'],
|
||||||
|
ins=[prefix + str(i - 1), c[-1].outs[-1]],
|
||||||
|
outs=[prefix + str(i)]
|
||||||
|
)
|
||||||
|
))
|
||||||
i += 1
|
i += 1
|
||||||
|
|
||||||
graph.extend(chains)
|
graph.extend(chains)
|
||||||
|
|
||||||
return runners, graph
|
return graph
|
||||||
|
|
|
@ -13,7 +13,7 @@ class TextSource(OBSSource):
|
||||||
self.shadow_color = '#000000'
|
self.shadow_color = '#000000'
|
||||||
|
|
||||||
def to_ffmpeg(self, scene):
|
def to_ffmpeg(self, scene):
|
||||||
return [], [FFmpegChain(
|
return [FFmpegChain(
|
||||||
FFmpegFilter('drawtext',
|
FFmpegFilter('drawtext',
|
||||||
expansion='none',
|
expansion='none',
|
||||||
fontcolor=self.color,
|
fontcolor=self.color,
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
from ..filters import FFmpegChain, FFmpegFilter
|
from ..filters import FFmpegInput, FFmpegChain, FFmpegFilter
|
||||||
from .base import OBSSource
|
from .base import OBSSource
|
||||||
|
|
||||||
|
|
||||||
|
@ -12,9 +12,7 @@ class ImageSource(OBSSource):
|
||||||
self.infile = data['settings']['file']
|
self.infile = data['settings']['file']
|
||||||
|
|
||||||
def to_ffmpeg(self, scene):
|
def to_ffmpeg(self, scene):
|
||||||
return [], [FFmpegChain(
|
return [FFmpegChain(inputs=[FFmpegInput(self.infile)])]
|
||||||
FFmpegFilter('movie', filename=self.infile)
|
|
||||||
)]
|
|
||||||
|
|
||||||
@OBSSource.type('ffmpeg_source')
|
@OBSSource.type('ffmpeg_source')
|
||||||
class FFmpegSource(OBSSource):
|
class FFmpegSource(OBSSource):
|
||||||
|
@ -28,7 +26,7 @@ class FFmpegSource(OBSSource):
|
||||||
self.loop = data['settings']['looping']
|
self.loop = data['settings']['looping']
|
||||||
|
|
||||||
def to_ffmpeg(self, scene):
|
def to_ffmpeg(self, scene):
|
||||||
return [], [FFmpegChain(
|
return [FFmpegChain(
|
||||||
FFmpegFilter('movie', filename=self.infile, loop=0 if self.loop else 1),
|
FFmpegFilter('realtime'),
|
||||||
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)
|
nick_id = 'drawtext@twitch-{}-nicks'.format(name)
|
||||||
chat_id = 'drawtext@twitch-{}-messages'.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)
|
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,
|
FFmpegFilter(nick_id,
|
||||||
text='Loading',
|
text='Loading',
|
||||||
expansion='none',
|
expansion='none',
|
||||||
|
@ -24,7 +24,8 @@ def make_twitch_chat(name, nickname, token, channel, x=200, y=25, addr='tcp://lo
|
||||||
y=y,
|
y=y,
|
||||||
**chat_args
|
**chat_args
|
||||||
),
|
),
|
||||||
FFmpegFilter('zmq')
|
FFmpegFilter('zmq'),
|
||||||
|
runners=[runner.run]
|
||||||
)]
|
)]
|
||||||
|
|
||||||
def zmq_escape(v):
|
def zmq_escape(v):
|
||||||
|
|
Loading…
Reference in New Issue