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
30 changes: 30 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,36 @@ 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.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
- All'apertura (o dal tasto **Progetti** in alto, o File → Pagina iniziale) vedi **i progetti recenti con la miniatura**, **Nuovo progetto** (HD, 24p/30p, verticale, quadrato, 4K), **Apri un progetto…**, **DaProdMontage** e **Continua** il montaggio in corso. Un clic su un recente lo riapre; la ✕ lo toglie dall'elenco (il file resta dov'è). Esc chiude. Quello che salvi o apri finisce da solo nell'elenco (nel browser il programma tiene anche una copia per riaprirlo).

### Contenitore
- **Scelta multipla**: **Ctrl+clic** aggiunge o toglie un file, **Maiusc+clic** prende un tratto, clic nel vuoto toglie la scelta. **Trascinando** uno dei file scelti li porti tutti nella timeline, **uno dopo l'altro** (anche in una cartella, o col tasto destro: in coda al cursore).
- I file **già in timeline hanno un bordo verde** (con il numero di volte che ci sono); quelli scelti, un bordo oro.
- **Aggancio dopo una clip**: lasciando un file sulla seconda metà di una clip si mette **subito dopo di lei**; più in generale il file si aggancia a inizi e fini delle clip con una calamita più larga (14 px), e **il fantasma che vedi mentre trascini è già nel punto giusto**, non solo quando lasci.

### DaProdMontage
- **Il monitor è più piccolo** (poco più di un quarto dello schermo): più posto alle scelte e ai file.
- **I video si guardano prima di sceglierli**: passandoci sopra con il mouse scorrono (con il tempo), e la **lente 🔍** li apre nel monitor in alto con il play.
- **Foto e video di un formato diverso dal progetto**: scegli **Intere, con lo sfondo** (nessun pezzo tagliato) oppure **Riempi il quadro** (a tutto quadro, tagliando i bordi). Non vengono mai stirati: nelle prove un cerchio resta un cerchio in 16:9, 9:16 e 1:1.
- Sistemato: con nessun pannello aperto o chiuso (nessuna classe sul banco) la griglia della pagina Montage non scattava.

### Voce e lingue (Finale)
- **Il cinese** fra le lingue della voce (Mandarino, `zh-CN`).
- **Se la voce parla un'altra lingua dei sottotitoli, i testi si traducono da soli prima di essere letti** (prima una voce inglese leggeva il testo italiano con l'accento sbagliato). I sottotitoli restano come sono: puoi avere sottotitoli in una lingua e voce in un'altra.
- **Traduci i sottotitoli**: scegli la lingua e le righe si traducono ai loro tempi (Ctrl+Z per tornare). Il traduttore è **NLLB-200 di Meta** (nove lingue: italiano, inglese, spagnolo, francese, tedesco, portoghese, cinese, giapponese, arabo); gira sul tuo computer e si scarica la prima volta (il modello è grande: circa 600 MB).
- **La voce AI è una clip per frase**: ogni frase sta al suo posto nella traccia "Voce AI" e si sposta, si abbassa, si taglia o si toglie come ogni altra clip. **Un clic su una riga dei sottotitoli sceglie la sua voce**, e il pannello a destra mostra volume e il resto.
- **Finale più semplice**: la pagina si chiama **Voce e lingue** e ha due passi (1 · Sottotitoli: in che lingua sono e traduci; 2 · Voce: scegli la voce e la lingua, e un tasto). Le scelte che quasi nessuno cambia (chi ascolta, il modello) stanno in un riquadro chiuso nei Sottotitoli.

