Guide

Riparare i file nel browser: come funziona IntactFile

La maggior parte dei file multimediali "corrotti" non lo è affatto: i dati sono intatti, manca solo l'indice che li mappa. IntactFile ricostruisce quell'indice in locale: il file viene letto dal disco, riparato nella scheda tramite WebAssembly e riscritto. Zero byte vengono caricati, e puoi dimostrarlo nella scheda Network. Ecco come funziona, e dove smette onestamente di funzionare.

Il file morto che non è morto

Qualcuno estrae una scheda SD mentre la spia della fotocamera è ancora accesa. Un drone finisce in acqua. OBS va in crash alla terza ora. Un telefono va in throttling termico e l'app di registrazione viene chiusa a metà scrittura. In ognuno di questi casi il risultato è lo stesso: un file video da più gigabyte che ogni player si rifiuta di aprire.ffmpeg stampa moov atom not found. Il sistema operativo si offre di eliminarlo. A ogni segno esteriore, sembra morto.

Di solito non lo è. Un file MP4 o MOV è per la maggior parte un unico, enorme blocco di fotogrammi compressi — il box mdat — con un piccolo indice alla fine chiamato atomo moov. Ilmoov è la mappa: registra dove inizia ogni fotogramma, quanto è grande, quando deve essere mostrato e la configurazione del codec di cui un decoder ha bisogno per prepararsi. Un player legge prima l'indice e lo usa per orientarsi nei dati. Senza di esso, il player non sa dove inizia nemmeno un singolo fotogramma, quindi si arrende del tutto invece di riprodurre "la maggior parte" del file.

Ecco il lato crudele di questo design: l'atomo moov viene scritto per ultimo, in un unico passaggio, nel momento in cui una registrazione viene finalizzata. Non può essere scritto prima, perché l'offset in byte di un fotogramma non può essere noto prima che il fotogramma esista, e la fotocamera non ha idea se ti fermerai tra dieci secondi o tra due ore. Perciò i registratori riversano i fotogrammi inmdat man mano che li codificano e scrivono l'indice solo quando premi stop. Interrompi quella finalizzazione — batteria che salta, spegnimento termico, estrazione della scheda, crash — e ottieni un file pieno di riprese reali e intatte, senza alcun indice. I byte ci sono. La mappa non è mai stata scritta.

IntactFile esiste esattamente per questo divario tra "i dati sono sopravvissuti" e "il file si apre". Per un MP4 o MOV con ilmoov mancante, ricostruisce l'indice. Per molti altri formati, ricostruisce qualunque struttura equivalente sia andata persa. E fa tutto questo sulla tua macchina, senza che il file lasci mai la scheda.

Perché riparare significa caricare — e perché noi non l'abbiamo fatto

Cerca uno strumento di riparazione file e quasi ogni risultato funziona allo stesso modo: carichi il tuo file su un server, un processo su quel server esegue la riparazione vera e propria, e tu scarichi il risultato. C'è una ragione lineare per questo. La logica di riparazione è di solito un binario nativo — un programma C o C++ costruito attorno a librerie comeffmpeg — e il posto comodo dove far girare un binario nativo è una macchina che controlli tu. Il browser è stato, per anni, il posto sbagliato dove eseguire quel tipo di codice.

Non volevamo costruirlo così, per ragioni che sono pratiche prima ancora che filosofiche. I file che le persone hanno bisogno di riparare sono esattamente quelli penosi da caricare: registrazioni da più gigabyte, spesso su una connessione lenta, spesso l'unica copia di qualcosa di insostituibile. Chiedere a qualcuno di caricare un video di matrimonio da 6 GB sul server di uno sconosciuto, aspettare il trasferimento due volte e fidarsi che l'originale venga poi eliminato è una brutta esperienza avvolta attorno a una storia sulla privacy ancora peggiore. Il contenuto del file non è affar nostro, e il modo più semplice per rispettarlo è rendere impossibile a noi vederlo.

Perciò il vincolo che ci siamo posti è stato netto: il file non lascia mai il tuo dispositivo. Non "cifrato in transito", non "eliminato dopo l'elaborazione" — proprio non trasferito affatto. Questo ha trasformato l'intero problema in una domanda ingegneristica: la riparazione stessa può girare nel browser? Grazie a WebAssembly, la risposta oggi è sì, e il resto di questo articolo parla di cosa ci è voluto per arrivarci e di dove le giunture sono ancora visibili.

Ricostruire un atomo moov, passo dopo passo

