Skip to content

Commit

Permalink
Merge pull request #25 from bbc/feat/decklink_input
Browse files Browse the repository at this point in the history
Added Decklink Input
  • Loading branch information
moschopsuk authored Dec 14, 2018
2 parents bd66f16 + b52d010 commit 60f626d
Show file tree
Hide file tree
Showing 3 changed files with 182 additions and 1 deletion.
3 changes: 3 additions & 0 deletions brave/inputs/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
from brave.inputs.test_audio import TestAudioInput
from brave.inputs.image import ImageInput
from brave.inputs.html import HTMLInput
from brave.inputs.decklink import DecklinkInput
from brave.abstract_collection import AbstractCollection
import brave.exceptions

Expand All @@ -23,6 +24,8 @@ def add(self, **args):
input = ImageInput(**args, collection=self)
elif args['type'] == 'html':
input = HTMLInput(**args, collection=self)
elif args['type'] == 'decklink':
input = DecklinkInput(**args, collection=self)
else:
raise brave.exceptions.InvalidConfiguration(f"Invalid input type '{str(args['type'])}'")

Expand Down
95 changes: 95 additions & 0 deletions brave/inputs/decklink.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
from brave.inputs.input import Input
from gi.repository import Gst
import brave.config as config


class DecklinkInput(Input):
'''
Handles input via a deckoink card/device.
This can allow SDI/HDMI singals to be localy mixed with brave
'''
def permitted_props(self):
return {
**super().permitted_props(),
'device': {
'type': 'int',
'default': 0,
},
'connection': {
'type': 'int',
'default': 1,
},
'mode': {
'type': 'int',
'default': 17,
},
'width': {
'type': 'int',
'default': 1280
},
'height': {
'type': 'int',
'default': 720
},
'xpos': {
'type': 'int',
'default': 0
},
'ypos': {
'type': 'int',
'default': 0
},
'zorder': {
'type': 'int',
'default': 1
}
}

def create_elements(self):
#TODO: Audio is currently lcoked to HDI/HDMI mode may need to figure a btter way to auto select the best one
if not self.create_pipeline_from_string('decklinkvideosrc'
' device-number=' + str(self.props['device']) +
' connection=' + str(self.props['connection']) +
' mode=' + str(self.props['mode']) +
' ! videoconvert'
+ self.default_video_pipeline_string_end() +
' decklinkaudiosrc device-number=' + str(self.props['device']) + ' connection=1 ! audioconvert'
+ self.default_audio_pipeline_string_end()):
return False

self.intervideosink = self.pipeline.get_by_name('intervideosink')
self.final_video_tee = self.pipeline.get_by_name('final_video_tee')
self.final_audio_tee = self.pipeline.get_by_name('final_audio_tee')
self.handle_updated_props()

def get_input_cap_props(self):
'''
Parses the caps that arrive from the input, and returns them.
This allows the height/width/framerate/audio_rate to be retrieved.
'''
elements = {}
if hasattr(self, 'intervideosink'):
elements['video'] = self.intervideosink

props = {}
for (audioOrVideo, element) in elements.items():
if not element:
return
caps = element.get_static_pad('sink').get_current_caps()
if not caps:
return
size = caps.get_size()
if size == 0:
return

structure = caps.get_structure(0)
props[audioOrVideo + '_caps_string'] = structure.to_string()
if structure.has_field('framerate'):
framerate = structure.get_fraction('framerate')
props['framerate'] = framerate.value_numerator / framerate.value_denominator
if structure.has_field('height'):
props['height'] = structure.get_int('height').value
if structure.has_field('width'):
props['width'] = structure.get_int('width').value

