mirror of https://github.com/FFmpeg/FFmpeg.git
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
932 lines
30 KiB
932 lines
30 KiB
/* |
|
* This file is part of FFmpeg. |
|
* |
|
* FFmpeg is free software; you can redistribute it and/or |
|
* modify it under the terms of the GNU Lesser General Public |
|
* License as published by the Free Software Foundation; either |
|
* version 2.1 of the License, or (at your option) any later version. |
|
* |
|
* FFmpeg 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 |
|
* Lesser General Public License for more details. |
|
* |
|
* You should have received a copy of the GNU Lesser General Public |
|
* License along with FFmpeg; if not, write to the Free Software |
|
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA |
|
*/ |
|
|
|
/* |
|
* |
|
* Copyright (c) Sandflow Consulting LLC |
|
* |
|
* Redistribution and use in source and binary forms, with or without |
|
* modification, are permitted provided that the following conditions are met: |
|
* |
|
* * Redistributions of source code must retain the above copyright notice, this |
|
* list of conditions and the following disclaimer. |
|
* * Redistributions in binary form must reproduce the above copyright notice, |
|
* this list of conditions and the following disclaimer in the documentation |
|
* and/or other materials provided with the distribution. |
|
* |
|
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" |
|
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE |
|
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE |
|
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE |
|
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR |
|
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF |
|
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS |
|
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN |
|
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) |
|
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE |
|
* POSSIBILITY OF SUCH DAMAGE. |
|
*/ |
|
|
|
/** |
|
* Implements IMP CPL processing |
|
* |
|
* @author Pierre-Anthony Lemieux |
|
* @file |
|
* @ingroup lavu_imf |
|
*/ |
|
|
|
#include "imf.h" |
|
#include "libavformat/mxf.h" |
|
#include "libavutil/bprint.h" |
|
#include "libavutil/error.h" |
|
#include <libxml/parser.h> |
|
|
|
xmlNodePtr ff_imf_xml_get_child_element_by_name(xmlNodePtr parent, const char *name_utf8) |
|
{ |
|
xmlNodePtr cur_element; |
|
|
|
cur_element = xmlFirstElementChild(parent); |
|
while (cur_element) { |
|
if (xmlStrcmp(cur_element->name, name_utf8) == 0) |
|
return cur_element; |
|
|
|
cur_element = xmlNextElementSibling(cur_element); |
|
} |
|
return NULL; |
|
} |
|
|
|
int ff_imf_xml_read_uuid(xmlNodePtr element, AVUUID uuid) |
|
{ |
|
int ret = 0; |
|
|
|
xmlChar *element_text = xmlNodeListGetString(element->doc, element->xmlChildrenNode, 1); |
|
if (!element_text) |
|
return AVERROR_INVALIDDATA; |
|
ret = av_uuid_urn_parse(element_text, uuid); |
|
if (ret) { |
|
av_log(NULL, AV_LOG_ERROR, "Invalid UUID\n"); |
|
ret = AVERROR_INVALIDDATA; |
|
} |
|
xmlFree(element_text); |
|
|
|
return ret; |
|
} |
|
|
|
int ff_imf_xml_read_rational(xmlNodePtr element, AVRational *rational) |
|
{ |
|
int ret = 0; |
|
|
|
xmlChar *element_text = xmlNodeListGetString(element->doc, element->xmlChildrenNode, 1); |
|
if (element_text == NULL || sscanf(element_text, "%i %i", &rational->num, &rational->den) != 2) { |
|
av_log(NULL, AV_LOG_ERROR, "Invalid rational number\n"); |
|
ret = AVERROR_INVALIDDATA; |
|
} |
|
xmlFree(element_text); |
|
|
|
return ret; |
|
} |
|
|
|
int ff_imf_xml_read_uint32(xmlNodePtr element, uint32_t *number) |
|
{ |
|
int ret = 0; |
|
|
|
xmlChar *element_text = xmlNodeListGetString(element->doc, element->xmlChildrenNode, 1); |
|
if (element_text == NULL || sscanf(element_text, "%" PRIu32, number) != 1) { |
|
av_log(NULL, AV_LOG_ERROR, "Invalid unsigned 32-bit integer"); |
|
ret = AVERROR_INVALIDDATA; |
|
} |
|
xmlFree(element_text); |
|
|
|
return ret; |
|
} |
|
|
|
static int ff_imf_xml_read_boolean(xmlNodePtr element, int *value) |
|
{ |
|
int ret = 0; |
|
|
|
xmlChar *element_text = xmlNodeListGetString(element->doc, element->xmlChildrenNode, 1); |
|
if (xmlStrcmp(element_text, "true") == 0 || xmlStrcmp(element_text, "1") == 0) |
|
*value = 1; |
|
else if (xmlStrcmp(element_text, "false") == 0 || xmlStrcmp(element_text, "0") == 0) |
|
*value = 0; |
|
else |
|
ret = 1; |
|
xmlFree(element_text); |
|
|
|
return ret; |
|
} |
|
|
|
static void imf_base_virtual_track_init(FFIMFBaseVirtualTrack *track) |
|
{ |
|
memset(track->id_uuid, 0, sizeof(track->id_uuid)); |
|
} |
|
|
|
static void imf_marker_virtual_track_init(FFIMFMarkerVirtualTrack *track) |
|
{ |
|
imf_base_virtual_track_init((FFIMFBaseVirtualTrack *)track); |
|
track->resource_count = 0; |
|
track->resources = NULL; |
|
} |
|
|
|
static void imf_trackfile_virtual_track_init(FFIMFTrackFileVirtualTrack *track) |
|
{ |
|
imf_base_virtual_track_init((FFIMFBaseVirtualTrack *)track); |
|
track->resource_count = 0; |
|
track->resources_alloc_sz = 0; |
|
track->resources = NULL; |
|
} |
|
|
|
static void imf_base_resource_init(FFIMFBaseResource *rsrc) |
|
{ |
|
rsrc->duration = 0; |
|
rsrc->edit_rate = av_make_q(0, 1); |
|
rsrc->entry_point = 0; |
|
rsrc->repeat_count = 1; |
|
} |
|
|
|
static void imf_marker_resource_init(FFIMFMarkerResource *rsrc) |
|
{ |
|
imf_base_resource_init((FFIMFBaseResource *)rsrc); |
|
rsrc->marker_count = 0; |
|
rsrc->markers = NULL; |
|
} |
|
|
|
static void imf_marker_init(FFIMFMarker *marker) |
|
{ |
|
marker->label_utf8 = NULL; |
|
marker->offset = 0; |
|
marker->scope_utf8 = NULL; |
|
} |
|
|
|
static void imf_trackfile_resource_init(FFIMFTrackFileResource *rsrc) |
|
{ |
|
imf_base_resource_init((FFIMFBaseResource *)rsrc); |
|
memset(rsrc->track_file_uuid, 0, sizeof(rsrc->track_file_uuid)); |
|
} |
|
|
|
static int fill_content_title(xmlNodePtr cpl_element, FFIMFCPL *cpl) |
|
{ |
|
xmlNodePtr element = NULL; |
|
|
|
if (!(element = ff_imf_xml_get_child_element_by_name(cpl_element, "ContentTitle"))) { |
|
av_log(NULL, AV_LOG_ERROR, "ContentTitle element not found in the IMF CPL\n"); |
|
return AVERROR_INVALIDDATA; |
|
} |
|
cpl->content_title_utf8 = xmlNodeListGetString(cpl_element->doc, |
|
element->xmlChildrenNode, |
|
1); |
|
if (!cpl->content_title_utf8) |
|
cpl->content_title_utf8 = xmlStrdup(""); |
|
if (!cpl->content_title_utf8) |
|
return AVERROR(ENOMEM); |
|
|
|
return 0; |
|
} |
|
|
|
static int digit_to_int(char digit) |
|
{ |
|
if (digit >= '0' && digit <= '9') |
|
return digit - '0'; |
|
return -1; |
|
} |
|
|
|
/** |
|
* Parses a string that conform to the TimecodeType used in IMF CPL and defined |
|
* in SMPTE ST 2067-3. |
|
* @param[in] s string to parse |
|
* @param[out] tc_comps pointer to an array of 4 integers where the parsed HH, |
|
* MM, SS and FF fields of the timecode are returned. |
|
* @return 0 on success, < 0 AVERROR code on error. |
|
*/ |
|
static int parse_cpl_tc_type(const char *s, int *tc_comps) |
|
{ |
|
if (av_strnlen(s, 11) != 11) |
|
return AVERROR(EINVAL); |
|
|
|
for (int i = 0; i < 4; i++) { |
|
int hi; |
|
int lo; |
|
|
|
hi = digit_to_int(s[i * 3]); |
|
lo = digit_to_int(s[i * 3 + 1]); |
|
|
|
if (hi == -1 || lo == -1) |
|
return AVERROR(EINVAL); |
|
|
|
tc_comps[i] = 10 * hi + lo; |
|
} |
|
|
|
return 0; |
|
} |
|
|
|
static int fill_timecode(xmlNodePtr cpl_element, FFIMFCPL *cpl) |
|
{ |
|
xmlNodePtr tc_element = NULL; |
|
xmlNodePtr element = NULL; |
|
xmlChar *tc_str = NULL; |
|
int df = 0; |
|
int comps[4]; |
|
int ret = 0; |
|
|
|
tc_element = ff_imf_xml_get_child_element_by_name(cpl_element, "CompositionTimecode"); |
|
if (!tc_element) |
|
return 0; |
|
|
|
element = ff_imf_xml_get_child_element_by_name(tc_element, "TimecodeDropFrame"); |
|
if (!element) { |
|
av_log(NULL, AV_LOG_ERROR, "CompositionTimecode element is missing\ |
|
a TimecodeDropFrame child element\n"); |
|
return AVERROR_INVALIDDATA; |
|
} |
|
|
|
if (ff_imf_xml_read_boolean(element, &df)) { |
|
av_log(NULL, AV_LOG_ERROR, "TimecodeDropFrame element is invalid\n"); |
|
return AVERROR_INVALIDDATA; |
|
} |
|
element = ff_imf_xml_get_child_element_by_name(tc_element, "TimecodeStartAddress"); |
|
if (!element) { |
|
av_log(NULL, AV_LOG_ERROR, "CompositionTimecode element is missing\ |
|
a TimecodeStartAddress child element\n"); |
|
return AVERROR_INVALIDDATA; |
|
} |
|
|
|
tc_str = xmlNodeListGetString(element->doc, element->xmlChildrenNode, 1); |
|
if (!tc_str) |
|
return AVERROR_INVALIDDATA; |
|
ret = parse_cpl_tc_type(tc_str, comps); |
|
xmlFree(tc_str); |
|
if (ret) |
|
return ret; |
|
|
|
cpl->tc = av_malloc(sizeof(AVTimecode)); |
|
if (!cpl->tc) |
|
return AVERROR(ENOMEM); |
|
ret = av_timecode_init_from_components(cpl->tc, cpl->edit_rate, |
|
df ? AV_TIMECODE_FLAG_DROPFRAME : 0, |
|
comps[0], comps[1], comps[2], comps[3], |
|
NULL); |
|
|
|
return ret; |
|
} |
|
|
|
static int fill_edit_rate(xmlNodePtr cpl_element, FFIMFCPL *cpl) |
|
{ |
|
xmlNodePtr element = NULL; |
|
|
|
if (!(element = ff_imf_xml_get_child_element_by_name(cpl_element, "EditRate"))) { |
|
av_log(NULL, AV_LOG_ERROR, "EditRate element not found in the IMF CPL\n"); |
|
return AVERROR_INVALIDDATA; |
|
} |
|
|
|
return ff_imf_xml_read_rational(element, &cpl->edit_rate); |
|
} |
|
|
|
static int fill_id(xmlNodePtr cpl_element, FFIMFCPL *cpl) |
|
{ |
|
xmlNodePtr element = NULL; |
|
|
|
if (!(element = ff_imf_xml_get_child_element_by_name(cpl_element, "Id"))) { |
|
av_log(NULL, AV_LOG_ERROR, "Id element not found in the IMF CPL\n"); |
|
return AVERROR_INVALIDDATA; |
|
} |
|
|
|
return ff_imf_xml_read_uuid(element, cpl->id_uuid); |
|
} |
|
|
|
static int fill_marker(xmlNodePtr marker_elem, FFIMFMarker *marker) |
|
{ |
|
xmlNodePtr element = NULL; |
|
int ret = 0; |
|
|
|
/* read Offset */ |
|
if (!(element = ff_imf_xml_get_child_element_by_name(marker_elem, "Offset"))) { |
|
av_log(NULL, AV_LOG_ERROR, "Offset element not found in a Marker\n"); |
|
return AVERROR_INVALIDDATA; |
|
} |
|
if ((ret = ff_imf_xml_read_uint32(element, &marker->offset))) |
|
return ret; |
|
|
|
/* read Label and Scope */ |
|
if (!(element = ff_imf_xml_get_child_element_by_name(marker_elem, "Label"))) { |
|
av_log(NULL, AV_LOG_ERROR, "Label element not found in a Marker\n"); |
|
return AVERROR_INVALIDDATA; |
|
} |
|
if (!(marker->label_utf8 = xmlNodeListGetString(element->doc, element->xmlChildrenNode, 1))) { |
|
av_log(NULL, AV_LOG_ERROR, "Empty Label element found in a Marker\n"); |
|
return AVERROR_INVALIDDATA; |
|
} |
|
if (!(marker->scope_utf8 = xmlGetNoNsProp(element, "scope"))) { |
|
marker->scope_utf8 |
|
= xmlCharStrdup("http://www.smpte-ra.org/schemas/2067-3/2013#standard-markers"); |
|
if (!marker->scope_utf8) { |
|
xmlFree(marker->label_utf8); |
|
return AVERROR(ENOMEM); |
|
} |
|
} |
|
|
|
return ret; |
|
} |
|
|
|
static int fill_base_resource(xmlNodePtr resource_elem, FFIMFBaseResource *resource, FFIMFCPL *cpl) |
|
{ |
|
xmlNodePtr element = NULL; |
|
int ret = 0; |
|
|
|
/* read EditRate */ |
|
if (!(element = ff_imf_xml_get_child_element_by_name(resource_elem, "EditRate"))) { |
|
resource->edit_rate = cpl->edit_rate; |
|
} else if ((ret = ff_imf_xml_read_rational(element, &resource->edit_rate))) { |
|
av_log(NULL, AV_LOG_ERROR, "Invalid EditRate element found in a Resource\n"); |
|
return ret; |
|
} |
|
|
|
/* read EntryPoint */ |
|
if ((element = ff_imf_xml_get_child_element_by_name(resource_elem, "EntryPoint"))) { |
|
if ((ret = ff_imf_xml_read_uint32(element, &resource->entry_point))) { |
|
av_log(NULL, AV_LOG_ERROR, "Invalid EntryPoint element found in a Resource\n"); |
|
return ret; |
|
} |
|
} else { |
|
resource->entry_point = 0; |
|
} |
|
|
|
/* read IntrinsicDuration */ |
|
if (!(element = ff_imf_xml_get_child_element_by_name(resource_elem, "IntrinsicDuration"))) { |
|
av_log(NULL, AV_LOG_ERROR, "IntrinsicDuration element missing from Resource\n"); |
|
return AVERROR_INVALIDDATA; |
|
} |
|
if ((ret = ff_imf_xml_read_uint32(element, &resource->duration))) { |
|
av_log(NULL, AV_LOG_ERROR, "Invalid IntrinsicDuration element found in a Resource\n"); |
|
return ret; |
|
} |
|
resource->duration -= resource->entry_point; |
|
|
|
/* read SourceDuration */ |
|
if ((element = ff_imf_xml_get_child_element_by_name(resource_elem, "SourceDuration"))) { |
|
if ((ret = ff_imf_xml_read_uint32(element, &resource->duration))) { |
|
av_log(NULL, AV_LOG_ERROR, "SourceDuration element missing from Resource\n"); |
|
return ret; |
|
} |
|
} |
|
|
|
/* read RepeatCount */ |
|
if ((element = ff_imf_xml_get_child_element_by_name(resource_elem, "RepeatCount"))) |
|
ret = ff_imf_xml_read_uint32(element, &resource->repeat_count); |
|
|
|
return ret; |
|
} |
|
|
|
static int fill_trackfile_resource(xmlNodePtr tf_resource_elem, |
|
FFIMFTrackFileResource *tf_resource, |
|
FFIMFCPL *cpl) |
|
{ |
|
xmlNodePtr element = NULL; |
|
int ret = 0; |
|
|
|
if ((ret = fill_base_resource(tf_resource_elem, (FFIMFBaseResource *)tf_resource, cpl))) |
|
return ret; |
|
|
|
/* read TrackFileId */ |
|
if ((element = ff_imf_xml_get_child_element_by_name(tf_resource_elem, "TrackFileId"))) { |
|
if ((ret = ff_imf_xml_read_uuid(element, tf_resource->track_file_uuid))) { |
|
av_log(NULL, AV_LOG_ERROR, "Invalid TrackFileId element found in Resource\n"); |
|
return ret; |
|
} |
|
} else { |
|
av_log(NULL, AV_LOG_ERROR, "TrackFileId element missing from Resource\n"); |
|
return AVERROR_INVALIDDATA; |
|
} |
|
|
|
return ret; |
|
} |
|
|
|
static int fill_marker_resource(xmlNodePtr marker_resource_elem, |
|
FFIMFMarkerResource *marker_resource, |
|
FFIMFCPL *cpl) |
|
{ |
|
xmlNodePtr element = NULL; |
|
int ret = 0; |
|
|
|
if ((ret = fill_base_resource(marker_resource_elem, (FFIMFBaseResource *)marker_resource, cpl))) |
|
return ret; |
|
|
|
/* read markers */ |
|
element = xmlFirstElementChild(marker_resource_elem); |
|
while (element) { |
|
if (xmlStrcmp(element->name, "Marker") == 0) { |
|
void *tmp; |
|
|
|
if (marker_resource->marker_count == UINT32_MAX) |
|
return AVERROR(ENOMEM); |
|
tmp = av_realloc_array(marker_resource->markers, |
|
marker_resource->marker_count + 1, |
|
sizeof(FFIMFMarker)); |
|
if (!tmp) |
|
return AVERROR(ENOMEM); |
|
marker_resource->markers = tmp; |
|
|
|
imf_marker_init(&marker_resource->markers[marker_resource->marker_count]); |
|
ret = fill_marker(element, |
|
&marker_resource->markers[marker_resource->marker_count]); |
|
marker_resource->marker_count++; |
|
if (ret) |
|
return ret; |
|
} |
|
|
|
element = xmlNextElementSibling(element); |
|
} |
|
|
|
return ret; |
|
} |
|
|
|
static int push_marker_sequence(xmlNodePtr marker_sequence_elem, FFIMFCPL *cpl) |
|
{ |
|
int ret = 0; |
|
AVUUID uuid; |
|
xmlNodePtr resource_list_elem = NULL; |
|
xmlNodePtr resource_elem = NULL; |
|
xmlNodePtr track_id_elem = NULL; |
|
unsigned long resource_elem_count; |
|
void *tmp; |
|
|
|
/* read TrackID element */ |
|
if (!(track_id_elem = ff_imf_xml_get_child_element_by_name(marker_sequence_elem, "TrackId"))) { |
|
av_log(NULL, AV_LOG_ERROR, "TrackId element missing from Sequence\n"); |
|
return AVERROR_INVALIDDATA; |
|
} |
|
if (ff_imf_xml_read_uuid(track_id_elem, uuid)) { |
|
av_log(NULL, AV_LOG_ERROR, "Invalid TrackId element found in Sequence\n"); |
|
return AVERROR_INVALIDDATA; |
|
} |
|
av_log(NULL, |
|
AV_LOG_DEBUG, |
|
"Processing IMF CPL Marker Sequence for Virtual Track " AV_PRI_UUID "\n", |
|
AV_UUID_ARG(uuid)); |
|
|
|
/* create main marker virtual track if it does not exist */ |
|
if (!cpl->main_markers_track) { |
|
cpl->main_markers_track = av_malloc(sizeof(FFIMFMarkerVirtualTrack)); |
|
if (!cpl->main_markers_track) |
|
return AVERROR(ENOMEM); |
|
imf_marker_virtual_track_init(cpl->main_markers_track); |
|
av_uuid_copy(cpl->main_markers_track->base.id_uuid, uuid); |
|
|
|
} else if (!av_uuid_equal(cpl->main_markers_track->base.id_uuid, uuid)) { |
|
av_log(NULL, AV_LOG_ERROR, "Multiple marker virtual tracks were found\n"); |
|
return AVERROR_INVALIDDATA; |
|
} |
|
|
|
/* process resources */ |
|
resource_list_elem = ff_imf_xml_get_child_element_by_name(marker_sequence_elem, "ResourceList"); |
|
if (!resource_list_elem) |
|
return 0; |
|
|
|
resource_elem_count = xmlChildElementCount(resource_list_elem); |
|
if (resource_elem_count > UINT32_MAX |
|
|| cpl->main_markers_track->resource_count > UINT32_MAX - resource_elem_count) |
|
return AVERROR(ENOMEM); |
|
tmp = av_realloc_array(cpl->main_markers_track->resources, |
|
cpl->main_markers_track->resource_count + resource_elem_count, |
|
sizeof(FFIMFMarkerResource)); |
|
if (!tmp) { |
|
av_log(NULL, AV_LOG_ERROR, "Cannot allocate Marker Resources\n"); |
|
return AVERROR(ENOMEM); |
|
} |
|
cpl->main_markers_track->resources = tmp; |
|
|
|
resource_elem = xmlFirstElementChild(resource_list_elem); |
|
while (resource_elem) { |
|
imf_marker_resource_init(&cpl->main_markers_track->resources[cpl->main_markers_track->resource_count]); |
|
ret = fill_marker_resource(resource_elem, |
|
&cpl->main_markers_track->resources[cpl->main_markers_track->resource_count], |
|
cpl); |
|
cpl->main_markers_track->resource_count++; |
|
if (ret) |
|
return ret; |
|
|
|
resource_elem = xmlNextElementSibling(resource_elem); |
|
} |
|
|
|
return ret; |
|
} |
|
|
|
static int has_stereo_resources(xmlNodePtr element) |
|
{ |
|
if (xmlStrcmp(element->name, "Left") == 0 || xmlStrcmp(element->name, "Right") == 0) |
|
return 1; |
|
|
|
element = xmlFirstElementChild(element); |
|
while (element) { |
|
if (has_stereo_resources(element)) |
|
return 1; |
|
|
|
element = xmlNextElementSibling(element); |
|
} |
|
|
|
return 0; |
|
} |
|
|
|
static int push_main_audio_sequence(xmlNodePtr audio_sequence_elem, FFIMFCPL *cpl) |
|
{ |
|
int ret = 0; |
|
AVUUID uuid; |
|
xmlNodePtr resource_list_elem = NULL; |
|
xmlNodePtr resource_elem = NULL; |
|
xmlNodePtr track_id_elem = NULL; |
|
unsigned long resource_elem_count; |
|
FFIMFTrackFileVirtualTrack *vt = NULL; |
|
void *tmp; |
|
|
|
/* read TrackID element */ |
|
if (!(track_id_elem = ff_imf_xml_get_child_element_by_name(audio_sequence_elem, "TrackId"))) { |
|
av_log(NULL, AV_LOG_ERROR, "TrackId element missing from audio sequence\n"); |
|
return AVERROR_INVALIDDATA; |
|
} |
|
if ((ret = ff_imf_xml_read_uuid(track_id_elem, uuid))) { |
|
av_log(NULL, AV_LOG_ERROR, "Invalid TrackId element found in audio sequence\n"); |
|
return ret; |
|
} |
|
av_log(NULL, |
|
AV_LOG_DEBUG, |
|
"Processing IMF CPL Audio Sequence for Virtual Track " AV_PRI_UUID "\n", |
|
AV_UUID_ARG(uuid)); |
|
|
|
/* get the main audio virtual track corresponding to the sequence */ |
|
for (uint32_t i = 0; i < cpl->main_audio_track_count; i++) { |
|
if (av_uuid_equal(cpl->main_audio_tracks[i].base.id_uuid, uuid)) { |
|
vt = &cpl->main_audio_tracks[i]; |
|
break; |
|
} |
|
} |
|
|
|
/* create a main audio virtual track if none exists for the sequence */ |
|
if (!vt) { |
|
if (cpl->main_audio_track_count == UINT32_MAX) |
|
return AVERROR(ENOMEM); |
|
tmp = av_realloc_array(cpl->main_audio_tracks, |
|
cpl->main_audio_track_count + 1, |
|
sizeof(FFIMFTrackFileVirtualTrack)); |
|
if (!tmp) |
|
return AVERROR(ENOMEM); |
|
|
|
cpl->main_audio_tracks = tmp; |
|
vt = &cpl->main_audio_tracks[cpl->main_audio_track_count]; |
|
imf_trackfile_virtual_track_init(vt); |
|
cpl->main_audio_track_count++; |
|
av_uuid_copy(vt->base.id_uuid, uuid); |
|
} |
|
|
|
/* process resources */ |
|
resource_list_elem = ff_imf_xml_get_child_element_by_name(audio_sequence_elem, "ResourceList"); |
|
if (!resource_list_elem) |
|
return 0; |
|
|
|
resource_elem_count = xmlChildElementCount(resource_list_elem); |
|
if (resource_elem_count > UINT32_MAX |
|
|| vt->resource_count > UINT32_MAX - resource_elem_count) |
|
return AVERROR(ENOMEM); |
|
tmp = av_fast_realloc(vt->resources, |
|
&vt->resources_alloc_sz, |
|
(vt->resource_count + resource_elem_count) |
|
* sizeof(FFIMFTrackFileResource)); |
|
if (!tmp) { |
|
av_log(NULL, AV_LOG_ERROR, "Cannot allocate Main Audio Resources\n"); |
|
return AVERROR(ENOMEM); |
|
} |
|
vt->resources = tmp; |
|
|
|
resource_elem = xmlFirstElementChild(resource_list_elem); |
|
while (resource_elem) { |
|
imf_trackfile_resource_init(&vt->resources[vt->resource_count]); |
|
ret = fill_trackfile_resource(resource_elem, |
|
&vt->resources[vt->resource_count], |
|
cpl); |
|
if (ret) |
|
av_log(NULL, AV_LOG_ERROR, "Invalid Resource\n"); |
|
else |
|
vt->resource_count++; |
|
|
|
resource_elem = xmlNextElementSibling(resource_elem); |
|
} |
|
|
|
return ret; |
|
} |
|
|
|
static int push_main_image_2d_sequence(xmlNodePtr image_sequence_elem, FFIMFCPL *cpl) |
|
{ |
|
int ret = 0; |
|
AVUUID uuid; |
|
xmlNodePtr resource_list_elem = NULL; |
|
xmlNodePtr resource_elem = NULL; |
|
xmlNodePtr track_id_elem = NULL; |
|
void *tmp; |
|
unsigned long resource_elem_count; |
|
|
|
/* skip stereoscopic resources */ |
|
if (has_stereo_resources(image_sequence_elem)) { |
|
av_log(NULL, AV_LOG_ERROR, "Stereoscopic 3D image virtual tracks not supported\n"); |
|
return AVERROR_PATCHWELCOME; |
|
} |
|
|
|
/* read TrackId element*/ |
|
if (!(track_id_elem = ff_imf_xml_get_child_element_by_name(image_sequence_elem, "TrackId"))) { |
|
av_log(NULL, AV_LOG_ERROR, "TrackId element missing from audio sequence\n"); |
|
return AVERROR_INVALIDDATA; |
|
} |
|
if ((ret = ff_imf_xml_read_uuid(track_id_elem, uuid))) { |
|
av_log(NULL, AV_LOG_ERROR, "Invalid TrackId element found in audio sequence\n"); |
|
return ret; |
|
} |
|
|
|
/* create main image virtual track if one does not exist */ |
|
if (!cpl->main_image_2d_track) { |
|
cpl->main_image_2d_track = av_malloc(sizeof(FFIMFTrackFileVirtualTrack)); |
|
if (!cpl->main_image_2d_track) |
|
return AVERROR(ENOMEM); |
|
imf_trackfile_virtual_track_init(cpl->main_image_2d_track); |
|
av_uuid_copy(cpl->main_image_2d_track->base.id_uuid, uuid); |
|
|
|
} else if (!av_uuid_equal(cpl->main_image_2d_track->base.id_uuid, uuid)) { |
|
av_log(NULL, AV_LOG_ERROR, "Multiple MainImage virtual tracks found\n"); |
|
return AVERROR_INVALIDDATA; |
|
} |
|
av_log(NULL, |
|
AV_LOG_DEBUG, |
|
"Processing IMF CPL Main Image Sequence for Virtual Track " AV_PRI_UUID "\n", |
|
AV_UUID_ARG(uuid)); |
|
|
|
/* process resources */ |
|
resource_list_elem = ff_imf_xml_get_child_element_by_name(image_sequence_elem, "ResourceList"); |
|
if (!resource_list_elem) |
|
return 0; |
|
|
|
resource_elem_count = xmlChildElementCount(resource_list_elem); |
|
if (resource_elem_count > UINT32_MAX |
|
|| cpl->main_image_2d_track->resource_count > UINT32_MAX - resource_elem_count |
|
|| (cpl->main_image_2d_track->resource_count + resource_elem_count) |
|
> INT_MAX / sizeof(FFIMFTrackFileResource)) |
|
return AVERROR(ENOMEM); |
|
tmp = av_fast_realloc(cpl->main_image_2d_track->resources, |
|
&cpl->main_image_2d_track->resources_alloc_sz, |
|
(cpl->main_image_2d_track->resource_count + resource_elem_count) |
|
* sizeof(FFIMFTrackFileResource)); |
|
if (!tmp) { |
|
av_log(NULL, AV_LOG_ERROR, "Cannot allocate Main Image Resources\n"); |
|
return AVERROR(ENOMEM); |
|
} |
|
cpl->main_image_2d_track->resources = tmp; |
|
|
|
resource_elem = xmlFirstElementChild(resource_list_elem); |
|
while (resource_elem) { |
|
imf_trackfile_resource_init( |
|
&cpl->main_image_2d_track->resources[cpl->main_image_2d_track->resource_count]); |
|
ret = fill_trackfile_resource(resource_elem, |
|
&cpl->main_image_2d_track->resources[cpl->main_image_2d_track->resource_count], |
|
cpl); |
|
if (ret) |
|
av_log(NULL, AV_LOG_ERROR, "Invalid Resource\n"); |
|
else |
|
cpl->main_image_2d_track->resource_count++; |
|
|
|
resource_elem = xmlNextElementSibling(resource_elem); |
|
} |
|
|
|
return 0; |
|
} |
|
|
|
static int fill_virtual_tracks(xmlNodePtr cpl_element, FFIMFCPL *cpl) |
|
{ |
|
int ret = 0; |
|
xmlNodePtr segment_list_elem = NULL; |
|
xmlNodePtr segment_elem = NULL; |
|
xmlNodePtr sequence_list_elem = NULL; |
|
xmlNodePtr sequence_elem = NULL; |
|
|
|
if (!(segment_list_elem = ff_imf_xml_get_child_element_by_name(cpl_element, "SegmentList"))) { |
|
av_log(NULL, AV_LOG_ERROR, "SegmentList element missing\n"); |
|
return AVERROR_INVALIDDATA; |
|
} |
|
|
|
/* process sequences */ |
|
segment_elem = xmlFirstElementChild(segment_list_elem); |
|
while (segment_elem) { |
|
av_log(NULL, AV_LOG_DEBUG, "Processing IMF CPL Segment\n"); |
|
|
|
sequence_list_elem = ff_imf_xml_get_child_element_by_name(segment_elem, "SequenceList"); |
|
if (!sequence_list_elem) |
|
continue; |
|
|
|
sequence_elem = xmlFirstElementChild(sequence_list_elem); |
|
while (sequence_elem) { |
|
if (xmlStrcmp(sequence_elem->name, "MarkerSequence") == 0) |
|
ret = push_marker_sequence(sequence_elem, cpl); |
|
|
|
else if (xmlStrcmp(sequence_elem->name, "MainImageSequence") == 0) |
|
ret = push_main_image_2d_sequence(sequence_elem, cpl); |
|
|
|
else if (xmlStrcmp(sequence_elem->name, "MainAudioSequence") == 0) |
|
ret = push_main_audio_sequence(sequence_elem, cpl); |
|
|
|
else |
|
av_log(NULL, |
|
AV_LOG_INFO, |
|
"The following Sequence is not supported and is ignored: %s\n", |
|
sequence_elem->name); |
|
|
|
/* abort parsing only if memory error occurred */ |
|
if (ret == AVERROR(ENOMEM)) |
|
return ret; |
|
|
|
sequence_elem = xmlNextElementSibling(sequence_elem); |
|
} |
|
|
|
segment_elem = xmlNextElementSibling(segment_elem); |
|
} |
|
|
|
return ret; |
|
} |
|
|
|
int ff_imf_parse_cpl_from_xml_dom(xmlDocPtr doc, FFIMFCPL **cpl) |
|
{ |
|
int ret = 0; |
|
xmlNodePtr cpl_element = NULL; |
|
|
|
*cpl = ff_imf_cpl_alloc(); |
|
if (!*cpl) { |
|
ret = AVERROR(ENOMEM); |
|
goto cleanup; |
|
} |
|
|
|
cpl_element = xmlDocGetRootElement(doc); |
|
if (!cpl_element || xmlStrcmp(cpl_element->name, "CompositionPlaylist")) { |
|
av_log(NULL, AV_LOG_ERROR, "The root element of the CPL is not CompositionPlaylist\n"); |
|
ret = AVERROR_INVALIDDATA; |
|
goto cleanup; |
|
} |
|
|
|
if ((ret = fill_content_title(cpl_element, *cpl))) |
|
goto cleanup; |
|
if ((ret = fill_id(cpl_element, *cpl))) |
|
goto cleanup; |
|
if ((ret = fill_edit_rate(cpl_element, *cpl))) |
|
goto cleanup; |
|
if ((ret = fill_timecode(cpl_element, *cpl))) |
|
goto cleanup; |
|
if ((ret = fill_virtual_tracks(cpl_element, *cpl))) |
|
goto cleanup; |
|
|
|
cleanup: |
|
if (*cpl && ret) { |
|
ff_imf_cpl_free(*cpl); |
|
*cpl = NULL; |
|
} |
|
return ret; |
|
} |
|
|
|
static void imf_marker_free(FFIMFMarker *marker) |
|
{ |
|
if (!marker) |
|
return; |
|
xmlFree(marker->label_utf8); |
|
xmlFree(marker->scope_utf8); |
|
} |
|
|
|
static void imf_marker_resource_free(FFIMFMarkerResource *rsrc) |
|
{ |
|
if (!rsrc) |
|
return; |
|
for (uint32_t i = 0; i < rsrc->marker_count; i++) |
|
imf_marker_free(&rsrc->markers[i]); |
|
av_freep(&rsrc->markers); |
|
} |
|
|
|
static void imf_marker_virtual_track_free(FFIMFMarkerVirtualTrack *vt) |
|
{ |
|
if (!vt) |
|
return; |
|
for (uint32_t i = 0; i < vt->resource_count; i++) |
|
imf_marker_resource_free(&vt->resources[i]); |
|
av_freep(&vt->resources); |
|
} |
|
|
|
static void imf_trackfile_virtual_track_free(FFIMFTrackFileVirtualTrack *vt) |
|
{ |
|
if (!vt) |
|
return; |
|
av_freep(&vt->resources); |
|
} |
|
|
|
static void imf_cpl_init(FFIMFCPL *cpl) |
|
{ |
|
av_uuid_nil(cpl->id_uuid); |
|
cpl->content_title_utf8 = NULL; |
|
cpl->edit_rate = av_make_q(0, 1); |
|
cpl->tc = NULL; |
|
cpl->main_markers_track = NULL; |
|
cpl->main_image_2d_track = NULL; |
|
cpl->main_audio_track_count = 0; |
|
cpl->main_audio_tracks = NULL; |
|
} |
|
|
|
FFIMFCPL *ff_imf_cpl_alloc(void) |
|
{ |
|
FFIMFCPL *cpl; |
|
|
|
cpl = av_malloc(sizeof(FFIMFCPL)); |
|
if (!cpl) |
|
return NULL; |
|
imf_cpl_init(cpl); |
|
return cpl; |
|
} |
|
|
|
void ff_imf_cpl_free(FFIMFCPL *cpl) |
|
{ |
|
if (!cpl) |
|
return; |
|
|
|
if (cpl->tc) |
|
av_freep(&cpl->tc); |
|
|
|
xmlFree(cpl->content_title_utf8); |
|
|
|
imf_marker_virtual_track_free(cpl->main_markers_track); |
|
|
|
if (cpl->main_markers_track) |
|
av_freep(&cpl->main_markers_track); |
|
|
|
imf_trackfile_virtual_track_free(cpl->main_image_2d_track); |
|
|
|
if (cpl->main_image_2d_track) |
|
av_freep(&cpl->main_image_2d_track); |
|
|
|
for (uint32_t i = 0; i < cpl->main_audio_track_count; i++) |
|
imf_trackfile_virtual_track_free(&cpl->main_audio_tracks[i]); |
|
|
|
if (cpl->main_audio_tracks) |
|
av_freep(&cpl->main_audio_tracks); |
|
|
|
av_freep(&cpl); |
|
} |
|
|
|
int ff_imf_parse_cpl(AVIOContext *in, FFIMFCPL **cpl) |
|
{ |
|
AVBPrint buf; |
|
xmlDoc *doc = NULL; |
|
int ret = 0; |
|
|
|
av_bprint_init(&buf, 0, INT_MAX); // xmlReadMemory uses integer length |
|
|
|
ret = avio_read_to_bprint(in, &buf, SIZE_MAX); |
|
if (ret < 0 || !avio_feof(in)) { |
|
av_log(NULL, AV_LOG_ERROR, "Cannot read IMF CPL\n"); |
|
if (ret == 0) |
|
ret = AVERROR_INVALIDDATA; |
|
goto clean_up; |
|
} |
|
|
|
LIBXML_TEST_VERSION |
|
|
|
doc = xmlReadMemory(buf.str, buf.len, NULL, NULL, 0); |
|
if (!doc) { |
|
av_log(NULL, |
|
AV_LOG_ERROR, |
|
"XML parsing failed when reading the IMF CPL\n"); |
|
ret = AVERROR_INVALIDDATA; |
|
goto clean_up; |
|
} |
|
|
|
if ((ret = ff_imf_parse_cpl_from_xml_dom(doc, cpl))) { |
|
av_log(NULL, AV_LOG_ERROR, "Cannot parse IMF CPL\n"); |
|
} else { |
|
av_log(NULL, |
|
AV_LOG_INFO, |
|
"IMF CPL ContentTitle: %s\n", |
|
(*cpl)->content_title_utf8); |
|
av_log(NULL, |
|
AV_LOG_INFO, |
|
"IMF CPL Id: " AV_PRI_UUID "\n", |
|
AV_UUID_ARG((*cpl)->id_uuid)); |
|
} |
|
|
|
xmlFreeDoc(doc); |
|
|
|
clean_up: |
|
av_bprint_finalize(&buf, NULL); |
|
|
|
return ret; |
|
}
|
|
|