Ricostruire un moov mancante è il caso di punta, quindi vale la pena ripercorrerlo. Il problema: hai ftyp (una minuscola intestazione "questo è un MP4"), un gigantesco mdat e nessun indice. La breccia che rende possibile la ricostruzione è chemdat non è davvero opaco per un parser che comprende i codec. I fotogrammi sono memorizzati come unità con prefisso di lunghezza, e il bitstream di ciascun codec porta con sé una struttura riconoscibile — start code e intestazioni che segnano il tipo di un fotogramma, le sue dimensioni, i suoi confini. Un parser che sa com'è fatta un'access unit H.264 o HEVC può percorrere i dati grezzi e recuperare i confini che l'indice descriveva.

Questa è la classe di riparazione che lo strumento open-sourceuntrunc ha stabilito. Trattiamo untruncrigorosamente come riferimento algoritmico — è rilasciato sotto licenza GPL — e il nostro motore video è una reimplementazione clean-room in TypeScript, ed è questo a permettere allo stesso identico codice di girare in una scheda del browser e in Node. I passi sono:

  1. Trovare i confini dei fotogrammi. Scansionaremdat, usando i prefissi di lunghezza e le firme dei codec per individuare dove inizia e finisce ogni fotogramma video e audio.
  2. Classificare e misurare. Assegnare ogni fotogramma al suo stream e registrarne la dimensione e l'offset in byte, ricostruendo campione dopo campione la materia prima delle tabelle delle dimensioni dei campioni (stsz) e degli offset dei chunk (stco).
  3. Recuperare temporizzazione e configurazione. La temporizzazione per fotogramma e la configurazione del decoder risiedevano nell'indice perduto. Quando il file danneggiato non può fornirle, un clip di riferimento integro dalla stessa fotocamera, registrato con le stesse impostazioni, insegna al motore com'è il "normale" — il frame rate, la timescale, i set di parametri del codec.
  4. Scrivere un nuovo indice e finalizzare. Assemblare le tabelle dei campioni recuperate in un nuovo moov e produrre un file conforme alle specifiche.

Il risultato riproduce tutto ciò che è fisicamente sopravvissuto, terminando dove finiscono i dati. I limiti onesti discendono direttamente dal meccanismo. Gli ultimissimi secondi — quelli ancora fermi nel buffer di memoria della fotocamera, mai scaricati sull'archiviazione — sono davvero persi; nessuna scansione può trovare fotogrammi che non sono mai stati scritti. Le registrazioni con una temporizzazione molto irregolare possono tornare con un lieve disallineamento audio/video, perché il clock ricostruito è dedotto anziché registrato. E se mdat stesso è fatto di zeri perché è stata l'archiviazione a guastarsi, e non il registratore, non ci sono fotogrammi da trovare. MOV è il formato QuickTime da cui è nato l'MP4 e condivide questa identica struttura, quindi un .mov che non si apre e un .mp4 che non si apre sono quasi sempre lo stesso problema in abiti diversi.

Gli altri formati, in breve

Una volta che inquadri la corruzione come "il payload è integro, l'indice è rotto", la stessa forma ricompare in un formato dopo l'altro. La maggior parte dei formati contenitore e documento tiene i propri dati in un posto e un indice da qualche altra parte, ed è quasi sempre l'indice a morire per primo. Ciascun motore qui sotto ricostruisce una variante diversa di quella tabella.

Cosa ricostruisce ciascun motore, e come è implementato.
FormatoCosa è rottoCome viene ricostruito
MP4 / MOVIndice moov mancante o troncatoRicostruzione clean-room in TypeScript dai fotogrammi sopravvissuti
ZIP / OOXMLCentral directory perduta (l'indice dell'archivio)TypeScript puro: scansiona i local file header, ricostruisce la central directory
PDFTabella di riferimenti incrociati (xref) rottaUn pre-passaggio xref in TypeScript, poi qpdf compilato in WebAssembly
JPEGTabelle di intestazione danneggiate o mancantiDecodifica mozjpeg / jSquash più un trapianto di intestazione donatrice
PNGLunghezze dei chunk, CRC o struttura corrottiChirurgia dei byte in TypeScript puro sullo stream dei chunk
SQLitePagine B-tree danneggiateRecupera i record raggiungibili, poi ricostruisce il database
RAR / 7zL'archivio non si estraeEstrazione tramite 7z compilato in WebAssembly

