hlassiege/darylpublic Fork
⑂ 8566bea
Commits
⬇ Clone ▾
git clone https://git.rickub.com/hlassiege/daryl.git
git clone ssh://git@rickub.com/hlassiege/daryl.git

Host key fingerprint (ed25519): SHA256:iycHnxEyq0Q7uyVpB7JlznP0G7JrTPXLYRcAU5CSLhc — verify it before your first connect.

Download ZIP

fix(derush): step frame by frame at the frame rate of the rush being played

Each arrow step now lands on a frame of the rush under the playhead
instead of moving by 1/fps of the first rush.
hlassiege committed 2026-10-12T10:43:46+02:00 Browse files
8566bea parent: 931d2cb
modified web/src/project/steps/DerushStep.vue +11 -4
@@ -17,6 +17,7 @@ import type { AutoCut, DerushClip, DerushSeg, TimelineClip } from "../types";
1717 import AutoDerush from "./derush/AutoDerush.vue";
1818 import { clipAt, clipLen, move, offsets, remove, removeRange, removeRushRanges, splitAt, timeOf, trim, type Selection } from "./derush/clips";
1919 import MediaPool from "./derush/MediaPool.vue";
20+import { frameStep } from "./montage/ops";
2021 import PlayerFrame from "./derush/PlayerFrame.vue";
2122 import Timeline, { type Peaks } from "./derush/Timeline.vue";
2223 import Transcript from "./derush/Transcript.vue";
@@ -213,8 +214,14 @@ function seekRush(rush: string, t: number) {
213214 }
214215
215216 // --- clavier, comme dans un logiciel de montage
216-const fps = computed(() => project.value.rushes[0]?.fps || 25);
217-const nudge = (d: number) => player.seek(Math.max(0, player.t.value + d));
217+/** Les images du rush sous la tête de lecture. */
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)));
218225 /** Saute au début du bout précédent / suivant. */
219226 function jump(dir: 1 | -1) {
220227 const here = player.t.value, bounds = [...offsets(clips.value), kept.value];
@@ -237,8 +244,8 @@ function onKey(e: KeyboardEvent) {
237244 else if (ctrl && k === "b") onSplit(player.t.value);
238245 else if (ctrl || e.altKey) done = false;
239246 else if (e.key === "Delete" || e.key === "Backspace") cutSelection();
240- else if (e.key === "ArrowLeft") nudge(e.shiftKey ? -1 : -1 / fps.value);
241- else if (e.key === "ArrowRight") nudge(e.shiftKey ? 1 : 1 / fps.value);
247+ else if (e.key === "ArrowLeft") e.shiftKey ? nudge(-1) : frame(-1);
248+ else if (e.key === "ArrowRight") e.shiftKey ? nudge(1) : frame(1);
242249 else if (e.key === "ArrowUp") jump(-1);
243250 else if (e.key === "ArrowDown") jump(1);
244251 else if (e.key === "Escape") selection.value = null;
@@ -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 montage216 // --- 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;