Skip to content

Commit 01a4aad

Browse files
committed
implemented method for generating captions from a clip
1 parent 643205a commit 01a4aad

File tree

3 files changed

+58
-10
lines changed

3 files changed

+58
-10
lines changed

playground/main.ts

Lines changed: 3 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -75,13 +75,13 @@ await composition.add(
7575
start: composition.findClips(core.VideoClip).at(0)?.stop.subtract(new core.Timestamp(6000)),
7676
stop: composition.duration
7777
})
78-
)
78+
);
7979

80-
await composition.add(
80+
(await composition.add(
8181
new core.AudioClip(await core.AudioSource.from('/audio.mp3'), {
8282
transcript: core.Transcript.fromJSON(captions).optimize(),
8383
})
84-
);
84+
)).generateCaptions();
8585

8686
await composition.add(
8787
new core.TextClip({
@@ -130,7 +130,3 @@ await composition.add(
130130
}]
131131
})
132132
);
133-
134-
await composition.createTrack('caption')
135-
.from(composition.findClips(core.AudioClip)[0])
136-
.create();

src/clips/media/media.spec.ts

Lines changed: 32 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,12 +9,14 @@ import { describe, expect, it, vi, beforeEach, afterAll } from 'vitest';
99
import { Keyframe, Timestamp, Transcript } from '../../models';
1010
import { captions } from '../../test/captions';
1111
import { Composition } from '../../composition';
12-
import { MediaTrack } from '../../tracks';
12+
import { CaptionTrack, MediaTrack } from '../../tracks';
13+
import { MediaClipProps } from './media.interfaces';
14+
import { Font, TextClip } from '../text';
15+
import { ValidationError } from '../../errors';
1316
import { VisualMixin, VisualMixinProps } from '../mixins';
1417
import { MediaClip } from './media';
1518

1619
import type { frame, MixinType } from '../../types';
17-
import { MediaClipProps } from './media.interfaces';
1820

1921
describe('The Media Clip', () => {
2022
const mockFn = vi.fn();
@@ -285,6 +287,34 @@ describe('The Media Clip', () => {
285287
expect(copy.range[1]).toBeInstanceOf(Timestamp);
286288
expect(copy.range[1].frames).toBe(80);
287289
});
290+
291+
it('should generate a caption track using a transcript', async () => {
292+
vi.spyOn(Font.prototype, 'load').mockImplementation(async () => new Font());
293+
clip.transcript = Transcript.fromJSON(captions);
294+
clip.state = 'READY';
295+
296+
const composition = new Composition();
297+
await composition.add(clip);
298+
299+
await clip.generateCaptions();
300+
301+
expect(composition.tracks.length).toBe(2);
302+
303+
const track = composition.tracks[0];
304+
305+
expect(track).toBeInstanceOf(CaptionTrack);
306+
expect(track.clips.length).toBe(36);
307+
expect(track.clips[0]).toBeInstanceOf(TextClip);
308+
expect((track.clips[0] as TextClip).text).toBe('Is the');
309+
});
310+
311+
it('should throw an error when trying to generate captions without a composition', async () => {
312+
vi.spyOn(Font.prototype, 'load').mockImplementation(async () => new Font());
313+
clip.transcript = Transcript.fromJSON(captions);
314+
clip.state = 'READY';
315+
316+
await expect(() => clip.generateCaptions()).rejects.toThrowError(ValidationError)
317+
});
288318
});
289319

290320
describe('Split tests - the Media Clip object', () => {

src/clips/media/media.ts

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,9 +11,10 @@ import { AsyncMixin } from '../mixins';
1111
import { RangeDeserializer } from './media.deserializer';
1212
import { serializable, } from '../../services';
1313
import { replaceKeyframes } from '../clip/clip.utils';
14+
import { ValidationError } from '../../errors';
1415
import { Clip } from '../clip';
1516

16-
import type { Track } from '../../tracks';
17+
import type { CaptionPresetStrategy, Track } from '../../tracks';
1718
import type { float, frame } from '../../types';
1819
import type { MediaClipProps } from './media.interfaces';
1920

@@ -274,6 +275,27 @@ export class MediaClip<Props extends MediaClipProps = MediaClipProps> extends As
274275
return copy;
275276
}
276277

278+
/**
279+
* Generates a new caption track for the current clip using the specified captioning strategy.
280+
* @param preset An optional CaptionPresetStrategy to define how captions should be generated.
281+
* @returns {Promise<void>} A promise that resolves when the caption track has been successfully created.
282+
*/
283+
public async generateCaptions(preset?: CaptionPresetStrategy): Promise<this> {
284+
if (!this.track?.composition) {
285+
throw new ValidationError({
286+
i18n: 'compositionNotDefined',
287+
message: 'Captions can only be generated after the clip has been added to the composition',
288+
});
289+
}
290+
291+
await this.track.composition
292+
.createTrack('caption')
293+
.from(this)
294+
.create(preset);
295+
296+
return this;
297+
}
298+
277299
public set(props?: Props): this {
278300
return super.set(props);
279301
}

0 commit comments

Comments
 (0)