### File a velocità costante (importazione)
- Un **video a frame rate variabile (VFR)**, come quelli dei telefoni, si rifà da solo a **frame rate costante** all'importazione (se no in montaggio audio e video slittano); un **brano a bitrate variabile (VBR: MP3, Opus, Vorbis)** si rifà a **bitrate costante**. Si converte solo se serve davvero, l'**originale non si tocca** (la copia va in Video/DaProd Video nell'app, in memoria nel browser) e il programma te lo dice. Per spegnere: `localStorage dpv-normalizza = no`.

### Per chi ha curiosità
- Dentro: `src/ui/home.ts` + `registraRecente`/`apriRecente` in `src/progetti.ts`; `src/media/traduci.ts` (+ worker); `src/media/normalizza.ts`; `componiVoceConPosti` e `posaVoce` con le clip per frase in `src/media/doppiaggio.ts`; `Sottotitolo.voce`.
- Prove nuove in `test/prove-seguito.mjs`. **Nota onesta**: i modelli di traduzione (NLLB) e la voce cinese di Magpie non sono stati provati con i modelli veri (dal luogo dove si prova non si raggiungono Hugging Face né i modelli NVIDIA): le prove usano un traduttore e un motore finti con lo stesso protocollo. Il cinese per la voce dipende dalla versione del motore di NVIDIA installata.

## [1.1.5] — 2026-10-01 · DaProdMontage con l'anteprima in alto, ogni montaggio diverso, proprietà riordinabili 🎲

### DaProdMontage: prima lo guardi, poi lo importi
Expand Down
6 changes: 5 additions & 1 deletion CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -94,8 +94,12 @@ Editor video "vecchio stile, moderno dentro" (ispirato a EDIUS e alle centraline
- **Togliere lo sfondo (1.1.4)**: `fx.keyColori` (fino a 3 colori, `coloriChiave`) nello shader (`src/render/compositore.ts`: chiave nel piano YUV, despill, choke, feather, pulizia, `vistaChiave` = maschera/scacchi). **AI**: `c.ritaglio` (`Ritaglio` in `tipi.ts`: modo `soggetto|persona|oggetti`, modello, qualità, punti coi clic), `src/media/ritaglio.ts` (catalogo modelli e coordinamento) + `ritaglio.worker.ts` (transformers.js dalla CDN: pipeline `background-removal` per BEN2/BiRefNet/MODNet, `SamModel`/`Sam2Model` con `input_points` per gli oggetti; elenco di modelli di ripiego) → `src/media/maschere.ts` (maschere R8 per "firma", cache IndexedDB, `potaMaschere`, interpolazione fra due maschere nel compositore) e `src/media/campiona.ts` (`pixelAl`). `src/core/sfondo.ts` = parti pure (campioni, firma, levigatura). UI: `src/ui/sfondo.ts` + `sfondoGruppo` in `ispettore.ts` + tasto SFONDO nel monitor; il contagocce è la modalità `SceltaImmagine` di `posiziona.ts`. **Nelle prove** il segmentatore è finto e la libreria transformers è finta (`context.route`): i modelli veri non sono mai stati provati dal container.
- **Animazioni (1.1.4)**: clip `kind: 'title'` con `gen.anim` (`AnimSpec`, catalogo `ANIMAZIONI` in `src/core/animazioni.ts`, 6 gruppi). Ogni animazione è una **funzione pura del tempo** (`DISEGNA[id]` in `src/render/anim/*.ts`, coordinate virtuali alte 1080, easing alla GSAP, caso con seme) disegnata su Canvas 2D da `disegnaAnimazione` (`src/render/animazioni.ts`), uguale nel monitor e nell'export (`esporta.ts` aspetta i font: `caricaFontAnimazioni` in `src/render/font.ts`, @fontsource). Pannello proprietà: `animatrice` in `ispettore.ts`; libreria nel contenitore (ramo *Animazioni*, `anteprimaAnimazione`); `g:anim:` in `generatori.ts`; un "fondo" va sempre sulla traccia video più in basso. `riquadroClip` le tratta a tutto quadro.
- **DaProdMontage (1.1.4, pagina F8)**: `src/ui/montage.ts` (la pagina: file, stile, scelte; `crea()` legge EXIF, misura qualità/pezzo migliore, cerca i battiti, poi `store.edit` → `costruisciSequenza`) su `src/core/montage.ts` (**puro**: `pianifica` = ordine, `senzaDoppioni` per firma 8×8, `ripartisci` con bisezione per far sommare le durate al tempo chiesto, `inFotogrammi` somma esatta, `sulBattito`, ripetizioni se le foto sono poche; `costruisciSequenza` = una sequenza nuova con sfondo sfumato, foto a scheda o a tutto quadro con `moto` (Ken Burns via `tf/tfFine`), transizioni/effetti come blocchi, sovrapposizioni e titoli come clip `title` con animazioni, musica e audio dei video) e i 28 stili in `src/core/montagePreset.ts` (`PRESET_MONTAGE`). Aiuti: `src/media/exif.ts` (DateTimeOriginal), `qualita.ts` (Laplaciano, luce, firma), `ritmo.ts` (battiti da onset-autocorrelazione). Funziona anche con **sole foto**. Prove: `test/prove-montage.mjs`.
**Anteprima (1.1.5)**: `crea(seme?)` costruisce la sequenza con `store.edit('DaProdMontage · variante XXXXX', …)` e la mostra nel **monitor vero** (la pagina ha il suo posto in griglia: `monitor` sopra, `.montage` sotto, timeline nascosta); `this.anteprima` ricorda etichetta e seme. **Rigenera** = `togliAnteprima()` (un `doUndo`, solo se in cima all'annulla c'è ancora la *sua* etichetta, se no vuol dire che è stata toccata e si lascia) + nuova `crea()`; **Importa** = azzera `anteprima` e apre il Montaggio; **Scarta** = annulla. Così le timeline non si accumulano e Ctrl+Z funziona (il listener su `doc` rimette i bottoni a "Crea"). **Il caso**: `pianifica` usa `casoSeme(o.seme)` per durate (`variata`), movimenti (`scegliMoto`: mai due uguali di fila, `Voce.forza/verso`), transizioni (una su 4 dalla lista `FANTASIA` del ritmo), sovrapposizioni ed effetti (spostati e a volte saltati), foto lasciate fuori (rumore sul punteggio); stesso seme = stesso montaggio. L'analisi (qualità, battiti) si fa una volta per file (`analisi`, `ritmi`). **Attenzione CSS**: le regole `lato-lungo` e `senza-bin/senza-lato` valgono anche per le pagine nuove se non le escludi (`:not([data-pagina='montage'])`, `[class]` per la specificità).
**Anteprima (1.1.5)**: `crea(seme?)` costruisce la sequenza con `store.edit('DaProdMontage · variante XXXXX', …)` e la mostra nel **monitor vero** (la pagina ha il suo posto in griglia: `monitor` sopra, `.montage` sotto, timeline nascosta); `this.anteprima` ricorda etichetta e seme. **Rigenera** = `togliAnteprima()` (un `doUndo`, solo se in cima all'annulla c'è ancora la *sua* etichetta, se no vuol dire che è stata toccata e si lascia) + nuova `crea()`; **Importa** = azzera `anteprima` e apre il Montaggio; **Scarta** = annulla. Così le timeline non si accumulano e Ctrl+Z funziona (il listener su `doc` rimette i bottoni a "Crea"). **Il caso**: `pianifica` usa `casoSeme(o.seme)` per durate (`variata`), movimenti (`scegliMoto`: mai due uguali di fila, `Voce.forza/verso`), transizioni (una su 4 dalla lista `FANTASIA` del ritmo), sovrapposizioni ed effetti (spostati e a volte saltati), foto lasciate fuori (rumore sul punteggio); stesso seme = stesso montaggio. L'analisi (qualità, battiti) si fa una volta per file (`analisi`, `ritmi`). **Attenzione CSS**: le regole `lato-lungo` e `senza-bin/senza-lato` valgono anche per le pagine nuove se non le escludi (`:not([data-pagina='montage'])`, `:not(.nessuna)` per la specificità (non `[class]`: senza classi non scatta)).
- **Proprietà (1.1.5)**: le sezioni (`Ispettore.gruppo(id, …)` in `src/ui/ispettore.ts`) partono **tutte chiuse** (`aperti` in memoria) e si **riordinano trascinando la maniglia** `⠿` (eventi pointer sul `document`, non HTML5 drag: in Tauri/Windows il drag&drop di sistema lo intercetta). L'ordine sta in localStorage `dpv-isp-ordine` per **tipo di elemento** (`clip:immagine|video|audio|titolo|animazione|generatore`, `blocco:effetto|transizione`); `conOrdine` riordina solo i *posti* delle sezioni (il resto del pannello resta dov'è), le sezioni nuove o nascoste vanno in fondo / restano dove stavano (`salvaOrdineDa`). Le prove che lavorano dentro le sezioni le aprono con `apriGruppi` (`test/aiuti.mjs`). Prove: `test/prove-pannello.mjs`.
- **Pagina iniziale e progetti recenti (1.1.6)**: `src/ui/home.ts` (`Home`, overlay a tutto schermo, evento `dpv:home`, tasto **Progetti** in testata, File → Pagina iniziale). Compare all'avvio **tranne se `navigator.webdriver`** (le prove automatiche partono col banco libero: per vederla nelle prove, `dpv:home`). I recenti stanno in localStorage `dpv-recenti` (`registraRecente` in `src/progetti.ts`, chiamata da `salva` e `apriFile`: nome, percorso, clip, durata, miniatura dal poster); nel browser si tiene anche una copia JSON in IndexedDB (`rec:<chiave>`) per riaprirla senza file.
- **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`.
- **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 @@ -34,6 +34,7 @@ ed elimini. Gira su **Windows, Mac e Android** e, in versione prova, **nel brows

## ▶ Come si monta

0. All'apertura trovi la **pagina iniziale**: i progetti recenti (con la miniatura), Nuovo, Apri e DaProdMontage. Torni quando vuoi dal tasto **Progetti** in alto.
1. **Importa** i video (pulsante *Importa*, `Ctrl+I`, o trascinali nel contenitore). Il primo video decide il formato del progetto. Nel contenitore a sinistra c'è l'albero: **Video, Musica, Immagini** e **le tue cartelle** (*Nuova cartella*, trascina i file dentro); importando da una cartella il file entra lì, da Musica la finestra mostra solo gli audio.
2. Nel **contenitore** passa il mouse su un video: scorre avanti e indietro col puntatore. **Trascinalo** nella timeline, o premi **+** per metterlo al cursore (e il cursore va alla fine: +, +, + e hai la scaletta). Se lì è occupato va su una traccia libera: **niente viene coperto**.
3. Vuoi scegliere il pezzo? **Doppio clic**: si apre nel monitor come **sorgente**. Segna **attacco** (`I`) e **stacco** (`O`) e premi **`,`** (inserisci) o **`.`** (sovrascrivi). **Tab** torna al montaggio.
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.5",
"version": "1.1.6",
"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.5"
version = "1.1.6"
description = "DaProd Video: il montaggio vecchio stile, moderno dentro"
authors = ["DaProd"]
license = "MIT"
Expand Down
12 changes: 7 additions & 5 deletions src/core/montage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,8 @@ export interface OpzMontage {
musica?: { media: string; durata: number; battiti?: number[]; nome?: string };
/** scarta foto sfocate e doppie quando sono troppe */
scarta: boolean;
/** foto e video di un formato diverso dal progetto: "intero" (scheda con ombra su uno sfondo sfumato) o "riempi" (a tutto quadro, taglia i bordi) */
adatta?: 'intero' | 'riempi';
}

/** una cosa sulla linea principale: quale file, quando comincia, quanto dura, da dove si prende (video) e come si muove */
Expand Down Expand Up @@ -327,20 +329,20 @@ export const fmtDur = (s: number) => { const m = Math.floor(s / 60), r = Math.ro

// ——— Ken Burns ———
/** dove sta e quanto è grande la foto: a tutto quadro se ha quasi le stesse proporzioni, se no intera, un po' più piccola, con l'ombra */
export function inquadra(p: Pick<Project, 'w' | 'h'>, m: Pick<MediaItem, 'width' | 'height' | 'rotation'>): { scala: number; scheda: boolean } {
export function inquadra(p: Pick<Project, 'w' | 'h'>, m: Pick<MediaItem, 'width' | 'height' | 'rotation'>, riempi = false): { scala: number; scheda: boolean } {
const w = m.rotation % 180 ? m.height : m.width, h = m.rotation % 180 ? m.width : m.height;
if (!w || !h) return { scala: 1, scheda: false };
const ra = (w / h) / (p.w / p.h);
if (ra >= 0.8 && ra <= 1.25) {
if (riempi || (ra >= 0.8 && ra <= 1.25)) {
const kf = Math.min(p.w / w, p.h / h), kc = Math.max(p.w / w, p.h / h);
return { scala: kc / kf, scheda: false };
}
return { scala: 0.88, scheda: true };
}

/** partenza e arrivo del movimento; tfFine è undefined per "fermo" */
export function moto(p: Pick<Project, 'w' | 'h'>, m: Pick<MediaItem, 'width' | 'height' | 'rotation'>, id: Moto, forza: number, indice = 0): { tf: Transform; tfFine?: Transform } {
const { scala, scheda } = inquadra(p, m);
export function moto(p: Pick<Project, 'w' | 'h'>, m: Pick<MediaItem, 'width' | 'height' | 'rotation'>, id: Moto, forza: number, indice = 0, riempi = false): { tf: Transform; tfFine?: Transform } {
const { scala, scheda } = inquadra(p, m, riempi);
const base: Transform = { ...TF0, scale: scala };
if (scheda) { base.ombra = 0.75; base.angoli = 0.035; }
if (id === 'fermo') return { tf: base };
Expand Down Expand Up @@ -404,7 +406,7 @@ export function costruisciSequenza(p: Project, piano: PianoMontage, o: OpzMontag
piano.voci.forEach((v, i) => {
const m = mediaDi(v.entrata.media);
const len = durF[i];
const mt = m ? moto(p, m, v.moto, pr.forzaMoto * v.forza, v.verso) : { tf: { ...TF0 } };
const mt = m ? moto(p, m, v.moto, pr.forzaMoto * v.forza, v.verso, o.adatta === 'riempi') : { tf: { ...TF0 } };
const link = v.entrata.tipo === 'video' && v.entrata.audio && o.audioVideo > -50 ? uid('l') : undefined;
const c = newClip('media', tFoto.id, a, len, { media: v.entrata.media, name: v.entrata.nome, srcIn: v.srcIn, tf: mt.tf, tfFine: mt.tfFine, link, fx: { ...FX0, effetti: pr.look.length ? [...pr.look] : undefined } });
if (i === 0) c.fadeIn = Math.min(Math.round(r * 0.8), Math.floor(len / 2));
Expand Down
6 changes: 5 additions & 1 deletion src/core/tipi.ts
Original file line number Diff line number Diff line change
Expand Up @@ -391,7 +391,11 @@ export interface Logo {
}

/** una riga dei sottotitoli: da e a in fotogrammi della timeline */
export interface Sottotitolo { id: string; da: number; a: number; testo: string }
export interface Sottotitolo {
id: string; da: number; a: number; testo: string;
/** la clip audio con la voce AI di questa riga (si sceglie dalla riga e si gestisce come ogni altra clip) */
voce?: string;
}

export interface Sottotitoli {
righe: Sottotitolo[];
Expand Down
Loading
Loading