Skip to content

Commit

Permalink
Merge pull request #60 from nrkno/feat/loudnessScan
Browse files Browse the repository at this point in the history
  • Loading branch information
jstarpl authored Apr 12, 2023
2 parents 88623e8 + 29f7bc4 commit 9a60c02
Show file tree
Hide file tree
Showing 17 changed files with 821 additions and 232 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -60,12 +60,13 @@ describe('Generate expectations - NRK', () => {
o.settings
)

expect(Object.keys(expectations)).toHaveLength(5) // copy, scan, deep-scan, thumbnail, preview
expect(Object.keys(expectations)).toHaveLength(6) // copy, scan, deep-scan, thumbnail, preview, loudness
// expect(expectations).toMatchSnapshot()

const eCopy = Object.values(expectations).find((e) => e.type === Expectation.Type.FILE_COPY)
const eScan = Object.values(expectations).find((e) => e.type === Expectation.Type.PACKAGE_SCAN)
const eDeepScan = Object.values(expectations).find((e) => e.type === Expectation.Type.PACKAGE_DEEP_SCAN)
const eLoudness = Object.values(expectations).find((e) => e.type === Expectation.Type.PACKAGE_LOUDNESS_SCAN)
const eThumbnail = Object.values(expectations).find((e) => e.type === Expectation.Type.MEDIA_FILE_THUMBNAIL)
const ePreview = Object.values(expectations).find((e) => e.type === Expectation.Type.MEDIA_FILE_PREVIEW)

Expand All @@ -74,6 +75,7 @@ describe('Generate expectations - NRK', () => {
expect(eDeepScan).toBeTruthy()
expect(eThumbnail).toBeTruthy()
expect(ePreview).toBeTruthy()
expect(eLoudness).toBeTruthy()
})
test('Duplicated packages', () => {
const o = setup()
Expand Down Expand Up @@ -108,7 +110,7 @@ describe('Generate expectations - NRK', () => {
o.settings
)

expect(Object.keys(expectations)).toHaveLength(5) // copy, scan, deep-scan, thumbnail, preview
expect(Object.keys(expectations)).toHaveLength(6) // copy, scan, deep-scan, thumbnail, preview, loudness

const eCopy = Object.values(expectations).find((e) => e.type === Expectation.Type.FILE_COPY)
expect(eCopy).toBeTruthy()
Expand Down Expand Up @@ -151,7 +153,7 @@ describe('Generate expectations - NRK', () => {
o.settings
)

expect(Object.keys(expectations)).toHaveLength(10) // 2x (copy, scan, deep-scan, thumbnail, preview)
expect(Object.keys(expectations)).toHaveLength(12) // 2x (copy, scan, deep-scan, thumbnail, preview, loudness)

const sorted = Object.values(expectations).sort((a, b) => {
// Lowest first: (lower is better)
Expand All @@ -165,13 +167,15 @@ describe('Generate expectations - NRK', () => {
Expectation.Type.FILE_COPY,
Expectation.Type.PACKAGE_SCAN,
Expectation.Type.PACKAGE_SCAN,
// The rest aren't as important
// The order of the rest aren't as important:
Expectation.Type.MEDIA_FILE_THUMBNAIL,
Expectation.Type.MEDIA_FILE_PREVIEW,
Expectation.Type.MEDIA_FILE_THUMBNAIL,
Expectation.Type.PACKAGE_DEEP_SCAN,
Expectation.Type.MEDIA_FILE_PREVIEW,
Expectation.Type.PACKAGE_DEEP_SCAN,
Expectation.Type.PACKAGE_LOUDNESS_SCAN,
Expectation.Type.PACKAGE_LOUDNESS_SCAN,
])
})
})
Expand Down Expand Up @@ -289,6 +293,9 @@ function setup() {
thumbnailPackageSettings: {
path: 'simpleMedia-thumbnail.webm',
},
loudnessPackageSettings: {
channelSpec: ['0+1'],
},
},
}),
simpleMedia2: literal<ExpectedPackage.ExpectedPackageMediaFile>({
Expand All @@ -315,6 +322,9 @@ function setup() {
thumbnailPackageSettings: {
path: 'simpleMedia2-thumbnail.webm',
},
loudnessPackageSettings: {
channelSpec: ['0+1'],
},
},
}),
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,14 @@ import {
PriorityAdditions,
} from './types'

type SomeClipCopyExpectation =
| Expectation.FileCopy
| Expectation.FileCopyProxy
| Expectation.FileVerify
| Expectation.QuantelClipCopy

type SomeClipFileOnDiskCopyExpectation = Expectation.FileCopy | Expectation.FileCopyProxy | Expectation.FileVerify

export function generateMediaFileCopy(
managerId: string,
expWrap: ExpectedPackageWrap,
Expand Down Expand Up @@ -171,11 +179,7 @@ export function generateQuantelCopy(managerId: string, expWrap: ExpectedPackageW
return exp
}
export function generatePackageScan(
expectation:
| Expectation.FileCopy
| Expectation.FileCopyProxy
| Expectation.FileVerify
| Expectation.QuantelClipCopy,
expectation: SomeClipCopyExpectation,
settings: PackageManagerSettings
): Expectation.PackageScan {
let priority = expectation.priority + PriorityAdditions.SCAN
Expand Down Expand Up @@ -230,11 +234,7 @@ export function generatePackageScan(
})
}
export function generatePackageDeepScan(
expectation:
| Expectation.FileCopy
| Expectation.FileCopyProxy
| Expectation.FileVerify
| Expectation.QuantelClipCopy,
expectation: SomeClipCopyExpectation,
settings: PackageManagerSettings
): Expectation.PackageDeepScan {
return literal<Expectation.PackageDeepScan>({
Expand Down Expand Up @@ -288,8 +288,61 @@ export function generatePackageDeepScan(
})
}

export function generatePackageLoudness(
expectation: SomeClipCopyExpectation,
packageSettings: ExpectedPackage.SideEffectLoudnessSettings,
settings: PackageManagerSettings
): Expectation.PackageLoudnessScan {
return literal<Expectation.PackageLoudnessScan>({
id: expectation.id + '_loudness',
priority: expectation.priority + PriorityAdditions.LOUDNESS_SCAN,
managerId: expectation.managerId,
type: Expectation.Type.PACKAGE_LOUDNESS_SCAN,
fromPackages: expectation.fromPackages,

statusReport: {
label: `Loudness Scan`,
description: `Measure clip loudness, using channels ${packageSettings.channelSpec.join(', ')}`,
requiredForPlayout: false,
displayRank: 14,
sendReport: expectation.statusReport.sendReport,
},

startRequirement: {
sources: expectation.endRequirement.targets,
content: expectation.endRequirement.content,
version: expectation.endRequirement.version,
},
endRequirement: {
targets: [
{
containerId: '__corePackageInfo',
label: 'Core package info',
accessors: {
coreCollection: {
type: Accessor.AccessType.CORE_PACKAGE_INFO,
},
},
},
],
content: null,
version: {
channels: packageSettings.channelSpec,
},
},
workOptions: {
...expectation.workOptions,
allowWaitForCPU: true,
usesCPUCount: 1,
removeDelay: settings.delayRemovalPackageInfo,
},
dependsOnFullfilled: [expectation.id],
triggerByFullfilledIds: [expectation.id],
})
}

export function generateMediaFileThumbnail(
expectation: Expectation.FileCopy | Expectation.FileCopyProxy | Expectation.FileVerify,
expectation: SomeClipFileOnDiskCopyExpectation,
packageContainerId: string,
settings: ExpectedPackage.SideEffectThumbnailSettings,
packageContainer: PackageContainer
Expand Down Expand Up @@ -342,7 +395,7 @@ export function generateMediaFileThumbnail(
})
}
export function generateMediaFilePreview(
expectation: Expectation.FileCopy | Expectation.FileCopyProxy | Expectation.FileVerify,
expectation: SomeClipFileOnDiskCopyExpectation,
packageContainerId: string,
settings: ExpectedPackage.SideEffectPreviewSettings,
packageContainer: PackageContainer
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import {
generateQuantelClipPreview,
generateJsonDataCopy,
generatePackageCopyFileProxy,
generatePackageLoudness,
} from './expectations-lib'
import { getSmartbullExpectedPackages, shouldBeIgnored } from './smartbull'
import { TEMPORARY_STORAGE_ID } from './lib'
Expand Down Expand Up @@ -259,6 +260,15 @@ function getSideEffectOfExpectation(
expectations[preview.id] = preview
}
}

if (expectation0.sideEffect?.loudnessPackageSettings) {
const loudness = generatePackageLoudness(
expectation,
expectation0.sideEffect?.loudnessPackageSettings,
settings
)
expectations[loudness.id] = loudness
}
} else if (expectation0.type === Expectation.Type.QUANTEL_CLIP_COPY) {
const expectation = expectation0 as Expectation.QuantelClipCopy

Expand Down Expand Up @@ -303,6 +313,15 @@ function getSideEffectOfExpectation(
expectations[preview.id] = preview
}
}

