Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions .github/workflows/pages.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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 }}
Expand Down
13 changes: 13 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
6 changes: 5 additions & 1 deletion CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -169,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).
Expand Down
4 changes: 2 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -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",
Expand Down
2 changes: 1 addition & 1 deletion src-tauri/Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion src-tauri/Cargo.toml
Original file line number Diff line number Diff line change
@@ -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"
Expand Down
3 changes: 2 additions & 1 deletion src/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -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<string, unknown>).__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<string, unknown>).__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 };
24 changes: 21 additions & 3 deletions src/media/fotogrammi.ts
Original file line number Diff line number Diff line change
Expand Up @@ -215,6 +215,17 @@ const flussi = new Map<string, Flusso>();
const ricerche = new Map<string, Ricerca>();
/** dove sta chiedendo ogni voce e da quando: il fotogramma nitido si chiede solo quando ci si ferma */
const fermo = new Map<string, { t: number; da: number }>();
/**
* 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
Expand Down Expand Up @@ -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;
Expand All @@ -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);
Expand Down Expand Up @@ -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();
}
}
Expand Down
30 changes: 30 additions & 0 deletions test/prove.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -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();
}

Expand Down
Loading