Multi-DJ livestream toolkit
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

125 lines
3.3 KiB

2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
  1. import threading
  2. def filter_escape(s):
  3. return (str(s)
  4. .replace(':', '\\:')
  5. .replace(',', '\\,')
  6. .replace(';', '\\;')
  7. )
  8. class FFmpegInput:
  9. def __init__(self, name, **options):
  10. self.name = name
  11. self.options = options
  12. def format(self):
  13. args = []
  14. for (k, v) in self.options.items():
  15. args.append('-' + k)
  16. if v is not None:
  17. args.append(str(v))
  18. args.append('-i')
  19. args.append(self.name)
  20. return args
  21. class FFmpegFilter:
  22. def __init__(self, name, *, ins=None, outs=None, **args):
  23. self.name = name
  24. self.args = args
  25. self.ins = ins or []
  26. self.outs = outs or []
  27. def __repr__(self):
  28. return '{}({}, {}, ins={!r}, outs={!r})'.format(
  29. self.__class__.__name__,
  30. self.name,
  31. ', '.join('{}={}'.format(k, v) for k, v in self.args.items()),
  32. self.ins,
  33. self.outs
  34. )
  35. def __str__(self):
  36. return '{}{}{}{}{}'.format(
  37. ''.join('[{}] '.format(i) for i in self.ins),
  38. filter_escape(self.name),
  39. '=' if self.args else '',
  40. ':'.join('{}={}'.format(k, filter_escape(v)) for k, v in self.args.items()),
  41. ''.join(' [{}]'.format(o) for o in self.outs),
  42. )
  43. class FFmpegChain:
  44. def __init__(self, *filters, inputs=None, runners=None):
  45. self.filters = list(filters)
  46. self.inputs = inputs or []
  47. self.runners = runners or []
  48. def append(self, f):
  49. self.filters.append(f)
  50. def extend(self, l):
  51. self.filters.extend(l)
  52. def __getitem__(self, i):
  53. return self.filters[i]
  54. def __add__(self, o):
  55. if not isinstance(o, FFmpegChain):
  56. raise TypeError('incompatible')
  57. return FFmpegChain(*(self.filters + o.filters))
  58. def __repr__(self):
  59. return '{}({})'.format(self.__class__.__name__, ', '.join(repr(f) for f in self.filters))
  60. def format_inputs(self):
  61. args = []
  62. for input in self.inputs:
  63. args.extend(input.format())
  64. return args
  65. def format_filters(self):
  66. return ', '.join(str(f) for f in self.filters)
  67. def run(self):
  68. for r in self.runners:
  69. t = threading.Thread(target=r, daemon=True)
  70. t.start()
  71. class FFmpegGraph:
  72. def __init__(self, *chains):
  73. self.chains = list(chains)
  74. def append(self, f):
  75. self.chains.append(f)
  76. def extend(self, l, link=False):
  77. if self.chains and link:
  78. l[0][0].ins = self.chains[-1][-1].outs
  79. self.chains.extend(l)
  80. def fixup(self):
  81. self.chains[-1][-1].outs = []
  82. def __getitem__(self, i):
  83. return self.chains[i]
  84. def __repr__(self):
  85. return '{}({})'.format(self.__class__.__name__, ', '.join(repr(c) for c in self.chains))
  86. def format(self):
  87. args = []
  88. in_no = 0
  89. for c in self.chains:
  90. args.extend(c.format_inputs())
  91. for i in range(len(c.inputs)):
  92. c.filters[0].ins.append(str(in_no))
  93. in_no += 1
  94. args.append('-filter_complex')
  95. args.append('; '.join(c.format_filters() for c in self.chains))
  96. return args
  97. def run(self):
  98. for c in self.chains:
  99. c.run()