// =================================================================================================
// ADOBE SYSTEMS INCORPORATED
// Copyright 2014 Adobe Systems Incorporated
// All Rights Reserved
//
// NOTICE: Adobe permits you to use, modify, and distribute this file in accordance with the terms
// of the Adobe license agreement accompanying it.
// =================================================================================================
#include "public/include/XMP_Environment.h" // ! XMP_Environment.h must be the first included header.
#include "public/include/XMP_Const.h"
#include "XMPFiles/source/FormatSupport/TimeConversionUtils.hpp"
#include <string>
#include <sstream>
#include <iomanip>
#include <cmath>
namespace TimeConversionUtils {
void DropFrameToHMSF(
XMP_Int64 inFrames,
XMP_Int64 inTimecodeFPS,
XMP_Uns32& outHours,
XMP_Uns32& outMinutes,
XMP_Uns32& outSeconds,
XMP_Uns32& outFrames)
{
XMP_Assert((inTimecodeFPS == 30) || (inTimecodeFPS == 60)); // No other drop frame rates are known at this time.
XMP_Int64 rateAdjustmentFactor = inTimecodeFPS / 30;
XMP_Int64 framesPerHour = (30 * 3600 - 108) * rateAdjustmentFactor;
XMP_Int64 framesPer10Minutes = (30 * 600 - 18) * rateAdjustmentFactor;
XMP_Int64 framesPerMinute = 30 * 60 * rateAdjustmentFactor;
XMP_Int64 framesPerSecond = 30 * rateAdjustmentFactor;
XMP_Int64 dropsPerMinute = 2 * rateAdjustmentFactor;
XMP_Int64 currentFrames = inFrames;
XMP_Int64 framesLeft = currentFrames;
if (currentFrames < 0)
{
framesLeft = -currentFrames;
}
if (framesLeft >= framesPerHour)
{
outHours = static_cast<XMP_Int32>(framesLeft / framesPerHour);
framesLeft = framesLeft % framesPerHour;
}
if (framesLeft >= framesPer10Minutes)
{
outMinutes = static_cast<XMP_Int32>(framesLeft / framesPer10Minutes) * 10;
framesLeft = framesLeft % framesPer10Minutes;
}
if (framesLeft >= framesPerMinute)
{
XMP_Int64 remainingDropMinutes = static_cast<XMP_Int64>((framesLeft - framesPerMinute) /
(framesPerMinute - dropsPerMinute));
++remainingDropMinutes;
outMinutes += static_cast<XMP_Int32>(remainingDropMinutes);
framesLeft -= ((framesPerMinute - dropsPerMinute) * remainingDropMinutes);
}
if (framesLeft >= framesPerSecond)
{
outSeconds = static_cast<XMP_Int32>(framesLeft / framesPerSecond);
}
outFrames = static_cast<XMP_Int32>(framesLeft % framesPerSecond);
}
bool ConvertSamplesToTimecode(
std::string & outTimecode,
XMP_Int64 inSamples,
XMP_Uns64 inSampleRate,
XMP_Int64 inTimecodeFPS,
bool inIsDrop,
bool inIsNoDrop,
bool inShowOnlyFrames = false,
bool inOnlyShowSeconds = false ,
bool inNoZeroPrefix = false ,
bool inShowFractional = false ,
bool inNoHours = false )
{
if (!(inIsDrop ? !inIsNoDrop : true))
{
XMP_Assert( !(inIsDrop ? !inIsNoDrop : true) );
return false;
}
if (inSampleRate == 0)
{
outTimecode = "00:00:00:00";
return true;
}
std::string possibleNegStr;
if (inSamples < 0)
{
inSamples *= -1;
possibleNegStr = "-";
}
XMP_Int64 rateNumerator = inTimecodeFPS;
XMP_Int64 rateDenominator = 1;
if (inIsDrop || inIsNoDrop)
{
rateNumerator = 1000 * inTimecodeFPS;
rateDenominator = 1001;
}
XMP_Int64 frameNumber = (inSamples * rateNumerator) / (inSampleRate * rateDenominator);
XMP_Int64 hundredthsOfFrames = ((inSamples * rateNumerator * 100) / (inSampleRate * rateDenominator)) % 100;
std::stringstream stream;
double fSamples = static_cast<double>(inSamples);
double fSampleRate = static_cast<double>(inSampleRate);
if (inIsDrop)
{
if (inShowOnlyFrames)
{
double fAdjustmentFactor = static_cast<double>(inTimecodeFPS) / 30.0;
double fCorrectionRatio = (600.0 * static_cast<double>(inTimecodeFPS) / 1.001) / (17982.0 * fAdjustmentFactor);
double fValue = fSamples * fCorrectionRatio / fSampleRate;
// "%ld"
stream << static_cast<int>(fValue * 29.97 * fAdjustmentFactor);
}
else
{
XMP_Uns32 hours = 0;
XMP_Uns32 minutes = 0;
XMP_Uns32 seconds = 0;
XMP_Uns32 frames = 0;
DropFrameToHMSF(
frameNumber,
inTimecodeFPS,
hours,
minutes,
seconds,
frames);
hours = hours % 24;
// "%02d;%02d;%02d;%02d"
stream << possibleNegStr << std::setfill('0') << std::setw(2) << static_cast<int>(hours)
<< ";"
<< std::setfill('0') << std::setw(2) << static_cast<int>(minutes)
<< ";"
<< std::setfill('0') << std::setw(2) << static_cast<int>(seconds)
<< ";"
<< std::setfill('0') << std::setw(2) << static_cast<int>(frames);
possibleNegStr.clear();
}
}
else
{
if (inShowOnlyFrames)
{
// "%ld"
stream << static_cast<int>(frameNumber);
}
else
{
XMP_Int64 framesPerMinute = inTimecodeFPS * 60;
XMP_Int64 framesPerHour = framesPerMinute * 60;
XMP_Int64 iHours = frameNumber / framesPerHour;
frameNumber %= framesPerHour;
XMP_Int64 mins = frameNumber / framesPerMinute;
frameNumber %= framesPerMinute;
XMP_Int64 seconds = frameNumber / inTimecodeFPS;
XMP_Int64 ss = frameNumber % inTimecodeFPS;
XMP_Int64 s = seconds;
if (inNoHours)
{
mins += iHours * 60;
iHours = 0;
}
if (((iHours) || (!inNoZeroPrefix)) && (!inNoHours))
{
iHours = iHours % 24;
// "%02ld:"
stream << possibleNegStr << std::setfill('0') << std::setw(2) << static_cast<int>(iHours)
<< ":";
possibleNegStr.clear();
}
if ((iHours) || (!inNoZeroPrefix))
{
// "%02ld:"
stream << possibleNegStr << std::setfill('0') << std::setw(2) << static_cast<int>(mins)
<< ":";
possibleNegStr.clear();
}
else if (mins)
{
// "%ld:"
stream << possibleNegStr << static_cast<int>(mins)
<< ":";
possibleNegStr.clear();
}
if (inOnlyShowSeconds)
{
// "%02ld"
stream << possibleNegStr << std::setfill('0') << std::setw(2) << static_cast<int>(s);
possibleNegStr.clear();
}
else
{
if ((iHours) || (mins) || (!inNoZeroPrefix))
{
// "%02ld:"
stream << possibleNegStr << std::setfill('0') << std::setw(2) << static_cast<int>(s)
<< ":";
possibleNegStr.clear();
}
else if (s)
{
// "%ld:"
stream << possibleNegStr << static_cast<int>(s)
<< ":";
possibleNegStr.clear();
}
if ((iHours) || (mins) || (s) || (!inNoZeroPrefix))
{
if (inTimecodeFPS <= 10)
{
// "%01ld"
stream << possibleNegStr << std::setfill('0') << std::setw(1) << static_cast<int>(ss);
possibleNegStr.clear();
}
else if ((inTimecodeFPS <= 100))
{
// "%02ld"
stream << possibleNegStr << std::setfill('0') << std::setw(2) << static_cast<int>(ss);
possibleNegStr.clear();
}
else if (inTimecodeFPS <= 1000)
{
// "%03ld"
stream << possibleNegStr << std::setfill('0') << std::setw(3) << static_cast<int>(ss);
possibleNegStr.clear();
}
else
{
// "%04ld"
stream << possibleNegStr << std::setfill('0') << std::setw(4) << static_cast<int>(ss);
possibleNegStr.clear();
}
}
else
{
// "%ld"
stream << possibleNegStr << static_cast<int>(ss);
possibleNegStr.clear();
}
if (inShowFractional)
{
// ".%02d"
stream << possibleNegStr << "."
<< std::setfill('0') << std::setw(2) << static_cast<int>(hundredthsOfFrames);
possibleNegStr.clear();
}
}
}
}
outTimecode = stream.str();
return true;
}
bool ConvertSamplesToSMPTETimecode(
std::string & outTimecode,
XMP_Int64 inSamples,
XMP_Uns64 inSampleRate,
const std::string & inTimecodeFormat )
{
bool result = false;
if ( inTimecodeFormat.compare( "24Timecode" ) == 0 ) {
result = ConvertSamplesToTimecode( outTimecode, inSamples, inSampleRate, 24, false, false );
} else if ( inTimecodeFormat.compare( "25Timecode" ) == 0 ) {
result = ConvertSamplesToTimecode( outTimecode, inSamples, inSampleRate, 25, false, false );
} else if ( inTimecodeFormat.compare( "2997DropTimecode") == 0 ) {
result = ConvertSamplesToTimecode( outTimecode, inSamples, inSampleRate, 30, true, false );
} else if ( inTimecodeFormat.compare( "2997NonDropTimecode") == 0 ) {
result = ConvertSamplesToTimecode( outTimecode, inSamples, inSampleRate, 30, false, true );
} else if ( inTimecodeFormat.compare( "30Timecode" ) == 0 ) {
result = ConvertSamplesToTimecode( outTimecode, inSamples, inSampleRate, 30, false, false );
} else if ( inTimecodeFormat.compare( "50Timecode" ) == 0 ) {
result = ConvertSamplesToTimecode( outTimecode, inSamples, inSampleRate, 50, false, false );
} else if ( inTimecodeFormat.compare( "5994DropTimecode" ) == 0 ) {
result = ConvertSamplesToTimecode( outTimecode, inSamples, inSampleRate, 60, true, false );
} else if ( inTimecodeFormat.compare( "5994NonDropTimecode" ) == 0 ) {
result = ConvertSamplesToTimecode( outTimecode, inSamples, inSampleRate, 60, false, true );
} else if ( inTimecodeFormat.compare( "60Timecode" ) == 0 ) {
result = ConvertSamplesToTimecode( outTimecode, inSamples, inSampleRate, 60, false, false );
} else if ( inTimecodeFormat.compare( "23976Timecode" ) == 0 ) {
result = ConvertSamplesToTimecode(outTimecode, inSamples, inSampleRate, 24, false, true);
}
return result;
}
bool StringToNumber(
XMP_Int32 & outNumber,
const std::string & inString )
{
bool numberFound = false;
outNumber = 0;
for ( size_t i = 0, endIndex = inString.size(); i < endIndex; i++ ) {
XMP_Int32 digit = inString[i] - '0';
if ( digit >= 0 && digit <= 9 ) {
outNumber *= 10;
outNumber += digit;
numberFound = true;
} else {
return numberFound;
}
}
return numberFound;
}
void ParseTimeCodeString(
const std::string & inTimecode,
XMP_Int32 & outHours,
XMP_Int32 & outMinutes,
XMP_Int32 & outSeconds,
XMP_Int32 & outFrames,
XMP_Int32 & outFractionalFrameNumerator,
XMP_Int32 & outFractionalFrameDenominator )
{
XMP_Int32 m1 = 0;
XMP_Int32 m2 = 0;
XMP_Int32 m3 = 0;
XMP_Int32 m4 = 0;
XMP_Int32 m5 = 0;
bool hasFoundDecimal = false;
XMP_Int32 digitCount = 0;
outFractionalFrameNumerator = 0;
outFractionalFrameDenominator = 1;
std::string::const_iterator iter = inTimecode.begin();
std::string::const_iterator iterEnd = inTimecode.end();
while (1)
{ // Skip leading white space
while ( iter != iterEnd && (*iter < '0' || *iter > '9') )
{
if (*iter == '.')
hasFoundDecimal = true;
iter++;
} // hh:mm:ss:ff.ddd
if (iter == iterEnd)
break;
if (!hasFoundDecimal)
{
// get MSB digits
StringToNumber(m1, std::string(iter, iterEnd));
// Skip the digits
while (iter != iterEnd && (*iter >= '0' && *iter <= '9'))
iter++;
// Skip the white space, note if "." or ":" ("." signifies decimal portion of frame)
while ( iter != iterEnd && (*iter < '0' || *iter > '9'))
{
if (*iter == '.')
hasFoundDecimal = true;
iter++;
}
if (iter == iterEnd)
break;
}
// shift and scan next MSB digits
if (!hasFoundDecimal)
{
m2 = m1;
digitCount = static_cast< XMP_Int32 >( iterEnd - iter );
StringToNumber(m1, std::string(iter, iterEnd));
// Skip the digits
while ( iter != iterEnd && (*iter >= '0' && *iter <= '9') )
iter++;
// Skip the white space, note if "." or ":" ("." signifies
// decimal portion of frame)
while (iter != iterEnd && (*iter < '0' || *iter > '9'))
{
if (*iter == '.')
hasFoundDecimal = true;
iter++;
}
}
if (iter == iterEnd)
break;
m3 = m2;
m2 = m1;
digitCount = static_cast< XMP_Int32 >( iterEnd - iter );
StringToNumber(m1, std::string(iter, iterEnd));
if (hasFoundDecimal)
break;
while (iter != iterEnd && (*iter >= '0' && *iter <= '9'))
iter++;
// Skip the white space, note if "." or ":" ("." signifies decimal portion of frame)
while (iter != iterEnd && (*iter < '0' || *iter > '9'))
{
if (*iter == '.')
hasFoundDecimal = true;
iter++;
}
if (iter == iterEnd)
break;
m4 = m3;
m3 = m2;
m2 = m1;
digitCount = static_cast< XMP_Int32 >( iterEnd - iter );
StringToNumber(m1, std::string(iter, iterEnd));
if (hasFoundDecimal)
break;
while (iter != iterEnd && (*iter >= '0' && *iter <= '9'))
{
iter++;
}
// Skip the white space, note if "." or ":" ("." signifies decimal portion of frame)
while (iter != iterEnd && (*iter < '0' || *iter > '9'))
{
if (*iter == '.')
hasFoundDecimal = true;
iter++;
}
if (iter == iterEnd)
break;
m5 = m4;
m4 = m3;
m3 = m2;
m2 = m1;
digitCount = static_cast< XMP_Int32 >( iterEnd - iter );
StringToNumber(m1, std::string(iter, iterEnd));
break;
}
if (hasFoundDecimal)
{
outFractionalFrameDenominator = static_cast<XMP_Int32>(pow(10.0, digitCount) + 0.5);
outFractionalFrameNumerator = m1;
m1 = m2;
m2 = m3;
m3 = m4;
m4 = m5;
m5 = 0;
}
outHours = m4;
outMinutes = m3;
outSeconds = m2;
outFrames = m1;
}
bool ConvertTimecodeToSamples(
XMP_Int64 & outSamples,
const std::string & inTimecode,
XMP_Uns64 inSampleRate,
XMP_Int64 inTimecodeFPS,
bool inNTSC,
bool inDropFrame)
{
/// @TODO: Ensure that negative and >64-bit values are OK and work as expected.
if (inTimecode.empty())
{
outSamples = static_cast<XMP_Int64>(-1);
return true;
}
XMP_Int32 hours;
XMP_Int32 minutes;
XMP_Int32 seconds;
XMP_Int32 frames;
XMP_Int32 fractionalFrameNumerator;
XMP_Int32 fractionalFrameDenominator;
ParseTimeCodeString(inTimecode, hours, minutes, seconds, frames, fractionalFrameNumerator, fractionalFrameDenominator);
XMP_Int64 framesPerSecond = inTimecodeFPS;
XMP_Int64 framesPerMinute = framesPerSecond * 60;
XMP_Int64 framesPerHour = framesPerMinute * 60;
XMP_Int64 wholeFrames = 0;
XMP_Int64 frameRateNumerator = inTimecodeFPS;
XMP_Int64 frameRateDenominator = 1;
if (inNTSC)
{
frameRateNumerator = 1000 * inTimecodeFPS;
frameRateDenominator = 1001;
}
if (inDropFrame)
{
XMP_Int64 frameGroupDropped = 2 * inTimecodeFPS / 30; // 2 or 4 frames dropped at a time.
XMP_Int64 framesPerHourDropped = 108 * inTimecodeFPS / 30;
framesPerHour -= framesPerHourDropped;
XMP_Int64 framesPerTenMinutes = framesPerHour / 6;
XMP_Assert( framesPerHour % 6 == 0 ); //, "Drop frame not supported on the given frame rate."
XMP_Int64 framesDroppedWithinTheLeastTenMinutes = 0;
if (minutes % 10 != 0)
{
if ((seconds == 0) && (frames < frameGroupDropped))
{
frames = static_cast<XMP_Int32>(frameGroupDropped); // Make sure invalid strings snap to the next higher valid frame.
}
framesDroppedWithinTheLeastTenMinutes = (minutes % 10) * frameGroupDropped;
}
wholeFrames = hours * framesPerHour + (minutes / 10) * framesPerTenMinutes + (minutes % 10) * framesPerMinute + seconds * framesPerSecond + frames - framesDroppedWithinTheLeastTenMinutes;
}
else
{
wholeFrames = hours * framesPerHour + minutes * framesPerMinute + seconds * framesPerSecond + frames;
}
if (frameRateNumerator * fractionalFrameDenominator == 0)
{
XMP_Assert( "Divide by zero in ConvertTimecodeToSamples" );
outSamples = 0;
return true;
}
//
// (frame count / frames per second) * samples per second = sample count.
//
// ((frame count + fractionalFrameNumerator / fractionalFrameDenominator) * samples per second / frames per second) = sample count.
// or in integer math:
// ((frame count * fractionalFrameDenominator + fractionalFrameNumerator) * samples per second / (frames per second * fractionalFrameDenominator)) = sample count.
// with rounding correction to give us the first sample contained entirely in the frame:
// There is a non-zero probability of rolling over this integer arithmetic.
double integerFailsafeNumerator = ((static_cast<double>(wholeFrames) * static_cast<double>(fractionalFrameDenominator) + fractionalFrameNumerator) * static_cast<double>(frameRateDenominator) * static_cast<double>(inSampleRate) + (frameRateNumerator * fractionalFrameDenominator - 1));
if (integerFailsafeNumerator > static_cast<double>(0x7000000000000000LL))
{
outSamples = static_cast<XMP_Int64>(integerFailsafeNumerator / (static_cast<double>(frameRateNumerator) * static_cast<double>(fractionalFrameDenominator)));
}
else
{
outSamples = ((wholeFrames * fractionalFrameDenominator + fractionalFrameNumerator) * frameRateDenominator * inSampleRate + (frameRateNumerator * fractionalFrameDenominator - 1)) / (frameRateNumerator * fractionalFrameDenominator);
}
return true;
}
bool ConvertSMPTETimecodeToSamples(
XMP_Int64 & outSamples,
const std::string & inTimecode,
XMP_Uns64 inSampleRate,
const std::string & inTimecodeFormat )
{
bool result = false;
if ( inTimecodeFormat.compare( "24Timecode" ) == 0 ) {
result = ConvertTimecodeToSamples( outSamples, inTimecode, inSampleRate, 24, false, false );
} else if ( inTimecodeFormat.compare( "25Timecode" ) == 0 ) {
result = ConvertTimecodeToSamples( outSamples, inTimecode, inSampleRate, 25, false, false );
} else if ( inTimecodeFormat.compare( "2997DropTimecode") == 0 ) {
result = ConvertTimecodeToSamples( outSamples, inTimecode, inSampleRate, 30, true, true );
} else if ( inTimecodeFormat.compare( "2997NonDropTimecode") == 0 ) {
result = ConvertTimecodeToSamples( outSamples, inTimecode, inSampleRate, 30, true, false );
} else if ( inTimecodeFormat.compare( "30Timecode" ) == 0 ) {
result = ConvertTimecodeToSamples( outSamples, inTimecode, inSampleRate, 30, false, false );
} else if ( inTimecodeFormat.compare( "50Timecode" ) == 0 ) {
result = ConvertTimecodeToSamples( outSamples, inTimecode, inSampleRate, 50, false, false );
} else if ( inTimecodeFormat.compare( "5994DropTimecode" ) == 0 ) {
result = ConvertTimecodeToSamples( outSamples, inTimecode, inSampleRate, 60, true, true );
} else if ( inTimecodeFormat.compare( "5994NonDropTimecode" ) == 0 ) {
result = ConvertTimecodeToSamples( outSamples, inTimecode, inSampleRate, 60, true, false );
} else if ( inTimecodeFormat.compare( "60Timecode" ) == 0 ) {
result = ConvertTimecodeToSamples( outSamples, inTimecode, inSampleRate, 60, false, false );
} else if ( inTimecodeFormat.compare( "23976Timecode" ) == 0 ) {
result = ConvertTimecodeToSamples( outSamples, inTimecode, inSampleRate, 24, true, false );
}
return result;
}
}