File indexing completed on 2024-12-08 04:27:14

0001 /*
0002     SPDX-FileCopyrightText: 2003 Jason Wood <jasonwood@blueyonder.co.uk>
0003     SPDX-FileCopyrightText: 2010 Jean-Baptiste Mardelle <jb@kdenlive.org>
0004 
0005     SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
0006 */
0007 
0008 /*
0009 
0010  Timecode calculation code for reference
0011  If we ever use Quicktime timecode with 50.94 Drop frame, keep in mind that there is a bug in the Quicktime code
0012 
0013 //CONVERT A FRAME NUMBER TO DROP FRAME TIMECODE
0014 //Code by David Heidelberger, adapted from Andrew Duncan
0015 //Given an int called framenumber and a double called framerate
0016 //Framerate should be 29.97, 59.94, or 23.976, otherwise the calculations will be off.
0017 
0018 int d;
0019 int m;
0020 
0021 int dropFrames = round(framerate * .066666); //Number of frames to drop on the minute marks is the nearest integer to 6% of the framerate
0022 int framesPerHour = round(framerate*60*60); //Number of frames in an hour
0023 int framesPer24Hours = framesPerHour*24; //Number of frames in a day - timecode rolls over after 24 hours
0024 int framesPer10Minutes = round(framerate * 60 * 10); //Number of frames per ten minutes
0025 int framesPerMinute = round(framerate)*60)-  dropFrames; //Number of frames per minute is the round of the framerate * 60 minus the number of dropped frames
0026 
0027 if (framenumber<0) //Negative time. Add 24 hours.
0028 {
0029     framenumber=framesPer24Hours+framenumber;
0030 }
0031 
0032 //If framenumber is greater than 24 hrs, next operation will rollover clock
0033 framenumber = framenumber % framesPer24Hours; //% is the modulus operator, which returns a remainder. a % b = the remainder of a/b
0034 
0035 d = framenumber\framesPer10Minutes; // \ means integer division, which is a/b without a remainder. Some languages you could use floor(a/b)
0036 m = framenumber % framesPer10Minutes;
0037 
0038 if (m>1)
0039 {
0040     framenumber=framenumber + (dropFrames*9*d) + dropFrames*((m-dropFrames)\framesPerMinute);
0041 }
0042 else
0043 {
0044     framenumber = framenumber + dropFrames*9*d;
0045 }
0046 
0047 int frRound = round(framerate);
0048 int frames = framenumber % frRound;
0049 int seconds = (framenumber \ frRound) % 60;
0050 int minutes = ((framenumber \ frRound) \ 60) % 60;
0051 int hours = (((framenumber \ frRound) \ 60) \ 60);
0052 
0053 ------------------------------------------------------------------------------------
0054 
0055 //CONVERT DROP FRAME TIMECODE TO A FRAME NUMBER
0056 //Code by David Heidelberger, adapted from Andrew Duncan
0057 //Given ints called hours, minutes, seconds, frames, and a double called framerate
0058 
0059 int dropFrames = round(framerate*.066666); //Number of drop frames is 6% of framerate rounded to nearest integer
0060 int timeBase = round(framerate); //We don’t need the exact framerate anymore, we just need it rounded to nearest integer
0061 
0062 int hourFrames = timeBase*60*60; //Number of frames per hour (non-drop)
0063 int minuteFrames = timeBase*60; //Number of frames per minute (non-drop)
0064 int totalMinutes = (60*hours) + minutes; //Total number of minutes
0065 int frameNumber = ((hourFrames * hours) + (minuteFrames * minutes) + (timeBase * seconds) + frames) - (dropFrames * (totalMinutes - (totalMinutes \ 10)));
0066 return frameNumber;
0067 
0068 */
0069 
0070 #include "timecode.h"
0071 
0072 #include "kdenlive_debug.h"
0073 
0074 Timecode::Timecode(Formats format, double framesPerSecond)
0075 {
0076     setFormat(framesPerSecond, format);
0077 }
0078 
0079 Timecode::~Timecode() = default;
0080 
0081 void Timecode::setFormat(double framesPerSecond, Formats format)
0082 {
0083     m_displayedFramesPerSecond = qRound(framesPerSecond);
0084     m_dropFrameTimecode = qFuzzyCompare(framesPerSecond, 30000.0 / 1001.0);
0085     m_format = format;
0086     m_realFps = framesPerSecond;
0087     if (m_dropFrameTimecode) {
0088         m_dropFrames = round(m_realFps * .066666);     // Number of frames to drop on the minute marks is the nearest integer to 6% of the framerate
0089         m_framesPer10Minutes = round(m_realFps * 600); // Number of frames per ten minutes
0090     }
0091 }
0092 
0093 Timecode::Formats Timecode::format() const
0094 {
0095     return m_format;
0096 }
0097 
0098 double Timecode::fps() const
0099 {
0100     return m_realFps;
0101 }
0102 
0103 const QString Timecode::mask(const GenTime &t) const
0104 {
0105     if (m_realFps > 100) {
0106         if (t < GenTime()) {
0107             if (m_dropFrameTimecode) {
0108                 return QStringLiteral("#99:99:99,999");
0109             }
0110             return QStringLiteral("#99:99:99:999");
0111         }
0112         if (m_dropFrameTimecode) {
0113             return QStringLiteral("99:99:99,999");
0114         }
0115         return QStringLiteral("99:99:99:999");
0116     }
0117     if (t < GenTime()) {
0118         if (m_dropFrameTimecode) {
0119             return QStringLiteral("#99:99:99,99");
0120         }
0121         return QStringLiteral("#99:99:99:99");
0122     }
0123     if (m_dropFrameTimecode) {
0124         return QStringLiteral("99:99:99,99");
0125     }
0126     return QStringLiteral("99:99:99:99");
0127 }
0128 
0129 QString Timecode::reformatSeparators(QString duration) const
0130 {
0131     if (m_dropFrameTimecode) {
0132         return duration.replace(8, 1, ';');
0133     }
0134     return duration.replace(8, 1, ':');
0135 }
0136 
0137 int Timecode::getFrameCount(const QString &duration) const
0138 {
0139     if (duration.isEmpty()) {
0140         return 0;
0141     }
0142     int hours, minutes, seconds, frames;
0143     int offset = 0;
0144     if (duration.at(0) == '-') {
0145         offset = 1;
0146 #if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
0147         hours = duration.midRef(1, 2).toInt();
0148 #else
0149         hours = QStringView(duration).mid(1, 2).toInt();
0150 #endif
0151     } else {
0152 #if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
0153         hours = duration.leftRef(2).toInt();
0154 #else
0155         hours = QStringView(duration).left(2).toInt();
0156 #endif
0157     }
0158 #if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
0159     minutes = duration.midRef(3 + offset, 2).toInt();
0160     seconds = duration.midRef(6 + offset, 2).toInt();
0161     frames = duration.rightRef(duration.length() - 9 - offset).toInt();
0162 #else
0163     minutes = QStringView(duration).mid(3 + offset, 2).toInt();
0164     seconds = QStringView(duration).mid(6 + offset, 2).toInt();
0165     frames = QStringView(duration).right(duration.length() - 9 - offset).toInt();
0166 #endif
0167     if (m_dropFrameTimecode) {
0168         // CONVERT DROP FRAME TIMECODE TO A FRAME NUMBER
0169         // Code by David Heidelberger, adapted from Andrew Duncan
0170         // Given ints called hours, minutes, seconds, frames, and a double called framerate
0171 
0172         int totalMinutes = (60 * hours) + minutes; // Total number of minutes
0173         int frameNumber =
0174             ((m_displayedFramesPerSecond * 3600 * hours) + (m_displayedFramesPerSecond * 60 * minutes) + (m_displayedFramesPerSecond * seconds) + frames) -
0175             (m_dropFrames * (totalMinutes - floor(totalMinutes / 10)));
0176         return frameNumber;
0177     }
0178     return qRound((hours * 3600.0 + minutes * 60.0 + seconds) * m_realFps + frames);
0179 }
0180 
0181 QString Timecode::getDisplayTimecode(const GenTime &time, bool frameDisplay) const
0182 {
0183     if (frameDisplay) {
0184         return QString::number((int)time.frames(m_realFps));
0185     }
0186     return getTimecode(time);
0187 }
0188 
0189 QString Timecode::getTimecode(const GenTime &time) const
0190 {
0191     switch (m_format) {
0192     case HH_MM_SS_FF:
0193         return getTimecodeHH_MM_SS_FF(time);
0194         break;
0195     case HH_MM_SS_HH:
0196         return getTimecodeHH_MM_SS_HH(time);
0197         break;
0198     case Frames:
0199         return getTimecodeFrames(time);
0200         break;
0201     case Seconds:
0202         return getTimecodeSeconds(time);
0203         break;
0204     default:
0205         qCWarning(KDENLIVE_LOG) << "Unknown timecode format specified, defaulting to HH_MM_SS_FF" << '\n';
0206         return getTimecodeHH_MM_SS_FF(time);
0207     }
0208 }
0209 
0210 const QString Timecode::getDisplayTimecodeFromFrames(int frames, bool frameDisplay) const
0211 {
0212     if (frameDisplay) {
0213         return QString::number(frames);
0214     }
0215     return getTimecodeHH_MM_SS_FF(frames);
0216 }
0217 
0218 const QString Timecode::getTimecodeFromFrames(int frames) const
0219 {
0220     return getTimecodeHH_MM_SS_FF(frames);
0221 }
0222 
0223 // static
0224 QString Timecode::getStringTimecode(int frames, const double &fps, bool showFrames)
0225 {
0226     // Returns the timecode in an hh:mm:ss format
0227 
0228     bool negative = false;
0229     if (frames < 0) {
0230         negative = true;
0231         frames = qAbs(frames);
0232     }
0233 
0234     auto seconds = (int)(frames / fps);
0235     int frms = frames % qRound(fps);
0236     int minutes = seconds / 60;
0237     seconds = seconds % 60;
0238     int hours = minutes / 60;
0239     minutes = minutes % 60;
0240     QString text = showFrames
0241                        ? QString("%1:%2:%3.%4")
0242                              .arg(hours, 2, 10, QLatin1Char('0'))
0243                              .arg(minutes, 2, 10, QLatin1Char('0'))
0244                              .arg(seconds, 2, 10, QLatin1Char('0'))
0245                              .arg(frms, fps > 100 ? 3 : 2, 10, QLatin1Char('0'))
0246                        : QString("%1:%2:%3").arg(hours, 2, 10, QLatin1Char('0')).arg(minutes, 2, 10, QLatin1Char('0')).arg(seconds, 2, 10, QLatin1Char('0'));
0247     if (negative) {
0248         text.prepend('-');
0249     }
0250     return text;
0251 }
0252 
0253 const QString Timecode::getTimecodeHH_MM_SS_FF(const GenTime &time) const
0254 {
0255     if (m_dropFrameTimecode) {
0256         return getTimecodeDropFrame(time);
0257     }
0258     return getTimecodeHH_MM_SS_FF((int)time.frames(m_realFps));
0259 }
0260 
0261 const QString Timecode::getTimecodeHH_MM_SS_FF(int frames) const
0262 {
0263     if (m_dropFrameTimecode) {
0264         return getTimecodeDropFrame(frames);
0265     }
0266 
0267     bool negative = false;
0268     if (frames < 0) {
0269         negative = true;
0270         frames = qAbs(frames);
0271     }
0272 
0273     int hours = frames / (m_realFps * 3600);
0274     frames -= floor(hours * 3600 * m_realFps);
0275 
0276     int minutes = frames / (m_realFps * 60);
0277     frames -= floor(minutes * 60 * m_realFps);
0278 
0279     int seconds = frames / m_realFps;
0280     frames -= ceil(seconds * m_realFps);
0281     QString text = QString("%1:%2:%3:%4")
0282                        .arg(hours, 2, 10, QLatin1Char('0'))
0283                        .arg(minutes, 2, 10, QLatin1Char('0'))
0284                        .arg(seconds, 2, 10, QLatin1Char('0'))
0285                        .arg(frames, m_realFps > 100 ? 3 : 2, 10, QLatin1Char('0'));
0286     if (negative) {
0287         text.prepend('-');
0288     }
0289     return text;
0290 }
0291 
0292 const QString Timecode::getTimecodeHH_MM_SS_HH(const GenTime &time) const
0293 {
0294     auto hundredths = (int)(time.seconds() * 100);
0295 
0296     bool negative = false;
0297     if (hundredths < 0) {
0298         negative = true;
0299         hundredths = qAbs(hundredths);
0300     }
0301 
0302     int seconds = hundredths / 100;
0303     hundredths = hundredths % 100;
0304     int minutes = seconds / 60;
0305     seconds = seconds % 60;
0306     int hours = minutes / 60;
0307     minutes = minutes % 60;
0308 
0309     QString text = QString("%1:%2:%3%5%4")
0310                        .arg(hours, 2, 10, QLatin1Char('0'))
0311                        .arg(minutes, 2, 10, QLatin1Char('0'))
0312                        .arg(seconds, 2, 10, QLatin1Char('0'))
0313                        .arg(hundredths, 2, 10, QLatin1Char('0'))
0314                        .arg(m_dropFrameTimecode ? QLatin1Char(',') : QLatin1Char(':'));
0315     if (negative) {
0316         text.prepend('-');
0317     }
0318     return text;
0319 }
0320 
0321 const QString Timecode::getTimecodeFrames(const GenTime &time) const
0322 {
0323     return QString::number((int)time.frames(m_realFps));
0324 }
0325 
0326 const QString Timecode::getTimecodeSeconds(const GenTime &time) const
0327 {
0328     return QString::number(time.seconds(), 'f');
0329 }
0330 
0331 const QString Timecode::getTimecodeDropFrame(const GenTime &time) const
0332 {
0333     return getTimecodeDropFrame((int)time.frames(m_realFps));
0334 }
0335 
0336 const QString Timecode::getTimecodeDropFrame(int framenumber) const
0337 {
0338     // CONVERT A FRAME NUMBER TO DROP FRAME TIMECODE
0339     // Based on code by David Heidelberger, adapted from Andrew Duncan
0340     // Given an int called framenumber and a double called framerate
0341     // Framerate should be 29.97, 59.94, or 23.976, otherwise the calculations will be off.
0342 
0343     bool negative = false;
0344     if (framenumber < 0) {
0345         negative = true;
0346         framenumber = qAbs(framenumber);
0347     }
0348 
0349     int d = floor(framenumber / m_framesPer10Minutes);
0350     int m = framenumber % m_framesPer10Minutes;
0351 
0352     if (m > m_dropFrames) {
0353         framenumber += (m_dropFrames * 9 * d) + m_dropFrames * (floor((m - m_dropFrames) / (round(m_realFps * 60) - m_dropFrames)));
0354     } else {
0355         framenumber += m_dropFrames * 9 * d;
0356     }
0357 
0358     int frames = framenumber % m_displayedFramesPerSecond;
0359     int seconds = (int)floor(framenumber / m_displayedFramesPerSecond) % 60;
0360     int minutes = (int)floor(floor(framenumber / m_displayedFramesPerSecond) / 60) % 60;
0361     int hours = floor(floor(floor(framenumber / m_displayedFramesPerSecond) / 60) / 60);
0362 
0363     QString text = QString("%1:%2:%3,%4")
0364                        .arg(hours, 2, 10, QLatin1Char('0'))
0365                        .arg(minutes, 2, 10, QLatin1Char('0'))
0366                        .arg(seconds, 2, 10, QLatin1Char('0'))
0367                        .arg(frames, m_realFps > 100 ? 3 : 2, 10, QLatin1Char('0'));
0368     if (negative) {
0369         text.prepend('-');
0370     }
0371     return text;
0372 }