if (expectation0.sideEffect?.loudnessPackageSettings) {
const loudness = generatePackageLoudness(
expectation,
expectation0.sideEffect?.loudnessPackageSettings,
settings
)
expectations[loudness.id] = loudness
}
}
return expectations
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -61,4 +61,5 @@ export enum PriorityAdditions {
THUMBNAIL = 1002,
PREVIEW = 1003,
DEEP_SCAN = 1004,
LOUDNESS_SCAN = 1010,
}
9 changes: 7 additions & 2 deletions apps/single-app/app/expectedPackages.json
Original file line number Diff line number Diff line change
Expand Up @@ -76,11 +76,16 @@
"sideEffect": {
"previewContainerId": null,
"previewPackageSettings": null,
"thumbnailContainerId": "thumbnails0",
"thumbnailContainerId": "thumbnails0_local",
"thumbnailPackageSettings": {
"path": "thumbnail.png"
},
"loudnessPackageSettings": {
"channelSpec": [
"0"
]
}
}
}
]
}
}
21 changes: 21 additions & 0 deletions shared/packages/api/src/expectationApi.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ export namespace Expectation {
| FileCopyProxy
| PackageScan
| PackageDeepScan
| PackageLoudnessScan
| MediaFileThumbnail
| MediaFilePreview
| QuantelClipCopy
Expand All @@ -35,6 +36,7 @@ export namespace Expectation {

PACKAGE_SCAN = 'package_scan',
PACKAGE_DEEP_SCAN = 'package_deep_scan',
PACKAGE_LOUDNESS_SCAN = 'package_loudness_scan',

QUANTEL_CLIP_COPY = 'quantel_clip_copy',
// QUANTEL_CLIP_SCAN = 'quantel_clip_scan',
Expand Down Expand Up @@ -186,6 +188,25 @@ export namespace Expectation {
}
workOptions: WorkOptions.Base & WorkOptions.RemoveDelay
}
/** Defines a Loudness Scan of a Media file. A Loudness Scan is to be performed on (one of) the sources and the scan result is to be stored on the target. */
export interface PackageLoudnessScan extends Base {
type: Type.PACKAGE_LOUDNESS_SCAN

startRequirement: {
sources: SpecificPackageContainerOnPackage.FileSource[] | SpecificPackageContainerOnPackage.QuantelClip[]
content: FileCopy['endRequirement']['content'] | QuantelClipCopy['endRequirement']['content']
version: FileCopy['endRequirement']['version'] | QuantelClipCopy['endRequirement']['version']
}
endRequirement: {
targets: SpecificPackageContainerOnPackage.CorePackage[]
content: null // not using content, entries are stored using this.fromPackages
version: {
/** List of channels or stereo channel pairs to be inspected for loudness, 0-indexed. Use channel number as string (e.g. "0") or two numbers with a plus sign for stereo pairs (e.g. "0+1") */
channels: (`${number}` | `${number}+${number}`)[]
}
}
workOptions: WorkOptions.Base & WorkOptions.RemoveDelay
}
/** Defines a Thumbnail of a Media file. A Thumbnail is to be created from one of the the sources and the resulting file is to be stored on the target. */
export interface MediaFileThumbnail extends Base {
type: Type.MEDIA_FILE_THUMBNAIL
Expand Down
17 changes: 17 additions & 0 deletions shared/packages/api/src/inputApi.ts
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,9 @@ export namespace ExpectedPackage {
/** Which container thumbnails are to be put into */
thumbnailContainerId?: string | null
thumbnailPackageSettings?: SideEffectThumbnailSettings | null

/** Should the package be scanned for loudness */
loudnessPackageSettings?: SideEffectLoudnessSettings
}
}
export interface SideEffectPreviewSettings {
Expand All @@ -99,6 +102,20 @@ export namespace ExpectedPackage {
seekTime?: number
}

export interface SideEffectLoudnessSettings {
/** Which channels should be scanned. Use a single 0-indexed number, or two numbers with a plus sign ("0+1") for stereo pairs.
* You can specify multiple channels and channel pairs to be scanned, as separate entries in the array. This can be useful
* when the streams contain different language versions or audio that will be played jointly, but processed separately
* in the production chain (f.g. a stereo mix of a speaker and a stereo ambient sound mix)
*
* When expecting varied channel arrangements within the clip, it can be useful to specify multiple combinations,
* f.g. ["0", "0+1"] (for single stream stereo and discreet channel stereo) and then select the correct measurement in the
* blueprints based on the context */
channelSpec: SideEffectLoudnessSettingsChannelSpec[]
}

export type SideEffectLoudnessSettingsChannelSpec = `${number}` | `${number}+${number}`

export interface ExpectedPackageMediaFile extends Base {
type: PackageType.MEDIA_FILE
content: {
Expand Down
5 changes: 3 additions & 2 deletions shared/packages/worker/src/worker/accessorHandlers/atem.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import { promisify } from 'util'
import { UniversalVersion } from '../workers/windowsWorker/lib/lib'
import { MAX_EXEC_BUFFER } from '../lib/lib'
import { defaultCheckHandleRead, defaultCheckHandleWrite } from './lib/lib'
import { getFFMpegExecutable, getFFProbeExecutable } from '../workers/windowsWorker/expectationHandlers/lib/ffmpeg'

const fsReadFile = promisify(fs.readFile)

Expand Down Expand Up @@ -544,7 +545,7 @@ async function getStreamIndicies(inputFile: string, type: 'video' | 'audio'): Pr

async function ffprobe(args: string[]): Promise<string> {
return new Promise((resolve, reject) => {
const file = process.platform === 'win32' ? 'ffprobe.exe' : 'ffprobe'
const file = getFFProbeExecutable()
execFile(
file,
args,
Expand All @@ -565,7 +566,7 @@ async function ffprobe(args: string[]): Promise<string> {

async function ffmpeg(args: string[]): Promise<string> {
return new Promise((resolve, reject) => {
const file = process.platform === 'win32' ? 'ffmpeg.exe' : 'ffmpeg'
const file = getFFMpegExecutable()
execFile(
file,
['-v error', ...args],
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -340,7 +340,6 @@ interface ThumbnailMetadata {
/** Returns arguments for FFMpeg to generate a thumbnail image file */
export function thumbnailFFMpegArguments(input: string, metadata: ThumbnailMetadata, seekTimeCode?: string): string[] {
return [
// process.platform === 'win32' ? 'ffmpeg.exe' : 'ffmpeg',
'-hide_banner',
seekTimeCode ? `-ss ${seekTimeCode}` : undefined,
`-i "${input}"`,
Expand Down
Loading

0 comments on commit 9a60c02

Please sign in to comment.