hlassiege/darylpublic Fork
⑂ ad42f3d
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

feat(montage): retry a failed motion render or ask Daryl to fix it

The inspector only showed the render error. It now offers to queue the failed renders again, or to open a request to Daryl prefilled with the error so he fixes the composition.
hlassiege committed 2026-10-22T18:05:11+02:00 Browse files
ad42f3d parent: d49f56d
modified backend/studio/modules/projects/api.py +8 -0
@@ -1501,6 +1501,14 @@ def put_montage(pid: str, req: MontageRequest):
15011501 return {**_montage_view(pid), "base": base}
15021502
15031503
1504+@router.post("/{pid}/montage/renders")
1505+def retry_motion_renders(pid: str):
1506+ """Relance les rendus de motions ratés (et ceux qui manquent)."""
1507+ _check(pid)
1508+ montage.enqueue_motion_renders(pid)
1509+ return _montage_view(pid)
1510+
1511+
15041512 @router.post("/{pid}/montage/undo")
15051513 def undo_montage(pid: str):
15061514 _check(pid)
@@ -1501,6 +1501,14 @@ def put_montage(pid: str, req: MontageRequest):
1501 return {**_montage_view(pid), "base": base}1501 return {**_montage_view(pid), "base": base}
1502 1502
1503 1503
1504+@router.post("/{pid}/montage/renders")
1505+def retry_motion_renders(pid: str):
1506+ """Relance les rendus de motions ratés (et ceux qui manquent)."""
1507+ _check(pid)
1508+ montage.enqueue_motion_renders(pid)
1509+ return _montage_view(pid)
1510+
1511+
1504 @router.post("/{pid}/montage/undo")1512 @router.post("/{pid}/montage/undo")
1505 def undo_montage(pid: str):1513 def undo_montage(pid: str):
1506 _check(pid)1514 _check(pid)
modified web/src/i18n/locales/en/editing.ts +3 -0
@@ -71,6 +71,9 @@ export default {
7171 renderReady: ".mov render ready",
7272 renderNone: "Composition not written yet",
7373 renderError: "Render failed: {error}",
74+ retryRender: "Retry render",
75+ fixRender: "Ask Daryl to fix it",
76+ askFixRender: "The render of motion {id} fails: {error}. Fix its composition so it renders, without changing the animation.",
7477 renderProgress: "Rendering · {pct}%",
7578 renderPending: "Render queued",
7679 volume: "Volume",
@@ -71,6 +71,9 @@ export default {
71 renderReady: ".mov render ready",71 renderReady: ".mov render ready",
72 renderNone: "Composition not written yet",72 renderNone: "Composition not written yet",
73 renderError: "Render failed: {error}",73 renderError: "Render failed: {error}",
74+ retryRender: "Retry render",
75+ fixRender: "Ask Daryl to fix it",
76+ askFixRender: "The render of motion {id} fails: {error}. Fix its composition so it renders, without changing the animation.",
74 renderProgress: "Rendering · {pct}%",77 renderProgress: "Rendering · {pct}%",
75 renderPending: "Render queued",78 renderPending: "Render queued",
76 volume: "Volume",79 volume: "Volume",
modified web/src/i18n/locales/fr/editing.ts +3 -0
@@ -71,6 +71,9 @@ export default {
7171 renderReady: "Rendu .mov prêt",
7272 renderNone: "Composition pas encore écrite",
7373 renderError: "Rendu raté : {error}",
74+ retryRender: "Relancer le rendu",
75+ fixRender: "Faire corriger par Daryl",
76+ askFixRender: "Le rendu du motion {id} échoue : {error}. Corrige sa composition pour qu'il passe, sans changer l'animation.",
7477 renderProgress: "Rendu · {pct} %",
7578 renderPending: "Rendu en attente",
7679 volume: "Volume",
@@ -71,6 +71,9 @@ export default {
71 renderReady: "Rendu .mov prêt",71 renderReady: "Rendu .mov prêt",
72 renderNone: "Composition pas encore écrite",72 renderNone: "Composition pas encore écrite",
73 renderError: "Rendu raté : {error}",73 renderError: "Rendu raté : {error}",
74+ retryRender: "Relancer le rendu",
75+ fixRender: "Faire corriger par Daryl",
76+ askFixRender: "Le rendu du motion {id} échoue : {error}. Corrige sa composition pour qu'il passe, sans changer l'animation.",
74 renderProgress: "Rendu · {pct} %",77 renderProgress: "Rendu · {pct} %",
75 renderPending: "Rendu en attente",78 renderPending: "Rendu en attente",
76 volume: "Volume",79 volume: "Volume",
modified web/src/project/steps/MontageStep.vue +1 -1
@@ -381,7 +381,7 @@ onUnmounted(() => window.removeEventListener("keydown", onKey));
381381 :image-popup="imagePopup" @image-popup="(tab: ImageTab | null) => (imagePopup = tab)" />
382382 <Inspector v-else v-model:selection="selection" class="insp" :tl="tl" :view="view" :available="M.available.value"
383383 :t="t" :total="M.total.value" @edit="edit" @comment="comment" @seek="seek" @beat="addBeat"
384- @insert="(m: AvailableMedia) => insertClip(m, t)" @media="M.loadAvailable()" />
384+ @insert="(m: AvailableMedia) => insertClip(m, t)" @media="M.loadAvailable()" @rerender="M.retryRenders()" />
385385 </div>
386386
387387 <div class="toolbar">
@@ -381,7 +381,7 @@ onUnmounted(() => window.removeEventListener("keydown", onKey));
381 :image-popup="imagePopup" @image-popup="(tab: ImageTab | null) => (imagePopup = tab)" />381 :image-popup="imagePopup" @image-popup="(tab: ImageTab | null) => (imagePopup = tab)" />
382 <Inspector v-else v-model:selection="selection" class="insp" :tl="tl" :view="view" :available="M.available.value"382 <Inspector v-else v-model:selection="selection" class="insp" :tl="tl" :view="view" :available="M.available.value"
383 :t="t" :total="M.total.value" @edit="edit" @comment="comment" @seek="seek" @beat="addBeat"383 :t="t" :total="M.total.value" @edit="edit" @comment="comment" @seek="seek" @beat="addBeat"
384- @insert="(m: AvailableMedia) => insertClip(m, t)" @media="M.loadAvailable()" />384+ @insert="(m: AvailableMedia) => insertClip(m, t)" @media="M.loadAvailable()" @rerender="M.retryRenders()" />
385 </div>385 </div>
386 386
387 <div class="toolbar">387 <div class="toolbar">
modified web/src/project/steps/montage/Inspector.vue +6 -1
@@ -22,7 +22,7 @@ import {
2222 const props = defineProps<{
2323 tl: Timeline; view: MontageView; available: AvailableMedia[]; t: number; total: number;
2424 }>();
25-const emit = defineEmits<{ edit: [(t: Timeline) => void]; comment: [string?]; seek: [number]; insert: [AvailableMedia]; media: []; beat: [] }>();
25+const emit = defineEmits<{ edit: [(t: Timeline) => void]; comment: [string?]; seek: [number]; insert: [AvailableMedia]; media: []; beat: []; rerender: [] }>();
2626 const sel = defineModel<Selection | null>("selection", { required: true });
2727 const { t: tr } = useI18n();
2828
@@ -225,6 +225,11 @@ const SOUND_ROLES = computed(() => [["sfx", tr("editing.inspector.sound")], ["mu
225225 : motionState.render === "error" ? tr("editing.inspector.renderError", { error: motionState.error ?? "" })
226226 : motionState.render === "rendering" ? tr("editing.inspector.renderProgress", { pct: Math.round(motionState.progress * 100) }) : tr("editing.inspector.renderPending") }}
227227 </span>
228+ <div v-if="motionState?.render === 'error'" class="row">
229+ <button class="btn sm" @click="emit('rerender')">{{ tr("editing.inspector.retryRender") }}</button>
230+ <button class="btn sm" @click="emit('comment', tr('editing.inspector.askFixRender', { id: item.data.id, error: motionState.error ?? '' }))">
231+ {{ tr("editing.inspector.fixRender") }}</button>
232+ </div>
228233 </template>
229234
230235 <template v-else-if="item.track === 'audio'">
@@ -22,7 +22,7 @@ import {
22 const props = defineProps<{22 const props = defineProps<{
23 tl: Timeline; view: MontageView; available: AvailableMedia[]; t: number; total: number;23 tl: Timeline; view: MontageView; available: AvailableMedia[]; t: number; total: number;
24 }>();24 }>();
25-const emit = defineEmits<{ edit: [(t: Timeline) => void]; comment: [string?]; seek: [number]; insert: [AvailableMedia]; media: []; beat: [] }>();25+const emit = defineEmits<{ edit: [(t: Timeline) => void]; comment: [string?]; seek: [number]; insert: [AvailableMedia]; media: []; beat: []; rerender: [] }>();
26 const sel = defineModel<Selection | null>("selection", { required: true });26 const sel = defineModel<Selection | null>("selection", { required: true });
27 const { t: tr } = useI18n();27 const { t: tr } = useI18n();
28 28
@@ -225,6 +225,11 @@ const SOUND_ROLES = computed(() => [["sfx", tr("editing.inspector.sound")], ["mu
225 : motionState.render === "error" ? tr("editing.inspector.renderError", { error: motionState.error ?? "" })225 : motionState.render === "error" ? tr("editing.inspector.renderError", { error: motionState.error ?? "" })
226 : motionState.render === "rendering" ? tr("editing.inspector.renderProgress", { pct: Math.round(motionState.progress * 100) }) : tr("editing.inspector.renderPending") }}226 : motionState.render === "rendering" ? tr("editing.inspector.renderProgress", { pct: Math.round(motionState.progress * 100) }) : tr("editing.inspector.renderPending") }}
227 </span>227 </span>
228+ <div v-if="motionState?.render === 'error'" class="row">
229+ <button class="btn sm" @click="emit('rerender')">{{ tr("editing.inspector.retryRender") }}</button>
230+ <button class="btn sm" @click="emit('comment', tr('editing.inspector.askFixRender', { id: item.data.id, error: motionState.error ?? '' }))">
231+ {{ tr("editing.inspector.fixRender") }}</button>
232+ </div>
228 </template>233 </template>
229 234
230 <template v-else-if="item.track === 'audio'">235 <template v-else-if="item.track === 'audio'">
modified web/src/project/steps/montage/useMontage.ts +10 -1
@@ -125,6 +125,15 @@ export function useMontage(pid: string) {
125125 }
126126 }
127127
128+ /** Relance les rendus de motions ratés. */
129+ async function retryRenders() {
130+ try {
131+ receive(await post<MontageView>(`${base}/renders`));
132+ } catch (e) {
133+ toast((e as Error).message, true);
134+ }
135+ }
136+
128137 /** Une demande à Daryl : la suite de la conversation `task`, sinon une nouvelle demande mise en file. `shot` :
129138 * l'image en pause dont on parle. Rend la demande (null si elle n'est pas partie). */
130139 async function send(text: string, selection: Selection | null, shot: Shot | null, task: string | null): Promise<string | null> {
@@ -179,7 +188,7 @@ export function useMontage(pid: string) {
179188 window.removeEventListener("focus", loadAvailable);
180189 });
181190
182- return { view, tl, available, dirty, holding, busy, total, edit, shiftBeats, save, flush, undo, send, stop, close, exportResolve, load, loadAvailable };
191+ return { view, tl, available, dirty, holding, busy, total, edit, shiftBeats, save, flush, undo, retryRenders, send, stop, close, exportResolve, load, loadAvailable };
183192 }
184193
185194 export type MontageState = ReturnType<typeof useMontage>;
@@ -125,6 +125,15 @@ export function useMontage(pid: string) {
125 }125 }
126 }126 }
127 127
128+ /** Relance les rendus de motions ratés. */
129+ async function retryRenders() {
130+ try {
131+ receive(await post<MontageView>(`${base}/renders`));
132+ } catch (e) {
133+ toast((e as Error).message, true);
134+ }
135+ }
136+
128 /** Une demande à Daryl : la suite de la conversation `task`, sinon une nouvelle demande mise en file. `shot` :137 /** Une demande à Daryl : la suite de la conversation `task`, sinon une nouvelle demande mise en file. `shot` :
129 * l'image en pause dont on parle. Rend la demande (null si elle n'est pas partie). */138 * l'image en pause dont on parle. Rend la demande (null si elle n'est pas partie). */
130 async function send(text: string, selection: Selection | null, shot: Shot | null, task: string | null): Promise<string | null> {139 async function send(text: string, selection: Selection | null, shot: Shot | null, task: string | null): Promise<string | null> {
@@ -179,7 +188,7 @@ export function useMontage(pid: string) {
179 window.removeEventListener("focus", loadAvailable);188 window.removeEventListener("focus", loadAvailable);
180 });189 });
181 190
182- return { view, tl, available, dirty, holding, busy, total, edit, shiftBeats, save, flush, undo, send, stop, close, exportResolve, load, loadAvailable };191+ return { view, tl, available, dirty, holding, busy, total, edit, shiftBeats, save, flush, undo, retryRenders, send, stop, close, exportResolve, load, loadAvailable };
183 }192 }
184 193
185 export type MontageState = ReturnType<typeof useMontage>;194 export type MontageState = ReturnType<typeof useMontage>;