return props
85 changes: 84 additions & 1 deletion public/js/inputs.js
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,9 @@ inputsHandler._inputCardBody = (input) => {
if (input.props.hasOwnProperty('freq')) details.push('<div><strong>Frequency:</strong> ' + input.props.freq + 'Hz</div>')
if (input.props.hasOwnProperty('pattern')) details.push('<div><strong>Pattern:</strong> ' + inputsHandler.patternTypes[input.props.pattern] + '</div>')
if (input.props.hasOwnProperty('wave')) details.push('<div><strong>Wave:</strong> ' + inputsHandler.waveTypes[input.props.wave] + '</div>')
if (input.props.hasOwnProperty('device')) details.push('<div><strong>Device Num:</strong> ' + input.props.device + '</div>')
if (input.props.hasOwnProperty('connection')) details.push('<div><strong>Connection Type:</strong> ' + inputsHandler.decklinkConnection[input.props.connection] + '</div>')
if (input.props.hasOwnProperty('mode')) details.push('<div><strong>Input Mode:</strong> ' + inputsHandler.decklinkModes[input.props.mode] + '</div>')

if (input.hasOwnProperty('duration')) {
var duration = prettyDuration(input.duration)
Expand Down Expand Up @@ -186,12 +189,41 @@ inputsHandler._populateForm = function(input) {
max: 20000
})

var device = formGroup({
id: 'input-device',
label: 'Device Num',
name: 'device',
type: 'number',
value: input.props.device || 0
})

var connection = formGroup({
id: 'connection-device',
label: 'Connection Type',
name: 'connection',
type: 'number',
options: inputsHandler.decklinkConnection,
initialOption: 'Select connection type',
value: input.props.connection || inputsHandler.decklinkConnection[1]
})

var mode = formGroup({
id: 'mode-device',
label: 'Input Mode',
name: 'mode',
type: 'number',
options: inputsHandler.decklinkModes,
initialOption: 'Select input mode',
value: input.props.mode || inputsHandler.decklinkModes[17]
})

var isNew = !input.hasOwnProperty('id')
if (isNew) {
var options = {
'uri': 'URI (for files, RTMP, RTSP and HLS)',
'image': 'Image',
'html': 'HTML (for showing a web page)',
'decklink': 'Decklink Device',
'test_video': 'Test video stream',
'test_audio': 'Test audio stream',
}
Expand Down Expand Up @@ -241,7 +273,14 @@ inputsHandler._populateForm = function(input) {
form.append(sizeBox);
form.append(zOrderBox);
}

else if (input.type === 'decklink') {
if (isNew) form.append(device);
if (isNew) form.append(mode);
if (isNew) form.append(connection);
form.append(positionBox);
form.append(sizeBox);
form.append(zOrderBox);
}
form.find('select[name="type"]').change(inputsHandler._handleNewFormType);
}

Expand Down Expand Up @@ -397,6 +436,50 @@ inputsHandler.waveTypes = [
'Violet noise'
]

inputsHandler.decklinkModes = [
'Automatic detection (Hardware Dependant)',
'NTSC SD 60i',
'NTSC SD 60i (24 fps)',
'PAL SD 50i',
'NTSC SD 60p',
'PAL SD 50p',
'HD1080 23.98p',
'HD1080 24p',
'HD1080 25p',
'HD1080 29.97p',
'HD1080 30p',
'HD1080 50i',
'HD1080 59.94i',
'HD1080 60i',
'HD1080 50p',
'HD1080 59.94p',
'HD1080 60p',
'HD720 50p',
'HD720 59.94p',
'HD720 60p',
'2k 23.98p',
'2k 24p',
'2k 25p',
'4k 23.98p',
'4k 24p',
'4k 25p',
'4k 29.97p',
'4k 30p',
'4k 50p',
'4k 59.94p',
'4k 60p',
]

inputsHandler.decklinkConnection = [
'Auto (Hardware Dependant)',
'SDI',
'HDMI',
'Optical SDI',
'Component',
'Composite',
'S-Video',
]

function prettyDuration(d) {
if (d < 0) return null
var seconds = Math.floor(d / 1000000000)
Expand Down

0 comments on commit 60f626d

Please sign in to comment.