Skip to content
Merged
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
2 changes: 1 addition & 1 deletion apps/app/nodemon.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{
"watch": ["src/**/*"],
"watch": ["src/**/*", "../../shared/packages/*/src/**/*"],
"ignore": ["*.test.ts", "src/react/*", "README"],
"exec": "electron ./dist/main.mjs --inspect=9229",
"ext": "ts"
Expand Down
4 changes: 2 additions & 2 deletions apps/app/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@
"@sofie-automation/sorensen": "^1.5.8",
"axios": "^1.7.7",
"bufferutil": "^4.0.8",
"casparcg-connection": "^6.2.0",
"casparcg-connection": "*",
"classnames": "^2.5.1",
"deep-extend": "^0.6.0",
"deepmerge-ts": "^5.1.0",
Expand Down Expand Up @@ -109,7 +109,7 @@
"short-uuid": "^5.2.0",
"socket.io-client": "^4.8.0",
"superfly-timeline": "^8.3.1",
"timeline-state-resolver-types": "^8.1.2",
"timeline-state-resolver-types": "^9.2.0",
"tiny-warning": "^1.0.3",
"type-fest": "^4.26.1",
"url": "^0.11.4",
Expand Down
17 changes: 10 additions & 7 deletions apps/app/src/electron/EverythingService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,9 +36,10 @@ import {
TSRTimelineObj,
Mapping,
DeviceType,
MappingCasparCG,
SomeMappingCasparCG,
TSRTimelineContent,
DeviceOptionsAny,
TSRMappingOptions,
} from 'timeline-state-resolver-types'
import { ActionDescription, IPCServerMethods, UndoableResult, UpdateAppDataOptions } from '../ipc/IPCAPI.js'
import { GroupPreparedPlayData } from '../models/GUI/PreparedPlayhead.js'
Expand Down Expand Up @@ -1897,7 +1898,7 @@ export class EverythingService implements ConvertToServerSide<IPCServerMethods>
obj.layer = addToLayerId
usePreviousLayerId = obj.layer

const mapping = project.mappings[obj.layer] as Mapping | undefined
const mapping = project.mappings[obj.layer] as Mapping<TSRMappingOptions> | undefined
const allow = mapping && allowAddingResourceToLayer(project, resource, mapping)
if (!allow) {
if (arg.resourceIds.length > 1) continue // ignore the error if we're adding multiple resources
Expand Down Expand Up @@ -2333,7 +2334,7 @@ export class EverythingService implements ConvertToServerSide<IPCServerMethods>
}

// Find all timeline objects which reside on the missing layer.
const createdMappings: { [mappingId: string]: Mapping } = {}
const createdMappings: { [mappingId: string]: Mapping<TSRMappingOptions> } = {}

for (const group of rundown.groups) {
for (const part of group.parts) {
Expand Down Expand Up @@ -2378,7 +2379,7 @@ export class EverythingService implements ConvertToServerSide<IPCServerMethods>
throw new Error('No layer could be automatically created.')
case 1: {
newLayerId = Object.keys(createdMappings)[0]
const newMapping = Object.values<Mapping>(createdMappings)[0]
const newMapping = Object.values<Mapping<TSRMappingOptions>>(createdMappings)[0]

if (!newMapping.layerName) {
throw new Error('INTERNAL ERROR: Layer lacks a name.')
Expand Down Expand Up @@ -2797,7 +2798,7 @@ export class EverythingService implements ConvertToServerSide<IPCServerMethods>
for (const layerId of Object.keys(possibleLayers)) {
const mapping = arg.project.mappings[layerId]
if (mapping?.device === DeviceType.CASPARCG) {
const m = mapping as MappingCasparCG
const m = mapping.options as SomeMappingCasparCG

if (useCasparCGChannel && m.channel !== useCasparCGChannel) {
possibleLayers[layerId] = -999
Expand All @@ -2822,14 +2823,16 @@ export class EverythingService implements ConvertToServerSide<IPCServerMethods>

if (!addToLayerId) {
// If no layer was found, create a new layer:
let newMapping: Mapping | undefined = undefined
let newMapping: Mapping<TSRMappingOptions> | undefined = undefined
const deviceId = arg.resource?.deviceId || guessDeviceIdFromTimelineObject(arg.project, arg.obj)
if (deviceId) {
newMapping = getMappingFromTimelineObject(arg.obj, deviceId, arg.resource)
}

if (!newMapping && arg.originalLayerId !== undefined) {
const originalLayer = arg.project.mappings[arg.originalLayerId] as Mapping | undefined
const originalLayer = arg.project.mappings[arg.originalLayerId] as
| Mapping<TSRMappingOptions>
| undefined
if (originalLayer) {
newMapping = {
...deepClone(originalLayer),
Expand Down
27 changes: 18 additions & 9 deletions apps/app/src/electron/makeDevData.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,14 @@ import {
DeviceOptionsCasparCG,
DeviceType,
Mapping,
MappingAtem,
SomeMappingAtem,
MappingAtemType,
MappingCasparCG,
SomeMappingCasparCG,
TSRTimelineObj,
TimelineContentCCGMedia,
TimelineContentTypeCasparCg,
MappingCasparCGType,
TSRMappingOptions,
} from 'timeline-state-resolver-types'
import { shortID } from '../lib/util.js'
import { Bridge } from '../models/project/Bridge.js'
Expand Down Expand Up @@ -68,12 +70,15 @@ export function makeDevData(): {
// Mappings
{
for (const m of rangeIds(1, 50)) {
project.mappings[m.id] = literal<MappingCasparCG>({
project.mappings[m.id] = literal<Mapping<SomeMappingCasparCG>>({
device: device.type,
deviceId: casparDeviceId,
layerName: `CasparCG 1-${m.i}`,
channel: 1,
layer: m.i,
options: {
mappingType: MappingCasparCGType.Layer,
channel: 1,
layer: m.i,
},
})
}
}
Expand All @@ -90,12 +95,14 @@ export function makeDevData(): {
// Mappings
{
for (const m of rangeIds(1, 10)) {
project.mappings[m.id] = literal<MappingAtem>({
project.mappings[m.id] = literal<Mapping<SomeMappingAtem>>({
device: device.type,
deviceId: deviceId,
layerName: `ME 1-${m.i}`,
mappingType: MappingAtemType.MixEffect,
index: m.i,
options: {
mappingType: MappingAtemType.MixEffect,
index: m.i,
},
})
}
}
Expand Down Expand Up @@ -138,7 +145,9 @@ export function makeDevData(): {
// Timeline
{
const layerId = pickRandom(
Object.entries<Mapping>(project.mappings).filter((e) => e[1].deviceId === casparDeviceId),
Object.entries<Mapping<TSRMappingOptions>>(project.mappings).filter(
(e) => e[1].deviceId === casparDeviceId
),
notRandom
)[0]

Expand Down
45 changes: 42 additions & 3 deletions apps/app/src/electron/storageHandler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,13 +12,15 @@ import { protectString, ResourceAny, ResourceId, MetadataAny, TSRDeviceId } from
import { baseFolder } from '../lib/baseFolder.js'
import * as _ from 'lodash-es'
import { makeDevData } from './makeDevData.js'
import { getPartLabel, shortID } from '../lib/util.js'
import { DeviceType, Mapping, TimelineContentTypeCasparCg } from 'timeline-state-resolver-types'
import { Complete, getPartLabel, shortID } from '../lib/util.js'
import { DeviceType, Mapping, TimelineContentTypeCasparCg, TSRMappingOptions } from 'timeline-state-resolver-types'
import { CURRENT_VERSION } from './bridgeHandler.js'
import { ensureValidId, ensureValidObject } from '../lib/TimelineObj.js'
import { AnalogInput } from '../models/project/AnalogInput.js'
import { ValidatorCache } from 'graphics-data-definition'
import { Bridge } from '../models/project/Bridge.js'
import { getDefaultMappingForDeviceType } from '../lib/TSRMappings.js'
import { translateMappingType, translateTSRDeviceType } from '../lib/TSR.js'

const fsWriteFile = fs.promises.writeFile
const fsAppendFile = fs.promises.appendFile
Expand Down Expand Up @@ -1237,9 +1239,46 @@ export class StorageHandler extends EventEmitter {
// Ensure mapping id's are valid, Timeline-wise:
const mappings = project.mappings
project.mappings = {}
for (const [layerName, mapping] of Object.entries<Mapping>(mappings)) {
for (const [layerName, mapping] of Object.entries<Mapping<TSRMappingOptions>>(mappings)) {
project.mappings[ensureValidId(layerName)] = mapping
}

// Fixup bridge device types (TSR r51):
for (const bridge of Object.values(project.bridges)) {
if (bridge.clientSidePeripheralSettings) {
for (const device of Object.values(bridge.settings.devices)) {
device.type = translateTSRDeviceType(device.type)
}
}
}

// Fixup mapping device types (TSR r51):
for (const [layerName, mapping] of Object.entries(project.mappings)) {
mapping.device = translateTSRDeviceType(mapping.device)

if (!mapping.options) {
const newMapping: Complete<Mapping<any>> = {
device: mapping.device,
deviceId: mapping.deviceId,
layerName: mapping.layerName,
options: {
..._.omit(mapping, 'device', 'deviceId', 'layerName'),
},
}
if (newMapping.options.mappingType === undefined) {
// If there is no mappingType, set it to the default:
const tmpMapping = getDefaultMappingForDeviceType(newMapping.device, newMapping.deviceId as any, {})
newMapping.options.mappingType = tmpMapping.options.mappingType
} else {
newMapping.options.mappingType = translateMappingType(
newMapping.device,
newMapping.options.mappingType
)
}

project.mappings[layerName] = newMapping
}
}
}
private ensureCompatibilityRundown(rundown: Omit<Rundown, 'id'>) {
for (const group of rundown.groups) {
Expand Down
165 changes: 165 additions & 0 deletions apps/app/src/lib/TSR.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,11 @@
import {
DeviceType,
MappingAtemType,
MappingObsType,
MappingPanasonicPTZType,
MappingVmixType,
} from 'timeline-state-resolver-types'

export const ATEM_DEFAULT_TRANSITION_RATE = 25

export function getAtemFrameRate(): number {
Expand All @@ -6,3 +14,160 @@ export function getAtemFrameRate(): number {

return 25
}

export function translateMappingType(deviceType: DeviceType, mappingType: string | undefined): string | undefined {
// Note: Not all device types need fixing, many didn't use the property as a number when the change was made
// https://github.com/nrkno/sofie-timeline-state-resolver/pull/237

switch (deviceType) {
case DeviceType.ATEM: {
switch (mappingType as any as number) {
case 0:
return MappingAtemType.MixEffect
case 1:
return MappingAtemType.DownStreamKeyer
case 2:
return MappingAtemType.SuperSourceBox
case 3:
return MappingAtemType.Auxilliary
case 4:
return MappingAtemType.MediaPlayer
case 5:
return MappingAtemType.SuperSourceProperties
case 6:
return MappingAtemType.AudioChannel
case 7:
return MappingAtemType.MacroPlayer
case 8:
return MappingAtemType.AudioRouting
default:
return mappingType
}
}
case DeviceType.OBS: {
switch (mappingType as any as number) {
case 0:
return MappingObsType.CurrentTransition
case 1:
return MappingObsType.CurrentScene
case 2:
return MappingObsType.Recording
case 3:
return MappingObsType.Streaming
case 4:
return MappingObsType.SceneItem
case 5:
return MappingObsType.InputAudio
case 6:
return MappingObsType.InputSettings
default:
return mappingType
}
}
case DeviceType.PANASONIC_PTZ: {
switch (mappingType as any as number) {
case 0:
return MappingPanasonicPTZType.PresetSpeed
case 1:
return MappingPanasonicPTZType.PresetMem
case 2:
return MappingPanasonicPTZType.Zoom
case 3:
return MappingPanasonicPTZType.ZoomSpeed
default:
return mappingType
}
}
case DeviceType.VMIX: {
switch (mappingType as any as number) {
case 0:
return MappingVmixType.Program
case 1:
return MappingVmixType.Preview
case 2:
return MappingVmixType.Input
case 3:
return MappingVmixType.AudioChannel
case 4:
return MappingVmixType.Output
case 5:
return MappingVmixType.Overlay
case 6:
return MappingVmixType.Recording
case 7:
return MappingVmixType.Streaming
case 8:
return MappingVmixType.External
case 9:
return MappingVmixType.FadeToBlack
case 10:
return MappingVmixType.Fader
case 11:
return MappingVmixType.Script
default:
return mappingType
}
}
}

return mappingType
}

export function translateTSRDeviceType(deviceType: DeviceType): DeviceType {
// https://github.com/nrkno/sofie-timeline-state-resolver/pull/237

if (typeof deviceType === 'number') {
return oldDeviceTypeToNewMapping[deviceType] || deviceType
}
return deviceType
}

enum OldDeviceType {
ABSTRACT = 0,
CASPARCG = 1,
ATEM = 2,
LAWO = 3,
HTTPSEND = 4,
PANASONIC_PTZ = 5,
TCPSEND = 6,
HYPERDECK = 7,
PHAROS = 8,
OSC = 9,
HTTPWATCHER = 10,
SISYFOS = 11,
QUANTEL = 12,
VIZMSE = 13,
SINGULAR_LIVE = 14,
SHOTOKU = 15,
VMIX = 20,
OBS = 21,
SOFIE_CHEF = 22,
TELEMETRICS = 23,
TRICASTER = 24,
MULTI_OSC = 25,
}

const oldDeviceTypeToNewMapping = {
[OldDeviceType.ABSTRACT]: DeviceType.ABSTRACT,
[OldDeviceType.CASPARCG]: DeviceType.CASPARCG,
[OldDeviceType.ATEM]: DeviceType.ATEM,
[OldDeviceType.LAWO]: DeviceType.LAWO,
[OldDeviceType.HTTPSEND]: DeviceType.HTTPSEND,
[OldDeviceType.PANASONIC_PTZ]: DeviceType.PANASONIC_PTZ,
[OldDeviceType.TCPSEND]: DeviceType.TCPSEND,
[OldDeviceType.HYPERDECK]: DeviceType.HYPERDECK,
[OldDeviceType.PHAROS]: DeviceType.PHAROS,
[OldDeviceType.OSC]: DeviceType.OSC,
[OldDeviceType.HTTPWATCHER]: DeviceType.HTTPWATCHER,
[OldDeviceType.SISYFOS]: DeviceType.SISYFOS,
[OldDeviceType.QUANTEL]: DeviceType.QUANTEL,
[OldDeviceType.VIZMSE]: DeviceType.VIZMSE,
[OldDeviceType.SINGULAR_LIVE]: DeviceType.SINGULAR_LIVE,
[OldDeviceType.SHOTOKU]: DeviceType.SHOTOKU,
[OldDeviceType.VMIX]: DeviceType.VMIX,
[OldDeviceType.OBS]: DeviceType.OBS,
[OldDeviceType.SOFIE_CHEF]: DeviceType.SOFIE_CHEF,
[OldDeviceType.TELEMETRICS]: DeviceType.TELEMETRICS,
[OldDeviceType.TRICASTER]: DeviceType.TRICASTER,
[OldDeviceType.MULTI_OSC]: DeviceType.MULTI_OSC,
}
Loading
Loading