Skip to content

Commit c5c58ae

Browse files
committed
WIP to add speed editing - video is sped up but the timeline doesnt change
1 parent cfde66f commit c5c58ae

File tree

4 files changed

+129
-49
lines changed

4 files changed

+129
-49
lines changed

apps/desktop/src/routes/editor/ConfigSidebar.tsx

Lines changed: 101 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -263,7 +263,7 @@ export function ConfigSidebar() {
263263
class={cx(
264264
"flex justify-center relative border-transparent border z-10 items-center rounded-md size-9 transition will-change-transform",
265265
state.selectedTab !== item.id &&
266-
"group-hover:border-gray-300 group-disabled:border-none"
266+
"group-hover:border-gray-300 group-disabled:border-none"
267267
)}
268268
>
269269
<Dynamic component={item.icon} />
@@ -940,17 +940,13 @@ function BackgroundConfig(props: { scrollRef: HTMLDivElement }) {
940940
ref={setBackgroundRef}
941941
class="flex overflow-x-auto overscroll-contain relative z-40 flex-row gap-2 items-center mb-3 text-xs hide-scroll"
942942
style={{
943-
"-webkit-mask-image": `linear-gradient(to right, transparent, black ${
944-
scrollX() > 0 ? "24px" : "0"
945-
}, black calc(100% - ${
946-
reachedEndOfScroll() ? "0px" : "24px"
947-
}), transparent)`,
948-
949-
"mask-image": `linear-gradient(to right, transparent, black ${
950-
scrollX() > 0 ? "24px" : "0"
951-
}, black calc(100% - ${
952-
reachedEndOfScroll() ? "0px" : "24px"
953-
}), transparent);`,
943+
"-webkit-mask-image": `linear-gradient(to right, transparent, black ${scrollX() > 0 ? "24px" : "0"
944+
}, black calc(100% - ${reachedEndOfScroll() ? "0px" : "24px"
945+
}), transparent)`,
946+
947+
"mask-image": `linear-gradient(to right, transparent, black ${scrollX() > 0 ? "24px" : "0"
948+
}, black calc(100% - ${reachedEndOfScroll() ? "0px" : "24px"
949+
}), transparent);`,
954950
}}
955951
>
956952
<For each={Object.entries(BACKGROUND_THEMES)}>
@@ -977,10 +973,10 @@ function BackgroundConfig(props: { scrollRef: HTMLDivElement }) {
977973
value={
978974
project.background.source.type === "wallpaper"
979975
? wallpapers()?.find((w) =>
980-
(
981-
project.background.source as { path?: string }
982-
).path?.includes(w.id)
983-
)?.url ?? undefined
976+
(
977+
project.background.source as { path?: string }
978+
).path?.includes(w.id)
979+
)?.url ?? undefined
984980
: undefined
985981
}
986982
onChange={(photoUrl) => {
@@ -1271,7 +1267,7 @@ function BackgroundConfig(props: { scrollRef: HTMLDivElement }) {
12711267
const rawNewAngle =
12721268
Math.round(
12731269
start +
1274-
(downEvent.clientY - moveEvent.clientY)
1270+
(downEvent.clientY - moveEvent.clientY)
12751271
) % max;
12761272
const newAngle = moveEvent.shiftKey
12771273
? rawNewAngle
@@ -1281,7 +1277,7 @@ function BackgroundConfig(props: { scrollRef: HTMLDivElement }) {
12811277
!moveEvent.shiftKey &&
12821278
hapticsEnabled() &&
12831279
project.background.source.type ===
1284-
"gradient" &&
1280+
"gradient" &&
12851281
project.background.source.angle !== newAngle
12861282
) {
12871283
commands.performHapticFeedback(
@@ -1494,8 +1490,8 @@ function CameraConfig(props: { scrollRef: HTMLDivElement }) {
14941490
item.x === "left"
14951491
? "left-2"
14961492
: item.x === "right"
1497-
? "right-2"
1498-
: "left-1/2 transform -translate-x-1/2",
1493+
? "right-2"
1494+
: "left-1/2 transform -translate-x-1/2",
14991495
item.y === "top" ? "top-2" : "bottom-2"
15001496
)}
15011497
onClick={() => setProject("camera", "position", item)}
@@ -1752,8 +1748,7 @@ function ZoomSegmentConfig(props: {
17521748
createEffect(() => {
17531749
video.src = convertFileSrc(
17541750
// TODO: this shouldn't be so hardcoded
1755-
`${
1756-
editorInstance.path
1751+
`${editorInstance.path
17571752
}/content/segments/segment-${segmentIndex()}/display.mp4`
17581753
);
17591754
});
@@ -1850,15 +1845,15 @@ function ZoomSegmentConfig(props: {
18501845
x: Math.max(
18511846
Math.min(
18521847
(moveEvent.clientX - bounds.left) /
1853-
bounds.width,
1848+
bounds.width,
18541849
1
18551850
),
18561851
0
18571852
),
18581853
y: Math.max(
18591854
Math.min(
18601855
(moveEvent.clientY - bounds.top) /
1861-
bounds.height,
1856+
bounds.height,
18621857
1
18631858
),
18641859
0
@@ -1873,12 +1868,10 @@ function ZoomSegmentConfig(props: {
18731868
<div
18741869
class="absolute z-10 w-6 h-6 rounded-full border border-gray-400 -translate-x-1/2 -translate-y-1/2 bg-gray-1"
18751870
style={{
1876-
left: `calc(${mode().x * 100}% + ${
1877-
2 + mode().x * -6
1878-
}px)`,
1879-
top: `calc(${mode().y * 100}% + ${
1880-
2 + mode().y * -6
1881-
}px)`,
1871+
left: `calc(${mode().x * 100}% + ${2 + mode().x * -6
1872+
}px)`,
1873+
top: `calc(${mode().y * 100}% + ${2 + mode().y * -6
1874+
}px)`,
18821875
}}
18831876
/>
18841877
<div class="overflow-hidden rounded-lg border border-gray-3 bg-gray-2">
@@ -1945,6 +1938,84 @@ function ClipSegmentConfig(props: {
19451938
Delete
19461939
</EditorButton>
19471940
</div>
1941+
<Field name="Speed" icon={<IconLucideFastForward class="size-4" />}>
1942+
<div class="flex flex-col gap-2">
1943+
<Slider
1944+
value={[props.segment.timescale]}
1945+
onChange={(v) =>
1946+
setProject(
1947+
"timeline",
1948+
"segments",
1949+
props.segmentIndex,
1950+
"timescale",
1951+
v[0]
1952+
)
1953+
}
1954+
minValue={0.25}
1955+
maxValue={4}
1956+
step={0.25}
1957+
formatTooltip={(v) => `${v}x`}
1958+
/>
1959+
<div class="flex gap-2 text-xs">
1960+
<button
1961+
onClick={() =>
1962+
setProject(
1963+
"timeline",
1964+
"segments",
1965+
props.segmentIndex,
1966+
"timescale",
1967+
0.5
1968+
)
1969+
}
1970+
class="px-2 py-1 rounded-md bg-gray-3 hover:bg-gray-4 transition-colors"
1971+
>
1972+
0.5x
1973+
</button>
1974+
<button
1975+
onClick={() =>
1976+
setProject(
1977+
"timeline",
1978+
"segments",
1979+
props.segmentIndex,
1980+
"timescale",
1981+
1
1982+
)
1983+
}
1984+
class="px-2 py-1 rounded-md bg-gray-3 hover:bg-gray-4 transition-colors"
1985+
>
1986+
1x
1987+
</button>
1988+
<button
1989+
onClick={() =>
1990+
setProject(
1991+
"timeline",
1992+
"segments",
1993+
props.segmentIndex,
1994+
"timescale",
1995+
1.5
1996+
)
1997+
}
1998+
class="px-2 py-1 rounded-md bg-gray-3 hover:bg-gray-4 transition-colors"
1999+
>
2000+
1.5x
2001+
</button>
2002+
<button
2003+
onClick={() =>
2004+
setProject(
2005+
"timeline",
2006+
"segments",
2007+
props.segmentIndex,
2008+
"timescale",
2009+
2
2010+
)
2011+
}
2012+
class="px-2 py-1 rounded-md bg-gray-3 hover:bg-gray-4 transition-colors"
2013+
>
2014+
2x
2015+
</button>
2016+
</div>
2017+
</div>
2018+
</Field>
19482019
<ComingSoonTooltip>
19492020
<Field name="Hide Cursor" disabled value={<Toggle disabled />} />
19502021
</ComingSoonTooltip>

apps/desktop/src/routes/editor/Timeline/ClipTrack.tsx

Lines changed: 23 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -79,7 +79,7 @@ export function ClipTrack(
7979

8080
const segmentRecording = (s = i()) =>
8181
editorInstance.recordings.segments[
82-
segments()[s].recordingSegment ?? 0
82+
segments()[s].recordingSegment ?? 0
8383
];
8484

8585
const marker = useSectionMarker(() => ({
@@ -112,9 +112,8 @@ export function ClipTrack(
112112
<div
113113
class="absolute w-0 z-10 h-full *:absolute"
114114
style={{
115-
transform: `translateX(${
116-
i() === 0 ? segmentX() : segmentX()
117-
}px)`,
115+
transform: `translateX(${i() === 0 ? segmentX() : segmentX()
116+
}px)`,
118117
}}
119118
>
120119
<div class="w-[2px] bottom-0 -top-2 rounded-full from-red-300 to-transparent bg-gradient-to-b -translate-x-1/2" />
@@ -277,7 +276,7 @@ export function ClipTrack(
277276
segmentI === i()
278277
? acc
279278
: acc +
280-
(segment.end - segment.start) / segment.timescale,
279+
(segment.end - segment.start) / segment.timescale,
281280
0
282281
);
283282

@@ -290,7 +289,7 @@ export function ClipTrack(
290289
const prevSegmentIsSameClip =
291290
prevSegment?.recordingSegment !== undefined
292291
? prevSegment.recordingSegment ===
293-
segment.recordingSegment
292+
segment.recordingSegment
294293
: false;
295294

296295
function update(event: MouseEvent) {
@@ -344,6 +343,12 @@ export function ClipTrack(
344343
<IconLucideClock class="size-3.5" />{" "}
345344
{(segment.end - segment.start).toFixed(1)}s
346345
</div>
346+
<Show when={segment.timescale !== 1}>
347+
<div class="flex gap-1 items-center text-xs font-bold text-blue-11">
348+
<IconLucideFastForward class="size-3" />
349+
{segment.timescale}x
350+
</div>
351+
</Show>
347352
</div>
348353
</Show>
349354
);
@@ -367,15 +372,15 @@ export function ClipTrack(
367372
segmentI === i()
368373
? acc
369374
: acc +
370-
(segment.end - segment.start) / segment.timescale,
375+
(segment.end - segment.start) / segment.timescale,
371376
0
372377
);
373378

374379
const nextSegment = segments()[i() + 1];
375380
const nextSegmentIsSameClip =
376381
nextSegment?.recordingSegment !== undefined
377382
? nextSegment.recordingSegment ===
378-
segment.recordingSegment
383+
segment.recordingSegment
379384
: false;
380385

381386
function update(event: MouseEvent) {
@@ -482,9 +487,8 @@ function Markings(props: { segment: TimelineSegment; prevDuration: number }) {
482487
{(marking) => (
483488
<div
484489
style={{
485-
transform: `translateX(${
486-
(marking - props.segment.start) / secsPerPixel()
487-
}px)`,
490+
transform: `translateX(${(marking - props.segment.start) / secsPerPixel()
491+
}px)`,
488492
}}
489493
class="absolute z-10 w-px h-12 bg-gradient-to-b from-transparent to-transparent via-white-transparent-40 dark:via-black-transparent-60"
490494
/>
@@ -528,10 +532,10 @@ function useSectionMarker(
528532
}
529533
): Accessor<
530534
| ({ type: "dual" } & (
531-
| { left: SectionMarker; right: null }
532-
| { left: null; right: SectionMarker }
533-
| { left: SectionMarker; right: SectionMarker }
534-
))
535+
| { left: SectionMarker; right: null }
536+
| { left: null; right: SectionMarker }
537+
| { left: SectionMarker; right: SectionMarker }
538+
))
535539
| { type: "single"; value: SectionMarker }
536540
| null
537541
> {
@@ -544,10 +548,10 @@ function useSectionMarker(
544548
return segments[0].start === 0
545549
? null
546550
: {
547-
type: "dual",
548-
right: { type: "time", time: segments[0].start },
549-
left: null,
550-
};
551+
type: "dual",
552+
right: { type: "time", time: segments[0].start },
553+
left: null,
554+
};
551555
}
552556

553557
if (i === segments.length - 1 && position === "right") {

packages/ui-solid/src/auto-imports.d.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,7 @@ declare global {
8282
const IconLucideDatabase: typeof import('~icons/lucide/database.jsx')['default']
8383
const IconLucideEdit: typeof import('~icons/lucide/edit.jsx')['default']
8484
const IconLucideEye: typeof import('~icons/lucide/eye.jsx')['default']
85+
const IconLucideFastForward: typeof import('~icons/lucide/fast-forward.jsx')['default']
8586
const IconLucideFolder: typeof import('~icons/lucide/folder.jsx')['default']
8687
const IconLucideGift: typeof import('~icons/lucide/gift.jsx')['default']
8788
const IconLucideHardDrive: typeof import('~icons/lucide/hard-drive.jsx')['default']

rust-toolchain.toml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
[toolchain]
2+
channel = "nightly"
3+
components = ["rustfmt", "clippy"]
4+
profile = "minimal"

0 commit comments

Comments
 (0)