xref: /third_party/ffmpeg/libavformat/imf_cpl.c (revision cabdff1a)
1/*
2 * This file is part of FFmpeg.
3 *
4 * FFmpeg is free software; you can redistribute it and/or
5 * modify it under the terms of the GNU Lesser General Public
6 * License as published by the Free Software Foundation; either
7 * version 2.1 of the License, or (at your option) any later version.
8 *
9 * FFmpeg 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 * Lesser General Public License for more details.
13 *
14 * You should have received a copy of the GNU Lesser General Public
15 * License along with FFmpeg; if not, write to the Free Software
16 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
17 */
18
19/*
20 *
21 * Copyright (c) Sandflow Consulting LLC
22 *
23 * Redistribution and use in source and binary forms, with or without
24 * modification, are permitted provided that the following conditions are met:
25 *
26 * * Redistributions of source code must retain the above copyright notice, this
27 *   list of conditions and the following disclaimer.
28 * * Redistributions in binary form must reproduce the above copyright notice,
29 *   this list of conditions and the following disclaimer in the documentation
30 *   and/or other materials provided with the distribution.
31 *
32 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
33 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
34 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
35 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
36 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
37 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
38 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
39 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
40 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
41 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
42 * POSSIBILITY OF SUCH DAMAGE.
43 */
44
45/**
46 * Implements IMP CPL processing
47 *
48 * @author Pierre-Anthony Lemieux
49 * @file
50 * @ingroup lavu_imf
51 */
52
53#include "imf.h"
54#include "libavformat/mxf.h"
55#include "libavutil/bprint.h"
56#include "libavutil/error.h"
57#include <libxml/parser.h>
58
59xmlNodePtr ff_imf_xml_get_child_element_by_name(xmlNodePtr parent, const char *name_utf8)
60{
61    xmlNodePtr cur_element;
62
63    cur_element = xmlFirstElementChild(parent);
64    while (cur_element) {
65        if (xmlStrcmp(cur_element->name, name_utf8) == 0)
66            return cur_element;
67
68        cur_element = xmlNextElementSibling(cur_element);
69    }
70    return NULL;
71}
72
73int ff_imf_xml_read_uuid(xmlNodePtr element, AVUUID uuid)
74{
75    xmlChar *element_text = NULL;
76    int ret = 0;
77
78    element_text = xmlNodeListGetString(element->doc, element->xmlChildrenNode, 1);
79    if (!element_text)
80        return AVERROR_INVALIDDATA;
81    ret = av_uuid_urn_parse(element_text, uuid);
82    if (ret) {
83        av_log(NULL, AV_LOG_ERROR, "Invalid UUID\n");
84        ret = AVERROR_INVALIDDATA;
85    }
86    xmlFree(element_text);
87
88    return ret;
89}
90
91int ff_imf_xml_read_rational(xmlNodePtr element, AVRational *rational)
92{
93    xmlChar *element_text = NULL;
94    int ret = 0;
95
96    element_text = xmlNodeListGetString(element->doc, element->xmlChildrenNode, 1);
97    if (element_text == NULL || sscanf(element_text, "%i %i", &rational->num, &rational->den) != 2) {
98        av_log(NULL, AV_LOG_ERROR, "Invalid rational number\n");
99        ret = AVERROR_INVALIDDATA;
100    }
101    xmlFree(element_text);
102
103    return ret;
104}
105
106int ff_imf_xml_read_uint32(xmlNodePtr element, uint32_t *number)
107{
108    xmlChar *element_text = NULL;
109    int ret = 0;
110
111    element_text = xmlNodeListGetString(element->doc, element->xmlChildrenNode, 1);
112    if (element_text == NULL || sscanf(element_text, "%" PRIu32, number) != 1) {
113        av_log(NULL, AV_LOG_ERROR, "Invalid unsigned 32-bit integer");
114        ret = AVERROR_INVALIDDATA;
115    }
116    xmlFree(element_text);
117
118    return ret;
119}
120
121static void imf_base_virtual_track_init(FFIMFBaseVirtualTrack *track)
122{
123    memset(track->id_uuid, 0, sizeof(track->id_uuid));
124}
125
126static void imf_marker_virtual_track_init(FFIMFMarkerVirtualTrack *track)
127{
128    imf_base_virtual_track_init((FFIMFBaseVirtualTrack *)track);
129    track->resource_count = 0;
130    track->resources = NULL;
131}
132
133static void imf_trackfile_virtual_track_init(FFIMFTrackFileVirtualTrack *track)
134{
135    imf_base_virtual_track_init((FFIMFBaseVirtualTrack *)track);
136    track->resource_count = 0;
137    track->resources_alloc_sz = 0;
138    track->resources = NULL;
139}
140
141static void imf_base_resource_init(FFIMFBaseResource *rsrc)
142{
143    rsrc->duration = 0;
144    rsrc->edit_rate = av_make_q(0, 1);
145    rsrc->entry_point = 0;
146    rsrc->repeat_count = 1;
147}
148
149static void imf_marker_resource_init(FFIMFMarkerResource *rsrc)
150{
151    imf_base_resource_init((FFIMFBaseResource *)rsrc);
152    rsrc->marker_count = 0;
153    rsrc->markers = NULL;
154}
155
156static void imf_marker_init(FFIMFMarker *marker)
157{
158    marker->label_utf8 = NULL;
159    marker->offset = 0;
160    marker->scope_utf8 = NULL;
161}
162
163static void imf_trackfile_resource_init(FFIMFTrackFileResource *rsrc)
164{
165    imf_base_resource_init((FFIMFBaseResource *)rsrc);
166    memset(rsrc->track_file_uuid, 0, sizeof(rsrc->track_file_uuid));
167}
168
169static int fill_content_title(xmlNodePtr cpl_element, FFIMFCPL *cpl)
170{
171    xmlNodePtr element = NULL;
172
173    if (!(element = ff_imf_xml_get_child_element_by_name(cpl_element, "ContentTitle"))) {
174        av_log(NULL, AV_LOG_ERROR, "ContentTitle element not found in the IMF CPL\n");
175        return AVERROR_INVALIDDATA;
176    }
177    cpl->content_title_utf8 = xmlNodeListGetString(cpl_element->doc,
178                                                   element->xmlChildrenNode,
179                                                   1);
180    if (!cpl->content_title_utf8)
181        cpl->content_title_utf8 = xmlStrdup("");
182    if (!cpl->content_title_utf8)
183        return AVERROR(ENOMEM);
184
185    return 0;
186}
187
188static int fill_edit_rate(xmlNodePtr cpl_element, FFIMFCPL *cpl)
189{
190    xmlNodePtr element = NULL;
191
192    if (!(element = ff_imf_xml_get_child_element_by_name(cpl_element, "EditRate"))) {
193        av_log(NULL, AV_LOG_ERROR, "EditRate element not found in the IMF CPL\n");
194        return AVERROR_INVALIDDATA;
195    }
196
197    return ff_imf_xml_read_rational(element, &cpl->edit_rate);
198}
199
200static int fill_id(xmlNodePtr cpl_element, FFIMFCPL *cpl)
201{
202    xmlNodePtr element = NULL;
203
204    if (!(element = ff_imf_xml_get_child_element_by_name(cpl_element, "Id"))) {
205        av_log(NULL, AV_LOG_ERROR, "Id element not found in the IMF CPL\n");
206        return AVERROR_INVALIDDATA;
207    }
208
209    return ff_imf_xml_read_uuid(element, cpl->id_uuid);
210}
211
212static int fill_marker(xmlNodePtr marker_elem, FFIMFMarker *marker)
213{
214    xmlNodePtr element = NULL;
215    int ret = 0;
216
217    /* read Offset */
218    if (!(element = ff_imf_xml_get_child_element_by_name(marker_elem, "Offset"))) {
219        av_log(NULL, AV_LOG_ERROR, "Offset element not found in a Marker\n");
220        return AVERROR_INVALIDDATA;
221    }
222    if ((ret = ff_imf_xml_read_uint32(element, &marker->offset)))
223        return ret;
224
225    /* read Label and Scope */
226    if (!(element = ff_imf_xml_get_child_element_by_name(marker_elem, "Label"))) {
227        av_log(NULL, AV_LOG_ERROR, "Label element not found in a Marker\n");
228        return AVERROR_INVALIDDATA;
229    }
230    if (!(marker->label_utf8 = xmlNodeListGetString(element->doc, element->xmlChildrenNode, 1))) {
231        av_log(NULL, AV_LOG_ERROR, "Empty Label element found in a Marker\n");
232        return AVERROR_INVALIDDATA;
233    }
234    if (!(marker->scope_utf8 = xmlGetNoNsProp(element, "scope"))) {
235        marker->scope_utf8
236            = xmlCharStrdup("http://www.smpte-ra.org/schemas/2067-3/2013#standard-markers");
237        if (!marker->scope_utf8) {
238            xmlFree(marker->label_utf8);
239            return AVERROR(ENOMEM);
240        }
241    }
242
243    return ret;
244}
245
246static int fill_base_resource(xmlNodePtr resource_elem, FFIMFBaseResource *resource, FFIMFCPL *cpl)
247{
248    xmlNodePtr element = NULL;
249    int ret = 0;
250
251    /* read EditRate */
252    if (!(element = ff_imf_xml_get_child_element_by_name(resource_elem, "EditRate"))) {
253        resource->edit_rate = cpl->edit_rate;
254    } else if ((ret = ff_imf_xml_read_rational(element, &resource->edit_rate))) {
255        av_log(NULL, AV_LOG_ERROR, "Invalid EditRate element found in a Resource\n");
256        return ret;
257    }
258
259    /* read EntryPoint */
260    if ((element = ff_imf_xml_get_child_element_by_name(resource_elem, "EntryPoint"))) {
261        if ((ret = ff_imf_xml_read_uint32(element, &resource->entry_point))) {
262            av_log(NULL, AV_LOG_ERROR, "Invalid EntryPoint element found in a Resource\n");
263            return ret;
264        }
265    } else {
266        resource->entry_point = 0;
267    }
268
269    /* read IntrinsicDuration */
270    if (!(element = ff_imf_xml_get_child_element_by_name(resource_elem, "IntrinsicDuration"))) {
271        av_log(NULL, AV_LOG_ERROR, "IntrinsicDuration element missing from Resource\n");
272        return AVERROR_INVALIDDATA;
273    }
274    if ((ret = ff_imf_xml_read_uint32(element, &resource->duration))) {
275        av_log(NULL, AV_LOG_ERROR, "Invalid IntrinsicDuration element found in a Resource\n");
276        return ret;
277    }
278    resource->duration -= resource->entry_point;
279
280    /* read SourceDuration */
281    if ((element = ff_imf_xml_get_child_element_by_name(resource_elem, "SourceDuration"))) {
282        if ((ret = ff_imf_xml_read_uint32(element, &resource->duration))) {
283            av_log(NULL, AV_LOG_ERROR, "SourceDuration element missing from Resource\n");
284            return ret;
285        }
286    }
287
288    /* read RepeatCount */
289    if ((element = ff_imf_xml_get_child_element_by_name(resource_elem, "RepeatCount")))
290        ret = ff_imf_xml_read_uint32(element, &resource->repeat_count);
291
292    return ret;
293}
294
295static int fill_trackfile_resource(xmlNodePtr tf_resource_elem,
296                                   FFIMFTrackFileResource *tf_resource,
297                                   FFIMFCPL *cpl)
298{
299    xmlNodePtr element = NULL;
300    int ret = 0;
301
302    if ((ret = fill_base_resource(tf_resource_elem, (FFIMFBaseResource *)tf_resource, cpl)))
303        return ret;
304
305    /* read TrackFileId */
306    if ((element = ff_imf_xml_get_child_element_by_name(tf_resource_elem, "TrackFileId"))) {
307        if ((ret = ff_imf_xml_read_uuid(element, tf_resource->track_file_uuid))) {
308            av_log(NULL, AV_LOG_ERROR, "Invalid TrackFileId element found in Resource\n");
309            return ret;
310        }
311    } else {
312        av_log(NULL, AV_LOG_ERROR, "TrackFileId element missing from Resource\n");
313        return AVERROR_INVALIDDATA;
314    }
315
316    return ret;
317}
318
319static int fill_marker_resource(xmlNodePtr marker_resource_elem,
320                                FFIMFMarkerResource *marker_resource,
321                                FFIMFCPL *cpl)
322{
323    xmlNodePtr element = NULL;
324    int ret = 0;
325
326    if ((ret = fill_base_resource(marker_resource_elem, (FFIMFBaseResource *)marker_resource, cpl)))
327        return ret;
328
329    /* read markers */
330    element = xmlFirstElementChild(marker_resource_elem);
331    while (element) {
332        if (xmlStrcmp(element->name, "Marker") == 0) {
333            void *tmp;
334
335            if (marker_resource->marker_count == UINT32_MAX)
336                return AVERROR(ENOMEM);
337            tmp = av_realloc_array(marker_resource->markers,
338                                   marker_resource->marker_count + 1,
339                                   sizeof(FFIMFMarker));
340            if (!tmp)
341                return AVERROR(ENOMEM);
342            marker_resource->markers = tmp;
343
344            imf_marker_init(&marker_resource->markers[marker_resource->marker_count]);
345            ret = fill_marker(element,
346                              &marker_resource->markers[marker_resource->marker_count]);
347            marker_resource->marker_count++;
348            if (ret)
349                return ret;
350        }
351
352        element = xmlNextElementSibling(element);
353    }
354
355    return ret;
356}
357
358static int push_marker_sequence(xmlNodePtr marker_sequence_elem, FFIMFCPL *cpl)
359{
360    int ret = 0;
361    AVUUID uuid;
362    xmlNodePtr resource_list_elem = NULL;
363    xmlNodePtr resource_elem = NULL;
364    xmlNodePtr track_id_elem = NULL;
365    unsigned long resource_elem_count;
366    void *tmp;
367
368    /* read TrackID element */
369    if (!(track_id_elem = ff_imf_xml_get_child_element_by_name(marker_sequence_elem, "TrackId"))) {
370        av_log(NULL, AV_LOG_ERROR, "TrackId element missing from Sequence\n");
371        return AVERROR_INVALIDDATA;
372    }
373    if (ff_imf_xml_read_uuid(track_id_elem, uuid)) {
374        av_log(NULL, AV_LOG_ERROR, "Invalid TrackId element found in Sequence\n");
375        return AVERROR_INVALIDDATA;
376    }
377    av_log(NULL,
378           AV_LOG_DEBUG,
379           "Processing IMF CPL Marker Sequence for Virtual Track " AV_PRI_UUID "\n",
380           AV_UUID_ARG(uuid));
381
382    /* create main marker virtual track if it does not exist */
383    if (!cpl->main_markers_track) {
384        cpl->main_markers_track = av_malloc(sizeof(FFIMFMarkerVirtualTrack));
385        if (!cpl->main_markers_track)
386            return AVERROR(ENOMEM);
387        imf_marker_virtual_track_init(cpl->main_markers_track);
388        av_uuid_copy(cpl->main_markers_track->base.id_uuid, uuid);
389
390    } else if (!av_uuid_equal(cpl->main_markers_track->base.id_uuid, uuid)) {
391        av_log(NULL, AV_LOG_ERROR, "Multiple marker virtual tracks were found\n");
392        return AVERROR_INVALIDDATA;
393    }
394
395    /* process resources */
396    resource_list_elem = ff_imf_xml_get_child_element_by_name(marker_sequence_elem, "ResourceList");
397    if (!resource_list_elem)
398        return 0;
399
400    resource_elem_count = xmlChildElementCount(resource_list_elem);
401    if (resource_elem_count > UINT32_MAX
402        || cpl->main_markers_track->resource_count > UINT32_MAX - resource_elem_count)
403        return AVERROR(ENOMEM);
404    tmp = av_realloc_array(cpl->main_markers_track->resources,
405                           cpl->main_markers_track->resource_count + resource_elem_count,
406                           sizeof(FFIMFMarkerResource));
407    if (!tmp) {
408        av_log(NULL, AV_LOG_ERROR, "Cannot allocate Marker Resources\n");
409        return AVERROR(ENOMEM);
410    }
411    cpl->main_markers_track->resources = tmp;
412
413    resource_elem = xmlFirstElementChild(resource_list_elem);
414    while (resource_elem) {
415        imf_marker_resource_init(&cpl->main_markers_track->resources[cpl->main_markers_track->resource_count]);
416        ret = fill_marker_resource(resource_elem,
417                                   &cpl->main_markers_track->resources[cpl->main_markers_track->resource_count],
418                                   cpl);
419        cpl->main_markers_track->resource_count++;
420        if (ret)
421            return ret;
422
423        resource_elem = xmlNextElementSibling(resource_elem);
424    }
425
426    return ret;
427}
428
429static int has_stereo_resources(xmlNodePtr element)
430{
431    if (xmlStrcmp(element->name, "Left") == 0 || xmlStrcmp(element->name, "Right") == 0)
432        return 1;
433
434    element = xmlFirstElementChild(element);
435    while (element) {
436        if (has_stereo_resources(element))
437            return 1;
438
439        element = xmlNextElementSibling(element);
440    }
441
442    return 0;
443}
444
445static int push_main_audio_sequence(xmlNodePtr audio_sequence_elem, FFIMFCPL *cpl)
446{
447    int ret = 0;
448    AVUUID uuid;
449    xmlNodePtr resource_list_elem = NULL;
450    xmlNodePtr resource_elem = NULL;
451    xmlNodePtr track_id_elem = NULL;
452    unsigned long resource_elem_count;
453    FFIMFTrackFileVirtualTrack *vt = NULL;
454    void *tmp;
455
456    /* read TrackID element */
457    if (!(track_id_elem = ff_imf_xml_get_child_element_by_name(audio_sequence_elem, "TrackId"))) {
458        av_log(NULL, AV_LOG_ERROR, "TrackId element missing from audio sequence\n");
459        return AVERROR_INVALIDDATA;
460    }
461    if ((ret = ff_imf_xml_read_uuid(track_id_elem, uuid))) {
462        av_log(NULL, AV_LOG_ERROR, "Invalid TrackId element found in audio sequence\n");
463        return ret;
464    }
465    av_log(NULL,
466           AV_LOG_DEBUG,
467           "Processing IMF CPL Audio Sequence for Virtual Track " AV_PRI_UUID "\n",
468           AV_UUID_ARG(uuid));
469
470    /* get the main audio virtual track corresponding to the sequence */
471    for (uint32_t i = 0; i < cpl->main_audio_track_count; i++) {
472        if (av_uuid_equal(cpl->main_audio_tracks[i].base.id_uuid, uuid)) {
473            vt = &cpl->main_audio_tracks[i];
474            break;
475        }
476    }
477
478    /* create a main audio virtual track if none exists for the sequence */
479    if (!vt) {
480        if (cpl->main_audio_track_count == UINT32_MAX)
481            return AVERROR(ENOMEM);
482        tmp = av_realloc_array(cpl->main_audio_tracks,
483                               cpl->main_audio_track_count + 1,
484                               sizeof(FFIMFTrackFileVirtualTrack));
485        if (!tmp)
486            return AVERROR(ENOMEM);
487
488        cpl->main_audio_tracks = tmp;
489        vt = &cpl->main_audio_tracks[cpl->main_audio_track_count];
490        imf_trackfile_virtual_track_init(vt);
491        cpl->main_audio_track_count++;
492        av_uuid_copy(vt->base.id_uuid, uuid);
493    }
494
495    /* process resources */
496    resource_list_elem = ff_imf_xml_get_child_element_by_name(audio_sequence_elem, "ResourceList");
497    if (!resource_list_elem)
498        return 0;
499
500    resource_elem_count = xmlChildElementCount(resource_list_elem);
501    if (resource_elem_count > UINT32_MAX
502        || vt->resource_count > UINT32_MAX - resource_elem_count)
503        return AVERROR(ENOMEM);
504    tmp = av_fast_realloc(vt->resources,
505                          &vt->resources_alloc_sz,
506                          (vt->resource_count + resource_elem_count)
507                              * sizeof(FFIMFTrackFileResource));
508    if (!tmp) {
509        av_log(NULL, AV_LOG_ERROR, "Cannot allocate Main Audio Resources\n");
510        return AVERROR(ENOMEM);
511    }
512    vt->resources = tmp;
513
514    resource_elem = xmlFirstElementChild(resource_list_elem);
515    while (resource_elem) {
516        imf_trackfile_resource_init(&vt->resources[vt->resource_count]);
517        ret = fill_trackfile_resource(resource_elem,
518                                      &vt->resources[vt->resource_count],
519                                      cpl);
520        if (ret)
521            av_log(NULL, AV_LOG_ERROR, "Invalid Resource\n");
522        else
523            vt->resource_count++;
524
525        resource_elem = xmlNextElementSibling(resource_elem);
526    }
527
528    return ret;
529}
530
531static int push_main_image_2d_sequence(xmlNodePtr image_sequence_elem, FFIMFCPL *cpl)
532{
533    int ret = 0;
534    AVUUID uuid;
535    xmlNodePtr resource_list_elem = NULL;
536    xmlNodePtr resource_elem = NULL;
537    xmlNodePtr track_id_elem = NULL;
538    void *tmp;
539    unsigned long resource_elem_count;
540
541    /* skip stereoscopic resources */
542    if (has_stereo_resources(image_sequence_elem)) {
543        av_log(NULL, AV_LOG_ERROR, "Stereoscopic 3D image virtual tracks not supported\n");
544        return AVERROR_PATCHWELCOME;
545    }
546
547    /* read TrackId element*/
548    if (!(track_id_elem = ff_imf_xml_get_child_element_by_name(image_sequence_elem, "TrackId"))) {
549        av_log(NULL, AV_LOG_ERROR, "TrackId element missing from audio sequence\n");
550        return AVERROR_INVALIDDATA;
551    }
552    if ((ret = ff_imf_xml_read_uuid(track_id_elem, uuid))) {
553        av_log(NULL, AV_LOG_ERROR, "Invalid TrackId element found in audio sequence\n");
554        return ret;
555    }
556
557    /* create main image virtual track if one does not exist */
558    if (!cpl->main_image_2d_track) {
559        cpl->main_image_2d_track = av_malloc(sizeof(FFIMFTrackFileVirtualTrack));
560        if (!cpl->main_image_2d_track)
561            return AVERROR(ENOMEM);
562        imf_trackfile_virtual_track_init(cpl->main_image_2d_track);
563        av_uuid_copy(cpl->main_image_2d_track->base.id_uuid, uuid);
564
565    } else if (!av_uuid_equal(cpl->main_image_2d_track->base.id_uuid, uuid)) {
566        av_log(NULL, AV_LOG_ERROR, "Multiple MainImage virtual tracks found\n");
567        return AVERROR_INVALIDDATA;
568    }
569    av_log(NULL,
570           AV_LOG_DEBUG,
571           "Processing IMF CPL Main Image Sequence for Virtual Track " AV_PRI_UUID "\n",
572           AV_UUID_ARG(uuid));
573
574    /* process resources */
575    resource_list_elem = ff_imf_xml_get_child_element_by_name(image_sequence_elem, "ResourceList");
576    if (!resource_list_elem)
577        return 0;
578
579    resource_elem_count = xmlChildElementCount(resource_list_elem);
580    if (resource_elem_count > UINT32_MAX
581        || cpl->main_image_2d_track->resource_count > UINT32_MAX - resource_elem_count
582        || (cpl->main_image_2d_track->resource_count + resource_elem_count)
583            > INT_MAX / sizeof(FFIMFTrackFileResource))
584        return AVERROR(ENOMEM);
585    tmp = av_fast_realloc(cpl->main_image_2d_track->resources,
586                          &cpl->main_image_2d_track->resources_alloc_sz,
587                          (cpl->main_image_2d_track->resource_count + resource_elem_count)
588                              * sizeof(FFIMFTrackFileResource));
589    if (!tmp) {
590        av_log(NULL, AV_LOG_ERROR, "Cannot allocate Main Image Resources\n");
591        return AVERROR(ENOMEM);
592    }
593    cpl->main_image_2d_track->resources = tmp;
594
595    resource_elem = xmlFirstElementChild(resource_list_elem);
596    while (resource_elem) {
597        imf_trackfile_resource_init(
598            &cpl->main_image_2d_track->resources[cpl->main_image_2d_track->resource_count]);
599        ret = fill_trackfile_resource(resource_elem,
600                                      &cpl->main_image_2d_track->resources[cpl->main_image_2d_track->resource_count],
601                                      cpl);
602        if (ret)
603            av_log(NULL, AV_LOG_ERROR, "Invalid Resource\n");
604        else
605            cpl->main_image_2d_track->resource_count++;
606
607        resource_elem = xmlNextElementSibling(resource_elem);
608    }
609
610    return 0;
611}
612
613static int fill_virtual_tracks(xmlNodePtr cpl_element, FFIMFCPL *cpl)
614{
615    int ret = 0;
616    xmlNodePtr segment_list_elem = NULL;
617    xmlNodePtr segment_elem = NULL;
618    xmlNodePtr sequence_list_elem = NULL;
619    xmlNodePtr sequence_elem = NULL;
620
621    if (!(segment_list_elem = ff_imf_xml_get_child_element_by_name(cpl_element, "SegmentList"))) {
622        av_log(NULL, AV_LOG_ERROR, "SegmentList element missing\n");
623        return AVERROR_INVALIDDATA;
624    }
625
626    /* process sequences */
627    segment_elem = xmlFirstElementChild(segment_list_elem);
628    while (segment_elem) {
629        av_log(NULL, AV_LOG_DEBUG, "Processing IMF CPL Segment\n");
630
631        sequence_list_elem = ff_imf_xml_get_child_element_by_name(segment_elem, "SequenceList");
632        if (!segment_list_elem)
633            continue;
634
635        sequence_elem = xmlFirstElementChild(sequence_list_elem);
636        while (sequence_elem) {
637            if (xmlStrcmp(sequence_elem->name, "MarkerSequence") == 0)
638                ret = push_marker_sequence(sequence_elem, cpl);
639
640            else if (xmlStrcmp(sequence_elem->name, "MainImageSequence") == 0)
641                ret = push_main_image_2d_sequence(sequence_elem, cpl);
642
643            else if (xmlStrcmp(sequence_elem->name, "MainAudioSequence") == 0)
644                ret = push_main_audio_sequence(sequence_elem, cpl);
645
646            else
647                av_log(NULL,
648                       AV_LOG_INFO,
649                       "The following Sequence is not supported and is ignored: %s\n",
650                       sequence_elem->name);
651
652            /* abort parsing only if memory error occurred */
653            if (ret == AVERROR(ENOMEM))
654                return ret;
655
656            sequence_elem = xmlNextElementSibling(sequence_elem);
657        }
658
659        segment_elem = xmlNextElementSibling(segment_elem);
660    }
661
662    return ret;
663}
664
665int ff_imf_parse_cpl_from_xml_dom(xmlDocPtr doc, FFIMFCPL **cpl)
666{
667    int ret = 0;
668    xmlNodePtr cpl_element = NULL;
669
670    *cpl = ff_imf_cpl_alloc();
671    if (!*cpl) {
672        ret = AVERROR(ENOMEM);
673        goto cleanup;
674    }
675
676    cpl_element = xmlDocGetRootElement(doc);
677    if (!cpl_element || xmlStrcmp(cpl_element->name, "CompositionPlaylist")) {
678        av_log(NULL, AV_LOG_ERROR, "The root element of the CPL is not CompositionPlaylist\n");
679        ret = AVERROR_INVALIDDATA;
680        goto cleanup;
681    }
682
683    if ((ret = fill_content_title(cpl_element, *cpl)))
684        goto cleanup;
685    if ((ret = fill_id(cpl_element, *cpl)))
686        goto cleanup;
687    if ((ret = fill_edit_rate(cpl_element, *cpl)))
688        goto cleanup;
689    if ((ret = fill_virtual_tracks(cpl_element, *cpl)))
690        goto cleanup;
691
692cleanup:
693    if (*cpl && ret) {
694        ff_imf_cpl_free(*cpl);
695        *cpl = NULL;
696    }
697    return ret;
698}
699
700static void imf_marker_free(FFIMFMarker *marker)
701{
702    if (!marker)
703        return;
704    xmlFree(marker->label_utf8);
705    xmlFree(marker->scope_utf8);
706}
707
708static void imf_marker_resource_free(FFIMFMarkerResource *rsrc)
709{
710    if (!rsrc)
711        return;
712    for (uint32_t i = 0; i < rsrc->marker_count; i++)
713        imf_marker_free(&rsrc->markers[i]);
714    av_freep(&rsrc->markers);
715}
716
717static void imf_marker_virtual_track_free(FFIMFMarkerVirtualTrack *vt)
718{
719    if (!vt)
720        return;
721    for (uint32_t i = 0; i < vt->resource_count; i++)
722        imf_marker_resource_free(&vt->resources[i]);
723    av_freep(&vt->resources);
724}
725
726static void imf_trackfile_virtual_track_free(FFIMFTrackFileVirtualTrack *vt)
727{
728    if (!vt)
729        return;
730    av_freep(&vt->resources);
731}
732
733static void imf_cpl_init(FFIMFCPL *cpl)
734{
735    av_uuid_nil(cpl->id_uuid);
736    cpl->content_title_utf8 = NULL;
737    cpl->edit_rate = av_make_q(0, 1);
738    cpl->main_markers_track = NULL;
739    cpl->main_image_2d_track = NULL;
740    cpl->main_audio_track_count = 0;
741    cpl->main_audio_tracks = NULL;
742}
743
744FFIMFCPL *ff_imf_cpl_alloc(void)
745{
746    FFIMFCPL *cpl;
747
748    cpl = av_malloc(sizeof(FFIMFCPL));
749    if (!cpl)
750        return NULL;
751    imf_cpl_init(cpl);
752    return cpl;
753}
754
755void ff_imf_cpl_free(FFIMFCPL *cpl)
756{
757    if (!cpl)
758        return;
759
760    xmlFree(cpl->content_title_utf8);
761
762    imf_marker_virtual_track_free(cpl->main_markers_track);
763
764    if (cpl->main_markers_track)
765        av_freep(&cpl->main_markers_track);
766
767    imf_trackfile_virtual_track_free(cpl->main_image_2d_track);
768
769    if (cpl->main_image_2d_track)
770        av_freep(&cpl->main_image_2d_track);
771
772    for (uint32_t i = 0; i < cpl->main_audio_track_count; i++)
773        imf_trackfile_virtual_track_free(&cpl->main_audio_tracks[i]);
774
775    if (cpl->main_audio_tracks)
776        av_freep(&cpl->main_audio_tracks);
777
778    av_freep(&cpl);
779}
780
781int ff_imf_parse_cpl(AVIOContext *in, FFIMFCPL **cpl)
782{
783    AVBPrint buf;
784    xmlDoc *doc = NULL;
785    int ret = 0;
786
787    av_bprint_init(&buf, 0, INT_MAX); // xmlReadMemory uses integer length
788
789    ret = avio_read_to_bprint(in, &buf, SIZE_MAX);
790    if (ret < 0 || !avio_feof(in)) {
791        av_log(NULL, AV_LOG_ERROR, "Cannot read IMF CPL\n");
792        if (ret == 0)
793            ret = AVERROR_INVALIDDATA;
794        goto clean_up;
795    }
796
797    LIBXML_TEST_VERSION
798
799    doc = xmlReadMemory(buf.str, buf.len, NULL, NULL, 0);
800    if (!doc) {
801        av_log(NULL,
802                AV_LOG_ERROR,
803                "XML parsing failed when reading the IMF CPL\n");
804        ret = AVERROR_INVALIDDATA;
805        goto clean_up;
806    }
807
808    if ((ret = ff_imf_parse_cpl_from_xml_dom(doc, cpl))) {
809        av_log(NULL, AV_LOG_ERROR, "Cannot parse IMF CPL\n");
810    } else {
811        av_log(NULL,
812                AV_LOG_INFO,
813                "IMF CPL ContentTitle: %s\n",
814                (*cpl)->content_title_utf8);
815        av_log(NULL,
816                AV_LOG_INFO,
817                "IMF CPL Id: " AV_PRI_UUID "\n",
818                AV_UUID_ARG((*cpl)->id_uuid));
819    }
820
821    xmlFreeDoc(doc);
822
823clean_up:
824    av_bprint_finalize(&buf, NULL);
825
826    return ret;
827}
828