diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 65e8ec84..9b66e8e0 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -380,7 +380,7 @@ jobs: path: | release/*.exe release/*.blockmap - release/*.yml + release/latest*.yml if-no-files-found: error build-linux-x64: diff --git a/src/components/video-editor/hooks/useEditorRegions.ts b/src/components/video-editor/hooks/useEditorRegions.ts index 1aaa6863..fde29f44 100644 --- a/src/components/video-editor/hooks/useEditorRegions.ts +++ b/src/components/video-editor/hooks/useEditorRegions.ts @@ -87,7 +87,10 @@ export function useEditorRegions({ // Trim regions are derived from clips (gaps = sections to remove in export) const trimRegions = useMemo(() => { - if (totalMs <= 0 || clipRegions.length === 0) return []; + if (totalMs <= 0) return []; + if (clipRegions.length === 0) { + return [{ id: "trim-all", startMs: 0, endMs: totalMs }]; + } return clipsToTrims(clipRegions, totalMs); }, [clipRegions, totalMs]); @@ -153,7 +156,7 @@ export function useEditorRegions({ id: `clip-speed-${c.id}`, startMs: getClipSourceStartMs(c), endMs: getClipSourceEndMs(c), - speed: c.speed as SpeedRegion["speed"], + speed: c.speed, })), [clipRegions], ); @@ -356,32 +359,39 @@ export function useEditorRegions({ const endDelta = newEnd - oldClip.endMs; const isMove = Math.abs(startDelta - endDelta) < 1 && Math.abs(startDelta) > 0; - if (!isMove && Math.abs(startDelta) > 0) { - // Left-edge resize: shift sourceStartMs by startDelta * speed - const speed = - Number.isFinite(oldClip.speed) && oldClip.speed > 0 - ? oldClip.speed - : 1; - updated.sourceStartMs = Math.max( - 0, - Math.round(getClipSourceStartMs(oldClip) + startDelta * speed), - ); - } - // Move: sourceStartMs stays the same (explicit or fallback unchanged since we set it) + const sourceStart = getClipSourceStartMs(oldClip); + const speed = + Number.isFinite(oldClip.speed) && oldClip.speed > 0 + ? oldClip.speed + : 1; + if (isMove) { + // Move: freeze the resolved source start + updated.sourceStartMs = sourceStart; + } else if (Math.abs(startDelta) > 0) { + // Left-edge resize: shift sourceStartMs by startDelta * speed + updated.sourceStartMs = Math.max( + 0, + Math.round(sourceStart + startDelta * speed), + ); + } } return updated; }), ); - // Remove zooms that no longer overlap any clip after the change + // Remove regions that no longer overlap any clip after the change const updatedClips = clipRegions.map((c) => c.id === id ? { ...c, startMs: newStart, endMs: newEnd } : c, ); - setZoomRegions((prev) => - prev.filter((z) => - updatedClips.some((c) => z.startMs < c.endMs && z.endMs > c.startMs), - ), - ); + const keepOverlapping = ( + regions: T[], + ): T[] => + regions.filter((r) => + updatedClips.some((c) => r.startMs < c.endMs && r.endMs > c.startMs), + ); + setZoomRegions((prev) => keepOverlapping(prev)); + setAnnotationRegions((prev) => keepOverlapping(prev)); + setAudioRegions((prev) => keepOverlapping(prev)); }, [clipRegions], ); diff --git a/src/components/video-editor/timeline/TimelineEditor.tsx b/src/components/video-editor/timeline/TimelineEditor.tsx index c49157c0..34089854 100644 --- a/src/components/video-editor/timeline/TimelineEditor.tsx +++ b/src/components/video-editor/timeline/TimelineEditor.tsx @@ -54,6 +54,7 @@ import type { ZoomMode, ZoomRegion, } from "../types"; +import { getClipSourceStartMs, getClipSourceEndMs } from "../types"; import AudioWaveform from "./AudioWaveform"; import Item from "./Item"; import KeyframeMarkers from "./KeyframeMarkers"; @@ -171,6 +172,8 @@ interface TimelineRenderItem { zoomDepth?: number; zoomMode?: ZoomMode; speedValue?: number; + sourceStartMs?: number; + sourceEndMs?: number; variant: "zoom" | "trim" | "clip" | "annotation" | "speed" | "audio"; } @@ -705,11 +708,13 @@ function Timeline({ onSelect={() => onSelectClip?.(item.id)} variant="clip" backgroundLayer={ - audioPeaks ? ( + audioPeaks && + item.sourceStartMs !== undefined && + item.sourceEndMs !== undefined ? ( ) : undefined } @@ -1518,6 +1523,8 @@ const TimelineEditor = forwardRef( rowId: CLIP_ROW_ID, span: { start: region.startMs, end: region.endMs }, label: `Clip ${index + 1}`, + sourceStartMs: getClipSourceStartMs(region), + sourceEndMs: getClipSourceEndMs(region), variant: "clip", })); diff --git a/src/components/video-editor/types.ts b/src/components/video-editor/types.ts index eba49bda..9ea179ad 100644 --- a/src/components/video-editor/types.ts +++ b/src/components/video-editor/types.ts @@ -387,7 +387,7 @@ export interface SpeedRegion { id: string; startMs: number; endMs: number; - speed: PlaybackSpeed; + speed: number; } export const SPEED_OPTIONS: Array<{ speed: PlaybackSpeed; label: string }> = [