atomsrecovery: read from mdat only what is on headers
[platform/upstream/gst-plugins-good.git] / gst / isomp4 / atomsrecovery.c
1 /* Quicktime muxer plugin for GStreamer
2  * Copyright (C) 2010 Thiago Santos <thiago.sousa.santos@collabora.co.uk>
3  *
4  * This library is free software; you can redistribute it and/or
5  * modify it under the terms of the GNU Library General Public
6  * License as published by the Free Software Foundation; either
7  * version 2 of the License, or (at your option) any later version.
8  *
9  * This library is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
12  * Library General Public License for more details.
13  *
14  * You should have received a copy of the GNU Library General Public
15  * License along with this library; if not, write to the
16  * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
17  * Boston, MA 02110-1301, USA.
18  */
19 /*
20  * Unless otherwise indicated, Source Code is licensed under MIT license.
21  * See further explanation attached in License Statement (distributed in the file
22  * LICENSE).
23  *
24  * Permission is hereby granted, free of charge, to any person obtaining a copy of
25  * this software and associated documentation files (the "Software"), to deal in
26  * the Software without restriction, including without limitation the rights to
27  * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
28  * of the Software, and to permit persons to whom the Software is furnished to do
29  * so, subject to the following conditions:
30  *
31  * The above copyright notice and this permission notice shall be included in all
32  * copies or substantial portions of the Software.
33  *
34  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
35  * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
36  * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
37  * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
38  * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
39  * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
40  * SOFTWARE.
41  */
42
43 /*
44  * This module contains functions for serializing partial information from
45  * a mux in progress (by qtmux elements). This enables reconstruction of the
46  * moov box if a crash happens and thus recovering the movie file.
47  *
48  * Usage:
49  * 1) pipeline: ...yourelements ! qtmux moov-recovery-file=path.mrf ! \
50  * filesink location=moovie.mov
51  *
52  * 2) CRASH!
53  *
54  * 3) gst-launch-1.0 qtmoovrecover recovery-input=path.mrf broken-input=moovie.mov \
55         fixed-output=recovered.mov
56  *
57  * 4) (Hopefully) enjoy recovered.mov.
58  *
59  * --- Recovery file layout ---
60  * 1) Version (a guint16)
61  * 2) Prefix atom (if present)
62  * 3) ftyp atom
63  * 4) MVHD atom (without timescale/duration set)
64  * 5) moovie timescale
65  * 6) number of traks
66  * 7) list of trak atoms (stbl data is ignored, except for the stsd atom)
67  * 8) Buffers metadata (metadata that is relevant to the container)
68  *    Buffers metadata are stored in the order they are added to the mdat,
69  *    each entre has a fixed size and is stored in BE. booleans are stored
70  *    as a single byte where 0 means false, otherwise is true.
71  *   Metadata:
72  *   - guint32   track_id;
73  *   - guint32   nsamples;
74  *   - guint32   delta;
75  *   - guint32   size;
76  *   - guint64   chunk_offset;
77  *   - gboolean  sync;
78  *   - gboolean  do_pts;
79  *   - guint64   pts_offset; (always present, ignored if do_pts is false)
80  *
81  * The mdat file might contain ftyp and then mdat, in case this is the faststart
82  * temporary file there is no ftyp and no mdat header, only the buffers data.
83  *
84  * Notes about recovery file layout: We still don't store tags nor EDTS data.
85  *
86  * IMPORTANT: this is still at a experimental state.
87  */
88
89 #include "atomsrecovery.h"
90
91 #define MAX_CHUNK_SIZE (1024 * 1024)    /* 1MB */
92
93 #define ATOMS_RECOV_OUTPUT_WRITE_ERROR(err) \
94     g_set_error (err, ATOMS_RECOV_QUARK, ATOMS_RECOV_ERR_FILE, \
95         "Failed to write to output file: %s", g_strerror (errno))
96
97 static gboolean
98 atoms_recov_write_version (FILE * f)
99 {
100   guint8 data[2];
101   GST_WRITE_UINT16_BE (data, ATOMS_RECOV_FILE_VERSION);
102   return fwrite (data, 2, 1, f) == 1;
103 }
104
105 static gboolean
106 atoms_recov_write_ftyp_info (FILE * f, AtomFTYP * ftyp, GstBuffer * prefix)
107 {
108   guint8 *data = NULL;
109   guint64 offset = 0;
110   guint64 size = 0;
111
112   if (prefix) {
113     GstMapInfo map;
114
115     gst_buffer_map (prefix, &map, GST_MAP_READ);
116     if (fwrite (map.data, 1, map.size, f) != map.size) {
117       gst_buffer_unmap (prefix, &map);
118       return FALSE;
119     }
120     gst_buffer_unmap (prefix, &map);
121   }
122   if (!atom_ftyp_copy_data (ftyp, &data, &size, &offset)) {
123     return FALSE;
124   }
125   if (fwrite (data, 1, offset, f) != offset) {
126     g_free (data);
127     return FALSE;
128   }
129   g_free (data);
130   return TRUE;
131 }
132
133 /*
134  * Writes important info on the 'moov' atom (non-trak related)
135  * to be able to recover the moov structure after a crash.
136  *
137  * Currently, it writes the MVHD atom.
138  */
139 static gboolean
140 atoms_recov_write_moov_info (FILE * f, AtomMOOV * moov)
141 {
142   guint8 *data;
143   guint64 size;
144   guint64 offset = 0;
145   guint64 atom_size = 0;
146   gint writen = 0;
147
148   /* likely enough */
149   size = 256;
150   data = g_malloc (size);
151   atom_size = atom_mvhd_copy_data (&moov->mvhd, &data, &size, &offset);
152   if (atom_size > 0)
153     writen = fwrite (data, 1, atom_size, f);
154   g_free (data);
155   return atom_size > 0 && writen == atom_size;
156 }
157
158 /*
159  * Writes the number of traks to the file.
160  * This simply writes a guint32 in BE.
161  */
162 static gboolean
163 atoms_recov_write_traks_number (FILE * f, guint32 traks)
164 {
165   guint8 data[4];
166   GST_WRITE_UINT32_BE (data, traks);
167   return fwrite (data, 4, 1, f) == 1;
168 }
169
170 /*
171  * Writes the moov's timescale to the file
172  * This simply writes a guint32 in BE.
173  */
174 static gboolean
175 atoms_recov_write_moov_timescale (FILE * f, guint32 timescale)
176 {
177   guint8 data[4];
178   GST_WRITE_UINT32_BE (data, timescale);
179   return fwrite (data, 4, 1, f) == 1;
180 }
181
182 /*
183  * Writes the trak atom to the file.
184  */
185 gboolean
186 atoms_recov_write_trak_info (FILE * f, AtomTRAK * trak)
187 {
188   guint8 *data;
189   guint64 size;
190   guint64 offset = 0;
191   guint64 atom_size = 0;
192   gint writen = 0;
193
194   /* buffer is realloced to a larger size if needed */
195   size = 4 * 1024;
196   data = g_malloc (size);
197   atom_size = atom_trak_copy_data (trak, &data, &size, &offset);
198   if (atom_size > 0)
199     writen = fwrite (data, atom_size, 1, f);
200   g_free (data);
201   return atom_size > 0 && writen == atom_size;
202 }
203
204 gboolean
205 atoms_recov_write_trak_samples (FILE * f, AtomTRAK * trak, guint32 nsamples,
206     guint32 delta, guint32 size, guint64 chunk_offset, gboolean sync,
207     gboolean do_pts, gint64 pts_offset)
208 {
209   guint8 data[TRAK_BUFFER_ENTRY_INFO_SIZE];
210   /*
211    * We have to write a TrakBufferEntryInfo
212    */
213   GST_WRITE_UINT32_BE (data + 0, trak->tkhd.track_ID);
214   GST_WRITE_UINT32_BE (data + 4, nsamples);
215   GST_WRITE_UINT32_BE (data + 8, delta);
216   GST_WRITE_UINT32_BE (data + 12, size);
217   GST_WRITE_UINT64_BE (data + 16, chunk_offset);
218   if (sync)
219     GST_WRITE_UINT8 (data + 24, 1);
220   else
221     GST_WRITE_UINT8 (data + 24, 0);
222   if (do_pts) {
223     GST_WRITE_UINT8 (data + 25, 1);
224     GST_WRITE_UINT64_BE (data + 26, pts_offset);
225   } else {
226     GST_WRITE_UINT8 (data + 25, 0);
227     GST_WRITE_UINT64_BE (data + 26, 0);
228   }
229
230   return fwrite (data, 1, TRAK_BUFFER_ENTRY_INFO_SIZE, f) ==
231       TRAK_BUFFER_ENTRY_INFO_SIZE;
232 }
233
234 gboolean
235 atoms_recov_write_headers (FILE * f, AtomFTYP * ftyp, GstBuffer * prefix,
236     AtomMOOV * moov, guint32 timescale, guint32 traks_number)
237 {
238   if (!atoms_recov_write_version (f)) {
239     return FALSE;
240   }
241
242   if (!atoms_recov_write_ftyp_info (f, ftyp, prefix)) {
243     return FALSE;
244   }
245
246   if (!atoms_recov_write_moov_info (f, moov)) {
247     return FALSE;
248   }
249
250   if (!atoms_recov_write_moov_timescale (f, timescale)) {
251     return FALSE;
252   }
253
254   if (!atoms_recov_write_traks_number (f, traks_number)) {
255     return FALSE;
256   }
257
258   return TRUE;
259 }
260
261 static gboolean
262 read_atom_header (FILE * f, guint32 * fourcc, guint32 * size)
263 {
264   guint8 aux[8];
265
266   if (fread (aux, 1, 8, f) != 8)
267     return FALSE;
268   *size = GST_READ_UINT32_BE (aux);
269   *fourcc = GST_READ_UINT32_LE (aux + 4);
270   return TRUE;
271 }
272
273 static gboolean
274 moov_recov_file_parse_prefix (MoovRecovFile * moovrf)
275 {
276   guint32 fourcc;
277   guint32 size;
278   guint32 total_size = 0;
279   if (fseek (moovrf->file, 2, SEEK_SET) != 0)
280     return FALSE;
281   if (!read_atom_header (moovrf->file, &fourcc, &size)) {
282     return FALSE;
283   }
284
285   if (fourcc != FOURCC_ftyp) {
286     /* we might have a prefix here */
287     if (fseek (moovrf->file, size - 8, SEEK_CUR) != 0)
288       return FALSE;
289
290     total_size += size;
291
292     /* now read the ftyp */
293     if (!read_atom_header (moovrf->file, &fourcc, &size))
294       return FALSE;
295   }
296
297   /* this has to be the ftyp */
298   if (fourcc != FOURCC_ftyp)
299     return FALSE;
300   total_size += size;
301   moovrf->prefix_size = total_size;
302   return fseek (moovrf->file, size - 8, SEEK_CUR) == 0;
303 }
304
305 static gboolean
306 moov_recov_file_parse_mvhd (MoovRecovFile * moovrf)
307 {
308   guint32 fourcc;
309   guint32 size;
310   if (!read_atom_header (moovrf->file, &fourcc, &size)) {
311     return FALSE;
312   }
313   /* check for sanity */
314   if (fourcc != FOURCC_mvhd)
315     return FALSE;
316
317   moovrf->mvhd_size = size;
318   moovrf->mvhd_pos = ftell (moovrf->file) - 8;
319
320   /* skip the remaining of the mvhd in the file */
321   return fseek (moovrf->file, size - 8, SEEK_CUR) == 0;
322 }
323
324 static gboolean
325 mdat_recov_file_parse_mdat_start (MdatRecovFile * mdatrf)
326 {
327   guint32 fourcc, size;
328
329   if (!read_atom_header (mdatrf->file, &fourcc, &size)) {
330     return FALSE;
331   }
332   if (size == 1) {
333     mdatrf->mdat_header_size = 16;
334     mdatrf->mdat_size = 16;
335   } else {
336     mdatrf->mdat_header_size = 8;
337     mdatrf->mdat_size = 8;
338   }
339   mdatrf->mdat_start = ftell (mdatrf->file) - 8;
340
341   return fourcc == FOURCC_mdat;
342 }
343
344 static gboolean
345 mdat_recov_file_find_mdat (FILE * file, GError ** err)
346 {
347   guint32 fourcc = 0, size = 0;
348   gboolean failure = FALSE;
349   while (fourcc != FOURCC_mdat && !failure) {
350     if (!read_atom_header (file, &fourcc, &size)) {
351       goto parse_error;
352     }
353     switch (fourcc) {
354         /* skip these atoms */
355       case FOURCC_ftyp:
356       case FOURCC_free:
357       case FOURCC_udta:
358         if (fseek (file, size - 8, SEEK_CUR) != 0) {
359           goto file_seek_error;
360         }
361         break;
362       case FOURCC_mdat:
363         break;
364       default:
365         GST_ERROR ("Unexpected atom in headers %" GST_FOURCC_FORMAT,
366             GST_FOURCC_ARGS (fourcc));
367         failure = TRUE;
368         break;
369     }
370   }
371
372   if (!failure) {
373     /* Reverse to mdat start */
374     if (fseek (file, -8, SEEK_CUR) != 0)
375       goto file_seek_error;
376   }
377
378   return !failure;
379
380 parse_error:
381   g_set_error (err, ATOMS_RECOV_QUARK, ATOMS_RECOV_ERR_FILE,
382       "Failed to parse atom");
383   return FALSE;
384
385 file_seek_error:
386   g_set_error (err, ATOMS_RECOV_QUARK, ATOMS_RECOV_ERR_FILE,
387       "Failed to seek to start of the file");
388   return FALSE;
389
390 }
391
392 MdatRecovFile *
393 mdat_recov_file_create (FILE * file, gboolean datafile, GError ** err)
394 {
395   MdatRecovFile *mrf = g_new0 (MdatRecovFile, 1);
396
397   g_return_val_if_fail (file != NULL, NULL);
398
399   mrf->file = file;
400   mrf->rawfile = datafile;
401
402   /* get the file/data length */
403   if (fseek (file, 0, SEEK_END) != 0)
404     goto file_length_error;
405   /* still needs to deduce the mdat header and ftyp size */
406   mrf->data_size = ftell (file);
407   if (mrf->data_size == -1L)
408     goto file_length_error;
409
410   if (fseek (file, 0, SEEK_SET) != 0)
411     goto file_seek_error;
412
413   if (datafile) {
414     /* this file contains no atoms, only raw data to be placed on the mdat
415      * this happens when faststart mode is used */
416     mrf->mdat_start = 0;
417     mrf->mdat_header_size = 16;
418     mrf->mdat_size = 16;
419     return mrf;
420   }
421
422   if (!mdat_recov_file_find_mdat (file, err)) {
423     goto fail;
424   }
425
426   /* we don't parse this if we have a tmpdatafile */
427   if (!mdat_recov_file_parse_mdat_start (mrf)) {
428     g_set_error (err, ATOMS_RECOV_QUARK, ATOMS_RECOV_ERR_PARSING,
429         "Error while parsing mdat atom");
430     goto fail;
431   }
432
433   return mrf;
434
435 file_seek_error:
436   g_set_error (err, ATOMS_RECOV_QUARK, ATOMS_RECOV_ERR_FILE,
437       "Failed to seek to start of the file");
438   goto fail;
439
440 file_length_error:
441   g_set_error (err, ATOMS_RECOV_QUARK, ATOMS_RECOV_ERR_FILE,
442       "Failed to determine file size");
443   goto fail;
444
445 fail:
446   mdat_recov_file_free (mrf);
447   return NULL;
448 }
449
450 void
451 mdat_recov_file_free (MdatRecovFile * mrf)
452 {
453   fclose (mrf->file);
454   g_free (mrf);
455 }
456
457 static gboolean
458 moov_recov_parse_num_traks (MoovRecovFile * moovrf)
459 {
460   guint8 traks[4];
461   if (fread (traks, 1, 4, moovrf->file) != 4)
462     return FALSE;
463   moovrf->num_traks = GST_READ_UINT32_BE (traks);
464   return TRUE;
465 }
466
467 static gboolean
468 moov_recov_parse_moov_timescale (MoovRecovFile * moovrf)
469 {
470   guint8 ts[4];
471   if (fread (ts, 1, 4, moovrf->file) != 4)
472     return FALSE;
473   moovrf->timescale = GST_READ_UINT32_BE (ts);
474   return TRUE;
475 }
476
477 static gboolean
478 skip_atom (MoovRecovFile * moovrf, guint32 expected_fourcc)
479 {
480   guint32 size;
481   guint32 fourcc;
482
483   if (!read_atom_header (moovrf->file, &fourcc, &size))
484     return FALSE;
485   if (fourcc != expected_fourcc)
486     return FALSE;
487
488   return (fseek (moovrf->file, size - 8, SEEK_CUR) == 0);
489 }
490
491 static gboolean
492 moov_recov_parse_tkhd (MoovRecovFile * moovrf, TrakRecovData * trakrd)
493 {
494   guint32 size;
495   guint32 fourcc;
496   guint8 data[4];
497
498   /* make sure we are on a tkhd atom */
499   if (!read_atom_header (moovrf->file, &fourcc, &size))
500     return FALSE;
501   if (fourcc != FOURCC_tkhd)
502     return FALSE;
503
504   trakrd->tkhd_file_offset = ftell (moovrf->file) - 8;
505
506   /* move 8 bytes forward to the trak_id pos */
507   if (fseek (moovrf->file, 12, SEEK_CUR) != 0)
508     return FALSE;
509   if (fread (data, 1, 4, moovrf->file) != 4)
510     return FALSE;
511
512   /* advance the rest of tkhd */
513   if (fseek (moovrf->file, 68, SEEK_CUR) != 0)
514     return FALSE;
515
516   trakrd->trak_id = GST_READ_UINT32_BE (data);
517   return TRUE;
518 }
519
520 static gboolean
521 moov_recov_parse_stbl (MoovRecovFile * moovrf, TrakRecovData * trakrd)
522 {
523   guint32 size;
524   guint32 fourcc;
525   guint32 auxsize;
526
527   if (!read_atom_header (moovrf->file, &fourcc, &size))
528     return FALSE;
529   if (fourcc != FOURCC_stbl)
530     return FALSE;
531
532   trakrd->stbl_file_offset = ftell (moovrf->file) - 8;
533   trakrd->stbl_size = size;
534
535   /* skip the stsd */
536   if (!read_atom_header (moovrf->file, &fourcc, &auxsize))
537     return FALSE;
538   if (fourcc != FOURCC_stsd)
539     return FALSE;
540   if (fseek (moovrf->file, auxsize - 8, SEEK_CUR) != 0)
541     return FALSE;
542
543   trakrd->stsd_size = auxsize;
544   trakrd->post_stsd_offset = ftell (moovrf->file);
545
546   /* as this is the last atom we parse, we don't skip forward */
547
548   return TRUE;
549 }
550
551 static gboolean
552 moov_recov_parse_minf (MoovRecovFile * moovrf, TrakRecovData * trakrd)
553 {
554   guint32 size;
555   guint32 fourcc;
556   guint32 auxsize;
557
558   if (!read_atom_header (moovrf->file, &fourcc, &size))
559     return FALSE;
560   if (fourcc != FOURCC_minf)
561     return FALSE;
562
563   trakrd->minf_file_offset = ftell (moovrf->file) - 8;
564   trakrd->minf_size = size;
565
566   /* skip either of vmhd, smhd, hmhd that might follow */
567   if (!read_atom_header (moovrf->file, &fourcc, &auxsize))
568     return FALSE;
569   if (fourcc != FOURCC_vmhd && fourcc != FOURCC_smhd && fourcc != FOURCC_hmhd &&
570       fourcc != FOURCC_gmhd)
571     return FALSE;
572   if (fseek (moovrf->file, auxsize - 8, SEEK_CUR))
573     return FALSE;
574
575   /* skip a possible hdlr and the following dinf */
576   if (!read_atom_header (moovrf->file, &fourcc, &auxsize))
577     return FALSE;
578   if (fourcc == FOURCC_hdlr) {
579     if (fseek (moovrf->file, auxsize - 8, SEEK_CUR))
580       return FALSE;
581     if (!read_atom_header (moovrf->file, &fourcc, &auxsize))
582       return FALSE;
583   }
584   if (fourcc != FOURCC_dinf)
585     return FALSE;
586   if (fseek (moovrf->file, auxsize - 8, SEEK_CUR))
587     return FALSE;
588
589   /* now we are ready to read the stbl */
590   if (!moov_recov_parse_stbl (moovrf, trakrd))
591     return FALSE;
592
593   return TRUE;
594 }
595
596 static gboolean
597 moov_recov_parse_mdhd (MoovRecovFile * moovrf, TrakRecovData * trakrd)
598 {
599   guint32 size;
600   guint32 fourcc;
601   guint8 data[4];
602
603   /* make sure we are on a tkhd atom */
604   if (!read_atom_header (moovrf->file, &fourcc, &size))
605     return FALSE;
606   if (fourcc != FOURCC_mdhd)
607     return FALSE;
608
609   trakrd->mdhd_file_offset = ftell (moovrf->file) - 8;
610
611   /* get the timescale */
612   if (fseek (moovrf->file, 12, SEEK_CUR) != 0)
613     return FALSE;
614   if (fread (data, 1, 4, moovrf->file) != 4)
615     return FALSE;
616   trakrd->timescale = GST_READ_UINT32_BE (data);
617   if (fseek (moovrf->file, 8, SEEK_CUR) != 0)
618     return FALSE;
619   return TRUE;
620 }
621
622 static gboolean
623 moov_recov_parse_mdia (MoovRecovFile * moovrf, TrakRecovData * trakrd)
624 {
625   guint32 size;
626   guint32 fourcc;
627
628   /* make sure we are on a tkhd atom */
629   if (!read_atom_header (moovrf->file, &fourcc, &size))
630     return FALSE;
631   if (fourcc != FOURCC_mdia)
632     return FALSE;
633
634   trakrd->mdia_file_offset = ftell (moovrf->file) - 8;
635   trakrd->mdia_size = size;
636
637   if (!moov_recov_parse_mdhd (moovrf, trakrd))
638     return FALSE;
639
640   if (!skip_atom (moovrf, FOURCC_hdlr))
641     return FALSE;
642   if (!moov_recov_parse_minf (moovrf, trakrd))
643     return FALSE;
644   return TRUE;
645 }
646
647 static gboolean
648 moov_recov_parse_trak (MoovRecovFile * moovrf, TrakRecovData * trakrd)
649 {
650   guint64 offset;
651   guint32 size;
652   guint32 fourcc;
653
654   offset = ftell (moovrf->file);
655   if (offset == -1) {
656     return FALSE;
657   }
658
659   /* make sure we are on a trak atom */
660   if (!read_atom_header (moovrf->file, &fourcc, &size)) {
661     return FALSE;
662   }
663   if (fourcc != FOURCC_trak) {
664     return FALSE;
665   }
666   trakrd->trak_size = size;
667
668   /* now we should have a trak header 'tkhd' */
669   if (!moov_recov_parse_tkhd (moovrf, trakrd))
670     return FALSE;
671
672   /* FIXME add edts handling here and in qtmux, as this is only detected
673    * after buffers start flowing */
674
675   if (!moov_recov_parse_mdia (moovrf, trakrd))
676     return FALSE;
677
678   if (fseek (moovrf->file,
679           (long int) trakrd->mdia_file_offset + trakrd->mdia_size,
680           SEEK_SET) != 0)
681     return FALSE;
682
683   trakrd->extra_atoms_offset = ftell (moovrf->file);
684   trakrd->extra_atoms_size = size - (trakrd->extra_atoms_offset - offset);
685
686   trakrd->file_offset = offset;
687   /* position after the trak */
688   return fseek (moovrf->file, (long int) offset + size, SEEK_SET) == 0;
689 }
690
691 MoovRecovFile *
692 moov_recov_file_create (FILE * file, GError ** err)
693 {
694   gint i;
695   MoovRecovFile *moovrf = g_new0 (MoovRecovFile, 1);
696
697   g_return_val_if_fail (file != NULL, NULL);
698
699   moovrf->file = file;
700
701   /* look for ftyp and prefix at the start */
702   if (!moov_recov_file_parse_prefix (moovrf)) {
703     g_set_error (err, ATOMS_RECOV_QUARK, ATOMS_RECOV_ERR_PARSING,
704         "Error while parsing prefix atoms");
705     goto fail;
706   }
707
708   /* parse the mvhd */
709   if (!moov_recov_file_parse_mvhd (moovrf)) {
710     g_set_error (err, ATOMS_RECOV_QUARK, ATOMS_RECOV_ERR_PARSING,
711         "Error while parsing mvhd atom");
712     goto fail;
713   }
714
715   if (!moov_recov_parse_moov_timescale (moovrf)) {
716     g_set_error (err, ATOMS_RECOV_QUARK, ATOMS_RECOV_ERR_PARSING,
717         "Error while parsing timescale");
718     goto fail;
719   }
720   if (!moov_recov_parse_num_traks (moovrf)) {
721     g_set_error (err, ATOMS_RECOV_QUARK, ATOMS_RECOV_ERR_PARSING,
722         "Error while parsing parsing number of traks");
723     goto fail;
724   }
725
726   /* sanity check */
727   if (moovrf->num_traks > 1024) {
728     g_set_error (err, ATOMS_RECOV_QUARK, ATOMS_RECOV_ERR_PARSING,
729         "Unsupported number of traks");
730     goto fail;
731   }
732
733   /* init the traks */
734   moovrf->traks_rd = g_new0 (TrakRecovData, moovrf->num_traks);
735   for (i = 0; i < moovrf->num_traks; i++) {
736     atom_stbl_init (&(moovrf->traks_rd[i].stbl));
737   }
738   for (i = 0; i < moovrf->num_traks; i++) {
739     if (!moov_recov_parse_trak (moovrf, &(moovrf->traks_rd[i]))) {
740       g_set_error (err, ATOMS_RECOV_QUARK, ATOMS_RECOV_ERR_PARSING,
741           "Error while parsing trak atom");
742       goto fail;
743     }
744   }
745
746   return moovrf;
747
748 fail:
749   moov_recov_file_free (moovrf);
750   return NULL;
751 }
752
753 void
754 moov_recov_file_free (MoovRecovFile * moovrf)
755 {
756   gint i;
757   fclose (moovrf->file);
758   if (moovrf->traks_rd) {
759     for (i = 0; i < moovrf->num_traks; i++) {
760       atom_stbl_clear (&(moovrf->traks_rd[i].stbl));
761     }
762     g_free (moovrf->traks_rd);
763   }
764   g_free (moovrf);
765 }
766
767 static gboolean
768 moov_recov_parse_buffer_entry (MoovRecovFile * moovrf, TrakBufferEntryInfo * b)
769 {
770   guint8 data[TRAK_BUFFER_ENTRY_INFO_SIZE];
771   gint read;
772
773   read = fread (data, 1, TRAK_BUFFER_ENTRY_INFO_SIZE, moovrf->file);
774   if (read != TRAK_BUFFER_ENTRY_INFO_SIZE)
775     return FALSE;
776
777   b->track_id = GST_READ_UINT32_BE (data);
778   b->nsamples = GST_READ_UINT32_BE (data + 4);
779   b->delta = GST_READ_UINT32_BE (data + 8);
780   b->size = GST_READ_UINT32_BE (data + 12);
781   b->chunk_offset = GST_READ_UINT64_BE (data + 16);
782   b->sync = data[24] != 0;
783   b->do_pts = data[25] != 0;
784   b->pts_offset = GST_READ_UINT64_BE (data + 26);
785   return TRUE;
786 }
787
788 static gboolean
789 mdat_recov_add_sample (MdatRecovFile * mdatrf, guint32 size)
790 {
791   /* test if this data exists */
792   if (mdatrf->mdat_size - mdatrf->mdat_header_size + size > mdatrf->data_size)
793     return FALSE;
794
795   mdatrf->mdat_size += size;
796   return TRUE;
797 }
798
799 static TrakRecovData *
800 moov_recov_get_trak (MoovRecovFile * moovrf, guint32 id)
801 {
802   gint i;
803   for (i = 0; i < moovrf->num_traks; i++) {
804     if (moovrf->traks_rd[i].trak_id == id)
805       return &(moovrf->traks_rd[i]);
806   }
807   return NULL;
808 }
809
810 static void
811 trak_recov_data_add_sample (TrakRecovData * trak, TrakBufferEntryInfo * b)
812 {
813   trak->duration += b->nsamples * b->delta;
814   atom_stbl_add_samples (&trak->stbl, b->nsamples, b->delta, b->size,
815       b->chunk_offset, b->sync, b->pts_offset);
816 }
817
818 /*
819  * Parses the buffer entries in the MoovRecovFile and matches the inputs
820  * with the data in the MdatRecovFile. Whenever a buffer entry of that
821  * represents 'x' bytes of data, the same amount of data is 'validated' in
822  * the MdatRecovFile and will be inluded in the generated moovie file.
823  */
824 gboolean
825 moov_recov_parse_buffers (MoovRecovFile * moovrf, MdatRecovFile * mdatrf,
826     GError ** err)
827 {
828   TrakBufferEntryInfo entry;
829   TrakRecovData *trak;
830
831   /* we assume both moovrf and mdatrf are at the starting points of their
832    * data reading */
833   while (moov_recov_parse_buffer_entry (moovrf, &entry)) {
834     /* be sure we still have this data in mdat */
835     trak = moov_recov_get_trak (moovrf, entry.track_id);
836     if (trak == NULL) {
837       g_set_error (err, ATOMS_RECOV_QUARK, ATOMS_RECOV_ERR_PARSING,
838           "Invalid trak id found in buffer entry");
839       return FALSE;
840     }
841     if (!mdat_recov_add_sample (mdatrf, entry.size))
842       break;
843     trak_recov_data_add_sample (trak, &entry);
844   }
845   return TRUE;
846 }
847
848 static guint32
849 trak_recov_data_get_trak_atom_size (TrakRecovData * trak)
850 {
851   AtomSTBL *stbl = &trak->stbl;
852   guint64 offset;
853
854   /* write out our stbl child atoms */
855   offset = 0;
856
857   if (!atom_stts_copy_data (&stbl->stts, NULL, NULL, &offset)) {
858     goto fail;
859   }
860   if (atom_array_get_len (&stbl->stss.entries) > 0) {
861     if (!atom_stss_copy_data (&stbl->stss, NULL, NULL, &offset)) {
862       goto fail;
863     }
864   }
865   if (!atom_stsc_copy_data (&stbl->stsc, NULL, NULL, &offset)) {
866     goto fail;
867   }
868   if (!atom_stsz_copy_data (&stbl->stsz, NULL, NULL, &offset)) {
869     goto fail;
870   }
871   if (stbl->ctts) {
872     if (!atom_ctts_copy_data (stbl->ctts, NULL, NULL, &offset)) {
873       goto fail;
874     }
875   }
876   if (!atom_stco64_copy_data (&stbl->stco64, NULL, NULL, &offset)) {
877     goto fail;
878   }
879
880   return trak->trak_size + ((trak->stsd_size + offset + 8) - trak->stbl_size);
881
882 fail:
883   return 0;
884 }
885
886 static guint8 *
887 moov_recov_get_stbl_children_data (MoovRecovFile * moovrf, TrakRecovData * trak,
888     guint64 * p_size)
889 {
890   AtomSTBL *stbl = &trak->stbl;
891   guint8 *buffer;
892   guint64 size;
893   guint64 offset;
894
895   /* write out our stbl child atoms
896    *
897    * Use 1MB as a starting size, *_copy_data functions
898    * will grow the buffer if needed.
899    */
900   size = 1024 * 1024;
901   buffer = g_malloc0 (size);
902   offset = 0;
903
904   if (!atom_stts_copy_data (&stbl->stts, &buffer, &size, &offset)) {
905     goto fail;
906   }
907   if (atom_array_get_len (&stbl->stss.entries) > 0) {
908     if (!atom_stss_copy_data (&stbl->stss, &buffer, &size, &offset)) {
909       goto fail;
910     }
911   }
912   if (!atom_stsc_copy_data (&stbl->stsc, &buffer, &size, &offset)) {
913     goto fail;
914   }
915   if (!atom_stsz_copy_data (&stbl->stsz, &buffer, &size, &offset)) {
916     goto fail;
917   }
918   if (stbl->ctts) {
919     if (!atom_ctts_copy_data (stbl->ctts, &buffer, &size, &offset)) {
920       goto fail;
921     }
922   }
923   if (!atom_stco64_copy_data (&stbl->stco64, &buffer, &size, &offset)) {
924     goto fail;
925   }
926   *p_size = offset;
927   return buffer;
928
929 fail:
930   g_free (buffer);
931   return NULL;
932 }
933
934 static gboolean
935 copy_data_from_file_to_file (FILE * from, guint position, guint size, FILE * to,
936     GError ** err)
937 {
938   guint8 *data = NULL;
939
940   if (fseek (from, position, SEEK_SET) != 0)
941     goto fail;
942   data = g_malloc (size);
943   if (fread (data, 1, size, from) != size) {
944     goto fail;
945   }
946   if (fwrite (data, 1, size, to) != size) {
947     ATOMS_RECOV_OUTPUT_WRITE_ERROR (err);
948     goto fail;
949   }
950
951   g_free (data);
952   return TRUE;
953
954 fail:
955   g_free (data);
956   return FALSE;
957 }
958
959 gboolean
960 moov_recov_write_file (MoovRecovFile * moovrf, MdatRecovFile * mdatrf,
961     FILE * outf, GError ** err, GError ** warn)
962 {
963   guint8 auxdata[16];
964   guint8 *data = NULL;
965   guint8 *prefix_data = NULL;
966   guint8 *mvhd_data = NULL;
967   guint8 *trak_data = NULL;
968   guint32 moov_size = 0;
969   gint i;
970   guint64 stbl_children_size = 0;
971   guint8 *stbl_children = NULL;
972   guint32 longest_duration = 0;
973   guint16 version;
974   guint remaining;
975
976   /* check the version */
977   if (fseek (moovrf->file, 0, SEEK_SET) != 0) {
978     g_set_error (err, ATOMS_RECOV_QUARK, ATOMS_RECOV_ERR_FILE,
979         "Failed to seek to the start of the moov recovery file");
980     goto fail;
981   }
982   if (fread (auxdata, 1, 2, moovrf->file) != 2) {
983     g_set_error (err, ATOMS_RECOV_QUARK, ATOMS_RECOV_ERR_FILE,
984         "Failed to read version from file");
985   }
986
987   version = GST_READ_UINT16_BE (auxdata);
988   if (version != ATOMS_RECOV_FILE_VERSION) {
989     g_set_error (err, ATOMS_RECOV_QUARK, ATOMS_RECOV_ERR_VERSION,
990         "Input file version (%u) is not supported in this version (%u)",
991         version, ATOMS_RECOV_FILE_VERSION);
992     return FALSE;
993   }
994
995   /* write the ftyp */
996   prefix_data = g_malloc (moovrf->prefix_size);
997   if (fread (prefix_data, 1, moovrf->prefix_size,
998           moovrf->file) != moovrf->prefix_size) {
999     g_set_error (err, ATOMS_RECOV_QUARK, ATOMS_RECOV_ERR_FILE,
1000         "Failed to read the ftyp atom from file");
1001     goto fail;
1002   }
1003   if (fwrite (prefix_data, 1, moovrf->prefix_size, outf) != moovrf->prefix_size) {
1004     ATOMS_RECOV_OUTPUT_WRITE_ERROR (err);
1005     goto fail;
1006   }
1007   g_free (prefix_data);
1008   prefix_data = NULL;
1009
1010   /* need to calculate the moov size beforehand to add the offset to
1011    * chunk offset entries */
1012   moov_size += moovrf->mvhd_size + 8;   /* mvhd + moov size + fourcc */
1013   for (i = 0; i < moovrf->num_traks; i++) {
1014     TrakRecovData *trak = &(moovrf->traks_rd[i]);
1015     guint32 duration;           /* in moov's timescale */
1016     guint32 trak_size;
1017
1018     /* convert trak duration to moov's duration */
1019     duration = gst_util_uint64_scale_round (trak->duration, moovrf->timescale,
1020         trak->timescale);
1021
1022     if (duration > longest_duration)
1023       longest_duration = duration;
1024     trak_size = trak_recov_data_get_trak_atom_size (trak);
1025     if (trak_size == 0) {
1026       g_set_error (err, ATOMS_RECOV_QUARK, ATOMS_RECOV_ERR_GENERIC,
1027           "Failed to estimate trak atom size");
1028       goto fail;
1029     }
1030     moov_size += trak_size;
1031   }
1032
1033   /* add chunks offsets */
1034   for (i = 0; i < moovrf->num_traks; i++) {
1035     TrakRecovData *trak = &(moovrf->traks_rd[i]);
1036     /* 8 or 16 for the mdat header */
1037     gint64 offset = moov_size + ftell (outf) + mdatrf->mdat_header_size;
1038     atom_stco64_chunks_set_offset (&trak->stbl.stco64, offset);
1039   }
1040
1041   /* write the moov */
1042   GST_WRITE_UINT32_BE (auxdata, moov_size);
1043   GST_WRITE_UINT32_LE (auxdata + 4, FOURCC_moov);
1044   if (fwrite (auxdata, 1, 8, outf) != 8) {
1045     ATOMS_RECOV_OUTPUT_WRITE_ERROR (err);
1046     goto fail;
1047   }
1048
1049   /* write the mvhd */
1050   mvhd_data = g_malloc (moovrf->mvhd_size);
1051   if (fseek (moovrf->file, moovrf->mvhd_pos, SEEK_SET) != 0)
1052     goto fail;
1053   if (fread (mvhd_data, 1, moovrf->mvhd_size,
1054           moovrf->file) != moovrf->mvhd_size)
1055     goto fail;
1056   GST_WRITE_UINT32_BE (mvhd_data + 20, moovrf->timescale);
1057   GST_WRITE_UINT32_BE (mvhd_data + 24, longest_duration);
1058   if (fwrite (mvhd_data, 1, moovrf->mvhd_size, outf) != moovrf->mvhd_size) {
1059     ATOMS_RECOV_OUTPUT_WRITE_ERROR (err);
1060     goto fail;
1061   }
1062   g_free (mvhd_data);
1063   mvhd_data = NULL;
1064
1065   /* write the traks, this is the tough part because we need to update:
1066    * - stbl atom
1067    * - sizes of atoms from stbl to trak
1068    * - trak duration
1069    */
1070   for (i = 0; i < moovrf->num_traks; i++) {
1071     TrakRecovData *trak = &(moovrf->traks_rd[i]);
1072     guint trak_data_size;
1073     guint32 stbl_new_size;
1074     guint32 minf_new_size;
1075     guint32 mdia_new_size;
1076     guint32 trak_new_size;
1077     guint32 size_diff;
1078     guint32 duration;           /* in moov's timescale */
1079
1080     /* convert trak duration to moov's duration */
1081     duration = gst_util_uint64_scale_round (trak->duration, moovrf->timescale,
1082         trak->timescale);
1083
1084     stbl_children = moov_recov_get_stbl_children_data (moovrf, trak,
1085         &stbl_children_size);
1086     if (stbl_children == NULL)
1087       goto fail;
1088
1089     /* calc the new size of the atoms from stbl to trak in the atoms tree */
1090     stbl_new_size = trak->stsd_size + stbl_children_size + 8;
1091     size_diff = stbl_new_size - trak->stbl_size;
1092     minf_new_size = trak->minf_size + size_diff;
1093     mdia_new_size = trak->mdia_size + size_diff;
1094     trak_new_size = trak->trak_size + size_diff;
1095
1096     if (fseek (moovrf->file, trak->file_offset, SEEK_SET) != 0)
1097       goto fail;
1098     trak_data_size = trak->post_stsd_offset - trak->file_offset;
1099     trak_data = g_malloc (trak_data_size);
1100     if (fread (trak_data, 1, trak_data_size, moovrf->file) != trak_data_size) {
1101       goto fail;
1102     }
1103     /* update the size values in those read atoms before writing */
1104     GST_WRITE_UINT32_BE (trak_data, trak_new_size);
1105     GST_WRITE_UINT32_BE (trak_data + (trak->mdia_file_offset -
1106             trak->file_offset), mdia_new_size);
1107     GST_WRITE_UINT32_BE (trak_data + (trak->minf_file_offset -
1108             trak->file_offset), minf_new_size);
1109     GST_WRITE_UINT32_BE (trak_data + (trak->stbl_file_offset -
1110             trak->file_offset), stbl_new_size);
1111
1112     /* update duration values in tkhd and mdhd */
1113     GST_WRITE_UINT32_BE (trak_data + (trak->tkhd_file_offset -
1114             trak->file_offset) + 28, duration);
1115     GST_WRITE_UINT32_BE (trak_data + (trak->mdhd_file_offset -
1116             trak->file_offset) + 24, trak->duration);
1117
1118     if (fwrite (trak_data, 1, trak_data_size, outf) != trak_data_size) {
1119       ATOMS_RECOV_OUTPUT_WRITE_ERROR (err);
1120       goto fail;
1121     }
1122     if (fwrite (stbl_children, 1, stbl_children_size, outf) !=
1123         stbl_children_size) {
1124       ATOMS_RECOV_OUTPUT_WRITE_ERROR (err);
1125       goto fail;
1126     }
1127
1128     g_free (trak_data);
1129     trak_data = NULL;
1130     g_free (stbl_children);
1131     stbl_children = NULL;
1132
1133     /* Copy the extra atoms after 'minf' */
1134     if (!copy_data_from_file_to_file (moovrf->file, trak->extra_atoms_offset,
1135             trak->extra_atoms_size, outf, err))
1136       goto fail;
1137   }
1138
1139   /* write the mdat */
1140   /* write the header first */
1141   if (mdatrf->mdat_header_size == 16) {
1142     GST_WRITE_UINT32_BE (auxdata, 1);
1143     GST_WRITE_UINT32_LE (auxdata + 4, FOURCC_mdat);
1144     GST_WRITE_UINT64_BE (auxdata + 8, mdatrf->mdat_size);
1145   } else if (mdatrf->mdat_header_size == 8) {
1146     GST_WRITE_UINT32_BE (auxdata, mdatrf->mdat_size);
1147     GST_WRITE_UINT32_LE (auxdata + 4, FOURCC_mdat);
1148   } else {
1149     GST_ERROR ("Unexpected atom size: %u", mdatrf->mdat_header_size);
1150     g_assert_not_reached ();
1151     goto fail;
1152   }
1153
1154   if (fwrite (auxdata, 1, mdatrf->mdat_header_size,
1155           outf) != mdatrf->mdat_header_size) {
1156     ATOMS_RECOV_OUTPUT_WRITE_ERROR (err);
1157     goto fail;
1158   }
1159
1160   /* now read the mdat data and output to the file */
1161   if (fseek (mdatrf->file, mdatrf->mdat_start +
1162           (mdatrf->rawfile ? 0 : mdatrf->mdat_header_size), SEEK_SET) != 0)
1163     goto fail;
1164
1165   remaining = mdatrf->mdat_size - mdatrf->mdat_header_size;
1166   data = g_malloc (MAX_CHUNK_SIZE);
1167   while (!feof (mdatrf->file) && remaining > 0) {
1168     gint read, write, readsize;
1169
1170     readsize = MIN (MAX_CHUNK_SIZE, remaining);
1171
1172     read = fread (data, 1, readsize, mdatrf->file);
1173     write = fwrite (data, 1, read, outf);
1174     remaining -= read;
1175
1176     if (write != read) {
1177       g_set_error (err, ATOMS_RECOV_QUARK, ATOMS_RECOV_ERR_FILE,
1178           "Failed to copy data to output file: %s", g_strerror (errno));
1179       goto fail;
1180     }
1181   }
1182   g_free (data);
1183
1184   if (remaining) {
1185     g_set_error (warn, ATOMS_RECOV_QUARK, ATOMS_RECOV_ERR_FILE,
1186         "Samples in recovery file were not present on headers."
1187         " Bytes lost: %u", remaining);
1188   } else if (!feof (mdatrf->file)) {
1189     g_set_error (warn, ATOMS_RECOV_QUARK, ATOMS_RECOV_ERR_FILE,
1190         "Samples in headers were not found in data file.");
1191     GST_FIXME ("Rewrite mdat size if we reach this to make the file"
1192         " fully correct");
1193   }
1194
1195   return TRUE;
1196
1197 fail:
1198   g_free (stbl_children);
1199   g_free (mvhd_data);
1200   g_free (prefix_data);
1201   g_free (trak_data);
1202   g_free (data);
1203   return FALSE;
1204 }