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
22 changes: 22 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,28 @@ 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.2.0] — 2026-10-03 · La prima release ufficiale: più veloce, più chiaro, la barra delle attività ⚡

Questa versione non aggiunge strumenti: rende **più fluido e più chiaro quello che c'è già**.

### La barra delle attività (in basso a destra)
- Tutto quello che il programma fa dietro le quinte — **riaprire i file di un progetto, forme d'onda, colore automatico, copie leggere (proxy), conversioni a velocità costante, sottotitoli, voce e traduzione con l'AI, togliere lo sfondo, import e pacchetti** — ha **una riga sola nel centro attività**. In basso a destra c'è sempre **una barra** con cosa sta facendo, la percentuale e il tempo che manca; se le cose da fare sono più di una compare **+N**.
- **Un clic sulla barra apre il pannello**: per ogni attività la barra di avanzamento, **quanto è passato, quanto manca**, e per i gruppi "12 fatti · 2 al lavoro · 26 in coda"; sotto, **le finite da poco** (con quanto ci hanno messo). **✕** ferma una riga, **⏸ Pausa** non fa partire lavori nuovi, **Pulisci** toglie le finite, Esc chiude. Sul telefono la barra sta sopra i pulsanti in basso.
- Le barre del Finale, di DaProdMontage e del ritaglio con l'AI (e quelle che comparivano in un riquadro sopra l'interfaccia) ora **stanno tutte qui**.

