diff --git a/.husky/pre-commit b/.husky/pre-commit new file mode 100644 index 000000000..d2ae35e84 --- /dev/null +++ b/.husky/pre-commit @@ -0,0 +1,4 @@ +#!/bin/sh +. "$(dirname "$0")/_/husky.sh" + +yarn lint-staged diff --git a/config/webpack.config.js b/config/webpack.config.js index 03dc72483..874828977 100644 --- a/config/webpack.config.js +++ b/config/webpack.config.js @@ -30,7 +30,7 @@ module.exports = env => { // versionIntegration = versionIntegration.replace(/[^\d.]/g, '') || '0.0.0' versionTSRTypes = '1.3.0' - versionIntegration = '46.2.0' + versionIntegration = '46.3.0' const entrypoints = env.bundle ? GetEntrypointsForBundle(env.bundle) : BlueprintEntrypoints diff --git a/docker-entrypoint.sh b/docker-entrypoint.sh index 80b2389ec..b31e8c371 100755 --- a/docker-entrypoint.sh +++ b/docker-entrypoint.sh @@ -18,6 +18,7 @@ yarn build-now --env.server=$SERVER if [[ -d "/opt/blueprints/nginx" ]]; then echo "Volume mount exists copying files" - cp -r shelf-layouts/ nginx/ cp -r external-frames/ nginx/ -fi \ No newline at end of file +fi + +./scripts/upload-layouts.sh layouts/ diff --git a/external-frames/multiview/off-tubes-shelf.json b/external-frames/multiview/off-tubes-shelf.json index ce90d2672..1359332a1 120000 --- a/external-frames/multiview/off-tubes-shelf.json +++ b/external-frames/multiview/off-tubes-shelf.json @@ -1 +1 @@ -../../shelf-layouts/Kommentator.json \ No newline at end of file +../../layouts/tv2_offtube_showstyle/Kommentator.json \ No newline at end of file diff --git a/shelf-layouts/AFVD_Default_showstyle_hotkeys.json b/layouts/AFVD_Default_showstyle_hotkeys.json similarity index 100% rename from shelf-layouts/AFVD_Default_showstyle_hotkeys.json rename to layouts/AFVD_Default_showstyle_hotkeys.json diff --git a/shelf-layouts/inspect all adlibs.json b/layouts/inspect all adlibs.json old mode 100755 new mode 100644 similarity index 100% rename from shelf-layouts/inspect all adlibs.json rename to layouts/inspect all adlibs.json diff --git a/shelf-layouts/Mini_Shelf_Layout.json b/layouts/tv2_afvd_showstyle/Mini_Shelf_Layout.json similarity index 96% rename from shelf-layouts/Mini_Shelf_Layout.json rename to layouts/tv2_afvd_showstyle/Mini_Shelf_Layout.json index a06c12f44..89c302580 100644 --- a/shelf-layouts/Mini_Shelf_Layout.json +++ b/layouts/tv2_afvd_showstyle/Mini_Shelf_Layout.json @@ -1,4 +1,5 @@ { + "_id": "fgDCkL4Y5GQrdyuRW", "name": "Flow Producer Mini Shelf", "type": "dashboard_layout", "icon": "", diff --git a/shelf-layouts/Presenter_Clock_Layout.json b/layouts/tv2_afvd_showstyle/Presenter_Clock_Layout.json similarity index 100% rename from shelf-layouts/Presenter_Clock_Layout.json rename to layouts/tv2_afvd_showstyle/Presenter_Clock_Layout.json diff --git a/shelf-layouts/Rundown_Header_Layout.json b/layouts/tv2_afvd_showstyle/Rundown_Header_Layout.json similarity index 80% rename from shelf-layouts/Rundown_Header_Layout.json rename to layouts/tv2_afvd_showstyle/Rundown_Header_Layout.json index 3e40d02f0..1d0f91d73 100644 --- a/shelf-layouts/Rundown_Header_Layout.json +++ b/layouts/tv2_afvd_showstyle/Rundown_Header_Layout.json @@ -25,11 +25,15 @@ "y": 0.3, "width": 1, "height": 1, - "scale": 0.7, + "scale": 1, "customClasses": [ "" ], - "hideDiff": true + "hideDiff": true, + "xUnit": "rem", + "yUnit": "rem", + "widthUnit": "rem", + "heightUnit": "rem" }, { "_id": "4QhdsoZfbDXeNXZLY", @@ -45,14 +49,17 @@ "nextInCurrentPart": false, "oneNextPerSourceLayer": false, "plannedEndText": "", - "scale": 0.7, + "scale": 1, "width": 4, "height": 1, "x": 8.8, "y": 0.3, "hidePlannedEnd": true, "hideCountdown": true, - "xUnit": "%" + "xUnit": "%", + "yUnit": "rem", + "widthUnit": "rem", + "heightUnit": "rem" }, { "_id": "NAT56wYzfbKRX9Dw2", @@ -70,12 +77,15 @@ "plannedEndText": "Planned end", "hideCountdown": true, "hideDiff": true, - "scale": 0.7, + "scale": 1, "y": 0.3, "x": 18.2, "width": 1, "height": 1, - "xUnit": "%" + "xUnit": "%", + "yUnit": "rem", + "widthUnit": "rem", + "heightUnit": "rem" }, { "_id": "qYm6yy529p69wkxC4", @@ -90,12 +100,15 @@ "default": false, "nextInCurrentPart": false, "oneNextPerSourceLayer": false, - "scale": 0.7, + "scale": 1, "y": 0.9, "x": 22, "width": 10, "height": 1, - "xUnit": "%" + "xUnit": "%", + "yUnit": "rem", + "widthUnit": "rem", + "heightUnit": "rem" }, { "_id": "tsCeq5TJR6PNk8mbx", @@ -110,13 +123,16 @@ "default": false, "nextInCurrentPart": false, "oneNextPerSourceLayer": false, - "scale": 0.7, + "scale": 1, "width": 10, "height": 1, "text": "INEWS RUNDOWN", "x": 22, "y": 0.3, - "xUnit": "%" + "xUnit": "%", + "yUnit": "rem", + "widthUnit": "rem", + "heightUnit": "rem" }, { "_id": "uWHhxH2Mchdeds4q6", @@ -131,11 +147,15 @@ "default": false, "nextInCurrentPart": false, "oneNextPerSourceLayer": false, - "scale": 0.7, + "scale": 1, "y": 0.3, "x": -4.1, "width": 2.6, - "height": 1 + "height": 1, + "xUnit": "rem", + "yUnit": "rem", + "widthUnit": "rem", + "heightUnit": "rem" }, { "_id": "awa4skeRvYqC2ZFhd", @@ -150,12 +170,15 @@ "default": false, "nextInCurrentPart": false, "oneNextPerSourceLayer": false, - "scale": 0.7, + "scale": 1, "y": 0.3, "x": 74.4, "width": 15, "height": 1, - "xUnit": "%" + "xUnit": "%", + "yUnit": "rem", + "widthUnit": "rem", + "heightUnit": "rem" }, { "_id": "NaaYLEHtqmtWkAD48", @@ -170,12 +193,15 @@ "default": false, "nextInCurrentPart": false, "oneNextPerSourceLayer": false, - "scale": 0.7, + "scale": 1, "x": 67.3, "y": 0.3, "width": 5, "height": 1, - "xUnit": "%" + "xUnit": "%", + "yUnit": "rem", + "widthUnit": "rem", + "heightUnit": "rem" }, { "_id": "NFhMKGcs94BGTAvs2", @@ -193,10 +219,12 @@ "iconColor": "#000000", "x": 37, "width": 27, - "height": 2, + "height": 1.4, "y": 0, "xUnit": "%", - "widthUnit": "%" + "widthUnit": "%", + "yUnit": "rem", + "heightUnit": "rem" }, { "_id": "ckTBdCMfJp7mJhBTp", @@ -211,20 +239,24 @@ "default": false, "nextInCurrentPart": false, "oneNextPerSourceLayer": false, - "scale": 1.3, + "scale": 1.6, "customClasses": [ "" ], "timingType": "count_down", - "y": -1.35, - "x": 37.5, + "y": -1.6, + "x": 38, "hideLabel": true, "requiredLayerIds": [ "studio0_clip", "studio0_voiceover" ], "xUnit": "%", - "width": 5 + "width": 5, + "yUnit": "rem", + "widthUnit": "rem", + "heightUnit": "rem", + "height": -0.8 }, { "_id": "uW28ZfmJ6b2hMZo5Y", @@ -240,11 +272,14 @@ "nextInCurrentPart": false, "oneNextPerSourceLayer": false, "text": "PART COUNTDOWN", - "scale": 0.7, - "y": 0.05, + "scale": 0.9, + "y": 0.1, "x": 38, "xUnit": "%", - "width": 6 + "width": 6, + "yUnit": "rem", + "widthUnit": "rem", + "heightUnit": "rem" }, { "_id": "zvkPAEWTZWjLbyHPg", @@ -260,12 +295,15 @@ "nextInCurrentPart": false, "oneNextPerSourceLayer": false, "timingType": "count_up", - "scale": 0.6, + "scale": 0.9, "x": -36.7, - "y": 0.3, + "y": 0.1, "xUnit": "%", - "widthUnit": "em", - "width": 3.8 + "widthUnit": "rem", + "width": 3.8, + "hideLabel": false, + "yUnit": "rem", + "heightUnit": "rem" }, { "_id": "ou4qa2eEmDihBykfQ", @@ -281,11 +319,14 @@ "nextInCurrentPart": false, "oneNextPerSourceLayer": false, "timingType": "count_down", - "scale": 0.6, - "y": 0.3, + "scale": 0.9, + "y": 0.1, "x": 47, "xUnit": "%", - "width": 6 + "width": 6, + "yUnit": "rem", + "widthUnit": "rem", + "heightUnit": "rem" }, { "_id": "5dPjQgaZEd3cgjoCo", @@ -300,9 +341,9 @@ "default": false, "nextInCurrentPart": false, "oneNextPerSourceLayer": false, - "scale": 0.6, - "y": 1.4, - "x": 50.6, + "scale": 0.8, + "y": 1.2, + "x": 50.7, "requiredLayerIds": [ "studio0_script" ], @@ -315,7 +356,9 @@ ], "requireAllAdditionalSourcelayers": false, "xUnit": "%", - "widthUnit": "%" + "widthUnit": "%", + "yUnit": "rem", + "heightUnit": "rem" }, { "_id": "mK85XTh7ERR6Ddhm6", @@ -331,10 +374,13 @@ "nextInCurrentPart": false, "oneNextPerSourceLayer": false, "text": "END WORDS", - "scale": 0.7, + "scale": 1, "x": 47, - "y": 1.7, - "xUnit": "%" + "y": 1.8, + "xUnit": "%", + "yUnit": "rem", + "widthUnit": "rem", + "heightUnit": "rem" } ], "showStyleBaseId": "show0" diff --git a/shelf-layouts/Rundown_View_Layout.json b/layouts/tv2_afvd_showstyle/Rundown_View_Layout.json similarity index 100% rename from shelf-layouts/Rundown_View_Layout.json rename to layouts/tv2_afvd_showstyle/Rundown_View_Layout.json diff --git a/shelf-layouts/Shortcuts and adlib scroll.json b/layouts/tv2_afvd_showstyle/Shortcuts_and_adlib_scroll.json old mode 100755 new mode 100644 similarity index 78% rename from shelf-layouts/Shortcuts and adlib scroll.json rename to layouts/tv2_afvd_showstyle/Shortcuts_and_adlib_scroll.json index e77fea9c4..35d3ffa45 --- a/shelf-layouts/Shortcuts and adlib scroll.json +++ b/layouts/tv2_afvd_showstyle/Shortcuts_and_adlib_scroll.json @@ -13,8 +13,8 @@ "rundownBaseline": false, "default": false, "x": 1, - "y": 7, - "width": -15.8, + "y": 6.9, + "width": -15.3, "height": 5, "overflowHorizontally": true, "showAsTimeline": true, @@ -42,7 +42,11 @@ "flow_producer" ], "toggleOnSingleClick": true, - "scale": 0 + "scale": 1.05, + "xUnit": "rem", + "yUnit": "rem", + "widthUnit": "rem", + "heightUnit": "rem" }, { "_id": "s4KkTnsERPNcrkqeD", @@ -53,12 +57,14 @@ "rank": 0, "rundownBaseline": false, "default": false, - "url": "http://10.201.76.12:8005/keyboardmap", - "x": 1.2, - "y": 18, - "width": 54.8, - "height": 18, - "scale": 0.79 + "x": 1, + "y": 21.3, + "width": 59.2, + "scale": 0.79, + "xUnit": "rem", + "yUnit": "rem", + "widthUnit": "rem", + "heightUnit": "rem" }, { "_id": "pyRvtqdtAbFE2gekk", @@ -71,8 +77,8 @@ "default": false, "x": -1, "y": 0, - "width": 15.7, - "height": 15, + "width": 13.5, + "height": 18.9, "includeClearInRundownBaseline": false, "assignHotKeys": false, "hide": false, @@ -85,25 +91,11 @@ "buttonWidthScale": 1.7, "buttonHeightScale": 1.7, "toggleOnSingleClick": true, - "scale": 0.6 - }, - { - "_id": "QZ6xdWdFmm5Mu7ZKJ", - "type": "filter", - "name": "", - "currentSegment": false, - "displayStyle": "list", - "rank": 0, - "rundownBaseline": "only", - "default": false, - "width": 0, - "height": 0, - "y": -1, - "x": -1, - "includeClearInRundownBaseline": true, - "assignHotKeys": true, - "hide": true, - "toggleOnSingleClick": true + "scale": 0.9, + "xUnit": "rem", + "yUnit": "rem", + "widthUnit": "rem", + "heightUnit": "rem" }, { "_id": "j54rMBuuTvufKJ8yp", @@ -117,17 +109,22 @@ "hideDuplicates": true, "default": false, "x": 1, - "y": 14, + "y": 13.9, "sourceLayerIds": [ "studio0_dve" ], - "width": -15.8, + "width": -15.3, "height": 5, "buttonWidthScale": 1.5, "buttonHeightScale": 1.5, "showAsTimeline": true, "includeClearInRundownBaseline": true, - "toggleOnSingleClick": true + "toggleOnSingleClick": true, + "scale": 1.05, + "xUnit": "rem", + "yUnit": "rem", + "widthUnit": "rem", + "heightUnit": "rem" }, { "_id": "nmNfZ8azaAAHD7jjT", @@ -135,7 +132,7 @@ "name": "ADLIB PANEL: server", "currentSegment": false, "displayStyle": "buttons", - "rank": 0, + "rank": 0.3, "rundownBaseline": false, "showThumbnailsInList": false, "hideDuplicates": true, @@ -147,11 +144,16 @@ "buttonWidthScale": 1.5, "buttonHeightScale": 1.5, "y": 0, - "width": -15.8, - "height": 5, + "width": -15.3, + "height": 4.9, "showAsTimeline": true, "includeClearInRundownBaseline": true, - "toggleOnSingleClick": true + "toggleOnSingleClick": true, + "scale": 1.05, + "xUnit": "rem", + "yUnit": "rem", + "widthUnit": "rem", + "heightUnit": "rem" } ], "type": "dashboard_layout", diff --git a/shelf-layouts/Kommentator.json b/layouts/tv2_offtube_showstyle/Kommentator.json similarity index 81% rename from shelf-layouts/Kommentator.json rename to layouts/tv2_offtube_showstyle/Kommentator.json index 23ee60f43..51b6fa752 100644 --- a/shelf-layouts/Kommentator.json +++ b/layouts/tv2_offtube_showstyle/Kommentator.json @@ -11,12 +11,16 @@ "rank": 0, "rundownBaseline": false, "default": false, - "url": "http://multiview.sofqbXX-od.tv2.local/", + "url": "http://multiview.{{studio.settings.sofieUrl}}/", "width": -1, "height": -1, "x": 0, "y": 0, - "scale": 0 + "scale": 1, + "xUnit": "rem", + "yUnit": "rem", + "widthUnit": "rem", + "heightUnit": "rem" }, { "_id": "pyRvtqdtAbFE2gekk", @@ -45,7 +49,11 @@ ], "showThumbnailsInList": true, "hideDuplicates": false, - "toggleOnSingleClick": true + "toggleOnSingleClick": true, + "xUnit": "rem", + "yUnit": "rem", + "widthUnit": "rem", + "heightUnit": "rem" }, { "_id": "QZ6xdWdFmm5Mu7ZKJ", @@ -79,7 +87,11 @@ "kommentator" ], "showThumbnailsInList": true, - "toggleOnSingleClick": true + "toggleOnSingleClick": true, + "xUnit": "rem", + "yUnit": "rem", + "widthUnit": "rem", + "heightUnit": "rem" }, { "_id": "zHX89zpASjzyn7GTz", @@ -107,7 +119,11 @@ "studio0_voiceover" ], "showThumbnailsInList": true, - "toggleOnSingleClick": true + "toggleOnSingleClick": true, + "xUnit": "rem", + "yUnit": "rem", + "widthUnit": "rem", + "heightUnit": "rem" }, { "_id": "YC7RNPTbkXd5wnagp", @@ -125,7 +141,6 @@ "displayTakeButtons": true, "sourceLayerIds": [ "studio0_graphicsIdent", - "studio0_graphicsIdent_persistent", "studio0_graphicsTop", "studio0_graphicsLower", "studio0_pilotOverlay" @@ -135,7 +150,11 @@ ], "hideDuplicates": true, "lineBreak": "\n - ", - "toggleOnSingleClick": true + "toggleOnSingleClick": true, + "xUnit": "rem", + "yUnit": "rem", + "widthUnit": "rem", + "heightUnit": "rem" }, { "_id": "icuekg6wiJxejsYsZ", @@ -159,7 +178,11 @@ "kommentator" ], "showThumbnailsInList": true, - "toggleOnSingleClick": true + "toggleOnSingleClick": true, + "xUnit": "rem", + "yUnit": "rem", + "widthUnit": "rem", + "heightUnit": "rem" }, { "_id": "6GDpdq547HjjZL9YK", @@ -179,12 +202,14 @@ "scale": 0, "thumbnailPriorityNextPieces": true, "hideThumbnailsForActivePieces": true, - "thumbnailPriorityNextPieces": true, - "hideThumbnailsForActivePieces": true, "thumbnailSourceLayerIds": [ "studio0_full", "studio0_jingle" - ] + ], + "xUnit": "rem", + "yUnit": "rem", + "widthUnit": "rem", + "heightUnit": "rem" }, { "_id": "gho9Kigti38sQYqq3", @@ -205,7 +230,11 @@ "studio0_offtube_jingle" ], "showBlackIfNoThumbnailPiece": false, - "hideThumbnailsForActivePieces": true + "hideThumbnailsForActivePieces": true, + "xUnit": "rem", + "yUnit": "rem", + "widthUnit": "rem", + "heightUnit": "rem" }, { "_id": "ooCyWRXeDE6ufYWWQ", @@ -223,7 +252,11 @@ "role": "queue", "tags": [ "offtube_set_cam_next" - ] + ], + "xUnit": "rem", + "yUnit": "rem", + "widthUnit": "rem", + "heightUnit": "rem" }, { "_id": "PDKiKYeYB7zcAq6Lp", @@ -242,7 +275,11 @@ "tags": [ "offtube_set_remote_next" ], - "adlibRank": 0 + "adlibRank": 0, + "xUnit": "rem", + "yUnit": "rem", + "widthUnit": "rem", + "heightUnit": "rem" }, { "_id": "AMrCRTvefKDS6qogp", @@ -261,7 +298,11 @@ "offtube_set_remote_next" ], "role": "queue", - "adlibRank": 1 + "adlibRank": 1, + "xUnit": "rem", + "yUnit": "rem", + "widthUnit": "rem", + "heightUnit": "rem" }, { "_id": "HLzpg7NdLk6tdYZfT", @@ -280,7 +321,11 @@ "offtube_set_remote_next" ], "role": "queue", - "adlibRank": 2 + "adlibRank": 2, + "xUnit": "rem", + "yUnit": "rem", + "widthUnit": "rem", + "heightUnit": "rem" }, { "_id": "mRPYndcCsfZJL9zYH", @@ -300,7 +345,11 @@ ], "role": "queue", "thumbnailPriorityNextPieces": false, - "showBlackIfNoThumbnailPiece": false + "showBlackIfNoThumbnailPiece": false, + "xUnit": "rem", + "yUnit": "rem", + "widthUnit": "rem", + "heightUnit": "rem" }, { "_id": "PDKiKYeYB7zcAq6La", @@ -323,7 +372,11 @@ "studio0_offtube_jingle", "studio0_jingle" ], - "showBlackIfNoThumbnailPiece": true + "showBlackIfNoThumbnailPiece": true, + "xUnit": "rem", + "yUnit": "rem", + "widthUnit": "rem", + "heightUnit": "rem" }, { "_id": "AMrCRTvefKDS6qoga", @@ -342,7 +395,11 @@ "tags": [ "offtube_set_server_next" ], - "assignHotKeys": false + "assignHotKeys": false, + "xUnit": "rem", + "yUnit": "rem", + "widthUnit": "rem", + "heightUnit": "rem" }, { "_id": "HLzpg7NdLk6tdYZfa", @@ -361,7 +418,11 @@ "tags": [ "offtube_set_dve_next" ], - "role": "queue" + "role": "queue", + "xUnit": "rem", + "yUnit": "rem", + "widthUnit": "rem", + "heightUnit": "rem" }, { "_id": "zavcByHmMndMvZjCA", @@ -381,7 +442,12 @@ "studio0_clip", "studio0_voiceover", "studio0_jingle" - ] + ], + "xUnit": "rem", + "yUnit": "rem", + "widthUnit": "rem", + "heightUnit": "rem", + "scale": 1.5 }, { "_id": "get64yaM3ZzT9aRs3", @@ -399,8 +465,12 @@ "x": 1.3, "y": 24.9, "width": 22, - "scale": 0, - "showPartTitle": true + "scale": 1.4, + "showPartTitle": true, + "xUnit": "rem", + "yUnit": "rem", + "widthUnit": "rem", + "heightUnit": "rem" }, { "_id": "h9xDp3HvzLaSo4yJi", @@ -418,8 +488,12 @@ "x": 3, "y": 3.8, "width": 16, - "height": 3, - "scale": 0 + "height": 3.2, + "scale": 1, + "xUnit": "rem", + "yUnit": "rem", + "widthUnit": "rem", + "heightUnit": "rem" }, { "_id": "dkKTHZAwF98aKkD47", @@ -434,7 +508,7 @@ "default": false, "nextInCurrentPart": false, "oneNextPerSourceLayer": false, - "url": "http://sofqb06-od:1176/?view=mic-tally", + "url": "http://{{studio.settings.sofieUrl}}:1176/?view=mic-tally", "x": 3, "y": 0, "width": 16, @@ -442,7 +516,11 @@ "customClasses": [ "" ], - "scale": 0 + "scale": 0, + "xUnit": "rem", + "yUnit": "rem", + "widthUnit": "rem", + "heightUnit": "rem" } ], "type": "dashboard_layout", diff --git a/shelf-layouts/Q_Flow_Rundown_View_Layout.json b/layouts/tv2_offtube_showstyle/Q_Flow_Rundown_View_Layout.json similarity index 100% rename from shelf-layouts/Q_Flow_Rundown_View_Layout.json rename to layouts/tv2_offtube_showstyle/Q_Flow_Rundown_View_Layout.json diff --git a/shelf-layouts/Q flow.json b/layouts/tv2_offtube_showstyle/Q_flow.json similarity index 100% rename from shelf-layouts/Q flow.json rename to layouts/tv2_offtube_showstyle/Q_flow.json diff --git a/layouts/tv2_offtube_showstyle/Rundown_Header_Layout.json b/layouts/tv2_offtube_showstyle/Rundown_Header_Layout.json new file mode 120000 index 000000000..8e094bb37 --- /dev/null +++ b/layouts/tv2_offtube_showstyle/Rundown_Header_Layout.json @@ -0,0 +1 @@ +../tv2_afvd_showstyle/Rundown_Header_Layout.json \ No newline at end of file diff --git a/package.json b/package.json index ad0ffd4dd..eadfc7cac 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "tv2-sofie-blueprints-inews", - "version": "1.8.2", + "version": "1.8.3", "repository": "https://github.com/olzzon/tv2-sofie-blueprints-inews", "license": "MIT", "private": true, @@ -19,20 +19,15 @@ "unit": "jest --maxWorkers=2", "test": "yarn lint --fix && yarn unit", "release": "standard-version", + "prepare": "husky install", "prepareChangelog": "standard-version --prerelease", "validate": "yarn validate:dependencies", "validate:dependencies": "yarn audit --groups dependencies && yarn license-validate", "license-validate": "license-checker --onlyAllow \"MIT;Apache-2.0;ISC;BSD;CC0;CC-BY-3.0;CC-BY-4.0;UNLICENSED\" --summary" }, - "husky": { - "hooks": { - "pre-commit": "lint-staged" - } - }, "lint-staged": { "*.{js,ts,css,json,md}": [ - "prettier --write", - "git add" + "prettier --write" ] }, "devDependencies": { @@ -41,10 +36,12 @@ "@types/underscore": "^1.8.9", "axios": "^0.19.0", "git-revision-webpack-plugin": "^3.0.3", + "husky": "^8.0.3", "jest": "^27.5.1", "jest-haste-map": "^24.5.0", "jest-resolve": "^24.5.0", "license-checker": "^25.0.1", + "lint-staged": "^13.2.2", "moment": "^2.29.2", "prettier": "^2.0.0", "standard-version": "9.1.1", @@ -60,9 +57,9 @@ "webpack-cli": "^3.1.2" }, "dependencies": { - "@sofie-automation/blueprints-integration": "npm:@tv2media/blueprints-integration@46.2.2", - "underscore": "^1.12.1", - "ts-mockito": "^2.6.1" + "@sofie-automation/blueprints-integration": "npm:@tv2media/blueprints-integration@46.3.0-staging", + "ts-mockito": "^2.6.1", + "underscore": "^1.12.1" }, "resolutions": { "moment": "^2.29.2" diff --git a/scripts/upload-layouts.sh b/scripts/upload-layouts.sh new file mode 100644 index 000000000..7c5e01012 --- /dev/null +++ b/scripts/upload-layouts.sh @@ -0,0 +1,33 @@ +#!/bin/sh + +echo "Upload Shelf Layouts" + +if [ $# -eq 0 ]; then + echo "Error: Path to layouts directory not provided." + echo "Usage: $(basename "$0") LAYOUTS_PATH" + exit 1 +fi + +directory=$1 + +upload_layouts_from_directory() { + local directory="$1" + local blueprint_id=$(basename "${directory}") + local count=0 + + for file in "$directory"/*.json; do + if [[ -f "$file" ]]; then + count+=1 + url="${SERVER}/shelfLayouts/uploadByShowStyleBlueprintId/${blueprint_id}" + echo "Upload ${file} to blueprint ${blueprint_id}" + target_path="$(readlink -f "$file")" + curl -X POST --data-binary "@$target_path" --header "Content-Type:application/json" -w "\n" "$url" + fi + done +} + +for subdirectory in "$directory"/*; do + if [[ -d "$subdirectory" ]]; then + upload_layouts_from_directory "$subdirectory" + fi +done diff --git a/shelf-layouts/List.json b/shelf-layouts/List.json deleted file mode 100755 index cf6fbfdc0..000000000 --- a/shelf-layouts/List.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "_id": "pmj8mCofYiHfGiGcn", - "name": "List", - "showStyleBaseId": "show0", - "filters": [], - "type": "dashboard_layout", - "regionId": "shelf_layouts" -} \ No newline at end of file diff --git a/shelf-layouts/Sisyfos layout.json b/shelf-layouts/Sisyfos layout.json deleted file mode 100755 index a6b030372..000000000 --- a/shelf-layouts/Sisyfos layout.json +++ /dev/null @@ -1,19 +0,0 @@ -{ - "_id": "v5Pa22MpPDnar9Txm", - "name": "Sisyfos layout", - "showStyleBaseId": "show0", - "filters": [ - { - "_id": "mZBzZX3iyerrQk7nT", - "type": "external_frame", - "name": "Sisyfos", - "currentSegment": false, - "displayStyle": "list", - "rank": 0, - "rundownBaseline": false, - "default": true, - "url": "http://192.168.0.2:1176" - } - ], - "type": "rundown_layout" -} \ No newline at end of file diff --git a/shelf-layouts/Streamdeck.json b/shelf-layouts/Streamdeck.json deleted file mode 100755 index e72309736..000000000 --- a/shelf-layouts/Streamdeck.json +++ /dev/null @@ -1,51 +0,0 @@ -{ - "_id": "tBxXcFSt8QgJxWLyi", - "name": "Streamdeck", - "showStyleBaseId": "show0", - "filters": [ - { - "_id": "efuNPn68Mwz3HPPXC", - "type": "filter", - "name": "Floating overlay", - "currentSegment": true, - "displayStyle": "list", - "rank": 0, - "rundownBaseline": false, - "default": false, - "x": 0, - "y": 9, - "width": -4, - "height": -1, - "hide": false, - "includeClearInRundownBaseline": false, - "sourceLayerIds": [ - "studio0_graphicsTop", - "studio0_graphicsLower" - ], - "showAsTimeline": false - }, - { - "_id": "6tBKYCCDbxBzTENLx", - "type": "filter", - "name": "Pvw kams", - "currentSegment": false, - "displayStyle": "list", - "rank": 0, - "rundownBaseline": true, - "default": false, - "x": 0, - "y": 0, - "sourceLayerIds": [ - "studio0_camera" - ], - "width": -4, - "height": 4, - "includeClearInRundownBaseline": true, - "label": [ - "k" - ] - } - ], - "type": "dashboard_layout", - "regionId": "shelf_layouts" -} \ No newline at end of file diff --git a/shelf-layouts/dve controls.json b/shelf-layouts/dve controls.json deleted file mode 100755 index 6f8a2b6f2..000000000 --- a/shelf-layouts/dve controls.json +++ /dev/null @@ -1,54 +0,0 @@ -{ - "_id": "Evne9f6kKGHhxTdz3", - "name": "dve controls", - "showStyleBaseId": "show0", - "filters": [ - { - "_id": "zMN9rx3kQn99uWC5P", - "type": "filter", - "name": "LAYOUT AND IP1", - "currentSegment": false, - "displayStyle": "list", - "rank": 0, - "rundownBaseline": true, - "default": false, - "includeClearInRundownBaseline": true, - "sourceLayerIds": [ - "studio0_dve_adlib", - "studio0_dve_box1" - ] - }, - { - "_id": "cfxi2T5fXiddz4s8p", - "type": "filter", - "name": "IP2", - "currentSegment": false, - "displayStyle": "list", - "rank": 0, - "rundownBaseline": "only", - "default": false, - "includeClearInRundownBaseline": true, - "sourceLayerIds": [ - "studio0_dve_box2" - ], - "y": 11 - }, - { - "_id": "KWQqhz6CeB3EBwt5g", - "type": "filter", - "name": "IP3", - "currentSegment": false, - "displayStyle": "list", - "rank": 0, - "rundownBaseline": "only", - "default": false, - "includeClearInRundownBaseline": true, - "sourceLayerIds": [ - "studio0_dve_box3" - ], - "y": 23 - } - ], - "type": "dashboard_layout", - "regionId": "shelf_layouts" -} \ No newline at end of file diff --git a/src/__mocks__/context.ts b/src/__mocks__/context.ts index a56d73ca1..5619b36a3 100644 --- a/src/__mocks__/context.ts +++ b/src/__mocks__/context.ts @@ -461,9 +461,20 @@ export class ActionExecutionContextMock extends ShowStyleUserContextMock impleme } /** Get the resolved PieceInstances for a modifiable PartInstance */ public async getResolvedPieceInstances( - _part: 'current' | 'next' + part: 'current' | 'next' ): Promise>> { - return [] + const pieces = part === 'current' ? this.currentPieceInstances : this.nextPieceInstances ?? [] + const now = Date.now() + // this is nowhere near to what core does; we should reconsider the way we're mocking this context + return pieces.map((pieceInstance) => { + const pieceStart = pieceInstance.piece.enable.start + const resolvedStart = pieceStart === 'now' ? now : pieceStart + + return { + ...pieceInstance, + resolvedStart + } + }) } /** Get the last active piece on given layer */ public async findLastPieceOnLayer( diff --git a/src/tv2-common/__tests__/EvaluateCueTelemetrics.spec.ts b/src/tv2-common/__tests__/EvaluateCueTelemetrics.spec.ts index 276d1565d..aa231bb1f 100644 --- a/src/tv2-common/__tests__/EvaluateCueTelemetrics.spec.ts +++ b/src/tv2-common/__tests__/EvaluateCueTelemetrics.spec.ts @@ -171,6 +171,20 @@ describe('EvaluateCueRobotCamera', () => { } } + it('does not create a piece when adlib is true', () => { + const cueDefinition: CueDefinitionRobotCamera = { + type: CueType.RobotCamera, + presetIdentifier: 1, + iNewsCommand: '', + adlib: true + } + const pieces: IBlueprintPiece[] = [] + + EvaluateCueRobotCamera(cueDefinition, pieces, '') + + expect(pieces.length).toEqual(0) + }) + describe('already has a piece with same externalId', () => { it('has another start time, creates another blueprint piece', () => { const cueDefinition: CueDefinitionRobotCamera = createRobotCameraCueDefinition(1, 20) diff --git a/src/tv2-common/__tests__/calculate-time.spec.ts b/src/tv2-common/__tests__/calculate-time.spec.ts new file mode 100644 index 000000000..321b57ae9 --- /dev/null +++ b/src/tv2-common/__tests__/calculate-time.spec.ts @@ -0,0 +1,101 @@ +import { calculateTime, CueTime } from 'tv2-common' + +describe('calculateTime', () => { + it('receives an infinite mode - returns undefined', () => { + const time: CueTime = { + infiniteMode: 'B' + } + const result = calculateTime(time) + expect(result).toBeUndefined() + }) + + it('receives empty time - returns 0', () => { + const time: CueTime = {} + const result = calculateTime(time) + expect(result).toBe(0) + }) + + it('receives time with 1 second - returns 1000', () => { + const time: CueTime = { + seconds: 1 + } + const result = calculateTime(time) + expect(result).toBe(1000) + }) + + it('receives time with 10 seconds - returns 10000', () => { + const time: CueTime = { + seconds: 10 + } + const result = calculateTime(time) + expect(result).toBe(10000) + }) + + it('receives time with 17 second - returns 17000', () => { + const time: CueTime = { + seconds: 17 + } + const result = calculateTime(time) + expect(result).toBe(17000) + }) + + it('receives time with 1 frame - returns 40', () => { + const time: CueTime = { + frames: 1 + } + const result = calculateTime(time) + expect(result).toBe(40) + }) + + it('receives time with 10 frames - returns 400', () => { + const time: CueTime = { + frames: 10 + } + const result = calculateTime(time) + expect(result).toBe(400) + }) + + it('receives time with 17 frames - returns 680', () => { + const time: CueTime = { + frames: 17 + } + const result = calculateTime(time) + expect(result).toBe(680) + }) + + it('receives both a infinite mode and seconds - returns undefined', () => { + const time: CueTime = { + infiniteMode: 'B', + seconds: 29 + } + const result = calculateTime(time) + expect(result).toBeUndefined() + }) + + it('receives both a infinite mode and frames - returns undefined', () => { + const time: CueTime = { + infiniteMode: 'B', + frames: 29 + } + const result = calculateTime(time) + expect(result).toBeUndefined() + }) + + it('receives 3 seconds and 4 frames - returns 3160', () => { + const time: CueTime = { + seconds: 3, + frames: 4 + } + const result = calculateTime(time) + expect(result).toBe(3160) + }) + + it('receives 4 seconds and 3 frames - returns 4120', () => { + const time: CueTime = { + seconds: 4, + frames: 3 + } + const result = calculateTime(time) + expect(result).toBe(4120) + }) +}) diff --git a/src/tv2-common/__tests__/frame-time.spec.ts b/src/tv2-common/__tests__/frame-time.spec.ts index 17d9a1606..dc065b3c9 100644 --- a/src/tv2-common/__tests__/frame-time.spec.ts +++ b/src/tv2-common/__tests__/frame-time.spec.ts @@ -197,7 +197,7 @@ describe('CreateTiming', () => { enable: { start: 0 }, - lifespan: PieceLifespan.OutOnShowStyleEnd + lifespan: PieceLifespan.OutOnRundownChange }) ) }) diff --git a/src/tv2-common/__tests__/gfx-schema-generator.spec.ts b/src/tv2-common/__tests__/gfx-schema-generator.spec.ts new file mode 100644 index 000000000..ac2bd1ba8 --- /dev/null +++ b/src/tv2-common/__tests__/gfx-schema-generator.spec.ts @@ -0,0 +1,245 @@ +import { IShowStyleUserContext, TSR } from 'blueprints-integration' +import { anyNumber, anything, instance, mock, verify, when } from 'ts-mockito' +import { + CasparCgGfxDesignValues, + TableConfigGfxSchema, + TableConfigGfxSetup, + TableConfigItemGfxDefaults +} from 'tv2-common' +import { SharedGraphicLLayer } from 'tv2-constants' +import { GfxSchemaGenerator } from '../cues/gfx-schema-generator' +import { DveLoopGenerator } from '../helpers/graphics/caspar/dve-loop-generator' +import { MockContextBuilder } from './mock-context-builder' + +// tslint:disable:no-object-literal-type-assertion +describe('GfxSchemaGenerator', () => { + describe('createTimelineObjectsFromGfxDefaults', () => { + it('has no schema configured - notifies about error', () => { + const context = new MockContextBuilder() + .setGfxDefaults({ + DefaultSchema: { label: 'someSchema' } + } as TableConfigItemGfxDefaults) + .build() + + const core = mock() + when(context.core).thenReturn(instance(core)) + + const testee = createTestee() + + const result = testee.createBaselineTimelineObjectsFromGfxDefaults(instance(context)) + expect(result).toBeTruthy() + + verify(core.notifyUserError(anything())).once() + }) + + function createTestee(dveLoopGenerator?: DveLoopGenerator) { + if (!dveLoopGenerator) { + dveLoopGenerator = mock(DveLoopGenerator) + when(dveLoopGenerator.createCasparCgDveLoopsFromCue(anything(), anything(), anyNumber())).thenReturn([]) + } + return new GfxSchemaGenerator(instance(dveLoopGenerator)) + } + + it('has schema configured, but no match from default schema - notifies about error', () => { + const context = new MockContextBuilder() + .setGfxDefaults({ + DefaultSchema: { value: 'someSchema' } + } as TableConfigItemGfxDefaults) + .setGfxSchemaTemplates([ + { + GfxSchemaTemplatesName: 'someOtherSchema' + } + ] as TableConfigGfxSchema[]) + .build() + + const core = mock() + when(context.core).thenReturn(instance(core)) + + const testee = createTestee() + const result = testee.createBaselineTimelineObjectsFromGfxDefaults(instance(context)) + expect(result).toBeTruthy() + + verify(core.notifyUserError(anything())).once() + }) + + it('has correctly configured schema - does not notify about error', () => { + const schemaId = 'someSchemaId' + const context = new MockContextBuilder() + .setGfxDefaults({ + DefaultSchema: { value: schemaId } + } as TableConfigItemGfxDefaults) + .setGfxSchemaTemplates([ + { + _id: schemaId, + GfxSchemaTemplatesName: 'SomeSchema', + CasparCgDesignValues: '[{}]' + } + ] as TableConfigGfxSchema[]) + .build() + + const core = mock() + when(context.core).thenReturn(instance(core)) + + const testee = createTestee() + const result = testee.createBaselineTimelineObjectsFromGfxDefaults(instance(context)) + expect(result).toBeTruthy() + + verify(core.notifyUserError(anything())).never() + }) + + describe('graphicsType is "VIZ"', () => { + it('creates Viz timelineObject', () => { + const schemaName = 'someSchema' + const vizTemplateName = 'someVizTemplate' + const ovlShowName = 'someOvlName' + const context = new MockContextBuilder() + .setGraphicsType('VIZ') + .setGfxDefaults({ + DefaultSchema: { label: schemaName } + } as TableConfigItemGfxDefaults) + .setGfxSchemaTemplates([ + { + GfxSchemaTemplatesName: schemaName, + CasparCgDesignValues: '[{}]', + VizTemplate: vizTemplateName + } + ] as TableConfigGfxSchema[]) + .setSelectedGfxSetup({ + OvlShowName: ovlShowName + } as TableConfigGfxSetup) + .build() + const testee = createTestee() + + const result = testee.createBaselineTimelineObjectsFromGfxDefaults(instance(context)) + const schemaTimelineObject = result.find( + (timelineObject) => timelineObject.layer === SharedGraphicLLayer.GraphicLLayerSchema + ) + expect(schemaTimelineObject).toBeTruthy() + const vizSchema = schemaTimelineObject as TSR.TimelineObjVIZMSEElementInternal + expect(vizSchema.classes).toContain(vizTemplateName) + expect(vizSchema.content.deviceType).toBe(TSR.DeviceType.VIZMSE) + expect(vizSchema.content.type).toBe(TSR.TimelineContentTypeVizMSE.ELEMENT_INTERNAL) + expect(vizSchema.content.templateName).toBe(vizTemplateName) + expect(vizSchema.content.showName).toBe(ovlShowName) + }) + + it('creates casparCg dve loop timelineObjects', () => { + const schemaName = 'someSchema' + const context = new MockContextBuilder() + .setGraphicsType('VIZ') + .setGfxDefaults({ + DefaultSchema: { label: schemaName } + } as TableConfigItemGfxDefaults) + .setGfxSchemaTemplates([ + { + GfxSchemaTemplatesName: schemaName, + CasparCgDesignValues: '[{}]' + } + ] as TableConfigGfxSchema[]) + .build() + + const casparCgDveLoopTimelineObjects: TSR.TimelineObjCCGMedia[] = [ + { + id: 'id1' + }, + { + id: 'id2' + } + ] as TSR.TimelineObjCCGMedia[] + + const dveLoopGenerator = mock(DveLoopGenerator) + when(dveLoopGenerator.createCasparCgDveLoopsFromCue(anything(), anything(), anyNumber())).thenReturn( + casparCgDveLoopTimelineObjects + ) + + const testee = createTestee(dveLoopGenerator) + const result = testee.createBaselineTimelineObjectsFromGfxDefaults(instance(context)) + + verify(dveLoopGenerator.createCasparCgDveLoopsFromCue(anything(), anything(), anyNumber())).called() + expect(result.includes(casparCgDveLoopTimelineObjects[0])).toBeTruthy() + expect(result.includes(casparCgDveLoopTimelineObjects[1])).toBeTruthy() + }) + }) + + describe('graphicsType is "HTML"', () => { + it('creates CasparCG timelineObject', () => { + const schemaName = 'someSchema' + const vizTemplateName = 'someVizTemplate' + const casparCgDesignValues: CasparCgGfxDesignValues[] = [ + { + name: 'designName', + backgroundLoop: 'someBackgroundLoop', + properties: { + attribute1: 'value1', + attribute2: 'value2', + attribute3: 'value3' + } + } + ] + const context = new MockContextBuilder() + .setGraphicsType('HTML') + .setGfxDefaults({ + DefaultSchema: { label: schemaName } + } as TableConfigItemGfxDefaults) + .setGfxSchemaTemplates([ + { + GfxSchemaTemplatesName: schemaName, + CasparCgDesignValues: JSON.stringify(casparCgDesignValues), + VizTemplate: vizTemplateName + } + ] as TableConfigGfxSchema[]) + .build() + const testee = createTestee() + + const result = testee.createBaselineTimelineObjectsFromGfxDefaults(instance(context)) + const schemaTimelineObject = result.find( + (timelineObject) => timelineObject.layer === SharedGraphicLLayer.GraphicLLayerSchema + ) + expect(schemaTimelineObject).toBeTruthy() + const casparCgSchema = schemaTimelineObject as TSR.TimelineObjCCGTemplate + expect(casparCgSchema.classes).toContain(vizTemplateName) + expect(casparCgSchema.content.deviceType).toBe(TSR.DeviceType.CASPARCG) + expect(casparCgSchema.content.type).toBe(TSR.TimelineContentTypeCasparCg.TEMPLATE) + expect(casparCgSchema.content.templateType).toBe('html') + expect(casparCgSchema.content.data.designs).toStrictEqual(casparCgDesignValues) + }) + + it('creates casparCg dve loop timelineObjects', () => { + const schemaName = 'someSchema' + const context = new MockContextBuilder() + .setGraphicsType('HTML') + .setGfxDefaults({ + DefaultSchema: { label: schemaName } + } as TableConfigItemGfxDefaults) + .setGfxSchemaTemplates([ + { + GfxSchemaTemplatesName: schemaName, + CasparCgDesignValues: '[{}]' + } + ] as TableConfigGfxSchema[]) + .build() + + const casparCgDveLoopTimelineObjects: TSR.TimelineObjCCGMedia[] = [ + { + id: 'id1' + }, + { + id: 'id2' + } + ] as TSR.TimelineObjCCGMedia[] + + const dveLoopGenerator = mock(DveLoopGenerator) + when(dveLoopGenerator.createCasparCgDveLoopsFromCue(anything(), anything(), anyNumber())).thenReturn( + casparCgDveLoopTimelineObjects + ) + + const testee = createTestee(dveLoopGenerator) + const result = testee.createBaselineTimelineObjectsFromGfxDefaults(instance(context)) + + verify(dveLoopGenerator.createCasparCgDveLoopsFromCue(anything(), anything(), anyNumber())).called() + expect(result.includes(casparCgDveLoopTimelineObjects[0])).toBeTruthy() + expect(result.includes(casparCgDveLoopTimelineObjects[1])).toBeTruthy() + }) + }) + }) +}) diff --git a/src/tv2-common/__tests__/mock-context-builder.ts b/src/tv2-common/__tests__/mock-context-builder.ts new file mode 100644 index 000000000..997ce3bc1 --- /dev/null +++ b/src/tv2-common/__tests__/mock-context-builder.ts @@ -0,0 +1,62 @@ +import { mock, when } from 'ts-mockito' +import { + ShowStyleContext, + TableConfigGfxSchema, + TableConfigGfxSetup, + TableConfigItemGfxDefaults, + TV2ShowStyleConfig +} from 'tv2-common' + +// tslint:disable:no-object-literal-type-assertion +/** + * A builder class to create mocks for ShowStyleContext. + * To make this useful to as many as possible all defaults should be empty or as close to empty as possible. + * Feel free to add more setters methods as needed. + */ +export class MockContextBuilder { + private gfxDefaults: TableConfigItemGfxDefaults[] = [] + private gfxSchemaTemplates: TableConfigGfxSchema[] = [] + private selectedGfxSetup: TableConfigGfxSetup = { + _id: '', + Name: '', + HtmlPackageFolder: '' + } + private graphicsType: 'HTML' | 'VIZ' = 'HTML' + + public build(): ShowStyleContext { + const context = mock() + + when(context.config).thenReturn({ + showStyle: { + GfxDefaults: this.gfxDefaults, + GfxSchemaTemplates: this.gfxSchemaTemplates + }, + studio: { + GraphicsType: this.graphicsType + }, + selectedGfxSetup: this.selectedGfxSetup + } as TV2ShowStyleConfig) + + return context + } + + public setGfxDefaults(gfxDefault: TableConfigItemGfxDefaults): MockContextBuilder { + this.gfxDefaults = [gfxDefault] + return this + } + + public setGfxSchemaTemplates(gfxSchemaTemplates: TableConfigGfxSchema[]): MockContextBuilder { + this.gfxSchemaTemplates = gfxSchemaTemplates + return this + } + + public setSelectedGfxSetup(selectedGfxSetup: TableConfigGfxSetup): MockContextBuilder { + this.selectedGfxSetup = selectedGfxSetup + return this + } + + public setGraphicsType(graphicsType: 'HTML' | 'VIZ'): MockContextBuilder { + this.graphicsType = graphicsType + return this + } +} diff --git a/src/tv2-common/__tests__/onTimelineGenerate.spec.ts b/src/tv2-common/__tests__/onTimelineGenerate.spec.ts index 6e20cbedd..6fbcf014a 100644 --- a/src/tv2-common/__tests__/onTimelineGenerate.spec.ts +++ b/src/tv2-common/__tests__/onTimelineGenerate.spec.ts @@ -3,23 +3,28 @@ import { SisyfosLLAyer } from '../../tv2_afvd_studio/layers' import { createSisyfosPersistedLevelsTimelineObject, PieceMetaData, - SisyfosPersistMetaData + SisyfosPersistenceMetaData } from '../onTimelineGenerate' const LAYER_THAT_WANTS_TO_BE_PERSISTED = 'layerThatWantsToBePersisted' -const LAYERS_THAT_WANTS_TO_BE_PERSISTED_ARRAY: SisyfosPersistMetaData['sisyfosLayers'] = [ +const LAYERS_THAT_WANTS_TO_BE_PERSISTED_ARRAY: SisyfosPersistenceMetaData['sisyfosLayers'] = [ LAYER_THAT_WANTS_TO_BE_PERSISTED ] +const PART_INSTANCE_ID = 'part1' // tslint:disable:no-object-literal-type-assertion describe('onTimelineGenerate', () => { describe('createSisyfosPersistedLevelsTimelineObject', () => { - it('has one layer to persist, piece accept persist - timelineObject with layer is added', () => { + it('has one layer to persist from the previous part, piece accepts persistence - timelineObject with layer is added', () => { const resolvedPieces: Array> = [ createPieceInstance('currentPiece', 10, undefined, true, true) ] - const result = createSisyfosPersistedLevelsTimelineObject(resolvedPieces, LAYERS_THAT_WANTS_TO_BE_PERSISTED_ARRAY) + const result = createSisyfosPersistedLevelsTimelineObject( + PART_INSTANCE_ID, + resolvedPieces, + LAYERS_THAT_WANTS_TO_BE_PERSISTED_ARRAY + ) const indexOfLayerThatWantToBePersisted = result.content.channels.findIndex( (channel) => channel.mappedLayer === LAYER_THAT_WANTS_TO_BE_PERSISTED @@ -27,12 +32,16 @@ describe('onTimelineGenerate', () => { expect(indexOfLayerThatWantToBePersisted).toBeGreaterThanOrEqual(0) }) - it('has layer to persist, timelineObject with correct Sisyfos information is added', () => { + it('has one layer to persist from the previous part, timelineObject with correct Sisyfos information is added', () => { const resolvedPieces: Array> = [ createPieceInstance('currentPiece', 10, undefined, true, true) ] - const result = createSisyfosPersistedLevelsTimelineObject(resolvedPieces, LAYERS_THAT_WANTS_TO_BE_PERSISTED_ARRAY) + const result = createSisyfosPersistedLevelsTimelineObject( + PART_INSTANCE_ID, + resolvedPieces, + LAYERS_THAT_WANTS_TO_BE_PERSISTED_ARRAY + ) expect(result.layer).toEqual(SisyfosLLAyer.SisyfosPersistedLevels) expect(result.content.deviceType).toEqual(TSR.DeviceType.SISYFOS) @@ -45,49 +54,53 @@ describe('onTimelineGenerate', () => { createPieceInstance('currentPiece', 10, undefined, true, true) ] - const result = createSisyfosPersistedLevelsTimelineObject(resolvedPieces, LAYERS_THAT_WANTS_TO_BE_PERSISTED_ARRAY) + const result = createSisyfosPersistedLevelsTimelineObject( + PART_INSTANCE_ID, + resolvedPieces, + LAYERS_THAT_WANTS_TO_BE_PERSISTED_ARRAY + ) expect(result.content.channels[0].isPgm).toEqual(1) }) - it('should persist only current piece layer when piece wants to persist but dont accept', () => { + it('should not persist anything when no piece in current part accepts persistence', () => { const resolvedPieces: Array> = [ - createPieceInstance('previousPiece', 0, 10, true, true), - createPieceInstance('currentPiece', 10, undefined, true, false) + createPieceInstance('previousPiece', 0, undefined, true, true, 'OTHER_PART') ] - const result = createSisyfosPersistedLevelsTimelineObject(resolvedPieces, LAYERS_THAT_WANTS_TO_BE_PERSISTED_ARRAY) + const result = createSisyfosPersistedLevelsTimelineObject( + PART_INSTANCE_ID, + resolvedPieces, + LAYERS_THAT_WANTS_TO_BE_PERSISTED_ARRAY + ) - expect(result.content.channels).toHaveLength(1) + expect(result.content.channels).toHaveLength(0) }) - it('should not persist anything when current piece dont accept and dont want to persist', () => { + it('should not persist anything when the current piece does not accept and does not want to persist', () => { const resolvedPieces: Array> = [ - createPieceInstance('previousPiece', 0, 10, true, true), + createPieceInstance('previousPiece', 0, undefined, true, true), createPieceInstance('currentPiece', 10, undefined, false, false) ] - const result = createSisyfosPersistedLevelsTimelineObject(resolvedPieces, LAYERS_THAT_WANTS_TO_BE_PERSISTED_ARRAY) + const result = createSisyfosPersistedLevelsTimelineObject( + PART_INSTANCE_ID, + resolvedPieces, + LAYERS_THAT_WANTS_TO_BE_PERSISTED_ARRAY + ) expect(result.content.channels).toHaveLength(0) }) - it('should persist when previous piece does not accept persist, but current does accept', () => { - const resolvedPieces: Array> = [ - createPieceInstance('previousPiece', 0, 10, true, false), - createPieceInstance('currentPiece', 10, undefined, false, true) - ] - - const result = createSisyfosPersistedLevelsTimelineObject(resolvedPieces, LAYERS_THAT_WANTS_TO_BE_PERSISTED_ARRAY) - - expect(result.content.channels).toHaveLength(1) - }) - it('should persist when current piece accepts persist and duration is not undefined', () => { const resolvedPieces: Array> = [ createPieceInstance('currentPiece', 0, 5, false, true) ] - const result = createSisyfosPersistedLevelsTimelineObject(resolvedPieces, LAYERS_THAT_WANTS_TO_BE_PERSISTED_ARRAY) + const result = createSisyfosPersistedLevelsTimelineObject( + PART_INSTANCE_ID, + resolvedPieces, + LAYERS_THAT_WANTS_TO_BE_PERSISTED_ARRAY + ) expect(result.content.channels).toHaveLength(1) }) @@ -96,7 +109,7 @@ describe('onTimelineGenerate', () => { const firstLayerThatWantToBePersisted: string = 'firstLayer' const secondLayerThatWantToBePersisted: string = 'secondLayer' const thirdLayerThatWantToBePersisted: string = 'thirdLayer' - const layersThatWantToBePersisted: SisyfosPersistMetaData['sisyfosLayers'] = [ + const layersThatWantToBePersisted: SisyfosPersistenceMetaData['sisyfosLayers'] = [ firstLayerThatWantToBePersisted, secondLayerThatWantToBePersisted, thirdLayerThatWantToBePersisted @@ -105,7 +118,11 @@ describe('onTimelineGenerate', () => { createPieceInstance('currentPiece', 0, 5, true, true) ] - const result = createSisyfosPersistedLevelsTimelineObject(resolvedPieces, layersThatWantToBePersisted) + const result = createSisyfosPersistedLevelsTimelineObject( + PART_INSTANCE_ID, + resolvedPieces, + layersThatWantToBePersisted + ) expect( result.content.channels.some((channel) => channel.mappedLayer === firstLayerThatWantToBePersisted) @@ -124,39 +141,46 @@ describe('onTimelineGenerate', () => { createExecuteActionPieceInstance('currentPieceIsExecuteAction', 10, undefined, false, false) ] - const result = createSisyfosPersistedLevelsTimelineObject(resolvedPieces, LAYERS_THAT_WANTS_TO_BE_PERSISTED_ARRAY) + const result = createSisyfosPersistedLevelsTimelineObject( + PART_INSTANCE_ID, + resolvedPieces, + LAYERS_THAT_WANTS_TO_BE_PERSISTED_ARRAY + ) expect(result.content.channels).toHaveLength(0) }) - it('cuts to executeAction that accept persist from piece that accept, add persist timelineObject containing all layers that want to be persisted plus previous piece layers', () => { + it('cuts to executeAction that accepts persistence, dont persist layers from previous part', () => { const resolvedPieces: Array> = [ createPieceInstance('previousPieceNotExecuteAction', 0, 10, true, true), createExecuteActionPieceInstance('currentPieceIsExecuteAction', 10, undefined, false, true) ] - const result = createSisyfosPersistedLevelsTimelineObject(resolvedPieces, LAYERS_THAT_WANTS_TO_BE_PERSISTED_ARRAY) + const result = createSisyfosPersistedLevelsTimelineObject( + PART_INSTANCE_ID, + resolvedPieces, + LAYERS_THAT_WANTS_TO_BE_PERSISTED_ARRAY + ) - expect( - result.content.channels.some((channel) => channel.mappedLayer === LAYER_THAT_WANTS_TO_BE_PERSISTED) - ).toBeTruthy() - expect( - result.content.channels.some((channel) => channel.mappedLayer === resolvedPieces[0].piece.name) - ).toBeTruthy() + expect(result.content.channels).toHaveLength(0) }) - it('cuts to executionAction that accept from piece that dont accept, add persist timelineObject that only contain previous piece layers', () => { + it('cuts to executeAction that accepts persistence, from piece that accepts, add persist timelineObject containing all layers that want to be persisted plus previous piece layers', () => { const resolvedPieces: Array> = [ - createPieceInstance('previousPieceNotExecuteAction', 0, 10, true, false), - createExecuteActionPieceInstance('currentPieceIsExecuteAction', 10, undefined, false, true) + createPieceInstance('previousPieceNotExecuteAction', 0, 10, true, true), + createExecuteActionPieceInstance('currentPieceIsExecuteAction', 10, undefined, false, true, PART_INSTANCE_ID, [ + 'previousPieceLayer' + ]) ] - const result = createSisyfosPersistedLevelsTimelineObject(resolvedPieces, LAYERS_THAT_WANTS_TO_BE_PERSISTED_ARRAY) + const result = createSisyfosPersistedLevelsTimelineObject( + PART_INSTANCE_ID, + resolvedPieces, + LAYERS_THAT_WANTS_TO_BE_PERSISTED_ARRAY + ) expect(result.content.channels).toHaveLength(1) - expect( - result.content.channels.some((channel) => channel.mappedLayer === resolvedPieces[0].piece.name) - ).toBeTruthy() + expect(result.content.channels[0].mappedLayer).toBe('previousPieceLayer') }) it('cuts to executeAction that accept persist from piece that dont want to persist and dont accept persist, dont persist any layers', () => { @@ -165,132 +189,21 @@ describe('onTimelineGenerate', () => { createExecuteActionPieceInstance('currentPieceIsExecuteAction', 10, undefined, false, true) ] - const result = createSisyfosPersistedLevelsTimelineObject(resolvedPieces, LAYERS_THAT_WANTS_TO_BE_PERSISTED_ARRAY) - - expect(result.content.channels).toHaveLength(0) - }) - - it('cuts to executeAction that accept persist from piece that dont want to persist and that accept persist, persist previous layers', () => { - const resolvedPieces: Array> = [ - createPieceInstance('previousPieceNotExecuteAction', 0, 10, false, true), - createExecuteActionPieceInstance('currentPieceIsExecuteAction', 10, undefined, false, true) - ] - - const result = createSisyfosPersistedLevelsTimelineObject(resolvedPieces, LAYERS_THAT_WANTS_TO_BE_PERSISTED_ARRAY) - - expect(result.content.channels).toHaveLength(1) - expect( - result.content.channels.some((channel) => channel.mappedLayer === LAYER_THAT_WANTS_TO_BE_PERSISTED) - ).toBeTruthy() - }) - - it('cuts from executeAction that dont accept to piece that accepts, dont persist', () => { - const resolvedPieces: Array> = [ - createExecuteActionPieceInstance('previousPieceIsExecuteAction', 0, 10, false, false), - createPieceInstance('currentPieceNotExecuteAction', 10, undefined, false, true) - ] - - const result = createSisyfosPersistedLevelsTimelineObject(resolvedPieces, LAYERS_THAT_WANTS_TO_BE_PERSISTED_ARRAY) - - expect(result.content.channels).toHaveLength(0) - }) - - it('cuts from executeAction that dont accept to piece that dont accepts, dont persist layers', () => { - const resolvedPieces: Array> = [ - createExecuteActionPieceInstance('previousPieceIsExecuteAction', 0, 10, false, false), - createPieceInstance('currentPieceNotExecuteAction', 10, undefined, false, false) - ] - - const result = createSisyfosPersistedLevelsTimelineObject(resolvedPieces, LAYERS_THAT_WANTS_TO_BE_PERSISTED_ARRAY) - - expect(result.content.channels).toHaveLength(0) - }) - - it('cuts from executeAction that accept to piece that dont accepts, dont persist layers', () => { - const resolvedPieces: Array> = [ - createExecuteActionPieceInstance('previousPieceIsExecuteAction', 0, 10, false, true), - createPieceInstance('currentPieceNotExecuteAction', 10, undefined, false, false) - ] - - const result = createSisyfosPersistedLevelsTimelineObject(resolvedPieces, LAYERS_THAT_WANTS_TO_BE_PERSISTED_ARRAY) - - expect(result.content.channels).toHaveLength(0) - }) - - it('cuts from executeAction that accept to piece that accepts, add persist timelineObject with previous layer before executeAction + new layer', () => { - const resolvedPieces: Array> = [ - createPieceInstance('firstPiece', 0, 5, true, true), - createExecuteActionPieceInstance('previousPieceIsExecuteAction', 5, 5, false, true, { - acceptPersistAudio: true, - sisyfosLayers: [] - }), - createPieceInstance('currentPieceNotExecuteAction', 10, undefined, false, true) - ] - - const result = createSisyfosPersistedLevelsTimelineObject(resolvedPieces, LAYERS_THAT_WANTS_TO_BE_PERSISTED_ARRAY) - - expect(result.content.channels).toHaveLength(2) - }) - - it('cuts from piece that wants to persist to executeAction that do not accept to piece that accepts, do not persist', () => { - const resolvedPieces: Array> = [ - createPieceInstance('firstPiece', 0, 5, true, true), - createExecuteActionPieceInstance('previousPieceIsExecuteAction', 5, 5, false, true, { - acceptPersistAudio: false, - sisyfosLayers: [] - }), - createPieceInstance('currentPieceNotExecuteAction', 10, undefined, false, true) - ] - - const result = createSisyfosPersistedLevelsTimelineObject(resolvedPieces, LAYERS_THAT_WANTS_TO_BE_PERSISTED_ARRAY) - - expect(result.content.channels).toHaveLength(0) - }) - - it('cuts from piece that wants to persist to executeAction that accepts to another executeAction that accepts, persist layer from first piece', () => { - const resolvedPieces: Array> = [ - createPieceInstance('firstPiece', 0, 5, true, true), - createExecuteActionPieceInstance('executeAction', 5, undefined, false, true, { - acceptPersistAudio: true, - sisyfosLayers: [], - previousPersistMetaDataForCurrentPiece: { - acceptPersistAudio: true, - sisyfosLayers: [] - } - }) - ] - - const result = createSisyfosPersistedLevelsTimelineObject(resolvedPieces, []) - - expect(result.content.channels).toHaveLength(1) - expect(result.content.channels.some((channel) => channel.mappedLayer === 'firstPiece')).toBeTruthy() - }) - - it('cuts from piece that wants to persist to executeAction that do not accept to another executeAction that accepts, dont persist any layers', () => { - const resolvedPieces: Array> = [ - createPieceInstance('firstPiece', 0, 5, true, true), - createExecuteActionPieceInstance('executeAction', 5, undefined, false, true, { - acceptPersistAudio: true, - sisyfosLayers: [], - previousPersistMetaDataForCurrentPiece: { - acceptPersistAudio: false, - sisyfosLayers: [] - } - }) - ] - - const result = createSisyfosPersistedLevelsTimelineObject(resolvedPieces, []) + const result = createSisyfosPersistedLevelsTimelineObject( + PART_INSTANCE_ID, + resolvedPieces, + LAYERS_THAT_WANTS_TO_BE_PERSISTED_ARRAY + ) expect(result.content.channels).toHaveLength(0) }) it('should not contain any duplicate layers to persist', () => { const resolvedPieces: Array> = [ - createPieceInstance('piece', 0, 5, true, true), - createPieceInstance('piece', 5, undefined, true, true) + createExecuteActionPieceInstance('piece', 5, undefined, true, true, PART_INSTANCE_ID, ['piece']) ] - const result = createSisyfosPersistedLevelsTimelineObject(resolvedPieces, []) + const result = createSisyfosPersistedLevelsTimelineObject(PART_INSTANCE_ID, resolvedPieces, ['piece']) expect(result.content.channels).toHaveLength(1) }) @@ -302,18 +215,20 @@ function createPieceInstance( start: number, duration: number | undefined, wantToPersistAudio: boolean, - acceptPersistAudio: boolean + acceptPersistAudio: boolean, + partInstanceId: string = PART_INSTANCE_ID ): IBlueprintResolvedPieceInstance { return { resolvedStart: start, resolvedDuration: duration, + partInstanceId, piece: { name, metaData: { sisyfosPersistMetaData: { sisyfosLayers: [name], wantsToPersistAudio: wantToPersistAudio, - acceptPersistAudio + acceptsPersistedAudio: acceptPersistAudio } } } as IBlueprintPieceDB @@ -326,19 +241,22 @@ function createExecuteActionPieceInstance( duration: number | undefined, wantToPersistAudio: boolean, acceptPersistAudio: boolean, - previousMetaData?: SisyfosPersistMetaData + partInstanceId: string = PART_INSTANCE_ID, + previousSisyfosLayers?: string[] ): IBlueprintResolvedPieceInstance { return { resolvedStart: start, resolvedDuration: duration, + partInstanceId, piece: { name, metaData: { sisyfosPersistMetaData: { sisyfosLayers: [name], wantsToPersistAudio: wantToPersistAudio, - acceptPersistAudio, - previousPersistMetaDataForCurrentPiece: previousMetaData + acceptsPersistedAudio: acceptPersistAudio, + isModifiedOrInsertedByAction: true, + previousSisyfosLayers } } } as IBlueprintPieceDB diff --git a/src/tv2-common/actions/__tests__/executeAction.spec.ts b/src/tv2-common/actions/__tests__/executeAction.spec.ts new file mode 100644 index 000000000..6e78efc0c --- /dev/null +++ b/src/tv2-common/actions/__tests__/executeAction.spec.ts @@ -0,0 +1,191 @@ +import { PartEndStateExt } from '../../onTimelineGenerate' +import { mergePersistenceMetaData } from '../executeAction' + +const CURRENT_SEGMENT_ID = 'segment1' +const NEXT_LAYERS = ['nextLayer1', 'nextLayer2'] +const CURRENT_LAYERS = ['currentLayer1', 'currentLayer2'] +const PREVIOUS_LAYERS = ['previousLayer1', 'previousLayer2'] +const PREVIOUS_PERSISTED_LAYERS = ['previousPersistedLayer1', 'previousPersistedLayer2'] + +describe('executeAction', () => { + describe('mergePersistenceMetaData', () => { + it('does not populate previousSisyfosLayers when next accepts and current does not want to persist', () => { + const result = mergePersistenceMetaData( + CURRENT_SEGMENT_ID, + { + sisyfosLayers: NEXT_LAYERS, + acceptsPersistedAudio: true + }, + { + sisyfosLayers: CURRENT_LAYERS, + wantsToPersistAudio: false + }, + undefined + ) + + expect(result).toEqual({ + sisyfosLayers: NEXT_LAYERS, + acceptsPersistedAudio: true + }) + }) + + it('populates previousSisyfosLayers with current layer when next accepts and current wants to persist', () => { + const result = mergePersistenceMetaData( + CURRENT_SEGMENT_ID, + { + sisyfosLayers: NEXT_LAYERS, + acceptsPersistedAudio: true + }, + { + sisyfosLayers: CURRENT_LAYERS, + wantsToPersistAudio: true + }, + undefined + ) + + expect(result).toEqual({ + sisyfosLayers: NEXT_LAYERS, + previousSisyfosLayers: CURRENT_LAYERS, + acceptsPersistedAudio: true + }) + }) + + it('does not populate previousSisyfosLayers when next accepts, current does not accept persistence and previous part wanted to persist', () => { + const result = mergePersistenceMetaData( + CURRENT_SEGMENT_ID, + { + sisyfosLayers: NEXT_LAYERS, + acceptsPersistedAudio: true + }, + { + sisyfosLayers: CURRENT_LAYERS, + wantsToPersistAudio: false + }, + createPartEndState(CURRENT_SEGMENT_ID) + ) + + expect(result).toEqual({ + sisyfosLayers: NEXT_LAYERS, + acceptsPersistedAudio: true + }) + }) + + it('populates previousSisyfosLayers with current and previous layers when next accepts and current&previous want to persist', () => { + const result = mergePersistenceMetaData( + CURRENT_SEGMENT_ID, + { + sisyfosLayers: NEXT_LAYERS, + acceptsPersistedAudio: true + }, + { + sisyfosLayers: CURRENT_LAYERS, + wantsToPersistAudio: true + }, + createPartEndState(CURRENT_SEGMENT_ID) + ) + + expect(result).toEqual({ + sisyfosLayers: NEXT_LAYERS, + previousSisyfosLayers: [...PREVIOUS_LAYERS, ...CURRENT_LAYERS], + acceptsPersistedAudio: true + }) + }) + + it('populates previousSisyfosLayers with current layers when next accepts and current&previous want to persist, but previous was in a different segment', () => { + const result = mergePersistenceMetaData( + CURRENT_SEGMENT_ID, + { + sisyfosLayers: NEXT_LAYERS, + acceptsPersistedAudio: true + }, + { + sisyfosLayers: CURRENT_LAYERS, + wantsToPersistAudio: true + }, + createPartEndState('anotherSegment') + ) + + expect(result).toEqual({ + sisyfosLayers: NEXT_LAYERS, + previousSisyfosLayers: [...CURRENT_LAYERS], + acceptsPersistedAudio: true + }) + }) + + it('populates previousSisyfosLayers with current layers when next accepts, current&previous want to persist, but current is injected', () => { + const result = mergePersistenceMetaData( + CURRENT_SEGMENT_ID, + { + sisyfosLayers: NEXT_LAYERS, + acceptsPersistedAudio: true + }, + { + sisyfosLayers: CURRENT_LAYERS, + wantsToPersistAudio: true, + isModifiedOrInsertedByAction: true + }, + createPartEndState(CURRENT_SEGMENT_ID) + ) + + expect(result).toEqual({ + sisyfosLayers: NEXT_LAYERS, + previousSisyfosLayers: CURRENT_LAYERS, + acceptsPersistedAudio: true + }) + }) + + it('populates previousSisyfosLayers with current layers and previous persisted when next accepts, current&previous want to persist, but current is injected', () => { + const result = mergePersistenceMetaData( + CURRENT_SEGMENT_ID, + { + sisyfosLayers: NEXT_LAYERS, + acceptsPersistedAudio: true + }, + { + sisyfosLayers: CURRENT_LAYERS, + previousSisyfosLayers: PREVIOUS_PERSISTED_LAYERS, + wantsToPersistAudio: true, + isModifiedOrInsertedByAction: true + }, + createPartEndState(CURRENT_SEGMENT_ID) + ) + + expect(result).toEqual({ + sisyfosLayers: NEXT_LAYERS, + previousSisyfosLayers: [...PREVIOUS_PERSISTED_LAYERS, ...CURRENT_LAYERS], + acceptsPersistedAudio: true + }) + }) + + it('does not populate previousSisyfosLayers when next does not accept persistence', () => { + const result = mergePersistenceMetaData( + CURRENT_SEGMENT_ID, + { + sisyfosLayers: NEXT_LAYERS, + acceptsPersistedAudio: false + }, + { + sisyfosLayers: CURRENT_LAYERS, + wantsToPersistAudio: true + }, + createPartEndState(CURRENT_SEGMENT_ID) + ) + + expect(result).toEqual({ + sisyfosLayers: NEXT_LAYERS, + acceptsPersistedAudio: false + }) + }) + }) +}) + +function createPartEndState(segmentId: string): PartEndStateExt { + return { + partInstanceId: 'partId', + segmentId, + sisyfosPersistenceMetaData: { + sisyfosLayers: PREVIOUS_LAYERS + }, + mediaPlayerSessions: {} + } +} diff --git a/src/tv2-common/actions/actionTypes.ts b/src/tv2-common/actions/actionTypes.ts index b69062d9e..6f3ddaf2a 100644 --- a/src/tv2-common/actions/actionTypes.ts +++ b/src/tv2-common/actions/actionTypes.ts @@ -87,12 +87,16 @@ export interface ActionCommentatorSelectJingle extends ActionBase { type: AdlibActionType.COMMENTATOR_SELECT_JINGLE } -export interface ActionClearGraphics extends ActionBase { - type: AdlibActionType.CLEAR_GRAPHICS +export interface ActionClearAllGraphics extends ActionBase { + type: AdlibActionType.CLEAR_ALL_GRAPHICS sendCommands?: boolean label: string } +export interface ActionClearTemaGraphics extends ActionBase { + type: AdlibActionType.CLEAR_TEMA_GRAPHICS +} + export interface ActionTakeWithTransitionVariantBase { type: 'cut' | 'mix' | 'breaker' | 'dip' } @@ -141,6 +145,10 @@ export interface ActionFadeDownPersistedAudioLevels extends ActionBase { type: AdlibActionType.FADE_DOWN_PERSISTED_AUDIO_LEVELS } +export interface ActionFadeDownSoundPlayer extends ActionBase { + type: AdlibActionType.FADE_DOWN_SOUND_PLAYER +} + export type TV2AdlibAction = | ActionSelectServerClip | ActionSelectDVE @@ -154,8 +162,9 @@ export type TV2AdlibAction = | ActionCommentatorSelectDVE | ActionCommentatorSelectFull | ActionCommentatorSelectJingle - | ActionClearGraphics + | ActionClearAllGraphics | ActionTakeWithTransition | ActionRecallLastLive | ActionRecallLastDVE | ActionFadeDownPersistedAudioLevels + | ActionFadeDownSoundPlayer diff --git a/src/tv2-common/actions/executeAction.ts b/src/tv2-common/actions/executeAction.ts index 03edfa10e..804783955 100644 --- a/src/tv2-common/actions/executeAction.ts +++ b/src/tv2-common/actions/executeAction.ts @@ -8,6 +8,7 @@ import { IBlueprintPiece, IBlueprintPieceDB, IBlueprintPieceInstance, + IBlueprintResolvedPieceInstance, PieceLifespan, SplitsContent, TimelineObjectCoreExt, @@ -16,7 +17,7 @@ import { WithTimeline } from 'blueprints-integration' import { - ActionClearGraphics, + ActionClearAllGraphics, ActionCutSourceToBox, ActionCutToCamera, ActionCutToRemote, @@ -27,6 +28,7 @@ import { ActionSelectServerClip, calculateTime, createDipTransitionBlueprintPieceForPart, + createFadeSoundContent, createInTransitionForTransitionStyle, CreatePartServerBase, CueDefinition, @@ -37,6 +39,7 @@ import { DVESources, EvaluateCuesOptions, executeWithContext, + findLastPlayingPieceInstance, GetDVETemplate, getServerPosition, GetSisyfosTimelineObjForCamera, @@ -46,16 +49,18 @@ import { literal, MakeContentDVE2, PartDefinition, + PartEndStateExt, PieceMetaData, PilotGraphicGenerator, ServerSelectMode, ShowStyleContext, - SisyfosPersistMetaData, + SisyfosPersistenceMetaData, TableConfigItemBreaker, TimelineBlueprintExt, TransitionStyle, TV2AdlibAction, TV2BlueprintConfigBase, + TV2ShowStyleConfig, TV2StudioConfigBase, UniformConfig } from 'tv2-common' @@ -66,6 +71,7 @@ import { PartType, SharedGraphicLLayer, SharedOutputLayer, + SharedSisyfosLLayer, SharedSourceLayer, SourceType, TallyTags @@ -99,14 +105,9 @@ const STOPPABLE_GRAPHICS_LAYERS = [ SharedSourceLayer.PgmGraphicsTLF ] -const FADE_SISYFOS_LEVELS_PIECE_NAME = 'fadeDown' - -export interface ActionExecutionSettings< - StudioConfig extends TV2StudioConfigBase, - ShowStyleConfig extends TV2BlueprintConfigBase -> { +export interface ActionExecutionSettings { EvaluateCues: ( - context: ShowStyleContext, + context: ShowStyleContext, part: IBlueprintPart, pieces: IBlueprintPiece[], adLibPieces: IBlueprintAdLibPiece[], @@ -155,7 +156,7 @@ export interface ActionExecutionSettings< SELECTED_ADLIB_LAYERS: string[] } createJingleContent: ( - context: ShowStyleContext, + context: ShowStyleContext, file: string, breakerConfig: TableConfigItemBreaker ) => WithTimeline @@ -172,7 +173,7 @@ export async function executeAction< >( coreContext: IActionExecutionContext, uniformConfig: UniformConfig, - settings: ActionExecutionSettings, + settings: ActionExecutionSettings, actionIdStr: string, userData: ActionUserData, triggerMode?: string @@ -204,8 +205,11 @@ export async function executeAction< case AdlibActionType.SELECT_JINGLE: await executeActionSelectJingle(context, settings, actionId, userData as ActionSelectJingle) break - case AdlibActionType.CLEAR_GRAPHICS: - await executeActionClearGraphics(context, userData as ActionClearGraphics) + case AdlibActionType.CLEAR_ALL_GRAPHICS: + await executeActionClearAllGraphics(context, userData as ActionClearAllGraphics) + break + case AdlibActionType.CLEAR_TEMA_GRAPHICS: + await executeActionClearTemaGraphics(context) break case AdlibActionType.CUT_TO_CAMERA: await executeActionCutToCamera(context, settings, actionId, userData as ActionCutToCamera) @@ -249,6 +253,15 @@ export async function executeAction< await executeActionCallRobotPreset(context, preset) break } + case AdlibActionType.FADE_DOWN_SOUND_PLAYER: { + const fadeDurationFrames: number = Number(triggerMode) + if (Number.isNaN(fadeDurationFrames)) { + context.core.notifyUserWarning(`Calling Fade Sound Player ignored. '${triggerMode}' is not a number`) + break + } + await fadeDownSoundPlayer(context, fadeDurationFrames) + break + } default: assertUnreachable(actionId) break @@ -280,7 +293,7 @@ async function getExistingTransition< ShowStyleConfig extends TV2BlueprintConfigBase >( context: ActionExecutionContext, - settings: ActionExecutionSettings, + settings: ActionExecutionSettings, part: 'current' | 'next' ): Promise { const existingTransition = await context.core @@ -385,7 +398,7 @@ async function executeActionSelectServerClip< ShowStyleConfig extends TV2BlueprintConfigBase >( context: ActionExecutionContext, - settings: ActionExecutionSettings, + settings: ActionExecutionSettings, actionId: string, userData: ActionSelectServerClip, triggerMode?: ServerSelectMode, @@ -498,11 +511,7 @@ function dveContainsServer(sources: DVESources): boolean { function isServerOnPgm< StudioConfig extends TV2StudioConfigBase, ShowStyleConfig extends TV2BlueprintConfigBase ->( - pieceInstance: IBlueprintPieceInstance, - settings: ActionExecutionSettings, - voLayer: boolean -) { +>(pieceInstance: IBlueprintPieceInstance, settings: ActionExecutionSettings, voLayer: boolean) { return ( pieceInstance.piece.sourceLayerId === (voLayer ? settings.SourceLayers.VO : settings.SourceLayers.Server) || (pieceInstance.piece.sourceLayerId === settings.SourceLayers.DVEAdLib && @@ -515,7 +524,7 @@ async function executeActionSelectDVE< ShowStyleConfig extends TV2BlueprintConfigBase >( context: ActionExecutionContext, - settings: ActionExecutionSettings, + settings: ActionExecutionSettings, actionId: string, userData: ActionSelectDVE ) { @@ -551,9 +560,6 @@ async function executeActionSelectDVE< mediaPlayerSessions: dveContainsServer(parsedCue.sources) ? [externalId] : [], sources: parsedCue.sources, config: rawTemplate, - sisyfosPersistMetaData: { - sisyfosLayers: [] - }, userData } @@ -601,7 +607,7 @@ async function cutServerToBox< ShowStyleConfig extends TV2BlueprintConfigBase >( context: ActionExecutionContext, - settings: ActionExecutionSettings, + settings: ActionExecutionSettings, newDvePiece: IBlueprintPiece, containedServerBefore?: boolean, modifiesCurrent?: boolean @@ -697,7 +703,7 @@ async function executeActionSelectDVELayout< ShowStyleConfig extends TV2BlueprintConfigBase >( context: ActionExecutionContext, - settings: ActionExecutionSettings, + settings: ActionExecutionSettings, actionId: string, userData: ActionSelectDVELayout ) { @@ -740,9 +746,6 @@ async function executeActionSelectDVELayout< const newMetaData: DVEPieceMetaData = { sources, config: userData.config, - sisyfosPersistMetaData: { - sisyfosLayers: [] - }, userData: { type: AdlibActionType.SELECT_DVE, config: { @@ -790,10 +793,7 @@ async function executeActionSelectDVELayout< const newMetaData2: DVEPieceMetaData = { ...meta, sources: { ...sources, ...meta.sources }, - config: userData.config, - sisyfosPersistMetaData: { - sisyfosLayers: [] - } + config: userData.config } const pieceContent = MakeContentDVE2(context, userData.config, {}, meta.sources, settings.DVEGeneratorOptions) @@ -832,7 +832,7 @@ async function startNewDVELayout< ShowStyleConfig extends TV2BlueprintConfigBase >( context: ActionExecutionContext, - settings: ActionExecutionSettings, + settings: ActionExecutionSettings, dvePiece: IBlueprintPiece, pieceContent: WithTimeline, metaData: DVEPieceMetaData, @@ -921,7 +921,7 @@ async function executeActionSelectJingle< ShowStyleConfig extends TV2BlueprintConfigBase >( context: ActionExecutionContext, - settings: ActionExecutionSettings, + settings: ActionExecutionSettings, actionId: string, userData: ActionSelectJingle ) { @@ -990,7 +990,7 @@ async function executeActionCutToCamera< ShowStyleConfig extends TV2BlueprintConfigBase >( context: ActionExecutionContext, - settings: ActionExecutionSettings, + settings: ActionExecutionSettings, actionId: string, userData: ActionCutToCamera ) { @@ -1020,8 +1020,8 @@ async function executeActionCutToCamera< metaData: { sisyfosPersistMetaData: { sisyfosLayers: [], - acceptPersistAudio: sourceInfoCam.acceptPersistAudio, - isPieceInjectedInPart: true + acceptsPersistedAudio: sourceInfoCam.acceptPersistAudio, + isModifiedOrInsertedByAction: userData.cutDirectly } }, tags: [GetTagForKam(userData.sourceDefinition)], @@ -1042,25 +1042,22 @@ async function executeActionCutToCamera< await executePiece(context, settings, kamPiece, !userData.cutDirectly, part) } -async function executePiece< - StudioConfig extends TV2StudioConfigBase, - ShowStyleConfig extends TV2BlueprintConfigBase ->( - context: ActionExecutionContext, - settings: ActionExecutionSettings, +async function executePiece( + context: ActionExecutionContext, + settings: ActionExecutionSettings, pieceToExecute: IBlueprintPiece, shouldBeQueued: boolean, partToQueue: IBlueprintPart ) { - const currentPieceInstances = await context.core.getPieceInstances('current') + const currentPieceInstances = await context.core.getResolvedPieceInstances('current') const isServerInCurrentPart = currentPieceInstances.some( (p) => p.piece.sourceLayerId === settings.SourceLayers.Server || p.piece.sourceLayerId === settings.SourceLayers.VO ) const layersWithCutDirect: string[] = [settings.SourceLayers.Live, settings.SourceLayers.Cam] - const currentPiece: IBlueprintPieceInstance | undefined = findLastPlayingPieceInstance( + const currentPieceInstance: IBlueprintResolvedPieceInstance | undefined = findLastPlayingPieceInstance( currentPieceInstances, - layersWithCutDirect + (p) => layersWithCutDirect.includes(p.piece.sourceLayerId) ) if (shouldBeQueued || isServerInCurrentPart) { @@ -1074,40 +1071,85 @@ async function executePiece< if (isServerInCurrentPart && !shouldBeQueued) { await context.core.takeAfterExecuteAction(true) } - } else if (currentPiece && !isPlannedPieceOnLayer(currentPiece, settings.SourceLayers.Live)) { - pieceToExecute.externalId = currentPiece.piece.externalId - pieceToExecute.enable = currentPiece.piece.enable - const currentMetaData = currentPiece.piece.metaData! - const metaData = pieceToExecute.metaData! - metaData.sisyfosPersistMetaData!.previousPersistMetaDataForCurrentPiece = currentMetaData.sisyfosPersistMetaData + } else if (currentPieceInstance && !isPlannedPieceOnLayer(currentPieceInstance, settings.SourceLayers.Live)) { + await executePieceByReplacingCurrent(context, pieceToExecute, currentPieceInstance, currentPieceInstances) + } else { + await executePieceByStoppingCurrent(context, pieceToExecute, currentPieceInstances, settings) + } +} - await stopGraphicPiecesThatShouldEndWithPart(context, currentPieceInstances) +async function executePieceByStoppingCurrent( + context: ActionExecutionContext, + pieceToExecute: IBlueprintPiece, + currentPieceInstances: Array>, + settings: ActionExecutionSettings +) { + const currentExternalId = await context.core + .getPartInstance('current') + .then((currentPartInstance) => currentPartInstance?.part.externalId) - await context.core.updatePieceInstance(currentPiece._id, pieceToExecute) - } else { - const currentExternalId = await context.core - .getPartInstance('current') - .then((currentPartInstance) => currentPartInstance?.part.externalId) + if (currentExternalId) { + pieceToExecute.externalId = currentExternalId + } - if (currentExternalId) { - pieceToExecute.externalId = currentExternalId - } + const currentPieceInstanceWithMetaData = findLastPlayingPieceInstance( + currentPieceInstances, + (piece) => !!piece.piece.metaData?.sisyfosPersistMetaData + ) - await context.core.stopPiecesOnLayers([ - settings.SourceLayers.DVE, - ...(settings.SourceLayers.DVEAdLib ? [settings.SourceLayers.DVEAdLib] : []), - settings.SourceLayers.Effekt, - settings.SourceLayers.Live, - settings.SourceLayers.Server, - settings.SourceLayers.VO, - ...(settings.SourceLayers.EVS ? [settings.SourceLayers.EVS] : []), - settings.SourceLayers.Continuity - ]) - await stopGraphicPiecesThatShouldEndWithPart(context, currentPieceInstances) + await updatePersistenceMetaData(context, pieceToExecute, currentPieceInstanceWithMetaData) + + await context.core.stopPiecesOnLayers([ + settings.SourceLayers.DVE, + ...(settings.SourceLayers.DVEAdLib ? [settings.SourceLayers.DVEAdLib] : []), + settings.SourceLayers.Effekt, + settings.SourceLayers.Live, + settings.SourceLayers.Server, + settings.SourceLayers.VO, + ...(settings.SourceLayers.EVS ? [settings.SourceLayers.EVS] : []), + settings.SourceLayers.Continuity + ]) + await stopGraphicPiecesThatShouldEndWithPart(context, currentPieceInstances) + + pieceToExecute.enable = { start: 'now' } + await context.core.insertPiece('current', pieceToExecute) +} + +async function executePieceByReplacingCurrent( + context: ActionExecutionContext, + pieceToExecute: IBlueprintPiece, + currentPiece: IBlueprintResolvedPieceInstance, + currentPieceInstances: Array> +) { + pieceToExecute.externalId = currentPiece.piece.externalId + pieceToExecute.enable = currentPiece.piece.enable + + await updatePersistenceMetaData(context, pieceToExecute, currentPiece) + + await stopGraphicPiecesThatShouldEndWithPart(context, currentPieceInstances) - pieceToExecute.enable = { start: 'now' } - await context.core.insertPiece('current', pieceToExecute) + await context.core.updatePieceInstance(currentPiece._id, pieceToExecute) +} + +async function updatePersistenceMetaData( + context: ActionExecutionContext, + pieceToExecute: IBlueprintPiece, + currentPieceInstance: IBlueprintResolvedPieceInstance | undefined +) { + const currentPart = await context.core.getPartInstance('current') + if (!currentPart) { + return } + + const currentMetaData = currentPieceInstance?.piece.metaData + const nextMetaData = pieceToExecute.metaData! + const previousPartEndState = currentPart.previousPartEndState as PartEndStateExt | undefined + nextMetaData.sisyfosPersistMetaData = mergePersistenceMetaData( + currentPart.segmentId, + nextMetaData.sisyfosPersistMetaData!, + currentMetaData?.sisyfosPersistMetaData, + previousPartEndState + ) } function isPlannedPieceOnLayer(currentPiece: IBlueprintPieceInstance, sourceLayerId: string) { @@ -1118,24 +1160,6 @@ function isPlannedPieceOnLayer(currentPiece: IBlueprintPieceInstance>, - sourceLayerIds: string[] -): IBlueprintPieceInstance | undefined { - const playingPiecesOnSelectedLayers = currentPieceInstances.filter( - (p) => !p.stoppedPlayback && sourceLayerIds.includes(p.piece.sourceLayerId) - ) - if (playingPiecesOnSelectedLayers.length <= 1) { - return playingPiecesOnSelectedLayers[0] - } - return playingPiecesOnSelectedLayers.reduce((prev, current) => { - const prevStartedPlayback = prev.startedPlayback ?? prev.dynamicallyInserted?.time ?? Infinity - const currentStartedPlayback = current.startedPlayback ?? current.dynamicallyInserted?.time ?? Infinity - - return prevStartedPlayback > currentStartedPlayback ? prev : current - }) -} - async function stopGraphicPiecesThatShouldEndWithPart( context: ActionExecutionContext, currentPieceInstances: Array> @@ -1160,7 +1184,7 @@ async function executeActionCutToRemote< ShowStyleConfig extends TV2BlueprintConfigBase >( context: ActionExecutionContext, - settings: ActionExecutionSettings, + settings: ActionExecutionSettings, actionId: string, userData: ActionCutToRemote ) { @@ -1183,13 +1207,13 @@ async function executeActionCutToRemote< const eksternSisyfos: TSR.TimelineObjSisyfosAny[] = GetSisyfosTimelineObjForRemote(context.config, sourceInfo) - const sisyfosPersistMetaData: SisyfosPersistMetaData = + const sisyfosPersistMetaData: SisyfosPersistenceMetaData = sourceInfo !== undefined ? { sisyfosLayers: sourceInfo.sisyfosLayers ?? [], wantsToPersistAudio: sourceInfo.wantsToPersistAudio, - acceptPersistAudio: sourceInfo.acceptPersistAudio, - isPieceInjectedInPart: userData.cutDirectly + acceptsPersistedAudio: sourceInfo.acceptPersistAudio, + isModifiedOrInsertedByAction: userData.cutDirectly } : { sisyfosLayers: [] } @@ -1231,7 +1255,7 @@ async function executeActionCutSourceToBox< ShowStyleConfig extends TV2BlueprintConfigBase >( context: ActionExecutionContext, - settings: ActionExecutionSettings, + settings: ActionExecutionSettings, _actionId: string, userData: ActionCutSourceToBox ) { @@ -1351,7 +1375,7 @@ function groupPiecesBySourceLayer(pieceInstances: Array ->(settings: ActionExecutionSettings, piecesBySourceLayer: PiecesBySourceLayer) { +>(settings: ActionExecutionSettings, piecesBySourceLayer: PiecesBySourceLayer) { const sourceLayersOrderedByPriority = [ settings.SourceLayers.Cam, settings.SourceLayers.DVE, @@ -1376,7 +1400,7 @@ async function executeActionTakeWithTransition< ShowStyleConfig extends TV2BlueprintConfigBase >( context: ActionExecutionContext, - settings: ActionExecutionSettings, + settings: ActionExecutionSettings, actionId: string, userData: ActionTakeWithTransition ) { @@ -1589,7 +1613,7 @@ async function findMediaPlayerSessions( async function executeActionCommentatorSelectServer< StudioConfig extends TV2StudioConfigBase, ShowStyleConfig extends TV2BlueprintConfigBase ->(context: ActionExecutionContext, settings: ActionExecutionSettings) { +>(context: ActionExecutionContext, settings: ActionExecutionSettings) { const data = await findDataStore(context, [ settings.SelectedAdlibs.SourceLayer.Server, settings.SelectedAdlibs.SourceLayer.VO @@ -1622,7 +1646,7 @@ async function executeActionCommentatorSelectServer< async function executeActionCommentatorSelectDVE< StudioConfig extends TV2StudioConfigBase, ShowStyleConfig extends TV2BlueprintConfigBase ->(context: ActionExecutionContext, settings: ActionExecutionSettings) { +>(context: ActionExecutionContext, settings: ActionExecutionSettings) { if (!settings.SelectedAdlibs.SourceLayer.DVE) { return } @@ -1639,7 +1663,7 @@ async function executeActionCommentatorSelectDVE< async function executeActionCommentatorSelectFull< StudioConfig extends TV2StudioConfigBase, ShowStyleConfig extends TV2BlueprintConfigBase ->(context: ActionExecutionContext, settings: ActionExecutionSettings) { +>(context: ActionExecutionContext, settings: ActionExecutionSettings) { const data = await findDataStore(context, [SharedSourceLayer.SelectedAdlibGraphicsFull]) if (!data) { @@ -1652,7 +1676,7 @@ async function executeActionCommentatorSelectFull< async function executeActionCommentatorSelectJingle< StudioConfig extends TV2StudioConfigBase, ShowStyleConfig extends TV2BlueprintConfigBase ->(context: ActionExecutionContext, settings: ActionExecutionSettings) { +>(context: ActionExecutionContext, settings: ActionExecutionSettings) { if (!settings.SelectedAdlibs.SourceLayer.Effekt) { return } @@ -1671,7 +1695,7 @@ async function executeActionRecallLastLive< ShowStyleConfig extends TV2BlueprintConfigBase >( context: ActionExecutionContext, - settings: ActionExecutionSettings, + settings: ActionExecutionSettings, actionId: string ) { const lastLive = await context.core.findLastPieceOnLayer(settings.SourceLayers.Live, { @@ -1728,15 +1752,9 @@ async function executeActionRecallLastDVE< ShowStyleConfig extends TV2BlueprintConfigBase >( context: ActionExecutionContext, - settings: ActionExecutionSettings, + settings: ActionExecutionSettings, actionId: string ) { - const currentPart = context.core.getPartInstance('current') - - if (!currentPart) { - return - } - const lastPlayedScheduledDVE = (await context.core.findLastPieceOnLayer(settings.SourceLayers.DVE, { originalOnly: true })) as IBlueprintPieceInstance | undefined @@ -1778,22 +1796,29 @@ async function addLatestPieceOnLayerForDve( } async function executeActionFadeDownPersistedAudioLevels(context: ActionExecutionContext) { - const fadeSisyfosMetaData = await createFadeSisyfosLevelsMetaData(context) - const resetSisyfosPersistedLevelsPiece: IBlueprintPiece = { - externalId: 'fadeSisyfosPersistedLevelsDown', - name: FADE_SISYFOS_LEVELS_PIECE_NAME, - outputLayerId: '', - sourceLayerId: '', - enable: { start: 'now' }, - lifespan: PieceLifespan.WithinPart, - metaData: { - sisyfosPersistMetaData: fadeSisyfosMetaData - }, - content: { - timelineObjects: [] - } + const resolvedPieceInstances = await context.core.getResolvedPieceInstances('current') + if (resolvedPieceInstances.length === 0) { + return + } + + const latestPiece = findLastPlayingPieceInstance( + resolvedPieceInstances, + (piece) => !!piece.piece.metaData?.sisyfosPersistMetaData + ) + if (!latestPiece) { + return + } + + latestPiece.piece.content.timelineObjects = latestPiece.piece.content.timelineObjects.filter( + (timelineObject) => timelineObject.layer !== SharedSisyfosLLayer.SisyfosPersistedLevels + ) + const latestPieceMetaData = latestPiece.piece.metaData?.sisyfosPersistMetaData + if (latestPieceMetaData) { + delete latestPieceMetaData.previousSisyfosLayers + delete latestPieceMetaData.acceptsPersistedAudio + latestPieceMetaData.isModifiedOrInsertedByAction = true } - await context.core.insertPiece('current', resetSisyfosPersistedLevelsPiece) + return context.core.updatePieceInstance(latestPiece._id, latestPiece.piece) } async function executeActionCallRobotPreset(context: ActionExecutionContext, preset: number): Promise { @@ -1805,30 +1830,20 @@ async function executeActionCallRobotPreset(context: ActionExecutionContext, pre await context.core.insertPiece('current', robotCameraPiece) } -async function createFadeSisyfosLevelsMetaData(context: ActionExecutionContext) { - const resolvedPieceInstances = await context.core.getResolvedPieceInstances('current') - const emptySisyfosMetaData: SisyfosPersistMetaData = { - sisyfosLayers: [] - } - if (resolvedPieceInstances.length === 0) { - return emptySisyfosMetaData - } - - const latestPiece = resolvedPieceInstances - .filter((piece) => piece.piece.name !== FADE_SISYFOS_LEVELS_PIECE_NAME) - .sort((a, b) => b.resolvedStart - a.resolvedStart)[0] - - const latestPieceMetaData = latestPiece.piece.metaData - - if (!latestPieceMetaData || !latestPieceMetaData.sisyfosPersistMetaData) { - return emptySisyfosMetaData - } - - return { - sisyfosLayers: latestPieceMetaData.sisyfosPersistMetaData.sisyfosLayers, - wantsToPersistAudio: latestPieceMetaData.sisyfosPersistMetaData.wantsToPersistAudio, - acceptPersistAudio: false +async function fadeDownSoundPlayer(context: ActionExecutionContext, fadeDurationFrames: number): Promise { + const fadeSoundPlayerPiece: IBlueprintPiece = { + externalId: `fadeSoundPlayerIn${fadeDurationFrames}frames`, + name: 'Fade Sound Player', + enable: { + start: 'now', + duration: getTimeFromFrames(fadeDurationFrames) + }, + outputLayerId: SharedOutputLayer.MUSIK, + sourceLayerId: SharedSourceLayer.PgmAudioBed, + lifespan: PieceLifespan.WithinPart, + content: createFadeSoundContent(context.config, fadeDurationFrames) } + await context.core.insertPiece('current', fadeSoundPlayerPiece) } async function scheduleLastPlayedDVE< @@ -1836,7 +1851,7 @@ async function scheduleLastPlayedDVE< ShowStyleConfig extends TV2BlueprintConfigBase >( context: ActionExecutionContext, - settings: ActionExecutionSettings, + settings: ActionExecutionSettings, actionId: string, lastPlayedDVE: IBlueprintPieceInstance ): Promise { @@ -1857,7 +1872,7 @@ async function executeActionSelectFull< ShowStyleConfig extends TV2BlueprintConfigBase >( context: ActionExecutionContext, - settings: ActionExecutionSettings, + settings: ActionExecutionSettings, userData: ActionSelectFullGrafik ) { const externalId = generateExternalId(context, 'cut_to_full', [userData.name]) @@ -1915,11 +1930,30 @@ async function executeActionSelectFull< await context.core.stopPiecesOnLayers([SharedSourceLayer.SelectedAdlibGraphicsFull]) } -async function executeActionClearGraphics< +async function executeActionClearAllGraphics< StudioConfig extends TV2StudioConfigBase, ShowStyleConfig extends TV2BlueprintConfigBase ->(context: ActionExecutionContext, userData: ActionClearGraphics) { +>(context: ActionExecutionContext, userData: ActionClearAllGraphics) { await context.core.stopPiecesOnLayers(STOPPABLE_GRAPHICS_LAYERS) + const timelineObjects: TSR.TSRTimelineObj[] = + context.config.studio.GraphicsType === 'VIZ' + ? [ + { + id: '', + enable: { + start: 0 + }, + priority: 100, + layer: SharedGraphicLLayer.GraphicLLayerAdLibs, + content: { + deviceType: TSR.DeviceType.VIZMSE, + type: TSR.TimelineContentTypeVizMSE.CLEAR_ALL_ELEMENTS, + channelsToSendCommands: userData.sendCommands ? ['OVL1', 'FULL1', 'WALL1'] : undefined, + showName: context.config.selectedGfxSetup.OvlShowName ?? '' // @todo: improve types at the junction of HTML and Viz + } + } + ] + : [] await context.core.insertPiece('current', { enable: { start: 'now', @@ -1930,41 +1964,73 @@ async function executeActionClearGraphics< sourceLayerId: SharedSourceLayer.PgmAdlibGraphicCmd, outputLayerId: SharedOutputLayer.SEC, lifespan: PieceLifespan.WithinPart, - content: - context.config.studio.GraphicsType === 'HTML' - ? { - timelineObjects: [ - literal({ - id: '', - enable: { - start: 0 - }, - priority: 1, - layer: SharedGraphicLLayer.GraphicLLayerAdLibs, - content: { - deviceType: TSR.DeviceType.ABSTRACT - } - }) - ] - } - : { - timelineObjects: [ - literal({ - id: '', - enable: { - start: 0 - }, - priority: 100, - layer: SharedGraphicLLayer.GraphicLLayerAdLibs, - content: { - deviceType: TSR.DeviceType.VIZMSE, - type: TSR.TimelineContentTypeVizMSE.CLEAR_ALL_ELEMENTS, - channelsToSendCommands: userData.sendCommands ? ['OVL1', 'FULL1', 'WALL1'] : undefined, - showName: context.config.selectedGfxSetup.OvlShowName ?? '' // @todo: improve types at the junction of HTML and Viz - } - }) - ] - }, + content: { + timelineObjects + }, tags: userData.sendCommands ? [TallyTags.GFX_CLEAR] : [TallyTags.GFX_ALTUD] }) } + +async function executeActionClearTemaGraphics(context: ActionExecutionContext) { + await context.core.stopPiecesOnLayers([SharedSourceLayer.PgmGraphicsTema]) + const timelineObjects: TSR.TSRTimelineObj[] = + context.config.studio.GraphicsType === 'VIZ' + ? [ + { + id: '', + enable: { + start: 0 + }, + priority: 100, + layer: SharedGraphicLLayer.GraphicLLayerAdLibs, + content: { + deviceType: TSR.DeviceType.VIZMSE, + type: TSR.TimelineContentTypeVizMSE.ELEMENT_INTERNAL, + templateName: 'OUT_TEMA_H', + templateData: [], + showName: context.config.selectedGfxSetup.OvlShowName ?? '' + } + } + ] + : [] + await context.core.insertPiece('current', { + enable: { + start: 'now', + duration: 3000 + }, + externalId: 'clearTemaGFX', + name: 'GFX Temaud', + sourceLayerId: SharedSourceLayer.PgmAdlibGraphicCmd, + outputLayerId: SharedOutputLayer.SEC, + lifespan: PieceLifespan.WithinPart, + content: { + timelineObjects + }, + tags: [TallyTags.GFX_TEMAUD] + }) +} + +export function mergePersistenceMetaData( + currentSegmentId: string, + nextMetaData: SisyfosPersistenceMetaData, + currentMetaData: SisyfosPersistenceMetaData | undefined, + previousPartEndState: PartEndStateExt | undefined +): SisyfosPersistenceMetaData { + if (!currentMetaData) { + return nextMetaData + } + if (nextMetaData.acceptsPersistedAudio && currentMetaData.wantsToPersistAudio) { + const includePreviousPart = + !currentMetaData.isModifiedOrInsertedByAction && + previousPartEndState && + previousPartEndState.segmentId === currentSegmentId + nextMetaData.previousSisyfosLayers = Array.from( + new Set([ + ...(includePreviousPart ? previousPartEndState?.sisyfosPersistenceMetaData.sisyfosLayers : []), + ...(currentMetaData.previousSisyfosLayers ?? []), + ...currentMetaData.sisyfosLayers + ]) + ) + } + return nextMetaData +} diff --git a/src/tv2-common/blueprintConfig.ts b/src/tv2-common/blueprintConfig.ts index 64483d277..00d29ed4d 100644 --- a/src/tv2-common/blueprintConfig.ts +++ b/src/tv2-common/blueprintConfig.ts @@ -31,9 +31,10 @@ export interface TableConfigItemGfxTemplate { } export interface TableConfigItemGfxDesignTemplate { + _id: string INewsName: string INewsStyleColumn: string - /** Name of the Viz template trigering design change. For HTML graphics it coresponds to a CSS class. */ + /** Name of the Viz template triggering design change. For HTML graphics it corresponds to a CSS class. */ VizTemplate: string } @@ -45,8 +46,8 @@ export interface TableConfigItemGfxShowMapping { export interface TableConfigItemGfxDefaults { DefaultSetupName: { value: string; label: string } - DefaultSchema: string - DefaultDesign: string + DefaultSchema: { value: string; label: string } + DefaultDesign: { value: string; label: string } } export interface TableConfigItemAdLibTransitions { @@ -54,9 +55,11 @@ export interface TableConfigItemAdLibTransitions { } export interface TableConfigGfxSchema { - SchemaName: string + _id: string + GfxSchemaTemplatesName: string INewsSkemaColumn: string VizTemplate: string + CasparCgDesignValues: string } export interface TableConfigGfxSetup { @@ -67,6 +70,12 @@ export interface TableConfigGfxSetup { FullShowName?: string } +export interface CasparCgGfxDesignValues { + name: string + properties: Record + backgroundLoop: string +} + export interface ProcessedStudioConfig { sources: SourceMapping mediaPlayers: MediaPlayerConfig // Atem Input Ids @@ -149,6 +158,7 @@ export interface TV2StudioConfigBase { fadeIn: number fadeOut: number volume: number + useAudioFilterSyntax?: boolean } } @@ -179,6 +189,7 @@ export interface TV2ShowstyleBlueprintConfigBase { LYDConfig: TableConfigItemValue GfxSchemaTemplates: TableConfigGfxSchema[] GfxSetups: TableConfigGfxSetup[] + GfxShowMapping: TableConfigItemGfxShowMapping[] GfxDefaults: TableConfigItemGfxDefaults[] } diff --git a/src/tv2-common/content/dve.ts b/src/tv2-common/content/dve.ts index 493e3cd7e..447fda0f6 100644 --- a/src/tv2-common/content/dve.ts +++ b/src/tv2-common/content/dve.ts @@ -114,6 +114,8 @@ export interface DVEOptions { type BoxConfig = DVEConfigBox & { source: number } type BoxSources = Array<(VTContent | CameraContent | RemoteContent | GraphicsContent) & SplitsContentBoxProperties> +const DEFAULT_LOCATOR_TYPE = 'locators' + export function MakeContentDVEBase< StudioConfig extends TV2StudioConfigBase, ShowStyleConfig extends TV2BlueprintConfigBase @@ -275,14 +277,9 @@ export function MakeContentDVE2< } }) - let graphicsTemplateStyle: any = '' - try { - if (dveConfig.DVEGraphicsTemplateJSON) { - graphicsTemplateStyle = JSON.parse(dveConfig.DVEGraphicsTemplateJSON.toString()) - } - } catch { - context.core.notifyUserWarning(`DVE Graphics Template JSON is not valid for ${dveConfig.DVEName}`) - } + const graphicsTemplate = getDveGraphicsTemplate(dveConfig, context.core.notifyUserWarning) + const graphicsTemplateStyle = getDveGraphicsTemplateStyle(graphicsTemplate) + const locatorType = getDveLocatorType(graphicsTemplate) let keyFile = dveConfig.DVEGraphicsKey ? dveConfig.DVEGraphicsKey.toString() : undefined let frameFile = dveConfig.DVEGraphicsFrame ? dveConfig.DVEGraphicsFrame.toString() : undefined @@ -331,9 +328,9 @@ export function MakeContentDVE2< enable: { start: 0 }, priority: 1, layer: SharedGraphicLLayer.GraphicLLayerLocators, - content: CreateHTMLRendererContent(context.config, 'locators', { + content: CreateHTMLRendererContent(context.config, locatorType, { ...graphicsTemplateContent, - style: graphicsTemplateStyle ?? {} + style: graphicsTemplateStyle }) }), ...(keyFile @@ -378,6 +375,34 @@ export function MakeContentDVE2< } } +function getDveGraphicsTemplate(dveConfigInput: DVEConfigInput, notifyUserWarning: (message: string) => void): object { + try { + const dveGraphicsTemplate = JSON.parse(dveConfigInput.DVEGraphicsTemplateJSON) + if (!isValidDveGraphicsTemplate(dveGraphicsTemplate)) { + notifyUserWarning(`DVE Graphics Template for ${dveConfigInput.DVEName} is invalid.`) + return {} + } + return dveGraphicsTemplate + } catch { + notifyUserWarning(`DVE Graphics Template JSON for ${dveConfigInput.DVEName} is ill-formed.`) + return {} + } +} + +function isValidDveGraphicsTemplate(dveGraphicsTemplate: unknown): dveGraphicsTemplate is object { + return typeof dveGraphicsTemplate === 'object' && dveGraphicsTemplate !== null +} + +function getDveGraphicsTemplateStyle(dveGraphicsTemplate: { locatorType?: string }): object { + const { locatorType, ...dveGraphicsTemplateStyle } = dveGraphicsTemplate + return dveGraphicsTemplateStyle +} + +function getDveLocatorType(dveGraphicsTemplate: { locatorType?: string }): string { + const { locatorType } = dveGraphicsTemplate + return locatorType ?? DEFAULT_LOCATOR_TYPE +} + const setBoxSource = ( boxConfig: BoxConfig, boxSources: BoxSources, diff --git a/src/tv2-common/content/server.ts b/src/tv2-common/content/server.ts index c8c1c557f..5aa5a7731 100644 --- a/src/tv2-common/content/server.ts +++ b/src/tv2-common/content/server.ts @@ -84,7 +84,8 @@ function GetServerTimeline( seek: contentProps.seek, length: contentProps.seek ? contentProps.clipDuration : undefined, inPoint: contentProps.seek ? 0 : undefined, - playing: true + playing: true, + noStarttime: true }, metaData: { mediaPlayerSession: contentProps.mediaPlayerSession diff --git a/src/tv2-common/cueTiming.ts b/src/tv2-common/cueTiming.ts index d2378b955..292a2745e 100644 --- a/src/tv2-common/cueTiming.ts +++ b/src/tv2-common/cueTiming.ts @@ -1,7 +1,8 @@ import { CueDefinition, CueTime } from './inewsConversion/converters/ParseCue' import { IBlueprintPiece, PieceLifespan } from 'blueprints-integration' -import { FRAME_RATE, TV2BlueprintConfigBase, TV2StudioConfigBase } from 'tv2-common' +import { TV2BlueprintConfigBase, TV2StudioConfigBase } from 'tv2-common' +import { FRAME_RATE } from './frameTime' const FRAME_TIME = 1000 / FRAME_RATE // TODO: This should be pulled from config. @@ -47,7 +48,7 @@ export function getLifeSpan(mode: 'B' | 'S' | 'O'): PieceLifespan { case 'S': return PieceLifespan.OutOnSegmentEnd case 'O': - return PieceLifespan.OutOnShowStyleEnd + return PieceLifespan.OutOnRundownChange } } diff --git a/src/tv2-common/cues/EvaluateCueRobotCamera.ts b/src/tv2-common/cues/EvaluateCueRobotCamera.ts index d626706d3..45d5055b8 100644 --- a/src/tv2-common/cues/EvaluateCueRobotCamera.ts +++ b/src/tv2-common/cues/EvaluateCueRobotCamera.ts @@ -9,6 +9,10 @@ export function EvaluateCueRobotCamera( pieces: IBlueprintPiece[], externalId: string ): void { + if (cueDefinition.adlib) { + // robot adlibs from iNews cues are not supported + return + } const startTime: number = cueDefinition.start ? calculateTime(cueDefinition.start) ?? 0 : 0 const existingPiece = findExistingPieceForRobotCameraLayerAndStartTime(pieces, startTime) diff --git a/src/tv2-common/cues/ekstern.ts b/src/tv2-common/cues/ekstern.ts index cb5d86a14..d0cba50b1 100644 --- a/src/tv2-common/cues/ekstern.ts +++ b/src/tv2-common/cues/ekstern.ts @@ -61,7 +61,7 @@ export function EvaluateEksternBase< sisyfosPersistMetaData: { sisyfosLayers: sourceInfoEkstern.sisyfosLayers ?? [], wantsToPersistAudio: sourceInfoEkstern.wantsToPersistAudio, - acceptPersistAudio: sourceInfoEkstern.acceptPersistAudio + acceptsPersistedAudio: sourceInfoEkstern.acceptPersistAudio } }, content: literal>({ @@ -98,7 +98,7 @@ export function EvaluateEksternBase< sisyfosPersistMetaData: { sisyfosLayers: sourceInfoEkstern.sisyfosLayers ?? [], wantsToPersistAudio: sourceInfoEkstern.wantsToPersistAudio, - acceptPersistAudio: sourceInfoEkstern.acceptPersistAudio + acceptsPersistedAudio: sourceInfoEkstern.acceptPersistAudio } }, tags: [GetTagForLive(parsedCue.sourceDefinition)], diff --git a/src/tv2-common/cues/gfx-schema-generator-facade.ts b/src/tv2-common/cues/gfx-schema-generator-facade.ts new file mode 100644 index 000000000..bb9ea9e76 --- /dev/null +++ b/src/tv2-common/cues/gfx-schema-generator-facade.ts @@ -0,0 +1,8 @@ +import { DveLoopGenerator } from '../helpers/graphics/caspar/dve-loop-generator' +import { GfxSchemaGenerator } from './gfx-schema-generator' + +export abstract class GfxSchemaGeneratorFacade { + public static create(): GfxSchemaGenerator { + return new GfxSchemaGenerator(new DveLoopGenerator()) + } +} diff --git a/src/tv2-common/cues/gfx-schema-generator.ts b/src/tv2-common/cues/gfx-schema-generator.ts new file mode 100644 index 000000000..020487d3a --- /dev/null +++ b/src/tv2-common/cues/gfx-schema-generator.ts @@ -0,0 +1,199 @@ +import { GraphicsContent, IBlueprintPiece, PieceLifespan, TSR, WithTimeline } from 'blueprints-integration' +import { + calculateTime, + CueDefinitionGfxSchema, + getHtmlTemplateName, + literal, + ShowStyleContext, + TableConfigGfxSchema, + TV2ShowStyleConfig +} from 'tv2-common' +import { CueType, SharedGraphicLLayer, SharedOutputLayer, SharedSourceLayer } from 'tv2-constants' +import { DveLoopGenerator } from '../helpers/graphics/caspar/dve-loop-generator' + +const NON_BASELINE_SCHEMA: string = 'NON_BASELINE_SCHEMA' +const VALID_EMPTY_SCHEMA_VALUE: string = 'N/A' + +export class GfxSchemaGenerator { + constructor(private dveLoopGenerator: DveLoopGenerator) {} + + public createBaselineTimelineObjectsFromGfxDefaults(context: ShowStyleContext): TSR.TSRTimelineObjBase[] { + const schemaId: string = context.config.showStyle.GfxDefaults[0].DefaultSchema.value + if (VALID_EMPTY_SCHEMA_VALUE === schemaId) { + return [] + } + const schema: TableConfigGfxSchema | undefined = context.config.showStyle.GfxSchemaTemplates.find( + (s) => s._id === schemaId + ) + if (!schema || !schema.CasparCgDesignValues) { + context.core.notifyUserError( + `Unable to create baseline DVE loops for CasparCG. Check GfxDefaults Schema is configured.` + ) + return [] + } + + const cue: CueDefinitionGfxSchema = { + type: CueType.GraphicSchema, + schema: schema.VizTemplate, + CasparCgDesignValues: schema.CasparCgDesignValues ? JSON.parse(schema.CasparCgDesignValues) : [], + iNewsCommand: '' + } + return this.createBaselineTimelineObjects(context, cue, 10) + } + + public createBlueprintPieceFromGfxSchemaCue( + context: ShowStyleContext, + pieces: IBlueprintPiece[], + partId: string, + cue: CueDefinitionGfxSchema + ): void { + if (!cue.schema) { + context.core.notifyUserWarning(`No valid Schema found for ${cue.schema}`) + return + } + + const start = (cue.start ? calculateTime(cue.start) : 0) ?? 0 + pieces.push({ + externalId: partId, + name: cue.schema, + enable: { + start + }, + outputLayerId: SharedOutputLayer.SEC, + sourceLayerId: SharedSourceLayer.PgmSchema, + // @ts-ignore + lifespan: cue.isFromField ? 'rundown-change-segment-lookback' : PieceLifespan.OutOnRundownChange, + content: literal>({ + fileName: cue.schema, + path: cue.schema, + ignoreMediaObjectStatus: true, + timelineObjects: this.createTimelineObjects(context, cue) + }) + }) + } + + private createTimelineObjects( + context: ShowStyleContext, + cue: CueDefinitionGfxSchema, + priority?: number + ): TSR.TSRTimelineObjBase[] { + switch (context.config.studio.GraphicsType) { + case 'VIZ': { + return [ + this.createVizSchemaTimelineObject(context.config, cue), + ...this.dveLoopGenerator.createCasparCgDveLoopsFromCue(context, cue, priority) + ] + } + case 'HTML': { + return [ + this.createCasparCgSchemaTimelineObject(context, cue), + ...this.dveLoopGenerator.createCasparCgDveLoopsFromCue(context, cue, priority) + ] + } + default: { + return [] + } + } + } + + private createBaselineTimelineObjects( + context: ShowStyleContext, + cue: CueDefinitionGfxSchema, + priority?: number + ): TSR.TSRTimelineObjBase[] { + switch (context.config.studio.GraphicsType) { + case 'VIZ': { + return [ + this.createBaselineVizSchemaTimelineObject(context.config, cue), + ...this.dveLoopGenerator.createCasparCgDveLoopsFromCue(context, cue, priority) + ] + } + case 'HTML': { + return [ + this.createBaselineCasparCgSchemaTimelineObject(context, cue), + ...this.dveLoopGenerator.createCasparCgDveLoopsFromCue(context, cue, priority) + ] + } + default: { + return [] + } + } + } + + private createBaselineVizSchemaTimelineObject( + config: TV2ShowStyleConfig, + cue: CueDefinitionGfxSchema + ): TSR.TimelineObjVIZMSEElementInternal { + return { + id: '', + enable: { while: `!.${NON_BASELINE_SCHEMA}` }, + priority: 100, + classes: [cue.schema], + layer: SharedGraphicLLayer.GraphicLLayerSchema, + content: { + deviceType: TSR.DeviceType.VIZMSE, + type: TSR.TimelineContentTypeVizMSE.ELEMENT_INTERNAL, + templateName: cue.schema, + templateData: [], + showName: config.selectedGfxSetup.OvlShowName ?? '' + } + } + } + + private createVizSchemaTimelineObject( + config: TV2ShowStyleConfig, + cue: CueDefinitionGfxSchema + ): TSR.TimelineObjVIZMSEElementInternal { + return { + ...this.createBaselineVizSchemaTimelineObject(config, cue), + enable: { start: 0 }, + classes: [cue.schema, NON_BASELINE_SCHEMA] + } + } + + private createBaselineCasparCgSchemaTimelineObject( + context: ShowStyleContext, + cue: CueDefinitionGfxSchema + ): TSR.TimelineObjCCGTemplate { + return { + id: '', + enable: { while: `!.${NON_BASELINE_SCHEMA}` }, + priority: 100, + classes: [cue.schema], + layer: SharedGraphicLLayer.GraphicLLayerSchema, + content: { + deviceType: TSR.DeviceType.CASPARCG, + type: TSR.TimelineContentTypeCasparCg.TEMPLATE, + templateType: 'html', + name: getHtmlTemplateName(context.config), + data: this.createCasparCgHtmlSchemaData(context, cue), + useStopCommand: false + } + } + } + + private createCasparCgSchemaTimelineObject( + context: ShowStyleContext, + cue: CueDefinitionGfxSchema + ): TSR.TimelineObjCasparCGBase { + return literal({ + ...this.createBaselineCasparCgSchemaTimelineObject(context, cue), + enable: { start: 0 }, + classes: [cue.schema, NON_BASELINE_SCHEMA] + }) + } + + // TODO: Remove any from return type + private createCasparCgHtmlSchemaData(context: ShowStyleContext, cue: CueDefinitionGfxSchema): any { + if (!cue.CasparCgDesignValues || cue.CasparCgDesignValues.length === 0) { + context.core.notifyUserError( + `Unable to find Schema Design combination for "${cue.schema}". Design values will not be sent to CasparCG!` + ) + return {} + } + return { + designs: cue.CasparCgDesignValues, + partialUpdate: true + } + } +} diff --git a/src/tv2-common/cues/lyd.ts b/src/tv2-common/cues/lyd.ts index 0cec4474d..7cc9ef032 100644 --- a/src/tv2-common/cues/lyd.ts +++ b/src/tv2-common/cues/lyd.ts @@ -95,6 +95,13 @@ export function EvaluateLYD( } } +export function createFadeSoundContent( + config: TV2ShowStyleConfig, + fadeDurationFrames: number +): WithTimeline { + return LydContent(config, 'empty', 'fade', fadeDurationFrames, fadeDurationFrames) +} + function LydContent( config: TV2ShowStyleConfig, file: string, @@ -137,11 +144,11 @@ function LydContent( deviceType: TSR.DeviceType.CASPARCG, type: TSR.TimelineContentTypeCasparCg.MEDIA, file: filePath, - channelLayout: 'bed', + ...getAudioBedChannelLayoutProperties(config), loop: true, noStarttime: true, mixer: { - volume: Number(config.studio.AudioBedSettings.volume) / 100 + volume: config.studio.AudioBedSettings.volume / 100 }, transitions: { inTransition: { @@ -177,6 +184,19 @@ function LydContent( }) } +function getAudioBedChannelLayoutProperties( + config: TV2ShowStyleConfig +): Pick { + if (config.studio.AudioBedSettings.useAudioFilterSyntax) { + return { + audioFilter: 'pan=4c|c2=c0|c3=c1' + } + } + return { + channelLayout: 'bed' + } +} + export function CreateLYDBaseline(studio: string): TSR.TSRTimelineObj[] { return [ literal({ diff --git a/src/tv2-common/evaluateCues.ts b/src/tv2-common/evaluateCues.ts index d9255f3d9..f9d4385de 100644 --- a/src/tv2-common/evaluateCues.ts +++ b/src/tv2-common/evaluateCues.ts @@ -13,6 +13,7 @@ import { CueDefinitionClearGrafiks, CueDefinitionDVE, CueDefinitionEkstern, + CueDefinitionGfxSchema, CueDefinitionJingle, CueDefinitionLYD, CueDefinitionRobotCamera, @@ -20,6 +21,7 @@ import { IsTargetingFull, IsTargetingOVL, PartDefinition, + PieceMetaData, ShowStyleContext } from 'tv2-common' import { CueType } from 'tv2-constants' @@ -40,8 +42,8 @@ export interface Adlib { } export class EvaluateCueResult { - public readonly pieces: IBlueprintPiece[] = [] - public readonly adlibPieces: IBlueprintAdLibPiece[] = [] + public readonly pieces: Array> = [] + public readonly adlibPieces: Array> = [] public readonly actions: IBlueprintActionManifest[] = [] public push(source: EvaluateCueResult): EvaluateCueResult { @@ -61,23 +63,23 @@ export interface EvaluateCuesShowstyleOptions { ) => EvaluateCueResult EvaluateCueBackgroundLoop?: ( context: ShowStyleContext, - pieces: IBlueprintPiece[], - adlibPieces: IBlueprintAdLibPiece[], - actions: IBlueprintActionManifest[], partId: string, parsedCue: CueDefinitionBackgroundLoop, adlib?: boolean, rank?: number - ) => void + ) => EvaluateCueResult EvaluateCueGraphicDesign?: ( context: ShowStyleContext, - pieces: IBlueprintPiece[], - adlibPieces: IBlueprintAdLibPiece[], - actions: IBlueprintActionManifest[], partId: string, parsedCue: CueDefinitionGraphicDesign, adlib?: boolean, rank?: number + ) => EvaluateCueResult + EvaluateCueGraphicSchema?: ( + context: ShowStyleContext, + pieces: IBlueprintPiece[], + partId: string, + parsedCue: CueDefinitionGfxSchema ) => void EvaluateCueRouting?: ( context: ShowStyleContext, @@ -279,18 +281,16 @@ export async function EvaluateCuesBase( break case CueType.GraphicDesign: if (showStyleOptions.EvaluateCueGraphicDesign) { - showStyleOptions.EvaluateCueGraphicDesign( - context, - pieces, - adLibPieces, - actions, - partDefinition.externalId, - cue, - shouldAdlib, - adLibRank + result.push( + showStyleOptions.EvaluateCueGraphicDesign(context, partDefinition.externalId, cue, shouldAdlib, adLibRank) ) } break + case CueType.GraphicSchema: + if (showStyleOptions.EvaluateCueGraphicSchema) { + showStyleOptions.EvaluateCueGraphicSchema(context, pieces, partDefinition.externalId, cue) + } + break case CueType.ClearGrafiks: if (showStyleOptions.EvaluateCueClearGrafiks) { showStyleOptions.EvaluateCueClearGrafiks( @@ -306,15 +306,14 @@ export async function EvaluateCuesBase( break case CueType.BackgroundLoop: if (showStyleOptions.EvaluateCueBackgroundLoop) { - showStyleOptions.EvaluateCueBackgroundLoop( - context, - pieces, - adLibPieces, - actions, - partDefinition.externalId, - cue, - shouldAdlib, - adLibRank + result.push( + showStyleOptions.EvaluateCueBackgroundLoop( + context, + partDefinition.externalId, + cue, + shouldAdlib, + adLibRank + ) ) } break @@ -395,17 +394,6 @@ export async function EvaluateCuesBase( } }) } - } else if (obj.content.type === TSR.TimelineContentTypeVizMSE.CLEAR_ALL_ELEMENTS) { - const o = obj as TSR.TimelineObjVIZMSEClearAllElements - piece.expectedPlayoutItems.push({ - deviceSubType: TSR.DeviceType.VIZMSE, - content: { - templateName: 'altud', - channel: 'OVL1', - templateData: [], - showName: o.content.showName - } - }) } } }) diff --git a/src/tv2-common/helpers/graphics/Graphic.ts b/src/tv2-common/helpers/graphics/Graphic.ts index 077f7bf52..181224963 100644 --- a/src/tv2-common/helpers/graphics/Graphic.ts +++ b/src/tv2-common/helpers/graphics/Graphic.ts @@ -106,7 +106,7 @@ export abstract class Graphic { protected getPieceLifespan(): PieceLifespan { if (IsTargetingWall(this.engine)) { - return PieceLifespan.OutOnShowStyleEnd + return PieceLifespan.OutOnRundownChange } if (IsTargetingTLF(this.engine)) { return PieceLifespan.WithinPart diff --git a/src/tv2-common/helpers/graphics/caspar/__tests__/dve-loop-generator.spec.ts b/src/tv2-common/helpers/graphics/caspar/__tests__/dve-loop-generator.spec.ts new file mode 100644 index 000000000..6b09c8426 --- /dev/null +++ b/src/tv2-common/helpers/graphics/caspar/__tests__/dve-loop-generator.spec.ts @@ -0,0 +1,191 @@ +import { Timeline, TSR } from 'blueprints-integration' +import { mock } from 'ts-mockito' +import { CueDefinitionGfxSchema, ShowStyleContext } from 'tv2-common' +import { CueType, SharedCasparLLayer } from 'tv2-constants' +import { DveLoopGenerator } from '../dve-loop-generator' + +describe('DveLoopGenerator', () => { + describe('createCasparCgDveLoopsFromCue', () => { + it('does not have any CasparCgDesignValues configured - throws error', () => { + const context = mock() + const cue: CueDefinitionGfxSchema = { + type: CueType.GraphicSchema, + schema: 'randomSchema', + iNewsCommand: '' + } + + const testee: DveLoopGenerator = new DveLoopGenerator() + expect(() => { + testee.createCasparCgDveLoopsFromCue(context, cue) + }).toThrow() + }) + + it('has an empty CasparCgDesignValues array configured - throws error', () => { + const context = mock() + const cue: CueDefinitionGfxSchema = { + type: CueType.GraphicSchema, + schema: 'randomSchema', + iNewsCommand: '', + CasparCgDesignValues: [] + } + + const testee: DveLoopGenerator = new DveLoopGenerator() + expect(() => { + testee.createCasparCgDveLoopsFromCue(context, cue) + }).toThrow() + }) + + it('has one CasparCgDesignValue configured - returns one TimelineObject', () => { + const context = mock() + const cue: CueDefinitionGfxSchema = { + type: CueType.GraphicSchema, + schema: 'randomSchema', + iNewsCommand: '', + CasparCgDesignValues: [ + { name: 'someName', backgroundLoop: 'someLoop', properties: { someAttribute: 'someValue' } } + ] + } + + const testee: DveLoopGenerator = new DveLoopGenerator() + const result = testee.createCasparCgDveLoopsFromCue(context, cue) + + expect(result).toHaveLength(1) + }) + + it('has 5 CasparCgDesignValues configured - returns five TimelineObjects', () => { + const context = mock() + + const cue: CueDefinitionGfxSchema = { + type: CueType.GraphicSchema, + schema: 'randomSchema', + iNewsCommand: '', + CasparCgDesignValues: new Array(5).map(() => ({ + name: 'someName', + backgroundLoop: 'someLoop', + properties: { someAttribute: 'someValue' } + })) + } + + const testee: DveLoopGenerator = new DveLoopGenerator() + const result = testee.createCasparCgDveLoopsFromCue(context, cue) + + expect(result).toHaveLength(5) + }) + + it('sets the enable to be while .schema and .design name', () => { + const context = mock() + const schemaName = 'someSchemaName' + const designName = 'someDesignName' + + const cue: CueDefinitionGfxSchema = { + type: CueType.GraphicSchema, + schema: schemaName, + iNewsCommand: '', + CasparCgDesignValues: [ + { + name: designName, + backgroundLoop: 'someLoop', + properties: {} + } + ] + } + + const testee: DveLoopGenerator = new DveLoopGenerator() + const result = testee.createCasparCgDveLoopsFromCue(context, cue) + const timelineEnable: Timeline.TimelineEnable = result[0].enable as Timeline.TimelineEnable + const references: string[] = (timelineEnable.while as string).split('&').map((s) => s.trim()) + + expect(references).toHaveLength(2) + expect(references).toContain(`.${schemaName}`) + expect(references).toContain(`.${designName}`) + expect(references[0]).not.toEqual(references[1]) + }) + + it('creates the timelineObject on CasparCgDveLoop layer', () => { + const context = mock() + const cue = createCueDefinitionGfxSchema() + + const testee: DveLoopGenerator = new DveLoopGenerator() + const result = testee.createCasparCgDveLoopsFromCue(context, cue) + + expect(result[0].layer).toBe(SharedCasparLLayer.CasparCGDVELoop) + }) + + function createCueDefinitionGfxSchema(): CueDefinitionGfxSchema { + const cue: CueDefinitionGfxSchema = { + type: CueType.GraphicSchema, + schema: 'someSchemaName', + iNewsCommand: '', + CasparCgDesignValues: [ + { + name: 'someDesignName', + backgroundLoop: 'someLoop', + properties: {} + } + ] + } + return cue + } + + it('gets no priority - priority is set to 100', () => { + const context = mock() + const cue = createCueDefinitionGfxSchema() + + const testee: DveLoopGenerator = new DveLoopGenerator() + const result: TSR.TimelineObjCCGMedia[] = testee.createCasparCgDveLoopsFromCue(context, cue) + + expect(result[0].priority).toBe(100) + }) + + it('gets a priority - priority is set to the parsed priority', () => { + const context = mock() + const cue = createCueDefinitionGfxSchema() + const priority = 50 + + const testee: DveLoopGenerator = new DveLoopGenerator() + const result: TSR.TimelineObjCCGMedia[] = testee.createCasparCgDveLoopsFromCue(context, cue, priority) + + expect(result[0].priority).toBe(priority) + }) + + it('creates the timelineObject with DeviceType CasparCG', () => { + const context = mock() + const cue = createCueDefinitionGfxSchema() + + const testee: DveLoopGenerator = new DveLoopGenerator() + const result: TSR.TimelineObjCCGMedia[] = testee.createCasparCgDveLoopsFromCue(context, cue) + + expect(result[0].content.deviceType).toBe(TSR.DeviceType.CASPARCG) + }) + + it('creates the timelineObject with CasparCG media type', () => { + const context = mock() + const cue = createCueDefinitionGfxSchema() + + const testee: DveLoopGenerator = new DveLoopGenerator() + const result: TSR.TimelineObjCCGMedia[] = testee.createCasparCgDveLoopsFromCue(context, cue) + + expect(result[0].content.type).toBe(TSR.TimelineContentTypeCasparCg.MEDIA) + }) + + it('sets the background loop as file prefixed with "dve/"', () => { + const context = mock() + const cue = createCueDefinitionGfxSchema() + + const testee: DveLoopGenerator = new DveLoopGenerator() + const result: TSR.TimelineObjCCGMedia[] = testee.createCasparCgDveLoopsFromCue(context, cue) + + expect(result[0].content.file).toBe(`dve/${cue.CasparCgDesignValues![0].backgroundLoop}`) + }) + + it('sets loop to be true', () => { + const context = mock() + const cue = createCueDefinitionGfxSchema() + + const testee: DveLoopGenerator = new DveLoopGenerator() + const result: TSR.TimelineObjCCGMedia[] = testee.createCasparCgDveLoopsFromCue(context, cue) + + expect(result[0].content.loop).toBeTruthy() + }) + }) +}) diff --git a/src/tv2-common/helpers/graphics/caspar/dve-loop-generator.ts b/src/tv2-common/helpers/graphics/caspar/dve-loop-generator.ts new file mode 100644 index 000000000..33896ae08 --- /dev/null +++ b/src/tv2-common/helpers/graphics/caspar/dve-loop-generator.ts @@ -0,0 +1,58 @@ +import { TSR } from 'blueprints-integration' +import { CasparCgGfxDesignValues, CueDefinitionGfxSchema, ShowStyleContext } from 'tv2-common' +import { SharedCasparLLayer } from 'tv2-constants' + +export class DveLoopGenerator { + public createCasparCgDveLoopsFromCue( + context: ShowStyleContext, + cue: CueDefinitionGfxSchema, + priority?: number + ): TSR.TimelineObjCCGMedia[] { + if (!cue.CasparCgDesignValues || !cue.CasparCgDesignValues.length) { + const errorMessage = `No CasparCgDesignValues configured for Schema {${cue.schema}}` + context.core.notifyUserError(errorMessage) + return [] + } + return cue.CasparCgDesignValues.map((designValues) => + this.createDveLoopTimelineObjectForSchema(cue.schema, designValues, priority) + ) + } + + private createDveLoopTimelineObjectForSchema( + schemaName: string, + design: CasparCgGfxDesignValues, + priority?: number + ): TSR.TimelineObjCCGMedia { + return { + id: '', + enable: { + while: `.${schemaName} & .${design.name}` + }, + priority: priority ?? 100, + layer: SharedCasparLLayer.CasparCGDVELoop, + content: { + deviceType: TSR.DeviceType.CASPARCG, + type: TSR.TimelineContentTypeCasparCg.MEDIA, + file: `dve/${design.backgroundLoop}`, + loop: true + } + } + } + + public createDveLoopTimelineObject(fileName: string): TSR.TimelineObjCCGMedia[] { + return [ + { + id: '', + enable: { start: 0 }, + priority: 100, + layer: SharedCasparLLayer.CasparCGDVELoop, + content: { + deviceType: TSR.DeviceType.CASPARCG, + type: TSR.TimelineContentTypeCasparCg.MEDIA, + file: `dve/${fileName}`, + loop: true + } + } + ] + } +} diff --git a/src/tv2-common/helpers/graphics/caspar/index.ts b/src/tv2-common/helpers/graphics/caspar/index.ts index 810f82a8e..8ac97ac61 100644 --- a/src/tv2-common/helpers/graphics/caspar/index.ts +++ b/src/tv2-common/helpers/graphics/caspar/index.ts @@ -2,3 +2,4 @@ export * from './slotMappings' export * from './htmlPilotGraphicGenerator' export * from './HtmlInternalGraphic' export * from './util' +export * from './dve-loop-generator' diff --git a/src/tv2-common/helpers/graphics/caspar/util.ts b/src/tv2-common/helpers/graphics/caspar/util.ts index 8ca061db1..29e646622 100644 --- a/src/tv2-common/helpers/graphics/caspar/util.ts +++ b/src/tv2-common/helpers/graphics/caspar/util.ts @@ -1,8 +1,10 @@ import { TSR } from 'blueprints-integration' import { + getCasparCgBaselineDesignTimelineObjects, getTimelineLayerForGraphic, joinAssetToFolder, layerToHTMLGraphicSlot, + ShowStyleContext, Slots, TV2ShowStyleConfig } from 'tv2-common' @@ -10,7 +12,7 @@ import { SharedGraphicLLayer } from 'tv2-constants' export function CreateHTMLRendererContent( config: TV2ShowStyleConfig, - mappedTemplate: string, + graphicTemplateName: string, data: object ): TSR.TimelineObjCCGTemplate['content'] { return { @@ -20,7 +22,7 @@ export function CreateHTMLRendererContent( name: getHtmlTemplateName(config), data: { display: 'program', - slots: getHtmlTemplateContent(config, mappedTemplate, data), + slots: getHtmlTemplateContent(config, graphicTemplateName, data), partialUpdate: true }, useStopCommand: false, @@ -30,12 +32,22 @@ export function CreateHTMLRendererContent( } } +function getMappedGraphicsTemplateName(templateName: string): string { + switch (templateName) { + case 'locators-afvb': + return 'locators' + default: + return templateName + } +} + export function getHtmlTemplateContent( config: TV2ShowStyleConfig, - graphicTemplate: string, + graphicTemplateName: string, data: object ): Partial { - const layer = getTimelineLayerForGraphic(config, graphicTemplate) + const mappedGraphicTemplateName = getMappedGraphicsTemplateName(graphicTemplateName) + const layer = getTimelineLayerForGraphic(config, mappedGraphicTemplateName) const slot = layerToHTMLGraphicSlot[layer] @@ -47,15 +59,15 @@ export function getHtmlTemplateContent( [slot]: { display: 'program', payload: { - type: graphicTemplate, + type: graphicTemplateName, ...data } } } } -export function getHtmlGraphicBaseline(config: TV2ShowStyleConfig) { - const templateName = getHtmlTemplateName(config) +export function getHtmlGraphicBaseline(context: ShowStyleContext): TSR.TSRTimelineObj[] { + const templateName = getHtmlTemplateName(context.config) const partiallyUpdatableLayerMappings = [ SharedGraphicLLayer.GraphicLLayerOverlayIdent, SharedGraphicLLayer.GraphicLLayerOverlayLower, @@ -67,7 +79,7 @@ export function getHtmlGraphicBaseline(config: TV2ShowStyleConfig) { return [ ...getSlotBaselineTimelineObjects(templateName, partiallyUpdatableLayerMappings), getCompoundSlotBaselineTimelineObject(templateName, partiallyUpdatableLayerMappings), - getDesignBaselineTimelineObject(templateName), + ...getCasparCgBaselineDesignTimelineObjects(context, templateName), getFullPilotBaselineTimelineObject(templateName) ] } @@ -140,29 +152,6 @@ function getCompoundSlotBaselineTimelineObject( } } -function getDesignBaselineTimelineObject(templateName: string): TSR.TimelineObjCCGTemplate { - return { - id: '', - enable: { - while: '1' - }, - priority: 0, - layer: SharedGraphicLLayer.GraphicLLayerDesign, - content: { - deviceType: TSR.DeviceType.CASPARCG, - type: TSR.TimelineContentTypeCasparCg.TEMPLATE, - templateType: 'html', - name: templateName, - data: { - display: 'program', - design: '', - partialUpdate: true - }, - useStopCommand: false - } - } -} - function getFullPilotBaselineTimelineObject(templateName: string): TSR.TimelineObjCCGTemplate { return { id: '', diff --git a/src/tv2-common/helpers/graphics/design/index.ts b/src/tv2-common/helpers/graphics/design/index.ts index 93c6d5db6..e2f1eb5b0 100644 --- a/src/tv2-common/helpers/graphics/design/index.ts +++ b/src/tv2-common/helpers/graphics/design/index.ts @@ -1,6 +1,5 @@ import { GraphicsContent, - IBlueprintActionManifest, IBlueprintAdLibPiece, IBlueprintPiece, PieceLifespan, @@ -10,106 +9,192 @@ import { import { calculateTime, CueDefinitionGraphicDesign, + EvaluateCueResult, getHtmlTemplateName, - literal, + PieceMetaData, ShowStyleContext, TV2ShowStyleConfig } from 'tv2-common' import { SharedGraphicLLayer, SharedOutputLayer, SharedSourceLayer } from 'tv2-constants' +const NON_BASELINE_DESIGN: string = 'NON_BASELINE_DESIGN' +const VALID_EMPTY_DESIGN_VALUE: string = 'N/A' + export function EvaluateDesignBase( context: ShowStyleContext, - pieces: IBlueprintPiece[], - adlibPieces: IBlueprintAdLibPiece[], - _actions: IBlueprintActionManifest[], partId: string, parsedCue: CueDefinitionGraphicDesign, adlib?: boolean, rank?: number -) { - const start = (parsedCue.start ? calculateTime(parsedCue.start) : 0) ?? 0 - if (!parsedCue.design || !parsedCue.design.length) { - context.core.notifyUserWarning(`No valid design found for ${parsedCue.design}`) - return +): EvaluateCueResult { + const result = new EvaluateCueResult() + if (!parsedCue.design) { + context.core.notifyUserWarning(`No valid design found for ${JSON.stringify(parsedCue)}`) + return result } - if (adlib) { - adlibPieces.push({ - _rank: rank || 0, - externalId: partId, - name: parsedCue.design, - outputLayerId: SharedOutputLayer.SEC, - sourceLayerId: SharedSourceLayer.PgmDesign, - lifespan: PieceLifespan.OutOnShowStyleEnd, - content: literal>({ - fileName: parsedCue.design, - path: parsedCue.design, - ignoreMediaObjectStatus: true, - timelineObjects: designTimeline(context.config, parsedCue) - }) - }) - } else { - pieces.push({ - externalId: partId, - name: parsedCue.design, - enable: { - start - }, - outputLayerId: SharedOutputLayer.SEC, - sourceLayerId: SharedSourceLayer.PgmDesign, - lifespan: PieceLifespan.OutOnShowStyleEnd, - content: literal>({ - fileName: parsedCue.design, - path: parsedCue.design, - ignoreMediaObjectStatus: true, - timelineObjects: designTimeline(context.config, parsedCue) - }) - }) + result.adlibPieces.push(createDesignAdlibPiece(context, partId, parsedCue, rank)) + return result } + result.pieces.push(createDesignPiece(context, partId, parsedCue)) + return result } -function designTimeline(config: TV2ShowStyleConfig, parsedCue: CueDefinitionGraphicDesign): TSR.TSRTimelineObj[] { - switch (config.studio.GraphicsType) { +function createDesignAdlibPiece( + context: ShowStyleContext, + partId: string, + cue: CueDefinitionGraphicDesign, + rank?: number +): IBlueprintAdLibPiece { + return { + _rank: rank ?? 0, + externalId: partId, + name: cue.design, + outputLayerId: SharedOutputLayer.SEC, + sourceLayerId: SharedSourceLayer.PgmDesign, + // @ts-ignore + lifespan: cue.isFromField ? 'rundown-change-segment-lookback' : PieceLifespan.OutOnRundownChange, + content: createDesignPieceContent(context, cue) + } +} + +function createDesignPiece( + context: ShowStyleContext, + partId: string, + cue: CueDefinitionGraphicDesign +): IBlueprintPiece { + const start = (cue.start ? calculateTime(cue.start) : 0) ?? 0 + return { + externalId: partId, + name: cue.design, + enable: { + start + }, + outputLayerId: SharedOutputLayer.SEC, + sourceLayerId: SharedSourceLayer.PgmDesign, + // @ts-ignore + lifespan: cue.isFromField ? 'rundown-change-segment-lookback' : PieceLifespan.OutOnRundownChange, + content: createDesignPieceContent(context, cue) + } +} + +function createDesignPieceContent( + context: ShowStyleContext, + cue: CueDefinitionGraphicDesign +): WithTimeline { + return { + fileName: cue.design, + path: cue.design, + ignoreMediaObjectStatus: true, + timelineObjects: designTimeline(context, cue) + } +} + +function designTimeline(context: ShowStyleContext, parsedCue: CueDefinitionGraphicDesign): TSR.TSRTimelineObj[] { + switch (context.config.studio.GraphicsType) { case 'HTML': - return [ - literal({ - id: '', - enable: { - start: 0 - }, - priority: 1, - layer: SharedGraphicLLayer.GraphicLLayerDesign, - content: { - deviceType: TSR.DeviceType.CASPARCG, - type: TSR.TimelineContentTypeCasparCg.TEMPLATE, - templateType: 'html', - name: getHtmlTemplateName(config), - data: { - display: 'program', - design: parsedCue.design, - partialUpdate: true - }, - useStopCommand: false - } - }) - ] + return [getNonBaselineCasparCgDesignTimelineObject(context, parsedCue)] case 'VIZ': - return [ - literal({ - id: '', - enable: { start: 0 }, - priority: 100, - layer: SharedGraphicLLayer.GraphicLLayerDesign, - content: { - deviceType: TSR.DeviceType.VIZMSE, - type: TSR.TimelineContentTypeVizMSE.ELEMENT_INTERNAL, - templateName: parsedCue.design, - templateData: [], - showName: config.selectedGfxSetup.OvlShowName ?? '' // @todo: improve types at the junction of HTML and Viz - } - }) - ] + return [getNonBaselineVizDesignTimelineObject(context.config, parsedCue.design)] default: return [] } } + +function getNonBaselineCasparCgDesignTimelineObject( + context: ShowStyleContext, + parsedCue: CueDefinitionGraphicDesign +): TSR.TimelineObjCCGTemplate { + return { + id: '', + enable: { + start: 0 + }, + priority: 100, + classes: [`${parsedCue.design}`, NON_BASELINE_DESIGN], + layer: SharedGraphicLLayer.GraphicLLayerDesign, + content: createCasparCgDesignContent(parsedCue.design, getHtmlTemplateName(context.config)) + } +} + +function createCasparCgDesignContent(design: string, templateName: string): TSR.TimelineObjCCGTemplate['content'] { + return { + deviceType: TSR.DeviceType.CASPARCG, + type: TSR.TimelineContentTypeCasparCg.TEMPLATE, + templateType: 'html', + name: templateName, + data: { + display: 'program', + design, + partialUpdate: true + }, + useStopCommand: false + } +} + +function getNonBaselineVizDesignTimelineObject(config: TV2ShowStyleConfig, design: string) { + const vizDesignTimelineObject = getVizDesignTimelineObject(config, design) + vizDesignTimelineObject.classes!.push(NON_BASELINE_DESIGN) + return vizDesignTimelineObject +} + +function getVizDesignTimelineObject(config: TV2ShowStyleConfig, design: string): TSR.TimelineObjVIZMSEElementInternal { + return { + id: '', + enable: { start: 0 }, + priority: 100, + classes: [design], + layer: SharedGraphicLLayer.GraphicLLayerDesign, + content: { + deviceType: TSR.DeviceType.VIZMSE, + type: TSR.TimelineContentTypeVizMSE.ELEMENT_INTERNAL, + templateName: design, + templateData: [], + showName: config.selectedGfxSetup.OvlShowName ?? '' // @todo: improve types at the junction of HTML and Viz + } + } +} + +export function getVizBaselineDesignTimelineObject(context: ShowStyleContext) { + const designReference = context.config.showStyle.GfxDefaults[0].DefaultDesign + if (VALID_EMPTY_DESIGN_VALUE === designReference.value) { + return [] + } + const design = context.config.showStyle.GfxDesignTemplates.find( + (designTemplate) => designTemplate._id === designReference.value + ) + if (!design) { + context.core.notifyUserWarning(`Design ${designReference.label} not found in GFX Design Templates`) + return [] + } + return [getVizDesignTimelineObject(context.config, design.VizTemplate)] +} + +export function getCasparCgBaselineDesignTimelineObjects( + context: ShowStyleContext, + templateName: string +): TSR.TimelineObjCCGTemplate[] { + const designReference = context.config.showStyle.GfxDefaults[0].DefaultDesign + if (VALID_EMPTY_DESIGN_VALUE === designReference.value) { + return [] + } + const design = context.config.showStyle.GfxDesignTemplates.find( + (designTemplate) => designTemplate._id === designReference.value + ) + if (!design) { + context.core.notifyUserWarning(`Design ${designReference.label} not found in GFX Design Templates`) + return [] + } + return [ + { + id: '', + enable: { + while: `!.${NON_BASELINE_DESIGN}` + }, + priority: 1, + classes: [design.VizTemplate], + layer: SharedGraphicLLayer.GraphicLLayerDesign, + content: createCasparCgDesignContent(design.VizTemplate, templateName) + } + ] +} diff --git a/src/tv2-common/helpers/graphics/internal/InternalGraphic.ts b/src/tv2-common/helpers/graphics/internal/InternalGraphic.ts index e32a132e5..c2c722c8c 100644 --- a/src/tv2-common/helpers/graphics/internal/InternalGraphic.ts +++ b/src/tv2-common/helpers/graphics/internal/InternalGraphic.ts @@ -57,11 +57,6 @@ export abstract class InternalGraphic extends Graphic { sourceLayerId: this.sourceLayerId, outputLayerId: SharedOutputLayer.OVERLAY, lifespan: PieceLifespan.WithinPart, - metaData: { - sisyfosPersistMetaData: { - sisyfosLayers: [] - } - }, expectedDuration: 5000, tags: [AdlibTags.ADLIB_KOMMENTATOR], content: _.clone(this.content) @@ -83,11 +78,6 @@ export abstract class InternalGraphic extends Graphic { expectedDuration: this.getPieceEnable().duration }), lifespan: this.getPieceLifespan(), - metaData: { - sisyfosPersistMetaData: { - sisyfosLayers: [] - } - }, content: _.clone(this.content) } } @@ -105,9 +95,6 @@ export abstract class InternalGraphic extends Graphic { sourceLayerId: this.sourceLayerId, lifespan: this.getPieceLifespan(), metaData: { - sisyfosPersistMetaData: { - sisyfosLayers: [] - }, partType: this.partDefinition?.type, pieceExternalId: this.partDefinition?.externalId }, diff --git a/src/tv2-common/helpers/graphics/pilot/PilotGraphicGenerator.ts b/src/tv2-common/helpers/graphics/pilot/PilotGraphicGenerator.ts index 4becf0940..e494d0865 100644 --- a/src/tv2-common/helpers/graphics/pilot/PilotGraphicGenerator.ts +++ b/src/tv2-common/helpers/graphics/pilot/PilotGraphicGenerator.ts @@ -21,10 +21,8 @@ import { HtmlPilotGraphicGenerator, IsTargetingFull, IsTargetingWall, - literal, PieceMetaData, ShowStyleContext, - SisyfosPersistMetaData, t, TV2ShowStyleConfig, VizPilotGraphicGenerator @@ -124,11 +122,6 @@ export abstract class PilotGraphicGenerator extends Graphic { sourceLayerId: this.getSourceLayer(), prerollDuration: this.getPrerollDuration(), lifespan: this.getPieceLifespan(), - metaData: { - sisyfosPersistMetaData: { - sisyfosLayers: [] - } - }, content: this.getContent(), tags: IsTargetingFull(this.engine) ? [GetTagForFull(this.segmentExternalId, this.cue.graphic.vcpid), TallyTags.FULL_IS_LIVE] @@ -136,7 +129,7 @@ export abstract class PilotGraphicGenerator extends Graphic { } } - public createAdlibPiece(rank?: number): IBlueprintAdLibPiece { + public createAdlibPiece(rank?: number): IBlueprintAdLibPiece { const pilotPiece = this.createPiece() pilotPiece.tags = [...(pilotPiece.tags ?? []), AdlibTags.ADLIB_FLOW_PRODUCER, AdlibTags.ADLIB_KOMMENTATOR] return { @@ -170,10 +163,7 @@ export abstract class PilotGraphicGenerator extends Graphic { name: this.cue.graphic.name, vcpid: this.cue.graphic.vcpid, segmentExternalId: this.segmentExternalId - }, - sisyfosPersistMetaData: literal({ - sisyfosLayers: [] - }) + } }, content, tags: [GetTagForFullNext(this.segmentExternalId, this.cue.graphic.vcpid)] diff --git a/src/tv2-common/helpers/graphics/util.ts b/src/tv2-common/helpers/graphics/util.ts index 1c661ff76..02ab3f4c6 100644 --- a/src/tv2-common/helpers/graphics/util.ts +++ b/src/tv2-common/helpers/graphics/util.ts @@ -1,5 +1,10 @@ import { IBlueprintPart, TSR } from 'blueprints-integration' -import { getHtmlGraphicBaseline, TV2ShowStyleConfig } from 'tv2-common' +import { + getHtmlGraphicBaseline, + getVizBaselineDesignTimelineObject, + ShowStyleContext, + TV2ShowStyleConfig +} from 'tv2-common' export function applyFullGraphicPropertiesToPart(config: TV2ShowStyleConfig, part: IBlueprintPart) { const keepAliveDuration = @@ -17,10 +22,10 @@ export function applyFullGraphicPropertiesToPart(config: TV2ShowStyleConfig, par } } -export function getGraphicBaseline(config: TV2ShowStyleConfig): TSR.TSRTimelineObj[] { - if (config.studio.GraphicsType === 'VIZ') { - return [] +export function getGraphicBaseline(context: ShowStyleContext): TSR.TSRTimelineObj[] { + if (context.config.studio.GraphicsType === 'VIZ') { + return getVizBaselineDesignTimelineObject(context) } else { - return getHtmlGraphicBaseline(config) + return getHtmlGraphicBaseline(context) } } diff --git a/src/tv2-common/inewsConversion/converters/ParseBody.ts b/src/tv2-common/inewsConversion/converters/ParseBody.ts index 52e72aa81..8e8b6aa61 100644 --- a/src/tv2-common/inewsConversion/converters/ParseBody.ts +++ b/src/tv2-common/inewsConversion/converters/ParseBody.ts @@ -1,5 +1,8 @@ import { - CueDefinitionFromLayout, + createCueDefinitionGraphicDesign, + createCueDefinitionGraphicSchema, + CueDefinitionFromField, + INewsFields, parseTransitionStyle, PostProcessDefinitions, TransitionStyle, @@ -186,7 +189,7 @@ export function ParseBody( segmentName: string, body: string, cues: UnparsedCue[], - fields: any, + fields: INewsFields, modified: number ): PartDefinition[] { let definitions: PartDefinition[] = [] @@ -361,11 +364,30 @@ export function ParseBody( partDefinition.cues = partDefinition.cues.filter((c) => c.type !== CueType.UNKNOWN) }) - definitions = stripRedundantCuesWhenLayoutCueIsPresent(definitions) + definitions[0]?.cues.push(...parseFieldsToCueDefinitions(fields, config)) + definitions = stripRedundantCuesWhenFieldCueIsPresent(definitions) return PostProcessDefinitions(definitions, segmentId) } +function parseFieldsToCueDefinitions(fields: INewsFields, config: TV2ShowStyleConfig): CueDefinition[] { + const cueDefinitions: CueDefinition[] = [] + if (fields.layout) { + const cueDefinitionGraphicDesign = createCueDefinitionGraphicDesign(fields.layout, config) + if (cueDefinitionGraphicDesign) { + cueDefinitions.push(cueDefinitionGraphicDesign) + } + } + + if (fields.skema) { + const cueDefinitionGraphicSchema = createCueDefinitionGraphicSchema(fields.skema, config) + if (cueDefinitionGraphicSchema) { + cueDefinitions.push(cueDefinitionGraphicSchema) + } + } + return cueDefinitions +} + export function FindTargetPair(partDefinition: PartDefinition): boolean { const index = partDefinition.cues.findIndex( (cue) => (cue.type === CueType.UNPAIRED_TARGET && cue.mergeable) || (cue.type === CueType.Telefon && !cue.graphic) @@ -697,24 +719,36 @@ export function isMinusMic(inputName: string): boolean { return /minus mic/i.test(inputName) } -export function stripRedundantCuesWhenLayoutCueIsPresent(partDefinitions: PartDefinition[]): PartDefinition[] { - const hasLayoutCue: boolean = partDefinitions.some((definition) => +export function stripRedundantCuesWhenFieldCueIsPresent(partDefinitions: PartDefinition[]): PartDefinition[] { + return stripRedundantCuesForSchema(stripRedundantCuesForDesign(partDefinitions)) +} + +function stripRedundantCuesForDesign(partDefinitions: PartDefinition[]): PartDefinition[] { + return stripRedundantCues(partDefinitions, [CueType.GraphicDesign]) +} + +function stripRedundantCuesForSchema(partDefinitions: PartDefinition[]): PartDefinition[] { + return stripRedundantCues(partDefinitions, [CueType.GraphicSchema]) +} + +function stripRedundantCues(partDefinitions: PartDefinition[], cueTypesToCheck: CueType[]): PartDefinition[] { + const hasFieldCue: boolean = partDefinitions.some((definition) => definition.cues.some((cue) => { - const cueFromLayout = cue as CueDefinitionFromLayout - return cueFromLayout.isFromLayout + const cueFromField = cue as CueDefinitionFromField + return cueTypesToCheck.includes(cue.type) && cueFromField.isFromField }) ) - if (!hasLayoutCue) { + if (!hasFieldCue) { return partDefinitions } return partDefinitions.map((definition) => { const cues = definition.cues.filter((cue) => { - if (cue.type !== CueType.GraphicDesign && cue.type !== CueType.BackgroundLoop) { + if (!cueTypesToCheck.includes(cue.type)) { return true } - return (cue as CueDefinitionFromLayout).isFromLayout + return (cue as CueDefinitionFromField).isFromField }) return { ...definition, diff --git a/src/tv2-common/inewsConversion/converters/ParseCue.ts b/src/tv2-common/inewsConversion/converters/ParseCue.ts index ad754901c..1ad29a499 100644 --- a/src/tv2-common/inewsConversion/converters/ParseCue.ts +++ b/src/tv2-common/inewsConversion/converters/ParseCue.ts @@ -1,4 +1,11 @@ -import { literal, TableConfigItemGfxDesignTemplate, TV2ShowStyleConfig, UnparsedCue } from 'tv2-common' +import { + CasparCgGfxDesignValues, + literal, + TableConfigGfxSchema, + TableConfigItemGfxDesignTemplate, + TV2ShowStyleConfig, + UnparsedCue +} from 'tv2-common' import { CueType, GraphicEngine, SourceType } from 'tv2-constants' import { getSourceDefinition, @@ -109,19 +116,33 @@ export interface CueDefinitionUnpairedPilot extends CueDefinitionBase { engineNumber?: number } -export interface CueDefinitionBackgroundLoop extends CueDefinitionBase, CueDefinitionFromLayout { +export interface CueDefinitionDveBackgroundLoop extends CueDefinitionBase, CueDefinitionFromField { + type: CueType.BackgroundLoop + target: 'DVE' + backgroundLoop: string +} + +export interface CueDefinitionFullBackgroundLoop extends CueDefinitionBase, CueDefinitionFromField { type: CueType.BackgroundLoop - target: 'FULL' | 'DVE' + target: 'FULL' backgroundLoop: string } -export interface CueDefinitionGraphicDesign extends CueDefinitionBase, CueDefinitionFromLayout { +export type CueDefinitionBackgroundLoop = CueDefinitionDveBackgroundLoop | CueDefinitionFullBackgroundLoop + +export interface CueDefinitionGraphicDesign extends CueDefinitionBase, CueDefinitionFromField { type: CueType.GraphicDesign design: string } -export interface CueDefinitionFromLayout { - isFromLayout?: boolean +export interface CueDefinitionFromField { + isFromField?: boolean +} + +export interface CueDefinitionGfxSchema extends CueDefinitionBase, CueDefinitionFromField { + type: CueType.GraphicSchema + schema: string + CasparCgDesignValues?: CasparCgGfxDesignValues[] } export interface GraphicInternal { @@ -180,6 +201,7 @@ export type CueDefinition = | CueDefinitionUnpairedPilot | CueDefinitionBackgroundLoop | CueDefinitionGraphicDesign + | CueDefinitionGfxSchema | CueDefinitionGraphic | CueDefinitionRouting | CueDefinitionPgmClean @@ -252,8 +274,6 @@ export function ParseCue(cue: UnparsedCue, config: TV2ShowStyleConfig): CueDefin return parsePgmClean(cue) } else if (/^MINUSKAM\s*=/i.test(cue[0])) { return parseMixMinus(cue) - } else if (/^DESIGN_LAYOUT=/i.test(cue[0])) { - return parseDesignLayout(cue, config) } else if (/^ROBOT\s*=/i.test(cue[0])) { return parseRobotCue(cue) } @@ -267,7 +287,11 @@ export function ParseCue(cue: UnparsedCue, config: TV2ShowStyleConfig): CueDefin function parsekg( cue: string[], config: TV2ShowStyleConfig -): CueDefinitionGraphic | CueDefinitionGraphicDesign | CueDefinitionUnpairedTarget { +): + | CueDefinitionGraphic + | CueDefinitionGraphicDesign + | CueDefinitionGfxSchema + | CueDefinitionUnpairedTarget { let kgCue: CueDefinitionGraphic = { type: CueType.Graphic, target: 'OVL', @@ -313,7 +337,7 @@ function parsekg( let textFields = cue.length - 1 if (isTime(cue[cue.length - 1])) { kgCue = { ...kgCue, ...parseTime(cue[cue.length - 1]) } - } else if (!cue[cue.length - 1].match(/;[x|\d+].[x|\d+]x/i)) { + } else if (!isAdLibTimecode(cue[cue.length - 1])) { textFields += 1 } else { kgCue.adlib = true @@ -350,6 +374,26 @@ function parsekg( }) } + const graphicsSchemaConfig = code + ? config.showStyle.GfxSchemaTemplates.find( + (template) => template.GfxSchemaTemplatesName.toUpperCase() === graphic.template.toUpperCase() + ) + : undefined + + if (graphicsSchemaConfig) { + return literal({ + type: CueType.GraphicSchema, + schema: graphicsSchemaConfig.VizTemplate, + iNewsCommand: kgCue.iNewsCommand, + start: kgCue.start, + end: kgCue.end, + adlib: kgCue.adlib, + CasparCgDesignValues: graphicsSchemaConfig.CasparCgDesignValues + ? JSON.parse(graphicsSchemaConfig.CasparCgDesignValues) + : [] + }) + } + const graphicConfig = code ? config.showStyle.GfxTemplates.find( (template) => @@ -636,12 +680,10 @@ function parseLYD(cue: string[]) { lydCue.variant = command[1] } - if (cue[1]) { - if (isTime(cue[1])) { - lydCue = { ...lydCue, ...parseTime(cue[1]) } - } else if (cue[1].match(/;[x|\d+].[x|\d+]x/i)) { - lydCue.adlib = true - } + if (cue[1] && isTime(cue[1])) { + lydCue = { ...lydCue, ...parseTime(cue[1]) } + } else if (isAdLibTimecode(cue[1])) { + lydCue.adlib = true } return lydCue @@ -896,10 +938,10 @@ export function parseTime(line: string): Pick({ type: CueType.GraphicDesign, design: designConfig.VizTemplate, - iNewsCommand: layout, - start: { - frames: 1 - }, - isFromLayout: true + iNewsCommand: '', + isFromField: true }) } @@ -926,13 +965,42 @@ function findGraphicDesignConfiguration( ) } +export function createCueDefinitionGraphicSchema( + schema: string, + config: TV2ShowStyleConfig +): CueDefinitionGfxSchema | undefined { + const schemaConfiguration = findGraphicSchemaConfiguration(config, schema) + if (!schemaConfiguration) { + return undefined + } + + return literal({ + type: CueType.GraphicSchema, + schema: schemaConfiguration.VizTemplate, + iNewsCommand: '', + isFromField: true, + CasparCgDesignValues: schemaConfiguration.CasparCgDesignValues + ? JSON.parse(schemaConfiguration.CasparCgDesignValues) + : [] + }) +} + +function findGraphicSchemaConfiguration(config: TV2ShowStyleConfig, schema: string): TableConfigGfxSchema | undefined { + return config.showStyle.GfxSchemaTemplates.find( + (template) => template.INewsSkemaColumn && template.INewsSkemaColumn.toUpperCase() === schema.toUpperCase() + ) +} + function parseRobotCue(cue: string[]): CueDefinitionRobotCamera { const presetIdentifier: number = Number(cue[0].match(/\d+/)) - const time: Pick = cue[1] ? parseTime(cue[1]) : { start: { seconds: 0 } } + const isAdLib = isAdLibTimecode(cue[1]) + const time: Pick = + cue[1] && !isAdLib ? parseTime(cue[1]) : { start: { seconds: 0 } } return { type: CueType.RobotCamera, iNewsCommand: 'RobotCamera', presetIdentifier, + adlib: isAdLib, ...time } } @@ -959,3 +1027,7 @@ export function UnpairedPilotToGraphic( adlib: targetCue?.adlib ?? pilotCue.adlib }) } + +function isAdLibTimecode(timecode: string | undefined): boolean { + return !!timecode && /;[x|\d+].[x|\d+]x/i.test(timecode) +} diff --git a/src/tv2-common/inewsConversion/converters/__tests__/body-parser.spec.ts b/src/tv2-common/inewsConversion/converters/__tests__/body-parser.spec.ts index 6521f5934..235204c05 100644 --- a/src/tv2-common/inewsConversion/converters/__tests__/body-parser.spec.ts +++ b/src/tv2-common/inewsConversion/converters/__tests__/body-parser.spec.ts @@ -1,9 +1,9 @@ import { - CueDefinitionBackgroundLoop, CueDefinitionGraphicDesign, getTransitionProperties, + INewsFields, PartdefinitionTypes, - stripRedundantCuesWhenLayoutCueIsPresent, + stripRedundantCuesWhenFieldCueIsPresent, TransitionStyle, UnparsedCue } from 'tv2-common' @@ -41,7 +41,7 @@ import { GraphicPilot } from '../ParseCue' -const fields = {} +const emptyFields = {} const unparsedUnknown: UnparsedCue = ['Some invalid cue'] @@ -200,7 +200,7 @@ describe('Body parser', () => { unparsedJingle3 ] - const result = ParseBody(config, '00000000001', 'test-segment', body1, cues1, fields, 0) + const result = ParseBody(config, '00000000001', 'test-segment', body1, cues1, emptyFields, 0) expect(stripExternalId(result)).toEqual( literal([ literal({ @@ -247,7 +247,7 @@ describe('Body parser', () => { '\r\n

\r\n

Thid id thr trext for the next DVE

\r\n

***LIVE***

\r\n

\r\n

\r\n

\r\n

Spib her

\r\n

\r\n\r\n

Script here

\r\n' const cues2 = [unparsedUnknown, unparsedGrafik1, null, unparsedGrafik3, unparsedEkstern1] - const result = ParseBody(config, '00000000001', 'test-segment', body2, cues2, fields, 0) + const result = ParseBody(config, '00000000001', 'test-segment', body2, cues2, emptyFields, 0) expect(stripExternalId(result)).toEqual( literal([ literal({ @@ -282,7 +282,7 @@ describe('Body parser', () => { '\r\n

\r\n

Thid id thr trext for the next DVE

\r\n

***LIVE***

\r\n

\r\n

\r\n

\r\n

Spib her

\r\n

\r\n\r\n

Script here

\r\n' const cues2 = [['DVE=MORBARN', 'INP1=Kam 1', 'INP2=Kam 2', 'BYNAVN=Live/Odense'], unparsedEkstern1, unparsedGrafik1] - const result = ParseBody(config, '00000000001', 'test-segment', body2, cues2, fields, 0) + const result = ParseBody(config, '00000000001', 'test-segment', body2, cues2, emptyFields, 0) expect(stripExternalId(result)).toEqual( literal([ literal({ @@ -339,7 +339,7 @@ describe('Body parser', () => { unparsedJingle3 ] - const result = ParseBody(config, '00000000001', 'test-segment', body3, cues3, fields, 0) + const result = ParseBody(config, '00000000001', 'test-segment', body3, cues3, emptyFields, 0) expect(stripExternalId(result)).toEqual( literal([ literal({ @@ -398,7 +398,7 @@ describe('Body parser', () => { const body4 = "\r\n

\r\n

\r\n

CAMERA 1

\r\n

Her står em masse tekst

\r\n" const cues4 = [unparsedUnknown] - const result = ParseBody(config, '00000000001', 'test-segment', body4, cues4, fields, 0) + const result = ParseBody(config, '00000000001', 'test-segment', body4, cues4, emptyFields, 0) expect(stripExternalId(result)).toEqual( literal([ literal({ @@ -408,7 +408,7 @@ describe('Body parser', () => { script: 'Her står em masse tekst\n', sourceDefinition: { sourceType: SourceType.KAM, id: '1', raw: 'CAMERA 1', minusMic: false, name: 'KAM 1' }, externalId: '', - fields, + fields: emptyFields, modified: 0, storyName: 'test-segment', segmentExternalId: '00000000001' @@ -421,7 +421,7 @@ describe('Body parser', () => { const body5 = '\r\n

\r\n

\r\n

KAM 1

\r\n

--tlftopt-><--

\r\n

\r\n

\r\n

************ 100%GRAFIK ***********

\r\n

\r\n

\r\n

\r\n' const cues5 = [unparsedUnknown, unparsedGrafik1, unparsedGrafik2, unparsedGrafik3, unparsedEkstern1] - const result = ParseBody(config, '00000000001', 'test-segment', body5, cues5, fields, 0) + const result = ParseBody(config, '00000000001', 'test-segment', body5, cues5, emptyFields, 0) expect(stripExternalId(result)).toEqual( literal([ literal({ @@ -467,7 +467,7 @@ describe('Body parser', () => { const body6 = '\r\n

\r\n

\r\n

KAM 1

\r\n

--værter-><--

\r\n' const cues6 = [unparsedUnknown] - const result = ParseBody(config, '00000000001', 'test-segment', body6, cues6, fields, 0) + const result = ParseBody(config, '00000000001', 'test-segment', body6, cues6, emptyFields, 0) expect(stripExternalId(result)).toEqual( literal([ literal({ @@ -490,7 +490,7 @@ describe('Body parser', () => { const body7 = '\r\n

\r\n

\r\n

\r\n

***ATTACK***

\r\n

----ss3 Sport LOOP-><-

\r\n

---AR DIGI OUT-><---

\r\n

---bundter herunder--->

\r\n

\r\n

\r\n

\r\n

SLUTORD:... wauw

\r\n

\r\n

KAM 4

\r\n

NEDLÆG

\r\n

Long script. Long script. Long script. Long script. Long script. Long script. Long script. Long script. Long script. Long script. Long script. Long script.

\r\n

\r\n' const cues7 = [unparsedUnknown, unparsedGrafik1, unparsedGrafik2, unparsedGrafik3] - const result = ParseBody(config, '00000000001', 'test-segment', body7, cues7, fields, 0) + const result = ParseBody(config, '00000000001', 'test-segment', body7, cues7, emptyFields, 0) expect(stripExternalId(result)).toEqual( literal([ literal({ @@ -526,7 +526,7 @@ describe('Body parser', () => { const body8 = '\r\n

COMMENT OUTSIDE!!

\r\n

KAM 2

\r\n

KADA

\r\n

\r\n

\r\n

Efter "BYNAVN=" og efter "#kg direkte"

\r\n

\r\n

Kilde til optagelse på select-feed.

\r\n

\r\n

Some script

\r\n

\r\n

***LIVE***

\r\n

Some script

\r\n

\r\n

\r\n

- Bullet 1?

\r\n

\r\n

- Bullet 2?

\r\n

\r\n

- Bullet 3?

\r\n

\r\n' const cues8 = [unparsedUnknown, unparsedGrafik1, unparsedGrafik2, unparsedGrafik3, unparsedEkstern1] - const result = ParseBody(config, '00000000001', 'test-segment', body8, cues8, fields, 0) + const result = ParseBody(config, '00000000001', 'test-segment', body8, cues8, emptyFields, 0) expect(stripExternalId(result)).toEqual( literal([ literal({ @@ -561,7 +561,7 @@ describe('Body parser', () => { const body9 = '\r\n

COMMENT OUTSIDE!!

\r\n

KAM 2

\r\n

\r\n

\r\n

Efter "BYNAVN=" og efter "#kg direkte"

\r\n

\r\n

Kilde til optagelse på select-feed.

\r\n

\r\n

Some script.

\r\n

\r\n

Some more script with "a quote"

\r\n

\r\n

Yet more script, this time it\'s a question?

\r\n

\r\n

***LIVE***

\r\n

More commentary

\r\n

\r\n

\r\n

Danmark?

\r\n

\r\n

Grønland en "absurd diskussion"?

\r\n

\r\n

\r\n' const cues9 = [unparsedUnknown, unparsedGrafik1, unparsedGrafik2, unparsedGrafik3, unparsedEkstern1] - const result = ParseBody(config, '00000000001', 'test-segment', body9, cues9, fields, 0) + const result = ParseBody(config, '00000000001', 'test-segment', body9, cues9, emptyFields, 0) expect(stripExternalId(result)).toEqual( literal([ literal({ @@ -596,7 +596,7 @@ describe('Body parser', () => { const body10 = '\r\n

KAM 2

\r\n

\r\n

\r\n

Efter "BYNAVN=" og efter "#kg direkte"

\r\n

\r\n

Kilde til optagelse på select-feed.

\r\n

\r\n

Question?

\r\n

\r\n

Question, but in PI tags?

\r\n

\r\n

USA og Danmark?

\r\n

\r\n

***LIVE***

\r\n

Comment

\r\n

\r\n

This line should be ignored

\r\n

\r\n

Also this one?

\r\n

\r\n

More comments

\r\n

Even more?

\r\n

\r\n' const cues10 = [unparsedUnknown, unparsedGrafik1, unparsedGrafik2, unparsedGrafik3, unparsedEkstern1] - const result = ParseBody(config, '00000000001', 'test-segment', body10, cues10, fields, 0) + const result = ParseBody(config, '00000000001', 'test-segment', body10, cues10, emptyFields, 0) expect(stripExternalId(result)).toEqual( literal([ literal({ @@ -631,7 +631,7 @@ describe('Body parser', () => { const body11 = '\r\n

KAM 1

\r\n

\r\n

Some script.

\r\n

\r\n

***VO***

\r\n

\r\n

SB: Say this over this clip (10 sek)

\r\n

\r\n

More script.

\r\n

\r\n

Even more

\r\n

\r\n

More script again.

\r\n

\r\n

Couple of comments

\r\n

Should be ignored

\r\n

\r\n' const cues11 = [unparsedUnknown, unparsedGrafik1, unparsedGrafik2] - const result = ParseBody(config, '00000000001', 'test-segment', body11, cues11, fields, 0) + const result = ParseBody(config, '00000000001', 'test-segment', body11, cues11, emptyFields, 0) expect(stripExternalId(result)).toEqual( literal([ literal({ @@ -665,7 +665,7 @@ describe('Body parser', () => { const body11 = '\r\n

KAM 1

\r\n

\r\n

Some script.

\r\n

\r\n

***VOV***

\r\n

\r\n

SB: Say this over this clip (10 sek)

\r\n

\r\n

More script.

\r\n

\r\n

Even more

\r\n

\r\n

More script again.

\r\n

\r\n

Couple of comments

\r\n

Should be ignored

\r\n

\r\n' const cues11 = [unparsedUnknown, unparsedGrafik1, unparsedGrafik2] - const result = ParseBody(config, '00000000001', 'test-segment', body11, cues11, fields, 0) + const result = ParseBody(config, '00000000001', 'test-segment', body11, cues11, emptyFields, 0) expect(stripExternalId(result)).toEqual( literal([ literal({ @@ -699,7 +699,7 @@ describe('Body parser', () => { const body12 = '\r\n

This is an interview.

\r\n

\r\n

\r\n

KAM 3

\r\n

\r\n

<-- Comment about this

\r\n

\r\n

Also about this!

\r\n

\r\n

Remember:

\r\n

\r\n

Here is our correspondant.

\r\n

\r\n

What\'s going on over there?

\r\n

\r\n

***LIVE***

\r\n

There is a graphic in this part

\r\n

.

\r\n

\r\n

Ask a question?

\r\n

\r\n

Ask another?

\r\n

\r\n

What\'s the reaction?

\r\n

\r\n

\r\n

\r\n' const cues12 = [unparsedUnknown, unparsedGrafik1, unparsedGrafik2] - const result = ParseBody(config, '00000000001', 'test-segment', body12, cues12, fields, 0) + const result = ParseBody(config, '00000000001', 'test-segment', body12, cues12, emptyFields, 0) expect(stripExternalId(result)).toEqual( literal([ literal({ @@ -722,7 +722,7 @@ describe('Body parser', () => { const body13 = '\r\n

\r\n

\r\n

\r\n

\r\n

\r\n

\r\n

\r\n

\r\n

\r\n

\r\n

\r\n

\r\n

\r\n

\r\n

\r\n' const cues13 = [unparsedUnknown] - const result = ParseBody(config, '00000000001', 'test-segment', body13, cues13, fields, 0) + const result = ParseBody(config, '00000000001', 'test-segment', body13, cues13, emptyFields, 0) expect(stripExternalId(result)).toEqual(literal([])) }) @@ -737,7 +737,7 @@ describe('Body parser', () => { unparsedEkstern1, unparsedEkstern2 ] - const result = ParseBody(config, '00000000001', 'test-segment', body14, cues14, fields, 0) + const result = ParseBody(config, '00000000001', 'test-segment', body14, cues14, emptyFields, 0) expect(stripExternalId(result)).toEqual( literal([ literal({ @@ -783,7 +783,7 @@ describe('Body parser', () => { const body15 = '\r\n

---JINGLE sport grafisk intro---><----

\r\n

\r\n

---AUDIO til grafisk intro , fortsætter under teasere---><----

\r\n

\r\n' const cues15 = [unparsedUnknown, unparsedGrafik1] - const result = ParseBody(config, '00000000001', 'INTRO', body15, cues15, fields, 0) + const result = ParseBody(config, '00000000001', 'INTRO', body15, cues15, emptyFields, 0) expect(stripExternalId(result)).toEqual( literal([ literal({ @@ -813,7 +813,7 @@ describe('Body parser', () => { unparsedJingle2, unparsedJingle3 ] - const result = ParseBody(config, '00000000001', 'test-segment', body16, cues16, fields, 0) + const result = ParseBody(config, '00000000001', 'test-segment', body16, cues16, emptyFields, 0) expect(stripExternalId(result)).toEqual( literal([ literal({ @@ -823,7 +823,7 @@ describe('Body parser', () => { script: 'Hallo, I wnat to tell you......\n', sourceDefinition: SOURCE_DEFINITION_KAM_2, cues: [cueGrafik1], - fields, + fields: emptyFields, modified: 0, storyName: 'test-segment', segmentExternalId: '00000000001' @@ -834,7 +834,7 @@ describe('Body parser', () => { rawType: 'SERVER', script: '', cues: [cueGrafik2, cueGrafik3, cueJingle1, cueJingle2, cueJingle3], - fields, + fields: emptyFields, modified: 0, storyName: 'test-segment', segmentExternalId: '00000000001' @@ -846,7 +846,7 @@ describe('Body parser', () => { script: '', sourceDefinition: SOURCE_DEFINITION_KAM_2, cues: [], - fields, + fields: emptyFields, modified: 0, storyName: 'test-segment', endWords: '', @@ -859,7 +859,7 @@ describe('Body parser', () => { script: '', sourceDefinition: SOURCE_DEFINITION_KAM_2, cues: [], - fields, + fields: emptyFields, modified: 0, storyName: 'test-segment', segmentExternalId: '00000000001' @@ -884,7 +884,7 @@ describe('Body parser', () => { unparsedTelefon1, unparsedTelefon2 ] - const result = ParseBody(config, '00000000001', 'test-segment', body17, cues17, fields, 0) + const result = ParseBody(config, '00000000001', 'test-segment', body17, cues17, emptyFields, 0) expect(stripExternalId(result)).toEqual( literal([ literal({ @@ -894,7 +894,7 @@ describe('Body parser', () => { cues: [cueEkstern1], title: 'LIVE 1', script: '', - fields, + fields: emptyFields, modified: 0, storyName: 'test-segment', segmentExternalId: '00000000001' @@ -906,7 +906,7 @@ describe('Body parser', () => { cues: [cueEkstern2], title: 'LIVE 2', script: '', - fields, + fields: emptyFields, modified: 0, storyName: 'test-segment', segmentExternalId: '00000000001' @@ -918,7 +918,7 @@ describe('Body parser', () => { rawType: 'KAM 1', cues: [], script: 'Single line of script\n', - fields, + fields: emptyFields, modified: 0, storyName: 'test-segment', segmentExternalId: '00000000001' @@ -929,7 +929,7 @@ describe('Body parser', () => { rawType: 'SERVER', cues: [cueGrafik2, cueGrafik3, cueJingle1, cueJingle2, cueJingle3], script: '', - fields, + fields: emptyFields, modified: 0, storyName: 'test-segment', segmentExternalId: '00000000001' @@ -940,7 +940,7 @@ describe('Body parser', () => { rawType: '', cues: [cueTelefon1], script: '', - fields, + fields: emptyFields, modified: 0, storyName: 'test-segment', segmentExternalId: '00000000001' @@ -951,7 +951,7 @@ describe('Body parser', () => { rawType: '', cues: [cueTelefon2], script: '', - fields, + fields: emptyFields, modified: 0, storyName: 'test-segment', endWords: 'Skarpere regler.', @@ -964,7 +964,7 @@ describe('Body parser', () => { rawType: 'KAM 2', cues: [], script: 'And some script.\n', - fields, + fields: emptyFields, modified: 0, storyName: 'test-segment', segmentExternalId: '00000000001' @@ -977,7 +977,7 @@ describe('Body parser', () => { const body18 = '\r\n

***VO EFFEKT 0***

\r\n

\r\n

With some script.

\r\n

\r\n' const cues18 = [unparsedGrafik1] - const result = ParseBody(config, '00000000001', 'test-segment', body18, cues18, fields, 0) + const result = ParseBody(config, '00000000001', 'test-segment', body18, cues18, emptyFields, 0) expect(stripExternalId(result)).toEqual( literal([ literal({ @@ -987,7 +987,7 @@ describe('Body parser', () => { rawType: 'VO', cues: [cueGrafik1], script: 'With some script.\n', - fields, + fields: emptyFields, modified: 0, storyName: 'test-segment', segmentExternalId: '00000000001' @@ -1000,7 +1000,7 @@ describe('Body parser', () => { const body19 = '\r\n

\r\n

KAM 1 EFFEKT 1

\r\n

Dette er takst

\r\n

\r\n

SERVER

\r\n

\r\n

\r\n

STORT BILLEDE AF STUDIE

\r\n

\r\n' const cues19 = [unparsedGrafik1, unparsedGrafik2] - const result = ParseBody(config, '00000000001', 'test-segment', body19, cues19, fields, 0) + const result = ParseBody(config, '00000000001', 'test-segment', body19, cues19, emptyFields, 0) expect(stripExternalId(result)).toEqual( literal([ literal({ @@ -1011,7 +1011,7 @@ describe('Body parser', () => { rawType: 'KAM 1', cues: [], script: 'Dette er takst\n', - fields, + fields: emptyFields, modified: 0, storyName: 'test-segment', segmentExternalId: '00000000001' @@ -1022,7 +1022,7 @@ describe('Body parser', () => { rawType: 'SERVER', cues: [cueGrafik1, cueGrafik2], script: 'STORT BILLEDE AF STUDIE\n', - fields, + fields: emptyFields, modified: 0, storyName: 'test-segment', segmentExternalId: '00000000001' @@ -1035,7 +1035,7 @@ describe('Body parser', () => { const body20 = '\r\n

OBS: der skal være 2 primære templates mellem 2 breakere

\r\n

K2 NBA18_LEAD_OUT

\r\n

\r\n

\r\n

\r\n

\r\n' const cues20 = [unparsedJingle1] - const result = ParseBody(config, '00000000001', 'test-segment', body20, cues20, fields, 0) + const result = ParseBody(config, '00000000001', 'test-segment', body20, cues20, emptyFields, 0) expect(stripExternalId(result)).toEqual( literal([ literal({ @@ -1045,7 +1045,7 @@ describe('Body parser', () => { cues: [cueJingle1], title: '1', script: '', - fields, + fields: emptyFields, modified: 0, storyName: 'test-segment', segmentExternalId: '00000000001' @@ -1066,7 +1066,7 @@ describe('Body parser', () => { ['kg bund TEXT MORETEXT', 'some@email.fakeTLD', ';x.xx'], ['SS=3-NYH-19-LOOP', ';0.01'] ] - const result = ParseBody(config, '00000000001', 'test-segment', body21, cues21, fields, 0) + const result = ParseBody(config, '00000000001', 'test-segment', body21, cues21, emptyFields, 0) expect(stripExternalId(result)).toEqual( literal([ literal({ @@ -1076,7 +1076,7 @@ describe('Body parser', () => { rawType: 'KAM 2', cues: [], script: 'Hallo, I wnat to tell you......\nHEREEEELLLLOOOK\nYES\n', - fields, + fields: emptyFields, modified: 0, storyName: 'test-segment', segmentExternalId: '00000000001' @@ -1132,7 +1132,7 @@ describe('Body parser', () => { }) ]), script: '', - fields, + fields: emptyFields, modified: 0, storyName: 'test-segment', segmentExternalId: '00000000001' @@ -1144,7 +1144,7 @@ describe('Body parser', () => { rawType: 'KAM 1', cues: [], script: '', - fields, + fields: emptyFields, modified: 0, storyName: 'test-segment', segmentExternalId: '00000000001' @@ -1156,7 +1156,7 @@ describe('Body parser', () => { rawType: 'KAM 1', cues: [], script: '', - fields, + fields: emptyFields, modified: 0, storyName: 'test-segment', endWords: '', @@ -1169,7 +1169,7 @@ describe('Body parser', () => { rawType: 'KAM 1', cues: [], script: '', - fields, + fields: emptyFields, modified: 0, storyName: 'test-segment', segmentExternalId: '00000000001' @@ -1186,7 +1186,7 @@ describe('Body parser', () => { ['kg tlfdirekte Odense', ';0.00-S'], ['kg tlftoptlive', 'TEXT MORETEXT', 'place', ';0.00-S'] ] - const result = ParseBody(config, '00000000001', 'test-segment', body22, cues22, fields, 0) + const result = ParseBody(config, '00000000001', 'test-segment', body22, cues22, emptyFields, 0) expect(stripExternalId(result)).toEqual( literal([ literal({ @@ -1196,7 +1196,7 @@ describe('Body parser', () => { rawType: 'KAM 1', cues: [], script: 'Skriv spib her\n', - fields, + fields: emptyFields, modified: 0, storyName: 'test-segment', segmentExternalId: '00000000001' @@ -1261,7 +1261,7 @@ describe('Body parser', () => { }) ], script: '', - fields, + fields: emptyFields, modified: 0, storyName: 'test-segment', segmentExternalId: '00000000001' @@ -1278,7 +1278,7 @@ describe('Body parser', () => { ['kg tlfdirekte Odense', ';0.00-S'], ['kg tlftoptlive', 'TEXT MORETEXT', 'Place', ';0.00-S'] ] - const result = ParseBody(config, '00000000001', 'test-segment', body22, cues22, fields, 0) + const result = ParseBody(config, '00000000001', 'test-segment', body22, cues22, emptyFields, 0) expect(stripExternalId(result)).toEqual( literal([ literal({ @@ -1292,7 +1292,7 @@ describe('Body parser', () => { rawType: 'KAM 1', cues: [], script: 'Skriv spib her\n', - fields, + fields: emptyFields, modified: 0, storyName: 'test-segment', segmentExternalId: '00000000001' @@ -1357,7 +1357,7 @@ describe('Body parser', () => { }) ], script: '', - fields, + fields: emptyFields, modified: 0, storyName: 'test-segment', segmentExternalId: '00000000001' @@ -1370,7 +1370,7 @@ describe('Body parser', () => { const body18 = '\r\n

***VOSB EFFEKT 0***

\r\n

\r\n

Some script.

\r\n

\r\n' const cues18 = [unparsedGrafik1] - const result = ParseBody(config, '00000000001', 'test-segment', body18, cues18, fields, 0) + const result = ParseBody(config, '00000000001', 'test-segment', body18, cues18, emptyFields, 0) expect(stripExternalId(result)).toEqual( literal([ literal({ @@ -1380,7 +1380,7 @@ describe('Body parser', () => { rawType: 'VOSB', cues: [cueGrafik1], script: 'Some script.\n', - fields, + fields: emptyFields, modified: 0, storyName: 'test-segment', segmentExternalId: '00000000001' @@ -1393,7 +1393,7 @@ describe('Body parser', () => { const body18 = '\r\n

***VOSB EFFEKT 0***

\r\n

\r\n

Some script here, possibly a note to the presenter

\r\n

Some script.

\r\n

\r\n' const cues18 = [unparsedGrafik1] - const result = ParseBody(config, '00000000001', 'test-segment', body18, cues18, fields, 0) + const result = ParseBody(config, '00000000001', 'test-segment', body18, cues18, emptyFields, 0) expect(stripExternalId(result)).toEqual( literal([ literal({ @@ -1403,7 +1403,7 @@ describe('Body parser', () => { rawType: 'VOSB', cues: [cueGrafik1], script: 'Some script.\n', - fields, + fields: emptyFields, modified: 0, storyName: 'test-segment', segmentExternalId: '00000000001' @@ -1429,7 +1429,7 @@ describe('Body parser', () => { 'TELEFON/KORT//LIVE_KABUL' ] ] - const result = ParseBody(config, '00000000001', 'test-segment', body26, cues26, fields, 0) + const result = ParseBody(config, '00000000001', 'test-segment', body26, cues26, emptyFields, 0) expect(stripExternalId(result)).toEqual([ literal({ externalId: '', @@ -1452,7 +1452,7 @@ describe('Body parser', () => { iNewsCommand: 'kg' }) ], - fields, + fields: emptyFields, modified: 0, script: '', storyName: 'test-segment', @@ -1493,7 +1493,7 @@ describe('Body parser', () => { }) ], title: 'TELEFON/KORT//LIVE_KABUL', - fields, + fields: emptyFields, modified: 0, script: '', storyName: 'test-segment', @@ -1506,7 +1506,7 @@ describe('Body parser', () => { const body27 = '\r\n

\r\n

\r\n

EVS 1

\r\n

\r\n

\r\n

\r\n

\r\n

\r\n

\r\n

\r\n

Skriv din spib her

\r\n

\r\n' const cues27 = [unparsedGrafik1, unparsedGrafik2, unparsedGrafik3] - const result = ParseBody(config, '00000000001', 'test-segment', body27, cues27, fields, 0) + const result = ParseBody(config, '00000000001', 'test-segment', body27, cues27, emptyFields, 0) expect(stripExternalId(result)).toEqual([ literal({ externalId: '', @@ -1520,7 +1520,7 @@ describe('Body parser', () => { vo: false }, cues: [cueGrafik1, cueGrafik2, cueGrafik3], - fields, + fields: emptyFields, modified: 0, script: 'Skriv din spib her\n', storyName: 'test-segment', @@ -1533,7 +1533,7 @@ describe('Body parser', () => { const body27 = '\r\n

\r\n

\r\n

EVS1VOV

\r\n

\r\n

\r\n

\r\n

\r\n

\r\n

\r\n

\r\n

Skriv din spib her

\r\n

\r\n' const cues27 = [unparsedGrafik1, unparsedGrafik2, unparsedGrafik3] - const result = ParseBody(config, '00000000001', 'test-segment', body27, cues27, fields, 0) + const result = ParseBody(config, '00000000001', 'test-segment', body27, cues27, emptyFields, 0) expect(stripExternalId(result)).toEqual([ literal({ externalId: '', @@ -1547,7 +1547,7 @@ describe('Body parser', () => { vo: true }, cues: [cueGrafik1, cueGrafik2, cueGrafik3], - fields, + fields: emptyFields, modified: 0, script: 'Skriv din spib her\n', storyName: 'test-segment', @@ -1559,7 +1559,7 @@ describe('Body parser', () => { test('test 27c: accepts spaces in EVS VO red text', () => { const body27 = '\r\n

EVS 1 VO

\r\n

EVS 2VO

\r\n

EVS3VO

\r\n

EVS4 VO

\r\n' - const result = ParseBody(config, '00000000001', 'test-segment', body27, [], fields, 0) + const result = ParseBody(config, '00000000001', 'test-segment', body27, [], emptyFields, 0) expect(stripExternalId(result)).toEqual([ literal({ externalId: '', @@ -1573,7 +1573,7 @@ describe('Body parser', () => { vo: true }, cues: [], - fields, + fields: emptyFields, modified: 0, script: '', storyName: 'test-segment', @@ -1591,7 +1591,7 @@ describe('Body parser', () => { vo: true }, cues: [], - fields, + fields: emptyFields, modified: 0, script: '', storyName: 'test-segment', @@ -1609,7 +1609,7 @@ describe('Body parser', () => { vo: true }, cues: [], - fields, + fields: emptyFields, modified: 0, script: '', storyName: 'test-segment', @@ -1627,7 +1627,7 @@ describe('Body parser', () => { vo: true }, cues: [], - fields, + fields: emptyFields, modified: 0, script: '', storyName: 'test-segment', @@ -1646,7 +1646,7 @@ describe('Body parser', () => { ['DVE=SOMMERFUGL', 'INP1=KAM 1', 'INP2=LIVE 2', 'BYNAVN=Rodovre'], ['EKSTERN=LIVE 2'] ] - const result = ParseBody(config, '00000000001', 'test-segment', body28, cues28, fields, 0) + const result = ParseBody(config, '00000000001', 'test-segment', body28, cues28, emptyFields, 0) expect(stripExternalId(result)).toEqual([ literal({ externalId: '', @@ -1684,7 +1684,7 @@ describe('Body parser', () => { }) ], script: '', - fields, + fields: emptyFields, modified: 0, storyName: 'test-segment', endWords: 'bare mega fedt', @@ -1708,7 +1708,7 @@ describe('Body parser', () => { ], title: 'SOMMERFUGL', script: '', - fields, + fields: emptyFields, modified: 0, storyName: 'test-segment', segmentExternalId: '00000000001' @@ -1720,7 +1720,7 @@ describe('Body parser', () => { cues: [cueEkstern2], title: 'LIVE 2', script: 'Some Script here\n', - fields, + fields: emptyFields, modified: 0, storyName: 'test-segment', segmentExternalId: '00000000001' @@ -1744,7 +1744,7 @@ describe('Body parser', () => { unparsedTelefon1, unparsedTelefon2 ] - const result = ParseBody(config, '00000000001', 'test-segment', body29, cues29, fields, 0) + const result = ParseBody(config, '00000000001', 'test-segment', body29, cues29, emptyFields, 0) expect(stripExternalId(result)).toEqual( literal([ literal({ @@ -1754,7 +1754,7 @@ describe('Body parser', () => { cues: [cueEkstern1], title: 'LIVE 1', script: '', - fields, + fields: emptyFields, modified: 0, storyName: 'test-segment', segmentExternalId: '00000000001' @@ -1766,7 +1766,7 @@ describe('Body parser', () => { cues: [cueEkstern2], title: 'LIVE 2', script: '', - fields, + fields: emptyFields, modified: 0, storyName: 'test-segment', segmentExternalId: '00000000001' @@ -1778,7 +1778,7 @@ describe('Body parser', () => { rawType: 'KAM 1', cues: [], script: 'Some script.\n', - fields, + fields: emptyFields, modified: 0, storyName: 'test-segment', segmentExternalId: '00000000001' @@ -1789,7 +1789,7 @@ describe('Body parser', () => { rawType: 'SERVER', cues: [cueGrafik2, cueGrafik3, cueJingle1, cueJingle2, cueJingle3], script: '', - fields, + fields: emptyFields, modified: 0, storyName: 'test-segment', segmentExternalId: '00000000001' @@ -1800,7 +1800,7 @@ describe('Body parser', () => { rawType: '', cues: [cueTelefon1], script: '', - fields, + fields: emptyFields, modified: 0, storyName: 'test-segment', segmentExternalId: '00000000001' @@ -1811,7 +1811,7 @@ describe('Body parser', () => { rawType: '', cues: [cueTelefon2], script: '', - fields, + fields: emptyFields, modified: 0, storyName: 'test-segment', endWords: 'Skarpere regler.', @@ -1824,7 +1824,7 @@ describe('Body parser', () => { rawType: 'KAM 2', cues: [], script: 'Some script.\n', - fields, + fields: emptyFields, modified: 0, storyName: 'test-segment', segmentExternalId: '00000000001' @@ -1843,7 +1843,7 @@ describe('Body parser', () => { ['TEMA=sport_kortnyt', 'TEMA SPORT KORT NYT', ';0.00-S'], ['#kg bund TEXT MORETEXT', 'Triatlet', ';0.00'] ] - const result = ParseBody(config, '00000000001', 'test-segment', body30, cues30, fields, 0) + const result = ParseBody(config, '00000000001', 'test-segment', body30, cues30, emptyFields, 0) expect(stripExternalId(result)).toEqual([ literal({ externalId: '', @@ -1863,7 +1863,7 @@ describe('Body parser', () => { ], title: 'SOMMERFUGL', script: '', - fields, + fields: emptyFields, modified: 0, storyName: 'test-segment', segmentExternalId: '00000000001' @@ -1875,7 +1875,7 @@ describe('Body parser', () => { cues: [cueEkstern2], title: 'LIVE 2', script: 'And some script\n', - fields, + fields: emptyFields, modified: 0, storyName: 'test-segment', segmentExternalId: '00000000001' @@ -1916,7 +1916,7 @@ describe('Body parser', () => { }) ], script: 'Server script\n', - fields, + fields: emptyFields, modified: 0, storyName: 'test-segment', endWords: 'bare mega fedt', @@ -1935,7 +1935,7 @@ describe('Body parser', () => { ['DVE=SOMMERFUGL', 'INP1=KAM 1', 'INP2=LIVE 2', 'BYNAVN=Rodovre'], ['EKSTERN=LIVE 2'] ] - const result = ParseBody(config, '00000000001', 'test-segment', body31, cues31, fields, 0) + const result = ParseBody(config, '00000000001', 'test-segment', body31, cues31, emptyFields, 0) expect(stripExternalId(result)).toEqual([ literal({ externalId: '', @@ -1974,7 +1974,7 @@ describe('Body parser', () => { }) ], script: '', - fields, + fields: emptyFields, modified: 0, storyName: 'test-segment', endWords: 'bare mega fedt', @@ -1998,7 +1998,7 @@ describe('Body parser', () => { ], title: 'SOMMERFUGL', script: '', - fields, + fields: emptyFields, modified: 0, storyName: 'test-segment', segmentExternalId: '00000000001' @@ -2010,7 +2010,7 @@ describe('Body parser', () => { cues: [cueEkstern2], script: 'Some script\n', title: 'LIVE 2', - fields, + fields: emptyFields, modified: 0, storyName: 'test-segment', segmentExternalId: '00000000001' @@ -2021,7 +2021,7 @@ describe('Body parser', () => { rawType: 'SERVER', cues: [], script: '', - fields, + fields: emptyFields, modified: 0, storyName: 'test-segment', segmentExternalId: '00000000001' @@ -2032,7 +2032,7 @@ describe('Body parser', () => { test('test 32', () => { const body32 = '\r\n

\r\n

KAM1

\r\n' const cues32: string[][] = [] - const result = ParseBody(config, '00000000001', 'test-segment', body32, cues32, fields, 0) + const result = ParseBody(config, '00000000001', 'test-segment', body32, cues32, emptyFields, 0) expect(stripExternalId(result)).toEqual([ literal({ externalId: '', @@ -2041,7 +2041,7 @@ describe('Body parser', () => { rawType: 'KAM1', cues: [], script: '', - fields, + fields: emptyFields, modified: 0, storyName: 'test-segment', segmentExternalId: '00000000001' @@ -2063,7 +2063,7 @@ describe('Body parser', () => { 'TEMA_SPORT_KORTNYT/Mosart=L|00:02|O' ] ] - const result = ParseBody(config, '00000000001', 'test-segment', body33, cues33, fields, 0) + const result = ParseBody(config, '00000000001', 'test-segment', body33, cues33, emptyFields, 0) expect(stripExternalId(result)).toEqual([ literal({ externalId: '', @@ -2111,7 +2111,7 @@ describe('Body parser', () => { ], title: 'SN_breaker_kortnyt_start', script: '', - fields, + fields: emptyFields, modified: 0, storyName: 'test-segment', segmentExternalId: '00000000001' @@ -2131,7 +2131,7 @@ describe('Body parser', () => { 'HojreVideo/12-12-2019/MOSART=L|00:00|O' ] ] - const result = ParseBody(config, '00000000001', 'test-segment', body34, cues34, fields, 0) + const result = ParseBody(config, '00000000001', 'test-segment', body34, cues34, emptyFields, 0) expect(stripExternalId(result)).toEqual([ literal({ externalId: '', @@ -2173,7 +2173,7 @@ describe('Body parser', () => { }) ], script: '', - fields, + fields: emptyFields, modified: 0, storyName: 'test-segment', segmentExternalId: '00000000001' @@ -2196,7 +2196,7 @@ describe('Body parser', () => { 'PROFILE/MEST BRUGTE STARTERE I NBA/08-12-2019' ] ] - const result = ParseBody(config, '00000000001', 'test-segment', body35, cues35, fields, 0) + const result = ParseBody(config, '00000000001', 'test-segment', body35, cues35, emptyFields, 0) expect(stripExternalId(result)).toEqual([ literal({ type: PartType.Grafik, @@ -2232,7 +2232,7 @@ describe('Body parser', () => { const body36 = '\r\n

KAM 1

\r\n

Kam 1 script

\r\n

***SERVER***

\r\n

Server script

\r\n

KAM 2

\r\n

KAM 2 script

\r\n' const cues36: string[][] = [] - const result = ParseBody(config, '00000000001', 'test-segment', body36, cues36, fields, 0) + const result = ParseBody(config, '00000000001', 'test-segment', body36, cues36, emptyFields, 0) expect(stripExternalId(result)).toEqual([ literal({ type: PartType.Kam, @@ -2276,7 +2276,7 @@ describe('Body parser', () => { const body36 = '\r\n

KAM 1

\r\n

Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis

\r\n

\r\n

\r\n

\r\n

\r\n

KAM 2

\r\n' const cues36 = [['EKSTERN=LIVE 1']] - const result = ParseBody(config, '00000000001', 'test-segment', body36, cues36, fields, 0) + const result = ParseBody(config, '00000000001', 'test-segment', body36, cues36, emptyFields, 0) expect(stripExternalId(result)).toEqual([ literal({ type: PartType.Kam, @@ -2339,14 +2339,14 @@ describe('Body parser', () => { 'News/Citat/ARFG/LIVE/stoppoints_3' ] ] - const result = ParseBody(config, '00000000001', 'test-segment', body38, cues38, fields, 0) + const result = ParseBody(config, '00000000001', 'test-segment', body38, cues38, emptyFields, 0) expect(stripExternalId(result)).toEqual([ literal({ externalId: '', type: PartType.Kam, sourceDefinition: SOURCE_DEFINITION_KAM_1, rawType: 'KAM 1', - fields, + fields: emptyFields, modified: 0, script: '', storyName: 'test-segment', @@ -2391,7 +2391,7 @@ describe('Body parser', () => { } }) ], - fields, + fields: emptyFields, modified: 0, script: '', storyName: 'test-segment', @@ -2406,7 +2406,7 @@ describe('Body parser', () => { ['GRAFIK=FULL', 'INP1=', 'INP='], ['#cg4 pilotdata', 'TELEFON/KORT//LIVE_KABUL', 'VCPID=2552305', 'ContinueCount=3', 'TELEFON/KORT//LIVE_KABU'] ] - const result = ParseBody(config, '00000000001', 'test-segment', bodyTarget, cuesTarget, fields, 0) + const result = ParseBody(config, '00000000001', 'test-segment', bodyTarget, cuesTarget, emptyFields, 0) expect(stripExternalId(result)).toEqual( literal([ literal({ @@ -2439,7 +2439,7 @@ describe('Body parser', () => { ], title: 'TELEFON/KORT//LIVE_KABUL', script: '', - fields, + fields: emptyFields, modified: 0, storyName: 'test-segment', segmentExternalId: '00000000001' @@ -2471,7 +2471,7 @@ describe('Body parser', () => { 'Senderplan/23-10-2019' ] ] - const result = ParseBody(config, '00000000001', 'test-segment', bodyTarget, cuesTarget, fields, 0) + const result = ParseBody(config, '00000000001', 'test-segment', bodyTarget, cuesTarget, emptyFields, 0) expect(stripExternalId(result)).toEqual( literal([ literal({ @@ -2502,7 +2502,7 @@ describe('Body parser', () => { ], title: 'Senderplan/23-10-2019', script: '', - fields, + fields: emptyFields, modified: 0, storyName: 'test-segment', segmentExternalId: '00000000001' @@ -2535,7 +2535,7 @@ describe('Body parser', () => { ], title: 'Senderplan/23-10-2019', script: '', - fields, + fields: emptyFields, modified: 0, storyName: 'test-segment', segmentExternalId: '00000000001' @@ -2550,7 +2550,7 @@ describe('Body parser', () => { ['SS=sc-stills', 'INP1=EVS 1', ';0.00.01'], ['#cg4 pilotdata', 'TELEFON/KORT//LIVE_KABUL', 'VCPID=2552305', 'ContinueCount=3', 'TELEFON/KORT//LIVE_KABU'] ] - const result = ParseBody(config, '00000000001', 'test-segment', bodyTarget, cuesTarget, fields, 0) + const result = ParseBody(config, '00000000001', 'test-segment', bodyTarget, cuesTarget, emptyFields, 0) expect(stripExternalId(result)).toEqual( literal([ literal({ @@ -2582,7 +2582,7 @@ describe('Body parser', () => { }) ], script: '', - fields, + fields: emptyFields, modified: 0, storyName: 'test-segment', segmentExternalId: '00000000001' @@ -2595,7 +2595,7 @@ describe('Body parser', () => { const body27 = '\r\n

\r\n

\r\n

EVS 1 EFFEKT 1

\r\n

\r\n

\r\n

\r\n

\r\n

\r\n

\r\n

\r\n

Skriv din spib her

\r\n

\r\n' const cues27 = [unparsedGrafik1, unparsedGrafik2, unparsedGrafik3] - const result = ParseBody(config, '00000000001', 'test-segment', body27, cues27, fields, 0) + const result = ParseBody(config, '00000000001', 'test-segment', body27, cues27, emptyFields, 0) expect(stripExternalId(result)).toEqual([ literal({ externalId: '', @@ -2610,7 +2610,7 @@ describe('Body parser', () => { }, effekt: 1, cues: [cueGrafik1, cueGrafik2, cueGrafik3], - fields, + fields: emptyFields, modified: 0, script: 'Skriv din spib her\n', storyName: 'test-segment', @@ -2622,7 +2622,7 @@ describe('Body parser', () => { test('EKSTERN 1 with EFFEKT', () => { const body = '\r\n

***LIVE***

\r\n

\r\n' const cues = [['EKSTERN=LIVE 1 EFFEKT 1']] - const result = ParseBody(config, '00000000001', 'test-segment', body, cues, fields, 0) + const result = ParseBody(config, '00000000001', 'test-segment', body, cues, emptyFields, 0) expect(stripExternalId(result)).toEqual([ literal({ externalId: '', @@ -2631,7 +2631,7 @@ describe('Body parser', () => { rawType: '', effekt: 1, cues: [cueEkstern1], - fields, + fields: emptyFields, modified: 0, script: '', storyName: 'test-segment', @@ -2655,7 +2655,7 @@ describe('Body parser', () => { 'LgfxWeb/-ETKAEM_07-05-2019_17:55:42' ] ] - const result = ParseBody(config, '00000000001', 'test-segment', body, cues, fields, 0) + const result = ParseBody(config, '00000000001', 'test-segment', body, cues, emptyFields, 0) expect(stripExternalId(result)).toEqual([ literal({ type: PartType.Kam, @@ -2679,7 +2679,7 @@ describe('Body parser', () => { ], rawType: 'KAM 1', script: '', - fields, + fields: emptyFields, modified: 0, storyName: 'test-segment', segmentExternalId: '00000000001' @@ -2700,7 +2700,7 @@ describe('Body parser', () => { 'LgfxWeb/-ETKAEM_07-05-2019_17:55:42' ] ] - const result = ParseBody(config, '00000000001', 'test-segment', body, cues, fields, 0) + const result = ParseBody(config, '00000000001', 'test-segment', body, cues, emptyFields, 0) expect(stripExternalId(result)).toEqual([ literal({ type: PartType.Kam, @@ -2752,7 +2752,7 @@ describe('Body parser', () => { 'LgfxWeb/-ETKAEM_07-05-2019_17:55:42' ] ] - const result = ParseBody(config, '00000000001', 'test-segment', body, cues, fields, 0) + const result = ParseBody(config, '00000000001', 'test-segment', body, cues, emptyFields, 0) expect(stripExternalId(result)).toEqual([ literal({ type: PartType.Kam, @@ -2798,7 +2798,7 @@ describe('Body parser', () => { 'LgfxWeb/-ETKAEM_07-05-2019_17:55:42' ] ] - const result = ParseBody(config, '00000000001', 'test-segment', body, cues, fields, 0) + const result = ParseBody(config, '00000000001', 'test-segment', body, cues, emptyFields, 0) expect(stripExternalId(result)).toEqual([ literal({ type: PartType.Kam, @@ -2807,7 +2807,7 @@ describe('Body parser', () => { cues: [], rawType: 'KAM 1', script: '', - fields, + fields: emptyFields, modified: 0, storyName: 'test-segment', segmentExternalId: '00000000001' @@ -2834,7 +2834,7 @@ describe('Body parser', () => { title: 'LgfxWeb/-ETKAEM_07-05-2019_17:55:42', rawType: '', script: '', - fields, + fields: emptyFields, modified: 0, storyName: 'test-segment', segmentExternalId: '00000000001' @@ -2856,7 +2856,7 @@ describe('Body parser', () => { 'LgfxWeb/-ETKAEM_07-05-2019_17:55:42' ] ] - const result = ParseBody(config, '00000000001', 'test-segment', body, cues, fields, 0) + const result = ParseBody(config, '00000000001', 'test-segment', body, cues, emptyFields, 0) expect(stripExternalId(result)).toEqual([ literal({ type: PartType.Kam, @@ -2865,7 +2865,7 @@ describe('Body parser', () => { cues: [], rawType: 'KAM 1', script: '', - fields, + fields: emptyFields, modified: 0, storyName: 'test-segment', segmentExternalId: '00000000001' @@ -2892,7 +2892,7 @@ describe('Body parser', () => { ], rawType: '100%GRAFIK', script: 'Some script...\n', - fields, + fields: emptyFields, modified: 0, storyName: 'test-segment', segmentExternalId: '00000000001' @@ -2914,7 +2914,7 @@ describe('Body parser', () => { 'LgfxWeb/-ETKAEM_07-05-2019_17:55:42' ] ] - const result = ParseBody(config, '00000000001', 'test-segment', body, cues, fields, 0) + const result = ParseBody(config, '00000000001', 'test-segment', body, cues, emptyFields, 0) expect(stripExternalId(result)).toEqual([ literal({ type: PartType.Kam, @@ -2923,7 +2923,7 @@ describe('Body parser', () => { cues: [], rawType: 'KAM 1', script: 'Some script...\n', - fields, + fields: emptyFields, modified: 0, storyName: 'test-segment', segmentExternalId: '00000000001' @@ -2941,7 +2941,7 @@ describe('Body parser', () => { ], rawType: '', script: 'Some script 1...\n', - fields, + fields: emptyFields, modified: 0, storyName: 'test-segment', segmentExternalId: '00000000001' @@ -2963,7 +2963,7 @@ describe('Body parser', () => { ], rawType: '100%GRAFIK', script: 'Some script 2...\n', - fields, + fields: emptyFields, modified: 0, storyName: 'test-segment', segmentExternalId: '00000000001' @@ -2984,7 +2984,7 @@ describe('Body parser', () => { 'LgfxWeb/-ETKAEM_07-05-2019_17:55:42' ] ] - const result = ParseBody(config, '00000000001', 'test-segment', body, cues, fields, 0) + const result = ParseBody(config, '00000000001', 'test-segment', body, cues, emptyFields, 0) expect(stripExternalId(result)).toEqual([ literal({ type: PartType.Kam, @@ -3008,7 +3008,7 @@ describe('Body parser', () => { ], rawType: 'KAM 1', script: '', - fields, + fields: emptyFields, modified: 0, storyName: 'test-segment', segmentExternalId: '00000000001' @@ -3029,7 +3029,7 @@ describe('Body parser', () => { 'LgfxWeb/-ETKAEM_07-05-2019_17:55:42' ] ] - const result = ParseBody(config, '00000000001', 'test-segment', body, cues, fields, 0) + const result = ParseBody(config, '00000000001', 'test-segment', body, cues, emptyFields, 0) expect(stripExternalId(result)).toEqual([ literal({ type: PartType.Kam, @@ -3053,7 +3053,7 @@ describe('Body parser', () => { ], rawType: 'KAM 1', script: '', - fields, + fields: emptyFields, modified: 0, storyName: 'test-segment', segmentExternalId: '00000000001' @@ -3074,7 +3074,7 @@ describe('Body parser', () => { 'LgfxWeb/-ETKAEM_07-05-2019_17:55:42/MOSART=L|00:00|O' ] ] - const result = ParseBody(config, '00000000001', 'test-segment', body, cues, fields, 0) + const result = ParseBody(config, '00000000001', 'test-segment', body, cues, emptyFields, 0) expect(stripExternalId(result)).toEqual([ literal({ type: PartType.Kam, @@ -3083,7 +3083,7 @@ describe('Body parser', () => { cues: [], rawType: 'KAM 1', script: '', - fields, + fields: emptyFields, modified: 0, storyName: 'test-segment', segmentExternalId: '00000000001' @@ -3118,7 +3118,7 @@ describe('Body parser', () => { ], rawType: '', script: '', - fields, + fields: emptyFields, modified: 0, storyName: 'test-segment', segmentExternalId: '00000000001' @@ -3139,7 +3139,7 @@ describe('Body parser', () => { 'LgfxWeb/-ETKAEM_07-05-2019_17:55:42/MOSART=W|00:00|O' ] ] - const result = ParseBody(config, '00000000001', 'test-segment', body, cues, fields, 0) + const result = ParseBody(config, '00000000001', 'test-segment', body, cues, emptyFields, 0) expect(stripExternalId(result)).toEqual([ literal({ type: PartType.Kam, @@ -3148,7 +3148,7 @@ describe('Body parser', () => { cues: [], rawType: 'KAM 1', script: '', - fields, + fields: emptyFields, modified: 0, storyName: 'test-segment', segmentExternalId: '00000000001' @@ -3183,7 +3183,7 @@ describe('Body parser', () => { ], rawType: '', script: '', - fields, + fields: emptyFields, modified: 0, storyName: 'test-segment', segmentExternalId: '00000000001' @@ -3204,7 +3204,7 @@ describe('Body parser', () => { 'LgfxWeb/-ETKAEM_07-05-2019_17:55:42/MOSART=F|00:00|O' ] ] - const result = ParseBody(config, '00000000001', 'test-segment', body, cues, fields, 0) + const result = ParseBody(config, '00000000001', 'test-segment', body, cues, emptyFields, 0) expect(stripExternalId(result)).toEqual([ literal({ type: PartType.Kam, @@ -3213,7 +3213,7 @@ describe('Body parser', () => { cues: [], rawType: 'KAM 1', script: '', - fields, + fields: emptyFields, modified: 0, storyName: 'test-segment', segmentExternalId: '00000000001' @@ -3231,7 +3231,7 @@ describe('Body parser', () => { ], rawType: '', script: '', - fields, + fields: emptyFields, modified: 0, storyName: 'test-segment', segmentExternalId: '00000000001' @@ -3261,7 +3261,7 @@ describe('Body parser', () => { title: 'LgfxWeb/-ETKAEM_07-05-2019_17:55:42', rawType: '', script: '', - fields, + fields: emptyFields, modified: 0, storyName: 'test-segment', segmentExternalId: '00000000001' @@ -3285,7 +3285,7 @@ describe('Body parser', () => { 'SP-H/Fakta/EM HÃ…NDBOLD' ] ] - const result = ParseBody(config, '00000000001', 'test-segment', body, cues, fields, 0) + const result = ParseBody(config, '00000000001', 'test-segment', body, cues, emptyFields, 0) expect(stripExternalId(result)).toEqual([ literal({ type: PartType.Kam, @@ -3310,7 +3310,7 @@ describe('Body parser', () => { ], rawType: 'KAM 1', script: '', - fields, + fields: emptyFields, modified: 0, storyName: 'test-segment', segmentExternalId: '00000000001' @@ -3337,7 +3337,7 @@ describe('Body parser', () => { ], rawType: 'VO', script: '', - fields, + fields: emptyFields, modified: 0, storyName: 'test-segment', segmentExternalId: '00000000001' @@ -3351,7 +3351,7 @@ describe('Body parser', () => { it('has no no cues, does nothing', () => { const definitions: PartDefinition[] = [createPartDefinition(), createPartDefinition()] - const result: PartDefinition[] = stripRedundantCuesWhenLayoutCueIsPresent(definitions) + const result: PartDefinition[] = stripRedundantCuesWhenFieldCueIsPresent(definitions) expect(result).toEqual(definitions) }) @@ -3365,7 +3365,7 @@ describe('Body parser', () => { ]) ] - const result: PartDefinition[] = stripRedundantCuesWhenLayoutCueIsPresent(definitions) + const result: PartDefinition[] = stripRedundantCuesWhenFieldCueIsPresent(definitions) expect(result[0].cues).toHaveLength(1) const graphicDesignCue: CueDefinitionGraphicDesign = result[0].cues[0] as CueDefinitionGraphicDesign @@ -3375,7 +3375,7 @@ describe('Body parser', () => { it('only have a regular design cue, does nothing', () => { const definitions: PartDefinition[] = [createPartDefinition([createDesignCueDefinition('someDesign')])] - const result: PartDefinition[] = stripRedundantCuesWhenLayoutCueIsPresent(definitions) + const result: PartDefinition[] = stripRedundantCuesWhenFieldCueIsPresent(definitions) expect(result).toEqual(definitions) }) @@ -3385,7 +3385,7 @@ describe('Body parser', () => { createPartDefinition([createDesignCueDefinition('designFromLayout', true)]) ] - const result: PartDefinition[] = stripRedundantCuesWhenLayoutCueIsPresent(definitions) + const result: PartDefinition[] = stripRedundantCuesWhenFieldCueIsPresent(definitions) expect(result).toEqual(definitions) }) @@ -3401,7 +3401,7 @@ describe('Body parser', () => { ]) ] - const result: PartDefinition[] = stripRedundantCuesWhenLayoutCueIsPresent(definitions) + const result: PartDefinition[] = stripRedundantCuesWhenFieldCueIsPresent(definitions) const cues = result[0].cues expect(cues).toHaveLength(3) @@ -3422,7 +3422,7 @@ describe('Body parser', () => { createPartDefinition([createDesignCueDefinition('regularDesign')]) ] - const result: PartDefinition[] = stripRedundantCuesWhenLayoutCueIsPresent(definitions) + const result: PartDefinition[] = stripRedundantCuesWhenFieldCueIsPresent(definitions) const cues: CueDefinition[] = result.flatMap((definition) => definition.cues) expect(cues).toHaveLength(1) @@ -3430,28 +3430,12 @@ describe('Body parser', () => { expect(graphicCue.design).toBe(layoutDesign) }) - it('has layout background cue and regular background cue, remove regular background cue', () => { - const layoutBackground = 'layoutBackground' - const definitions: PartDefinition[] = [ - createPartDefinition([ - createBackgroundLoopCueDefinition(layoutBackground, true), - createBackgroundLoopCueDefinition('regularBackground') - ]) - ] - - const result: PartDefinition[] = stripRedundantCuesWhenLayoutCueIsPresent(definitions) - - expect(result[0].cues).toHaveLength(1) - const backgroundCue: CueDefinitionBackgroundLoop = result[0].cues[0] as CueDefinitionBackgroundLoop - expect(backgroundCue.backgroundLoop).toBe(layoutBackground) - }) - it('only have a regular background cue, does nothing', () => { const definitions: PartDefinition[] = [ createPartDefinition([createBackgroundLoopCueDefinition('regularBackground')]) ] - const result: PartDefinition[] = stripRedundantCuesWhenLayoutCueIsPresent(definitions) + const result: PartDefinition[] = stripRedundantCuesWhenFieldCueIsPresent(definitions) expect(result).toEqual(definitions) }) @@ -3461,7 +3445,7 @@ describe('Body parser', () => { createPartDefinition([createBackgroundLoopCueDefinition('layoutBackground', true)]) ] - const result: PartDefinition[] = stripRedundantCuesWhenLayoutCueIsPresent(definitions) + const result: PartDefinition[] = stripRedundantCuesWhenFieldCueIsPresent(definitions) expect(result).toEqual(definitions) }) @@ -3492,6 +3476,74 @@ describe('Body parser', () => { expect(result.transition!.duration).toBe(250) }) }) + + describe('create CueDefinitions from fields', () => { + it('receives a layout field, DesignCueDefinition is added to first PartDefinition', () => { + const segmentId: string = 'randomSegmentId' + const segmentName: string = 'randomSegmentName' + const body: string = '\r\n

CAMERA 1

' + const cues: UnparsedCue[] = [] + const fields: INewsFields = { + layout: config.showStyle.GfxDesignTemplates[0].INewsStyleColumn + } + + const result: PartDefinition[] = ParseBody(config, segmentId, segmentName, body, cues, fields, 0) + + expect(result).toHaveLength(1) + const cueDefinitions = result[0].cues + expect(cueDefinitions.some((cue) => cue.type === CueType.GraphicDesign)).toBeTruthy() + }) + + it('receives both a layout field and a body design cue, only one DesignCueDefinition is added to first PartDefinition', () => { + const segmentId: string = 'randomSegmentId' + const segmentName: string = 'randomSegmentName' + const body: string = `\r\n

CAMERA 1<\a idref="0">

\r\n` + const cues: UnparsedCue[] = [[`kg=DESIGN_FODBOLD_22`]] + const fields: INewsFields = { + layout: config.showStyle.GfxDesignTemplates[0].INewsStyleColumn + } + + const result: PartDefinition[] = ParseBody(config, segmentId, segmentName, body, cues, fields, 0) + + expect(result).toHaveLength(1) + const cueDefinitions = result[0].cues + expect(cueDefinitions).toHaveLength(1) + expect(cueDefinitions.some((cue) => cue.type === CueType.GraphicDesign)).toBeTruthy() + }) + + it('receives a schema field, DesignSchemaDefinition is added to first PartDefinition', () => { + const segmentId: string = 'randomSegmentId' + const segmentName: string = 'randomSegmentName' + const body: string = '\r\n

CAMERA 1

' + const cues: UnparsedCue[] = [] + const fields: INewsFields = { + skema: config.showStyle.GfxSchemaTemplates[0].INewsSkemaColumn + } + + const result: PartDefinition[] = ParseBody(config, segmentId, segmentName, body, cues, fields, 0) + + expect(result).toHaveLength(1) + const cueDefinitions = result[0].cues + expect(cueDefinitions.some((cue) => cue.type === CueType.GraphicSchema)).toBeTruthy() + }) + + it('receives both a schema field and a body schema cue, only one DesignSchemaDefinition is added to first PartDefinition', () => { + const segmentId: string = 'randomSegmentId' + const segmentName: string = 'randomSegmentName' + const body: string = `\r\n

CAMERA 1<\a idref="0">

\r\n` + const cues: UnparsedCue[] = [[`kg=SKEMA_NEWS`]] + const fields: INewsFields = { + skema: config.showStyle.GfxSchemaTemplates[0].INewsSkemaColumn + } + + const result: PartDefinition[] = ParseBody(config, segmentId, segmentName, body, cues, fields, 0) + + expect(result).toHaveLength(1) + const cueDefinitions = result[0].cues + expect(cueDefinitions).toHaveLength(1) + expect(cueDefinitions.some((cue) => cue.type === CueType.GraphicSchema)).toBeTruthy() + }) + }) }) function createPartDefinition(cues?: CueDefinition[]): PartDefinition { @@ -3511,21 +3563,21 @@ function createPartDefinition(cues?: CueDefinition[]): PartDefinition { } } -function createDesignCueDefinition(design: string, isFromLayout?: boolean): CueDefinition { +function createDesignCueDefinition(design: string, isFromField?: boolean): CueDefinition { return { type: CueType.GraphicDesign, design, iNewsCommand: '', - isFromLayout + isFromField } } -function createBackgroundLoopCueDefinition(backgroundLoop: string, isFromLayout?: boolean): CueDefinition { +function createBackgroundLoopCueDefinition(backgroundLoop: string, isFromField?: boolean): CueDefinition { return { type: CueType.BackgroundLoop, target: 'DVE', backgroundLoop, - isFromLayout, + isFromField, iNewsCommand: '' } } diff --git a/src/tv2-common/inewsConversion/converters/__tests__/cue-parser.spec.ts b/src/tv2-common/inewsConversion/converters/__tests__/cue-parser.spec.ts index 17bcb04bf..ddac4131f 100644 --- a/src/tv2-common/inewsConversion/converters/__tests__/cue-parser.spec.ts +++ b/src/tv2-common/inewsConversion/converters/__tests__/cue-parser.spec.ts @@ -1918,5 +1918,11 @@ describe('Cue parser', () => { expect(result!.start!.seconds).toBe(24) expect(result!.start!.frames).toBe(10) }) + + it('receives time code ;x.xx, it is an adlib', () => { + const cue = ['ROBOT=11', ';x.xx'] + const result = ParseCue(cue, config) as CueDefinitionRobotCamera + expect(result.adlib).toBe(true) + }) }) }) diff --git a/src/tv2-common/migrations/index.ts b/src/tv2-common/migrations/index.ts index 765a9c7e6..36dd4a126 100644 --- a/src/tv2-common/migrations/index.ts +++ b/src/tv2-common/migrations/index.ts @@ -1,7 +1,8 @@ -import { BasicConfigItemValue, IBlueprintShowStyleVariant } from '@sofie-automation/blueprints-integration' import { + BasicConfigItemValue, BlueprintMappings, ConfigItemValue, + IBlueprintShowStyleVariant, MigrationContextShowStyle, MigrationContextStudio, MigrationStepShowStyle, @@ -160,8 +161,8 @@ export function mapGfxTemplateToDesignTemplateAndDeleteOriginals( gfxTemplates .filter((template) => template.IsDesign) - .map((template) => { - designTemplates.push({ ...template, INewsStyleColumn: '' }) + .forEach((template, index) => { + designTemplates.push({ _id: `${index}`, ...template, INewsStyleColumn: '' }) }) const newGfxTemplates = gfxTemplates.filter((template) => !template.IsDesign) diff --git a/src/tv2-common/migrations/renameConfigurationHelper.ts b/src/tv2-common/migrations/renameConfigurationHelper.ts index 3297af306..6017d60c9 100644 --- a/src/tv2-common/migrations/renameConfigurationHelper.ts +++ b/src/tv2-common/migrations/renameConfigurationHelper.ts @@ -1,9 +1,10 @@ -import { BasicConfigItemValue, IBlueprintShowStyleVariant } from '@sofie-automation/blueprints-integration' import { + BasicConfigItemValue, + IBlueprintShowStyleVariant, MigrationContextShowStyle, MigrationStepShowStyle, TableConfigItemValue -} from '../../types/blueprints-integration' +} from 'blueprints-integration' /** * "Renames" the id of a table by copying over all values over into a new table which has the new id - then removes the values from the old table diff --git a/src/tv2-common/onTimelineGenerate.ts b/src/tv2-common/onTimelineGenerate.ts index 8614d70e8..492a8073a 100644 --- a/src/tv2-common/onTimelineGenerate.ts +++ b/src/tv2-common/onTimelineGenerate.ts @@ -20,18 +20,17 @@ import { ServerPosition, TimelineContext } from 'tv2-common' -import { AbstractLLayer, PartType, TallyTags } from 'tv2-constants' +import { AbstractLLayer, PartType, SharedSisyfosLLayer, TallyTags } from 'tv2-constants' import * as _ from 'underscore' -import { SisyfosLLAyer } from '../tv2_afvd_studio/layers' import { TV2BlueprintConfigBase, TV2StudioConfigBase } from './blueprintConfig' export interface PartEndStateExt { - sisyfosPersistMetaData: SisyfosPersistMetaData + sisyfosPersistenceMetaData: SisyfosPersistenceMetaData mediaPlayerSessions: { [layer: string]: string[] } isJingle?: boolean fullFileName?: string serverPosition?: ServerPosition - segmentId?: string + segmentId: string partInstanceId: string } @@ -59,7 +58,7 @@ export type TimelineBlueprintExt = TSR.TSRTimelineObjBase & { } export interface PieceMetaData { - sisyfosPersistMetaData?: SisyfosPersistMetaData + sisyfosPersistMetaData?: SisyfosPersistenceMetaData mediaPlayerSessions?: string[] modifiedByAction?: boolean } @@ -81,12 +80,27 @@ export interface ServerPieceMetaData extends PieceMetaData { userData: ActionSelectServerClip } -export interface SisyfosPersistMetaData { +export interface SisyfosPersistenceMetaData { + /** + * The layers this piece wants to persist into the next part + */ sisyfosLayers: string[] + /** + * The layers this piece gathered from previous pieces and wants to persist into the next part + */ + previousSisyfosLayers?: string[] + /** + * Whether `sisyfosLayers` and `previousSisyfosLayers` may be persisted into the next part if accepted + */ wantsToPersistAudio?: boolean - acceptPersistAudio?: boolean - previousPersistMetaDataForCurrentPiece?: SisyfosPersistMetaData - isPieceInjectedInPart?: boolean + /** + * Whether `sisyfosLayers` and `previousSisyfosLayers` from the previous part may be persisted + */ + acceptsPersistedAudio?: boolean + /** + * Whether the piece was inserted/updated by fast Camera/Live cutting within a part or fading down persisted levels + */ + isModifiedOrInsertedByAction?: boolean } export interface PartMetaData { @@ -112,10 +126,16 @@ export function onTimelineGenerate< isNewSegment: context.core.previousPartInstance?.segmentId !== context.core.currentPartInstance?.segmentId } - if (!persistentState.isNewSegment || isAnyPieceInjectedIntoPart(context, resolvedPieces)) { + if ( + (!persistentState.isNewSegment || isAnyPieceInjectedIntoPart(context, resolvedPieces)) && + context.core.currentPartInstance + ) { const sisyfosPersistedLevelsTimelineObject = createSisyfosPersistedLevelsTimelineObject( + context.core.currentPartInstance._id, resolvedPieces, - previousPartEndState2 ? previousPartEndState2.sisyfosPersistMetaData.sisyfosLayers : [] + previousPartEndState2 && !persistentState.isNewSegment + ? previousPartEndState2.sisyfosPersistenceMetaData.sisyfosLayers + : [] ) timeline.push(sisyfosPersistedLevelsTimelineObject) } @@ -211,26 +231,25 @@ function isAnyPieceInjectedIntoPart( return resolvedPieces .filter((piece) => piece.partInstanceId === context.core.currentPartInstance?._id) .some((piece) => { - return piece.piece.metaData?.sisyfosPersistMetaData?.isPieceInjectedInPart + return piece.piece.metaData?.sisyfosPersistMetaData?.isModifiedOrInsertedByAction }) } export function getEndStateForPart( _context: IRundownContext, - _previousPersistentState: TimelinePersistentState | undefined, + previousPersistentState: TimelinePersistentState | undefined, partInstance: IBlueprintPartInstance, resolvedPieces: Array>, time: number ): PartEndState { const endState: PartEndStateExt = { - sisyfosPersistMetaData: { + sisyfosPersistenceMetaData: { sisyfosLayers: [] }, mediaPlayerSessions: {}, segmentId: partInstance.segmentId, partInstanceId: partInstance._id } - const previousPartEndState = partInstance?.previousPartEndState as Partial const activePieces = resolvedPieces.filter( (p) => @@ -240,11 +259,12 @@ export function getEndStateForPart( (!p.piece.enable.duration || p.piece.enable.start + p.piece.enable.duration >= time) ) - const previousPersistentState: TimelinePersistentStateExt = _previousPersistentState as TimelinePersistentStateExt - endState.sisyfosPersistMetaData.sisyfosLayers = findLayersToPersist( + const previousPartEndState = partInstance?.previousPartEndState as Partial + const previousPersistentStateExt: TimelinePersistentStateExt = previousPersistentState as TimelinePersistentStateExt + endState.sisyfosPersistenceMetaData.sisyfosLayers = findLayersToPersistOnPartEnd( activePieces, - !previousPersistentState.isNewSegment && previousPartEndState && previousPartEndState.sisyfosPersistMetaData - ? previousPartEndState.sisyfosPersistMetaData.sisyfosLayers + !previousPersistentStateExt.isNewSegment && previousPartEndState && previousPartEndState.sisyfosPersistenceMetaData + ? previousPartEndState.sisyfosPersistenceMetaData.sisyfosLayers : [] ) @@ -271,16 +291,21 @@ export function getEndStateForPart( } export function createSisyfosPersistedLevelsTimelineObject( + currentPartInstanceId: string, resolvedPieces: Array>, - previousSisyfosLayersThatWantsToBePersisted: SisyfosPersistMetaData['sisyfosLayers'] + layersWantingToPersistFromPreviousPart: string[] ): TSR.TimelineObjSisyfosChannels { - const layersToPersist = findLayersToPersist(resolvedPieces, previousSisyfosLayersThatWantsToBePersisted) + const layersToPersist = findPersistedLayers( + currentPartInstanceId, + resolvedPieces, + layersWantingToPersistFromPreviousPart + ) return { id: 'sisyfosPersistenceObject', enable: { start: 0 }, - layer: SisyfosLLAyer.SisyfosPersistedLevels, + layer: SharedSisyfosLLayer.SisyfosPersistedLevels, content: { deviceType: TSR.DeviceType.SISYFOS, type: TSR.TimelineContentTypeSisyfos.CHANNELS, @@ -295,53 +320,72 @@ export function createSisyfosPersistedLevelsTimelineObject( } } -function findLayersToPersist( - pieces: Array>, - sisyfosLayersThatWantsToBePersisted: string[] +function findLayersToPersistOnPartEnd( + pieceInstances: Array>, + layersWantingToPersistFromPreviousPart: string[] = [] ): string[] { - const sortedPieces = pieces - .filter((piece) => piece.piece.metaData?.sisyfosPersistMetaData) - .sort((a, b) => b.resolvedStart - a.resolvedStart) + const latestPieceInstance = findLastPlayingPieceInstance( + pieceInstances, + (pieceInstance) => !!pieceInstance.piece.metaData?.sisyfosPersistMetaData + ) + const latestPieceMetaData = latestPieceInstance?.piece.metaData?.sisyfosPersistMetaData - if (sortedPieces.length === 0) { + if (!latestPieceMetaData?.wantsToPersistAudio) { return [] } - const firstPieceMetaData = sortedPieces[0].piece.metaData! - if (!firstPieceMetaData.sisyfosPersistMetaData!.acceptPersistAudio) { - return firstPieceMetaData.sisyfosPersistMetaData!.wantsToPersistAudio - ? firstPieceMetaData.sisyfosPersistMetaData!.sisyfosLayers - : [] + if (!latestPieceMetaData.acceptsPersistedAudio) { + return latestPieceMetaData.wantsToPersistAudio ? latestPieceMetaData.sisyfosLayers : [] } const layersToPersist: string[] = [] - for (let i = 0; i < sortedPieces.length; i++) { - const pieceMetaData = sortedPieces[i].piece.metaData! - const sisyfosPersistMetaData: SisyfosPersistMetaData = pieceMetaData.sisyfosPersistMetaData! - if (sisyfosPersistMetaData.wantsToPersistAudio) { - layersToPersist.push(...sisyfosPersistMetaData.sisyfosLayers) - } + if (!latestPieceMetaData?.isModifiedOrInsertedByAction) { + layersToPersist.push(...layersWantingToPersistFromPreviousPart) + } else if (latestPieceMetaData.previousSisyfosLayers) { + layersToPersist.push(...latestPieceMetaData.previousSisyfosLayers) + } + layersToPersist.push(...latestPieceMetaData.sisyfosLayers) - if (doesMetaDataNotAcceptPersistAudioDeep(sisyfosPersistMetaData)) { - break - } + return Array.from(new Set(layersToPersist)) +} - if (i === sortedPieces.length - 1) { - layersToPersist.push(...sisyfosLayersThatWantsToBePersisted) - } +function findPersistedLayers( + currentPartInstanceId: string, + pieceInstances: Array>, + layersWantingToPersistFromPreviousPart: string[] +): string[] { + const latestPieceInstance = findLastPlayingPieceInstance( + pieceInstances, + (pieceInstance) => + !!pieceInstance.piece.metaData?.sisyfosPersistMetaData && pieceInstance.partInstanceId === currentPartInstanceId + ) + + const latestPieceMetaData = latestPieceInstance?.piece.metaData + if (!latestPieceMetaData?.sisyfosPersistMetaData!.acceptsPersistedAudio) { + return [] + } + + const layersToPersist: string[] = [] + if (!latestPieceMetaData.sisyfosPersistMetaData?.isModifiedOrInsertedByAction) { + layersToPersist.push(...layersWantingToPersistFromPreviousPart) + } else if (latestPieceMetaData.sisyfosPersistMetaData.previousSisyfosLayers) { + layersToPersist.push(...latestPieceMetaData.sisyfosPersistMetaData.previousSisyfosLayers) } return Array.from(new Set(layersToPersist)) } -function doesMetaDataNotAcceptPersistAudioDeep(metaData: SisyfosPersistMetaData): boolean { - if (!metaData.acceptPersistAudio) { - return true +export function findLastPlayingPieceInstance( + currentPieceInstances: Array>, + predicate?: (pieceInstance: IBlueprintResolvedPieceInstance) => boolean +): IBlueprintResolvedPieceInstance | undefined { + const playingPieces = currentPieceInstances.filter((p) => !p.stoppedPlayback && (predicate ? predicate(p) : true)) + if (playingPieces.length <= 1) { + return playingPieces[0] } - if (metaData.previousPersistMetaDataForCurrentPiece) { - return doesMetaDataNotAcceptPersistAudioDeep(metaData.previousPersistMetaDataForCurrentPiece) - } - return false + return playingPieces.reduce((prev, current) => { + return prev.resolvedStart > current.resolvedStart ? prev : current + }) } export function disablePilotWipeAfterJingle( diff --git a/src/tv2-common/parts/server.ts b/src/tv2-common/parts/server.ts index 986ac9aee..981e5f7c8 100644 --- a/src/tv2-common/parts/server.ts +++ b/src/tv2-common/parts/server.ts @@ -232,7 +232,7 @@ function getServerSelectionBlueprintPiece( userData: userDataElement, sisyfosPersistMetaData: { sisyfosLayers: [], - acceptPersistAudio: partProps.adLibPix && partProps.voLevels + acceptsPersistedAudio: partProps.adLibPix && partProps.voLevels } }, content: contentServerElement, diff --git a/src/tv2-common/showstyle/config-manifests.ts b/src/tv2-common/showstyle/config-manifests.ts index 3b2f839eb..d49e95307 100644 --- a/src/tv2-common/showstyle/config-manifests.ts +++ b/src/tv2-common/showstyle/config-manifests.ts @@ -1,4 +1,5 @@ import { ConfigManifestEntry, ConfigManifestEntryTable, ConfigManifestEntryType } from 'blueprints-integration' +import { DEFAULT_GRAPHICS } from '../migrations' export enum ShowStyleConfigId { GRAPHICS_SETUPS_TABLE_ID = 'GfxSetups', @@ -47,6 +48,150 @@ export const getGfxSetupsEntries = (columns: ConfigManifestEntryTable['columns'] } ] +const DESIGN_TABLE_ID = 'GfxDesignTemplates' +const DESIGN_NAME_COLUMN_ID = 'INewsName' + +export const gfxDesignTemplates: ConfigManifestEntry[] = [ + { + id: DESIGN_TABLE_ID, + name: 'GFX Design Templates', + description: '', + type: ConfigManifestEntryType.TABLE, + required: true, + defaultVal: DEFAULT_GRAPHICS.map((val) => ({ _id: '', ...val })).filter((template) => template.IsDesign), + columns: [ + { + id: DESIGN_NAME_COLUMN_ID, + name: 'iNews Name', + description: 'The name of the design', + type: ConfigManifestEntryType.STRING, + required: false, + defaultVal: '', + rank: 0 + }, + { + id: 'INewsStyleColumn', + name: 'iNews Style Column', + description: 'The selected style', + type: ConfigManifestEntryType.STRING, + required: false, + defaultVal: '', + rank: 1 + }, + { + id: 'VizTemplate', + name: 'GFX Template Name', + description: 'The name of the Viz Template', + type: ConfigManifestEntryType.STRING, + required: true, + defaultVal: '', + rank: 2 + } + ] + } +] + +const GFX_SCHEMA_TABLE_ID = 'GfxSchemaTemplates' +const GFX_SCHEMA_NAME_COLUMN_ID = 'GfxSchemaTemplatesName' + +export const gfxSchemaTemplates: ConfigManifestEntry[] = [ + { + id: GFX_SCHEMA_TABLE_ID, + name: 'GFX Skema Templates', + description: 'The values for the Skema and Design combinations', + type: ConfigManifestEntryType.TABLE, + required: false, + defaultVal: [], + columns: [ + { + id: GFX_SCHEMA_NAME_COLUMN_ID, + name: 'iNews Name', + description: 'The name of the design', + type: ConfigManifestEntryType.STRING, + required: false, + defaultVal: '', + rank: 0 + }, + { + id: 'INewsSkemaColumn', + name: 'iNews Skema Column', + description: 'The selected skema', + type: ConfigManifestEntryType.STRING, + required: false, + defaultVal: '', + rank: 1 + }, + { + id: 'VizTemplate', + name: 'GFX Template Name', + description: 'The name of the Viz Template', + type: ConfigManifestEntryType.STRING, + required: true, + defaultVal: '', + rank: 2 + }, + { + id: 'CasparCgDesignValues', + name: 'CasparCG Design Values', + description: + 'A JSON array containing color values and background loops for the different Designs of the Schema used by CasparCG', + type: ConfigManifestEntryType.JSON, + required: false, + defaultVal: '[]', + rank: 3 + } + ] + } +] + +export const gfxShowMapping: ConfigManifestEntry = { + id: 'GfxShowMapping', + name: 'GFX Show mapping', + description: + 'Maps Overlay Shows to the variety of Skemas and Designs. If a Setup does not have a corresponding Design/Skema, it should be left out of this table.', + type: ConfigManifestEntryType.TABLE, + required: false, + defaultVal: [], + columns: [ + { + id: ShowStyleConfigId.GFX_SHOW_MAPPING_DESIGN_COLUMN_ID, + name: 'Design', + rank: 0, + description: 'Name of the Design from the GFX Design table', + type: ConfigManifestEntryType.SELECT_FROM_COLUMN, + tableId: DESIGN_TABLE_ID, + columnId: DESIGN_NAME_COLUMN_ID, + multiple: false, + required: false, + defaultVal: '' + }, + { + id: ShowStyleConfigId.GFX_SHOW_MAPPING_GFX_SETUP_COLUMN_ID, + name: 'GFX Setup', + rank: 1, + description: 'Names of the GFX Setups', + type: ConfigManifestEntryType.SELECT_FROM_COLUMN, + tableId: ShowStyleConfigId.GRAPHICS_SETUPS_TABLE_ID, + columnId: ShowStyleConfigId.GRAPHICS_SETUPS_NAME_COLUMN_ID, + multiple: true, + required: false, + defaultVal: [] + }, + { + id: ShowStyleConfigId.GFX_SHOW_MAPPING_SCHEMA_COLUMN_ID, + name: 'GFX Skema Templates', + rank: 2, + description: 'Names of the Skemas', + type: ConfigManifestEntryType.SELECT_FROM_COLUMN, + tableId: GFX_SCHEMA_TABLE_ID, + columnId: GFX_SCHEMA_NAME_COLUMN_ID, + multiple: true, + required: false, + defaultVal: [] + } + ] +} + const GFX_DEFAULT_VALUES = { DefaultSetupName: '', DefaultSchema: '', diff --git a/src/tv2-common/types/inews.ts b/src/tv2-common/types/inews.ts index 21ca5f707..7905c6170 100644 --- a/src/tv2-common/types/inews.ts +++ b/src/tv2-common/types/inews.ts @@ -8,6 +8,7 @@ export interface INewsFields { cumeTime?: string // number backTime?: string // @number (seconds since midnight) layout?: string + skema?: string } export interface INewsMetaData { diff --git a/src/tv2-constants/enums.ts b/src/tv2-constants/enums.ts index 128d521f8..6a8449e8c 100644 --- a/src/tv2-constants/enums.ts +++ b/src/tv2-constants/enums.ts @@ -33,7 +33,8 @@ export enum CueType { Routing, PgmClean, MixMinus, - RobotCamera + RobotCamera, + GraphicSchema } export const enum PartType { @@ -87,6 +88,7 @@ export enum AdlibTags { ADLIB_CUT_TO_BOX_3 = 'cut_to_box_3', ADLIB_CUT_TO_BOX_4 = 'cut_to_box_4', ADLIB_GFX_ALTUD = 'gfx_altud', + ADLIB_GFX_TEMAUD = 'gfx_temaud', ADLIB_GFX_LOAD = 'gfx_load', ADLIB_GFX_CONTINUE_FORWARD = 'gfx_continue_forward', ADLIB_DSK_ON = 'dsk_on', @@ -136,18 +138,21 @@ export enum AdlibActionType { COMMENTATOR_SELECT_DVE = 'commentator_select_dve', COMMENTATOR_SELECT_FULL = 'commentator_select_full', COMMENTATOR_SELECT_JINGLE = 'commentator_select_jingle', - CLEAR_GRAPHICS = 'clear_graphics', + CLEAR_ALL_GRAPHICS = 'clear_all_graphics', + CLEAR_TEMA_GRAPHICS = 'clear_tema_graphics', TAKE_WITH_TRANSITION = 'take_with_transition', RECALL_LAST_LIVE = 'recall_last_live', RECALL_LAST_DVE = 'recall_last_dve', FADE_DOWN_PERSISTED_AUDIO_LEVELS = 'fade_down_persisted_audio_levels', - CALL_ROBOT_PRESET = 'call_robot_preset' + CALL_ROBOT_PRESET = 'call_robot_preset', + FADE_DOWN_SOUND_PLAYER = 'fade_down_sound_player' } export enum TallyTags { // Actions GFX_CLEAR = 'GFX_CLEAR', GFX_ALTUD = 'GFX_ALTUD', + GFX_TEMAUD = 'GFX_TEMAUD', TAKE_WITH_TRANSITION = 'TAKE_WITH_TRANSITION', // A particular source is live @@ -179,7 +184,8 @@ export enum SharedGraphicLLayer { GraphicLLayerAdLibs = 'graphic_adlibs', // <= viz_layer_adlibs GraphicLLayerWall = 'graphic_wall', // <= viz_layer_wall GraphicLLayerLocators = 'graphic_locators', - GraphicLLayerConcept = 'graphic_concept' + GraphicLLayerConcept = 'graphic_concept', + GraphicLLayerSchema = 'graphic_schema' } export enum AbstractLLayer { @@ -225,6 +231,7 @@ export enum SwitcherMediaPlayerLLayer { } export enum SharedCasparLLayer { + CasparCGDVELoop = 'casparcg_dve_loop', CasparCGLYD = 'casparcg_audio_lyd', CasparPlayerClipPending = 'casparcg_player_clip_pending', CasparPlayerJingle = 'casparcg_player_jingle' @@ -233,7 +240,8 @@ export enum SharedCasparLLayer { export enum SharedSisyfosLLayer { SisyfosSourceAudiobed = 'sisyfos_source_audiobed', SisyfosResync = 'sisyfos_resync', - SisyfosGroupStudioMics = 'sisyfos_group_studio_mics' + SisyfosGroupStudioMics = 'sisyfos_group_studio_mics', + SisyfosPersistedLevels = 'sisyfos_persisted_levels' } export enum RobotCameraLayer { @@ -274,6 +282,7 @@ export enum SharedSourceLayer { PgmPilotOverlay = 'studio0_pilotOverlay', // "Design" templates PgmDesign = 'studio0_design', + PgmSchema = 'studio0_schema', /** General, 'fallback', overlay layer */ PgmGraphicsOverlay = 'studio0_overlay', diff --git a/src/tv2_afvd_showstyle/GlobalAdlibActionsGenerator.ts b/src/tv2_afvd_showstyle/GlobalAdlibActionsGenerator.ts index db72624e3..f48952cae 100644 --- a/src/tv2_afvd_showstyle/GlobalAdlibActionsGenerator.ts +++ b/src/tv2_afvd_showstyle/GlobalAdlibActionsGenerator.ts @@ -1,11 +1,13 @@ import { IBlueprintActionManifest } from 'blueprints-integration' import { ActionCallRobotPreset, - ActionClearGraphics, + ActionClearAllGraphics, + ActionClearTemaGraphics, ActionCutSourceToBox, ActionCutToCamera, ActionCutToRemote, ActionFadeDownPersistedAudioLevels, + ActionFadeDownSoundPlayer, ActionRecallLastDVE, ActionRecallLastLive, ActionSelectDVELayout, @@ -39,8 +41,8 @@ export class GlobalAdlibActionsGenerator { this.config.sources.cameras .slice(0, 5) // the first x cameras to create INP1/2/3 cam-adlibs from .forEach((camera) => { - blueprintActions.push(this.makeCutDirectlyCameraAction(camera, globalRank++)) - blueprintActions.push(this.makeQueueAsNextCameraAction(camera, globalRank++)) + blueprintActions.push(this.makeCutCameraDirectlyAction(camera, globalRank++)) + blueprintActions.push(this.makeQueueCameraAsNextAction(camera, globalRank++)) }) this.config.sources.cameras @@ -53,7 +55,8 @@ export class GlobalAdlibActionsGenerator { .slice(0, 10) // the first x remote to create INP1/2/3 live-adlibs from .forEach((live) => { blueprintActions.push(...this.makeAdlibBoxesActions(live, globalRank++)) - blueprintActions.push(this.makeCutDirectLiveAction(live, globalRank++)) + blueprintActions.push(this.makeCutDirectlyLiveAction(live, globalRank++)) + blueprintActions.push(this.makeQueueAsNextLiveAction(live, globalRank++)) }) this.config.sources.feeds @@ -75,6 +78,7 @@ export class GlobalAdlibActionsGenerator { blueprintActions.push(this.makeClearGraphicsAction()) blueprintActions.push(this.makeClearGraphicsAltudAction()) + blueprintActions.push(this.makeClearGraphicsTemaudAction()) blueprintActions.push(...GetTransitionAdLibActions(this.config, 800)) @@ -84,18 +88,27 @@ export class GlobalAdlibActionsGenerator { blueprintActions.push(this.makePersistedAudioLevelsAction()) blueprintActions.push(this.makeRobotPresetAction()) + blueprintActions.push(this.makeFadeSoundPlayerAction()) return blueprintActions } - private makeCutDirectlyCameraAction(cameraSourceInfo: SourceInfo, rank: number): IBlueprintActionManifest { + private makeCutCameraDirectlyAction(cameraSourceInfo: SourceInfo, rank: number): IBlueprintActionManifest { return this.makeCutCameraAction(cameraSourceInfo, true, rank) } - private makeQueueAsNextCameraAction(cameraSourceInfo: SourceInfo, rank: number): IBlueprintActionManifest { + private makeQueueCameraAsNextAction(cameraSourceInfo: SourceInfo, rank: number): IBlueprintActionManifest { return this.makeCutCameraAction(cameraSourceInfo, false, rank) } + private makeCutDirectlyLiveAction(cameraSourceInfo: SourceInfo, rank: number): IBlueprintActionManifest { + return this.makeCutLiveAction(cameraSourceInfo, true, rank) + } + + private makeQueueAsNextLiveAction(cameraSourceInfo: SourceInfo, rank: number): IBlueprintActionManifest { + return this.makeCutLiveAction(cameraSourceInfo, false, rank) + } + private makeCutCameraAction( cameraSourceInfo: SourceInfo, cutDirectly: boolean, @@ -123,11 +136,11 @@ export class GlobalAdlibActionsGenerator { } } - private makeCutDirectLiveAction(info: SourceInfo, rank: number): IBlueprintActionManifest { + private makeCutLiveAction(info: SourceInfo, cutDirectly: boolean, rank: number): IBlueprintActionManifest { const sourceDefinition = SourceInfoToSourceDefinition(info) as SourceDefinitionRemote const userData: ActionCutToRemote = { type: AdlibActionType.CUT_TO_REMOTE, - cutDirectly: true, + cutDirectly, sourceDefinition } return { @@ -141,7 +154,7 @@ export class GlobalAdlibActionsGenerator { sourceLayerId: SourceLayer.PgmLive, outputLayerId: SharedOutputLayer.PGM, content: {}, - tags: [AdlibTags.ADLIB_CUT_DIRECT] + tags: cutDirectly ? [AdlibTags.ADLIB_CUT_DIRECT] : [AdlibTags.ADLIB_QUEUE_NEXT] } } } @@ -257,14 +270,14 @@ export class GlobalAdlibActionsGenerator { } private makeClearGraphicsAction(): IBlueprintActionManifest { - const userData: ActionClearGraphics = { - type: AdlibActionType.CLEAR_GRAPHICS, + const userData: ActionClearAllGraphics = { + type: AdlibActionType.CLEAR_ALL_GRAPHICS, sendCommands: true, label: 'GFX Clear' } return { externalId: generateExternalId(this.context.core, userData), - actionId: AdlibActionType.CLEAR_GRAPHICS, + actionId: AdlibActionType.CLEAR_ALL_GRAPHICS, userData, userDataManifest: {}, display: { @@ -281,14 +294,14 @@ export class GlobalAdlibActionsGenerator { } private makeClearGraphicsAltudAction(): IBlueprintActionManifest { - const userData: ActionClearGraphics = { - type: AdlibActionType.CLEAR_GRAPHICS, + const userData: ActionClearAllGraphics = { + type: AdlibActionType.CLEAR_ALL_GRAPHICS, sendCommands: false, label: 'GFX Altud' } return { externalId: generateExternalId(this.context.core, userData), - actionId: AdlibActionType.CLEAR_GRAPHICS, + actionId: AdlibActionType.CLEAR_ALL_GRAPHICS, userData, userDataManifest: {}, display: { @@ -304,6 +317,28 @@ export class GlobalAdlibActionsGenerator { } } + private makeClearGraphicsTemaudAction(): IBlueprintActionManifest { + const userData: ActionClearTemaGraphics = { + type: AdlibActionType.CLEAR_TEMA_GRAPHICS + } + return { + externalId: generateExternalId(this.context.core, userData), + actionId: AdlibActionType.CLEAR_TEMA_GRAPHICS, + userData, + userDataManifest: {}, + display: { + _rank: 400, + label: t(`GFX Temaud`), + sourceLayerId: SourceLayer.PgmAdlibGraphicCmd, + outputLayerId: SharedOutputLayer.SEC, + content: {}, + tags: [AdlibTags.ADLIB_GFX_TEMAUD], + currentPieceTags: [TallyTags.GFX_TEMAUD], + nextPieceTags: [TallyTags.GFX_TEMAUD] + } + } + } + private makeLastDveAction(): IBlueprintActionManifest { const recallLastLiveDveUserData: ActionRecallLastDVE = { type: AdlibActionType.RECALL_LAST_DVE @@ -342,6 +377,25 @@ export class GlobalAdlibActionsGenerator { } } + private makeFadeSoundPlayerAction(): IBlueprintActionManifest { + const fadeDownSoundPlayer: ActionFadeDownSoundPlayer = { + type: AdlibActionType.FADE_DOWN_SOUND_PLAYER + } + return { + externalId: generateExternalId(this.context.core, fadeDownSoundPlayer), + actionId: AdlibActionType.FADE_DOWN_SOUND_PLAYER, + userData: fadeDownSoundPlayer, + userDataManifest: {}, + display: { + _rank: 400, + label: t(`Fade down sound player`), + sourceLayerId: SourceLayer.PgmAudioBed, + outputLayerId: SharedOutputLayer.MUSIK, + tags: [] + } + } + } + private makeDveLayoutActions(): IBlueprintActionManifest[] { const blueprintActions: IBlueprintActionManifest[] = [] _.each(this.config.showStyle.DVEStyles, (dveConfig, i) => { diff --git a/src/tv2_afvd_showstyle/__tests__/config-manifest.spec.ts b/src/tv2_afvd_showstyle/__tests__/config-manifest.spec.ts index 507701a3f..261dce54d 100644 --- a/src/tv2_afvd_showstyle/__tests__/config-manifest.spec.ts +++ b/src/tv2_afvd_showstyle/__tests__/config-manifest.spec.ts @@ -20,8 +20,8 @@ const blankShowStyleConfig: GalleryShowStyleConfig = { GfxDefaults: [ { DefaultSetupName: { value: '', label: '' }, - DefaultDesign: '', - DefaultSchema: '' + DefaultDesign: { value: '', label: '' }, + DefaultSchema: { value: '', label: '' } } ] } diff --git a/src/tv2_afvd_showstyle/__tests__/configs.ts b/src/tv2_afvd_showstyle/__tests__/configs.ts index a717fbc1f..08c94a42c 100644 --- a/src/tv2_afvd_showstyle/__tests__/configs.ts +++ b/src/tv2_afvd_showstyle/__tests__/configs.ts @@ -127,7 +127,8 @@ export const defaultStudioConfig: StudioConfig = { AudioBedSettings: { fadeIn: 1000, fadeOut: 1000, - volume: 80 + volume: 80, + useAudioFilterSyntax: false }, CasparPrerollDuration: 280, GraphicsType: 'VIZ', @@ -232,8 +233,9 @@ export const defaultShowStyleConfig: GalleryShowStyleConfig = { ], GfxDesignTemplates: [ { + _id: '', INewsName: 'DESIGN_FODBOLD_22', - INewsStyleColumn: '', + INewsStyleColumn: 'F_22', VizTemplate: 'DESIGN_FODBOLD_22' } ], @@ -273,13 +275,21 @@ export const defaultShowStyleConfig: GalleryShowStyleConfig = { GfxSetups: [DEFAULT_GFX_SETUP], Transitions: [{ Transition: '1' }, { Transition: '2' }], ShowstyleTransition: 'CUT', - GfxSchemaTemplates: [], + GfxSchemaTemplates: [ + { + _id: 'SkemaNewsId', + GfxSchemaTemplatesName: 'SKEMA_NEWS', + VizTemplate: 'NE', + INewsSkemaColumn: 'SKEMA_NEWS', + CasparCgDesignValues: '[{}]' + } + ], GfxShowMapping: [], GfxDefaults: [ { DefaultSetupName: { value: 'SomeId', label: 'SomeProfile' }, - DefaultDesign: '', - DefaultSchema: '' + DefaultDesign: { value: '', label: '' }, + DefaultSchema: { value: 'SkemaNewsId', label: 'SKEMA_NEWS' } } ] } diff --git a/src/tv2_afvd_showstyle/config-manifests.ts b/src/tv2_afvd_showstyle/config-manifests.ts index 0b071afab..2c6794703 100644 --- a/src/tv2_afvd_showstyle/config-manifests.ts +++ b/src/tv2_afvd_showstyle/config-manifests.ts @@ -1,5 +1,12 @@ import { ConfigManifestEntry, ConfigManifestEntryType, TSR } from 'blueprints-integration' -import { DEFAULT_GRAPHICS, getGfxDefaults, getGfxSetupsEntries, ShowStyleConfigId } from 'tv2-common' +import { + DEFAULT_GRAPHICS, + getGfxDefaults, + getGfxSetupsEntries, + gfxDesignTemplates, + gfxSchemaTemplates, + gfxShowMapping +} from 'tv2-common' export const dveStylesManifest: ConfigManifestEntry = { id: 'DVEStyles', @@ -177,140 +184,6 @@ const gfxSetups = getGfxSetupsEntries([ const gfxDefaults = getGfxDefaults -const DESIGN_TABLE_ID = 'GfxDesignTemplates' -const DESIGN_NAME_COLUMN_ID = 'INewsName' - -export const gfxDesignTemplates: ConfigManifestEntry[] = [ - { - id: DESIGN_TABLE_ID, - name: 'GFX Design Templates', - description: '', - type: ConfigManifestEntryType.TABLE, - required: true, - defaultVal: DEFAULT_GRAPHICS.map((val) => ({ _id: '', ...val })).filter((template) => template.IsDesign), - columns: [ - { - id: DESIGN_NAME_COLUMN_ID, - name: 'iNews Name', - description: 'The name of the design', - type: ConfigManifestEntryType.STRING, - required: false, - defaultVal: '', - rank: 0 - }, - { - id: 'INewsStyleColumn', - name: 'iNews Style Column', - description: 'The selected style', - type: ConfigManifestEntryType.STRING, - required: false, - defaultVal: '', - rank: 1 - }, - { - id: 'VizTemplate', - name: 'GFX Template Name', - description: 'The name of the Viz Template', - type: ConfigManifestEntryType.STRING, - required: true, - defaultVal: '', - rank: 2 - } - ] - } -] - -const GFX_SCHEMA_TABLE_ID = 'GfxSchemaTemplates' -const GFX_SCHEMA_NAME_COLUMN_ID = 'GfxSchemaTemplatesName' - -export const gfxSchemaTemplates: ConfigManifestEntry[] = [ - { - id: GFX_SCHEMA_TABLE_ID, - name: 'GFX Skema Templates', - description: 'The values for the Skema and Design combinations', - type: ConfigManifestEntryType.TABLE, - required: false, - defaultVal: [], - columns: [ - { - id: GFX_SCHEMA_NAME_COLUMN_ID, - name: 'iNews Name', - description: 'The name of the design', - type: ConfigManifestEntryType.STRING, - required: false, - defaultVal: '', - rank: 0 - }, - { - id: 'INewsSkemaColumn', - name: 'iNews Skema Column', - description: 'The selected skema', - type: ConfigManifestEntryType.STRING, - required: false, - defaultVal: '', - rank: 1 - }, - { - id: 'VizTemplate', - name: 'GFX Template Name', - description: 'The name of the Viz Template', - type: ConfigManifestEntryType.STRING, - required: true, - defaultVal: '', - rank: 2 - } - ] - } -] - -export const gfxShowMapping: ConfigManifestEntry = { - id: 'GfxShowMapping', - name: 'GFX Show mapping', - description: - 'Maps Overlay Shows to the variety of Skemas and Designs. If a Setup does not have a corresponding Design/Skema, it should be left out of this table.', - type: ConfigManifestEntryType.TABLE, - required: false, - defaultVal: [], - columns: [ - { - id: ShowStyleConfigId.GFX_SHOW_MAPPING_DESIGN_COLUMN_ID, - name: 'Design', - rank: 0, - description: 'Name of the Design from the GFX Design table', - type: ConfigManifestEntryType.SELECT_FROM_COLUMN, - tableId: DESIGN_TABLE_ID, - columnId: DESIGN_NAME_COLUMN_ID, - multiple: false, - required: false, - defaultVal: '' - }, - { - id: ShowStyleConfigId.GFX_SHOW_MAPPING_GFX_SETUP_COLUMN_ID, - name: 'GFX Setup', - rank: 1, - description: 'Names of the GFX Setups', - type: ConfigManifestEntryType.SELECT_FROM_COLUMN, - tableId: ShowStyleConfigId.GRAPHICS_SETUPS_TABLE_ID, - columnId: ShowStyleConfigId.GRAPHICS_SETUPS_NAME_COLUMN_ID, - multiple: true, - required: false, - defaultVal: [] - }, - { - id: ShowStyleConfigId.GFX_SHOW_MAPPING_SCHEMA_COLUMN_ID, - name: 'GFX Skema Templates', - rank: 2, - description: 'Names of the Skemas', - type: ConfigManifestEntryType.SELECT_FROM_COLUMN, - tableId: GFX_SCHEMA_TABLE_ID, - columnId: GFX_SCHEMA_NAME_COLUMN_ID, - multiple: true, - required: false, - defaultVal: [] - } - ] -} - export const showStyleConfigManifest: ConfigManifestEntry[] = [ { id: 'MakeAdlibsForFulls', @@ -402,7 +275,7 @@ export const showStyleConfigManifest: ConfigManifestEntry[] = [ 'The Sofie Layer mapping to use in playback. This will ensure proper viz transition logic by matching the viz layers.', type: ConfigManifestEntryType.LAYER_MAPPINGS, filters: { - deviceTypes: [TSR.DeviceType.VIZMSE] + deviceTypes: [TSR.DeviceType.VIZMSE, TSR.DeviceType.CASPARCG] }, multiple: false, required: true, diff --git a/src/tv2_afvd_showstyle/getRundown.ts b/src/tv2_afvd_showstyle/getRundown.ts index 82d180b27..6ad285684 100644 --- a/src/tv2_afvd_showstyle/getRundown.ts +++ b/src/tv2_afvd_showstyle/getRundown.ts @@ -1,15 +1,12 @@ import { BlueprintResultBaseline, BlueprintResultRundown, - GraphicsContent, IBlueprintAdLibPiece, IngestRundown, IShowStyleUserContext, PieceLifespan, PlaylistTimingType, - TimelineObjectCoreExt, - TSR, - WithTimeline + TSR } from 'blueprints-integration' import { CasparPlayerClipLoadingLoop, @@ -20,7 +17,6 @@ import { findDskJingle, getGraphicBaseline, getMixMinusTimelineObject, - GetSisyfosTimelineObjForRemote, GetSisyfosTimelineObjForReplay, literal, MixMinusPriority, @@ -33,15 +29,10 @@ import { SwitcherType, TransitionStyle } from 'tv2-common' -import { - AdlibTags, - CONSTANTS, - ControlClasses, - SharedGraphicLLayer, - SharedOutputLayer, - SwitcherAuxLLayer -} from 'tv2-constants' +import { AdlibTags, CONSTANTS, SharedGraphicLLayer, SharedOutputLayer, SwitcherAuxLLayer } from 'tv2-constants' import * as _ from 'underscore' +import { GfxSchemaGenerator } from '../tv2-common/cues/gfx-schema-generator' +import { GfxSchemaGeneratorFacade } from '../tv2-common/cues/gfx-schema-generator-facade' import { getMixEffectBaseline } from '../tv2_afvd_studio/getBaseline' import { CasparLLayer, SisyfosLLAyer } from '../tv2_afvd_studio/layers' import { SisyfosChannel, sisyfosChannels } from '../tv2_afvd_studio/sisyfosChannels' @@ -51,6 +42,8 @@ import { GlobalAdlibActionsGenerator } from './GlobalAdlibActionsGenerator' import { GalleryBlueprintConfig } from './helpers/config' import { SourceLayer } from './layers' +const gfxSchemaGenerator: GfxSchemaGenerator = GfxSchemaGeneratorFacade.create() + export function getRundown(coreContext: IShowStyleUserContext, ingestRundown: IngestRundown): BlueprintResultRundown { const context = new ShowStyleContextImpl(coreContext, GALLERY_UNIFORM_CONFIG) return { @@ -77,12 +70,6 @@ class GlobalAdLibPiecesGenerator { const adLibPieces: IBlueprintAdLibPiece[] = [] let globalRank = 1000 - this.config.sources.lives - .slice(0, 10) // the first x lives to create live-adlibs from - .forEach((info) => { - adLibPieces.push(...this.makeRemoteAdLibs(info, globalRank++)) - }) - this.config.sources.lives .slice(0, 10) // the first x lives to create AUX1 (studio) adlibs .forEach((info) => { @@ -108,58 +95,6 @@ class GlobalAdLibPiecesGenerator { return adLibPieces } - // viz styles and dve backgrounds - public makeDesignAdLib(): IBlueprintAdLibPiece { - const timelineObjects: TimelineObjectCoreExt[] = [ - literal({ - id: '', - enable: { start: 0 }, - priority: 110, - layer: CasparLLayer.CasparCGDVELoop, - content: { - deviceType: TSR.DeviceType.CASPARCG, - type: TSR.TimelineContentTypeCasparCg.MEDIA, - file: 'dve/BG_LOADER_SC', - loop: true - } - }) - ] - // @todo: use GraphicsGenerator - if (this.config.studio.GraphicsType === 'VIZ') { - timelineObjects.push( - literal({ - id: '', - enable: { start: 0 }, - priority: 110, - layer: SharedGraphicLLayer.GraphicLLayerDesign, - content: { - deviceType: TSR.DeviceType.VIZMSE, - type: TSR.TimelineContentTypeVizMSE.ELEMENT_INTERNAL, - templateName: 'BG_LOADER_SC', - templateData: [], - showName: this.config.selectedGfxSetup.OvlShowName - } - }) - ) - } - const adLibPiece: IBlueprintAdLibPiece = { - _rank: 301, - externalId: 'dve-design-sc', - name: 'DVE Design SC', - outputLayerId: SharedOutputLayer.SEC, - sourceLayerId: SourceLayer.PgmDesign, - lifespan: PieceLifespan.OutOnShowStyleEnd, - tags: [AdlibTags.ADLIB_DESIGN_STYLE_SC], - content: literal>({ - fileName: 'BG_LOADER_SC', - path: 'BG_LOADER_SC', - ignoreMediaObjectStatus: true, - timelineObjects - }) - } - return adLibPiece - } - private makeEvsAdLib(info: SourceInfo, rank: number, vo: boolean): IBlueprintAdLibPiece { return { externalId: 'delayed', @@ -173,7 +108,7 @@ class GlobalAdLibPiecesGenerator { metaData: { sisyfosPersistMetaData: { sisyfosLayers: info.sisyfosLayers ?? [], - acceptPersistAudio: vo + acceptsPersistedAudio: vo } }, tags: [AdlibTags.ADLIB_QUEUE_NEXT, vo ? AdlibTags.ADLIB_VO_AUDIO_LEVEL : AdlibTags.ADLIB_FULL_AUDIO_LEVEL], @@ -244,45 +179,6 @@ class GlobalAdLibPiecesGenerator { } } - private makeRemoteAdLibs(info: SourceInfo, rank: number): Array> { - const res: Array> = [] - const eksternSisyfos = GetSisyfosTimelineObjForRemote(this.config, info) - res.push({ - externalId: 'live', - name: `LIVE ${info.id}`, - _rank: rank, - sourceLayerId: SourceLayer.PgmLive, - outputLayerId: SharedOutputLayer.PGM, - expectedDuration: 0, - lifespan: PieceLifespan.WithinPart, - toBeQueued: true, - metaData: { - sisyfosPersistMetaData: { - sisyfosLayers: info.sisyfosLayers ?? [], - wantsToPersistAudio: info.wantsToPersistAudio, - acceptPersistAudio: info.acceptPersistAudio - } - }, - tags: [AdlibTags.ADLIB_QUEUE_NEXT], - content: { - timelineObjects: [ - ...this.context.videoSwitcher.getOnAirTimelineObjectsWithLookahead({ - enable: { while: '1' }, - priority: 1, - content: { - input: info.port, - transition: TransitionStyle.CUT - }, - classes: [ControlClasses.OVERRIDDEN_ON_MIX_MINUS] - }), - ...eksternSisyfos - ] - } - }) - - return res - } - // aux adlibs private makeRemoteAuxStudioAdLibs(info: SourceInfo, rank: number): Array> { const res: Array> = [] @@ -298,7 +194,7 @@ class GlobalAdLibPiecesGenerator { sisyfosPersistMetaData: { sisyfosLayers: info.sisyfosLayers ?? [], wantsToPersistAudio: info.wantsToPersistAudio, - acceptPersistAudio: info.acceptPersistAudio + acceptsPersistedAudio: info.acceptPersistAudio } }, tags: [AdlibTags.ADLIB_TO_STUDIO_SCREEN_AUX], @@ -475,7 +371,7 @@ class GlobalAdLibPiecesGenerator { name: 'Stop Soundplayer', _rank: 700, sourceLayerId: SourceLayer.PgmAudioBed, - outputLayerId: 'musik', + outputLayerId: SharedOutputLayer.MUSIK, expectedDuration: 1000, lifespan: PieceLifespan.WithinPart, tags: [AdlibTags.ADLIB_STOP_AUDIO_BED], @@ -504,10 +400,12 @@ class GlobalAdLibPiecesGenerator { function getBaseline(context: ShowStyleContext): BlueprintResultBaseline { const jingleDsk = findDskJingle(context.config) const fullGfxDsk = findDskFullGfx(context.config) + const selectedGfxSetup = context.config.selectedGfxSetup return { timelineObjects: _.compact([ - ...getGraphicBaseline(context.config), + ...getGraphicBaseline(context), + ...gfxSchemaGenerator.createBaselineTimelineObjectsFromGfxDefaults(context), // Default timeline ...getMixEffectBaseline(context, context.config.studio.SwitcherSource.Default), @@ -676,23 +574,6 @@ function getBaseline(context: ShowStyleContext): Bluepri } } }), - literal({ - id: '', - enable: { while: '1' }, - priority: 0, - layer: CasparLLayer.CasparCGDVELoop, - content: { - deviceType: TSR.DeviceType.CASPARCG, - type: TSR.TimelineContentTypeCasparCg.MEDIA, - file: 'empty', - transitions: { - inTransition: { - type: TSR.Transition.CUT, - duration: CONSTANTS.DefaultClipFadeOut - } - } - } - }), literal({ id: '', enable: { while: 1 }, @@ -782,6 +663,26 @@ function getBaseline(context: ShowStyleContext): Bluepri concept: context.config.selectedGfxSetup.VcpConcept } }) - ]) + ]), + expectedPlayoutItems: [ + { + deviceSubType: TSR.DeviceType.VIZMSE, + content: { + templateName: 'OUT_TEMA_H', + channel: 'OVL1', + templateData: [], + showName: selectedGfxSetup.OvlShowName + } + }, + { + deviceSubType: TSR.DeviceType.VIZMSE, + content: { + templateName: 'altud', + channel: 'OVL1', + templateData: [], + showName: selectedGfxSetup.OvlShowName + } + } + ] } } diff --git a/src/tv2_afvd_showstyle/helpers/config.ts b/src/tv2_afvd_showstyle/helpers/config.ts index 0e19d7ce6..19e442fbe 100644 --- a/src/tv2_afvd_showstyle/helpers/config.ts +++ b/src/tv2_afvd_showstyle/helpers/config.ts @@ -1,11 +1,5 @@ import { IBlueprintConfig, ICommonContext, TableConfigItemValue } from 'blueprints-integration' -import { - findGfxSetup, - TableConfigGfxSetup, - TableConfigItemGfxDefaults, - TableConfigItemGfxShowMapping, - TV2ShowstyleBlueprintConfigBase -} from 'tv2-common' +import { findGfxSetup, TableConfigGfxSetup, TV2ShowstyleBlueprintConfigBase } from 'tv2-common' import { GalleryStudioConfig } from '../../tv2_afvd_studio/helpers/config' export interface GalleryTableConfigGfxSetup extends TableConfigGfxSetup { @@ -22,8 +16,6 @@ export interface GalleryBlueprintConfig extends GalleryStudioConfig { export interface GalleryShowStyleConfig extends TV2ShowstyleBlueprintConfigBase { WipesConfig: TableConfigItemValue GfxSetups: GalleryTableConfigGfxSetup[] - GfxShowMapping: TableConfigItemGfxShowMapping[] - GfxDefaults: TableConfigItemGfxDefaults[] } export function preprocessConfig(context: ICommonContext, rawConfig: IBlueprintConfig): any { diff --git a/src/tv2_afvd_showstyle/helpers/pieces/__tests__/grafikViz.spec.ts b/src/tv2_afvd_showstyle/helpers/pieces/__tests__/grafikViz.spec.ts index 88555a877..09ad233a1 100644 --- a/src/tv2_afvd_showstyle/helpers/pieces/__tests__/grafikViz.spec.ts +++ b/src/tv2_afvd_showstyle/helpers/pieces/__tests__/grafikViz.spec.ts @@ -119,9 +119,6 @@ describe('grafik piece', () => { }, lifespan: PieceLifespan.WithinPart, metaData: { - sisyfosPersistMetaData: { - sisyfosLayers: [] - }, partType: PartType.Kam, pieceExternalId: dummyPart.externalId }, @@ -183,11 +180,6 @@ describe('grafik piece', () => { externalId: partId, name: 'bund - Odense\n - Copenhagen', lifespan: PieceLifespan.WithinPart, - metaData: literal({ - sisyfosPersistMetaData: { - sisyfosLayers: [] - } - }), outputLayerId: SharedOutputLayer.OVERLAY, sourceLayerId: SourceLayer.PgmGraphicsLower, uniquenessId: 'gfx_bund - Odense\n - Copenhagen_studio0_graphicsLower_overlay_commentator', @@ -223,11 +215,6 @@ describe('grafik piece', () => { externalId: partId, name: 'bund - Odense\n - Copenhagen', lifespan: PieceLifespan.WithinPart, - metaData: literal({ - sisyfosPersistMetaData: { - sisyfosLayers: [] - } - }), outputLayerId: SharedOutputLayer.OVERLAY, sourceLayerId: SourceLayer.PgmGraphicsLower, uniquenessId: 'gfx_bund - Odense\n - Copenhagen_studio0_graphicsLower_overlay_flow', @@ -289,11 +276,6 @@ describe('grafik piece', () => { externalId: partId, name: 'bund - Odense\n - Copenhagen', lifespan: PieceLifespan.WithinPart, - metaData: literal({ - sisyfosPersistMetaData: { - sisyfosLayers: [] - } - }), outputLayerId: SharedOutputLayer.OVERLAY, sourceLayerId: SourceLayer.PgmGraphicsLower, uniquenessId: 'gfx_bund - Odense\n - Copenhagen_studio0_graphicsLower_overlay_commentator', @@ -329,11 +311,6 @@ describe('grafik piece', () => { externalId: partId, name: 'bund - Odense\n - Copenhagen', lifespan: PieceLifespan.WithinPart, - metaData: literal({ - sisyfosPersistMetaData: { - sisyfosLayers: [] - } - }), outputLayerId: SharedOutputLayer.OVERLAY, sourceLayerId: SourceLayer.PgmGraphicsLower, uniquenessId: 'gfx_bund - Odense\n - Copenhagen_studio0_graphicsLower_overlay_flow', @@ -401,9 +378,6 @@ describe('grafik piece', () => { }, lifespan: PieceLifespan.WithinPart, metaData: { - sisyfosPersistMetaData: { - sisyfosLayers: [] - }, partType: PartType.Kam, pieceExternalId: dummyPart.externalId }, @@ -508,7 +482,7 @@ describe('grafik piece', () => { enable: { start: 10000 }, - lifespan: PieceLifespan.OutOnShowStyleEnd + lifespan: PieceLifespan.OutOnRundownChange }) expect( result.pieces[0].content.timelineObjects.find((tlObject) => tlObject.content.deviceType === TSR.DeviceType.VIZMSE) @@ -552,9 +526,6 @@ describe('grafik piece', () => { }, lifespan: PieceLifespan.WithinPart, metaData: { - sisyfosPersistMetaData: { - sisyfosLayers: [] - }, partType: PartType.Kam, pieceExternalId: dummyPart.externalId }, @@ -622,9 +593,6 @@ describe('grafik piece', () => { }, lifespan: PieceLifespan.WithinPart, metaData: { - sisyfosPersistMetaData: { - sisyfosLayers: [] - }, partType: PartType.Kam, pieceExternalId: dummyPart.externalId }, @@ -686,11 +654,6 @@ describe('grafik piece', () => { externalId: partId, name: 'tlftoptlive - Line 1\n - Line 2', lifespan: PieceLifespan.WithinPart, - metaData: literal({ - sisyfosPersistMetaData: { - sisyfosLayers: [] - } - }), outputLayerId: SharedOutputLayer.OVERLAY, sourceLayerId: SourceLayer.PgmGraphicsTop, expectedDuration: 5000, @@ -726,11 +689,6 @@ describe('grafik piece', () => { externalId: partId, name: 'tlftoptlive - Line 1\n - Line 2', lifespan: PieceLifespan.OutOnSegmentEnd, - metaData: literal({ - sisyfosPersistMetaData: { - sisyfosLayers: [] - } - }), outputLayerId: SharedOutputLayer.OVERLAY, sourceLayerId: SourceLayer.PgmGraphicsTop, tags: ['flow_producer'], diff --git a/src/tv2_afvd_showstyle/helpers/pieces/__tests__/telefon.spec.ts b/src/tv2_afvd_showstyle/helpers/pieces/__tests__/telefon.spec.ts index 2280a97e2..0b7d853ad 100644 --- a/src/tv2_afvd_showstyle/helpers/pieces/__tests__/telefon.spec.ts +++ b/src/tv2_afvd_showstyle/helpers/pieces/__tests__/telefon.spec.ts @@ -71,9 +71,6 @@ describe('telefon', () => { sourceLayerId: SourceLayer.PgmGraphicsLower, lifespan: PieceLifespan.WithinPart, metaData: { - sisyfosPersistMetaData: { - sisyfosLayers: [] - }, partType: PartType.Kam, pieceExternalId: dummyPart.externalId }, diff --git a/src/tv2_afvd_showstyle/helpers/pieces/design.ts b/src/tv2_afvd_showstyle/helpers/pieces/design.ts index a9015fee2..3e768e2a8 100644 --- a/src/tv2_afvd_showstyle/helpers/pieces/design.ts +++ b/src/tv2_afvd_showstyle/helpers/pieces/design.ts @@ -1,16 +1,12 @@ -import { IBlueprintActionManifest, IBlueprintAdLibPiece, IBlueprintPiece } from 'blueprints-integration' import { CueDefinitionGraphicDesign, EvaluateDesignBase, ShowStyleContext } from 'tv2-common' import * as _ from 'underscore' export function EvaluateCueDesign( context: ShowStyleContext, - pieces: IBlueprintPiece[], - adlibPieces: IBlueprintAdLibPiece[], - actions: IBlueprintActionManifest[], partId: string, parsedCue: CueDefinitionGraphicDesign, adlib?: boolean, rank?: number ) { - EvaluateDesignBase(context, pieces, adlibPieces, actions, partId, parsedCue, adlib, rank) + return EvaluateDesignBase(context, partId, parsedCue, adlib, rank) } diff --git a/src/tv2_afvd_showstyle/helpers/pieces/dve.ts b/src/tv2_afvd_showstyle/helpers/pieces/dve.ts index bd3a27640..800babd1b 100644 --- a/src/tv2_afvd_showstyle/helpers/pieces/dve.ts +++ b/src/tv2_afvd_showstyle/helpers/pieces/dve.ts @@ -97,9 +97,6 @@ export function EvaluateDVE( name: pieceName, videoId: partDefinition.fields.videoId, segmentExternalId: partDefinition.segmentExternalId - }, - sisyfosPersistMetaData: { - sisyfosLayers: [] } } }) diff --git a/src/tv2_afvd_showstyle/helpers/pieces/evaluateCues.ts b/src/tv2_afvd_showstyle/helpers/pieces/evaluateCues.ts index fbfc1a158..52e5ae2d0 100644 --- a/src/tv2_afvd_showstyle/helpers/pieces/evaluateCues.ts +++ b/src/tv2_afvd_showstyle/helpers/pieces/evaluateCues.ts @@ -15,6 +15,7 @@ import { PartDefinition, ShowStyleContext } from 'tv2-common' +import { GfxSchemaGeneratorFacade } from '../../../tv2-common/cues/gfx-schema-generator-facade' import { GalleryBlueprintConfig } from '../../../tv2_afvd_showstyle/helpers/config' import { EvaluateAdLib } from './adlib' import { EvaluateClearGrafiks } from './clearGrafiks' @@ -50,6 +51,8 @@ export async function EvaluateCues( EvaluateCueGraphic, EvaluateCueBackgroundLoop, EvaluateCueGraphicDesign: EvaluateCueDesign, + EvaluateCueGraphicSchema: (cue, piece, partId, parsedCue) => + GfxSchemaGeneratorFacade.create().createBlueprintPieceFromGfxSchemaCue(cue, piece, partId, parsedCue), EvaluateCueRouting, EvaluateCueMixMinus, EvaluateCueRobotCamera diff --git a/src/tv2_afvd_showstyle/helpers/pieces/graphicBackgroundLoop.ts b/src/tv2_afvd_showstyle/helpers/pieces/graphicBackgroundLoop.ts index 84682cd8d..383d9b95b 100644 --- a/src/tv2_afvd_showstyle/helpers/pieces/graphicBackgroundLoop.ts +++ b/src/tv2_afvd_showstyle/helpers/pieces/graphicBackgroundLoop.ts @@ -1,49 +1,47 @@ +import { GraphicsContent, PieceLifespan, TSR, WithTimeline } from 'blueprints-integration' import { - GraphicsContent, - IBlueprintActionManifest, - IBlueprintAdLibPiece, - IBlueprintPiece, - PieceLifespan, - TSR, - WithTimeline -} from 'blueprints-integration' -import { calculateTime, CueDefinitionBackgroundLoop, literal, ShowStyleContext, TV2ShowStyleConfig } from 'tv2-common' + calculateTime, + CueDefinitionBackgroundLoop, + DveLoopGenerator, + EvaluateCueResult, + literal, + ShowStyleContext, + TV2ShowStyleConfig +} from 'tv2-common' import { SharedGraphicLLayer, SharedOutputLayer } from 'tv2-constants' -import { CasparLLayer } from '../../../tv2_afvd_studio/layers' import { SourceLayer } from '../../layers' export function EvaluateCueBackgroundLoop( context: ShowStyleContext, - pieces: IBlueprintPiece[], - adlibPieces: IBlueprintAdLibPiece[], - _actions: IBlueprintActionManifest[], partId: string, parsedCue: CueDefinitionBackgroundLoop, adlib?: boolean, rank?: number -) { +): EvaluateCueResult { + const result = new EvaluateCueResult() const start = (parsedCue.start ? calculateTime(parsedCue.start) : 0) ?? 0 if (parsedCue.target === 'DVE') { + const dveLoopGenerator = new DveLoopGenerator() // todo: where to instantiate it? const fileName = parsedCue.backgroundLoop const path = `dve/${fileName}` if (adlib) { - adlibPieces.push({ - _rank: rank || 0, + result.adlibPieces.push({ + _rank: rank ?? 0, externalId: partId, name: fileName, outputLayerId: SharedOutputLayer.SEC, sourceLayerId: SourceLayer.PgmDVEBackground, - lifespan: PieceLifespan.OutOnShowStyleEnd, + lifespan: PieceLifespan.OutOnRundownChange, content: literal>({ fileName, path, ignoreMediaObjectStatus: true, - timelineObjects: dveLoopTimeline(path) + timelineObjects: dveLoopGenerator.createDveLoopTimelineObject(fileName) }) }) } else { - pieces.push({ + result.pieces.push({ externalId: partId, name: fileName, enable: { @@ -51,25 +49,25 @@ export function EvaluateCueBackgroundLoop( }, outputLayerId: SharedOutputLayer.SEC, sourceLayerId: SourceLayer.PgmDVEBackground, - lifespan: PieceLifespan.OutOnShowStyleEnd, + lifespan: PieceLifespan.OutOnRundownChange, content: literal>({ fileName, path, ignoreMediaObjectStatus: true, - timelineObjects: dveLoopTimeline(path) + timelineObjects: dveLoopGenerator.createDveLoopTimelineObject(fileName) }) }) } } else { // Full if (adlib) { - adlibPieces.push({ - _rank: rank || 0, + result.adlibPieces.push({ + _rank: rank ?? 0, externalId: partId, name: parsedCue.backgroundLoop, outputLayerId: SharedOutputLayer.SEC, sourceLayerId: SourceLayer.PgmFullBackground, - lifespan: PieceLifespan.OutOnShowStyleEnd, + lifespan: PieceLifespan.OutOnRundownChange, content: literal>({ fileName: parsedCue.backgroundLoop, path: parsedCue.backgroundLoop, @@ -78,7 +76,7 @@ export function EvaluateCueBackgroundLoop( }) }) } else { - pieces.push({ + result.pieces.push({ externalId: partId, name: parsedCue.backgroundLoop, enable: { @@ -86,7 +84,7 @@ export function EvaluateCueBackgroundLoop( }, outputLayerId: SharedOutputLayer.SEC, sourceLayerId: SourceLayer.PgmFullBackground, - lifespan: PieceLifespan.OutOnShowStyleEnd, + lifespan: PieceLifespan.OutOnRundownChange, content: literal>({ fileName: parsedCue.backgroundLoop, path: parsedCue.backgroundLoop, @@ -96,23 +94,7 @@ export function EvaluateCueBackgroundLoop( }) } } -} - -function dveLoopTimeline(path: string): TSR.TSRTimelineObj[] { - return [ - literal({ - id: '', - enable: { start: 0 }, - priority: 100, - layer: CasparLLayer.CasparCGDVELoop, - content: { - deviceType: TSR.DeviceType.CASPARCG, - type: TSR.TimelineContentTypeCasparCg.MEDIA, - file: path, - loop: true - } - }) - ] + return result } function fullLoopTimeline(config: TV2ShowStyleConfig, parsedCue: CueDefinitionBackgroundLoop): TSR.TSRTimelineObj[] { diff --git a/src/tv2_afvd_showstyle/migrations/sourcelayer-defaults.ts b/src/tv2_afvd_showstyle/migrations/sourcelayer-defaults.ts index 9d7a98c42..2ccb5b060 100644 --- a/src/tv2_afvd_showstyle/migrations/sourcelayer-defaults.ts +++ b/src/tv2_afvd_showstyle/migrations/sourcelayer-defaults.ts @@ -449,6 +449,14 @@ const SEC: ISourceLayer[] = [ allowDisable: false, onPresenterScreen: false }, + { + _id: SourceLayer.PgmSchema, + _rank: 30, + name: 'Viz Schema', + abbreviation: '', + type: SourceLayerType.UNKNOWN, + isHidden: true + }, { _id: SourceLayer.PgmDVEBackground, _rank: 40, diff --git a/src/tv2_afvd_showstyle/parts/kam.ts b/src/tv2_afvd_showstyle/parts/kam.ts index ee7d598c8..6c4f5f249 100644 --- a/src/tv2_afvd_showstyle/parts/kam.ts +++ b/src/tv2_afvd_showstyle/parts/kam.ts @@ -53,11 +53,6 @@ export async function CreatePartKam( outputLayerId: SharedOutputLayer.PGM, sourceLayerId: SourceLayer.PgmJingle, lifespan: PieceLifespan.WithinPart, - metaData: { - sisyfosPersistMetaData: { - sisyfosLayers: [] - } - }, content: literal>({ ignoreMediaObjectStatus: true, fileName: '', @@ -95,7 +90,7 @@ export async function CreatePartKam( metaData: { sisyfosPersistMetaData: { sisyfosLayers: sourceInfoCam.sisyfosLayers ?? [], - acceptPersistAudio: sourceInfoCam.acceptPersistAudio + acceptsPersistedAudio: sourceInfoCam.acceptPersistAudio } }, content: { diff --git a/src/tv2_afvd_studio/__tests__/config-manifest.spec.ts b/src/tv2_afvd_studio/__tests__/config-manifest.spec.ts index f8947a852..d95b6ba98 100644 --- a/src/tv2_afvd_studio/__tests__/config-manifest.spec.ts +++ b/src/tv2_afvd_studio/__tests__/config-manifest.spec.ts @@ -59,7 +59,8 @@ const blankStudioConfig: StudioConfig = { AudioBedSettings: { fadeIn: 0, fadeOut: 0, - volume: 0 + volume: 0, + useAudioFilterSyntax: false }, CasparPrerollDuration: 0, PreventOverlayWithFull: true, diff --git a/src/tv2_afvd_studio/__tests__/graphics.spec.ts b/src/tv2_afvd_studio/__tests__/graphics.spec.ts index 309f81ab4..bfe7983aa 100644 --- a/src/tv2_afvd_studio/__tests__/graphics.spec.ts +++ b/src/tv2_afvd_studio/__tests__/graphics.spec.ts @@ -188,7 +188,7 @@ describe('Graphics', () => { expect(piece.outputLayerId).toBe(SharedOutputLayer.OVERLAY) expect(piece.enable).toEqual({ start: 2000 }) expect(piece.prerollDuration).toBe(context.config.studio.VizPilotGraphics.PrerollDuration) - expect(piece.lifespan).toBe(PieceLifespan.OutOnShowStyleEnd) + expect(piece.lifespan).toBe(PieceLifespan.OutOnRundownChange) const content = piece.content! const timeline = content.timelineObjects as TSR.TSRTimelineObj[] expect(timeline).toHaveLength(1) @@ -244,7 +244,7 @@ describe('Graphics', () => { expect(piece.outputLayerId).toBe(SharedOutputLayer.SEC) expect(piece.enable).toEqual({ start: 0 }) expect(piece.prerollDuration).toBe(context.config.studio.VizPilotGraphics.PrerollDuration) - expect(piece.lifespan).toBe(PieceLifespan.OutOnShowStyleEnd) + expect(piece.lifespan).toBe(PieceLifespan.OutOnRundownChange) const content = piece.content! const timeline = content.timelineObjects as TSR.TSRTimelineObj[] expect(timeline).toHaveLength(1) @@ -397,10 +397,40 @@ describe('Graphics', () => { expect(piece).toBeTruthy() expect(piece.outputLayerId).toBe(SharedOutputLayer.SEC) expect(piece.sourceLayerId).toBe(SourceLayer.PgmDesign) - expect(piece.lifespan).toBe(PieceLifespan.OutOnShowStyleEnd) + expect(piece.lifespan).toBe(PieceLifespan.OutOnRundownChange) expect(piece.enable).toEqual({ start: 0 }) }) + test('Design from field(column) has OutOnRundownChangeWithSegmentLookback lifespan', async () => { + const context = makeMockGalleryContext() + + const cues: CueDefinition[] = [ + literal({ + type: CueType.GraphicDesign, + design: 'DESIGN_FODBOLD', + iNewsCommand: 'KG', + isFromField: true + }) + ] + + const partDefinition: PartDefinition = literal({ + type: PartType.Unknown, + externalId: '', + segmentExternalId: SEGMENT_EXTERNAL_ID, + rawType: '', + cues, + script: '', + fields: {}, + modified: 0, + storyName: '' + }) + + const result = await CreatePartUnknown(context, partDefinition, 0) + expect(result.pieces).toHaveLength(1) + const piece = result.pieces[0] + expect(piece.lifespan).toBe('rundown-change-segment-lookback') + }) + it('Creates background loop', async () => { const context = makeMockGalleryContext() @@ -432,7 +462,7 @@ describe('Graphics', () => { expect(piece.name).toBe('DESIGN_SC') expect(piece.outputLayerId).toBe(SharedOutputLayer.SEC) expect(piece.sourceLayerId).toBe(SourceLayer.PgmDVEBackground) - expect(piece.lifespan).toBe(PieceLifespan.OutOnShowStyleEnd) + expect(piece.lifespan).toBe(PieceLifespan.OutOnRundownChange) const tlObj = (piece.content?.timelineObjects as TSR.TSRTimelineObj[]).find( (obj) => obj.content.deviceType === TSR.DeviceType.CASPARCG && obj.content.type === TSR.TimelineContentTypeCasparCg.MEDIA diff --git a/src/tv2_afvd_studio/config-manifests.ts b/src/tv2_afvd_studio/config-manifests.ts index 33eb18ba8..7663379b4 100644 --- a/src/tv2_afvd_studio/config-manifests.ts +++ b/src/tv2_afvd_studio/config-manifests.ts @@ -401,6 +401,14 @@ export const studioConfigManifest: ConfigManifestEntry[] = [ required: false, defaultVal: 25 }, + { + id: 'AudioBedSettings.useAudioFilterSyntax', + name: 'Use Audio Filter Syntax', + description: 'Required for CasparCG 2.3 and higher', + type: ConfigManifestEntryType.BOOLEAN, + required: false, + defaultVal: false + }, { id: 'CasparPrerollDuration', name: 'Caspar preroll duration', diff --git a/src/tv2_afvd_studio/layers.ts b/src/tv2_afvd_studio/layers.ts index 812eebaa6..eb218cc21 100644 --- a/src/tv2_afvd_studio/layers.ts +++ b/src/tv2_afvd_studio/layers.ts @@ -4,7 +4,6 @@ import * as _ from 'underscore' export enum VirtualAbstractLLayer {} enum AFVDCasparLLayer { - CasparCGDVELoop = 'casparcg_dve_loop', CasparCGFullBg = 'casparcg_full_bg', CasparCGDVEKey = 'casparcg_dve_key', CasparCGDVEFrame = 'casparcg_dve_frame', @@ -35,7 +34,6 @@ export type GraphicLLayer = AFVDGraphicLLayer | SharedGraphicLLayer enum AFVDSisyfosLLayer { SisyfosConfig = 'sisyfos_config', - SisyfosPersistedLevels = 'sisyfos_persisted_levels', SisyfosSourceClipPending = 'sisyfos_source_clip_pending', SisyfosSourceJingle = 'sisyfos_source_jingle', SisyfosSourceTLF = 'sisyfos_source_tlf_hybrid', diff --git a/src/tv2_afvd_studio/migrations/mappings-defaults.ts b/src/tv2_afvd_studio/migrations/mappings-defaults.ts index acf90db09..1753784e4 100644 --- a/src/tv2_afvd_studio/migrations/mappings-defaults.ts +++ b/src/tv2_afvd_studio/migrations/mappings-defaults.ts @@ -541,6 +541,12 @@ export const MAPPINGS_GRAPHICS: BlueprintMappings = { layerName: 'GFX Design', lookahead: LookaheadMode.NONE }), + [GraphicLLayer.GraphicLLayerSchema]: literal({ + device: TSR.DeviceType.VIZMSE, + deviceId: 'viz0', + layerName: 'GFX Skema', + lookahead: LookaheadMode.NONE + }), [GraphicLLayer.GraphicLLayerAdLibs]: literal({ device: TSR.DeviceType.VIZMSE, deviceId: 'viz0', diff --git a/src/tv2_offtube_showstyle/__tests__/config-manifest.spec.ts b/src/tv2_offtube_showstyle/__tests__/config-manifest.spec.ts index ad83a3268..980523dd3 100644 --- a/src/tv2_offtube_showstyle/__tests__/config-manifest.spec.ts +++ b/src/tv2_offtube_showstyle/__tests__/config-manifest.spec.ts @@ -16,6 +16,7 @@ const blankShowStyleConfig: OfftubeShowStyleConfig = { MakeAdlibsForFulls: true, GfxSchemaTemplates: [], GfxSetups: [], + GfxShowMapping: [], GfxDefaults: [] } diff --git a/src/tv2_offtube_showstyle/config-manifests.ts b/src/tv2_offtube_showstyle/config-manifests.ts index b771d7fff..f3e25135b 100644 --- a/src/tv2_offtube_showstyle/config-manifests.ts +++ b/src/tv2_offtube_showstyle/config-manifests.ts @@ -1,5 +1,12 @@ import { ConfigManifestEntry, ConfigManifestEntryType, TSR } from 'blueprints-integration' -import { DEFAULT_GRAPHICS, getGfxDefaults, getGfxSetupsEntries } from 'tv2-common' +import { + DEFAULT_GRAPHICS, + getGfxDefaults, + getGfxSetupsEntries, + gfxDesignTemplates, + gfxSchemaTemplates, + gfxShowMapping +} from 'tv2-common' export const dveStylesManifest: ConfigManifestEntry = { id: 'DVEStyles', @@ -142,92 +149,6 @@ export const dveStylesManifest: ConfigManifestEntry = { ] } -const DESIGN_TABLE_ID = 'GfxDesignTemplates' -const DESIGN_NAME_COLUMN_ID = 'INewsName' - -export const gfxDesignTemplates: ConfigManifestEntry[] = [ - { - id: DESIGN_TABLE_ID, - name: 'GFX Design Templates', - description: '', - type: ConfigManifestEntryType.TABLE, - required: true, - defaultVal: DEFAULT_GRAPHICS.map((val) => ({ _id: '', ...val })).filter((template) => template.IsDesign), - columns: [ - { - id: DESIGN_NAME_COLUMN_ID, - name: 'iNews Name', - description: 'The name of the design', - type: ConfigManifestEntryType.STRING, - required: false, - defaultVal: '', - rank: 0 - }, - { - id: 'INewsStyleColumn', - name: 'iNews Style Column', - description: 'The selected style', - type: ConfigManifestEntryType.STRING, - required: false, - defaultVal: '', - rank: 1 - }, - { - id: 'VizTemplate', - name: 'GFX Template Name', - description: 'The name of the design in the HTML package', - type: ConfigManifestEntryType.STRING, - required: true, - defaultVal: '', - rank: 2 - } - ] - } -] - -const GFX_SCHEMA_TABLE_ID = 'GfxSchemaTemplates' -const GFX_SCHEMA_NAME_COLUMN_ID = 'GfxSchemaTemplatesName' - -export const gfxSchemaTemplates: ConfigManifestEntry[] = [ - { - id: GFX_SCHEMA_TABLE_ID, - name: 'GFX Skema Templates', - description: 'The values for the Skema and Design combinations', - type: ConfigManifestEntryType.TABLE, - required: false, - defaultVal: [], - columns: [ - { - id: GFX_SCHEMA_NAME_COLUMN_ID, - name: 'iNews Name', - description: 'The name of the design', - type: ConfigManifestEntryType.STRING, - required: false, - defaultVal: '', - rank: 0 - }, - { - id: 'INewsSkemaColumn', - name: 'iNews Skema Column', - description: 'The selected skema', - type: ConfigManifestEntryType.STRING, - required: false, - defaultVal: '', - rank: 1 - }, - { - id: 'VizTemplate', - name: 'GFX Template Name', - description: 'For future asset control', - type: ConfigManifestEntryType.STRING, - required: true, - defaultVal: '', - rank: 2 - } - ] - } -] - export const showStyleConfigManifest: ConfigManifestEntry[] = [ { id: 'MakeAdlibsForFulls', @@ -583,6 +504,7 @@ export const showStyleConfigManifest: ConfigManifestEntry[] = [ ] }, ...gfxSchemaTemplates, + gfxShowMapping, ...getGfxSetupsEntries([]), getGfxDefaults ] diff --git a/src/tv2_offtube_showstyle/cues/OfftubeDVE.ts b/src/tv2_offtube_showstyle/cues/OfftubeDVE.ts index 456e0e776..56da4ae67 100644 --- a/src/tv2_offtube_showstyle/cues/OfftubeDVE.ts +++ b/src/tv2_offtube_showstyle/cues/OfftubeDVE.ts @@ -77,9 +77,6 @@ export function OfftubeEvaluateDVE( name: parsedCue.template, videoId: partDefinition.fields.videoId, segmentExternalId: partDefinition.segmentExternalId - }, - sisyfosPersistMetaData: { - sisyfosLayers: [] } }), tags: [ diff --git a/src/tv2_offtube_showstyle/cues/OfftubeGraphicBackgroundLoop.ts b/src/tv2_offtube_showstyle/cues/OfftubeGraphicBackgroundLoop.ts index 57db7ea18..d643d7ebf 100644 --- a/src/tv2_offtube_showstyle/cues/OfftubeGraphicBackgroundLoop.ts +++ b/src/tv2_offtube_showstyle/cues/OfftubeGraphicBackgroundLoop.ts @@ -1,62 +1,45 @@ +import { GraphicsContent, PieceLifespan, WithTimeline } from 'blueprints-integration' import { - GraphicsContent, - IBlueprintActionManifest, - IBlueprintAdLibPiece, - IBlueprintPiece, - PieceLifespan, - TSR, - WithTimeline -} from 'blueprints-integration' -import { calculateTime, CueDefinitionBackgroundLoop, literal, SegmentContext } from 'tv2-common' + calculateTime, + CueDefinitionBackgroundLoop, + DveLoopGenerator, + EvaluateCueResult, + literal, + SegmentContext +} from 'tv2-common' import { SharedOutputLayer } from 'tv2-constants' -import _ = require('underscore') -import { OfftubeCasparLLayer } from '../../tv2_offtube_studio/layers' import { OfftubeBlueprintConfig } from '../helpers/config' import { OfftubeSourceLayer } from '../layers' export function OfftubeEvaluateCueBackgroundLoop( _context: SegmentContext, - pieces: IBlueprintPiece[], - adlibPieces: IBlueprintAdLibPiece[], - _actions: IBlueprintActionManifest[], partId: string, parsedCue: CueDefinitionBackgroundLoop, adlib?: boolean, rank?: number -) { +): EvaluateCueResult { + const result = new EvaluateCueResult() + const dveLoopGenerator = new DveLoopGenerator() const fileName = parsedCue.backgroundLoop const path = `dve/${fileName}` const start = (parsedCue.start ? calculateTime(parsedCue.start) : 0) ?? 0 if (adlib) { - adlibPieces.push({ - _rank: rank || 0, + result.adlibPieces.push({ + _rank: rank ?? 0, externalId: partId, name: fileName, outputLayerId: SharedOutputLayer.SEC, sourceLayerId: OfftubeSourceLayer.PgmDVEBackground, - lifespan: PieceLifespan.OutOnShowStyleEnd, + lifespan: PieceLifespan.OutOnRundownChange, content: literal>({ fileName, path, ignoreMediaObjectStatus: true, - timelineObjects: _.compact([ - literal({ - id: '', - enable: { start: 0 }, - priority: 100, - layer: OfftubeCasparLLayer.CasparCGDVELoop, - content: { - deviceType: TSR.DeviceType.CASPARCG, - type: TSR.TimelineContentTypeCasparCg.MEDIA, - file: path, - loop: true - } - }) - ]) + timelineObjects: dveLoopGenerator.createDveLoopTimelineObject(fileName) }) }) } else { - pieces.push({ + result.pieces.push({ externalId: partId, name: fileName, enable: { @@ -64,26 +47,14 @@ export function OfftubeEvaluateCueBackgroundLoop( }, outputLayerId: SharedOutputLayer.SEC, sourceLayerId: OfftubeSourceLayer.PgmDVEBackground, - lifespan: PieceLifespan.OutOnShowStyleEnd, + lifespan: PieceLifespan.OutOnRundownChange, content: literal>({ fileName, path, ignoreMediaObjectStatus: true, - timelineObjects: _.compact([ - literal({ - id: '', - enable: { start: 0 }, - priority: 100, - layer: OfftubeCasparLLayer.CasparCGDVELoop, - content: { - deviceType: TSR.DeviceType.CASPARCG, - type: TSR.TimelineContentTypeCasparCg.MEDIA, - file: path, - loop: true - } - }) - ]) + timelineObjects: dveLoopGenerator.createDveLoopTimelineObject(fileName) }) }) } + return result } diff --git a/src/tv2_offtube_showstyle/cues/OfftubeGraphicDesign.ts b/src/tv2_offtube_showstyle/cues/OfftubeGraphicDesign.ts index 2b75fe108..0b57ab914 100644 --- a/src/tv2_offtube_showstyle/cues/OfftubeGraphicDesign.ts +++ b/src/tv2_offtube_showstyle/cues/OfftubeGraphicDesign.ts @@ -1,16 +1,12 @@ -import { IBlueprintActionManifest, IBlueprintAdLibPiece, IBlueprintPiece } from 'blueprints-integration' import { CueDefinitionGraphicDesign, EvaluateDesignBase, SegmentContext } from 'tv2-common' import { OfftubeBlueprintConfig } from '../helpers/config' export function OfftubeEvaluateGraphicDesign( context: SegmentContext, - pieces: IBlueprintPiece[], - adlibPieces: IBlueprintAdLibPiece[], - actions: IBlueprintActionManifest[], partId: string, parsedCue: CueDefinitionGraphicDesign, adlib?: boolean, rank?: number ) { - EvaluateDesignBase(context, pieces, adlibPieces, actions, partId, parsedCue, adlib, rank) + return EvaluateDesignBase(context, partId, parsedCue, adlib, rank) } diff --git a/src/tv2_offtube_showstyle/cues/OfftubeJingle.ts b/src/tv2_offtube_showstyle/cues/OfftubeJingle.ts index f0383e258..c0165b677 100644 --- a/src/tv2_offtube_showstyle/cues/OfftubeJingle.ts +++ b/src/tv2_offtube_showstyle/cues/OfftubeJingle.ts @@ -80,11 +80,6 @@ export function OfftubeEvaluateJingle( lifespan: PieceLifespan.WithinPart, outputLayerId: SharedOutputLayer.JINGLE, sourceLayerId: OfftubeSourceLayer.PgmJingle, - metaData: { - sisyfosPersistMetaData: { - sisyfosLayers: [] - } - }, prerollDuration: context.config.studio.CasparPrerollDuration + getTimeFromFrames(Number(jingle.StartAlpha)), content: createJingleContentOfftube(context, file, jingle), tags: [ diff --git a/src/tv2_offtube_showstyle/getRundown.ts b/src/tv2_offtube_showstyle/getRundown.ts index ac0ad6ae1..d0f427db3 100644 --- a/src/tv2_offtube_showstyle/getRundown.ts +++ b/src/tv2_offtube_showstyle/getRundown.ts @@ -12,7 +12,7 @@ import { TSR } from 'blueprints-integration' import { - ActionClearGraphics, + ActionClearAllGraphics, ActionCommentatorSelectDVE, ActionCommentatorSelectFull, ActionCommentatorSelectJingle, @@ -57,6 +57,8 @@ import { TallyTags } from 'tv2-constants' import * as _ from 'underscore' +import { GfxSchemaGenerator } from '../tv2-common/cues/gfx-schema-generator' +import { GfxSchemaGeneratorFacade } from '../tv2-common/cues/gfx-schema-generator-facade' import { OfftubeBlueprintConfig } from '../tv2_offtube_showstyle/helpers/config' import { OfftubeCasparLLayer, OfftubeSisyfosLLayer } from '../tv2_offtube_studio/layers' import { SisyfosChannel, sisyfosChannels } from '../tv2_offtube_studio/sisyfosChannels' @@ -64,6 +66,8 @@ import { QBOX_UNIFORM_CONFIG } from '../tv2_offtube_studio/uniformConfig' import { NUMBER_OF_DVE_BOXES } from './content/OfftubeDVEContent' import { OfftubeOutputLayers, OfftubeSourceLayer } from './layers' +const gfxSchemaGenerator: GfxSchemaGenerator = GfxSchemaGeneratorFacade.create() + export function getRundown(coreContext: IShowStyleUserContext, ingestRundown: IngestRundown): BlueprintResultRundown { const context = new ShowStyleContextImpl(coreContext, QBOX_UNIFORM_CONFIG) @@ -425,14 +429,14 @@ function getGlobalAdlibActionsOfftube( blueprintActions.push(makeCommentatorSelectFullAction()) function makeClearGraphicsAltudAction(): IBlueprintActionManifest { - const userData: ActionClearGraphics = { - type: AdlibActionType.CLEAR_GRAPHICS, + const userData: ActionClearAllGraphics = { + type: AdlibActionType.CLEAR_ALL_GRAPHICS, sendCommands: false, label: 'GFX Altud' } return { externalId: generateExternalId(context, userData), - actionId: AdlibActionType.CLEAR_GRAPHICS, + actionId: AdlibActionType.CLEAR_ALL_GRAPHICS, userData, userDataManifest: {}, display: { @@ -583,7 +587,7 @@ function getGlobalAdlibActionsOfftube( function getBaseline(context: ShowStyleContext): BlueprintResultBaseline { return { timelineObjects: _.compact([ - ...getGraphicBaseline(context.config), + ...getGraphicBaseline(context), // Default timeline context.videoSwitcher.getMixEffectTimelineObject({ layer: context.uniformConfig.mixEffects.program.mixEffectLayer, @@ -709,23 +713,7 @@ function getBaseline(context: ShowStyleContext): Bluepri } } }), - literal({ - id: '', - enable: { while: '1' }, - priority: 0, - layer: OfftubeCasparLLayer.CasparCGDVELoop, - content: { - deviceType: TSR.DeviceType.CASPARCG, - type: TSR.TimelineContentTypeCasparCg.MEDIA, - file: 'empty', - transitions: { - inTransition: { - type: TSR.Transition.CUT, - duration: CONSTANTS.DefaultClipFadeOut - } - } - } - }), + ...gfxSchemaGenerator.createBaselineTimelineObjectsFromGfxDefaults(context), literal({ id: '', diff --git a/src/tv2_offtube_showstyle/helpers/EvaluateCues.ts b/src/tv2_offtube_showstyle/helpers/EvaluateCues.ts index afa2a0070..38b8e7884 100644 --- a/src/tv2_offtube_showstyle/helpers/EvaluateCues.ts +++ b/src/tv2_offtube_showstyle/helpers/EvaluateCues.ts @@ -13,6 +13,7 @@ import { PartDefinition, SegmentContext } from 'tv2-common' +import { GfxSchemaGeneratorFacade } from '../../tv2-common/cues/gfx-schema-generator-facade' import { OfftubeEvaluateAdLib } from '../cues/OfftubeAdlib' import { OfftubeEvaluateDVE } from '../cues/OfftubeDVE' import { OfftubeEvaluateEkstern } from '../cues/OfftubeEkstern' @@ -44,7 +45,9 @@ export async function OfftubeEvaluateCues( EvaluateCueBackgroundLoop: OfftubeEvaluateCueBackgroundLoop, EvaluateCueGraphicDesign: OfftubeEvaluateGraphicDesign, EvaluateCuePgmClean: OfftubeEvaluatePgmClean, - EvaluateCueLYD: EvaluateLYD + EvaluateCueLYD: EvaluateLYD, + EvaluateCueGraphicSchema: (cue, piece, partId, parsedCue) => + GfxSchemaGeneratorFacade.create().createBlueprintPieceFromGfxSchemaCue(cue, piece, partId, parsedCue) }, context, part, diff --git a/src/tv2_offtube_showstyle/migrations/sourcelayer-defaults.ts b/src/tv2_offtube_showstyle/migrations/sourcelayer-defaults.ts index 0de7574bf..49de9c039 100644 --- a/src/tv2_offtube_showstyle/migrations/sourcelayer-defaults.ts +++ b/src/tv2_offtube_showstyle/migrations/sourcelayer-defaults.ts @@ -1,6 +1,7 @@ import { ISourceLayer, SourceLayerType } from 'blueprints-integration' import { GetDSKSourceLayerDefaults, literal } from 'tv2-common' import { SharedSourceLayer } from 'tv2-constants' +import { SourceLayer } from '../../tv2_afvd_showstyle/layers' import { ATEMModel } from '../../types/atem' import { OfftubeSourceLayer } from '../layers' @@ -378,6 +379,14 @@ const SEC: ISourceLayer[] = [ allowDisable: false, onPresenterScreen: false }, + { + _id: SourceLayer.PgmSchema, + _rank: 30, + name: 'Caspar Schema', + abbreviation: '', + type: SourceLayerType.UNKNOWN, + isHidden: true + }, { _id: OfftubeSourceLayer.PgmSisyfosAdlibs, _rank: 50, diff --git a/src/tv2_offtube_showstyle/parts/OfftubeKam.ts b/src/tv2_offtube_showstyle/parts/OfftubeKam.ts index 06ed859e6..80336f0e7 100644 --- a/src/tv2_offtube_showstyle/parts/OfftubeKam.ts +++ b/src/tv2_offtube_showstyle/parts/OfftubeKam.ts @@ -87,7 +87,7 @@ export async function OfftubeCreatePartKam( metaData: { sisyfosPersistMetaData: { sisyfosLayers: sourceInfoCam.sisyfosLayers ?? [], - acceptPersistAudio: sourceInfoCam.acceptPersistAudio + acceptsPersistedAudio: sourceInfoCam.acceptPersistAudio } }, tags: [GetTagForKam(partDefinition.sourceDefinition)], diff --git a/src/tv2_offtube_studio/layers.ts b/src/tv2_offtube_studio/layers.ts index 1f1ff0048..02dbd9570 100644 --- a/src/tv2_offtube_studio/layers.ts +++ b/src/tv2_offtube_studio/layers.ts @@ -26,7 +26,6 @@ enum CasparLLayer { /** Maps to the same Caspar layer as CasparPlayerJingle but its lookahead preloads the first frame */ CasparPlayerJinglePreload = 'casparcg_player_jingle_preload', CasparGraphicsFullLoop = 'casparcg_graphics_full_loop', - CasparCGDVELoop = 'casparcg_dve_loop', CasparCGDVEKeyedLoop = 'casparcg_dve_keyed_loop', CasparCGDVEKey = 'casparcg_dve_key', CasparCGDVEFrame = 'casparcg_dve_frame' diff --git a/src/tv2_offtube_studio/migrations/mappings-defaults.ts b/src/tv2_offtube_studio/migrations/mappings-defaults.ts index 87108ab3d..1c605eb8f 100644 --- a/src/tv2_offtube_studio/migrations/mappings-defaults.ts +++ b/src/tv2_offtube_studio/migrations/mappings-defaults.ts @@ -9,6 +9,7 @@ import { prefixLayers } from 'tv2-common' import { AbstractLLayer, SwitcherAuxLLayer, SwitcherDveLLayer, SwitcherMixEffectLLayer } from 'tv2-constants' +import { GraphicLLayer } from '../../tv2_afvd_studio/layers' import { ATEMModel } from '../../types/atem' import { OfftubeCasparLLayer, OfftubeGraphicLLayer, OfftubeSisyfosLLayer } from '../layers' @@ -355,6 +356,14 @@ const MAPPINGS_GRAPHICS: BlueprintMappings = { channel: 3, layer: 111 }), + [GraphicLLayer.GraphicLLayerSchema]: literal({ + device: TSR.DeviceType.CASPARCG, + deviceId: 'caspar01', + layerName: 'GFX Skema', + lookahead: LookaheadMode.NONE, + channel: 3, + layer: 111 + }), [OfftubeGraphicLLayer.GraphicLLayerOverlay]: literal({ device: TSR.DeviceType.CASPARCG, deviceId: 'caspar01', diff --git a/yarn.lock b/yarn.lock index 0fc6e8f0a..6bf2a93ed 100644 --- a/yarn.lock +++ b/yarn.lock @@ -560,20 +560,20 @@ dependencies: "@sinonjs/commons" "^1.7.0" -"@sofie-automation/blueprints-integration@npm:@tv2media/blueprints-integration@46.2.2": - version "46.2.2" - resolved "https://registry.yarnpkg.com/@tv2media/blueprints-integration/-/blueprints-integration-46.2.2.tgz#74c0533e79585755cd61397b531689754fd2ef6d" - integrity sha512-IjHlVaE0IuLp7R8mxIWz5AbnlC8EExtHHWt/F8tbbjSyKYeBahxS0asnbMFnDYMlOZznNrunT4nBy8vwGfTHhg== +"@sofie-automation/blueprints-integration@npm:@tv2media/blueprints-integration@46.3.0-staging": + version "46.3.0-staging" + resolved "https://registry.yarnpkg.com/@tv2media/blueprints-integration/-/blueprints-integration-46.3.0-staging.tgz#2aaf96cae9876e051016fb5c6f386d35fbfc1e99" + integrity sha512-VC+/iM8QKq95rGMsb4kQU3MVPnt9R3wxgll/Ni5KtWeZ+qgfJPz4d6Cqj16ADYiHppUyBehEyU27Z8euMG1f6Q== dependencies: - "@sofie-automation/shared-lib" "npm:@tv2media/shared-lib@46.2.0" - timeline-state-resolver-types "npm:@tv2media/timeline-state-resolver-types@3.5.0" + "@sofie-automation/shared-lib" "npm:@tv2media/shared-lib@46.3.0-staging" + timeline-state-resolver-types "npm:@tv2media/timeline-state-resolver-types@3.5.1" tslib "^2.4.0" type-fest "^2.19.0" -"@sofie-automation/shared-lib@npm:@tv2media/shared-lib@46.2.0": - version "46.2.0" - resolved "https://registry.yarnpkg.com/@tv2media/shared-lib/-/shared-lib-46.2.0.tgz#934f14339330bd0064d7a0c3046dae10e2754099" - integrity sha512-ojW4qHQ+dfQDzFotqq4kEJNb6nVSDk/KU2u1S2fzXkZzgXW4Xq2hNUyk5lfUMcd7nBsiRVnPzmVSqxbRfUd1MQ== +"@sofie-automation/shared-lib@npm:@tv2media/shared-lib@46.3.0-staging": + version "46.3.0-staging" + resolved "https://registry.yarnpkg.com/@tv2media/shared-lib/-/shared-lib-46.3.0-staging.tgz#405ddd324e7f66f472635258915eb026054b8406" + integrity sha512-LYPJEXNO4NNiCxB/PsqW1BedtLjvBFntIUfVKwfuizkZJkYnJ8JZQdrUcxgyCJg5/SYeSccUrhorI4YKDqcx8w== dependencies: tslib "^2.4.0" type-fest "^2.19.0" @@ -935,6 +935,14 @@ agent-base@6: dependencies: debug "4" +aggregate-error@^3.0.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/aggregate-error/-/aggregate-error-3.1.0.tgz#92670ff50f5359bdb7a3e0d40d0ec30c5737687a" + integrity sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA== + dependencies: + clean-stack "^2.0.0" + indent-string "^4.0.0" + ajv-errors@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/ajv-errors/-/ajv-errors-1.0.1.tgz#f35986aceb91afadec4102fbd85014950cefa64d" @@ -955,7 +963,7 @@ ajv@^6.1.0, ajv@^6.10.2: json-schema-traverse "^0.4.1" uri-js "^4.2.2" -ansi-escapes@^4.2.1: +ansi-escapes@^4.2.1, ansi-escapes@^4.3.0: version "4.3.2" resolved "https://registry.yarnpkg.com/ansi-escapes/-/ansi-escapes-4.3.2.tgz#6b2291d1db7d98b6521d5f1efa42d0f3a9feb65e" integrity sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ== @@ -972,6 +980,11 @@ ansi-regex@^5.0.1: resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-5.0.1.tgz#082cb2c89c9fe8659a311a53bd6a4dc5301db304" integrity sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ== +ansi-regex@^6.0.1: + version "6.0.1" + resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-6.0.1.tgz#3183e38fae9a65d7cb5e53945cd5897d0260a06a" + integrity sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA== + ansi-styles@^3.2.0, ansi-styles@^3.2.1: version "3.2.1" resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-3.2.1.tgz#41fbb20243e50b12be0f04b8dedbf07520ce841d" @@ -991,6 +1004,11 @@ ansi-styles@^5.0.0: resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-5.2.0.tgz#07449690ad45777d1924ac2abb2fc8895dba836b" integrity sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA== +ansi-styles@^6.0.0: + version "6.2.1" + resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-6.2.1.tgz#0e62320cf99c21afff3b3012192546aacbfb05c5" + integrity sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug== + anymatch@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/anymatch/-/anymatch-2.0.0.tgz#bcb24b4f37934d9aa7ac17b4adaf89e7c76ef2eb" @@ -1087,6 +1105,11 @@ assign-symbols@^1.0.0: resolved "https://registry.yarnpkg.com/assign-symbols/-/assign-symbols-1.0.0.tgz#59667f41fadd4f20ccbc2bb96b8d4f7f78ec0367" integrity sha1-WWZ/QfrdTyDMvCu5a41Pf3jsA2c= +astral-regex@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/astral-regex/-/astral-regex-2.0.0.tgz#483143c567aeed4785759c0865786dc77d7d2e31" + integrity sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ== + async-each@^1.0.1: version "1.0.3" resolved "https://registry.yarnpkg.com/async-each/-/async-each-1.0.3.tgz#b727dbf87d7651602f06f4d4ac387f47d91b0cbf" @@ -1473,6 +1496,11 @@ capture-exit@^2.0.0: dependencies: rsvp "^4.8.4" +chalk@5.2.0: + version "5.2.0" + resolved "https://registry.yarnpkg.com/chalk/-/chalk-5.2.0.tgz#249623b7d66869c673699fb66d65723e54dfcfb3" + integrity sha512-ree3Gqw/nazQAPuJJEy+avdl7QfZMcUvmHIKgEZkGL+xOBzRvup5Hxo6LHuMceSxOabuJLJm5Yp/92R9eMmMvA== + chalk@^2.0.0, chalk@^2.0.1, chalk@^2.3.0, chalk@^2.4.1, chalk@^2.4.2: version "2.4.2" resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.4.2.tgz#cd42541677a54333cf541a49108c1432b44c9424" @@ -1572,6 +1600,34 @@ class-utils@^0.3.5: isobject "^3.0.0" static-extend "^0.1.1" +clean-stack@^2.0.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/clean-stack/-/clean-stack-2.2.0.tgz#ee8472dbb129e727b31e8a10a427dee9dfe4008b" + integrity sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A== + +cli-cursor@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/cli-cursor/-/cli-cursor-3.1.0.tgz#264305a7ae490d1d03bf0c9ba7c925d1753af307" + integrity sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw== + dependencies: + restore-cursor "^3.1.0" + +cli-truncate@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/cli-truncate/-/cli-truncate-2.1.0.tgz#c39e28bf05edcde5be3b98992a22deed5a2b93c7" + integrity sha512-n8fOixwDD6b/ObinzTrp1ZKFzbgvKZvuz/TvejnLn1aQfC6r52XEx85FmuC+3HI+JM7coBRXUvNqEU2PHVrHpg== + dependencies: + slice-ansi "^3.0.0" + string-width "^4.2.0" + +cli-truncate@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/cli-truncate/-/cli-truncate-3.1.0.tgz#3f23ab12535e3d73e839bb43e73c9de487db1389" + integrity sha512-wfOBkjXteqSnI59oPcJkcPl/ZmwvMMOj340qUIY1SKZCv0B9Cf4D4fAucRkIKQmsIuYK3x1rrgU7MeGRruiuiA== + dependencies: + slice-ansi "^5.0.0" + string-width "^5.0.0" + cliui@^5.0.0: version "5.0.0" resolved "https://registry.yarnpkg.com/cliui/-/cliui-5.0.0.tgz#deefcfdb2e800784aa34f46fa08e06851c7bbbc5" @@ -1632,6 +1688,11 @@ color-name@~1.1.4: resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.4.tgz#c2a09a87acbde69543de6f63fa3995c826c536a2" integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA== +colorette@^2.0.19: + version "2.0.20" + resolved "https://registry.yarnpkg.com/colorette/-/colorette-2.0.20.tgz#9eb793e6833067f7235902fcd3b09917a000a95a" + integrity sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w== + combined-stream@^1.0.8: version "1.0.8" resolved "https://registry.yarnpkg.com/combined-stream/-/combined-stream-1.0.8.tgz#c3d45a8b34fd730631a110a8a2520682b31d5a7f" @@ -1639,6 +1700,11 @@ combined-stream@^1.0.8: dependencies: delayed-stream "~1.0.0" +commander@^10.0.0: + version "10.0.1" + resolved "https://registry.yarnpkg.com/commander/-/commander-10.0.1.tgz#881ee46b4f77d1c1dccc5823433aa39b022cbe06" + integrity sha512-y4Mg2tXshplEbSGzx7amzPwKKOCGuoSRP/CjEdwwk0FOGlUbq6lKuoyDZTNZkmxHdJtp54hdfY/JUrdL7Xfdug== + commander@^2.12.1, commander@^2.20.0: version "2.20.3" resolved "https://registry.yarnpkg.com/commander/-/commander-2.20.3.tgz#fd485e84c03eb4881c20722ba48035e8531aeb33" @@ -2007,7 +2073,7 @@ dateformat@^3.0.0: resolved "https://registry.yarnpkg.com/dateformat/-/dateformat-3.0.3.tgz#a6e37499a4d9a9cf85ef5872044d62901c9889ae" integrity sha512-jyCETtSl3VMZMWeRo7iY1FL19ges1t55hMo5yaam4Jrsm5EPL89UQkoQRyiI+Yf4k8r2ZpdngkV8hr1lIdjb3Q== -debug@4, debug@^4.1.0, debug@^4.1.1: +debug@4, debug@^4.1.0, debug@^4.1.1, debug@^4.3.4: version "4.3.4" resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.4.tgz#1319f6579357f2338d3337d2cdd4914bb5dcc865" integrity sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ== @@ -2200,6 +2266,11 @@ duplexify@^3.4.2, duplexify@^3.6.0: readable-stream "^2.0.0" stream-shift "^1.0.0" +eastasianwidth@^0.2.0: + version "0.2.0" + resolved "https://registry.yarnpkg.com/eastasianwidth/-/eastasianwidth-0.2.0.tgz#696ce2ec0aa0e6ea93a397ffcf24aa7840c827cb" + integrity sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA== + electron-to-chromium@^1.4.84: version "1.4.116" resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.4.116.tgz#cf0b106d462a78e43ef33cc269caf2ad70e3c7a8" @@ -2233,6 +2304,11 @@ emoji-regex@^8.0.0: resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-8.0.0.tgz#e818fd69ce5ccfcb404594f842963bf53164cc37" integrity sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A== +emoji-regex@^9.2.2: + version "9.2.2" + resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-9.2.2.tgz#840c8803b0d8047f4ff0cf963176b32d4ef3ed72" + integrity sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg== + emojis-list@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/emojis-list/-/emojis-list-3.0.0.tgz#5570662046ad29e2e916e71aae260abdff4f6a78" @@ -2427,6 +2503,21 @@ execa@^5.0.0: signal-exit "^3.0.3" strip-final-newline "^2.0.0" +execa@^7.0.0: + version "7.1.1" + resolved "https://registry.yarnpkg.com/execa/-/execa-7.1.1.tgz#3eb3c83d239488e7b409d48e8813b76bb55c9c43" + integrity sha512-wH0eMf/UXckdUYnO21+HDztteVv05rq2GXksxT4fCGeHkBhw1DROXh40wcjMcRqDOWE7iPJ4n3M7e2+YFP+76Q== + dependencies: + cross-spawn "^7.0.3" + get-stream "^6.0.1" + human-signals "^4.3.0" + is-stream "^3.0.0" + merge-stream "^2.0.0" + npm-run-path "^5.1.0" + onetime "^6.0.0" + signal-exit "^3.0.7" + strip-final-newline "^3.0.0" + exit@^0.1.2: version "0.1.2" resolved "https://registry.yarnpkg.com/exit/-/exit-0.1.2.tgz#0632638f8d877cc82107d30a0fff1a17cba1cd0c" @@ -2733,7 +2824,7 @@ get-stream@^4.0.0: dependencies: pump "^3.0.0" -get-stream@^6.0.0: +get-stream@^6.0.0, get-stream@^6.0.1: version "6.0.1" resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-6.0.1.tgz#a262d8eef67aced57c2852ad6167526a43cbf7b7" integrity sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg== @@ -3036,6 +3127,16 @@ human-signals@^2.1.0: resolved "https://registry.yarnpkg.com/human-signals/-/human-signals-2.1.0.tgz#dc91fcba42e4d06e4abaed33b3e7a3c02f514ea0" integrity sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw== +human-signals@^4.3.0: + version "4.3.1" + resolved "https://registry.yarnpkg.com/human-signals/-/human-signals-4.3.1.tgz#ab7f811e851fca97ffbd2c1fe9a958964de321b2" + integrity sha512-nZXjEF2nbo7lIw3mgYjItAfgQXog3OjJogSbKa2CQIIvSGWcKgeJnQlNXip6NglNzYH45nSRiEVimMvYL8DDqQ== + +husky@^8.0.3: + version "8.0.3" + resolved "https://registry.yarnpkg.com/husky/-/husky-8.0.3.tgz#4936d7212e46d1dea28fef29bb3a108872cd9184" + integrity sha512-+dQSyqPh4x1hlO1swXBiNb2HzTDN1I2IGLQx1GrBuiqFJfoMrnZWwVmatvSiO+Iz8fBUnf+lekwNo4c2LlXItg== + iconv-lite@0.4.24: version "0.4.24" resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.24.tgz#2022b4b25fbddc21d2f524974a474aafe733908b" @@ -3271,6 +3372,11 @@ is-fullwidth-code-point@^3.0.0: resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz#f116f8064fe90b3f7844a38997c0b75051269f1d" integrity sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg== +is-fullwidth-code-point@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-4.0.0.tgz#fae3167c729e7463f8461ce512b080a49268aa88" + integrity sha512-O4L094N2/dZ7xqVdrXhh9r1KODPJpFms8B5sGdJLPy664AgvXsreZUyCQQNItZRDlYug4xStLjNp/sz3HvBowQ== + is-generator-fn@^2.0.0: version "2.1.0" resolved "https://registry.yarnpkg.com/is-generator-fn/-/is-generator-fn-2.1.0.tgz#7d140adc389aaf3011a8f2a2a4cfa6faadffb118" @@ -3361,6 +3467,11 @@ is-stream@^2.0.0: resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-2.0.1.tgz#fac1e3d53b97ad5a9d0ae9cef2389f5810a5c077" integrity sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg== +is-stream@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-3.0.0.tgz#e6bfd7aa6bef69f4f472ce9bb681e3e57b4319ac" + integrity sha512-LnQR4bZ9IADDRSkvpqMGvt/tEJWclzklNgSw48V5EAaAeDd6qGvN8ei6k5p0tvxSR171VmGyHuTiAOfxAbr8kA== + is-string@^1.0.5, is-string@^1.0.7: version "1.0.7" resolved "https://registry.yarnpkg.com/is-string/-/is-string-1.0.7.tgz#0dd12bf2006f255bb58f695110eff7491eebc0fd" @@ -4106,11 +4217,49 @@ license-checker@^25.0.1: spdx-satisfies "^4.0.0" treeify "^1.1.0" +lilconfig@2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/lilconfig/-/lilconfig-2.1.0.tgz#78e23ac89ebb7e1bfbf25b18043de756548e7f52" + integrity sha512-utWOt/GHzuUxnLKxB6dk81RoOeoNeHgbrXiuGk4yyF5qlRz+iIVWu56E2fqGHFrXz0QNUhLB/8nKqvRH66JKGQ== + lines-and-columns@^1.1.6: version "1.2.4" resolved "https://registry.yarnpkg.com/lines-and-columns/-/lines-and-columns-1.2.4.tgz#eca284f75d2965079309dc0ad9255abb2ebc1632" integrity sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg== +lint-staged@^13.2.2: + version "13.2.2" + resolved "https://registry.yarnpkg.com/lint-staged/-/lint-staged-13.2.2.tgz#5e711d3139c234f73402177be2f8dd312e6508ca" + integrity sha512-71gSwXKy649VrSU09s10uAT0rWCcY3aewhMaHyl2N84oBk4Xs9HgxvUp3AYu+bNsK4NrOYYxvSgg7FyGJ+jGcA== + dependencies: + chalk "5.2.0" + cli-truncate "^3.1.0" + commander "^10.0.0" + debug "^4.3.4" + execa "^7.0.0" + lilconfig "2.1.0" + listr2 "^5.0.7" + micromatch "^4.0.5" + normalize-path "^3.0.0" + object-inspect "^1.12.3" + pidtree "^0.6.0" + string-argv "^0.3.1" + yaml "^2.2.2" + +listr2@^5.0.7: + version "5.0.8" + resolved "https://registry.yarnpkg.com/listr2/-/listr2-5.0.8.tgz#a9379ffeb4bd83a68931a65fb223a11510d6ba23" + integrity sha512-mC73LitKHj9w6v30nLNGPetZIlfpUniNSsxxrbaPcWOjDb92SHPzJPi/t+v1YC/lxKz/AJ9egOjww0qUuFxBpA== + dependencies: + cli-truncate "^2.1.0" + colorette "^2.0.19" + log-update "^4.0.0" + p-map "^4.0.0" + rfdc "^1.3.0" + rxjs "^7.8.0" + through "^2.3.8" + wrap-ansi "^7.0.0" + load-json-file@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/load-json-file/-/load-json-file-4.0.0.tgz#2f5f45ab91e33216234fd53adab668eb4ec0993b" @@ -4180,6 +4329,16 @@ lodash@^4.17.15, lodash@^4.17.5, lodash@^4.7.0: resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c" integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg== +log-update@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/log-update/-/log-update-4.0.0.tgz#589ecd352471f2a1c0c570287543a64dfd20e0a1" + integrity sha512-9fkkDevMefjg0mmzWFBW8YkFP91OrizzkW3diF7CpG+S2EYdy4+TVfGwz1zeF8x7hCx1ovSPTOE9Ngib74qqUg== + dependencies: + ansi-escapes "^4.3.0" + cli-cursor "^3.1.0" + slice-ansi "^4.0.0" + wrap-ansi "^6.2.0" + loose-envify@^1.0.0: version "1.4.0" resolved "https://registry.yarnpkg.com/loose-envify/-/loose-envify-1.4.0.tgz#71ee51fa7be4caec1a63839f7e682d8132d30caf" @@ -4316,7 +4475,7 @@ micromatch@^3.0.4, micromatch@^3.1.10, micromatch@^3.1.4: snapdragon "^0.8.1" to-regex "^3.0.2" -micromatch@^4.0.0, micromatch@^4.0.4: +micromatch@^4.0.0, micromatch@^4.0.4, micromatch@^4.0.5: version "4.0.5" resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-4.0.5.tgz#bc8999a7cbbf77cdc89f132f6e467051b49090c6" integrity sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA== @@ -4349,6 +4508,11 @@ mimic-fn@^2.1.0: resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-2.1.0.tgz#7ed2c2ccccaf84d3ffcb7a69b57711fc2083401b" integrity sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg== +mimic-fn@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-4.0.0.tgz#60a90550d5cb0b239cca65d893b1a53b29871ecc" + integrity sha512-vqiC06CuhBTUdZH+RYl8sFrL096vA45Ok5ISO6sE/Mr1jRbGH4Csnhi8f3wKVl7x8mO4Au7Ir9D3Oyv1VYMFJw== + min-indent@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/min-indent/-/min-indent-1.0.1.tgz#a63f681673b30571fbe8bc25686ae746eefa9869" @@ -4588,6 +4752,13 @@ npm-run-path@^4.0.1: dependencies: path-key "^3.0.0" +npm-run-path@^5.1.0: + version "5.1.0" + resolved "https://registry.yarnpkg.com/npm-run-path/-/npm-run-path-5.1.0.tgz#bc62f7f3f6952d9894bd08944ba011a6ee7b7e00" + integrity sha512-sJOdmRGrY2sjNTRMbSvluQqg+8X7ZK61yvzBEIDhz4f8z1TZFYABsqjjCBd/0PUNE9M6QDgHJXQkGUEm7Q+l9Q== + dependencies: + path-key "^4.0.0" + null-check@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/null-check/-/null-check-1.0.0.tgz#977dffd7176012b9ec30d2a39db5cf72a0439edd" @@ -4617,6 +4788,11 @@ object-inspect@^1.12.0, object-inspect@^1.9.0: resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-1.12.0.tgz#6e2c120e868fd1fd18cb4f18c31741d0d6e776f0" integrity sha512-Ho2z80bVIvJloH+YzRmpZVQe87+qASmBUKZDWgx9cu+KDrX2ZDH/3tMy+gXbZETVGs2M8YdxObOh7XAtim9Y0g== +object-inspect@^1.12.3: + version "1.12.3" + resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-1.12.3.tgz#ba62dffd67ee256c8c086dfae69e016cd1f198b9" + integrity sha512-geUvdk7c+eizMNUDkRpW1wJwgfOiOeHbxBR/hLXK1aT6zmVSO0jsQcs7fj6MGw89jC/cjGfLcNOrtMYtGqm81g== + object-keys@^1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/object-keys/-/object-keys-1.1.1.tgz#1c47f272df277f3b1daf061677d9c82e2322c60e" @@ -4662,13 +4838,20 @@ once@^1.3.0, once@^1.3.1, once@^1.4.0: dependencies: wrappy "1" -onetime@^5.1.2: +onetime@^5.1.0, onetime@^5.1.2: version "5.1.2" resolved "https://registry.yarnpkg.com/onetime/-/onetime-5.1.2.tgz#d0e96ebb56b07476df1dd9c4806e5237985ca45e" integrity sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg== dependencies: mimic-fn "^2.1.0" +onetime@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/onetime/-/onetime-6.0.0.tgz#7c24c18ed1fd2e9bca4bd26806a33613c77d34b4" + integrity sha512-1FlR+gjXK7X+AsAHso35MnyN5KqGwJRi/31ft6x0M194ht7S+rWAvd7PHss9xSKMzE0asv1pyIHaJYq+BbacAQ== + dependencies: + mimic-fn "^4.0.0" + optionator@^0.8.1: version "0.8.3" resolved "https://registry.yarnpkg.com/optionator/-/optionator-0.8.3.tgz#84fa1d036fe9d3c7e21d99884b601167ec8fb495" @@ -4758,6 +4941,13 @@ p-locate@^5.0.0: dependencies: p-limit "^3.0.2" +p-map@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/p-map/-/p-map-4.0.0.tgz#bb2f95a5eda2ec168ec9274e06a747c3e2904d2b" + integrity sha512-/bjOqmgETBYB5BoEeGVea8dmvHb2m9GLy1E9W43yeyfP6QQCZGFNa+XRceJEuDB6zqr+gKpIAmlLebMpykw/MQ== + dependencies: + aggregate-error "^3.0.0" + p-try@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/p-try/-/p-try-1.0.0.tgz#cbc79cdbaf8fd4228e13f621f2b1a237c1b207b3" @@ -4861,6 +5051,11 @@ path-key@^3.0.0, path-key@^3.1.0: resolved "https://registry.yarnpkg.com/path-key/-/path-key-3.1.1.tgz#581f6ade658cbba65a0d3380de7753295054f375" integrity sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q== +path-key@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/path-key/-/path-key-4.0.0.tgz#295588dc3aee64154f877adb9d780b81c554bf18" + integrity sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ== + path-parse@^1.0.7: version "1.0.7" resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.7.tgz#fbc114b60ca42b30d9daf5858e4bd68bbedb6735" @@ -4894,6 +5089,11 @@ picomatch@^2.0.4, picomatch@^2.2.1, picomatch@^2.2.3, picomatch@^2.3.1: resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.3.1.tgz#3ba3833733646d9d3e4995946c1365a67fb07a42" integrity sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA== +pidtree@^0.6.0: + version "0.6.0" + resolved "https://registry.yarnpkg.com/pidtree/-/pidtree-0.6.0.tgz#90ad7b6d42d5841e69e0a2419ef38f8883aa057c" + integrity sha512-eG2dWTVw5bzqGRztnHExczNxt5VGsE6OwTeCG3fdUf9KBsZzO3R5OIIIzWR+iZA0NtZ+RDVdaoE2dK1cn6jH4g== + pify@^2.3.0: version "2.3.0" resolved "https://registry.yarnpkg.com/pify/-/pify-2.3.0.tgz#ed141a6ac043a849ea588498e7dca8b15330e90c" @@ -5289,11 +5489,24 @@ resolve@^1.10.0, resolve@^1.20.0, resolve@^1.3.2: path-parse "^1.0.7" supports-preserve-symlinks-flag "^1.0.0" +restore-cursor@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/restore-cursor/-/restore-cursor-3.1.0.tgz#39f67c54b3a7a58cea5236d95cf0034239631f7e" + integrity sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA== + dependencies: + onetime "^5.1.0" + signal-exit "^3.0.2" + ret@~0.1.10: version "0.1.15" resolved "https://registry.yarnpkg.com/ret/-/ret-0.1.15.tgz#b8a4825d5bdb1fc3f6f53c2bc33f81388681c7bc" integrity sha512-TTlYpa+OL+vMMNG24xSlQGEJ3B/RzEfUlLct7b5G/ytav+wPrplCpVMFuwzXbkecJrb6IYo1iFb0S9v37754mg== +rfdc@^1.3.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/rfdc/-/rfdc-1.3.0.tgz#d0b7c441ab2720d05dc4cf26e01c89631d9da08b" + integrity sha512-V2hovdzFbOi77/WajaSMXk2OLm+xNIeQdMMuB7icj7bk6zi2F8GGAxigcnDFpJHbNyNcgyJDiP+8nOrY5cZGrA== + rimraf@^2.5.4, rimraf@^2.6.3: version "2.7.1" resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-2.7.1.tgz#35797f13a7fdadc566142c29d4f07ccad483e3ec" @@ -5328,6 +5541,13 @@ run-queue@^1.0.0, run-queue@^1.0.3: dependencies: aproba "^1.1.1" +rxjs@^7.8.0: + version "7.8.1" + resolved "https://registry.yarnpkg.com/rxjs/-/rxjs-7.8.1.tgz#6f6f3d99ea8044291efd92e7c7fcf562c4057543" + integrity sha512-AA3TVj+0A2iuIoQkWEK/tqFjBq2j+6PO6Y0zJcvzLAFhEFIO3HL0vls9hWLncZbAAbK0mar7oZ4V079I/qPMxg== + dependencies: + tslib "^2.1.0" + safe-buffer@^5.0.1, safe-buffer@^5.1.0, safe-buffer@^5.1.1, safe-buffer@^5.1.2, safe-buffer@^5.2.0, safe-buffer@~5.2.0: version "5.2.1" resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.1.tgz#1eaf9fa9bdb1fdd4ec75f58f9cdb4e6b7827eec6" @@ -5466,7 +5686,7 @@ side-channel@^1.0.4: get-intrinsic "^1.0.2" object-inspect "^1.9.0" -signal-exit@^3.0.0, signal-exit@^3.0.2, signal-exit@^3.0.3: +signal-exit@^3.0.0, signal-exit@^3.0.2, signal-exit@^3.0.3, signal-exit@^3.0.7: version "3.0.7" resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.7.tgz#a9a1767f8af84155114eaabd73f99273c8f59ad9" integrity sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ== @@ -5486,6 +5706,32 @@ slash@^3.0.0: resolved "https://registry.yarnpkg.com/slash/-/slash-3.0.0.tgz#6539be870c165adbd5240220dbe361f1bc4d4634" integrity sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q== +slice-ansi@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/slice-ansi/-/slice-ansi-3.0.0.tgz#31ddc10930a1b7e0b67b08c96c2f49b77a789787" + integrity sha512-pSyv7bSTC7ig9Dcgbw9AuRNUb5k5V6oDudjZoMBSr13qpLBG7tB+zgCkARjq7xIUgdz5P1Qe8u+rSGdouOOIyQ== + dependencies: + ansi-styles "^4.0.0" + astral-regex "^2.0.0" + is-fullwidth-code-point "^3.0.0" + +slice-ansi@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/slice-ansi/-/slice-ansi-4.0.0.tgz#500e8dd0fd55b05815086255b3195adf2a45fe6b" + integrity sha512-qMCMfhY040cVHT43K9BFygqYbUPFZKHOg7K73mtTWJRb8pyP3fzf4Ixd5SzdEJQ6MRUg/WBnOLxghZtKKurENQ== + dependencies: + ansi-styles "^4.0.0" + astral-regex "^2.0.0" + is-fullwidth-code-point "^3.0.0" + +slice-ansi@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/slice-ansi/-/slice-ansi-5.0.0.tgz#b73063c57aa96f9cd881654b15294d95d285c42a" + integrity sha512-FC+lgizVPfie0kkhqUScwRu1O/lF6NOgJmlCgK+/LYxDCTk8sGelYaHDhFcDN+Sn3Cv+3VSa4Byeo+IMCzpMgQ== + dependencies: + ansi-styles "^6.0.0" + is-fullwidth-code-point "^4.0.0" + slide@~1.1.3: version "1.1.6" resolved "https://registry.yarnpkg.com/slide/-/slide-1.1.6.tgz#56eb027d65b4d2dce6cb2e2d32c4d4afc9e1d707" @@ -5722,6 +5968,11 @@ stream-shift@^1.0.0: resolved "https://registry.yarnpkg.com/stream-shift/-/stream-shift-1.0.1.tgz#d7088281559ab2778424279b0877da3c392d5a3d" integrity sha512-AiisoFqQ0vbGcZgQPY1cdP2I76glaVA/RauYR4G4thNFgkTqr90yXTo4LYX60Jl+sIlPNHHdGSwo01AvbKUSVQ== +string-argv@^0.3.1: + version "0.3.2" + resolved "https://registry.yarnpkg.com/string-argv/-/string-argv-0.3.2.tgz#2b6d0ef24b656274d957d54e0a4bbf6153dc02b6" + integrity sha512-aqD2Q0144Z+/RqG52NeHEkZauTAUWJO8c6yTftGJKO3Tja5tUgIfmIl6kExvhtxSDP7fXB6DvzkfMpCd/F3G+Q== + string-length@^4.0.1: version "4.0.2" resolved "https://registry.yarnpkg.com/string-length/-/string-length-4.0.2.tgz#a8a8dc7bd5c1a82b9b3c8b87e125f66871b6e57a" @@ -5748,6 +5999,15 @@ string-width@^4.1.0, string-width@^4.2.0: is-fullwidth-code-point "^3.0.0" strip-ansi "^6.0.1" +string-width@^5.0.0: + version "5.1.2" + resolved "https://registry.yarnpkg.com/string-width/-/string-width-5.1.2.tgz#14f8daec6d81e7221d2a357e668cab73bdbca794" + integrity sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA== + dependencies: + eastasianwidth "^0.2.0" + emoji-regex "^9.2.2" + strip-ansi "^7.0.1" + string.prototype.trimend@^1.0.4: version "1.0.4" resolved "https://registry.yarnpkg.com/string.prototype.trimend/-/string.prototype.trimend-1.0.4.tgz#e75ae90c2942c63504686c18b287b4a0b1a45f80" @@ -5797,6 +6057,13 @@ strip-ansi@^6.0.0, strip-ansi@^6.0.1: dependencies: ansi-regex "^5.0.1" +strip-ansi@^7.0.1: + version "7.0.1" + resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-7.0.1.tgz#61740a08ce36b61e50e65653f07060d000975fb2" + integrity sha512-cXNxvT8dFNRVfhVME3JAe98mkXDYN2O1l7jmcwMnOslDeESg1rF/OZMtK0nRAhiari1unG5cD4jG3rapUAkLbw== + dependencies: + ansi-regex "^6.0.1" + strip-bom@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/strip-bom/-/strip-bom-3.0.0.tgz#2334c18e9c759f7bdd56fdef7e9ae3d588e68ed3" @@ -5817,6 +6084,11 @@ strip-final-newline@^2.0.0: resolved "https://registry.yarnpkg.com/strip-final-newline/-/strip-final-newline-2.0.0.tgz#89b852fb2fcbe936f6f4b3187afb0a12c1ab58ad" integrity sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA== +strip-final-newline@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/strip-final-newline/-/strip-final-newline-3.0.0.tgz#52894c313fbff318835280aed60ff71ebf12b8fd" + integrity sha512-dOESqjYr96iWYylGObzd39EuNTa5VJxyvVAEm5Jnh7KGo75V43Hk1odPQkNDyXNmUR6k+gEiDVXnjB8HJ3crXw== + strip-indent@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/strip-indent/-/strip-indent-3.0.0.tgz#c32e1cee940b6b3432c771bc2c54bcce73cd3001" @@ -5951,15 +6223,15 @@ through2@^4.0.0: dependencies: readable-stream "3" -through@2, "through@>=2.2.7 <3": +through@2, "through@>=2.2.7 <3", through@^2.3.8: version "2.3.8" resolved "https://registry.yarnpkg.com/through/-/through-2.3.8.tgz#0dd4c9ffaabc357960b1b724115d7e0e86a2e1f5" integrity sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU= -"timeline-state-resolver-types@npm:@tv2media/timeline-state-resolver-types@3.4.0": - version "3.4.0" - resolved "https://registry.yarnpkg.com/@tv2media/timeline-state-resolver-types/-/timeline-state-resolver-types-3.4.0.tgz#179b66c64616fec2ec541fd9dd5a73517d8706fc" - integrity sha512-P174WyTAa6PVAotOJOEw8Dsqu1L3Cx/jEDmikLDLfFgRgmsYNSjtmgLzuzBY/O39iAxcqXs0ItM7QKcnX+ilwg== +"timeline-state-resolver-types@npm:@tv2media/timeline-state-resolver-types@3.5.1": + version "3.5.1" + resolved "https://registry.yarnpkg.com/@tv2media/timeline-state-resolver-types/-/timeline-state-resolver-types-3.5.1.tgz#9362ec90183babb8183914a7e36664218e2eb5e1" + integrity sha512-yBAI2d7cnWPuDtFsYkbFB/GBkN4p0Q7Ne4TqKCDl4haqFU4PNtIzI1shH9AOEMOcsmNh0ZxHTzgAG85zrGGXpQ== dependencies: tslib "^2.3.1" @@ -6110,6 +6382,11 @@ tslib@^1.7.1, tslib@^1.8.0, tslib@^1.8.1: resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.14.1.tgz#cf2d38bdc34a134bcaf1091c41f6619e2f672d00" integrity sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg== +tslib@^2.1.0: + version "2.5.0" + resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.5.0.tgz#42bfed86f5787aeb41d031866c8f402429e0fddf" + integrity sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg== + tslib@^2.3.1: version "2.3.1" resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.3.1.tgz#e8a335add5ceae51aa261d32a490158ef042ef01" @@ -6548,6 +6825,15 @@ wrap-ansi@^5.1.0: string-width "^3.0.0" strip-ansi "^5.0.0" +wrap-ansi@^6.2.0: + version "6.2.0" + resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-6.2.0.tgz#e9393ba07102e6c91a3b221478f0257cd2856e53" + integrity sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA== + dependencies: + ansi-styles "^4.0.0" + string-width "^4.1.0" + strip-ansi "^6.0.0" + wrap-ansi@^7.0.0: version "7.0.0" resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43" @@ -6612,6 +6898,11 @@ yallist@^4.0.0: resolved "https://registry.yarnpkg.com/yallist/-/yallist-4.0.0.tgz#9bb92790d9c0effec63be73519e11a35019a3a72" integrity sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A== +yaml@^2.2.2: + version "2.2.2" + resolved "https://registry.yarnpkg.com/yaml/-/yaml-2.2.2.tgz#ec551ef37326e6d42872dad1970300f8eb83a073" + integrity sha512-CBKFWExMn46Foo4cldiChEzn7S7SRV+wqiluAb6xmueD/fGyRHIhX8m14vVGgeFWjN540nKCNVj6P21eQjgTuA== + yargs-parser@20.x, yargs-parser@^20.2.2, yargs-parser@^20.2.3: version "20.2.9" resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-20.2.9.tgz#2eb7dc3b0289718fc295f362753845c41a0c94ee"