Skip to content

Commit 1b87733

Browse files
committed
waveform optimization
1 parent a4f3992 commit 1b87733

File tree

7 files changed

+73
-6
lines changed

7 files changed

+73
-6
lines changed

docs/docs/lib/media.md

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,13 @@ You can trim the source in frames:
2020
<Video video="assets/demo.mp4" trim={{ from: 30, duration: 120 }} />
2121
```
2222

23+
Waveform display in the timeline is automatic for clips shorter than 60 seconds.
24+
For longer clips, set `showWaveform` to enable it explicitly:
25+
26+
```tsx
27+
<Video video="assets/demo.mp4" showWaveform />
28+
```
29+
2330
### `video_length`
2431

2532
Returns the length of a video in frames.
@@ -37,3 +44,10 @@ import { Sound } from "../src/lib/sound/sound"
3744

3845
<Sound sound="assets/music.mp3" trim={{ trimStart: 30 }} />
3946
```
47+
48+
Waveform display in the timeline is automatic for clips shorter than 60 seconds.
49+
For longer clips, set `showWaveform` to enable it explicitly:
50+
51+
```tsx
52+
<Sound sound="assets/music.mp3" showWaveform />
53+
```

docs/i18n/ja/docusaurus-plugin-content-docs/current/lib/media.md

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,13 @@ import { Video } from "../src/lib/video/video"
2020
<Video video="assets/demo.mp4" trim={{ from: 30, duration: 120 }} />
2121
```
2222

23+
タイムラインの波形表示は60秒未満のクリップは自動で有効です。
24+
長いクリップは `showWaveform` を指定した場合のみ表示されます。
25+
26+
```tsx
27+
<Video video="assets/demo.mp4" showWaveform />
28+
```
29+
2330
### `video_length`
2431
動画の長さを取得します。
2532
```tsx
@@ -35,3 +42,10 @@ import { Sound } from "../src/lib/sound/sound"
3542

