Skip to content

Commit 3750d5f

Browse files
cedrik-fuoco-adskbernie-laberge
authored andcommitted
Add fallback to search for timecode in FFmpeg format context metadata (#887)
n/a When the streams metadata do not have the timecode information, I added a fallback mechanism that looks at the format context metadata and try to find the `timecode` information. During testing, there were some MXF media files that had the wrong start time. MacOS attention during the review. --------- Signed-off-by: Cédrik Fuoco <cedrik.fuoco@autodesk.com> Signed-off-by: Bernard Laberge <bernard.laberge@autodesk.com>
1 parent 6e29f7d commit 3750d5f

2 files changed

Lines changed: 70 additions & 27 deletions

File tree

src/lib/image/MovieFFMpeg/MovieFFMpeg.cpp

Lines changed: 66 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1515,6 +1515,43 @@ namespace TwkMovie
15151515
return true;
15161516
}
15171517

1518+
void MovieFFMpegReader::populateTimecodeMetadata(
1519+
const AVDictionaryEntry* tcEntry, const AVTimecode& avTimecode)
1520+
{
1521+
ostringstream tcStart, tcFR, tcFlags;
1522+
tcStart << tcEntry->value << " (" << avTimecode.start << ")";
1523+
m_info.proxy.newAttribute("Timecode/Start", tcStart.str());
1524+
tcFR << avTimecode.fps;
1525+
m_info.proxy.newAttribute("Timecode/FrameRate", tcFR.str());
1526+
if (avTimecode.flags & AV_TIMECODE_FLAG_DROPFRAME)
1527+
{
1528+
tcFlags << "Drop ";
1529+
}
1530+
if (avTimecode.flags & AV_TIMECODE_FLAG_24HOURSMAX)
1531+
{
1532+
tcFlags << "24-Hour Max Counter ";
1533+
}
1534+
if (avTimecode.flags & AV_TIMECODE_FLAG_ALLOWNEGATIVE)
1535+
{
1536+
tcFlags << "Allow Negative";
1537+
}
1538+
m_info.proxy.newAttribute("Timecode/Flags", tcFlags.str());
1539+
}
1540+
1541+
AVRational
1542+
MovieFFMpegReader::getTimecodeRate(AVStream* tsStream,
1543+
AVFormatContext* formatContext)
1544+
{
1545+
AVRational tcRate = {tsStream->time_base.den, tsStream->time_base.num};
1546+
1547+
if (isMOVformat(formatContext))
1548+
{
1549+
tcRate = tsStream->avg_frame_rate;
1550+
}
1551+
1552+
return tcRate;
1553+
}
1554+
15181555
int64_t MovieFFMpegReader::getFirstFrame(AVRational rate)
15191556
{
15201557
//
@@ -1531,17 +1568,11 @@ namespace TwkMovie
15311568
/ double(AV_TIME_BASE)));
15321569
int64_t firstFrame = max(int64_t(m_formatStartFrame), int64_t(1));
15331570

1571+
bool foundTimecode = false;
15341572
for (int i = 0; i < m_avFormatContext->nb_streams; i++)
15351573
{
15361574
AVStream* tsStream = m_avFormatContext->streams[i];
1537-
1538-
AVRational tcRate = {tsStream->time_base.den,
1539-
tsStream->time_base.num};
1540-
1541-
if (isMOVformat(m_avFormatContext))
1542-
{
1543-
tcRate = tsStream->avg_frame_rate;
1544-
}
1575+
AVRational tcRate = getTimecodeRate(tsStream, m_avFormatContext);
15451576

15461577
AVDictionaryEntry* tcrEntry;
15471578
tcrEntry = av_dict_get(tsStream->metadata, "reel_name", NULL, 0);
@@ -1566,29 +1597,37 @@ namespace TwkMovie
15661597
AVTimecode avTimecode;
15671598
av_timecode_init_from_string(&avTimecode, tcRate,
15681599
tcEntry->value, m_avFormatContext);
1569-
15701600
// Add the timecode attributes to the movie info
1571-
ostringstream tcStart, tcFR, tcFlags;
1572-
tcStart << tcEntry->value << " (" << avTimecode.start << ")";
1573-
m_info.proxy.newAttribute("Timecode/Start", tcStart.str());
1574-
tcFR << avTimecode.fps;
1575-
m_info.proxy.newAttribute("Timecode/FrameRate", tcFR.str());
1576-
if (avTimecode.flags & AV_TIMECODE_FLAG_DROPFRAME)
1577-
{
1578-
tcFlags << "Drop ";
1579-
}
1580-
if (avTimecode.flags & AV_TIMECODE_FLAG_24HOURSMAX)
1581-
{
1582-
tcFlags << "24-Hour Max Counter ";
1583-
}
1584-
if (avTimecode.flags & AV_TIMECODE_FLAG_ALLOWNEGATIVE)
1585-
{
1586-
tcFlags << "Allow Negative";
1587-
}
1588-
m_info.proxy.newAttribute("Timecode/Flags", tcFlags.str());
1601+
populateTimecodeMetadata(tcEntry, avTimecode);
15891602
m_timecodeTrack = i;
1603+
firstFrame = avTimecode.start;
1604+
foundTimecode = true;
1605+
}
1606+
}
15901607

1608+
// Fallback: check format-level metadata for timecode if not found in
1609+
// any stream
1610+
if (!foundTimecode && m_avFormatContext->metadata)
1611+
{
1612+
AVDictionaryEntry* fmtTcEntry =
1613+
av_dict_get(m_avFormatContext->metadata, "timecode", NULL, 0);
1614+
if (fmtTcEntry && fmtTcEntry->value)
1615+
{
1616+
// Try to get a valid frame rate the same way as the standard
1617+
// mechanism above.
1618+
AVRational tcRate = {24, 1}; // Default to 24fps
1619+
for (unsigned int s = 0; s < m_avFormatContext->nb_streams; ++s)
1620+
{
1621+
AVStream* tsStream = m_avFormatContext->streams[s];
1622+
tcRate = getTimecodeRate(tsStream, m_avFormatContext);
1623+
}
1624+
AVTimecode avTimecode;
1625+
av_timecode_init_from_string(
1626+
&avTimecode, tcRate, fmtTcEntry->value, m_avFormatContext);
1627+
// Add the timecode attributes to the movie info
1628+
populateTimecodeMetadata(fmtTcEntry, avTimecode);
15911629
firstFrame = avTimecode.start;
1630+
foundTimecode = true;
15921631
}
15931632
}
15941633
return firstFrame;

src/lib/image/MovieFFMpeg/MovieFFMpeg/MovieFFMpeg.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@
1414
extern "C"
1515
{
1616
#include <libavutil/pixfmt.h>
17+
#include <libavutil/dict.h>
18+
#include <libavutil/timecode.h>
1719
}
1820

1921
//
@@ -214,6 +216,8 @@ namespace TwkMovie
214216
void finishTrackFBAttrs(FrameBuffer* fb, std::string view);
215217
void trackFromStreamIndex(int index, VideoTrack*& vTrack,
216218
AudioTrack*& aTrack);
219+
void populateTimecodeMetadata(const AVDictionaryEntry* tcEntry, const AVTimecode& avTimecode);
220+
AVRational getTimecodeRate(AVStream* tsStream, AVFormatContext* formatContext);
217221

218222
//
219223
// Audio Methods

0 commit comments

Comments
 (0)