/* Quicktime muxer plugin for GStreamer * Copyright (C) 2010 Thiago Santos * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public * License along with this library; if not, write to the * Free Software Foundation, Inc., 59 Temple Place - Suite 330, * Boston, MA 02111-1307, USA. */ /* * Unless otherwise indicated, Source Code is licensed under MIT license. * See further explanation attached in License Statement (distributed in the file * LICENSE). * * Permission is hereby granted, free of charge, to any person obtaining a copy of * this software and associated documentation files (the "Software"), to deal in * the Software without restriction, including without limitation the rights to * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies * of the Software, and to permit persons to whom the Software is furnished to do * so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in all * copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ /** * This module contains functions for serializing partial information from * a mux in progress (by qtmux elements). This enables reconstruction of the * moov box if a crash happens and thus recovering the movie file. * * Usage: * 1) pipeline: ...yourelements ! qtmux moov-recovery-file=path.mrf ! \ * filesink location=moovie.mov * * 2) CRASH! * * 3) gst-launch qtmoovrecover recovery-input=path.mrf broken-input=moovie.mov \ fixed-output=recovered.mov * * 4) (Hopefully) enjoy recovered.mov. * * --- Recovery file layout --- * 1) Version (a guint16) * 2) Prefix atom (if present) * 3) ftyp atom * 4) MVHD atom (without timescale/duration set) * 5) moovie timescale * 6) number of traks * 7) list of trak atoms (stbl data is ignored, except for the stsd atom) * 8) Buffers metadata (metadata that is relevant to the container) * Buffers metadata are stored in the order they are added to the mdat, * each entre has a fixed size and is stored in BE. booleans are stored * as a single byte where 0 means false, otherwise is true. * Metadata: * - guint32 track_id; * - guint32 nsamples; * - guint32 delta; * - guint32 size; * - guint64 chunk_offset; * - gboolean sync; * - gboolean do_pts; * - guint64 pts_offset; (always present, ignored if do_pts is false) * * The mdat file might contain ftyp and then mdat, in case this is the faststart * temporary file there is no ftyp and no mdat header, only the buffers data. * * Notes about recovery file layout: We still don't store tags nor EDTS data. * * IMPORTANT: this is still at a experimental state. */ #include "atomsrecovery.h" #define ATOMS_RECOV_OUTPUT_WRITE_ERROR(err) \ g_set_error (err, ATOMS_RECOV_QUARK, ATOMS_RECOV_ERR_FILE, \ "Failed to write to output file: %s", g_strerror (errno)) static gboolean atoms_recov_write_version (FILE * f) { guint8 data[2]; GST_WRITE_UINT16_BE (data, ATOMS_RECOV_FILE_VERSION); return fwrite (data, 2, 1, f) == 1; } static gboolean atoms_recov_write_ftyp_info (FILE * f, AtomFTYP * ftyp, GstBuffer * prefix) { guint8 *data = NULL; guint64 offset = 0; guint64 size = 0; if (prefix) { if (fwrite (GST_BUFFER_DATA (prefix), 1, GST_BUFFER_SIZE (prefix), f) != GST_BUFFER_SIZE (prefix)) { return FALSE; } } if (!atom_ftyp_copy_data (ftyp, &data, &size, &offset)) { return FALSE; } if (fwrite (data, 1, offset, f) != offset) { g_free (data); return FALSE; } g_free (data); return TRUE; } /** * Writes important info on the 'moov' atom (non-trak related) * to be able to recover the moov structure after a crash. * * Currently, it writes the MVHD atom. */ static gboolean atoms_recov_write_moov_info (FILE * f, AtomMOOV * moov) { guint8 *data; guint64 size; guint64 offset = 0; guint64 atom_size = 0; gint writen = 0; /* likely enough */ size = 256; data = g_malloc (size); atom_size = atom_mvhd_copy_data (&moov->mvhd, &data, &size, &offset); if (atom_size > 0) writen = fwrite (data, 1, atom_size, f); g_free (data); return atom_size > 0 && writen == atom_size; } /** * Writes the number of traks to the file. * This simply writes a guint32 in BE. */ static gboolean atoms_recov_write_traks_number (FILE * f, guint32 traks) { guint8 data[4]; GST_WRITE_UINT32_BE (data, traks); return fwrite (data, 4, 1, f) == 1; } /** * Writes the moov's timescale to the file * This simply writes a guint32 in BE. */ static gboolean atoms_recov_write_moov_timescale (FILE * f, guint32 timescale) { guint8 data[4]; GST_WRITE_UINT32_BE (data, timescale); return fwrite (data, 4, 1, f) == 1; } /** * Writes the trak atom to the file. */ gboolean atoms_recov_write_trak_info (FILE * f, AtomTRAK * trak) { guint8 *data; guint64 size; guint64 offset = 0; guint64 atom_size = 0; gint writen = 0; /* buffer is realloced to a larger size if needed */ size = 4 * 1024; data = g_malloc (size); atom_size = atom_trak_copy_data (trak, &data, &size, &offset); if (atom_size > 0) writen = fwrite (data, atom_size, 1, f); g_free (data); return atom_size > 0 && writen == atom_size; } gboolean atoms_recov_write_trak_samples (FILE * f, AtomTRAK * trak, guint32 nsamples, guint32 delta, guint32 size, guint64 chunk_offset, gboolean sync, gboolean do_pts, gint64 pts_offset) { guint8 data[TRAK_BUFFER_ENTRY_INFO_SIZE]; /* * We have to write a TrakBufferEntryInfo */ GST_WRITE_UINT32_BE (data + 0, trak->tkhd.track_ID); GST_WRITE_UINT32_BE (data + 4, nsamples); GST_WRITE_UINT32_BE (data + 8, delta); GST_WRITE_UINT32_BE (data + 12, size); GST_WRITE_UINT64_BE (data + 16, chunk_offset); if (sync) GST_WRITE_UINT8 (data + 24, 1); else GST_WRITE_UINT8 (data + 24, 0); if (do_pts) { GST_WRITE_UINT8 (data + 25, 1); GST_WRITE_UINT64_BE (data + 26, pts_offset); } else { GST_WRITE_UINT8 (data + 25, 0); GST_WRITE_UINT64_BE (data + 26, 0); } return fwrite (data, 1, TRAK_BUFFER_ENTRY_INFO_SIZE, f) == TRAK_BUFFER_ENTRY_INFO_SIZE; } gboolean atoms_recov_write_headers (FILE * f, AtomFTYP * ftyp, GstBuffer * prefix, AtomMOOV * moov, guint32 timescale, guint32 traks_number) { if (!atoms_recov_write_version (f)) { return FALSE; } if (!atoms_recov_write_ftyp_info (f, ftyp, prefix)) { return FALSE; } if (!atoms_recov_write_moov_info (f, moov)) { return FALSE; } if (!atoms_recov_write_moov_timescale (f, timescale)) { return FALSE; } if (!atoms_recov_write_traks_number (f, traks_number)) { return FALSE; } return TRUE; } static gboolean read_atom_header (FILE * f, guint32 * fourcc, guint32 * size) { guint8 aux[8]; if (fread (aux, 1, 8, f) != 8) return FALSE; *size = GST_READ_UINT32_BE (aux); *fourcc = GST_READ_UINT32_LE (aux + 4); return TRUE; } static gboolean moov_recov_file_parse_prefix (MoovRecovFile * moovrf) { guint32 fourcc; guint32 size; guint32 total_size = 0; if (fseek (moovrf->file, 2, SEEK_SET) != 0) return FALSE; if (!read_atom_header (moovrf->file, &fourcc, &size)) { return FALSE; } if (fourcc != FOURCC_ftyp) { /* we might have a prefix here */ if (fseek (moovrf->file, size - 8, SEEK_CUR) != 0) return FALSE; total_size += size; /* now read the ftyp */ if (!read_atom_header (moovrf->file, &fourcc, &size)) return FALSE; } /* this has to be the ftyp */ if (fourcc != FOURCC_ftyp) return FALSE; total_size += size; moovrf->prefix_size = total_size; return fseek (moovrf->file, size - 8, SEEK_CUR) == 0; } static gboolean moov_recov_file_parse_mvhd (MoovRecovFile * moovrf) { guint32 fourcc; guint32 size; if (!read_atom_header (moovrf->file, &fourcc, &size)) { return FALSE; } /* check for sanity */ if (fourcc != FOURCC_mvhd) return FALSE; moovrf->mvhd_size = size; moovrf->mvhd_pos = ftell (moovrf->file) - 8; /* skip the remaining of the mvhd in the file */ return fseek (moovrf->file, size - 8, SEEK_CUR) == 0; } static gboolean mdat_recov_file_parse_mdat_start (MdatRecovFile * mdatrf) { guint32 fourcc, size; if (!read_atom_header (mdatrf->file, &fourcc, &size)) { return FALSE; } if (size == 1) { mdatrf->mdat_header_size = 16; mdatrf->mdat_size = 16; } else { mdatrf->mdat_header_size = 8; mdatrf->mdat_size = 8; } mdatrf->mdat_start = ftell (mdatrf->file) - 8; return fourcc == FOURCC_mdat; } MdatRecovFile * mdat_recov_file_create (FILE * file, gboolean datafile, GError ** err) { MdatRecovFile *mrf = g_new0 (MdatRecovFile, 1); guint32 fourcc, size; g_return_val_if_fail (file != NULL, NULL); mrf->file = file; mrf->rawfile = datafile; /* get the file/data length */ if (fseek (file, 0, SEEK_END) != 0) goto file_length_error; /* still needs to deduce the mdat header and ftyp size */ mrf->data_size = ftell (file); if (mrf->data_size == -1L) goto file_length_error; if (fseek (file, 0, SEEK_SET) != 0) goto file_seek_error; if (datafile) { /* this file contains no atoms, only raw data to be placed on the mdat * this happens when faststart mode is used */ mrf->mdat_start = 0; mrf->mdat_header_size = 16; mrf->mdat_size = 16; return mrf; } if (!read_atom_header (file, &fourcc, &size)) { goto parse_error; } if (fourcc != FOURCC_ftyp) { /* this could be a prefix atom, let's skip it and try again */ if (fseek (file, size - 8, SEEK_CUR) != 0) { goto file_seek_error; } if (!read_atom_header (file, &fourcc, &size)) { goto parse_error; } } if (fourcc != FOURCC_ftyp) { goto parse_error; } if (fseek (file, size - 8, SEEK_CUR) != 0) goto file_seek_error; /* we don't parse this if we have a tmpdatafile */ if (!mdat_recov_file_parse_mdat_start (mrf)) { g_set_error (err, ATOMS_RECOV_QUARK, ATOMS_RECOV_ERR_PARSING, "Error while parsing mdat atom"); goto fail; } return mrf; parse_error: g_set_error (err, ATOMS_RECOV_QUARK, ATOMS_RECOV_ERR_FILE, "Failed to parse atom"); goto fail; file_seek_error: g_set_error (err, ATOMS_RECOV_QUARK, ATOMS_RECOV_ERR_FILE, "Failed to seek to start of the file"); goto fail; file_length_error: g_set_error (err, ATOMS_RECOV_QUARK, ATOMS_RECOV_ERR_FILE, "Failed to determine file size"); goto fail; fail: mdat_recov_file_free (mrf); return NULL; } void mdat_recov_file_free (MdatRecovFile * mrf) { fclose (mrf->file); g_free (mrf); } static gboolean moov_recov_parse_num_traks (MoovRecovFile * moovrf) { guint8 traks[4]; if (fread (traks, 1, 4, moovrf->file) != 4) return FALSE; moovrf->num_traks = GST_READ_UINT32_BE (traks); return TRUE; } static gboolean moov_recov_parse_moov_timescale (MoovRecovFile * moovrf) { guint8 ts[4]; if (fread (ts, 1, 4, moovrf->file) != 4) return FALSE; moovrf->timescale = GST_READ_UINT32_BE (ts); return TRUE; } static gboolean skip_atom (MoovRecovFile * moovrf, guint32 expected_fourcc) { guint32 size; guint32 fourcc; if (!read_atom_header (moovrf->file, &fourcc, &size)) return FALSE; if (fourcc != expected_fourcc) return FALSE; return (fseek (moovrf->file, size - 8, SEEK_CUR) == 0); } static gboolean moov_recov_parse_tkhd (MoovRecovFile * moovrf, TrakRecovData * trakrd) { guint32 size; guint32 fourcc; guint8 data[4]; /* make sure we are on a tkhd atom */ if (!read_atom_header (moovrf->file, &fourcc, &size)) return FALSE; if (fourcc != FOURCC_tkhd) return FALSE; trakrd->tkhd_file_offset = ftell (moovrf->file) - 8; /* move 8 bytes forward to the trak_id pos */ if (fseek (moovrf->file, 12, SEEK_CUR) != 0) return FALSE; if (fread (data, 1, 4, moovrf->file) != 4) return FALSE; /* advance the rest of tkhd */ fseek (moovrf->file, 68, SEEK_CUR); trakrd->trak_id = GST_READ_UINT32_BE (data); return TRUE; } static gboolean moov_recov_parse_stbl (MoovRecovFile * moovrf, TrakRecovData * trakrd) { guint32 size; guint32 fourcc; guint32 auxsize; if (!read_atom_header (moovrf->file, &fourcc, &size)) return FALSE; if (fourcc != FOURCC_stbl) return FALSE; trakrd->stbl_file_offset = ftell (moovrf->file) - 8; trakrd->stbl_size = size; /* skip the stsd */ if (!read_atom_header (moovrf->file, &fourcc, &auxsize)) return FALSE; if (fourcc != FOURCC_stsd) return FALSE; if (fseek (moovrf->file, auxsize - 8, SEEK_CUR) != 0) return FALSE; trakrd->stsd_size = auxsize; trakrd->post_stsd_offset = ftell (moovrf->file); /* as this is the last atom we parse, we don't skip forward */ return TRUE; } static gboolean moov_recov_parse_minf (MoovRecovFile * moovrf, TrakRecovData * trakrd) { guint32 size; guint32 fourcc; guint32 auxsize; if (!read_atom_header (moovrf->file, &fourcc, &size)) return FALSE; if (fourcc != FOURCC_minf) return FALSE; trakrd->minf_file_offset = ftell (moovrf->file) - 8; trakrd->minf_size = size; /* skip either of vmhd, smhd, hmhd that might follow */ if (!read_atom_header (moovrf->file, &fourcc, &auxsize)) return FALSE; if (fourcc != FOURCC_vmhd && fourcc != FOURCC_smhd && fourcc != FOURCC_hmhd && fourcc != FOURCC_gmhd) return FALSE; if (fseek (moovrf->file, auxsize - 8, SEEK_CUR)) return FALSE; /* skip a possible hdlr and the following dinf */ if (!read_atom_header (moovrf->file, &fourcc, &auxsize)) return FALSE; if (fourcc == FOURCC_hdlr) { if (fseek (moovrf->file, auxsize - 8, SEEK_CUR)) return FALSE; if (!read_atom_header (moovrf->file, &fourcc, &auxsize)) return FALSE; } if (fourcc != FOURCC_dinf) return FALSE; if (fseek (moovrf->file, auxsize - 8, SEEK_CUR)) return FALSE; /* now we are ready to read the stbl */ if (!moov_recov_parse_stbl (moovrf, trakrd)) return FALSE; return TRUE; } static gboolean moov_recov_parse_mdhd (MoovRecovFile * moovrf, TrakRecovData * trakrd) { guint32 size; guint32 fourcc; guint8 data[4]; /* make sure we are on a tkhd atom */ if (!read_atom_header (moovrf->file, &fourcc, &size)) return FALSE; if (fourcc != FOURCC_mdhd) return FALSE; trakrd->mdhd_file_offset = ftell (moovrf->file) - 8; /* get the timescale */ if (fseek (moovrf->file, 12, SEEK_CUR) != 0) return FALSE; if (fread (data, 1, 4, moovrf->file) != 4) return FALSE; trakrd->timescale = GST_READ_UINT32_BE (data); if (fseek (moovrf->file, 8, SEEK_CUR) != 0) return FALSE; return TRUE; } static gboolean moov_recov_parse_mdia (MoovRecovFile * moovrf, TrakRecovData * trakrd) { guint32 size; guint32 fourcc; /* make sure we are on a tkhd atom */ if (!read_atom_header (moovrf->file, &fourcc, &size)) return FALSE; if (fourcc != FOURCC_mdia) return FALSE; trakrd->mdia_file_offset = ftell (moovrf->file) - 8; trakrd->mdia_size = size; if (!moov_recov_parse_mdhd (moovrf, trakrd)) return FALSE; if (!skip_atom (moovrf, FOURCC_hdlr)) return FALSE; if (!moov_recov_parse_minf (moovrf, trakrd)) return FALSE; return TRUE; } static gboolean moov_recov_parse_trak (MoovRecovFile * moovrf, TrakRecovData * trakrd) { guint64 offset; guint32 size; guint32 fourcc; offset = ftell (moovrf->file); if (offset == -1) { return FALSE; } /* make sure we are on a trak atom */ if (!read_atom_header (moovrf->file, &fourcc, &size)) { return FALSE; } if (fourcc != FOURCC_trak) { return FALSE; } trakrd->trak_size = size; /* now we should have a trak header 'tkhd' */ if (!moov_recov_parse_tkhd (moovrf, trakrd)) return FALSE; /* FIXME add edts handling here and in qtmux, as this is only detected * after buffers start flowing */ if (!moov_recov_parse_mdia (moovrf, trakrd)) return FALSE; trakrd->file_offset = offset; /* position after the trak */ return fseek (moovrf->file, (long int) offset + size, SEEK_SET) == 0; } MoovRecovFile * moov_recov_file_create (FILE * file, GError ** err) { gint i; MoovRecovFile *moovrf = g_new0 (MoovRecovFile, 1); g_return_val_if_fail (file != NULL, NULL); moovrf->file = file; /* look for ftyp and prefix at the start */ if (!moov_recov_file_parse_prefix (moovrf)) { g_set_error (err, ATOMS_RECOV_QUARK, ATOMS_RECOV_ERR_PARSING, "Error while parsing prefix atoms"); goto fail; } /* parse the mvhd */ if (!moov_recov_file_parse_mvhd (moovrf)) { g_set_error (err, ATOMS_RECOV_QUARK, ATOMS_RECOV_ERR_PARSING, "Error while parsing mvhd atom"); goto fail; } if (!moov_recov_parse_moov_timescale (moovrf)) { g_set_error (err, ATOMS_RECOV_QUARK, ATOMS_RECOV_ERR_PARSING, "Error while parsing timescale"); goto fail; } if (!moov_recov_parse_num_traks (moovrf)) { g_set_error (err, ATOMS_RECOV_QUARK, ATOMS_RECOV_ERR_PARSING, "Error while parsing parsing number of traks"); goto fail; } /* sanity check */ if (moovrf->num_traks > 1024) { g_set_error (err, ATOMS_RECOV_QUARK, ATOMS_RECOV_ERR_PARSING, "Unsupported number of traks"); goto fail; } /* init the traks */ moovrf->traks_rd = g_new0 (TrakRecovData, moovrf->num_traks); for (i = 0; i < moovrf->num_traks; i++) { atom_stbl_init (&(moovrf->traks_rd[i].stbl)); } for (i = 0; i < moovrf->num_traks; i++) { if (!moov_recov_parse_trak (moovrf, &(moovrf->traks_rd[i]))) { g_set_error (err, ATOMS_RECOV_QUARK, ATOMS_RECOV_ERR_PARSING, "Error while parsing trak atom"); goto fail; } } return moovrf; fail: moov_recov_file_free (moovrf); return NULL; } void moov_recov_file_free (MoovRecovFile * moovrf) { gint i; fclose (moovrf->file); if (moovrf->traks_rd) { for (i = 0; i < moovrf->num_traks; i++) { atom_stbl_clear (&(moovrf->traks_rd[i].stbl)); } g_free (moovrf->traks_rd); } g_free (moovrf); } static gboolean moov_recov_parse_buffer_entry (MoovRecovFile * moovrf, TrakBufferEntryInfo * b) { guint8 data[TRAK_BUFFER_ENTRY_INFO_SIZE]; gint read; read = fread (data, 1, TRAK_BUFFER_ENTRY_INFO_SIZE, moovrf->file); if (read != TRAK_BUFFER_ENTRY_INFO_SIZE) return FALSE; b->track_id = GST_READ_UINT32_BE (data); b->nsamples = GST_READ_UINT32_BE (data + 4); b->delta = GST_READ_UINT32_BE (data + 8); b->size = GST_READ_UINT32_BE (data + 12); b->chunk_offset = GST_READ_UINT64_BE (data + 16); b->sync = data[24] != 0; b->do_pts = data[25] != 0; b->pts_offset = GST_READ_UINT64_BE (data + 26); return TRUE; } static gboolean mdat_recov_add_sample (MdatRecovFile * mdatrf, guint32 size) { /* test if this data exists */ if (mdatrf->mdat_size - mdatrf->mdat_header_size + size > mdatrf->data_size) return FALSE; mdatrf->mdat_size += size; return TRUE; } static TrakRecovData * moov_recov_get_trak (MoovRecovFile * moovrf, guint32 id) { gint i; for (i = 0; i < moovrf->num_traks; i++) { if (moovrf->traks_rd[i].trak_id == id) return &(moovrf->traks_rd[i]); } return NULL; } static void trak_recov_data_add_sample (TrakRecovData * trak, TrakBufferEntryInfo * b) { trak->duration += b->nsamples * b->delta; atom_stbl_add_samples (&trak->stbl, b->nsamples, b->delta, b->size, b->chunk_offset, b->sync, b->pts_offset); } /** * Parses the buffer entries in the MoovRecovFile and matches the inputs * with the data in the MdatRecovFile. Whenever a buffer entry of that * represents 'x' bytes of data, the same amount of data is 'validated' in * the MdatRecovFile and will be inluded in the generated moovie file. */ gboolean moov_recov_parse_buffers (MoovRecovFile * moovrf, MdatRecovFile * mdatrf, GError ** err) { TrakBufferEntryInfo entry; TrakRecovData *trak; /* we assume both moovrf and mdatrf are at the starting points of their * data reading */ while (moov_recov_parse_buffer_entry (moovrf, &entry)) { /* be sure we still have this data in mdat */ trak = moov_recov_get_trak (moovrf, entry.track_id); if (trak == NULL) { g_set_error (err, ATOMS_RECOV_QUARK, ATOMS_RECOV_ERR_PARSING, "Invalid trak id found in buffer entry"); return FALSE; } if (!mdat_recov_add_sample (mdatrf, entry.size)) break; trak_recov_data_add_sample (trak, &entry); } return TRUE; } static guint32 trak_recov_data_get_trak_atom_size (TrakRecovData * trak) { AtomSTBL *stbl = &trak->stbl; guint64 offset; /* write out our stbl child atoms */ offset = 0; if (!atom_stts_copy_data (&stbl->stts, NULL, NULL, &offset)) { goto fail; } if (atom_array_get_len (&stbl->stss.entries) > 0) { if (!atom_stss_copy_data (&stbl->stss, NULL, NULL, &offset)) { goto fail; } } if (!atom_stsc_copy_data (&stbl->stsc, NULL, NULL, &offset)) { goto fail; } if (!atom_stsz_copy_data (&stbl->stsz, NULL, NULL, &offset)) { goto fail; } if (stbl->ctts) { if (!atom_ctts_copy_data (stbl->ctts, NULL, NULL, &offset)) { goto fail; } } if (!atom_stco64_copy_data (&stbl->stco64, NULL, NULL, &offset)) { goto fail; } return trak->trak_size + ((trak->stsd_size + offset + 8) - trak->stbl_size); fail: return 0; } static guint8 * moov_recov_get_stbl_children_data (MoovRecovFile * moovrf, TrakRecovData * trak, guint64 * p_size) { AtomSTBL *stbl = &trak->stbl; guint8 *buffer; guint64 size; guint64 offset; /* write out our stbl child atoms * * Use 1MB as a starting size, *_copy_data functions * will grow the buffer if needed. */ size = 1024 * 1024; buffer = g_malloc0 (size); offset = 0; if (!atom_stts_copy_data (&stbl->stts, &buffer, &size, &offset)) { goto fail; } if (atom_array_get_len (&stbl->stss.entries) > 0) { if (!atom_stss_copy_data (&stbl->stss, &buffer, &size, &offset)) { goto fail; } } if (!atom_stsc_copy_data (&stbl->stsc, &buffer, &size, &offset)) { goto fail; } if (!atom_stsz_copy_data (&stbl->stsz, &buffer, &size, &offset)) { goto fail; } if (stbl->ctts) { if (!atom_ctts_copy_data (stbl->ctts, &buffer, &size, &offset)) { goto fail; } } if (!atom_stco64_copy_data (&stbl->stco64, &buffer, &size, &offset)) { goto fail; } *p_size = offset; return buffer; fail: g_free (buffer); return NULL; } gboolean moov_recov_write_file (MoovRecovFile * moovrf, MdatRecovFile * mdatrf, FILE * outf, GError ** err) { guint8 auxdata[16]; guint8 *data = NULL; guint8 *prefix_data = NULL; guint8 *mvhd_data = NULL; guint8 *trak_data = NULL; guint32 moov_size = 0; gint i; guint64 stbl_children_size = 0; guint8 *stbl_children = NULL; guint32 longest_duration = 0; guint16 version; /* check the version */ if (fseek (moovrf->file, 0, SEEK_SET) != 0) { g_set_error (err, ATOMS_RECOV_QUARK, ATOMS_RECOV_ERR_FILE, "Failed to seek to the start of the moov recovery file"); goto fail; } if (fread (auxdata, 1, 2, moovrf->file) != 2) { g_set_error (err, ATOMS_RECOV_QUARK, ATOMS_RECOV_ERR_FILE, "Failed to read version from file"); } version = GST_READ_UINT16_BE (auxdata); if (version != ATOMS_RECOV_FILE_VERSION) { g_set_error (err, ATOMS_RECOV_QUARK, ATOMS_RECOV_ERR_VERSION, "Input file version (%u) is not supported in this version (%u)", version, ATOMS_RECOV_FILE_VERSION); return FALSE; } /* write the ftyp */ prefix_data = g_malloc (moovrf->prefix_size); if (fread (prefix_data, 1, moovrf->prefix_size, moovrf->file) != moovrf->prefix_size) { g_set_error (err, ATOMS_RECOV_QUARK, ATOMS_RECOV_ERR_FILE, "Failed to read the ftyp atom from file"); goto fail; } if (fwrite (prefix_data, 1, moovrf->prefix_size, outf) != moovrf->prefix_size) { ATOMS_RECOV_OUTPUT_WRITE_ERROR (err); goto fail; } g_free (prefix_data); prefix_data = NULL; /* need to calculate the moov size beforehand to add the offset to * chunk offset entries */ moov_size += moovrf->mvhd_size + 8; /* mvhd + moov size + fourcc */ for (i = 0; i < moovrf->num_traks; i++) { TrakRecovData *trak = &(moovrf->traks_rd[i]); guint32 duration; /* in moov's timescale */ guint32 trak_size; /* convert trak duration to moov's duration */ duration = gst_util_uint64_scale_round (trak->duration, moovrf->timescale, trak->timescale); if (duration > longest_duration) longest_duration = duration; trak_size = trak_recov_data_get_trak_atom_size (trak); if (trak_size == 0) { g_set_error (err, ATOMS_RECOV_QUARK, ATOMS_RECOV_ERR_GENERIC, "Failed to estimate trak atom size"); goto fail; } moov_size += trak_size; } /* add chunks offsets */ for (i = 0; i < moovrf->num_traks; i++) { TrakRecovData *trak = &(moovrf->traks_rd[i]); /* 16 for the mdat header */ gint64 offset = moov_size + ftell (outf) + 16; atom_stco64_chunks_add_offset (&trak->stbl.stco64, offset); } /* write the moov */ GST_WRITE_UINT32_BE (auxdata, moov_size); GST_WRITE_UINT32_LE (auxdata + 4, FOURCC_moov); if (fwrite (auxdata, 1, 8, outf) != 8) { ATOMS_RECOV_OUTPUT_WRITE_ERROR (err); goto fail; } /* write the mvhd */ mvhd_data = g_malloc (moovrf->mvhd_size); if (fseek (moovrf->file, moovrf->mvhd_pos, SEEK_SET) != 0) goto fail; if (fread (mvhd_data, 1, moovrf->mvhd_size, moovrf->file) != moovrf->mvhd_size) goto fail; GST_WRITE_UINT32_BE (mvhd_data + 20, moovrf->timescale); GST_WRITE_UINT32_BE (mvhd_data + 24, longest_duration); if (fwrite (mvhd_data, 1, moovrf->mvhd_size, outf) != moovrf->mvhd_size) { ATOMS_RECOV_OUTPUT_WRITE_ERROR (err); goto fail; } g_free (mvhd_data); mvhd_data = NULL; /* write the traks, this is the tough part because we need to update: * - stbl atom * - sizes of atoms from stbl to trak * - trak duration */ for (i = 0; i < moovrf->num_traks; i++) { TrakRecovData *trak = &(moovrf->traks_rd[i]); guint trak_data_size; guint32 stbl_new_size; guint32 minf_new_size; guint32 mdia_new_size; guint32 trak_new_size; guint32 size_diff; guint32 duration; /* in moov's timescale */ /* convert trak duration to moov's duration */ duration = gst_util_uint64_scale_round (trak->duration, moovrf->timescale, trak->timescale); stbl_children = moov_recov_get_stbl_children_data (moovrf, trak, &stbl_children_size); if (stbl_children == NULL) goto fail; /* calc the new size of the atoms from stbl to trak in the atoms tree */ stbl_new_size = trak->stsd_size + stbl_children_size + 8; size_diff = stbl_new_size - trak->stbl_size; minf_new_size = trak->minf_size + size_diff; mdia_new_size = trak->mdia_size + size_diff; trak_new_size = trak->trak_size + size_diff; if (fseek (moovrf->file, trak->file_offset, SEEK_SET) != 0) goto fail; trak_data_size = trak->post_stsd_offset - trak->file_offset; trak_data = g_malloc (trak_data_size); if (fread (trak_data, 1, trak_data_size, moovrf->file) != trak_data_size) { goto fail; } /* update the size values in those read atoms before writing */ GST_WRITE_UINT32_BE (trak_data, trak_new_size); GST_WRITE_UINT32_BE (trak_data + (trak->mdia_file_offset - trak->file_offset), mdia_new_size); GST_WRITE_UINT32_BE (trak_data + (trak->minf_file_offset - trak->file_offset), minf_new_size); GST_WRITE_UINT32_BE (trak_data + (trak->stbl_file_offset - trak->file_offset), stbl_new_size); /* update duration values in tkhd and mdhd */ GST_WRITE_UINT32_BE (trak_data + (trak->tkhd_file_offset - trak->file_offset) + 28, duration); GST_WRITE_UINT32_BE (trak_data + (trak->mdhd_file_offset - trak->file_offset) + 24, trak->duration); if (fwrite (trak_data, 1, trak_data_size, outf) != trak_data_size) { ATOMS_RECOV_OUTPUT_WRITE_ERROR (err); goto fail; } if (fwrite (stbl_children, 1, stbl_children_size, outf) != stbl_children_size) { ATOMS_RECOV_OUTPUT_WRITE_ERROR (err); goto fail; } g_free (trak_data); trak_data = NULL; g_free (stbl_children); stbl_children = NULL; } /* write the mdat */ /* write the header first */ GST_WRITE_UINT32_BE (auxdata, 1); GST_WRITE_UINT32_LE (auxdata + 4, FOURCC_mdat); GST_WRITE_UINT64_BE (auxdata + 8, mdatrf->mdat_size); if (fwrite (auxdata, 1, 16, outf) != 16) { ATOMS_RECOV_OUTPUT_WRITE_ERROR (err); goto fail; } /* now read the mdat data and output to the file */ if (fseek (mdatrf->file, mdatrf->mdat_start + (mdatrf->rawfile ? 0 : mdatrf->mdat_header_size), SEEK_SET) != 0) goto fail; data = g_malloc (4096); while (!feof (mdatrf->file)) { gint read, write; read = fread (data, 1, 4096, mdatrf->file); write = fwrite (data, 1, read, outf); if (write != read) { g_set_error (err, ATOMS_RECOV_QUARK, ATOMS_RECOV_ERR_FILE, "Failed to copy data to output file: %s", g_strerror (errno)); goto fail; } } g_free (data); return TRUE; fail: g_free (stbl_children); g_free (mvhd_data); g_free (prefix_data); g_free (trak_data); g_free (data); return FALSE; }