Alcuni meritano una frase in più. ZIP e OOXML sono lo stesso motore: un .docx, .xlsx o.pptx è un archivio ZIP, e lo ZIP tiene una "central directory" alla fine che elenca ogni voce. Perdila e le voci stesse sono ancora lì, ciascuna con il proprio local header, quindi il motore percorre il file trovando quelle intestazioni e ricostruisce la directory attorno ad esse — niente librerie, TypeScript puro. Il PDF tiene una tabella di riferimenti incrociati che mappa ogni oggetto su un offset in byte; quando è rotta, un pre-passaggio in TypeScript ricava di nuovo gli offset prima di passare il file a qpdf, che abbiamo compilato in WebAssembly invece di richiamare un binario esterno. IlJPEG è il caso anomalo: i dati di scansione compressi spesso sopravvivono mentre l'intestazione che porta le tabelle di quantizzazione e di Huffman no, quindi trapiantiamo un'intestazione donatrice da una foto integra scattata con la stessa fotocamera, poi decodifichiamo con mozjpeg. Tabelle diverse, stesso principio in tutto: ricostruire l'indice, lasciare i dati intatti.

L'architettura: WebAssembly, nella tua scheda

Far girare tutto questo lato client è una storia di WebAssembly con alcuni spigoli vivi su cui vale la pena essere onesti. Alcuni motori sono TypeScript puro e girano e basta. Altri incapsulano codice nativo esistente compilato in WASM — qpdf per il PDF, 7z per gli archivi e, per il lavoro video, libav.js. La scelta di libav.js è deliberata e guidata dalle licenze: è una build LGPL delle librerie FFmpeg, distribuibile in un prodotto, anziché una build GPL di ffmpeg, che non lo sarebbe. Le licenze qui sono un vincolo ingegneristico reale, non un ripensamento.

I due vincoli più duri sono la memoria e l'isolamento.

Memoria: streaming, non buffering

WebAssembly gira in uno spazio di indirizzamento limitato — in pratica qualcosa come 2–4 GB a seconda del browser e della build. I file che le persone hanno più bisogno di riparare sono spesso più grandi di così: una registrazione non finalizzata può pesare molti gigabyte. Perciò i motori non possono semplicemente caricare l'intero file in memoria e partire. Lavorano in streaming — leggendo ed elaborando il file per intervalli, tenendo solo ciò di cui un dato passaggio ha bisogno e scrivendo l'output in modo incrementale. Questo mantiene limitata la memoria e rende possibile riparare un file in una scheda che non potrebbe mai contenerlo per intero. (Manteniamo le affermazioni sulle prestazioni volutamente qualitative: dipende molto dal file, dal codec e dalla macchina.)

Isolamento: due origini, di proposito

Una WASM veloce vuole i thread, e i thread nel browser richiedonoSharedArrayBuffer, che i browser abilitano solo per le pagine che sono cross-origin isolated — pagine che inviano gli header COOP e COEP giusti e accettano le restrizioni che ne derivano. Quelle restrizioni sono scomode per una normale pagina di marketing che porta con sé cose di terze parti come gli annunci. Invece di sacrificare l'una per l'altra, abbiamo suddiviso il prodotto su due origini: una convenzionale origine di marketing che può portare gli annunci, e una separata origine dell'app, cross-origin isolated, che serve COOP/COEP affinché i motori WASM multithread possano girare a piena velocità. Sono più parti in movimento di un singolo sito, ma consente al materiale da leggere e al macchinario di riparazione di avere ciascuno l'ambiente di cui ha bisogno.

L'onestà come valore di ritorno

La decisione di design più importante non sta in nessuno dei codec. Sta nel fatto che ogni motore restituisce un esito esplicito —riuscito, parziale ofallito — insieme a una classe di danno che descrive ciò che ha trovato. Non inventa mai un successo.

Questo conta perché la riparazione file è un campo pieno di strumenti che producono un file in ogni caso, lasciandoti scoprire ore dopo che in realtà non si riproduce, o si riproduce per quattro secondi e si blocca. Quando la mappa manca e i dati sono sopravvissuti, possiamo ricostruirla e dirlo. Quando i dati sono fisicamente scomparsi — sovrascritti, azzerati da un disco che si sta guastando, troncati via — non c'è nulla da ricostruire, e la risposta giusta è dire quello, con chiarezza, invece di emettere un file dall'aspetto plausibile che poi fallisce quando conta. Anche "parziale" è un risultato di prima classe: recuperare le prime due ore di una registrazione di tre ore ed essere schietti sul fatto che l'ultima ora non è sopravvissuta è un esito genuinamente utile, purché non lo si spacci per completo.

