@aelionsdk/sdk 0.1.0-beta.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +63 -0
- package/dist/audio-controller.d.ts +25 -0
- package/dist/audio-controller.d.ts.map +1 -0
- package/dist/audio-controller.js +235 -0
- package/dist/audio-mastering.d.ts +27 -0
- package/dist/audio-mastering.d.ts.map +1 -0
- package/dist/audio-mastering.js +160 -0
- package/dist/composition.d.ts +75 -0
- package/dist/composition.d.ts.map +1 -0
- package/dist/composition.js +146 -0
- package/dist/default-schemas.d.ts +7 -0
- package/dist/default-schemas.d.ts.map +1 -0
- package/dist/default-schemas.js +18 -0
- package/dist/export-job.d.ts +22 -0
- package/dist/export-job.d.ts.map +1 -0
- package/dist/export-job.js +126 -0
- package/dist/extension-host.d.ts +90 -0
- package/dist/extension-host.d.ts.map +1 -0
- package/dist/extension-host.js +367 -0
- package/dist/index.d.ts +18 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +19 -0
- package/dist/media-provider.d.ts +49 -0
- package/dist/media-provider.d.ts.map +1 -0
- package/dist/media-provider.js +388 -0
- package/dist/migration-materials.d.ts +10 -0
- package/dist/migration-materials.d.ts.map +1 -0
- package/dist/migration-materials.js +148 -0
- package/dist/migration.d.ts +109 -0
- package/dist/migration.d.ts.map +1 -0
- package/dist/migration.js +1232 -0
- package/dist/persistence.d.ts +73 -0
- package/dist/persistence.d.ts.map +1 -0
- package/dist/persistence.js +245 -0
- package/dist/player.d.ts +22 -0
- package/dist/player.d.ts.map +1 -0
- package/dist/player.js +522 -0
- package/dist/preview-controller.d.ts +62 -0
- package/dist/preview-controller.d.ts.map +1 -0
- package/dist/preview-controller.js +377 -0
- package/dist/preview-quality.d.ts +7 -0
- package/dist/preview-quality.d.ts.map +1 -0
- package/dist/preview-quality.js +8 -0
- package/dist/production-media-provider.d.ts +112 -0
- package/dist/production-media-provider.d.ts.map +1 -0
- package/dist/production-media-provider.js +749 -0
- package/dist/project-builder.d.ts +249 -0
- package/dist/project-builder.d.ts.map +1 -0
- package/dist/project-builder.js +953 -0
- package/dist/runtime-material-registry.d.ts +10 -0
- package/dist/runtime-material-registry.d.ts.map +1 -0
- package/dist/runtime-material-registry.js +23 -0
- package/dist/session.d.ts +29 -0
- package/dist/session.d.ts.map +1 -0
- package/dist/session.js +1114 -0
- package/dist/types.d.ts +392 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +1 -0
- package/package.json +60 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 FoyonaCZY and AelionSDK contributors
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
# `@aelionsdk/sdk`
|
|
2
|
+
|
|
3
|
+
Browser-first video editing, preview, playback and export APIs for AelionSDK.
|
|
4
|
+
|
|
5
|
+
## Install the public Beta
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install @aelionsdk/sdk@next
|
|
9
|
+
npm install --save-dev @aelionsdk/vite-plugin@next vite
|
|
10
|
+
```
|
|
11
|
+
|
|
12
|
+
`next` currently resolves to `0.1.0-beta.1`. Pin the exact version after
|
|
13
|
+
validation instead of following a moving prerelease tag.
|
|
14
|
+
|
|
15
|
+
## Vite setup
|
|
16
|
+
|
|
17
|
+
```ts
|
|
18
|
+
// vite.config.ts
|
|
19
|
+
import { aelion } from '@aelionsdk/vite-plugin';
|
|
20
|
+
import { defineConfig } from 'vite';
|
|
21
|
+
|
|
22
|
+
export default defineConfig({
|
|
23
|
+
plugins: [aelion()],
|
|
24
|
+
});
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
The plugin emits the Renderer Worker, Export Worker and both AudioWorklet
|
|
28
|
+
entries. Non-Vite hosts can deploy those entries themselves and pass their
|
|
29
|
+
URLs through `AelionSessionOptions.runtimeAssets`.
|
|
30
|
+
|
|
31
|
+
## Start a session
|
|
32
|
+
|
|
33
|
+
```ts
|
|
34
|
+
import { Aelion, ProductionMediaProvider, createProject } from '@aelionsdk/sdk';
|
|
35
|
+
|
|
36
|
+
const media = new ProductionMediaProvider();
|
|
37
|
+
const session = await Aelion.createSession({ media });
|
|
38
|
+
|
|
39
|
+
const builder = createProject({
|
|
40
|
+
projectId: 'project_main',
|
|
41
|
+
title: 'My project',
|
|
42
|
+
width: 1920,
|
|
43
|
+
height: 1080,
|
|
44
|
+
frameRate: { numerator: 30, denominator: 1 },
|
|
45
|
+
sampleRate: 48_000,
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
await session.loadProject(builder.build());
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
See the [AelionSDK documentation](https://foyonaczy.github.io/AelionSDK/) for
|
|
52
|
+
media registration, Composition APIs, preview, local export, persistent
|
|
53
|
+
checkpoints, deployment headers and capability preflight.
|
|
54
|
+
|
|
55
|
+
## Beta contract
|
|
56
|
+
|
|
57
|
+
The Beta is tested on the automated Chromium and Firefox matrix. Playwright
|
|
58
|
+
WebKit and a mobile viewport cover public capability and fallback contracts;
|
|
59
|
+
physical Safari, iOS and Android devices are not yet certified. Local color
|
|
60
|
+
execution is RGBA8 SDR. Public APIs may still change before the first stable
|
|
61
|
+
release and will be recorded in the repository changelog and migration guide.
|
|
62
|
+
|
|
63
|
+
AelionSDK is licensed under the MIT License.
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { type LoudnessReport, type SilenceDetectionResult, type WaveformPeakResult } from '@aelionsdk/audio';
|
|
2
|
+
import type { AelionProject } from '@aelionsdk/project-schema';
|
|
3
|
+
import type { RenderIr } from '@aelionsdk/render-ir';
|
|
4
|
+
import type { TransactionBuilder, TransactionCommit } from '@aelionsdk/transaction';
|
|
5
|
+
import type { AelionAudioAnalysisOptions, AelionAudioApi, AelionAudioMasteringOptions, AelionAudioRemoveSilenceOptions, AelionAudioRemoveSilenceResult, AelionAudioWaveformOptions, AelionMediaProvider } from './types.js';
|
|
6
|
+
interface AudioControllerHost {
|
|
7
|
+
readonly ir: () => RenderIr;
|
|
8
|
+
readonly project: () => Readonly<AelionProject>;
|
|
9
|
+
readonly media: () => AelionMediaProvider;
|
|
10
|
+
readonly revision: () => bigint;
|
|
11
|
+
readonly edit: (label: string, callback: (transaction: TransactionBuilder) => void) => TransactionCommit;
|
|
12
|
+
}
|
|
13
|
+
export declare class SessionAudioController implements AelionAudioApi {
|
|
14
|
+
private readonly host;
|
|
15
|
+
constructor(host: AudioControllerHost);
|
|
16
|
+
getMastering(): AelionAudioMasteringOptions | undefined;
|
|
17
|
+
configureMastering(options: AelionAudioMasteringOptions): TransactionCommit;
|
|
18
|
+
analyze(options?: AelionAudioAnalysisOptions): Promise<LoudnessReport>;
|
|
19
|
+
waveform(options?: AelionAudioWaveformOptions): Promise<WaveformPeakResult>;
|
|
20
|
+
detectSilence(options: AelionAudioRemoveSilenceOptions): Promise<SilenceDetectionResult>;
|
|
21
|
+
removeSilence(options: AelionAudioRemoveSilenceOptions): Promise<AelionAudioRemoveSilenceResult>;
|
|
22
|
+
}
|
|
23
|
+
export declare function projectAudioMastering(project: Readonly<AelionProject>, sequenceId: string): AelionAudioMasteringOptions | undefined;
|
|
24
|
+
export {};
|
|
25
|
+
//# sourceMappingURL=audio-controller.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"audio-controller.d.ts","sourceRoot":"","sources":["../src/audio-controller.ts"],"names":[],"mappings":"AAAA,OAAO,EAKL,KAAK,cAAc,EACnB,KAAK,sBAAsB,EAC3B,KAAK,kBAAkB,EACxB,MAAM,kBAAkB,CAAC;AAE1B,OAAO,KAAK,EAAE,aAAa,EAAc,MAAM,2BAA2B,CAAC;AAC3E,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,sBAAsB,CAAC;AACrD,OAAO,KAAK,EAAE,kBAAkB,EAAE,iBAAiB,EAAE,MAAM,wBAAwB,CAAC;AAEpF,OAAO,KAAK,EACV,0BAA0B,EAC1B,cAAc,EACd,2BAA2B,EAC3B,+BAA+B,EAC/B,8BAA8B,EAC9B,0BAA0B,EAC1B,mBAAmB,EACpB,MAAM,YAAY,CAAC;AAIpB,UAAU,mBAAmB;IAC3B,QAAQ,CAAC,EAAE,EAAE,MAAM,QAAQ,CAAC;IAC5B,QAAQ,CAAC,OAAO,EAAE,MAAM,QAAQ,CAAC,aAAa,CAAC,CAAC;IAChD,QAAQ,CAAC,KAAK,EAAE,MAAM,mBAAmB,CAAC;IAC1C,QAAQ,CAAC,QAAQ,EAAE,MAAM,MAAM,CAAC;IAChC,QAAQ,CAAC,IAAI,EAAE,CACb,KAAK,EAAE,MAAM,EACb,QAAQ,EAAE,CAAC,WAAW,EAAE,kBAAkB,KAAK,IAAI,KAChD,iBAAiB,CAAC;CACxB;AAwED,qBAAa,sBAAuB,YAAW,cAAc;IACxC,OAAO,CAAC,QAAQ,CAAC,IAAI;gBAAJ,IAAI,EAAE,mBAAmB;IAEtD,YAAY,IAAI,2BAA2B,GAAG,SAAS;IAKvD,kBAAkB,CAAC,OAAO,EAAE,2BAA2B,GAAG,iBAAiB;IAoBrE,OAAO,CAAC,OAAO,GAAE,0BAA+B,GAAG,OAAO,CAAC,cAAc,CAAC;IAiBhF,QAAQ,CAAC,OAAO,GAAE,0BAA+B,GAAG,OAAO,CAAC,kBAAkB,CAAC;IAgBzE,aAAa,CACxB,OAAO,EAAE,+BAA+B,GACvC,OAAO,CAAC,sBAAsB,CAAC;IAkCrB,aAAa,CACxB,OAAO,EAAE,+BAA+B,GACvC,OAAO,CAAC,8BAA8B,CAAC;CA0H3C;AAED,wBAAgB,qBAAqB,CACnC,OAAO,EAAE,QAAQ,CAAC,aAAa,CAAC,EAChC,UAAU,EAAE,MAAM,GACjB,2BAA2B,GAAG,SAAS,CAEzC"}
|
|
@@ -0,0 +1,235 @@
|
|
|
1
|
+
import { StreamingLoudnessAnalyzer, buildWaveformPeaks, detectSilence, renderIrAudio, } from '@aelionsdk/audio';
|
|
2
|
+
import { sampleBoundaryUs, sampleIndexAtTime } from '@aelionsdk/core';
|
|
3
|
+
const MASTERING_EXTENSION = 'aelion.audio.mastering';
|
|
4
|
+
function channelCount(layout) {
|
|
5
|
+
if (layout === 'mono')
|
|
6
|
+
return 1;
|
|
7
|
+
if (layout === 'stereo')
|
|
8
|
+
return 2;
|
|
9
|
+
if (layout === '5.1')
|
|
10
|
+
return 6;
|
|
11
|
+
throw new RangeError(`Unsupported channel layout ${layout}`);
|
|
12
|
+
}
|
|
13
|
+
function totalFrames(ir) {
|
|
14
|
+
return sampleIndexAtTime(ir.durationUs, ir.sampleRate, 'floor');
|
|
15
|
+
}
|
|
16
|
+
function readSelection(host, options, startFrame, frameCount, signal) {
|
|
17
|
+
const ir = host.ir();
|
|
18
|
+
return renderIrAudio({
|
|
19
|
+
ir,
|
|
20
|
+
startFrame,
|
|
21
|
+
frameCount,
|
|
22
|
+
channelCount: channelCount(ir.channelLayout),
|
|
23
|
+
source: host.media(),
|
|
24
|
+
...(options.trackIds === undefined ? {} : { trackIds: options.trackIds }),
|
|
25
|
+
...(options.itemIds === undefined ? {} : { itemIds: options.itemIds }),
|
|
26
|
+
...(signal === undefined ? {} : { signal }),
|
|
27
|
+
});
|
|
28
|
+
}
|
|
29
|
+
function assertAudioItem(project, itemId) {
|
|
30
|
+
const item = project.items[itemId];
|
|
31
|
+
if (item === undefined)
|
|
32
|
+
throw new ReferenceError(`Unknown Item: ${itemId}`);
|
|
33
|
+
if (item.type !== 'audio')
|
|
34
|
+
throw new TypeError(`${itemId} is not an audio Item`);
|
|
35
|
+
return item;
|
|
36
|
+
}
|
|
37
|
+
function masteringFrom(project, sequenceId) {
|
|
38
|
+
const sequence = project.sequences[sequenceId];
|
|
39
|
+
const extensions = sequence?.extensions;
|
|
40
|
+
const value = extensions !== null && typeof extensions === 'object' && !Array.isArray(extensions)
|
|
41
|
+
? extensions[MASTERING_EXTENSION]
|
|
42
|
+
: undefined;
|
|
43
|
+
return value !== null && typeof value === 'object' && !Array.isArray(value)
|
|
44
|
+
? structuredClone(value)
|
|
45
|
+
: undefined;
|
|
46
|
+
}
|
|
47
|
+
export class SessionAudioController {
|
|
48
|
+
host;
|
|
49
|
+
constructor(host) {
|
|
50
|
+
this.host = host;
|
|
51
|
+
}
|
|
52
|
+
getMastering() {
|
|
53
|
+
const ir = this.host.ir();
|
|
54
|
+
return masteringFrom(this.host.project(), ir.sequenceId);
|
|
55
|
+
}
|
|
56
|
+
configureMastering(options) {
|
|
57
|
+
const ir = this.host.ir();
|
|
58
|
+
const project = this.host.project();
|
|
59
|
+
const sequence = project.sequences[ir.sequenceId];
|
|
60
|
+
if (sequence === undefined)
|
|
61
|
+
throw new ReferenceError(`Unknown Sequence: ${ir.sequenceId}`);
|
|
62
|
+
const currentExtensions = sequence.extensions !== null &&
|
|
63
|
+
typeof sequence.extensions === 'object' &&
|
|
64
|
+
!Array.isArray(sequence.extensions)
|
|
65
|
+
? sequence.extensions
|
|
66
|
+
: {};
|
|
67
|
+
const extensions = {
|
|
68
|
+
...currentExtensions,
|
|
69
|
+
[MASTERING_EXTENSION]: structuredClone(options),
|
|
70
|
+
};
|
|
71
|
+
return this.host.edit('Configure audio mastering', transaction => {
|
|
72
|
+
transaction.setField('sequences', ir.sequenceId, ['extensions'], extensions);
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
async analyze(options = {}) {
|
|
76
|
+
const ir = this.host.ir();
|
|
77
|
+
const channels = channelCount(ir.channelLayout);
|
|
78
|
+
const analyzer = new StreamingLoudnessAnalyzer(ir.sampleRate, channels);
|
|
79
|
+
const frames = totalFrames(ir);
|
|
80
|
+
const blockFrames = options.blockFrames ?? 8_192;
|
|
81
|
+
for (let startFrame = 0; startFrame < frames; startFrame += blockFrames) {
|
|
82
|
+
const frameCount = Math.min(blockFrames, frames - startFrame);
|
|
83
|
+
analyzer.process(await readSelection(this.host, options, startFrame, frameCount, options.signal));
|
|
84
|
+
options.onProgress?.((startFrame + frameCount) / Math.max(1, frames));
|
|
85
|
+
}
|
|
86
|
+
if (frames === 0)
|
|
87
|
+
options.onProgress?.(1);
|
|
88
|
+
return analyzer.finish();
|
|
89
|
+
}
|
|
90
|
+
waveform(options = {}) {
|
|
91
|
+
const ir = this.host.ir();
|
|
92
|
+
const channels = channelCount(ir.channelLayout);
|
|
93
|
+
return buildWaveformPeaks({
|
|
94
|
+
sampleRate: ir.sampleRate,
|
|
95
|
+
channelCount: channels,
|
|
96
|
+
totalFrames: totalFrames(ir),
|
|
97
|
+
...(options.windowFrames === undefined ? {} : { windowFrames: options.windowFrames }),
|
|
98
|
+
...(options.maxPoints === undefined ? {} : { maxPoints: options.maxPoints }),
|
|
99
|
+
...(options.signal === undefined ? {} : { signal: options.signal }),
|
|
100
|
+
...(options.onProgress === undefined ? {} : { onProgress: options.onProgress }),
|
|
101
|
+
readFrames: (startFrame, frameCount, signal) => readSelection(this.host, options, startFrame, frameCount, signal),
|
|
102
|
+
});
|
|
103
|
+
}
|
|
104
|
+
async detectSilence(options) {
|
|
105
|
+
const ir = this.host.ir();
|
|
106
|
+
const project = this.host.project();
|
|
107
|
+
const item = assertAudioItem(project, options.itemId);
|
|
108
|
+
const channels = channelCount(ir.channelLayout);
|
|
109
|
+
const itemStartFrame = sampleIndexAtTime(item.range.startUs, ir.sampleRate, 'floor');
|
|
110
|
+
const itemEndFrame = sampleIndexAtTime(item.range.startUs + item.range.durationUs, ir.sampleRate, 'floor');
|
|
111
|
+
return detectSilence({
|
|
112
|
+
sampleRate: ir.sampleRate,
|
|
113
|
+
channelCount: channels,
|
|
114
|
+
totalFrames: itemEndFrame - itemStartFrame,
|
|
115
|
+
...(options.thresholdDb === undefined ? {} : { thresholdDb: options.thresholdDb }),
|
|
116
|
+
...(options.minimumSilenceUs === undefined
|
|
117
|
+
? {}
|
|
118
|
+
: { minimumSilenceUs: options.minimumSilenceUs }),
|
|
119
|
+
...(options.paddingUs === undefined ? {} : { paddingUs: options.paddingUs }),
|
|
120
|
+
...(options.windowFrames === undefined ? {} : { windowFrames: options.windowFrames }),
|
|
121
|
+
...(options.signal === undefined ? {} : { signal: options.signal }),
|
|
122
|
+
...(options.onProgress === undefined ? {} : { onProgress: options.onProgress }),
|
|
123
|
+
readFrames: (startFrame, frameCount, signal) => readSelection(this.host, { itemIds: [item.id] }, itemStartFrame + startFrame, frameCount, signal),
|
|
124
|
+
});
|
|
125
|
+
}
|
|
126
|
+
async removeSilence(options) {
|
|
127
|
+
const baseRevision = this.host.revision();
|
|
128
|
+
const project = this.host.project();
|
|
129
|
+
const ir = this.host.ir();
|
|
130
|
+
const item = assertAudioItem(project, options.itemId);
|
|
131
|
+
if (item.linkGroupId !== undefined) {
|
|
132
|
+
throw new TypeError('Unlink an audio Item before removing silence');
|
|
133
|
+
}
|
|
134
|
+
if ((item.markerIds?.length ?? 0) > 0 || item.materialInstanceIds.length > 0) {
|
|
135
|
+
throw new TypeError('Audio Item markers/effects require an explicit split ownership policy');
|
|
136
|
+
}
|
|
137
|
+
if (Object.values(project.transitions).some(value => value.fromItemId === item.id || value.toItemId === item.id)) {
|
|
138
|
+
throw new TypeError('Remove transitions attached to an audio Item before removing silence');
|
|
139
|
+
}
|
|
140
|
+
if (item.source.timeMapping.type !== 'linear') {
|
|
141
|
+
throw new TypeError('Silence removal currently requires a linear audio TimeMap');
|
|
142
|
+
}
|
|
143
|
+
const mapping = item.source.timeMapping;
|
|
144
|
+
if (mapping.reverse ||
|
|
145
|
+
mapping.rate.numerator !== mapping.rate.denominator ||
|
|
146
|
+
mapping.boundary !== 'hold') {
|
|
147
|
+
throw new TypeError('Silence removal requires forward 1x hold-boundary audio');
|
|
148
|
+
}
|
|
149
|
+
const track = project.tracks[item.trackId];
|
|
150
|
+
if (track === undefined)
|
|
151
|
+
throw new ReferenceError(`Unknown Track: ${item.trackId}`);
|
|
152
|
+
const originalEndUs = item.range.startUs + item.range.durationUs;
|
|
153
|
+
const overlapping = track.itemIds
|
|
154
|
+
.filter(id => id !== item.id)
|
|
155
|
+
.flatMap(id => {
|
|
156
|
+
const candidate = project.items[id];
|
|
157
|
+
if (candidate === undefined)
|
|
158
|
+
return [];
|
|
159
|
+
const endUs = candidate.range.startUs + candidate.range.durationUs;
|
|
160
|
+
return candidate.range.startUs < originalEndUs && endUs > item.range.startUs
|
|
161
|
+
? [candidate.id]
|
|
162
|
+
: [];
|
|
163
|
+
});
|
|
164
|
+
if (overlapping.length > 0) {
|
|
165
|
+
throw new TypeError('Silence compaction requires a non-overlapping audio Item');
|
|
166
|
+
}
|
|
167
|
+
const detection = await this.detectSilence(options);
|
|
168
|
+
if (this.host.revision() !== baseRevision) {
|
|
169
|
+
throw new Error('Project revision changed while silence detection was running');
|
|
170
|
+
}
|
|
171
|
+
if (detection.removedFrames === 0) {
|
|
172
|
+
throw new RangeError('No removable silence was detected');
|
|
173
|
+
}
|
|
174
|
+
const kept = detection.nonSilent;
|
|
175
|
+
const nextItemId = track.itemIds[track.itemIds.indexOf(item.id) + 1];
|
|
176
|
+
const createdIds = [];
|
|
177
|
+
let cursorUs = item.range.startUs;
|
|
178
|
+
const segmentValues = kept.map((range, index) => {
|
|
179
|
+
const localStartUs = sampleBoundaryUs(range.startFrame, ir.sampleRate);
|
|
180
|
+
const localEndUs = sampleBoundaryUs(range.startFrame + range.frameCount, ir.sampleRate);
|
|
181
|
+
const durationUs = Math.max(1, localEndUs - localStartUs);
|
|
182
|
+
const id = index === 0 ? item.id : `${item.id}_audible_${baseRevision.toString()}_${index.toString()}`;
|
|
183
|
+
const value = structuredClone(item);
|
|
184
|
+
value.id = id;
|
|
185
|
+
value.range = { startUs: cursorUs, durationUs };
|
|
186
|
+
value.source.sourceRange = {
|
|
187
|
+
startUs: item.source.sourceRange.startUs + localStartUs,
|
|
188
|
+
durationUs,
|
|
189
|
+
};
|
|
190
|
+
cursorUs += durationUs;
|
|
191
|
+
return value;
|
|
192
|
+
});
|
|
193
|
+
const removedUs = item.range.durationUs - (cursorUs - item.range.startUs);
|
|
194
|
+
const laterItems = track.itemIds.flatMap(id => {
|
|
195
|
+
const value = project.items[id];
|
|
196
|
+
return value !== undefined && value.id !== item.id && value.range.startUs >= originalEndUs
|
|
197
|
+
? [value]
|
|
198
|
+
: [];
|
|
199
|
+
});
|
|
200
|
+
const laterTransitions = Object.values(project.transitions).filter(value => value.trackId === track.id && value.range.startUs >= originalEndUs);
|
|
201
|
+
const commit = this.host.edit('Remove audio silence', transaction => {
|
|
202
|
+
if (segmentValues.length === 0) {
|
|
203
|
+
transaction.listRemove('tracks', track.id, ['itemIds'], item.id);
|
|
204
|
+
transaction.deleteEntity('items', item.id);
|
|
205
|
+
}
|
|
206
|
+
else {
|
|
207
|
+
const first = segmentValues[0];
|
|
208
|
+
if (first === undefined)
|
|
209
|
+
throw new Error('Missing first audible segment');
|
|
210
|
+
transaction.setField('items', item.id, ['range'], first.range);
|
|
211
|
+
transaction.setField('items', item.id, ['source', 'sourceRange'], first.source.sourceRange);
|
|
212
|
+
for (const segment of segmentValues.slice(1)) {
|
|
213
|
+
transaction.createEntity('items', segment.id, segment);
|
|
214
|
+
transaction.listInsert('tracks', track.id, ['itemIds'], segment.id, nextItemId);
|
|
215
|
+
createdIds.push(segment.id);
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
for (const later of laterItems) {
|
|
219
|
+
transaction.setField('items', later.id, ['range', 'startUs'], later.range.startUs - removedUs);
|
|
220
|
+
}
|
|
221
|
+
for (const transition of laterTransitions) {
|
|
222
|
+
transaction.setField('transitions', transition.id, ['range', 'startUs'], transition.range.startUs - removedUs);
|
|
223
|
+
}
|
|
224
|
+
});
|
|
225
|
+
return {
|
|
226
|
+
commit,
|
|
227
|
+
detection,
|
|
228
|
+
itemIds: segmentValues.length === 0 ? [] : [item.id, ...createdIds],
|
|
229
|
+
removedUs,
|
|
230
|
+
};
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
export function projectAudioMastering(project, sequenceId) {
|
|
234
|
+
return masteringFrom(project, sequenceId);
|
|
235
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { type LoudnessReport } from '@aelionsdk/audio';
|
|
2
|
+
import type { RenderIr } from '@aelionsdk/render-ir';
|
|
3
|
+
import type { AelionAudioMasteringOptions, AelionMediaProvider } from './types.js';
|
|
4
|
+
export interface MasteredAudioRequest {
|
|
5
|
+
readonly startFrame: number;
|
|
6
|
+
readonly frameCount: number;
|
|
7
|
+
readonly channelCount: number;
|
|
8
|
+
}
|
|
9
|
+
export interface AudioMasteringReport {
|
|
10
|
+
readonly inputLoudness?: LoudnessReport;
|
|
11
|
+
readonly appliedGainDb: number;
|
|
12
|
+
readonly limiterEnabled: boolean;
|
|
13
|
+
readonly duckingRules: number;
|
|
14
|
+
}
|
|
15
|
+
export interface MasteredAudioRenderer {
|
|
16
|
+
readonly report: AudioMasteringReport;
|
|
17
|
+
readonly render: (request: MasteredAudioRequest, signal?: AbortSignal) => Promise<Float32Array>;
|
|
18
|
+
}
|
|
19
|
+
export interface CreateMasteredAudioRendererOptions {
|
|
20
|
+
readonly ir: RenderIr;
|
|
21
|
+
readonly source: AelionMediaProvider;
|
|
22
|
+
readonly processing?: AelionAudioMasteringOptions;
|
|
23
|
+
readonly signal?: AbortSignal;
|
|
24
|
+
readonly onAnalysisProgress?: (progress: number) => void;
|
|
25
|
+
}
|
|
26
|
+
export declare function createMasteredAudioRenderer(options: CreateMasteredAudioRendererOptions): Promise<MasteredAudioRenderer>;
|
|
27
|
+
//# sourceMappingURL=audio-mastering.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"audio-mastering.d.ts","sourceRoot":"","sources":["../src/audio-mastering.ts"],"names":[],"mappings":"AAAA,OAAO,EAKL,KAAK,cAAc,EACpB,MAAM,kBAAkB,CAAC;AAE1B,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,sBAAsB,CAAC;AAErD,OAAO,KAAK,EAAE,2BAA2B,EAAE,mBAAmB,EAAE,MAAM,YAAY,CAAC;AAEnF,MAAM,WAAW,oBAAoB;IACnC,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAC;CAC/B;AAED,MAAM,WAAW,oBAAoB;IACnC,QAAQ,CAAC,aAAa,CAAC,EAAE,cAAc,CAAC;IACxC,QAAQ,CAAC,aAAa,EAAE,MAAM,CAAC;IAC/B,QAAQ,CAAC,cAAc,EAAE,OAAO,CAAC;IACjC,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAC;CAC/B;AAED,MAAM,WAAW,qBAAqB;IACpC,QAAQ,CAAC,MAAM,EAAE,oBAAoB,CAAC;IACtC,QAAQ,CAAC,MAAM,EAAE,CAAC,OAAO,EAAE,oBAAoB,EAAE,MAAM,CAAC,EAAE,WAAW,KAAK,OAAO,CAAC,YAAY,CAAC,CAAC;CACjG;AAED,MAAM,WAAW,kCAAkC;IACjD,QAAQ,CAAC,EAAE,EAAE,QAAQ,CAAC;IACtB,QAAQ,CAAC,MAAM,EAAE,mBAAmB,CAAC;IACrC,QAAQ,CAAC,UAAU,CAAC,EAAE,2BAA2B,CAAC;IAClD,QAAQ,CAAC,MAAM,CAAC,EAAE,WAAW,CAAC;IAC9B,QAAQ,CAAC,kBAAkB,CAAC,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,IAAI,CAAC;CAC1D;AAuCD,wBAAsB,2BAA2B,CAC/C,OAAO,EAAE,kCAAkC,GAC1C,OAAO,CAAC,qBAAqB,CAAC,CA2IhC"}
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
import { SidechainDucker, StreamingLoudnessAnalyzer, TruePeakLimiter, renderIrAudio, } from '@aelionsdk/audio';
|
|
2
|
+
import { sampleIndexAtTime, throwIfAborted } from '@aelionsdk/core';
|
|
3
|
+
function channelCountForIr(ir) {
|
|
4
|
+
if (ir.channelLayout === 'mono')
|
|
5
|
+
return 1;
|
|
6
|
+
if (ir.channelLayout === 'stereo')
|
|
7
|
+
return 2;
|
|
8
|
+
if (ir.channelLayout === '5.1')
|
|
9
|
+
return 6;
|
|
10
|
+
throw new RangeError(`Unsupported channel layout ${ir.channelLayout}`);
|
|
11
|
+
}
|
|
12
|
+
function assertTrackSelection(ir, ids, name) {
|
|
13
|
+
for (const id of ids) {
|
|
14
|
+
const track = ir.tracks.find(value => value.id === id);
|
|
15
|
+
if (track === undefined)
|
|
16
|
+
throw new ReferenceError(`${name} references unknown Track ${id}`);
|
|
17
|
+
if (track.kind !== 'audio')
|
|
18
|
+
throw new TypeError(`${name} requires audio Tracks`);
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
function gainPcm(pcm, gain) {
|
|
22
|
+
if (gain === 1)
|
|
23
|
+
return pcm;
|
|
24
|
+
const output = new Float32Array(pcm.length);
|
|
25
|
+
for (let index = 0; index < pcm.length; index += 1) {
|
|
26
|
+
output[index] = (pcm[index] ?? 0) * gain;
|
|
27
|
+
}
|
|
28
|
+
return output;
|
|
29
|
+
}
|
|
30
|
+
function monoDetector(pcm, channels) {
|
|
31
|
+
const frames = pcm.length / channels;
|
|
32
|
+
const output = new Float32Array(frames);
|
|
33
|
+
for (let frame = 0; frame < frames; frame += 1) {
|
|
34
|
+
let sum = 0;
|
|
35
|
+
for (let channel = 0; channel < channels; channel += 1) {
|
|
36
|
+
sum += Math.abs(pcm[frame * channels + channel] ?? 0);
|
|
37
|
+
}
|
|
38
|
+
output[frame] = sum / channels;
|
|
39
|
+
}
|
|
40
|
+
return output;
|
|
41
|
+
}
|
|
42
|
+
export async function createMasteredAudioRenderer(options) {
|
|
43
|
+
const processing = options.processing ?? {};
|
|
44
|
+
const expectedChannelCount = channelCountForIr(options.ir);
|
|
45
|
+
const totalFrames = sampleIndexAtTime(options.ir.durationUs, options.ir.sampleRate, 'floor');
|
|
46
|
+
const raw = (request, signal, trackIds) => renderIrAudio({
|
|
47
|
+
ir: options.ir,
|
|
48
|
+
startFrame: request.startFrame,
|
|
49
|
+
frameCount: request.frameCount,
|
|
50
|
+
channelCount: request.channelCount,
|
|
51
|
+
source: options.source,
|
|
52
|
+
...(trackIds === undefined ? {} : { trackIds }),
|
|
53
|
+
...(signal === undefined ? {} : { signal }),
|
|
54
|
+
});
|
|
55
|
+
let inputLoudness;
|
|
56
|
+
let appliedGainDb = 0;
|
|
57
|
+
if (processing.targetLufs !== undefined) {
|
|
58
|
+
if (!Number.isFinite(processing.targetLufs)) {
|
|
59
|
+
throw new RangeError('targetLufs must be finite');
|
|
60
|
+
}
|
|
61
|
+
const analyzer = new StreamingLoudnessAnalyzer(options.ir.sampleRate, expectedChannelCount);
|
|
62
|
+
const blockFrames = 16_384;
|
|
63
|
+
for (let startFrame = 0; startFrame < totalFrames; startFrame += blockFrames) {
|
|
64
|
+
throwIfAborted(options.signal, 'Audio mastering analysis');
|
|
65
|
+
const frameCount = Math.min(blockFrames, totalFrames - startFrame);
|
|
66
|
+
analyzer.process(await raw({ startFrame, frameCount, channelCount: expectedChannelCount }, options.signal));
|
|
67
|
+
options.onAnalysisProgress?.((startFrame + frameCount) / Math.max(1, totalFrames));
|
|
68
|
+
}
|
|
69
|
+
if (totalFrames === 0)
|
|
70
|
+
options.onAnalysisProgress?.(1);
|
|
71
|
+
inputLoudness = analyzer.finish();
|
|
72
|
+
if (Number.isFinite(inputLoudness.integratedLufs)) {
|
|
73
|
+
const desired = processing.targetLufs - inputLoudness.integratedLufs;
|
|
74
|
+
const maximum = processing.maximumGainDb ?? 24;
|
|
75
|
+
if (!Number.isFinite(maximum) || maximum < 0) {
|
|
76
|
+
throw new RangeError('maximumGainDb must be a non-negative finite number');
|
|
77
|
+
}
|
|
78
|
+
appliedGainDb = Math.max(-maximum, Math.min(maximum, desired));
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
const linearGain = 10 ** (appliedGainDb / 20);
|
|
82
|
+
const ducking = (processing.ducking ?? []).map((rule, index) => {
|
|
83
|
+
assertTrackSelection(options.ir, rule.programTrackIds, `ducking[${index.toString()}].program`);
|
|
84
|
+
assertTrackSelection(options.ir, rule.sidechainTrackIds, `ducking[${index.toString()}].sidechain`);
|
|
85
|
+
const lookaheadUs = rule.lookaheadUs ?? 0;
|
|
86
|
+
if (lookaheadUs !== 0) {
|
|
87
|
+
throw new RangeError('Export ducking lookaheadUs must be zero until compensated tail flushing is enabled');
|
|
88
|
+
}
|
|
89
|
+
return {
|
|
90
|
+
rule,
|
|
91
|
+
processor: new SidechainDucker({
|
|
92
|
+
sampleRate: options.ir.sampleRate,
|
|
93
|
+
channelCount: expectedChannelCount,
|
|
94
|
+
thresholdDb: rule.thresholdDb ?? -30,
|
|
95
|
+
reductionDb: rule.reductionDb ?? -12,
|
|
96
|
+
attackUs: rule.attackUs ?? 10_000,
|
|
97
|
+
releaseUs: rule.releaseUs ?? 250_000,
|
|
98
|
+
lookaheadUs,
|
|
99
|
+
}),
|
|
100
|
+
};
|
|
101
|
+
});
|
|
102
|
+
const duckedProgramTracks = new Set();
|
|
103
|
+
for (const { rule } of ducking) {
|
|
104
|
+
for (const trackId of rule.programTrackIds) {
|
|
105
|
+
if (duckedProgramTracks.has(trackId)) {
|
|
106
|
+
throw new TypeError(`Audio Track ${trackId} cannot be a program Track in multiple ducking rules`);
|
|
107
|
+
}
|
|
108
|
+
duckedProgramTracks.add(trackId);
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
const limiterEnabled = processing.limiter !== undefined && processing.limiter !== false;
|
|
112
|
+
const limiterOptions = limiterEnabled ? processing.limiter : undefined;
|
|
113
|
+
if ((limiterOptions?.lookaheadUs ?? 0) !== 0) {
|
|
114
|
+
throw new RangeError('Export limiter lookaheadUs must be zero until compensated tail flushing is enabled');
|
|
115
|
+
}
|
|
116
|
+
let limiter;
|
|
117
|
+
let expectedStartFrame = 0;
|
|
118
|
+
return {
|
|
119
|
+
report: {
|
|
120
|
+
...(inputLoudness === undefined ? {} : { inputLoudness }),
|
|
121
|
+
appliedGainDb,
|
|
122
|
+
limiterEnabled,
|
|
123
|
+
duckingRules: ducking.length,
|
|
124
|
+
},
|
|
125
|
+
render: async (request, signal) => {
|
|
126
|
+
if (request.channelCount !== expectedChannelCount) {
|
|
127
|
+
throw new RangeError(`Mastered audio requires ${expectedChannelCount.toString()} channels`);
|
|
128
|
+
}
|
|
129
|
+
if (request.startFrame !== expectedStartFrame) {
|
|
130
|
+
throw new RangeError('Mastered audio blocks must be requested sequentially from frame zero');
|
|
131
|
+
}
|
|
132
|
+
expectedStartFrame += request.frameCount;
|
|
133
|
+
let output = gainPcm(await raw(request, signal), linearGain);
|
|
134
|
+
for (const value of ducking) {
|
|
135
|
+
const [programRaw, sidechain] = await Promise.all([
|
|
136
|
+
raw(request, signal, value.rule.programTrackIds),
|
|
137
|
+
raw(request, signal, value.rule.sidechainTrackIds),
|
|
138
|
+
]);
|
|
139
|
+
const program = gainPcm(programRaw, linearGain);
|
|
140
|
+
const ducked = value.processor.process(program, monoDetector(sidechain, request.channelCount));
|
|
141
|
+
const mixed = new Float32Array(output.length);
|
|
142
|
+
for (let index = 0; index < mixed.length; index += 1) {
|
|
143
|
+
mixed[index] = (output[index] ?? 0) - (program[index] ?? 0) + (ducked[index] ?? 0);
|
|
144
|
+
}
|
|
145
|
+
output = mixed;
|
|
146
|
+
}
|
|
147
|
+
if (limiterEnabled) {
|
|
148
|
+
limiter ??= new TruePeakLimiter({
|
|
149
|
+
sampleRate: options.ir.sampleRate,
|
|
150
|
+
channelCount: request.channelCount,
|
|
151
|
+
ceilingDbtp: limiterOptions?.ceilingDbtp ?? -1,
|
|
152
|
+
releaseUs: limiterOptions?.releaseUs ?? 100_000,
|
|
153
|
+
lookaheadUs: 0,
|
|
154
|
+
});
|
|
155
|
+
output = limiter.process(output);
|
|
156
|
+
}
|
|
157
|
+
return output;
|
|
158
|
+
},
|
|
159
|
+
};
|
|
160
|
+
}
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import type { JsonObject, JsonValue } from '@aelionsdk/core';
|
|
2
|
+
import type { AelionProject } from '@aelionsdk/project-schema';
|
|
3
|
+
import { ProjectBuilder, type AddAssetOptions, type AddCaptionClipOptions, type AddImageClipOptions, type AddMaterialInstanceOptions, type AddMediaClipOptions, type AddShapeClipOptions, type AddTextClipOptions, type AddTrackOptions, type ClipAnimatableProperty, type CreateProjectOptions, type Keyframe, type SetMaskOptions, type SetVisualOptions } from './project-builder.js';
|
|
4
|
+
export interface LayerOptions {
|
|
5
|
+
readonly id?: string;
|
|
6
|
+
readonly name?: string;
|
|
7
|
+
readonly enabled?: boolean;
|
|
8
|
+
readonly locked?: boolean;
|
|
9
|
+
}
|
|
10
|
+
export interface MaterialOptions extends Omit<AddMaterialInstanceOptions, 'id' | 'parameters' | 'name'> {
|
|
11
|
+
readonly name?: string;
|
|
12
|
+
readonly parameters?: JsonObject;
|
|
13
|
+
}
|
|
14
|
+
export interface ApplyMaterialOptions {
|
|
15
|
+
readonly id?: string;
|
|
16
|
+
readonly name?: string;
|
|
17
|
+
readonly parameters?: JsonObject;
|
|
18
|
+
}
|
|
19
|
+
export interface CompositionTransitionOptions extends Omit<ApplyMaterialOptions, 'id'> {
|
|
20
|
+
readonly transitionId?: string;
|
|
21
|
+
readonly materialInstanceId?: string;
|
|
22
|
+
readonly atUs: number;
|
|
23
|
+
readonly durationUs: number;
|
|
24
|
+
}
|
|
25
|
+
/** Reusable Material definition. Each use creates a separately owned Project instance. */
|
|
26
|
+
export declare class Material {
|
|
27
|
+
private readonly builder;
|
|
28
|
+
private readonly options;
|
|
29
|
+
constructor(builder: ProjectBuilder, options: MaterialOptions);
|
|
30
|
+
instantiate(options?: ApplyMaterialOptions): string;
|
|
31
|
+
}
|
|
32
|
+
/** Fluent handle for a Project Item. */
|
|
33
|
+
export declare class Clip {
|
|
34
|
+
readonly id: string;
|
|
35
|
+
readonly layer: Layer;
|
|
36
|
+
private readonly builder;
|
|
37
|
+
constructor(id: string, layer: Layer, builder: ProjectBuilder);
|
|
38
|
+
effect(material: Material, options?: ApplyMaterialOptions): this;
|
|
39
|
+
mask(source: Clip, options?: Omit<SetMaskOptions, 'sourceItemId'>): this;
|
|
40
|
+
keyframes<T extends JsonValue>(property: ClipAnimatableProperty, values: readonly Keyframe<T>[]): this;
|
|
41
|
+
visual(options: SetVisualOptions): this;
|
|
42
|
+
}
|
|
43
|
+
/** Ordered Composition layer backed by a schema-valid Aelion Track. */
|
|
44
|
+
export declare class Layer {
|
|
45
|
+
#private;
|
|
46
|
+
readonly kind: AddTrackOptions['kind'];
|
|
47
|
+
private readonly builder;
|
|
48
|
+
readonly id: string;
|
|
49
|
+
constructor(kind: AddTrackOptions['kind'], builder: ProjectBuilder, options?: LayerOptions);
|
|
50
|
+
video(options: Omit<AddMediaClipOptions, 'kind' | 'trackId'>): Clip;
|
|
51
|
+
audio(options: Omit<AddMediaClipOptions, 'kind' | 'trackId'>): Clip;
|
|
52
|
+
image(options: Omit<AddImageClipOptions, 'trackId'>): Clip;
|
|
53
|
+
text(options: Omit<AddTextClipOptions, 'trackId'>): Clip;
|
|
54
|
+
caption(options: Omit<AddCaptionClipOptions, 'trackId'>): Clip;
|
|
55
|
+
shape(options: Omit<AddShapeClipOptions, 'trackId'>): Clip;
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Product-level authoring API. It keeps Composition/Layer/Clip ergonomics while
|
|
59
|
+
* producing the same portable, validator-backed Project document as ProjectBuilder.
|
|
60
|
+
*/
|
|
61
|
+
export declare class Composition {
|
|
62
|
+
#private;
|
|
63
|
+
constructor(options?: CreateProjectOptions);
|
|
64
|
+
get projectId(): string;
|
|
65
|
+
get sequenceId(): string;
|
|
66
|
+
asset(options: AddAssetOptions): this;
|
|
67
|
+
layer(kind: AddTrackOptions['kind'], options?: LayerOptions): Layer;
|
|
68
|
+
material(options: MaterialOptions): Material;
|
|
69
|
+
transition(from: Clip, to: Clip, material: Material, options: CompositionTransitionOptions): string;
|
|
70
|
+
build(): Readonly<AelionProject>;
|
|
71
|
+
/** Escape hatch for advanced, schema-level operations without changing document semantics. */
|
|
72
|
+
advanced(): ProjectBuilder;
|
|
73
|
+
}
|
|
74
|
+
export declare function createComposition(options?: CreateProjectOptions): Composition;
|
|
75
|
+
//# sourceMappingURL=composition.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"composition.d.ts","sourceRoot":"","sources":["../src/composition.ts"],"names":[],"mappings":"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"}
|