| @@ -17,6 +17,7 @@ import type { AutoCut, DerushClip, DerushSeg, TimelineClip } from "../types"; |
| 17 | import AutoDerush from "./derush/AutoDerush.vue"; | 17 | import AutoDerush from "./derush/AutoDerush.vue"; |
| 18 | import { clipAt, clipLen, move, offsets, remove, removeRange, removeRushRanges, splitAt, timeOf, trim, type Selection } from "./derush/clips"; | 18 | import { clipAt, clipLen, move, offsets, remove, removeRange, removeRushRanges, splitAt, timeOf, trim, type Selection } from "./derush/clips"; |
| 19 | import MediaPool from "./derush/MediaPool.vue"; | 19 | import MediaPool from "./derush/MediaPool.vue"; |
| | 20 | +import { frameStep } from "./montage/ops"; |
| 20 | import PlayerFrame from "./derush/PlayerFrame.vue"; | 21 | import PlayerFrame from "./derush/PlayerFrame.vue"; |
| 21 | import Timeline, { type Peaks } from "./derush/Timeline.vue"; | 22 | import Timeline, { type Peaks } from "./derush/Timeline.vue"; |
| 22 | import Transcript from "./derush/Transcript.vue"; | 23 | import Transcript from "./derush/Transcript.vue"; |
| @@ -213,8 +214,14 @@ function seekRush(rush: string, t: number) { |
| 213 | } | 214 | } |
| 214 | | 215 | |
| 215 | // --- clavier, comme dans un logiciel de montage | 216 | // --- clavier, comme dans un logiciel de montage |
| 216 | -const fps = computed(() => project.value.rushes[0]?.fps || 25); | 217 | +/** Les images du rush sous la tête de lecture. */ |
| 217 | -const nudge = (d: number) => player.seek(Math.max(0, player.t.value + d)); | 218 | +const fps = computed(() => { |
| | 219 | + const hit = clipAt(flat.value, player.t.value); |
| | 220 | + return project.value.rushes.find((r) => r.id === hit?.c.rush)?.fps || project.value.rushes[0]?.fps || 25; |
| | 221 | +}); |
| | 222 | +const nudge = (d: number) => player.seek(Math.max(0, Math.min(player.t.value + d, kept.value))); |
| | 223 | +/** Une image plus loin (ou plus tôt), calée sur une image du rush. */ |
| | 224 | +const frame = (n: 1 | -1) => player.seek(Math.max(0, Math.min(frameStep(player.t.value, n, fps.value), kept.value))); |
| 218 | /** Saute au début du bout précédent / suivant. */ | 225 | /** Saute au début du bout précédent / suivant. */ |
| 219 | function jump(dir: 1 | -1) { | 226 | function jump(dir: 1 | -1) { |
| 220 | const here = player.t.value, bounds = [...offsets(clips.value), kept.value]; | 227 | const here = player.t.value, bounds = [...offsets(clips.value), kept.value]; |
| @@ -237,8 +244,8 @@ function onKey(e: KeyboardEvent) { |
| 237 | else if (ctrl && k === "b") onSplit(player.t.value); | 244 | else if (ctrl && k === "b") onSplit(player.t.value); |
| 238 | else if (ctrl || e.altKey) done = false; | 245 | else if (ctrl || e.altKey) done = false; |
| 239 | else if (e.key === "Delete" || e.key === "Backspace") cutSelection(); | 246 | else if (e.key === "Delete" || e.key === "Backspace") cutSelection(); |
| 240 | - else if (e.key === "ArrowLeft") nudge(e.shiftKey ? -1 : -1 / fps.value); | 247 | + else if (e.key === "ArrowLeft") e.shiftKey ? nudge(-1) : frame(-1); |
| 241 | - else if (e.key === "ArrowRight") nudge(e.shiftKey ? 1 : 1 / fps.value); | 248 | + else if (e.key === "ArrowRight") e.shiftKey ? nudge(1) : frame(1); |
| 242 | else if (e.key === "ArrowUp") jump(-1); | 249 | else if (e.key === "ArrowUp") jump(-1); |
| 243 | else if (e.key === "ArrowDown") jump(1); | 250 | else if (e.key === "ArrowDown") jump(1); |
| 244 | else if (e.key === "Escape") selection.value = null; | 251 | else if (e.key === "Escape") selection.value = null; |