Trattare il caso di fallimento come un risultato onesto e con un nome preciso, anziché come un imbarazzo da nascondere, è, secondo noi, la differenza tra uno strumento a cui puoi affidare l'unica copia di qualcosa e uno a cui non puoi.

Verificalo tu stesso: apri la scheda Network

Non devi credere sulla parola a "0 byte caricati". È il tipo di affermazione che puoi controllare in meno di un minuto, e preferiremmo che lo facessi:

  1. Apri lo strumento di riparazione nel tuo browser.
  2. Apri i DevTools (F12 o Cmd/Ctrl + Shift + I) e passa alla scheda Network.
  3. Trascina un file danneggiato sulla pagina.
  4. Osserva le richieste. Vedrai caricarsi gli asset della pagina stessa, ma nessuna richiesta trasporta il contenuto del tuo file. I byte vanno dal disco alla scheda e tornano al disco — mai sulla rete.

Per i più diffidenti, la prova più forte: stacca del tutto la connessione di rete ed esegui la riparazione offline. Funziona lo stesso, perché non c'è mai stato un server nel giro fin dall'inizio. Gli stessi motori alimentano anche un pacchetto Node per l'uso da riga di comando e automatizzato, ma sul sito web la riparazione avviene dove il tuo file già si trova.

Domande frequenti

IntactFile carica il mio file su un server?

No. Ogni motore gira lato client nel tuo browser, compilato in WebAssembly. Il file viene letto dal disco con la File API, ricostruito nella scheda e salvato di nuovo sul disco. Zero byte del file vengono inviati sulla rete. Puoi verificarlo da solo: apri i DevTools, tieni d'occhio la scheda Network, trascina un file e osserva che nessuna richiesta ne trasporta il contenuto. Gli stessi motori girano anche in Node per l'uso da riga di comando e lato server, ma sul sito web nulla lascia la tua macchina.

È lo stesso di untrunc?

Risolve la stessa classe di problema — ricostruire un indice MP4/MOV mancante a partire dai dati multimediali sopravvissuti — ma il codice non è untrunc. untrunc è rilasciato sotto licenza GPL, quindi lo usiamo solo come riferimento algoritmico e abbiamo scritto un'implementazione clean-room in TypeScript. Questo permette anche allo stesso identico motore di girare senza modifiche sia nel browser sia in Node, cosa che uno strumento nativo in C++ non può fare senza una build separata.

Perché alcune riparazioni richiedono un secondo file, integro?

Quando un MP4/MOV perde il suo indice, se ne vanno anche le informazioni di temporizzazione e la configurazione del decoder che vi risiedevano. Un clip di riferimento integro registrato con la stessa fotocamera, con le stesse impostazioni, permette al motore di imparare come sono la temporizzazione "normale" dei fotogrammi e la configurazione del codec per il tuo dispositivo, e di applicarle ai fotogrammi recuperati. Per il JPEG vale la stessa idea applicata alle intestazioni: un'intestazione donatrice da una foto scattata con la stessa fotocamera fornisce le tabelle di cui un decoder ha bisogno. Se il file danneggiato conserva ancora abbastanza dei propri metadati, non serve alcun riferimento.

Può riparare qualsiasi file corrotto?

No, e non fingerà di poterlo fare. La riparazione del contenitore e dell'indice funziona quando il payload è sopravvissuto e solo la mappa è andata persa o si è danneggiata — il che copre gran parte della "corruzione" del mondo reale. Quando i dati veri e propri sono fisicamente scomparsi (sovrascritti, azzerati da un disco che si sta guastando o troncati via), non c'è nulla da ricostruire, e ogni strumento onesto deve dirlo. Ogni motore restituisce un esito esplicito — riuscito, parziale o fallito — con una classe di danno, invece di produrre un file dall'aspetto plausibile che poi non si riproduce.

Come gestisce i file video molto grandi?

Le registrazioni non finalizzate spesso pesano diversi gigabyte, e la WebAssembly nel browser ha uno spazio di indirizzamento limitato (all'incirca 2–4 GB a seconda del browser e della build). Perciò i motori lavorano in streaming: leggono ed elaborano il file per intervalli invece di caricarlo tutto in memoria in una volta sola. Questo mantiene limitato l'uso della memoria e consente di riparare file da più gigabyte in una scheda che non potrebbe mai contenerli tutti insieme.

Vuoi il tour a livello di byte del formato al centro di tutto questo? LeggiAnatomia di un MP4: cos'è l'atomo moov e perché il tuo video non si riproduce.