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): 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()

View File

@ -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

View File

@ -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]
)] )]

View File

@ -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

View File

@ -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,

View File

@ -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)]
)] )]

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) 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):