Merge branch 'master' into 0.11
[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., 59 Temple Place - Suite 330,
17  * Boston, MA 02111-1307, 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 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 ATOMS_RECOV_OUTPUT_WRITE_ERROR(err) \
92     g_set_error (err, ATOMS_RECOV_QUARK, ATOMS_RECOV_ERR_FILE, \
93         "Failed to write to output file: %s", g_strerror (errno))
94
95 static gboolean
96 atoms_recov_write_version (FILE * f)
97 {
98   guint8 data[2];
99   GST_WRITE_UINT16_BE (data, ATOMS_RECOV_FILE_VERSION);
100   return fwrite (data, 2, 1, f) == 1;
101 }
102
103 static gboolean
104 atoms_recov_write_ftyp_info (FILE * f, AtomFTYP * ftyp, GstBuffer * prefix)
105 {
106   guint8 *data = NULL;
107   guint64 offset = 0;
108   guint64 size = 0;
109
110   if (prefix) {
111     guint8 *bdata;
112     gsize bsize;
113
114     bdata = gst_buffer_map (prefix, &bsize, NULL, GST_MAP_READ);
115     if (fwrite (bdata, 1, bsize, f) != bsize) {
116       gst_buffer_unmap (prefix, bdata, bsize);
117       return FALSE;
118     }
119     gst_buffer_unmap (prefix, bdata, bsize);
120   }
121   if (!atom_ftyp_copy_data (ftyp, &data, &size, &offset)) {
122     return FALSE;
123   }
124   if (fwrite (data, 1, offset, f) != offset) {
125     g_free (data);
126     return FALSE;
127   }
128   g_free (data);
129   return TRUE;
130 }
131
132 /**
133  * Writes important info on the 'moov' atom (non-trak related)
134  * to be able to recover the moov structure after a crash.
135  *
136  * Currently, it writes the MVHD atom.
137  */
138 static gboolean
139 atoms_recov_write_moov_info (FILE * f, AtomMOOV * moov)
140 {
141   guint8 *data;
142   guint64 size;
143   guint64 offset = 0;
144   guint64 atom_size = 0;
145   gint writen = 0;
146
147   /* likely enough */
148   size = 256;
149   data = g_malloc (size);
150   atom_size = atom_mvhd_copy_data (&moov->mvhd, &data, &size, &offset);
151   if (atom_size > 0)
152     writen = fwrite (data, 1, atom_size, f);
153   g_free (data);
154   return atom_size > 0 && writen == atom_size;
155 }
156
157 /**
158  * Writes the number of traks to the file.
159  * This simply writes a guint32 in BE.
160  */
161 static gboolean
162 atoms_recov_write_traks_number (FILE * f, guint32 traks)
163 {
164   guint8 data[4];
165   GST_WRITE_UINT32_BE (data, traks);
166   return fwrite (data, 4, 1, f) == 1;
167 }
168
169 /**
170  * Writes the moov's timescale to the file
171  * This simply writes a guint32 in BE.
172  */
173 static gboolean
174 atoms_recov_write_moov_timescale (FILE * f, guint32 timescale)
175 {
176   guint8 data[4];
177   GST_WRITE_UINT32_BE (data, timescale);
178   return fwrite (data, 4, 1, f) == 1;
179 }
180
181 /**
182  * Writes the trak atom to the file.
183  */
184 gboolean
185 atoms_recov_write_trak_info (FILE * f, AtomTRAK * trak)
186 {
187   guint8 *data;
188   guint64 size;
189   guint64 offset = 0;
190   guint64 atom_size = 0;
191   gint writen = 0;
192
193   /* buffer is realloced to a larger size if needed */
194   size = 4 * 1024;
195   data = g_malloc (size);
196   atom_size = atom_trak_copy_data (trak, &data, &size, &offset);
197   if (atom_size > 0)
198     writen = fwrite (data, atom_size, 1, f);
199   g_free (data);
200   return atom_size > 0 && writen == atom_size;
201 }
202
203 gboolean
204 atoms_recov_write_trak_samples (FILE * f, AtomTRAK * trak, guint32 nsamples,
205     guint32 delta, guint32 size, guint64 chunk_offset, gboolean sync,
206     gboolean do_pts, gint64 pts_offset)
207 {
208   guint8 data[TRAK_BUFFER_ENTRY_INFO_SIZE];
209   /*
210    * We have to write a TrakBufferEntryInfo
211    */
212   GST_WRITE_UINT32_BE (data + 0, trak->tkhd.track_ID);
213   GST_WRITE_UINT32_BE (data + 4, nsamples);
214   GST_WRITE_UINT32_BE (data + 8, delta);
215   GST_WRITE_UINT32_BE (data + 12, size);
216   GST_WRITE_UINT64_BE (data + 16, chunk_offset);
217   if (sync)
218     GST_WRITE_UINT8 (data + 24, 1);
219   else
220     GST_WRITE_UINT8 (data + 24, 0);
221   if (do_pts) {
222     GST_WRITE_UINT8 (data + 25, 1);
223     GST_WRITE_UINT64_BE (data + 26, pts_offset);
224   } else {
225     GST_WRITE_UINT8 (data + 25, 0);
226     GST_WRITE_UINT64_BE (data + 26, 0);
227   }
228
229   return fwrite (data, 1, TRAK_BUFFER_ENTRY_INFO_SIZE, f) ==
230       TRAK_BUFFER_ENTRY_INFO_SIZE;
231 }
232
233 gboolean
234 atoms_recov_write_headers (FILE * f, AtomFTYP * ftyp, GstBuffer * prefix,
235     AtomMOOV * moov, guint32 timescale, guint32 traks_number)
236 {
237   if (!atoms_recov_write_version (f)) {
238     return FALSE;
239   }
240
241   if (!atoms_recov_write_ftyp_info (f, ftyp, prefix)) {
242     return FALSE;
243   }
244
245   if (!atoms_recov_write_moov_info (f, moov)) {
246     return FALSE;
247   }
248
249   if (!atoms_recov_write_moov_timescale (f, timescale)) {
250     return FALSE;
251   }
252
253   if (!atoms_recov_write_traks_number (f, traks_number)) {
254     return FALSE;
255   }
256
257   return TRUE;
258 }
259
260 static gboolean
261 read_atom_header (FILE * f, guint32 * fourcc, guint32 * size)
262 {
263   guint8 aux[8];
264
265   if (fread (aux, 1, 8, f) != 8)
266     return FALSE;
267   *size = GST_READ_UINT32_BE (aux);
268   *fourcc = GST_READ_UINT32_LE (aux + 4);
269   return TRUE;
270 }
271
272 static gboolean
273 moov_recov_file_parse_prefix (MoovRecovFile * moovrf)
274 {
275   guint32 fourcc;
276   guint32 size;
277   guint32 total_size = 0;
278   if (fseek (moovrf->file, 2, SEEK_SET) != 0)
279     return FALSE;
280   if (!read_atom_header (moovrf->file, &fourcc, &size)) {
281     return FALSE;
282   }
283
284   if (fourcc != FOURCC_ftyp) {
285     /* we might have a prefix here */
286     if (fseek (moovrf->file, size - 8, SEEK_CUR) != 0)
287       return FALSE;
288
289     total_size += size;
290
291     /* now read the ftyp */
292     if (!read_atom_header (moovrf->file, &fourcc, &size))
293       return FALSE;
294   }
295
296   /* this has to be the ftyp */
297   if (fourcc != FOURCC_ftyp)
298     return FALSE;
299   total_size += size;
300   moovrf->prefix_size = total_size;
301   return fseek (moovrf->file, size - 8, SEEK_CUR) == 0;
302 }
303
304 static gboolean
305 moov_recov_file_parse_mvhd (MoovRecovFile * moovrf)
306 {
307   guint32 fourcc;
308   guint32 size;
309   if (!read_atom_header (moovrf->file, &fourcc, &size)) {
310     return FALSE;
311   }
312   /* check for sanity */
313   if (fourcc != FOURCC_mvhd)
314     return FALSE;
315
316   moovrf->mvhd_size = size;
317   moovrf->mvhd_pos = ftell (moovrf->file) - 8;
318
319   /* skip the remaining of the mvhd in the file */
320   return fseek (moovrf->file, size - 8, SEEK_CUR) == 0;
321 }
322
323 static gboolean
324 mdat_recov_file_parse_mdat_start (MdatRecovFile * mdatrf)
325 {
326   guint32 fourcc, size;
327
328   if (!read_atom_header (mdatrf->file, &fourcc, &size)) {
329     return FALSE;
330   }
331   if (size == 1) {
332     mdatrf->mdat_header_size = 16;
333     mdatrf->mdat_size = 16;
334   } else {
335     mdatrf->mdat_header_size = 8;
336     mdatrf->mdat_size = 8;
337   }
338   mdatrf->mdat_start = ftell (mdatrf->file) - 8;
339
340   return fourcc == FOURCC_mdat;
341 }
342
343 MdatRecovFile *
344 mdat_recov_file_create (FILE * file, gboolean datafile, GError ** err)
345 {
346   MdatRecovFile *mrf = g_new0 (MdatRecovFile, 1);
347   guint32 fourcc, size;
348
349   g_return_val_if_fail (file != NULL, NULL);
350
351   mrf->file = file;
352   mrf->rawfile = datafile;
353
354   /* get the file/data length */
355   if (fseek (file, 0, SEEK_END) != 0)
356     goto file_length_error;
357   /* still needs to deduce the mdat header and ftyp size */
358   mrf->data_size = ftell (file);
359   if (mrf->data_size == -1L)
360     goto file_length_error;
361
362   if (fseek (file, 0, SEEK_SET) != 0)
363     goto file_seek_error;
364
365   if (datafile) {
366     /* this file contains no atoms, only raw data to be placed on the mdat
367      * this happens when faststart mode is used */
368     mrf->mdat_start = 0;
369     mrf->mdat_header_size = 16;
370     mrf->mdat_size = 16;
371     return mrf;
372   }
373
374   if (!read_atom_header (file, &fourcc, &size)) {
375     goto parse_error;
376   }
377   if (fourcc != FOURCC_ftyp) {
378     /* this could be a prefix atom, let's skip it and try again */
379     if (fseek (file, size - 8, SEEK_CUR) != 0) {
380       goto file_seek_error;
381     }
382     if (!read_atom_header (file, &fourcc, &size)) {
383       goto parse_error;
384     }
385   }
386
387   if (fourcc != FOURCC_ftyp) {
388     goto parse_error;
389   }
390   if (fseek (file, size - 8, SEEK_CUR) != 0)
391     goto file_seek_error;
392
393   /* we don't parse this if we have a tmpdatafile */
394   if (!mdat_recov_file_parse_mdat_start (mrf)) {
395     g_set_error (err, ATOMS_RECOV_QUARK, ATOMS_RECOV_ERR_PARSING,
396         "Error while parsing mdat atom");
397     goto fail;
398   }
399
400   return mrf;
401
402 parse_error:
403   g_set_error (err, ATOMS_RECOV_QUARK, ATOMS_RECOV_ERR_FILE,
404       "Failed to parse atom");
405   goto fail;
406
407 file_seek_error:
408   g_set_error (err, ATOMS_RECOV_QUARK, ATOMS_RECOV_ERR_FILE,
409       "Failed to seek to start of the file");
410   goto fail;
411
412 file_length_error:
413   g_set_error (err, ATOMS_RECOV_QUARK, ATOMS_RECOV_ERR_FILE,
414       "Failed to determine file size");
415   goto fail;
416
417 fail:
418   mdat_recov_file_free (mrf);
419   return NULL;
420 }
421
422 void
423 mdat_recov_file_free (MdatRecovFile * mrf)
424 {
425   fclose (mrf->file);
426   g_free (mrf);
427 }
428
429 static gboolean
430 moov_recov_parse_num_traks (MoovRecovFile * moovrf)
431 {
432   guint8 traks[4];
433   if (fread (traks, 1, 4, moovrf->file) != 4)
434     return FALSE;
435   moovrf->num_traks = GST_READ_UINT32_BE (traks);
436   return TRUE;
437 }
438
439 static gboolean
440 moov_recov_parse_moov_timescale (MoovRecovFile * moovrf)
441 {
442   guint8 ts[4];
443   if (fread (ts, 1, 4, moovrf->file) != 4)
444     return FALSE;
445   moovrf->timescale = GST_READ_UINT32_BE (ts);
446   return TRUE;
447 }
448
449 static gboolean
450 skip_atom (MoovRecovFile * moovrf, guint32 expected_fourcc)
451 {
452   guint32 size;
453   guint32 fourcc;
454
455   if (!read_atom_header (moovrf->file, &fourcc, &size))
456     return FALSE;
457   if (fourcc != expected_fourcc)
458     return FALSE;
459
460   return (fseek (moovrf->file, size - 8, SEEK_CUR) == 0);
461 }
462
463 static gboolean
464 moov_recov_parse_tkhd (MoovRecovFile * moovrf, TrakRecovData * trakrd)
465 {
466   guint32 size;
467   guint32 fourcc;
468   guint8 data[4];
469
470   /* make sure we are on a tkhd atom */
471   if (!read_atom_header (moovrf->file, &fourcc, &size))
472     return FALSE;
473   if (fourcc != FOURCC_tkhd)
474     return FALSE;
475
476   trakrd->tkhd_file_offset = ftell (moovrf->file) - 8;
477
478   /* move 8 bytes forward to the trak_id pos */
479   if (fseek (moovrf->file, 12, SEEK_CUR) != 0)
480     return FALSE;
481   if (fread (data, 1, 4, moovrf->file) != 4)
482     return FALSE;
483
484   /* advance the rest of tkhd */
485   fseek (moovrf->file, 68, SEEK_CUR);
486
487   trakrd->trak_id = GST_READ_UINT32_BE (data);
488   return TRUE;
489 }
490
491 static gboolean
492 moov_recov_parse_stbl (MoovRecovFile * moovrf, TrakRecovData * trakrd)
493 {
494   guint32 size;
495   guint32 fourcc;
496   guint32 auxsize;
497
498   if (!read_atom_header (moovrf->file, &fourcc, &size))
499     return FALSE;
500   if (fourcc != FOURCC_stbl)
501     return FALSE;
502
503   trakrd->stbl_file_offset = ftell (moovrf->file) - 8;
504   trakrd->stbl_size = size;
505
506   /* skip the stsd */
507   if (!read_atom_header (moovrf->file, &fourcc, &auxsize))
508     return FALSE;
509   if (fourcc != FOURCC_stsd)
510     return FALSE;
511   if (fseek (moovrf->file, auxsize - 8, SEEK_CUR) != 0)
512     return FALSE;
513
514   trakrd->stsd_size = auxsize;
515   trakrd->post_stsd_offset = ftell (moovrf->file);
516
517   /* as this is the last atom we parse, we don't skip forward */
518
519   return TRUE;
520 }
521
522 static gboolean
523 moov_recov_parse_minf (MoovRecovFile * moovrf, TrakRecovData * trakrd)
524 {
525   guint32 size;
526   guint32 fourcc;
527   guint32 auxsize;
528
529   if (!read_atom_header (moovrf->file, &fourcc, &size))
530     return FALSE;
531   if (fourcc != FOURCC_minf)
532     return FALSE;
533
534   trakrd->minf_file_offset = ftell (moovrf->file) - 8;
535   trakrd->minf_size = size;
536
537   /* skip either of vmhd, smhd, hmhd that might follow */
538   if (!read_atom_header (moovrf->file, &fourcc, &auxsize))
539     return FALSE;
540   if (fourcc != FOURCC_vmhd && fourcc != FOURCC_smhd && fourcc != FOURCC_hmhd &&
541       fourcc != FOURCC_gmhd)
542     return FALSE;
543   if (fseek (moovrf->file, auxsize - 8, SEEK_CUR))
544     return FALSE;
545
546   /* skip a possible hdlr and the following dinf */
547   if (!read_atom_header (moovrf->file, &fourcc, &auxsize))
548     return FALSE;
549   if (fourcc == FOURCC_hdlr) {
550     if (fseek (moovrf->file, auxsize - 8, SEEK_CUR))
551       return FALSE;
552     if (!read_atom_header (moovrf->file, &fourcc, &auxsize))
553       return FALSE;
554   }
555   if (fourcc != FOURCC_dinf)
556     return FALSE;
557   if (fseek (moovrf->file, auxsize - 8, SEEK_CUR))
558     return FALSE;
559
560   /* now we are ready to read the stbl */
561   if (!moov_recov_parse_stbl (moovrf, trakrd))
562     return FALSE;
563
564   return TRUE;
565 }
566
567 static gboolean
568 moov_recov_parse_mdhd (MoovRecovFile * moovrf, TrakRecovData * trakrd)
569 {
570   guint32 size;
571   guint32 fourcc;
572   guint8 data[4];
573
574   /* make sure we are on a tkhd atom */
575   if (!read_atom_header (moovrf->file, &fourcc, &size))
576     return FALSE;
577   if (fourcc != FOURCC_mdhd)
578     return FALSE;
579
580   trakrd->mdhd_file_offset = ftell (moovrf->file) - 8;
581
582   /* get the timescale */
583   if (fseek (moovrf->file, 12, SEEK_CUR) != 0)
584     return FALSE;
585   if (fread (data, 1, 4, moovrf->file) != 4)
586     return FALSE;
587   trakrd->timescale = GST_READ_UINT32_BE (data);
588   if (fseek (moovrf->file, 8, SEEK_CUR) != 0)
589     return FALSE;
590   return TRUE;
591 }
592
593 static gboolean
594 moov_recov_parse_mdia (MoovRecovFile * moovrf, TrakRecovData * trakrd)
595 {
596   guint32 size;
597   guint32 fourcc;
598
599   /* make sure we are on a tkhd atom */
600   if (!read_atom_header (moovrf->file, &fourcc, &size))
601     return FALSE;
602   if (fourcc != FOURCC_mdia)
603     return FALSE;
604
605   trakrd->mdia_file_offset = ftell (moovrf->file) - 8;
606   trakrd->mdia_size = size;
607
608   if (!moov_recov_parse_mdhd (moovrf, trakrd))
609     return FALSE;
610
611   if (!skip_atom (moovrf, FOURCC_hdlr))
612     return FALSE;
613   if (!moov_recov_parse_minf (moovrf, trakrd))
614     return FALSE;
615   return TRUE;
616 }
617
618 static gboolean
619 moov_recov_parse_trak (MoovRecovFile * moovrf, TrakRecovData * trakrd)
620 {
621   guint64 offset;
622   guint32 size;
623   guint32 fourcc;
624
625   offset = ftell (moovrf->file);
626   if (offset == -1) {
627     return FALSE;
628   }
629
630   /* make sure we are on a trak atom */
631   if (!read_atom_header (moovrf->file, &fourcc, &size)) {
632     return FALSE;
633   }
634   if (fourcc != FOURCC_trak) {
635     return FALSE;
636   }
637   trakrd->trak_size = size;
638
639   /* now we should have a trak header 'tkhd' */
640   if (!moov_recov_parse_tkhd (moovrf, trakrd))
641     return FALSE;
642
643   /* FIXME add edts handling here and in qtmux, as this is only detected
644    * after buffers start flowing */
645
646   if (!moov_recov_parse_mdia (moovrf, trakrd))
647     return FALSE;
648
649   trakrd->file_offset = offset;
650   /* position after the trak */
651   return fseek (moovrf->file, (long int) offset + size, SEEK_SET) == 0;
652 }
653
654 MoovRecovFile *
655 moov_recov_file_create (FILE * file, GError ** err)
656 {
657   gint i;
658   MoovRecovFile *moovrf = g_new0 (MoovRecovFile, 1);
659
660   g_return_val_if_fail (file != NULL, NULL);
661
662   moovrf->file = file;
663
664   /* look for ftyp and prefix at the start */
665   if (!moov_recov_file_parse_prefix (moovrf)) {
666     g_set_error (err, ATOMS_RECOV_QUARK, ATOMS_RECOV_ERR_PARSING,
667         "Error while parsing prefix atoms");
668     goto fail;
669   }
670
671   /* parse the mvhd */
672   if (!moov_recov_file_parse_mvhd (moovrf)) {
673     g_set_error (err, ATOMS_RECOV_QUARK, ATOMS_RECOV_ERR_PARSING,
674         "Error while parsing mvhd atom");
675     goto fail;
676   }
677
678   if (!moov_recov_parse_moov_timescale (moovrf)) {
679     g_set_error (err, ATOMS_RECOV_QUARK, ATOMS_RECOV_ERR_PARSING,
680         "Error while parsing timescale");
681     goto fail;
682   }
683   if (!moov_recov_parse_num_traks (moovrf)) {
684     g_set_error (err, ATOMS_RECOV_QUARK, ATOMS_RECOV_ERR_PARSING,
685         "Error while parsing parsing number of traks");
686     goto fail;
687   }
688
689   /* sanity check */
690   if (moovrf->num_traks > 1024) {
691     g_set_error (err, ATOMS_RECOV_QUARK, ATOMS_RECOV_ERR_PARSING,
692         "Unsupported number of traks");
693     goto fail;
694   }
695
696   /* init the traks */
697   moovrf->traks_rd = g_new0 (TrakRecovData, moovrf->num_traks);
698   for (i = 0; i < moovrf->num_traks; i++) {
699     atom_stbl_init (&(moovrf->traks_rd[i].stbl));
700   }
701   for (i = 0; i < moovrf->num_traks; i++) {
702     if (!moov_recov_parse_trak (moovrf, &(moovrf->traks_rd[i]))) {
703       g_set_error (err, ATOMS_RECOV_QUARK, ATOMS_RECOV_ERR_PARSING,
704           "Error while parsing trak atom");
705       goto fail;
706     }
707   }
708
709   return moovrf;
710
711 fail:
712   moov_recov_file_free (moovrf);
713   return NULL;
714 }
715
716 void
717 moov_recov_file_free (MoovRecovFile * moovrf)
718 {
719   gint i;
720   fclose (moovrf->file);
721   if (moovrf->traks_rd) {
722     for (i = 0; i < moovrf->num_traks; i++) {
723       atom_stbl_clear (&(moovrf->traks_rd[i].stbl));
724     }
725     g_free (moovrf->traks_rd);
726   }
727   g_free (moovrf);
728 }
729
730 static gboolean
731 moov_recov_parse_buffer_entry (MoovRecovFile * moovrf, TrakBufferEntryInfo * b)
732 {
733   guint8 data[TRAK_BUFFER_ENTRY_INFO_SIZE];
734   gint read;
735
736   read = fread (data, 1, TRAK_BUFFER_ENTRY_INFO_SIZE, moovrf->file);
737   if (read != TRAK_BUFFER_ENTRY_INFO_SIZE)
738     return FALSE;
739
740   b->track_id = GST_READ_UINT32_BE (data);
741   b->nsamples = GST_READ_UINT32_BE (data + 4);
742   b->delta = GST_READ_UINT32_BE (data + 8);
743   b->size = GST_READ_UINT32_BE (data + 12);
744   b->chunk_offset = GST_READ_UINT64_BE (data + 16);
745   b->sync = data[24] != 0;
746   b->do_pts = data[25] != 0;
747   b->pts_offset = GST_READ_UINT64_BE (data + 26);
748   return TRUE;
749 }
750
751 static gboolean
752 mdat_recov_add_sample (MdatRecovFile * mdatrf, guint32 size)
753 {
754   /* test if this data exists */
755   if (mdatrf->mdat_size - mdatrf->mdat_header_size + size > mdatrf->data_size)
756     return FALSE;
757
758   mdatrf->mdat_size += size;
759   return TRUE;
760 }
761
762 static TrakRecovData *
763 moov_recov_get_trak (MoovRecovFile * moovrf, guint32 id)
764 {
765   gint i;
766   for (i = 0; i < moovrf->num_traks; i++) {
767     if (moovrf->traks_rd[i].trak_id == id)
768       return &(moovrf->traks_rd[i]);
769   }
770   return NULL;
771 }
772
773 static void
774 trak_recov_data_add_sample (TrakRecovData * trak, TrakBufferEntryInfo * b)
775 {
776   trak->duration += b->nsamples * b->delta;
777   atom_stbl_add_samples (&trak->stbl, b->nsamples, b->delta, b->size,
778       b->chunk_offset, b->sync, b->pts_offset);
779 }
780
781 /**
782  * Parses the buffer entries in the MoovRecovFile and matches the inputs
783  * with the data in the MdatRecovFile. Whenever a buffer entry of that
784  * represents 'x' bytes of data, the same amount of data is 'validated' in
785  * the MdatRecovFile and will be inluded in the generated moovie file.
786  */
787 gboolean
788 moov_recov_parse_buffers (MoovRecovFile * moovrf, MdatRecovFile * mdatrf,
789     GError ** err)
790 {
791   TrakBufferEntryInfo entry;
792   TrakRecovData *trak;
793
794   /* we assume both moovrf and mdatrf are at the starting points of their
795    * data reading */
796   while (moov_recov_parse_buffer_entry (moovrf, &entry)) {
797     /* be sure we still have this data in mdat */
798     trak = moov_recov_get_trak (moovrf, entry.track_id);
799     if (trak == NULL) {
800       g_set_error (err, ATOMS_RECOV_QUARK, ATOMS_RECOV_ERR_PARSING,
801           "Invalid trak id found in buffer entry");
802       return FALSE;
803     }
804     if (!mdat_recov_add_sample (mdatrf, entry.size))
805       break;
806     trak_recov_data_add_sample (trak, &entry);
807   }
808   return TRUE;
809 }
810
811 static guint32
812 trak_recov_data_get_trak_atom_size (TrakRecovData * trak)
813 {
814   AtomSTBL *stbl = &trak->stbl;
815   guint64 offset;
816
817   /* write out our stbl child atoms */
818   offset = 0;
819
820   if (!atom_stts_copy_data (&stbl->stts, NULL, NULL, &offset)) {
821     goto fail;
822   }
823   if (atom_array_get_len (&stbl->stss.entries) > 0) {
824     if (!atom_stss_copy_data (&stbl->stss, NULL, NULL, &offset)) {
825       goto fail;
826     }
827   }
828   if (!atom_stsc_copy_data (&stbl->stsc, NULL, NULL, &offset)) {
829     goto fail;
830   }
831   if (!atom_stsz_copy_data (&stbl->stsz, NULL, NULL, &offset)) {
832     goto fail;
833   }
834   if (stbl->ctts) {
835     if (!atom_ctts_copy_data (stbl->ctts, NULL, NULL, &offset)) {
836       goto fail;
837     }
838   }
839   if (!atom_stco64_copy_data (&stbl->stco64, NULL, NULL, &offset)) {
840     goto fail;
841   }
842
843   return trak->trak_size + ((trak->stsd_size + offset + 8) - trak->stbl_size);
844
845 fail:
846   return 0;
847 }
848
849 static guint8 *
850 moov_recov_get_stbl_children_data (MoovRecovFile * moovrf, TrakRecovData * trak,
851     guint64 * p_size)
852 {
853   AtomSTBL *stbl = &trak->stbl;
854   guint8 *buffer;
855   guint64 size;
856   guint64 offset;
857
858   /* write out our stbl child atoms
859    *
860    * Use 1MB as a starting size, *_copy_data functions
861    * will grow the buffer if needed.
862    */
863   size = 1024 * 1024;
864   buffer = g_malloc0 (size);
865   offset = 0;
866
867   if (!atom_stts_copy_data (&stbl->stts, &buffer, &size, &offset)) {
868     goto fail;
869   }
870   if (atom_array_get_len (&stbl->stss.entries) > 0) {
871     if (!atom_stss_copy_data (&stbl->stss, &buffer, &size, &offset)) {
872       goto fail;
873     }
874   }
875   if (!atom_stsc_copy_data (&stbl->stsc, &buffer, &size, &offset)) {
876     goto fail;
877   }
878   if (!atom_stsz_copy_data (&stbl->stsz, &buffer, &size, &offset)) {
879     goto fail;
880   }
881   if (stbl->ctts) {
882     if (!atom_ctts_copy_data (stbl->ctts, &buffer, &size, &offset)) {
883       goto fail;
884     }
885   }
886   if (!atom_stco64_copy_data (&stbl->stco64, &buffer, &size, &offset)) {
887     goto fail;
888   }
889   *p_size = offset;
890   return buffer;
891
892 fail:
893   g_free (buffer);
894   return NULL;
895 }
896
897 gboolean
898 moov_recov_write_file (MoovRecovFile * moovrf, MdatRecovFile * mdatrf,
899     FILE * outf, GError ** err)
900 {
901   guint8 auxdata[16];
902   guint8 *data = NULL;
903   guint8 *prefix_data = NULL;
904   guint8 *mvhd_data = NULL;
905   guint8 *trak_data = NULL;
906   guint32 moov_size = 0;
907   gint i;
908   guint64 stbl_children_size = 0;
909   guint8 *stbl_children = NULL;
910   guint32 longest_duration = 0;
911   guint16 version;
912
913   /* check the version */
914   if (fseek (moovrf->file, 0, SEEK_SET) != 0) {
915     g_set_error (err, ATOMS_RECOV_QUARK, ATOMS_RECOV_ERR_FILE,
916         "Failed to seek to the start of the moov recovery file");
917     goto fail;
918   }
919   if (fread (auxdata, 1, 2, moovrf->file) != 2) {
920     g_set_error (err, ATOMS_RECOV_QUARK, ATOMS_RECOV_ERR_FILE,
921         "Failed to read version from file");
922   }
923
924   version = GST_READ_UINT16_BE (auxdata);
925   if (version != ATOMS_RECOV_FILE_VERSION) {
926     g_set_error (err, ATOMS_RECOV_QUARK, ATOMS_RECOV_ERR_VERSION,
927         "Input file version (%u) is not supported in this version (%u)",
928         version, ATOMS_RECOV_FILE_VERSION);
929     return FALSE;
930   }
931
932   /* write the ftyp */
933   prefix_data = g_malloc (moovrf->prefix_size);
934   if (fread (prefix_data, 1, moovrf->prefix_size,
935           moovrf->file) != moovrf->prefix_size) {
936     g_set_error (err, ATOMS_RECOV_QUARK, ATOMS_RECOV_ERR_FILE,
937         "Failed to read the ftyp atom from file");
938     goto fail;
939   }
940   if (fwrite (prefix_data, 1, moovrf->prefix_size, outf) != moovrf->prefix_size) {
941     ATOMS_RECOV_OUTPUT_WRITE_ERROR (err);
942     goto fail;
943   }
944   g_free (prefix_data);
945   prefix_data = NULL;
946
947   /* need to calculate the moov size beforehand to add the offset to
948    * chunk offset entries */
949   moov_size += moovrf->mvhd_size + 8;   /* mvhd + moov size + fourcc */
950   for (i = 0; i < moovrf->num_traks; i++) {
951     TrakRecovData *trak = &(moovrf->traks_rd[i]);
952     guint32 duration;           /* in moov's timescale */
953     guint32 trak_size;
954
955     /* convert trak duration to moov's duration */
956     duration = gst_util_uint64_scale_round (trak->duration, moovrf->timescale,
957         trak->timescale);
958
959     if (duration > longest_duration)
960       longest_duration = duration;
961     trak_size = trak_recov_data_get_trak_atom_size (trak);
962     if (trak_size == 0) {
963       g_set_error (err, ATOMS_RECOV_QUARK, ATOMS_RECOV_ERR_GENERIC,
964           "Failed to estimate trak atom size");
965       goto fail;
966     }
967     moov_size += trak_size;
968   }
969
970   /* add chunks offsets */
971   for (i = 0; i < moovrf->num_traks; i++) {
972     TrakRecovData *trak = &(moovrf->traks_rd[i]);
973     /* 16 for the mdat header */
974     gint64 offset = moov_size + ftell (outf) + 16;
975     atom_stco64_chunks_add_offset (&trak->stbl.stco64, offset);
976   }
977
978   /* write the moov */
979   GST_WRITE_UINT32_BE (auxdata, moov_size);
980   GST_WRITE_UINT32_LE (auxdata + 4, FOURCC_moov);
981   if (fwrite (auxdata, 1, 8, outf) != 8) {
982     ATOMS_RECOV_OUTPUT_WRITE_ERROR (err);
983     goto fail;
984   }
985
986   /* write the mvhd */
987   mvhd_data = g_malloc (moovrf->mvhd_size);
988   if (fseek (moovrf->file, moovrf->mvhd_pos, SEEK_SET) != 0)
989     goto fail;
990   if (fread (mvhd_data, 1, moovrf->mvhd_size,
991           moovrf->file) != moovrf->mvhd_size)
992     goto fail;
993   GST_WRITE_UINT32_BE (mvhd_data + 20, moovrf->timescale);
994   GST_WRITE_UINT32_BE (mvhd_data + 24, longest_duration);
995   if (fwrite (mvhd_data, 1, moovrf->mvhd_size, outf) != moovrf->mvhd_size) {
996     ATOMS_RECOV_OUTPUT_WRITE_ERROR (err);
997     goto fail;
998   }
999   g_free (mvhd_data);
1000   mvhd_data = NULL;
1001
1002   /* write the traks, this is the tough part because we need to update:
1003    * - stbl atom
1004    * - sizes of atoms from stbl to trak
1005    * - trak duration
1006    */
1007   for (i = 0; i < moovrf->num_traks; i++) {
1008     TrakRecovData *trak = &(moovrf->traks_rd[i]);
1009     guint trak_data_size;
1010     guint32 stbl_new_size;
1011     guint32 minf_new_size;
1012     guint32 mdia_new_size;
1013     guint32 trak_new_size;
1014     guint32 size_diff;
1015     guint32 duration;           /* in moov's timescale */
1016
1017     /* convert trak duration to moov's duration */
1018     duration = gst_util_uint64_scale_round (trak->duration, moovrf->timescale,
1019         trak->timescale);
1020
1021     stbl_children = moov_recov_get_stbl_children_data (moovrf, trak,
1022         &stbl_children_size);
1023     if (stbl_children == NULL)
1024       goto fail;
1025
1026     /* calc the new size of the atoms from stbl to trak in the atoms tree */
1027     stbl_new_size = trak->stsd_size + stbl_children_size + 8;
1028     size_diff = stbl_new_size - trak->stbl_size;
1029     minf_new_size = trak->minf_size + size_diff;
1030     mdia_new_size = trak->mdia_size + size_diff;
1031     trak_new_size = trak->trak_size + size_diff;
1032
1033     if (fseek (moovrf->file, trak->file_offset, SEEK_SET) != 0)
1034       goto fail;
1035     trak_data_size = trak->post_stsd_offset - trak->file_offset;
1036     trak_data = g_malloc (trak_data_size);
1037     if (fread (trak_data, 1, trak_data_size, moovrf->file) != trak_data_size) {
1038       goto fail;
1039     }
1040     /* update the size values in those read atoms before writing */
1041     GST_WRITE_UINT32_BE (trak_data, trak_new_size);
1042     GST_WRITE_UINT32_BE (trak_data + (trak->mdia_file_offset -
1043             trak->file_offset), mdia_new_size);
1044     GST_WRITE_UINT32_BE (trak_data + (trak->minf_file_offset -
1045             trak->file_offset), minf_new_size);
1046     GST_WRITE_UINT32_BE (trak_data + (trak->stbl_file_offset -
1047             trak->file_offset), stbl_new_size);
1048
1049     /* update duration values in tkhd and mdhd */
1050     GST_WRITE_UINT32_BE (trak_data + (trak->tkhd_file_offset -
1051             trak->file_offset) + 28, duration);
1052     GST_WRITE_UINT32_BE (trak_data + (trak->mdhd_file_offset -
1053             trak->file_offset) + 24, trak->duration);
1054
1055     if (fwrite (trak_data, 1, trak_data_size, outf) != trak_data_size) {
1056       ATOMS_RECOV_OUTPUT_WRITE_ERROR (err);
1057       goto fail;
1058     }
1059     if (fwrite (stbl_children, 1, stbl_children_size, outf) !=
1060         stbl_children_size) {
1061       ATOMS_RECOV_OUTPUT_WRITE_ERROR (err);
1062       goto fail;
1063     }
1064     g_free (trak_data);
1065     trak_data = NULL;
1066     g_free (stbl_children);
1067     stbl_children = NULL;
1068   }
1069
1070   /* write the mdat */
1071   /* write the header first */
1072   GST_WRITE_UINT32_BE (auxdata, 1);
1073   GST_WRITE_UINT32_LE (auxdata + 4, FOURCC_mdat);
1074   GST_WRITE_UINT64_BE (auxdata + 8, mdatrf->mdat_size);
1075   if (fwrite (auxdata, 1, 16, outf) != 16) {
1076     ATOMS_RECOV_OUTPUT_WRITE_ERROR (err);
1077     goto fail;
1078   }
1079
1080   /* now read the mdat data and output to the file */
1081   if (fseek (mdatrf->file, mdatrf->mdat_start +
1082           (mdatrf->rawfile ? 0 : mdatrf->mdat_header_size), SEEK_SET) != 0)
1083     goto fail;
1084
1085   data = g_malloc (4096);
1086   while (!feof (mdatrf->file)) {
1087     gint read, write;
1088
1089     read = fread (data, 1, 4096, mdatrf->file);
1090     write = fwrite (data, 1, read, outf);
1091
1092     if (write != read) {
1093       g_set_error (err, ATOMS_RECOV_QUARK, ATOMS_RECOV_ERR_FILE,
1094           "Failed to copy data to output file: %s", g_strerror (errno));
1095       goto fail;
1096     }
1097   }
1098   g_free (data);
1099
1100   return TRUE;
1101
1102 fail:
1103   g_free (stbl_children);
1104   g_free (mvhd_data);
1105   g_free (prefix_data);
1106   g_free (trak_data);
1107   g_free (data);
1108   return FALSE;
1109 }