mirror of https://git.ffmpeg.org/ffmpeg.git
avformat/imfdec: use CPL start timecode if available
The IMF CPL contains an optional timecode start address. This patch reads the latter, if present, into the context's timecode metadata parameter. This addresses https://trac.ffmpeg.org/ticket/9842.
This commit is contained in:
parent
5ccd4d3060
commit
94922f6cab
|
@ -59,6 +59,7 @@
|
||||||
#include "libavformat/avio.h"
|
#include "libavformat/avio.h"
|
||||||
#include "libavutil/rational.h"
|
#include "libavutil/rational.h"
|
||||||
#include "libavutil/uuid.h"
|
#include "libavutil/uuid.h"
|
||||||
|
#include "libavutil/timecode.h"
|
||||||
#include <libxml/tree.h>
|
#include <libxml/tree.h>
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -130,6 +131,7 @@ typedef struct FFIMFCPL {
|
||||||
AVUUID id_uuid; /**< CompositionPlaylist/Id element */
|
AVUUID id_uuid; /**< CompositionPlaylist/Id element */
|
||||||
xmlChar *content_title_utf8; /**< CompositionPlaylist/ContentTitle element */
|
xmlChar *content_title_utf8; /**< CompositionPlaylist/ContentTitle element */
|
||||||
AVRational edit_rate; /**< CompositionPlaylist/EditRate element */
|
AVRational edit_rate; /**< CompositionPlaylist/EditRate element */
|
||||||
|
AVTimecode *tc; /**< CompositionPlaylist/CompositionTimecode element */
|
||||||
FFIMFMarkerVirtualTrack *main_markers_track; /**< Main Marker Virtual Track */
|
FFIMFMarkerVirtualTrack *main_markers_track; /**< Main Marker Virtual Track */
|
||||||
FFIMFTrackFileVirtualTrack *main_image_2d_track; /**< Main Image Virtual Track */
|
FFIMFTrackFileVirtualTrack *main_image_2d_track; /**< Main Image Virtual Track */
|
||||||
uint32_t main_audio_track_count; /**< Number of Main Audio Virtual Tracks */
|
uint32_t main_audio_track_count; /**< Number of Main Audio Virtual Tracks */
|
||||||
|
|
|
@ -116,6 +116,22 @@ int ff_imf_xml_read_uint32(xmlNodePtr element, uint32_t *number)
|
||||||
return ret;
|
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)
|
static void imf_base_virtual_track_init(FFIMFBaseVirtualTrack *track)
|
||||||
{
|
{
|
||||||
memset(track->id_uuid, 0, sizeof(track->id_uuid));
|
memset(track->id_uuid, 0, sizeof(track->id_uuid));
|
||||||
|
@ -179,6 +195,90 @@ static int fill_content_title(xmlNodePtr cpl_element, FFIMFCPL *cpl)
|
||||||
return 0;
|
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);
|
||||||
|
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)
|
static int fill_edit_rate(xmlNodePtr cpl_element, FFIMFCPL *cpl)
|
||||||
{
|
{
|
||||||
xmlNodePtr element = NULL;
|
xmlNodePtr element = NULL;
|
||||||
|
@ -682,6 +782,8 @@ int ff_imf_parse_cpl_from_xml_dom(xmlDocPtr doc, FFIMFCPL **cpl)
|
||||||
goto cleanup;
|
goto cleanup;
|
||||||
if ((ret = fill_edit_rate(cpl_element, *cpl)))
|
if ((ret = fill_edit_rate(cpl_element, *cpl)))
|
||||||
goto cleanup;
|
goto cleanup;
|
||||||
|
if ((ret = fill_timecode(cpl_element, *cpl)))
|
||||||
|
goto cleanup;
|
||||||
if ((ret = fill_virtual_tracks(cpl_element, *cpl)))
|
if ((ret = fill_virtual_tracks(cpl_element, *cpl)))
|
||||||
goto cleanup;
|
goto cleanup;
|
||||||
|
|
||||||
|
@ -731,6 +833,7 @@ static void imf_cpl_init(FFIMFCPL *cpl)
|
||||||
av_uuid_nil(cpl->id_uuid);
|
av_uuid_nil(cpl->id_uuid);
|
||||||
cpl->content_title_utf8 = NULL;
|
cpl->content_title_utf8 = NULL;
|
||||||
cpl->edit_rate = av_make_q(0, 1);
|
cpl->edit_rate = av_make_q(0, 1);
|
||||||
|
cpl->tc = NULL;
|
||||||
cpl->main_markers_track = NULL;
|
cpl->main_markers_track = NULL;
|
||||||
cpl->main_image_2d_track = NULL;
|
cpl->main_image_2d_track = NULL;
|
||||||
cpl->main_audio_track_count = 0;
|
cpl->main_audio_track_count = 0;
|
||||||
|
@ -753,6 +856,9 @@ void ff_imf_cpl_free(FFIMFCPL *cpl)
|
||||||
if (!cpl)
|
if (!cpl)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
if (cpl->tc)
|
||||||
|
av_freep(&cpl->tc);
|
||||||
|
|
||||||
xmlFree(cpl->content_title_utf8);
|
xmlFree(cpl->content_title_utf8);
|
||||||
|
|
||||||
imf_marker_virtual_track_free(cpl->main_markers_track);
|
imf_marker_virtual_track_free(cpl->main_markers_track);
|
||||||
|
|
|
@ -627,6 +627,8 @@ static int imf_read_header(AVFormatContext *s)
|
||||||
IMFContext *c = s->priv_data;
|
IMFContext *c = s->priv_data;
|
||||||
char *asset_map_path;
|
char *asset_map_path;
|
||||||
char *tmp_str;
|
char *tmp_str;
|
||||||
|
AVDictionaryEntry* tcr;
|
||||||
|
char tc_buf[AV_TIMECODE_STR_SIZE];
|
||||||
int ret = 0;
|
int ret = 0;
|
||||||
|
|
||||||
c->interrupt_callback = &s->interrupt_callback;
|
c->interrupt_callback = &s->interrupt_callback;
|
||||||
|
@ -646,6 +648,15 @@ static int imf_read_header(AVFormatContext *s)
|
||||||
if ((ret = ff_imf_parse_cpl(s->pb, &c->cpl)) < 0)
|
if ((ret = ff_imf_parse_cpl(s->pb, &c->cpl)) < 0)
|
||||||
return ret;
|
return ret;
|
||||||
|
|
||||||
|
tcr = av_dict_get(s->metadata, "timecode", NULL, 0);
|
||||||
|
if (!tcr && c->cpl->tc) {
|
||||||
|
ret = av_dict_set(&s->metadata, "timecode",
|
||||||
|
av_timecode_make_string(c->cpl->tc, tc_buf, 0), 0);
|
||||||
|
if (ret)
|
||||||
|
return ret;
|
||||||
|
av_log(s, AV_LOG_INFO, "Setting timecode to IMF CPL timecode %s\n", tc_buf);
|
||||||
|
}
|
||||||
|
|
||||||
av_log(s,
|
av_log(s,
|
||||||
AV_LOG_DEBUG,
|
AV_LOG_DEBUG,
|
||||||
"parsed IMF CPL: " AV_PRI_URN_UUID "\n",
|
"parsed IMF CPL: " AV_PRI_URN_UUID "\n",
|
||||||
|
|
Loading…
Reference in New Issue