mirror of
https://github.com/nexu-io/open-design.git
synced 2026-10-27 09:51:12 +01:00
Merge commit 'refs/pull/8619/head' of https://github.com/nexu-io/open-design into feat/motion-design-skill
This commit is contained in:
@@ -58,10 +58,17 @@ export function hyperFramesAudioEncodingArgs(tracks: HyperFramesAudioTrack[], du
|
||||
// Filters stay within FFmpeg 4.1 (the bundled Linux build): every track is
|
||||
// made stereo so one per-channel delay covers it (no `adelay all=1`), and
|
||||
// amix's input averaging is undone with a gain (no `amix normalize=0`).
|
||||
// Each track fades out where it stops sounding: its own authored end or the
|
||||
// film's end, whichever comes first. A track that already ends quietly is
|
||||
// unchanged; one cut short (a remake made shorter than its music) no longer
|
||||
// stops on a click.
|
||||
const filters = tracks.map((track, index) => {
|
||||
const delayMs = Math.round(track.start * 1000);
|
||||
const audible = Math.min(track.duration, duration - track.start);
|
||||
const fade = audible > 0 ? Math.min(0.6, audible / 4) : 0;
|
||||
const fadeOut = fade > 0 ? `afade=t=out:st=${round(audible - fade)}:d=${round(fade)},` : '';
|
||||
return `[${index + 1}:a]atrim=duration=${track.duration},asetpts=PTS-STARTPTS,`
|
||||
+ `aformat=channel_layouts=stereo,volume=${track.volume},adelay=${delayMs}|${delayMs}[a${index}]`;
|
||||
+ `aformat=channel_layouts=stereo,${fadeOut}volume=${track.volume},adelay=${delayMs}|${delayMs}[a${index}]`;
|
||||
});
|
||||
const mixed = tracks.length === 1
|
||||
? '[a0]'
|
||||
@@ -70,3 +77,7 @@ export function hyperFramesAudioEncodingArgs(tracks: HyperFramesAudioTrack[], du
|
||||
filters.push(`${mixed}apad,atrim=duration=${duration}[audio]`);
|
||||
return ['-filter_complex', filters.join(';'), '-map', '0:v', '-map', '[audio]', '-c:a', 'aac', '-b:a', '192k'];
|
||||
}
|
||||
|
||||
function round(seconds: number): number {
|
||||
return Math.round(seconds * 1000) / 1000;
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@ import { mkdtemp, writeFile, rm, symlink } from 'node:fs/promises';
|
||||
import { tmpdir } from 'node:os';
|
||||
import path from 'node:path';
|
||||
import { afterEach, beforeEach, expect, it } from 'vitest';
|
||||
import { prepareHyperFramesAudio } from '../../src/media/hyperframes-audio.js';
|
||||
import { hyperFramesAudioEncodingArgs, prepareHyperFramesAudio } from '../../src/media/hyperframes-audio.js';
|
||||
|
||||
let root: string;
|
||||
beforeEach(async () => {
|
||||
@@ -33,3 +33,17 @@ it.each(['data-start="intro + 2"', 'data-volume="2"', 'loop', 'data-media-start=
|
||||
await expect(prepareHyperFramesAudio(`<audio src="music.mp3" data-duration="3" ${attribute}></audio>`, root)).rejects.toThrow();
|
||||
},
|
||||
);
|
||||
|
||||
it('fades each track out where it stops sounding, including a cut at the film end', () => {
|
||||
const track = { file: 'music.mp3', format: 'mp3', volume: 1 };
|
||||
const graph = (tracks: Parameters<typeof hyperFramesAudioEncodingArgs>[0], duration: number) =>
|
||||
hyperFramesAudioEncodingArgs(tracks, duration)[1];
|
||||
// Music authored for 15 s in a film cut to 10 s fades over the film's last 0.6 s.
|
||||
expect(graph([{ ...track, start: 0, duration: 15 }], 10)).toContain('afade=t=out:st=9.4:d=0.6');
|
||||
// A track that ends inside the film fades at its own end, offset by its start.
|
||||
expect(graph([{ ...track, start: 2, duration: 4 }], 10)).toContain('afade=t=out:st=3.4:d=0.6');
|
||||
// Short tracks keep most of their sound: the fade is at most a quarter of what plays.
|
||||
expect(graph([{ ...track, start: 0, duration: 1 }], 10)).toContain('afade=t=out:st=0.75:d=0.25');
|
||||
// A track that starts after the film ends contributes no fade.
|
||||
expect(graph([{ ...track, start: 12, duration: 3 }], 10)).not.toContain('afade');
|
||||
});
|
||||
|
||||
@@ -94,4 +94,8 @@ Every template carries an original procedural soundtrack in
|
||||
used and no audio-generation model was involved. HTML preview sound starts with
|
||||
the film; when the browser blocks autoplay, the first click or key press turns
|
||||
it on, and Sound off mutes it. Sound follows the master timeline. The desktop
|
||||
export path mixes the timed local audio into the MP4 with FFmpeg.
|
||||
export path mixes the timed local audio into the MP4 with FFmpeg and fades each
|
||||
track out where it stops, so a remake made shorter than its music ends cleanly.
|
||||
Make-same keeps the template's soundtrack by default, because the music is part
|
||||
of the look being remade; a brief that asks for no music or different music
|
||||
takes precedence.
|
||||
|
||||
@@ -65,7 +65,12 @@ frame expensive and slow the MP4 export.
|
||||
## Sound
|
||||
Carry `assets/soundtrack.mp3` with the reference files. It is an original
|
||||
procedural soundtrack authored for this example and timed to its beats; no
|
||||
audio from the source video is used. Keep the `<audio>` element inside the
|
||||
audio from the source video is used. The soundtrack is part of the look the
|
||||
user is remaking: keep it by default, and follow the brief when it asks for no
|
||||
music or different music. The music lands on this film's beats, so keep the
|
||||
film close to this length; when the brief changes the length, keep
|
||||
`data-duration` within the film, and the export fades the track out where it
|
||||
stops. Keep the `<audio>` element inside the
|
||||
composition root, with numeric `data-start`, `data-duration` and `data-volume`;
|
||||
the desktop exporter mixes that local file into the MP4. Preview sound
|
||||
starts with the film; when the browser blocks autoplay, the first click or key
|
||||
|
||||
@@ -68,7 +68,12 @@ frame expensive and slow the MP4 export.
|
||||
## Sound
|
||||
Carry `assets/soundtrack.mp3` with the reference files. It is an original
|
||||
procedural soundtrack authored for this example and timed to its beats; no
|
||||
audio from the source video is used. Keep the `<audio>` element inside the
|
||||
audio from the source video is used. The soundtrack is part of the look the
|
||||
user is remaking: keep it by default, and follow the brief when it asks for no
|
||||
music or different music. The music lands on this film's beats, so keep the
|
||||
film close to this length; when the brief changes the length, keep
|
||||
`data-duration` within the film, and the export fades the track out where it
|
||||
stops. Keep the `<audio>` element inside the
|
||||
composition root, with numeric `data-start`, `data-duration` and `data-volume`;
|
||||
the desktop exporter mixes that local file into the MP4. Preview sound
|
||||
starts with the film; when the browser blocks autoplay, the first click or key
|
||||
|
||||
@@ -59,7 +59,12 @@ frame expensive and slow the MP4 export.
|
||||
## Sound
|
||||
Carry `assets/soundtrack.mp3` with the reference files. It is an original
|
||||
procedural soundtrack authored for this example and timed to its beats; no
|
||||
audio from the source video is used. Keep the `<audio>` element inside the
|
||||
audio from the source video is used. The soundtrack is part of the look the
|
||||
user is remaking: keep it by default, and follow the brief when it asks for no
|
||||
music or different music. The music lands on this film's beats, so keep the
|
||||
film close to this length; when the brief changes the length, keep
|
||||
`data-duration` within the film, and the export fades the track out where it
|
||||
stops. Keep the `<audio>` element inside the
|
||||
composition root, with numeric `data-start`, `data-duration` and `data-volume`;
|
||||
the desktop exporter mixes that local file into the MP4. Preview sound
|
||||
starts with the film; when the browser blocks autoplay, the first click or key
|
||||
|
||||
@@ -66,7 +66,12 @@ frame expensive and slow the MP4 export.
|
||||
## Sound
|
||||
Carry `assets/soundtrack.mp3` with the reference files. It is an original
|
||||
procedural soundtrack authored for this example and timed to its beats; no
|
||||
audio from the source video is used. Keep the `<audio>` element inside the
|
||||
audio from the source video is used. The soundtrack is part of the look the
|
||||
user is remaking: keep it by default, and follow the brief when it asks for no
|
||||
music or different music. The music lands on this film's beats, so keep the
|
||||
film close to this length; when the brief changes the length, keep
|
||||
`data-duration` within the film, and the export fades the track out where it
|
||||
stops. Keep the `<audio>` element inside the
|
||||
composition root, with numeric `data-start`, `data-duration` and `data-volume`;
|
||||
the desktop exporter mixes that local file into the MP4. Preview sound
|
||||
starts with the film; when the browser blocks autoplay, the first click or key
|
||||
|
||||
@@ -60,7 +60,12 @@ frame expensive and slow the MP4 export.
|
||||
## Sound
|
||||
Carry `assets/soundtrack.mp3` with the reference files. It is an original
|
||||
procedural soundtrack authored for this example and timed to its beats; no
|
||||
audio from the source video is used. Keep the `<audio>` element inside the
|
||||
audio from the source video is used. The soundtrack is part of the look the
|
||||
user is remaking: keep it by default, and follow the brief when it asks for no
|
||||
music or different music. The music lands on this film's beats, so keep the
|
||||
film close to this length; when the brief changes the length, keep
|
||||
`data-duration` within the film, and the export fades the track out where it
|
||||
stops. Keep the `<audio>` element inside the
|
||||
composition root, with numeric `data-start`, `data-duration` and `data-volume`;
|
||||
the desktop exporter mixes that local file into the MP4. Preview sound
|
||||
starts with the film; when the browser blocks autoplay, the first click or key
|
||||
|
||||
Reference in New Issue
Block a user