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