1 /* Quicktime muxer plugin for GStreamer
2 * Copyright (C) 2010 Thiago Santos <thiago.sousa.santos@collabora.co.uk>
3 *
4 * This library is free software; you can redistribute it and/or
5 * modify it under the terms of the GNU Library General Public
6 * License as published by the Free Software Foundation; either
7 * version 2 of the License, or (at your option) any later version.
8 *
9 * This library is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12 * Library General Public License for more details.
13 *
14 * You should have received a copy of the GNU Library General Public
15 * License along with this library; if not, write to the
16 * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
17 * Boston, MA 02110-1301, USA.
18 */
19 /*
20 * Unless otherwise indicated, Source Code is licensed under MIT license.
21 * See further explanation attached in License Statement (distributed in the file
22 * LICENSE).
23 *
24 * Permission is hereby granted, free of charge, to any person obtaining a copy of
25 * this software and associated documentation files (the "Software"), to deal in
26 * the Software without restriction, including without limitation the rights to
27 * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
28 * of the Software, and to permit persons to whom the Software is furnished to do
29 * so, subject to the following conditions:
30 *
31 * The above copyright notice and this permission notice shall be included in all
32 * copies or substantial portions of the Software.
33 *
34 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
35 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
36 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
37 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
38 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
39 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
40 * SOFTWARE.
41 */
42
43
44 /**
45 * SECTION:element-qtmoovrecover
46 * @title: qtmoovrecover
47 * @short_description: Utility element for recovering unfinished quicktime files
48 *
49 * This element recovers quicktime files created with qtmux using the moov
50 * recovery feature.
51 *
52 * ## Example pipelines
53 *
54 * |[
55 * TODO
56 * ]|
57 *
58 */
59
60 #ifdef HAVE_CONFIG_H
61 #include "config.h"
62 #endif
63
64 #include <glib/gstdio.h>
65 #include <gst/gst.h>
66
67 #include "gstisomp4elements.h"
68 #include "gstqtmoovrecover.h"
69
70 GST_DEBUG_CATEGORY_STATIC (gst_qt_moov_recover_debug);
71 #define GST_CAT_DEFAULT gst_qt_moov_recover_debug
72
73 /* QTMoovRecover signals and args */
74 enum
75 {
76 /* FILL ME */
77 LAST_SIGNAL
78 };
79
80 enum
81 {
82 PROP_0,
83 PROP_RECOVERY_INPUT,
84 PROP_BROKEN_INPUT,
85 PROP_FIXED_OUTPUT,
86 PROP_FAST_START_MODE
87 };
88
89 #define gst_qt_moov_recover_parent_class parent_class
90 G_DEFINE_TYPE (GstQTMoovRecover, gst_qt_moov_recover, GST_TYPE_PIPELINE);
91 GST_ELEMENT_REGISTER_DEFINE_WITH_CODE (qtmoovrecover, "qtmoovrecover",
92 GST_RANK_NONE, GST_TYPE_QT_MOOV_RECOVER, isomp4_element_init (plugin));
93
94 /* property functions */
95 static void gst_qt_moov_recover_set_property (GObject * object,
96 guint prop_id, const GValue * value, GParamSpec * pspec);
97 static void gst_qt_moov_recover_get_property (GObject * object,
98 guint prop_id, GValue * value, GParamSpec * pspec);
99
100 static GstStateChangeReturn gst_qt_moov_recover_change_state (GstElement *
101 element, GstStateChange transition);
102
103 static void gst_qt_moov_recover_finalize (GObject * object);
104
105 static void
gst_qt_moov_recover_class_init(GstQTMoovRecoverClass * klass)106 gst_qt_moov_recover_class_init (GstQTMoovRecoverClass * klass)
107 {
108 GObjectClass *gobject_class;
109 GstElementClass *gstelement_class;
110
111 gobject_class = (GObjectClass *) klass;
112 gstelement_class = (GstElementClass *) klass;
113
114 parent_class = g_type_class_peek_parent (klass);
115
116 gobject_class->finalize = gst_qt_moov_recover_finalize;
117 gobject_class->get_property = gst_qt_moov_recover_get_property;
118 gobject_class->set_property = gst_qt_moov_recover_set_property;
119
120 gstelement_class->change_state = gst_qt_moov_recover_change_state;
121
122 g_object_class_install_property (gobject_class, PROP_FIXED_OUTPUT,
123 g_param_spec_string ("fixed-output",
124 "Path to write the fixed file",
125 "Path to write the fixed file to (used as output)",
126 NULL, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
127 g_object_class_install_property (gobject_class, PROP_BROKEN_INPUT,
128 g_param_spec_string ("broken-input",
129 "Path to broken input file",
130 "Path to broken input file. (If qtmux was on faststart mode, this "
131 "file is the faststart file)", NULL,
132 G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
133 g_object_class_install_property (gobject_class, PROP_RECOVERY_INPUT,
134 g_param_spec_string ("recovery-input",
135 "Path to recovery file",
136 "Path to recovery file (used as input)", NULL,
137 G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
138 g_object_class_install_property (gobject_class, PROP_FAST_START_MODE,
139 g_param_spec_boolean ("faststart-mode",
140 "If the broken input is from faststart mode",
141 "If the broken input is from faststart mode",
142 FALSE, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
143
144 GST_DEBUG_CATEGORY_INIT (gst_qt_moov_recover_debug, "qtmoovrecover", 0,
145 "QT Moovie Recover");
146
147 gst_element_class_set_static_metadata (gstelement_class, "QT Moov Recover",
148 "Util", "Recovers unfinished qtmux files",
149 "Thiago Santos <thiago.sousa.santos@collabora.co.uk>");
150 }
151
152 static void
gst_qt_moov_recover_init(GstQTMoovRecover * qtmr)153 gst_qt_moov_recover_init (GstQTMoovRecover * qtmr)
154 {
155 }
156
157 static void
gst_qt_moov_recover_finalize(GObject * object)158 gst_qt_moov_recover_finalize (GObject * object)
159 {
160 G_OBJECT_CLASS (parent_class)->finalize (object);
161 }
162
163 static void
gst_qt_moov_recover_run(void * data)164 gst_qt_moov_recover_run (void *data)
165 {
166 FILE *moovrec = NULL;
167 FILE *mdatinput = NULL;
168 FILE *output = NULL;
169 MdatRecovFile *mdat_recov = NULL;
170 MoovRecovFile *moov_recov = NULL;
171 GstQTMoovRecover *qtmr = GST_QT_MOOV_RECOVER_CAST (data);
172 GError *err = NULL;
173 GError *warn = NULL;
174
175 GST_LOG_OBJECT (qtmr, "Starting task");
176
177 GST_DEBUG_OBJECT (qtmr, "Validating properties");
178 GST_OBJECT_LOCK (qtmr);
179 /* validate properties */
180 if (qtmr->broken_input == NULL) {
181 GST_OBJECT_UNLOCK (qtmr);
182 GST_ELEMENT_ERROR (qtmr, RESOURCE, SETTINGS,
183 ("Please set broken-input property"), (NULL));
184 goto end;
185 }
186 if (qtmr->recovery_input == NULL) {
187 GST_OBJECT_UNLOCK (qtmr);
188 GST_ELEMENT_ERROR (qtmr, RESOURCE, SETTINGS,
189 ("Please set recovery-input property"), (NULL));
190 goto end;
191 }
192 if (qtmr->fixed_output == NULL) {
193 GST_OBJECT_UNLOCK (qtmr);
194 GST_ELEMENT_ERROR (qtmr, RESOURCE, SETTINGS,
195 ("Please set fixed-output property"), (NULL));
196 goto end;
197 }
198
199 GST_DEBUG_OBJECT (qtmr, "Opening input/output files");
200 /* open files */
201 moovrec = g_fopen (qtmr->recovery_input, "rb");
202 if (moovrec == NULL) {
203 GST_OBJECT_UNLOCK (qtmr);
204 GST_ELEMENT_ERROR (qtmr, RESOURCE, OPEN_READ,
205 ("Failed to open recovery-input file"), (NULL));
206 goto end;
207 }
208
209 mdatinput = g_fopen (qtmr->broken_input, "rb");
210 if (mdatinput == NULL) {
211 GST_OBJECT_UNLOCK (qtmr);
212 GST_ELEMENT_ERROR (qtmr, RESOURCE, OPEN_READ,
213 ("Failed to open broken-input file"), (NULL));
214 goto end;
215 }
216 output = g_fopen (qtmr->fixed_output, "wb+");
217 if (output == NULL) {
218 GST_OBJECT_UNLOCK (qtmr);
219 GST_ELEMENT_ERROR (qtmr, RESOURCE, OPEN_READ_WRITE,
220 ("Failed to open fixed-output file"), (NULL));
221 goto end;
222 }
223 GST_OBJECT_UNLOCK (qtmr);
224
225 GST_DEBUG_OBJECT (qtmr, "Parsing input files");
226 /* now create our structures */
227 mdat_recov = mdat_recov_file_create (mdatinput, qtmr->faststart_mode, &err);
228 mdatinput = NULL;
229 if (mdat_recov == NULL) {
230 GST_ELEMENT_ERROR (qtmr, RESOURCE, FAILED,
231 ("Broken file could not be parsed correctly"), (NULL));
232 goto end;
233 }
234 moov_recov = moov_recov_file_create (moovrec, &err);
235 moovrec = NULL;
236 if (moov_recov == NULL) {
237 GST_ELEMENT_ERROR (qtmr, RESOURCE, FAILED,
238 ("Recovery file could not be parsed correctly"), (NULL));
239 goto end;
240 }
241
242 /* now parse the buffers data from moovrec */
243 if (!moov_recov_parse_buffers (moov_recov, mdat_recov, &err)) {
244 goto end;
245 }
246
247 GST_DEBUG_OBJECT (qtmr, "Writing fixed file to output");
248 if (!moov_recov_write_file (moov_recov, mdat_recov, output, &err, &warn)) {
249 goto end;
250 }
251
252 if (warn) {
253 GST_ELEMENT_WARNING (qtmr, RESOURCE, FAILED, ("%s", warn->message), (NULL));
254 g_error_free (warn);
255 }
256
257 /* here means success */
258 GST_DEBUG_OBJECT (qtmr, "Finished successfully, posting EOS");
259 gst_element_post_message (GST_ELEMENT_CAST (qtmr),
260 gst_message_new_eos (GST_OBJECT_CAST (qtmr)));
261
262 end:
263 GST_LOG_OBJECT (qtmr, "Finalizing task");
264 if (err) {
265 GST_ELEMENT_ERROR (qtmr, RESOURCE, FAILED, ("%s", err->message), (NULL));
266 g_error_free (err);
267 }
268
269 if (moov_recov)
270 moov_recov_file_free (moov_recov);
271 if (moovrec)
272 fclose (moovrec);
273
274 if (mdat_recov)
275 mdat_recov_file_free (mdat_recov);
276 if (mdatinput)
277 fclose (mdatinput);
278
279 if (output)
280 fclose (output);
281 GST_LOG_OBJECT (qtmr, "Leaving task");
282 gst_task_stop (qtmr->task);
283 }
284
285 static void
gst_qt_moov_recover_get_property(GObject * object,guint prop_id,GValue * value,GParamSpec * pspec)286 gst_qt_moov_recover_get_property (GObject * object,
287 guint prop_id, GValue * value, GParamSpec * pspec)
288 {
289 GstQTMoovRecover *qtmr = GST_QT_MOOV_RECOVER_CAST (object);
290
291 GST_OBJECT_LOCK (qtmr);
292 switch (prop_id) {
293 case PROP_FAST_START_MODE:
294 g_value_set_boolean (value, qtmr->faststart_mode);
295 break;
296 case PROP_BROKEN_INPUT:
297 g_value_set_string (value, qtmr->broken_input);
298 break;
299 case PROP_RECOVERY_INPUT:
300 g_value_set_string (value, qtmr->recovery_input);
301 break;
302 case PROP_FIXED_OUTPUT:
303 g_value_set_string (value, qtmr->fixed_output);
304 break;
305 default:
306 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
307 break;
308 }
309 GST_OBJECT_UNLOCK (qtmr);
310 }
311
312 static void
gst_qt_moov_recover_set_property(GObject * object,guint prop_id,const GValue * value,GParamSpec * pspec)313 gst_qt_moov_recover_set_property (GObject * object,
314 guint prop_id, const GValue * value, GParamSpec * pspec)
315 {
316 GstQTMoovRecover *qtmr = GST_QT_MOOV_RECOVER_CAST (object);
317
318 GST_OBJECT_LOCK (qtmr);
319 switch (prop_id) {
320 case PROP_FAST_START_MODE:
321 qtmr->faststart_mode = g_value_get_boolean (value);
322 break;
323 case PROP_BROKEN_INPUT:
324 g_free (qtmr->broken_input);
325 qtmr->broken_input = g_value_dup_string (value);
326 break;
327 case PROP_RECOVERY_INPUT:
328 g_free (qtmr->recovery_input);
329 qtmr->recovery_input = g_value_dup_string (value);
330 break;
331 case PROP_FIXED_OUTPUT:
332 g_free (qtmr->fixed_output);
333 qtmr->fixed_output = g_value_dup_string (value);
334 break;
335 default:
336 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
337 break;
338 }
339 GST_OBJECT_UNLOCK (qtmr);
340 }
341
342 static GstStateChangeReturn
gst_qt_moov_recover_change_state(GstElement * element,GstStateChange transition)343 gst_qt_moov_recover_change_state (GstElement * element,
344 GstStateChange transition)
345 {
346 GstStateChangeReturn ret;
347 GstQTMoovRecover *qtmr = GST_QT_MOOV_RECOVER_CAST (element);
348
349 switch (transition) {
350 case GST_STATE_CHANGE_NULL_TO_READY:
351 qtmr->task = gst_task_new (gst_qt_moov_recover_run, qtmr, NULL);
352 g_rec_mutex_init (&qtmr->task_mutex);
353 gst_task_set_lock (qtmr->task, &qtmr->task_mutex);
354 break;
355 case GST_STATE_CHANGE_PAUSED_TO_PLAYING:
356 gst_task_start (qtmr->task);
357 break;
358 default:
359 break;
360 }
361
362 ret = GST_ELEMENT_CLASS (parent_class)->change_state (element, transition);
363
364 switch (transition) {
365 case GST_STATE_CHANGE_PLAYING_TO_PAUSED:
366 gst_task_stop (qtmr->task);
367 gst_task_join (qtmr->task);
368 break;
369 case GST_STATE_CHANGE_READY_TO_NULL:
370 if (gst_task_get_state (qtmr->task) != GST_TASK_STOPPED)
371 GST_ERROR ("task %p should be stopped by now", qtmr->task);
372 gst_object_unref (qtmr->task);
373 qtmr->task = NULL;
374 g_rec_mutex_clear (&qtmr->task_mutex);
375 break;
376 default:
377 break;
378 }
379 return ret;
380 }
381