From 7d8fa7f6da53576b29733cf62c0e035db7980dc5 Mon Sep 17 00:00:00 2001 From: Claude Date: Wed, 7 Oct 2026 14:54:00 +0000 Subject: [PATCH 1/2] Pages: la libreria AI resta fuori dal sito di prova La protezione dei segreti di GitHub scambia "Mistral3ForConditionalGeneration" (un nome di classe dentro transformers.min.js) per una chiave API di Mistral e blocca il push su gh-pages: la versione prova era rimasta alla 1.2.1. Su Pages si toglie dist/ai e la libreria arriva dalla CDN come prima; le app la tengono. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01TqgA6T2ncfxMbmoaKeJWS4 --- .github/workflows/pages.yml | 4 ++++ CLAUDE.md | 3 ++- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/.github/workflows/pages.yml b/.github/workflows/pages.yml index a8326c9..259d74a 100644 --- a/.github/workflows/pages.yml +++ b/.github/workflows/pages.yml @@ -24,6 +24,10 @@ jobs: with: { node-version: 22, cache: npm } - run: npm ci - run: npm run build + # la libreria AI dentro l'app (dist/ai/) non va su Pages: la protezione dei segreti di GitHub scambia il nome di una + # classe della libreria ("Mistral3ForConditionalGeneration", 32 lettere vicino a "mistral") per una chiave API e + # blocca il push. Nel browser la libreria arriva dalla CDN come prima (libreria() in src/media/libreriaAI.ts). + - run: rm -rf dist/ai - name: Pubblica sul ramo gh-pages env: GH_TOKEN: ${{ github.token }} diff --git a/CLAUDE.md b/CLAUDE.md index 5e0df54..ce59dd4 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -135,7 +135,8 @@ Editor video "vecchio stile, moderno dentro" (ispirato a EDIUS e alle centraline (1840/1720/1620/1520/1420/1250 px) così **Esporta resta sempre nello schermo** (la prova la misura da 1024 a 1920). - **L'AI che non si ferma (1.3.0)**: `src/media/libreriaAI.ts` è la base di tutti i worker AI (voce, traduci, ritaglio, provaAI): la libreria **viaggia dentro l'app** in `public/ai/` (→ `dist/ai/`, la scrive `scripts/ai-locale.mjs` in `prebuild` da registry.npmjs.org col controllo - sha512, è in `.gitignore`; se manca si va sulla CDN), `wasmPaths` puntano lì (asyncify). `caricaConRipieghi` prova **WebGPU poi WASM** e per + sha512, è in `.gitignore`; se manca si va sulla CDN; **su Pages no**: `pages.yml` toglie `dist/ai` perché la protezione dei segreti di GitHub + scambia `Mistral3ForConditionalGeneration` per una chiave e blocca il push), `wasmPaths` puntano lì (asyncify). `caricaConRipieghi` prova **WebGPU poi WASM** e per ognuno **più pesi** (q8/fp16/fp32: a MODNet e BiRefNet manca q8); se la scheda si rompe a metà lavoro si ricarica su WASM e si rifà. Scheda video spegnibile (`dpv-ai-scheda`). Un worker morto si butta (`onerror` → `null`). **Errori**: `src/media/erroriAI.ts` (`spiegaErroreAI`: regole regex → frase italiana con cosa fare; le frasi già italiane passano). Nemotron che sbaglia → Whisper da solo (`ripiego` in `sottotitoliAI`), e From 06cc2b8d6864e2e35b2b116ec442495215234c34 Mon Sep 17 00:00:00 2001 From: Claude Date: Wed, 7 Oct 2026 15:13:24 +0000 Subject: [PATCH 2/2] =?UTF-8?q?1.3.1:=20il=20play=20non=20si=20ferma=20pi?= =?UTF-8?q?=C3=B9=20sui=20computer=20lenti?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit pulisci() contava "non usato da un po'" con l'orologio: se un giro di disegno durava più di 1,5 s (computer lento, scheda video che arranca, pagina ferma) buttava il flusso che si stava guardando subito prima del disegno, e quello nuovo ripartiva dal fotogramma chiave (con il GOP lungo anche 20 s prima): immagine ferma per secondi, poi nera quando anche la ricerca veniva chiusa. Era la prova "play dal mezzo col GOP lungo" che falliva su main con la 1.2.1 e la 1.3.0. Ora si conta dall'ultima richiesta di un fotogramma (ultimaRichiesta), con un tetto di 6 s per i flussi e 20 s per le ricerche, e chi presta il fotogramma di ripiego resta vivo. Col processore rallentato 6x: prima 4 play su 6 fermi, ora 6 su 6 si muovono subito. Prova nuova che fallisce senza la correzione. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01TqgA6T2ncfxMbmoaKeJWS4 --- CHANGELOG.md | 13 +++++++++++++ CLAUDE.md | 3 +++ package-lock.json | 4 ++-- package.json | 2 +- src-tauri/Cargo.lock | 2 +- src-tauri/Cargo.toml | 2 +- src/main.ts | 3 ++- src/media/fotogrammi.ts | 24 +++++++++++++++++++++--- test/prove.mjs | 30 ++++++++++++++++++++++++++++++ 9 files changed, 74 insertions(+), 9 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 49f7ca4..6984aeb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,19 @@ Tutte le versioni notevoli del banco di montaggio. Le date sono in formato AAAA- Ogni versione pubblicata ha la sua [release GitHub](https://github.com/cammo22/DaProdVideo/releases) con le app per Windows (portatile e installabile), Mac e Android, e va online su [GitHub Pages](https://cammo22.github.io/DaProdVideo/) come versione prova. +## [1.3.1] — 2026-10-07 · Il play non si ferma più sui computer lenti ▶️ + +Una versione piccola, con due correzioni trovate guardando le prove automatiche dopo l'uscita della 1.3.0. + +### Sistemato +- **Sui computer lenti il play poteva restare fermo per secondi, o andare nero**, soprattutto con le registrazioni LIVE e le riprese con pochi fotogrammi chiave. Se un giro di disegno durava più di un secondo e mezzo (un computer lento, la scheda video che arranca, la pagina ferma un attimo), il programma credeva che il flusso del video non servisse più e lo buttava: quello nuovo doveva ripartire dal fotogramma chiave, anche 20 secondi prima, e intanto il monitor restava sull'immagine di partenza. Dopo 8 secondi spariva anche quella, e il monitor andava nero. Ora un flusso si butta solo se intanto si sono disegnate *altre* cose senza di lui (la clip è finita), e chi presta l'immagine di ripiego resta vivo finché serve. Col processore rallentato 6 volte: prima 4 play su 6 restavano fermi (uno andava nero), ora 6 su 6 si muovono da subito. +- **La versione prova su GitHub Pages non si aggiornava**: GitHub scambiava il nome di una classe dentro la libreria AI (`Mistral3ForConditionalGeneration`) per una chiave segreta e bloccava la pubblicazione. Sul sito di prova la libreria AI arriva dalla CDN, come prima della 1.3.0; le app per Windows e Mac la tengono dentro. + +### Per chi ha curiosità +- `src/media/fotogrammi.ts`: `ultimaRichiesta` (l'ultima volta che si è chiesto un fotogramma) è l'orologio di `pulisci()`; tetto di 6 s per i flussi e 20 s per le ricerche. Prova nuova: la pagina ferma 1,8 s non fa buttare il flusso che si guarda, e uno che non serve più si chiude ancora. +- `.github/workflows/pages.yml`: toglie `dist/ai` prima del push su `gh-pages`. +- Le prove registrano lo stack degli errori anche dalle pagine aperte a parte. + ## [1.3.0] — 2026-10-07 · Tutto si trova, l'AI non si ferma 🔍✨ La versione per chi usa DaProd Video tutti i giorni: le funzioni AI che davano errore ora partono (e se una strada non va ne prendono un'altra da sole), e ogni strumento si trova in un attimo, con la spiegazione di come si usa. diff --git a/CLAUDE.md b/CLAUDE.md index ce59dd4..833ea83 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -170,6 +170,9 @@ Editor video "vecchio stile, moderno dentro" (ispirato a EDIUS e alle centraline dentro l'app) e `src-tauri/src/aggiorna.rs` (scarica col `curl` di sistema e apre setup/portatile/DMG). - **La riproduzione non deve mai bloccarsi**: `src/media/fotogrammi.ts` non butta un flusso che non ha ancora il primo fotogramma, e i proxy (`src/media/proxy.ts`) si fanno da soli per le riprese pesanti. L'export legge gli originali. + **`pulisci()` conta dall'ultima richiesta di un fotogramma (`ultimaRichiesta`), non dall'orologio** (1.3.1): un disegno lento non deve + far buttare il flusso che si guarda (col GOP lungo il nuovo ripartirebbe dal fotogramma chiave: immagine ferma, poi nera). Chi presta + il fotogramma di ripiego (ricerca, altra qualità) si tiene vivo. Nelle prove `__dpvTest.FT` = il modulo dei fotogrammi. - **Il montaggio non copre mai niente** da solo: spostare, lasciare, incollare e i generatori usano il modo `libero` (`src/core/montaggio.ts`: si fermano contro le vicine o vanno su una traccia libera). Copre solo SOVR dal monitor. Le transizioni non cambiano la durata delle clip. Il taglio tocca solo le tracce accese (se ce ne sono). diff --git a/package-lock.json b/package-lock.json index ece9d0b..03e7961 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "daprod-video", - "version": "1.3.0", + "version": "1.3.1", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "daprod-video", - "version": "1.3.0", + "version": "1.3.1", "license": "MIT", "dependencies": { "@fontsource/archivo-black": "^5.3.0", diff --git a/package.json b/package.json index b207b85..fc00f1a 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "daprod-video", "productName": "DaProd Video", - "version": "1.3.0", + "version": "1.3.1", "description": "DaProd Video: il montaggio video vecchio stile, moderno dentro. Timeline, due monitor, pulsantiera e VU a lancetta.", "author": "DaProd", "license": "MIT", diff --git a/src-tauri/Cargo.lock b/src-tauri/Cargo.lock index 3bdb1b7..61be77c 100644 --- a/src-tauri/Cargo.lock +++ b/src-tauri/Cargo.lock @@ -624,7 +624,7 @@ checksum = "52560adf09603e58c9a7ee1fe1dcb95a16927b17c127f0ac02d6e768a0e25bc1" [[package]] name = "daprod-video" -version = "1.3.0" +version = "1.3.1" dependencies = [ "serde", "serde_json", diff --git a/src-tauri/Cargo.toml b/src-tauri/Cargo.toml index 9476fa2..39ea5b1 100644 --- a/src-tauri/Cargo.toml +++ b/src-tauri/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "daprod-video" -version = "1.3.0" +version = "1.3.1" description = "DaProd Video: il montaggio vecchio stile, moderno dentro" authors = ["DaProd"] license = "MIT" diff --git a/src/main.ts b/src/main.ts index 6fcdaa1..57a60a4 100644 --- a/src/main.ts +++ b/src/main.ts @@ -79,6 +79,7 @@ import * as FE from './core/effettiClip'; import * as G from './render/grafica'; import { pianoVideo } from './render/piano'; import * as Z from './azioni'; +import * as FT from './media/fotogrammi'; import { statoDecoder } from './media/fotogrammi'; import { ripresaDiProva, ripresaVfrDiProva, branoVbrDiProva } from './demo'; import * as NZ from './media/normalizza'; @@ -116,4 +117,4 @@ import * as CH from './media/cache'; import * as SM from './core/stima'; import * as DG from './media/diagnosiAI'; import * as EA from './media/erroriAI'; -(window as unknown as Record).__dpvTest = { M, TC, P, esporta, creaEdl, motore, guadagnoClip, pianoVideo, Z, statoDecoder, ripresaDiProva, ripresaVfrDiProva, branoVbrDiProva, NZ, TD, PR, importaFile, apriFile, PK, proxyStato: (id: string) => mediaRT(id)?.proxyStato, mediaRT, B, S, SU, V, AG, LV, SQ, SOT, FE, G, mixaggio, PV, TR, TK, ST, Compositore, NM, DP, WV, LAV, SF, MK, RT, CP, USF, AN, RA, fontAnim, MT, MP, EX, QL, RI, AT, CH, SM, DG, EA, ui: () => banco }; +(window as unknown as Record).__dpvTest = { M, TC, P, esporta, creaEdl, motore, guadagnoClip, pianoVideo, Z, statoDecoder, ripresaDiProva, ripresaVfrDiProva, branoVbrDiProva, NZ, TD, PR, importaFile, apriFile, PK, proxyStato: (id: string) => mediaRT(id)?.proxyStato, mediaRT, B, S, SU, V, AG, LV, SQ, SOT, FE, G, mixaggio, PV, TR, TK, ST, Compositore, NM, DP, WV, LAV, SF, MK, RT, CP, USF, AN, RA, fontAnim, MT, MP, EX, QL, RI, AT, CH, SM, DG, EA, FT, ui: () => banco }; diff --git a/src/media/fotogrammi.ts b/src/media/fotogrammi.ts index 7b69615..1712d13 100644 --- a/src/media/fotogrammi.ts +++ b/src/media/fotogrammi.ts @@ -215,6 +215,17 @@ const flussi = new Map(); const ricerche = new Map(); /** dove sta chiedendo ogni voce e da quando: il fotogramma nitido si chiede solo quando ci si ferma */ const fermo = new Map(); +/** + * L'ultima volta che qualcuno ha chiesto un fotogramma. "Non usato da un po'" si conta da qui e non dall'orologio: un + * flusso è vecchio se dopo di lui si sono chiesti altri fotogrammi per un po', non se il disegno è lento (un computer + * lento, la scheda video che arranca, una lettura dei pixel, la memoria che si libera). Prima, dopo un giro più lungo + * di 1,5 s, pulisci() lo chiudeva subito prima del disegno e quello nuovo ripartiva dal fotogramma chiave (con le + * registrazioni LIVE anche 20 s prima): immagine ferma, poi nero. + */ +let ultimaRichiesta = performance.now(); +/** oltre questi si chiude comunque, anche se nessuno chiede più niente (un vuoto in timeline, la pagina ferma a lungo) */ +const ETA_MAX_FLUSSO = 6000, ETA_MAX_RICERCA = 20000; + /** * L'ultimo fotogramma di un flusso buttato a metà play (ripartito più avanti, tornato indietro): si mostra finché il * flusso nuovo non ha il suo. Prima in quel momento il monitor faceva un lampo nero, o tornava al fotogramma fermo di @@ -301,6 +312,7 @@ function flussoDi(k: string, l: Lettore, t: number): Flusso | null { * larghezza = quanti pixel servono (0 = va bene anche il proxy). */ export function fotogramma(key: string, mediaId: string, t: number, flusso: boolean, larghezza = 0): Fotogramma | null { + ultimaRichiesta = performance.now(); const r = mediaRT(mediaId); if (!r) return null; if (r.image) return r.image; @@ -317,7 +329,12 @@ export function fotogramma(key: string, mediaId: string, t: number, flusso: bool // mentre il flusso parte si mostra l'ultimo fotogramma vero: quello del flusso appena buttato, quello dell'altra // qualità (la copia leggera è diventata pronta a metà play), o quello che la ricerca aveva già const altra = base + (q === 'p' ? 'o' : 'p'); - return ponti.get(k) ?? flussi.get(altra)?.current ?? ricerche.get(k)?.current ?? ricerche.get(altra)?.current ?? f?.current ?? null; + const fa = flussi.get(altra), rk = ricerche.get(k), ra = ricerche.get(altra); + const ripiego = ponti.get(k) ?? fa?.current ?? rk?.current ?? ra?.current ?? f?.current ?? null; + // chi presta il fotogramma resta vivo finché serve: se no, col flusso nuovo ancora lontano (fotogrammi chiave radi), + // pulisci() chiudeva la ricerca dopo 8 s "senza uso" e il monitor andava nero + if (ripiego) for (const x of [fa, rk, ra]) if (x && x.current === ripiego) x.lastUse = ultimaRichiesta; + return ripiego; } const fl = flussi.get(k); const veloce = ricerca(k, l).at(t); @@ -370,10 +387,11 @@ export function prepara(key: string, mediaId: string, t: number) { /** chiude i flussi non usati da un po' (libera i decoder e la memoria video) */ export function pulisci(maxEta = 1500) { const now = performance.now(); - for (const [k, f] of flussi) if (now - f.lastUse > maxEta) { f.close(); flussi.delete(k); togliPonte(k); } + const vecchio = (uso: number, eta: number, max: number) => ultimaRichiesta - uso > eta || now - uso > max; + for (const [k, f] of flussi) if (vecchio(f.lastUse, maxEta, ETA_MAX_FLUSSO)) { f.close(); flussi.delete(k); togliPonte(k); } for (const k of [...ponti.keys()]) if (!flussi.has(k)) togliPonte(k); for (const [k, r] of ricerche) { - if (now - r.lastUse > 8000) { r.close(); ricerche.delete(k); } + if (vecchio(r.lastUse, 8000, ETA_MAX_RICERCA)) { r.close(); ricerche.delete(k); } else if (now - r.lastUse > 2000) r.riposa(); } } diff --git a/test/prove.mjs b/test/prove.mjs index aca8696..52efa3c 100644 --- a/test/prove.mjs +++ b/test/prove.mjs @@ -1974,6 +1974,36 @@ try { // Il proxy ha un GOP di mezzo secondo: si limita la frequenza alle ripartenze necessarie per raggiungere il GOP successivo. const limiteFlussi = Math.ceil(b.durataMs / 500) + 2; prova('col proxy il play parte subito anche dal mezzo (e non riparte a raffica)', b.partito && siMuove(b) && b.nati <= limiteFlussi, JSON.stringify({ ...b, limiteFlussi })); + // un disegno lento (un computer lento, una lettura dei pixel, la pagina ferma un attimo) non deve far buttare il flusso + // che si sta guardando: prima pulisci() lo chiudeva dopo 1,5 s e il nuovo ripartiva dal fotogramma chiave (immagine + // ferma, poi nera). Uno che davvero non serve più (si disegna un'altra clip) invece si chiude ancora. + const lento = await pg.evaluate(async () => { + const { FT } = window.__dpvTest; + const id = window.__dpv.doc.media[0].id; + const aspetta = (ms) => new Promise((ok) => setTimeout(ok, ms)); + FT.fermaFlussi(); + let primo = null; + for (let i = 0; i < 200 && !primo; i++) { primo = FT.fotogramma('lenta', id, 3, true); await aspetta(30); } + const n0 = FT.statoDecoder().nati; + const t0 = performance.now(); + while (performance.now() - t0 < 1800) { /* la pagina è ferma */ } + FT.pulisci(); + const dopo = FT.fotogramma('lenta', id, 3.04, true); + const nati = FT.statoDecoder().nati - n0; + // un'altra voce disegna per 1,7 s, la prima no: la prima si chiude (lo fa anche il pulisci() di ogni secondo), l'altra resta + const t1 = performance.now(); + while (performance.now() - t1 < 1700) { FT.fotogramma('altra', id, 5 + (performance.now() - t1) / 1000, true); await aspetta(40); } + FT.pulisci(); + const rimasti = FT.statoDecoder().flussi; + const n1 = FT.statoDecoder().nati; + FT.fotogramma('altra', id, 5 + (performance.now() - t1) / 1000, true); + const altraViva = FT.statoDecoder().nati === n1; + FT.fotogramma('lenta', id, 3.1, true); + const lentaChiusa = FT.statoDecoder().nati === n1 + 1; + FT.fermaFlussi(); + return { primo: !!primo, dopo: !!dopo, nati, rimasti, altraViva, lentaChiusa }; + }); + prova('la pagina ferma 1,8 s non fa buttare il flusso che si guarda (e uno che non serve più si chiude ancora)', lento.primo && lento.dopo && lento.nati === 0 && lento.rimasti === 1 && lento.altraViva && lento.lentaChiusa, JSON.stringify(lento)); await pg.close(); }