Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions packages/blueprints/src/base/showstyle/applyconfig/layers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,11 @@ export enum SourceLayer {
DVE_RETAIN = 'dveRetain',
GFX = 'gfx',

OGrafFullScreen = 'ograf_fullscreen',
OGrafOverlay1 = 'ograf_overlay1',
OGrafOverlay2 = 'ograf_overlay2',
OGrafOverlay3 = 'ograf_overlay3',

AudioBed = 'audioBed',
StudioGuests = 'guest',
HostOverride = 'hostOverride',
Expand All @@ -31,6 +36,9 @@ export function getOutputLayerForSourceLayer(layer: SourceLayer): OutputLayer {
switch (layer) {
case SourceLayer.Script:
return OutputLayer.Script
case SourceLayer.OGrafOverlay1:
case SourceLayer.OGrafOverlay2:
case SourceLayer.OGrafOverlay3:
case SourceLayer.LowerThird:
case SourceLayer.Strap:
case SourceLayer.Ticker:
Expand Down
28 changes: 28 additions & 0 deletions packages/blueprints/src/base/showstyle/applyconfig/sourcelayers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,34 @@ export function getSourceLayer(): ISourceLayer[] {
exclusiveGroup: 'pgm',
onPresenterScreen: true,
},
{
_id: SourceLayer.OGrafFullScreen,
type: SourceLayerType.GRAPHICS,
_rank: 110,
name: 'OGraf Full Screen',
abbreviation: 'GFX FULL',
},
{
_id: SourceLayer.OGrafOverlay1,
type: SourceLayerType.GRAPHICS,
_rank: 111,
name: 'OGraf Overlay 1',
abbreviation: 'GFX1',
},
{
_id: SourceLayer.OGrafOverlay2,
type: SourceLayerType.GRAPHICS,
_rank: 112,
name: 'OGraf Overlay 2',
abbreviation: 'GFX2',
},
{
_id: SourceLayer.OGrafOverlay3,
type: SourceLayerType.GRAPHICS,
_rank: 113,
name: 'OGraf Overlay 3',
abbreviation: 'GFX3',
},

{
_id: SourceLayer.LowerThird,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,19 @@ export function getTriggeredActions(): IBlueprintTriggeredActions[] {
),
...['F8'].map((key, i) => createAdLibHotkey(key, [SourceLayer.DVE], true, i, undefined)),
...['KeyQ', 'KeyW', 'KeyE', 'KeyR', 'KeyT', 'KeyY'].map((key, i) =>
createAdLibHotkey(key, [SourceLayer.LowerThird, SourceLayer.LowerThird, SourceLayer.Ticker], false, i, undefined)
createAdLibHotkey(
key,
[
SourceLayer.LowerThird,
SourceLayer.Ticker,
SourceLayer.OGrafOverlay1,
SourceLayer.OGrafOverlay2,
SourceLayer.OGrafOverlay3,
],
false,
i,
undefined
)
),
...['KeyV', 'Shift+KeyV'].map((key, i) => createAdLibHotkey(key, [SourceLayer.HostOverride], true, i, undefined)),
createAdLibHotkeyWithTriggerMode(rankCounter),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,5 @@ export enum ActionId {
LastRemote = 'lastRemote',
LastDVE = 'lastDVE',
GFXStep = 'GFXStep',
OGrafClear = 'ograf-clear',
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import {
import { ActionId } from './actionDefinitions.js'
import { SourceLayer } from '../applyconfig/layers.js'
import { ExampleGFXStepActionOptions, executeGraphicNextStep } from './steppedGraphicExample.js'
import { executeOGrafClear } from './ograf.js'

export async function executeAction(
context: IActionExecutionContext,
Expand Down Expand Up @@ -40,6 +41,8 @@ export async function executeAction(
await executeLastOnSourceLayer(context, SourceLayer.DVE)
} else if (actionId === ActionId.GFXStep) {
await executeGraphicNextStep(context, triggerMode, actionOptions as ExampleGFXStepActionOptions)
} else if (actionId === ActionId.OGrafClear) {
await executeOGrafClear(context)
}
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import { IActionExecutionContext } from '@sofie-automation/blueprints-integration'
import { SourceLayer } from '../applyconfig/layers.js'

export async function executeOGrafClear(context: IActionExecutionContext): Promise<void> {
await context.stopPiecesOnLayers([SourceLayer.OGrafOverlay1, SourceLayer.OGrafOverlay2, SourceLayer.OGrafOverlay3])
}
153 changes: 153 additions & 0 deletions packages/blueprints/src/base/showstyle/helpers/ograf-graphics.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,153 @@
import { IBlueprintAdLibPiece, IBlueprintPiece, PieceLifespan, TSR } from '@sofie-automation/blueprints-integration'
import { ObjectType, OGrafGraphicObject, SomeObject } from '../../../common/definitions/objects.js'
import { literal } from '../../../common/util.js'
import { StudioConfig } from '../../studio/helpers/config.js'
import { OGrafLayers } from '../../studio/layers.js'
import { getOutputLayerForSourceLayer, SourceLayer } from '../applyconfig/layers.js'
import { getClipPlayerInput } from './clips.js'
import { createVisionMixerObjects } from './visionMixer.js'
import { TimelineBlueprintExt } from '../../studio/customTypes.js'
import { GraphicsResult } from './graphics.js'

export function parseOGrafGraphicsFromObjects(config: StudioConfig, objects: SomeObject[]): GraphicsResult {
const graphicsObjects = objects.filter((o): o is OGrafGraphicObject => o.objectType === ObjectType.OGrafGraphic)

return {
pieces: graphicsObjects.filter((o) => !o.isAdlib).map((o) => parseOGrafGraphic(config, o)),
adLibPieces: graphicsObjects.filter((o) => Boolean(o.isAdlib)).map((o, i) => parseAdlibOGrafGraphic(config, o, i)),
}
}

function parseOGrafGraphic(config: StudioConfig, object: OGrafGraphicObject): IBlueprintPiece {
const sourceLayer = getSourceLayer(object)

return {
externalId: object.id,
name: makeOGrafName(object),
lifespan: PieceLifespan.WithinPart,
sourceLayerId: sourceLayer,
outputLayerId: getOutputLayerForSourceLayer(sourceLayer),
content: {
timelineObjects: getGraphicTlObject(config, object, false),

previewRenderer: config.previewRenderer,
},
enable: {
start: object.objectTime ?? 0,
duration: object.duration > 0 ? object.duration : undefined,
},
prerollDuration: config.casparcgLatency,
}
}
function parseAdlibOGrafGraphic(config: StudioConfig, object: OGrafGraphicObject, index: number): IBlueprintAdLibPiece {
const sourceLayer = getSourceLayer(object)

return {
externalId: object.id,
name: makeOGrafName(object),
lifespan: PieceLifespan.WithinPart,
sourceLayerId: sourceLayer,
outputLayerId: getOutputLayerForSourceLayer(sourceLayer),
content: {
timelineObjects: getGraphicTlObject(config, object, true),
},
_rank: index, // todo - probably some offset for ordering
expectedDuration: object.duration,
}
}

function getSourceLayer(object: OGrafGraphicObject): SourceLayer {
switch (object.attributes.type) {
case 'full-screen':
return SourceLayer.OGrafFullScreen
case 'overlay1':
return SourceLayer.OGrafOverlay1
case 'overlay2':
return SourceLayer.OGrafOverlay2
case 'overlay3':
return SourceLayer.OGrafOverlay3
default:
return SourceLayer.OGrafOverlay1
}
}
function getGraphicTlLayer(object: OGrafGraphicObject): OGrafLayers {
switch (object.attributes.type) {
case 'full-screen':
return OGrafLayers.OGrafFullScreen
case 'overlay1':
return OGrafLayers.OGrafOverlay1
case 'overlay2':
return OGrafLayers.OGrafOverlay2
case 'overlay3':
return OGrafLayers.OGrafOverlay3
default:
return OGrafLayers.OGrafOverlay1
}
}

function getGraphicTlObject(
config: StudioConfig,
object: OGrafGraphicObject,
isAdlib?: boolean
): TimelineBlueprintExt[] {
const fullscreenAtemInput = getClipPlayerInput(config)
const timelineLayer = getGraphicTlLayer(object)
const isFullscreen = timelineLayer === OGrafLayers.OGrafFullScreen

return [
literal<TimelineBlueprintExt<TSR.TimelineContentOgrafAny>>({
id: '',
enable: {
start: 0, // TODO - this might not be quite right
},
layer: timelineLayer,
priority: 1 + (isAdlib ? 10 : 0),
content: {
deviceType: TSR.DeviceType.OGRAF,
type: TSR.TimelineContentTypeOgraf.GRAPHIC,

graphicId: object.attributes['ograf-id'],
playing: true,

data: object.attributes['ograf-data'],
useStopCommand: true,
},
}),
...(isFullscreen ? createVisionMixerObjects(config, fullscreenAtemInput?.input || 0, config.casparcgLatency) : []),
]
}

function makeOGrafName(object: OGrafGraphicObject): string {
const data = object.attributes['ograf-data'] || {}

if (Object.keys(data).length === 0) {
// data is empty
return object.clipName
}

{
let canUseThis = true
const nameParts: string[] = []
for (const [key, value] of Object.entries<unknown>(data)) {
if (typeof value === 'string') {
// omit some values that likely won't be useful to show in the name:
if (
value.startsWith('#') || // likely a color
value.startsWith('{') // likely json
)
continue

nameParts.push(value)
} else if (typeof value === 'number') {
nameParts.push(`${value}`)
} else if (typeof value === 'boolean') {
if (value) nameParts.push(`${key}`)
} else {
canUseThis = false
}
}
if (canUseThis) return `${object.clipName} | ${nameParts.join(', ')}`
}

return `${object.clipName} | ${JSON.stringify(data)}`
}
5 changes: 3 additions & 2 deletions packages/blueprints/src/base/showstyle/manifest.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import { validateConfig } from './validateConfig.js'
import { applyConfig } from './applyconfig/index.js'
import * as ConfigSchema from '../../$schemas/main-showstyle-config.json'
import { dereferenceSync } from 'dereference-json-schema'
import onRundownActivate from './onRundownActivate.js'

export const baseManifest: Omit<ShowStyleBlueprintManifest<ShowStyleConfig>, 'blueprintId' | 'configPresets'> = {
/** The type of this blueprint */
Expand Down Expand Up @@ -66,8 +67,8 @@ export const baseManifest: Omit<ShowStyleBlueprintManifest<ShowStyleConfig>, 'bl
*/
applyConfig,
/** Called when a RundownPlaylist has been activated */
onRundownActivate: async (_context: IRundownActivationContext) => {
// Noop
onRundownActivate: async (context: IRundownActivationContext) => {
return onRundownActivate(context)
},
// Uncomment this to enable config fixup migrations between blueprint versions.
// Note: When defined, fixUpConfig must be run after every blueprint upload before
Expand Down
21 changes: 21 additions & 0 deletions packages/blueprints/src/base/showstyle/onRundownActivate.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import { IRundownActivationContext } from '@sofie-automation/blueprints-integration'

export default async function onRundownActivate(_context: IRundownActivationContext): Promise<void> {
// Clear OGraf renderers:
// const mappings = context.getStudioMappings()
// const devices = await context.listPlayoutDevices()
// for (const device of devices) {
// if (device.deviceType === TSR.DeviceType.OGRAF) {
// for (const mapping of Object.values<BlueprintMapping>(mappings)) {
// if (mapping.device !== TSR.DeviceType.OGRAF) continue
// if (mapping.deviceId === `${device.deviceId}`) {
// if (mapping.options.mappingType === TSR.MappingOgrafType.RenderTarget) {
// await context.executeTSRAction(device.deviceId, TSR.OgrafActions.ClearGraphics, {
// renderTarget: mapping.options.renderTarget,
// })
// }
// }
// }
// }
// }
}
18 changes: 16 additions & 2 deletions packages/blueprints/src/base/showstyle/part-adapters/camera.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,9 @@
import { BlueprintResultPart, IBlueprintPiece, PieceLifespan } from '@sofie-automation/blueprints-integration'
import {
BlueprintResultPart,
IBlueprintAdLibPiece,
IBlueprintPiece,
PieceLifespan,
} from '@sofie-automation/blueprints-integration'
import { PartContext } from '../../../common/context.js'
import { ObjectType, StudioGuestObject } from '../../../common/definitions/objects.js'
import { literal } from '../../../common/util.js'
Expand All @@ -13,6 +18,7 @@ import { getSourceInfoFromRaw } from '../helpers/sources.js'
import { createVisionMixerObjects } from '../helpers/visionMixer.js'
import { getOutputLayerForSourceLayer, SourceLayer } from '../applyconfig/layers.js'
import { parseConfig } from '../helpers/config.js'
import { parseOGrafGraphicsFromObjects } from '../helpers/ograf-graphics.js'

export function generateCameraPart(context: PartContext, part: PartProps<CameraProps>): BlueprintResultPart {
const config = parseConfig(context).studio
Expand Down Expand Up @@ -40,6 +46,8 @@ export function generateCameraPart(context: PartContext, part: PartProps<CameraP

const graphics = parseGraphicsFromObjects(config, part.objects)
if (graphics.pieces) pieces.push(...graphics.pieces)
const ografGraphics = parseOGrafGraphicsFromObjects(config, part.objects)
if (ografGraphics.pieces) pieces.push(...ografGraphics.pieces)

const clips = parseClipsFromObjects(context, config, part.objects)

Expand All @@ -48,6 +56,12 @@ export function generateCameraPart(context: PartContext, part: PartProps<CameraP
pieces.push(addGuest(config, guestObj.attributes.count))
}

// Also create adlibs for cameras:
const cameraAdLibPiece: IBlueprintAdLibPiece = {
...cameraPiece,
_rank: 0,
}

return {
part: {
externalId: part.payload.externalId,
Expand All @@ -56,7 +70,7 @@ export function generateCameraPart(context: PartContext, part: PartProps<CameraP
expectedDuration: part.payload.duration,
},
pieces,
adLibPieces: [...graphics.adLibPieces, ...clips],
adLibPieces: [...graphics.adLibPieces, ...clips, cameraAdLibPiece, ...ografGraphics.adLibPieces],
actions: [],
}
}
Expand Down
5 changes: 4 additions & 1 deletion packages/blueprints/src/base/showstyle/part-adapters/dve.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import { getOutputLayerForSourceLayer, SourceLayer } from '../applyconfig/layers
import { TimelineBlueprintExt } from '../../studio/customTypes.js'
import { VmixInputConfig } from '../../../$schemas/generated/main-studio-config.js'
import { parseConfig } from '../helpers/config.js'
import { parseOGrafGraphicsFromObjects } from '../helpers/ograf-graphics.js'

const SUPER_SOURCE_LATENCY = 80
const SUPER_SOURCE_INPUT = 6000
Expand Down Expand Up @@ -219,6 +220,8 @@ export function generateDVEPart(context: PartContext, part: PartProps<DVEProps>)

const graphics = parseGraphicsFromObjects(config, part.objects)
if (graphics.pieces) pieces.push(...graphics.pieces)
const ografGraphics = parseOGrafGraphicsFromObjects(config, part.objects)
if (ografGraphics.pieces) pieces.push(...ografGraphics.pieces)

const clips = parseClipsFromObjects(context, config, part.objects)

Expand All @@ -230,7 +233,7 @@ export function generateDVEPart(context: PartContext, part: PartProps<DVEProps>)
expectedDuration: part.payload.duration,
},
pieces,
adLibPieces: [...graphics.adLibPieces, ...clips],
adLibPieces: [...graphics.adLibPieces, ...clips, ...ografGraphics.adLibPieces],
actions: [],
}
}
Loading
Loading