3643
<Sound sound="assets/music.mp3" trim={{ trimStart: 30 }} />
3744
```
45+
46+
タイムラインの波形表示は60秒未満のクリップは自動で有効です。
47+
長いクリップは `showWaveform` を指定した場合のみ表示されます。
48+
49+
```tsx
50+
<Sound sound="assets/music.mp3" showWaveform />
51+
```

src/lib/audio-plan.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,8 @@ export type AudioSegment = {
3939
fadeInFrames?: number
4040
fadeOutFrames?: number
4141
volume?: number
42+
clipId?: string
43+
showWaveform?: boolean
4244
}
4345

4446
type Listener = () => void
@@ -79,7 +81,9 @@ export const registerAudioSegmentGlobal = (segment: AudioSegment) => {
7981
existing.durationFrames === segment.durationFrames &&
8082
(existing.fadeInFrames ?? 0) === (segment.fadeInFrames ?? 0) &&
8183
(existing.fadeOutFrames ?? 0) === (segment.fadeOutFrames ?? 0) &&
82-
(existing.volume ?? 1) === (segment.volume ?? 1)
84+
(existing.volume ?? 1) === (segment.volume ?? 1) &&
85+
(existing.clipId ?? null) === (segment.clipId ?? null) &&
86+
(existing.showWaveform ?? null) === (segment.showWaveform ?? null)
8387
) {
8488
return
8589
}

src/lib/clip.tsx

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -147,6 +147,21 @@ export const useClipRange = () => {
147147
}, [start, end])
148148
}
149149

150+
/**
151+
* Returns the id of the current clip.
152+
*
153+
* 現在のクリップ ID を返します。
154+
*
155+
* @example
156+
* ```ts
157+
* const id = useClipId()
158+
* ```
159+
*/
160+
export const useClipId = () => {
161+
const ctx = useContext(ClipContext)
162+
return ctx?.id ?? null
163+
}
164+
150165
/**
151166
* Returns the nesting depth of the current clip.
152167
*

src/lib/sound/sound.tsx

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { useCallback, useEffect, useId, useMemo, useRef } from "react"
22
import { PROJECT_SETTINGS } from "../../../project/project"
33
import { useGlobalCurrentFrame } from "../frame"
4-
import { useClipActive, useClipRange, useProvideClipDuration } from "../clip"
4+
import { useClipActive, useClipId, useClipRange, useProvideClipDuration } from "../clip"
55
import { registerAudioSegmentGlobal, unregisterAudioSegmentGlobal } from "../audio-plan"
66
import { fetchAudioBuffer } from "../audio"
77
import { useIsPlaying, useIsRender } from "../studio-state"
@@ -38,6 +38,7 @@ export type SoundProps = {
3838
fadeInFrames?: number
3939
fadeOutFrames?: number
4040
volume?: number
41+
showWaveform?: boolean
4142
}
4243

4344
/**
@@ -123,8 +124,10 @@ export const Sound = ({
123124
fadeInFrames = 0,
124125
fadeOutFrames = 0,
125126
volume = 1,
127+
showWaveform,
126128
}: SoundProps) => {
127129
const id = useId()
130+
const clipId = useClipId()
128131
const clipRange = useClipRange()
129132
const isActive = useClipActive()
130133
const isPlaying = useIsPlaying()
@@ -297,25 +300,29 @@ export const Sound = ({
297300
registerAudioSegmentGlobal({
298301
id,
299302
source: { kind: "sound", path: resolvedSound.path },
303+
clipId: clipId ?? undefined,
300304
projectStartFrame,
301305
sourceStartFrame: trimStartFrames,
302306
durationFrames: clamped,
303307
fadeInFrames: Math.min(fadeIn, clamped),
304308
fadeOutFrames: Math.min(fadeOut, clamped),
305309
volume: normalizedVolume,
310+
showWaveform,
306311
})
307312

308313
return () => {
309314
unregisterAudioSegmentGlobal(id)
310315
}
311316
}, [
312317
clipRange,
318+
clipId,
313319
durationFrames,
314320
fadeInFrames,
315321
fadeOutFrames,
316322
id,
317323
normalizedVolume,
318324
resolvedSound.path,
325+
showWaveform,
319326
trimStartFrames,
320327
])
321328

src/lib/video/video.tsx

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import { useEffect, useId, useMemo, useRef } from "react";
33
import { useCurrentFrame } from "../frame";
44
import { PROJECT_SETTINGS } from "../../../project/project";
55
import { useIsPlaying, useIsRender } from "../studio-state";
6-
import { useClipActive, useClipRange, useProvideClipDuration } from "../clip";
6+
import { useClipActive, useClipId, useClipRange, useProvideClipDuration } from "../clip";
77
import { registerAudioSegmentGlobal, unregisterAudioSegmentGlobal } from "../audio-plan";
88
import { VideoCanvasRender } from "./video-render";
99
import type { Trim } from "../trim";
@@ -37,6 +37,7 @@ export type VideoProps = {
3737
video: Video | string
3838
style?: CSSProperties
3939
trim?: Trim
40+
showWaveform?: boolean
4041
}
4142

4243
/**
@@ -175,9 +176,10 @@ export type VideoResolvedTrimProps = {
175176
* <Video video="assets/demo.mp4" trim={{ from: 30, duration: 120 }} />
176177
* ```
177178
*/
178-
export const Video = ({ video, style, trim }: VideoProps) => {
179+
export const Video = ({ video, style, trim, showWaveform }: VideoProps) => {
179180
const isRender = useIsRender()
180181
const id = useId()
182+
const clipId = useClipId()
181183
const clipRange = useClipRange()
182184
const resolvedVideo = useMemo(() => normalizeVideo(video), [video])
183185
const resolvedStyle = useMemo(() => {
@@ -215,15 +217,17 @@ export const Video = ({ video, style, trim }: VideoProps) => {
215217
registerAudioSegmentGlobal({
216218
id,
217219
source: { kind: "video", path: resolvedVideo.path },
220+
clipId: clipId ?? undefined,
218221
projectStartFrame,
219222
sourceStartFrame: trimStartFrames,
220223
durationFrames,
224+
showWaveform,
221225
})
222226

223227
return () => {
224228
unregisterAudioSegmentGlobal(id)
225229
}
226-
}, [clipRange, id, rawDurationFrames, resolvedVideo.path, trimEndFrames, trimStartFrames])
230+
}, [clipId, clipRange, id, rawDurationFrames, resolvedVideo.path, showWaveform, trimEndFrames, trimStartFrames])
227231

228232
if (isRender) {
229233
return (

src/ui/timeline.tsx

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,7 @@ export const TimelineUI = () => {
7171
const projectSettings = PROJECT_SETTINGS
7272
const { fps } = projectSettings
7373
const [zoom, setZoom] = useState(1)
74+
const waveformAutoLimitFrames = Math.max(1, Math.round(fps * 60))
7475

7576
const placedClips = useMemo(() => stackClipsIntoTracks(clips), [clips])
7677
const trackCount = Math.max(1, placedClips.reduce((max, clip) => Math.max(max, clip.trackIndex + 1), 0))
@@ -79,6 +80,14 @@ export const TimelineUI = () => {
7980
for (const clip of placedClips) {
8081
const segments: ClipWaveformSegment[] = []
8182
for (const segment of audioSegments) {
83+
if (segment.clipId && segment.clipId !== clip.id) {
84+
continue
85+
}
86+
const autoAllowed = segment.durationFrames < waveformAutoLimitFrames
87+
const shouldShowWaveform = segment.showWaveform ?? autoAllowed
88+
if (!shouldShowWaveform) {
89+
continue
90+
}
8291
const segStart = segment.projectStartFrame
8392
const segEnd = segStart + segment.durationFrames - 1
8493
if (segEnd < clip.start || segStart > clip.end) {
@@ -103,7 +112,7 @@ export const TimelineUI = () => {
103112
}
104113
}
105114
return map
106-
}, [audioSegments, placedClips])
115+
}, [audioSegments, placedClips, waveformAutoLimitFrames])
107116
const clipMap = useMemo(() => {
108117
const map = new Map<string, PositionedClip>()
109118
placedClips.forEach((c) => map.set(c.id, c))

0 commit comments

Comments
 (0)