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