### Riaprire un progetto con tante clip (era lento e a scatti)
- **I file si riaprono quattro alla volta, e prima quelli che stanno in timeline** (prima uno per volta, nell'ordine del contenitore). Con file nuovi: 3 volte più veloce; **riaprendo un progetto già visto: da 12 secondi a 0,2** (14 riprese).
- **Le misure già fatte si ricordano** (forma d'onda, colore automatico e locandina di ogni file, in un deposito a parte del browser/app: Vista → svuota copie leggere non lo tocca). Se il file è lo stesso, non si rifanno.
- **I lavori di fondo vanno a turno**, non tutti insieme: due leggeri alla volta (forme d'onda, colore) e **una sola copia pesante alla volta** (ferma finché il montaggio suona), e passa avanti chi sta in timeline. Prima, riaprendo un progetto con 90 riprese, partivano decine di decodifiche insieme e il programma scendeva a 5 fotogrammi al secondo.
- **La copia leggera (proxy) si fa solo per le riprese che stanno in timeline**: quelle che stanno solo nel contenitore aspettano (prima se ne facevano 96 per niente). Se ne metti una in timeline parte da sola; se la copia c'è già sul disco si usa subito.
- **Il contenitore non salta più**: mentre i file si riaprono e le forme d'onda crescono, le schede si aggiornano **al loro posto** (prima si rifaceva tutta la griglia a ogni passo e la pagina tornava in cima).

### File a velocità costante, dietro le quinte
- Un file a frame rate o bitrate variabile **entra subito** com'è e si lavora subito; **la copia a velocità costante si fa dietro le quinte** (una alla volta, col suo avanzamento, in pausa mentre il montaggio suona) e **prende il posto dell'originale** da sola quando è pronta: clip, segni e proprietà restano dove sono, e Annulla non rimette i dati vecchi. L'originale non si tocca. Prima l'import aspettava la conversione.

### Altro
- Sistemata la lettura dei pixel per il colore e la locandina (fuori dal thread principale dove si può), meno attese all'interfaccia.

## [1.1.6] — 2026-10-02 · Pagina iniziale, scelta multipla nel contenitore, voce in altre lingue (anche cinese), file a velocità costante 🗂

### Pagina iniziale: i tuoi progetti
Expand Down
15 changes: 15 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,21 @@ Editor video "vecchio stile, moderno dentro" (ispirato a EDIUS e alle centraline
- **Contenitore, scelta multipla (1.1.6)**: `scelti` + `cliccaCarta` (Ctrl/Maiusc) in `src/ui/contenitore.ts`; il dato del trascinamento è `m:id1|id2|…` (timeline: `mediaDaDato`, posa uno dopo l'altro con un solo `store.edit`). **Aggancio**: `fDrop` in `timeline.ts` (14 px, fine delle clip della traccia preferita, seconda metà di una clip → subito dopo) usato **sia** dal fantasma (`anteprimaDrop`) **sia** da `rilascia`. Classe `.in-timeline` (bordo verde) sulle carte con `usi > 0`.
- **Voce e lingue (1.1.6)**: `src/media/traduci.ts` (`traduciTesti`, NLLB-200 600M in `traduci.worker.ts` da transformers.js/CDN; `LINGUE_TRADUZIONE`; `impostaTraduttore` finto per le prove). `doppia` traduce da sola i testi se `lingua` ≠ `sottotitoliDi(p).lingua` (solo per la voce) e ritorna `posti` (`componiVoceConPosti`); `posaVoce(p, mediaId, dur, silenzia, posti)` fa **una clip per frase** dallo stesso WAV (`srcIn = da`) e mette `Sottotitolo.voce` = id della clip; in timeline un clic sulla riga SOTT sceglie quella clip. Il cinese è in `LINGUE_VOCE`/`LINGUE_PARLATE` (`nemo.ts`, `zh-CN`) ma **non** in `LINGUE_MOTORE` (riconoscimento). Finale: sezione `lingue` = "Voce e lingue" (`paginaLingue`, `traduciSottotitoli`, `pannelloVoce`).
- **File a velocità costante (1.1.6)**: `src/media/normalizza.ts` (`esamina` → `Verdetto`, `converti`) chiamato da `importaFile` prima di `importa`: video VFR (`computeFrameRateMetrics`, max/min > 1,25) → CFR con `Conversion({video:{frameRate}})`; audio-solo MP3/Opus/Vorbis con pacchetti molto variabili → AAC (o Opus) a bitrate costante. Output su disco nell'app (`registrazione_percorso` + `export_*`), in memoria nel browser; l'originale non si tocca. Spento con localStorage `dpv-normalizza = no`.
- **Centro attività (1.2.0, "prima release ufficiale")**: `src/media/attivita.ts` è il registro di tutto il lavoro di fondo (righe `Attivita`, gruppi dello
stesso tipo in una riga sola, `Lavoro` = la maniglia di chi lavora: `imposta(k, dettaglio)`, `fine`, `errore`, `segnale` per lo stop) e lo scheduler a corsie:
`inCoda({corsia:'leggero'|'pesante', titolo, categoria, gruppo, priorita}, fn)` (leggero = 2 alla volta, pesante = 1, ferma mentre il montaggio suona via
`pausaPerRiproduzione`, `pausaLavori` per la pausa dell'utente; priorità = funzione riletta a ogni turno, chi sta in timeline passa avanti con
`prioritaMedia`/`impostaUsoMedia` di `libreria.ts`). La UI è `src/ui/attivita.ts` (`CentroAttivita`: pezzo `.att` nella barra di stato + pannello `.att-pannello`,
righe aggiornate al loro posto). Chi lavora: `nuova()` (a mano) o `inCoda()`; `BarraLavoro('titolo', categoria, alAnnulla)` e `avvisoLungo()` si agganciano da soli;
`Stima`/`durataTesto` stanno in `src/core/stima.ts` (puri). Prove: `test/prove-attivita.mjs`; banchi di misura (non nelle prove) `test/_bench.mjs`.
- **Riapertura veloce (1.2.0)**: `riapriMedia` (progetti.ts) apre 4 file alla volta, prima quelli in timeline, con una sola riga "Riapro i file del progetto" e aggiorna l'interfaccia
al massimo ogni 600 ms. `src/media/cache.ts` (IndexedDB `dpv-cache`: `picchi` a un byte per valore con radice, `colore`, `poster` JPEG; chiave `chiaveMedia` = percorso|nome|dimensione|data|durata|WxH).
**La copia leggera (proxy) si fa solo per chi sta in timeline** (`proxyStato = 'attesa'` per gli altri; `risvegliaProxy` da `app.ts` a ogni modifica del documento, `proxyEsistente` apre subito
quelle già sul disco). Il contenitore, se cambia solo lo stato dei file o le locandine, aggiorna le schede al loro posto (`aggiornaSchede`, mai rifare la griglia: la pagina salterebbe);
la forma d'onda cresce con `ridisegnaCarta`.
- **Conversione VFR/CBR dietro le quinte (1.2.0)**: `controllaVelocita` in `progetti.ts` (dopo l'import: esame nel gruppo "esame", conversione nella corsia pesante) e `mettiCopia`: la copia
prende il posto del file con `store.aggiornaMedia(id, campi, spostaSrc)` (cambia progetto **e** cronologia annulla/ripeti, e sposta `srcIn` se l'inizio del file è cambiato), poi `apriMedia` di nuovo.
`importa(sel, {soloDescrizione:true})` legge com'è fatto un file senza locandina né lavori. Nel browser la copia si tiene in IndexedDB `file` (≤ 300 MB) e si toglie la maniglia.
- **Cursore che si aggancia** (`agganciaCursore` in `src/ui/timeline.ts`): sul righello si aggancia sempre (calamita `N`), Alt lo lascia libero.
- **Motore NVIDIA** (`src-tauri/src/motori.rs` + `src/media/nemo.ts`): NeMo-Speech.cpp v0.1.0 (Apache-2.0), programma a parte che l'app
scarica dalla release di NVIDIA (`nemo-speech-0.1.0-{windows,macos,linux}-{x86_64,aarch64}-{cpu,cuda,vulkan,metal}`, `.sha256` a fianco) in
Expand Down
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ ed elimini. Gira su **Windows, Mac e Android** e, in versione prova, **nel brows
15. **File → Salva il pacchetto .daprod**: il progetto **con dentro tutti i suoi file** (uno zip vero, i file restano identici). Lo apri su un altro computer e ritrovi tutto. Il **.dpv** invece è il progetto leggero: tiene il montaggio, i file restano dove sono.
17. **`F8`** apre **DaProdMontage**, la quarta pagina: butta dentro **foto e video alla rinfusa** (anche solo foto), scegli **cosa festeggi** (28 stili: matrimonio, battesimo, comunione, cresima, laurea, compleanno, Natale, viaggio, reel verticale…), quanto deve durare e, se vuoi, il brano. **✨ CREA IL MONTAGGIO** e il programma ordina le foto (per data di scatto), le tiene il tempo giusto, le muove, mette transizioni, titoli e quello che cade sopra, appoggia i tagli ai battiti. Lo vedi **in anteprima nel monitor in alto**: se non ti piace **🎲 Rigenera** (ogni volta cambia, anche con le stesse scelte), se ti piace **✅ Importa nella timeline** e lo ritocchi.
18. **Togliere lo sfondo**: sulla clip, **Proprietà → Sfondo** (o il tasto **SFONDO** sul monitor). **Colore** (green screen con fino a 3 colori, contagocce, via il riflesso), **Luce** o **AI** (soggetto, persona, oggetti coi clic). **Generatori → Animazioni**: 49 animazioni personalizzabili (sottopancia, testi, grafici, social, fondi, cerimonie).
19. **La barra delle attività** (in basso a destra) mostra sempre cosa fa il programma dietro le quinte — riaprire i file, forme d'onda, colore, copie leggere, conversioni, sottotitoli e voce con l'AI — con la percentuale e il tempo che manca. **Un clic** apre il pannello con tutte le righe (passati, mancano, in coda, finite da poco): **✕** ferma una riga, **⏸ Pausa** ferma i lavori in coda. I lavori pesanti vanno uno alla volta e aspettano mentre il montaggio suona; riaprendo un progetto con tante clip si riaprono prima i file che stanno in timeline, e le misure già fatte (forme d'onda, colore, locandine) si ricordano.
16. A **schermo pieno** le tracce crescono da sole; **`Ctrl+Shift`+rotella** le alza e le abbassa. Nell'app **`Ctrl +`** e **`Ctrl −`** ingrandiscono tutta l'interfaccia.

### 🎛 La pulsantiera
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.1.6",
"version": "1.2.0",
"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.1.6"
version = "1.2.0"
description = "DaProd Video: il montaggio vecchio stile, moderno dentro"
authors = ["DaProd"]
license = "MIT"
Expand Down
41 changes: 41 additions & 0 deletions src/core/stima.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
// Quanto è passato e quanto manca: la stima parte dalla velocità degli ultimi secondi (così regge anche quando una
// fase va più piano dell'altra, come lo scarico dei modelli e poi il lavoro vero). Pura: la usano la barra del lavoro
// e il centro attività.

/** "45 s", "2 min 10 s", "1 h 05 min" */
export function durataTesto(sec: number): string {
const s = Math.max(0, Math.round(sec));
if (s < 60) return `${s} s`;
const m = Math.floor(s / 60);
if (m < 60) return `${m} min${s % 60 ? ' ' + String(s % 60).padStart(2, '0') + ' s' : ''}`;
return `${Math.floor(m / 60)} h ${String(m % 60).padStart(2, '0')} min`;
}

/** la stima del tempo che manca */
export class Stima {
private punti: { t: number; k: number }[] = [];
private t0: number;
constructor(private ora: () => number = () => performance.now(), private finestra = 25000) { this.t0 = this.ora(); }

registra(k: number) {
const t = this.ora();
const u = this.punti[this.punti.length - 1];
// un passo indietro (nuova fase) non conta come velocità
if (u && k < u.k) this.punti = [];
this.punti.push({ t, k });
while (this.punti.length > 2 && t - this.punti[0].t > this.finestra) this.punti.shift();
}

/** secondi passati da quando è partito */
trascorso(): number { return (this.ora() - this.t0) / 1000; }

/** secondi che mancano, o null se ancora non si può dire */
restante(): number | null {
const p = this.punti;
if (p.length < 2) return null;
const a = p[0], b = p[p.length - 1];
const dt = (b.t - a.t) / 1000, dk = b.k - a.k;
if (dt < 2 || dk <= 1e-4 || b.k >= 1) return null;
return ((1 - b.k) * dt) / dk;
}
}
24 changes: 23 additions & 1 deletion src/core/store.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
// Lo stato dell'editor: il progetto, la selezione, il cursore e l'annulla/ripeti.
// Ogni modifica passa da edit(): prima si fa la fotografia, poi si cambia, poi si avvisa chi disegna.
import type { Project } from './tipi';
import type { Clip, MediaItem, Project, Sequenza } from './tipi';
import { newProject, progettoAperto } from './progetto';
import { FORMATI } from './tipi';

Expand Down Expand Up @@ -75,6 +75,28 @@ class Store {
return r;
}

/**
* Cambia i dati di un file "da fuori" (per esempio quando arriva la copia a velocità costante): vale nel progetto
* e in tutta la cronologia (se no un Annulla rimetterebbe i dati vecchi) e non è una modifica da annullare.
* spostaSrc = di quanti secondi si è mosso l'inizio del file: le clip e i segni lo seguono.
*/
aggiornaMedia(id: string, campi: Partial<MediaItem>, spostaSrc = 0) {
const tocca = (d: { media: MediaItem[]; clips: Clip[]; sequenze?: Sequenza[] }) => {
const m = d.media.find((x) => x.id === id);
if (m) {
Object.assign(m, campi);
if (spostaSrc) { if (m.markIn != null) m.markIn += spostaSrc; if (m.markOut != null) m.markOut += spostaSrc; }
}
if (!spostaSrc) return;
for (const c of d.clips) if (c.media === id) c.srcIn += spostaSrc;
for (const s of d.sequenze ?? []) for (const c of s.clips ?? []) if (c.media === id) c.srcIn += spostaSrc;
};
tocca(this.doc);
for (const s of [...this.undo, ...this.redo, ...(this.live ? [this.live] : [])]) tocca(s);
this.dirty = true;
this.emit('doc');
}

/** modifica "dal vivo" (trascinamenti): la fotografia la prende begin(), la chiude commit() */
private live: Snap | null = null;
begin(label: string) {
Expand Down
5 changes: 4 additions & 1 deletion src/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -111,4 +111,7 @@ import * as MP from './core/montagePreset';
import * as EX from './media/exif';
import * as QL from './media/qualita';
import * as RI from './media/ritmo';
(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, ui: () => banco };
import * as AT from './media/attivita';
import * as CH from './media/cache';
import * as SM from './core/stima';
(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, ui: () => banco };
Loading
Loading