forked from mia/Aegisub
Rewrite SMPTE timecode handling
Move SMPTE handling to agi::vfr::Framerate to get all of the interesting logic dealing with timcodes in one place, and to make it testable. Completely rewrite the SMPTE time conversions as testing them reveals that they were incorrect in some cases. Originally committed to SVN as r6631.
This commit is contained in:
parent
c7d3c8a5c3
commit
71776940f6
11 changed files with 539 additions and 119 deletions
|
@ -181,16 +181,18 @@ Framerate::Framerate(double fps)
|
||||||
: denominator(default_denominator)
|
: denominator(default_denominator)
|
||||||
, numerator(int64_t(fps * denominator))
|
, numerator(int64_t(fps * denominator))
|
||||||
, last(0)
|
, last(0)
|
||||||
|
, drop(false)
|
||||||
{
|
{
|
||||||
if (fps < 0.) throw BadFPS("FPS must be greater than zero");
|
if (fps < 0.) throw BadFPS("FPS must be greater than zero");
|
||||||
if (fps > 1000.) throw BadFPS("FPS must not be greater than 1000");
|
if (fps > 1000.) throw BadFPS("FPS must not be greater than 1000");
|
||||||
timecodes.push_back(0);
|
timecodes.push_back(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
Framerate::Framerate(int64_t numerator, int64_t denominator)
|
Framerate::Framerate(int64_t numerator, int64_t denominator, bool drop)
|
||||||
: denominator(denominator)
|
: denominator(denominator)
|
||||||
, numerator(numerator)
|
, numerator(numerator)
|
||||||
, last(0)
|
, last(0)
|
||||||
|
, drop(drop && numerator % denominator != 0)
|
||||||
{
|
{
|
||||||
if (numerator <= 0 || denominator <= 0)
|
if (numerator <= 0 || denominator <= 0)
|
||||||
throw BadFPS("Numerator and denominator must both be greater than zero");
|
throw BadFPS("Numerator and denominator must both be greater than zero");
|
||||||
|
@ -208,6 +210,7 @@ void Framerate::SetFromTimecodes() {
|
||||||
|
|
||||||
Framerate::Framerate(std::vector<int> const& timecodes)
|
Framerate::Framerate(std::vector<int> const& timecodes)
|
||||||
: timecodes(timecodes)
|
: timecodes(timecodes)
|
||||||
|
, drop(false)
|
||||||
{
|
{
|
||||||
SetFromTimecodes();
|
SetFromTimecodes();
|
||||||
}
|
}
|
||||||
|
@ -314,5 +317,71 @@ int Framerate::TimeAtFrame(int frame, Time type) const {
|
||||||
return timecodes[frame];
|
return timecodes[frame];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Framerate::SmpteAtFrame(int frame, int *h, int *m, int *s, int *f) const {
|
||||||
|
frame = std::max(frame, 0);
|
||||||
|
int ifps = (int)ceil(FPS());
|
||||||
|
|
||||||
|
if (drop && denominator == 1001 && numerator % 30000 == 0) {
|
||||||
|
// NTSC skips the first two frames of every minute except for multiples
|
||||||
|
// of ten. For multiples of NTSC, simply multiplying the number of
|
||||||
|
// frames skips seems like the most sensible option.
|
||||||
|
const int drop_factor = int(numerator / 30000);
|
||||||
|
const int one_minute = 60 * 30 * drop_factor - drop_factor * 2;
|
||||||
|
const int ten_minutes = 60 * 10 * 30 * drop_factor - drop_factor * 18;
|
||||||
|
const int ten_minute_groups = frame / ten_minutes;
|
||||||
|
const int last_ten_minutes = frame % ten_minutes;
|
||||||
|
|
||||||
|
frame += ten_minute_groups * 18 * drop_factor;
|
||||||
|
frame += (last_ten_minutes - 2 * drop_factor) / one_minute * 2 * drop_factor;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Non-integral frame rates other than NTSC aren't supported by SMPTE
|
||||||
|
// timecodes, but the user has asked for it so just give something that
|
||||||
|
// resembles a valid timecode which is no more than half a frame off
|
||||||
|
// wallclock time
|
||||||
|
else if (drop && ifps != FPS()) {
|
||||||
|
frame = int(frame / FPS() * ifps + 0.5);
|
||||||
|
}
|
||||||
|
|
||||||
|
*h = frame / (ifps * 60 * 60);
|
||||||
|
*m = (frame / (ifps * 60)) % 60;
|
||||||
|
*s = (frame / ifps) % 60;
|
||||||
|
*f = frame % ifps;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Framerate::SmpteAtTime(int ms, int *h, int *m, int *s, int *f) const {
|
||||||
|
SmpteAtFrame(FrameAtTime(ms), h, m, s, f);
|
||||||
|
}
|
||||||
|
|
||||||
|
int Framerate::FrameAtSmpte(int h, int m, int s, int f) const {
|
||||||
|
int ifps = (int)ceil(FPS());
|
||||||
|
|
||||||
|
if (drop && denominator == 1001 && numerator % 30000 == 0) {
|
||||||
|
const int drop_factor = int(numerator / 30000);
|
||||||
|
const int one_minute = 60 * 30 * drop_factor - drop_factor * 2;
|
||||||
|
const int ten_minutes = 60 * 10 * 30 * drop_factor - drop_factor * 18;
|
||||||
|
|
||||||
|
const int ten_m = m / 10;
|
||||||
|
m = m % 10;
|
||||||
|
|
||||||
|
// The specified frame doesn't actually exist so skip forward to the
|
||||||
|
// next frame that does
|
||||||
|
if (m != 0 && s == 0 && f < 2 * drop_factor)
|
||||||
|
f = 2 * drop_factor;
|
||||||
|
|
||||||
|
return h * ten_minutes * 6 + ten_m * ten_minutes + m * one_minute + s * ifps + f;
|
||||||
|
}
|
||||||
|
else if (drop && ifps != FPS()) {
|
||||||
|
int frame = (h * 60 * 60 + m * 60 + s) * ifps + f;
|
||||||
|
return int((double)frame / ifps * FPS() + 0.5);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (h * 60 * 60 + m * 60 + s) * ifps + f;
|
||||||
|
}
|
||||||
|
|
||||||
|
int Framerate::TimeAtSmpte(int h, int m, int s, int f) const {
|
||||||
|
return TimeAtFrame(FrameAtSmpte(h, m, s, f));
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -83,6 +83,9 @@ class Framerate {
|
||||||
/// Start time in milliseconds of each frame
|
/// Start time in milliseconds of each frame
|
||||||
std::vector<int> timecodes;
|
std::vector<int> timecodes;
|
||||||
|
|
||||||
|
/// Does this frame rate need drop frames and have them enabled?
|
||||||
|
bool drop;
|
||||||
|
|
||||||
/// Set FPS properties from the timecodes vector
|
/// Set FPS properties from the timecodes vector
|
||||||
void SetFromTimecodes();
|
void SetFromTimecodes();
|
||||||
public:
|
public:
|
||||||
|
@ -102,7 +105,8 @@ public:
|
||||||
/// @brief CFR constructor with rational timebase
|
/// @brief CFR constructor with rational timebase
|
||||||
/// @param numerator Timebase numerator
|
/// @param numerator Timebase numerator
|
||||||
/// @param denominator Timebase denominator
|
/// @param denominator Timebase denominator
|
||||||
Framerate(int64_t numerator, int64_t denominator);
|
/// @param drop Enable drop frames if the FPS requires it
|
||||||
|
Framerate(int64_t numerator, int64_t denominator, bool drop=true);
|
||||||
|
|
||||||
/// @brief VFR from frame times
|
/// @brief VFR from frame times
|
||||||
/// @param timecodes Vector of frame start times in milliseconds
|
/// @param timecodes Vector of frame start times in milliseconds
|
||||||
|
@ -139,6 +143,54 @@ public:
|
||||||
/// results for all frame numbers
|
/// results for all frame numbers
|
||||||
int TimeAtFrame(int frame, Time type = EXACT) const;
|
int TimeAtFrame(int frame, Time type = EXACT) const;
|
||||||
|
|
||||||
|
/// @brief Get the components of the SMPTE timecode for the given time
|
||||||
|
/// @param[out] h Hours component
|
||||||
|
/// @param[out] m Minutes component
|
||||||
|
/// @param[out] s Seconds component
|
||||||
|
/// @param[out] f Frames component
|
||||||
|
///
|
||||||
|
/// For NTSC (30000/1001), this generates proper SMPTE timecodes with drop
|
||||||
|
/// frames handled. For multiples of NTSC, this multiplies the number of
|
||||||
|
/// dropped frames. For other non-integral frame rates, it drops frames in
|
||||||
|
/// an undefined manner which results in no more than half a second error
|
||||||
|
/// from wall clock time.
|
||||||
|
///
|
||||||
|
/// For integral frame rates, no frame dropping occurs.
|
||||||
|
void SmpteAtTime(int ms, int *h, int *m, int *s, int *f) const;
|
||||||
|
|
||||||
|
/// @brief Get the components of the SMPTE timecode for the given frame
|
||||||
|
/// @param[out] h Hours component
|
||||||
|
/// @param[out] m Minutes component
|
||||||
|
/// @param[out] s Seconds component
|
||||||
|
/// @param[out] f Frames component
|
||||||
|
///
|
||||||
|
/// For NTSC (30000/1001), this generates proper SMPTE timecodes with drop
|
||||||
|
/// frames handled. For multiples of NTSC, this multiplies the number of
|
||||||
|
/// dropped frames. For other non-integral frame rates, it drops frames in
|
||||||
|
/// an undefined manner which results in no more than half a second error
|
||||||
|
/// from wall clock time.
|
||||||
|
///
|
||||||
|
/// For integral frame rates, no frame dropping occurs.
|
||||||
|
void SmpteAtFrame(int frame, int *h, int *m, int *s, int *f) const;
|
||||||
|
|
||||||
|
/// @brief Get the frame indicated by the SMPTE timecode components
|
||||||
|
/// @param h Hours component
|
||||||
|
/// @param m Minutes component
|
||||||
|
/// @param s Seconds component
|
||||||
|
/// @param f Frames component
|
||||||
|
/// @return Frame number
|
||||||
|
/// @see SmpteAtFrame
|
||||||
|
int FrameAtSmpte(int h, int m, int s, int f) const;
|
||||||
|
|
||||||
|
/// @brief Get the time indicated by the SMPTE timecode components
|
||||||
|
/// @param h Hours component
|
||||||
|
/// @param m Minutes component
|
||||||
|
/// @param s Seconds component
|
||||||
|
/// @param f Frames component
|
||||||
|
/// @return Time in milliseconds
|
||||||
|
/// @see SmpteAtTime
|
||||||
|
int TimeAtSmpte(int h, int m, int s, int f) const;
|
||||||
|
|
||||||
/// @brief Save the current time codes to a file as v2 timecodes
|
/// @brief Save the current time codes to a file as v2 timecodes
|
||||||
/// @param file File name
|
/// @param file File name
|
||||||
/// @param length Minimum number of frames to output
|
/// @param length Minimum number of frames to output
|
||||||
|
@ -149,9 +201,17 @@ public:
|
||||||
/// be otherwise sensible.
|
/// be otherwise sensible.
|
||||||
void Save(std::string const& file, int length = -1) const;
|
void Save(std::string const& file, int length = -1) const;
|
||||||
|
|
||||||
|
/// Is this frame rate possibly variable?
|
||||||
bool IsVFR() const {return timecodes.size() > 1; }
|
bool IsVFR() const {return timecodes.size() > 1; }
|
||||||
bool IsLoaded() const { return numerator > 0; };
|
|
||||||
|
/// Does this represent a valid frame rate?
|
||||||
|
bool IsLoaded() const { return numerator > 0; }
|
||||||
|
|
||||||
|
/// Get average FPS of this frame rate
|
||||||
double FPS() const { return double(numerator) / denominator; }
|
double FPS() const { return double(numerator) / denominator; }
|
||||||
|
|
||||||
|
/// Does this frame rate need drop frames for SMPTE timeish frame numbers?
|
||||||
|
bool NeedsDropFrames() const { return drop; }
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace vfr
|
} // namespace vfr
|
||||||
|
|
|
@ -41,6 +41,8 @@
|
||||||
#ifndef AGI_PRE
|
#ifndef AGI_PRE
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
#include <cmath>
|
#include <cmath>
|
||||||
|
|
||||||
|
#include <wx/tokenzr.h>
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#include "utils.h"
|
#include "utils.h"
|
||||||
|
@ -101,59 +103,25 @@ int AssTime::GetTimeSeconds() const { return (time % 60000) / 1000; }
|
||||||
int AssTime::GetTimeMiliseconds() const { return (time % 1000); }
|
int AssTime::GetTimeMiliseconds() const { return (time % 1000); }
|
||||||
int AssTime::GetTimeCentiseconds() const { return (time % 1000) / 10; }
|
int AssTime::GetTimeCentiseconds() const { return (time % 1000) / 10; }
|
||||||
|
|
||||||
FractionalTime::FractionalTime(agi::vfr::Framerate fps, bool dropframe)
|
SmpteFormatter::SmpteFormatter(agi::vfr::Framerate fps, char sep)
|
||||||
: fps(fps)
|
: fps(fps)
|
||||||
, drop(dropframe)
|
, sep(sep)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
wxString FractionalTime::ToSMPTE(AssTime time, char sep) {
|
wxString SmpteFormatter::ToSMPTE(AssTime time) const {
|
||||||
int h=0, m=0, s=0, f=0; // hours, minutes, seconds, fractions
|
int h=0, m=0, s=0, f=0;
|
||||||
int fn = fps.FrameAtTime(time);
|
fps.SmpteAtTime(time, &h, &m, &s, &f);
|
||||||
|
return wxString::Format("%02d%c%02d%c%02d%c%02d", h, sep, m, sep, s, sep, f);
|
||||||
// return 00:00:00:00
|
|
||||||
if (time <= 0) {
|
|
||||||
}
|
|
||||||
// dropframe?
|
|
||||||
else if (drop) {
|
|
||||||
fn += 2 * (fn / (30 * 60)) - 2 * (fn / (30 * 60 * 10));
|
|
||||||
h = fn / (30 * 60 * 60);
|
|
||||||
m = (fn / (30 * 60)) % 60;
|
|
||||||
s = (fn / 30) % 60;
|
|
||||||
f = fn % 30;
|
|
||||||
}
|
|
||||||
// no dropframe; h/m/s may or may not sync to wallclock time
|
|
||||||
else {
|
|
||||||
/*
|
|
||||||
This is truly the dumbest shit. What we're trying to ensure here
|
|
||||||
is that non-integer framerates are desynced from the wallclock
|
|
||||||
time by a correct amount of time. For example, in the
|
|
||||||
NTSC-without-dropframe case, 3600*num/den would be 107892
|
|
||||||
(when truncated to int), which is quite a good approximation of
|
|
||||||
how a long an hour is when counted in 30000/1001 frames per second.
|
|
||||||
Unfortunately, that's not what we want, since frame numbers will
|
|
||||||
still range from 00 to 29, meaning that we're really getting _30_
|
|
||||||
frames per second and not 29.97 and the full hour will be off by
|
|
||||||
almost 4 seconds (108000 frames versus 107892).
|
|
||||||
|
|
||||||
DEATH TO SMPTE
|
|
||||||
*/
|
|
||||||
int fps_approx = floor(fps.FPS() + 0.5);
|
|
||||||
int frames_per_h = 3600*fps_approx;
|
|
||||||
int frames_per_m = 60*fps_approx;
|
|
||||||
int frames_per_s = fps_approx;
|
|
||||||
|
|
||||||
h = fn / frames_per_h;
|
|
||||||
fn = fn % frames_per_h;
|
|
||||||
|
|
||||||
m = fn / frames_per_m;
|
|
||||||
fn = fn % frames_per_m;
|
|
||||||
|
|
||||||
s = fn / frames_per_s;
|
|
||||||
fn = fn % frames_per_s;
|
|
||||||
|
|
||||||
f = fn;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return wxString::Format("%02i%c%02%c%02i%c%02i", h, sep, m, sep, s, sep, f);
|
AssTime SmpteFormatter::FromSMPTE(wxString const& str) const {
|
||||||
|
long h, m, s, f;
|
||||||
|
wxArrayString toks = wxStringTokenize(str, sep);
|
||||||
|
if (toks.size() != 4) return 0;
|
||||||
|
toks[0].ToLong(&h);
|
||||||
|
toks[1].ToLong(&m);
|
||||||
|
toks[2].ToLong(&s);
|
||||||
|
toks[3].ToLong(&f);
|
||||||
|
return fps.TimeAtSmpte(h, m, s, f);
|
||||||
}
|
}
|
||||||
|
|
|
@ -69,24 +69,19 @@ public:
|
||||||
wxString GetASSFormated(bool ms=false) const;
|
wxString GetASSFormated(bool ms=false) const;
|
||||||
};
|
};
|
||||||
|
|
||||||
/// DOCME
|
/// @class SmpteFormatter
|
||||||
/// @class FractionalTime
|
/// @brief Convert times to and from SMPTE timecodes
|
||||||
/// @brief DOCME
|
class SmpteFormatter {
|
||||||
///
|
/// Frame rate to use
|
||||||
/// DOCME
|
|
||||||
class FractionalTime {
|
|
||||||
agi::vfr::Framerate fps;
|
agi::vfr::Framerate fps;
|
||||||
bool drop; ///< Enable SMPTE dropframe handling
|
/// Separator character
|
||||||
|
char sep;
|
||||||
/// How often to drop frames when enabled
|
|
||||||
static const int frames_per_period = 17982;
|
|
||||||
|
|
||||||
public:
|
public:
|
||||||
FractionalTime(agi::vfr::Framerate fps, bool dropframe = false);
|
SmpteFormatter(agi::vfr::Framerate fps, char sep=':');
|
||||||
|
|
||||||
bool IsDrop() const { return drop; }
|
|
||||||
agi::vfr::Framerate const& FPS() const { return fps; }
|
|
||||||
|
|
||||||
/// Convert an AssTime to a SMPTE timecode
|
/// Convert an AssTime to a SMPTE timecode
|
||||||
wxString ToSMPTE(AssTime time, char sep=':');
|
wxString ToSMPTE(AssTime time) const;
|
||||||
|
/// Convert a SMPTE timecode to an AssTime
|
||||||
|
AssTime FromSMPTE(wxString const& str) const;
|
||||||
};
|
};
|
||||||
|
|
|
@ -100,20 +100,19 @@ bool SubtitleFormat::CanSave(const AssFile *subs) const {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
FractionalTime SubtitleFormat::AskForFPS(bool showSMPTE) const {
|
agi::vfr::Framerate SubtitleFormat::AskForFPS(bool allow_vfr, bool show_smpte) const {
|
||||||
wxArrayString choices;
|
wxArrayString choices;
|
||||||
bool drop = false;
|
|
||||||
|
|
||||||
// Video FPS
|
// Video FPS
|
||||||
VideoContext *context = VideoContext::Get();
|
VideoContext *context = VideoContext::Get();
|
||||||
bool vidLoaded = context->TimecodesLoaded();
|
bool vidLoaded = context->TimecodesLoaded();
|
||||||
if (vidLoaded) {
|
if (vidLoaded) {
|
||||||
wxString vidFPS;
|
if (!context->FPS().IsVFR())
|
||||||
if (context->FPS().IsVFR())
|
choices.Add(wxString::Format(_("From video (%g)"), context->FPS().FPS()));
|
||||||
vidFPS = "VFR";
|
else if (allow_vfr)
|
||||||
|
choices.Add(_("From video (VFR)"));
|
||||||
else
|
else
|
||||||
vidFPS = wxString::Format("%.3f", context->FPS().FPS());
|
vidLoaded = false;
|
||||||
choices.Add(wxString::Format(_("From video (%s)"), vidFPS));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Standard FPS values
|
// Standard FPS values
|
||||||
|
@ -122,7 +121,7 @@ FractionalTime SubtitleFormat::AskForFPS(bool showSMPTE) const {
|
||||||
choices.Add(_("24.000 FPS (FILM)"));
|
choices.Add(_("24.000 FPS (FILM)"));
|
||||||
choices.Add(_("25.000 FPS (PAL)"));
|
choices.Add(_("25.000 FPS (PAL)"));
|
||||||
choices.Add(_("29.970 FPS (NTSC)"));
|
choices.Add(_("29.970 FPS (NTSC)"));
|
||||||
if (showSMPTE)
|
if (show_smpte)
|
||||||
choices.Add(_("29.970 FPS (NTSC with SMPTE dropframe)"));
|
choices.Add(_("29.970 FPS (NTSC with SMPTE dropframe)"));
|
||||||
choices.Add(_("30.000 FPS"));
|
choices.Add(_("30.000 FPS"));
|
||||||
choices.Add(_("50.000 FPS (PAL x2)"));
|
choices.Add(_("50.000 FPS (PAL x2)"));
|
||||||
|
@ -132,34 +131,35 @@ FractionalTime SubtitleFormat::AskForFPS(bool showSMPTE) const {
|
||||||
choices.Add(_("120.000 FPS"));
|
choices.Add(_("120.000 FPS"));
|
||||||
|
|
||||||
using agi::vfr::Framerate;
|
using agi::vfr::Framerate;
|
||||||
Framerate fps;
|
|
||||||
// Ask
|
// Ask
|
||||||
int choice = wxGetSingleChoiceIndex(_("Please choose the appropriate FPS for the subtitles:"), _("FPS"), choices);
|
int choice = wxGetSingleChoiceIndex(_("Please choose the appropriate FPS for the subtitles:"), _("FPS"), choices);
|
||||||
if (choice == -1)
|
if (choice == -1)
|
||||||
return FractionalTime(fps);
|
return Framerate();
|
||||||
|
|
||||||
// Get FPS from choice
|
// Get FPS from choice
|
||||||
if (vidLoaded) choice--;
|
if (vidLoaded)
|
||||||
// dropframe was displayed, that means all choices >4 are bumped up by 1
|
--choice;
|
||||||
if (!showSMPTE && choice > 4) ++choice;
|
if (!show_smpte && choice > 4)
|
||||||
|
--choice;
|
||||||
|
|
||||||
switch (choice) {
|
switch (choice) {
|
||||||
case -1: fps = context->FPS(); break; // VIDEO
|
case -1: return context->FPS(); break; // VIDEO
|
||||||
case 0: fps = Framerate(15, 1); break;
|
case 0: return Framerate(15, 1); break;
|
||||||
case 1: fps = Framerate(24000, 1001); break;
|
case 1: return Framerate(24000, 1001); break;
|
||||||
case 2: fps = Framerate(24, 1); break;
|
case 2: return Framerate(24, 1); break;
|
||||||
case 3: fps = Framerate(25, 1); break;
|
case 3: return Framerate(25, 1); break;
|
||||||
case 4: fps = Framerate(30000, 1001); break;
|
case 4: return Framerate(30000, 1001); break;
|
||||||
case 5: fps = Framerate(30000, 1001); drop = true; break;
|
case 5: return Framerate(30000, 1001, true); break;
|
||||||
case 6: fps = Framerate(30, 1); break;
|
case 6: return Framerate(30, 1); break;
|
||||||
case 7: fps = Framerate(50, 1); break;
|
case 7: return Framerate(50, 1); break;
|
||||||
case 8: fps = Framerate(60000, 1001); break;
|
case 8: return Framerate(60000, 1001); break;
|
||||||
case 9: fps = Framerate(60, 1); break;
|
case 9: return Framerate(60, 1); break;
|
||||||
case 10: fps = Framerate(120000, 1001); break;
|
case 10: return Framerate(120000, 1001); break;
|
||||||
case 11: fps = Framerate(120, 1); break;
|
case 11: return Framerate(120, 1); break;
|
||||||
}
|
}
|
||||||
|
|
||||||
return FractionalTime(fps, drop);
|
assert(false);
|
||||||
|
return Framerate();
|
||||||
}
|
}
|
||||||
|
|
||||||
void SubtitleFormat::StripTags(LineList &lines) const {
|
void SubtitleFormat::StripTags(LineList &lines) const {
|
||||||
|
|
|
@ -47,7 +47,7 @@
|
||||||
|
|
||||||
class AssEntry;
|
class AssEntry;
|
||||||
class AssFile;
|
class AssFile;
|
||||||
class FractionalTime;
|
namespace agi { namespace vfr { class Framerate; } }
|
||||||
|
|
||||||
/// DOCME
|
/// DOCME
|
||||||
/// @class SubtitleFormat
|
/// @class SubtitleFormat
|
||||||
|
@ -86,8 +86,9 @@ protected:
|
||||||
void MergeIdentical(LineList &lines) const;
|
void MergeIdentical(LineList &lines) const;
|
||||||
|
|
||||||
/// Prompt the user for a frame rate to use
|
/// Prompt the user for a frame rate to use
|
||||||
/// @param showSMPTE Include SMPTE as an option?
|
/// @param allow_vfr Include video frame rate as an option even if it's vfr
|
||||||
FractionalTime AskForFPS(bool showSMPTE=false) const;
|
/// @param show_smpte Show SMPTE drop frame option
|
||||||
|
agi::vfr::Framerate AskForFPS(bool allow_vfr, bool show_smpte) const;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
/// Constructor
|
/// Constructor
|
||||||
|
|
|
@ -58,8 +58,8 @@ bool EncoreSubtitleFormat::CanWriteFile(wxString const& filename) const {
|
||||||
}
|
}
|
||||||
|
|
||||||
void EncoreSubtitleFormat::WriteFile(const AssFile *src, wxString const& filename, wxString const&) const {
|
void EncoreSubtitleFormat::WriteFile(const AssFile *src, wxString const& filename, wxString const&) const {
|
||||||
FractionalTime ft = AskForFPS(true);
|
agi::vfr::Framerate fps = AskForFPS(false, true);
|
||||||
if (!ft.FPS().IsLoaded()) return;
|
if (!fps.IsLoaded()) return;
|
||||||
|
|
||||||
// Convert to encore
|
// Convert to encore
|
||||||
AssFile copy(*src);
|
AssFile copy(*src);
|
||||||
|
@ -70,17 +70,19 @@ void EncoreSubtitleFormat::WriteFile(const AssFile *src, wxString const& filenam
|
||||||
StripTags(copy.Line);
|
StripTags(copy.Line);
|
||||||
ConvertNewlines(copy.Line, "\r\n");
|
ConvertNewlines(copy.Line, "\r\n");
|
||||||
|
|
||||||
|
|
||||||
|
// Encode wants ; for NTSC and : for PAL
|
||||||
|
// The manual suggests no other frame rates are supported
|
||||||
|
char sep = fps.NeedsDropFrames() ? ';' : ':';
|
||||||
|
SmpteFormatter ft(fps, sep);
|
||||||
|
|
||||||
// Write lines
|
// Write lines
|
||||||
int i = 0;
|
int i = 0;
|
||||||
|
|
||||||
// Encore wants ; instead of : if we're dealing with NTSC dropframe stuff
|
|
||||||
char sep = ft.IsDrop() ? ';' : ':';
|
|
||||||
|
|
||||||
TextFileWriter file(filename, "UTF-8");
|
TextFileWriter file(filename, "UTF-8");
|
||||||
for (LineList::const_iterator cur = copy.Line.begin(); cur != copy.Line.end(); ++cur) {
|
for (LineList::const_iterator cur = copy.Line.begin(); cur != copy.Line.end(); ++cur) {
|
||||||
if (AssDialogue *current = dynamic_cast<AssDialogue*>(*cur)) {
|
if (AssDialogue *current = dynamic_cast<AssDialogue*>(*cur)) {
|
||||||
++i;
|
++i;
|
||||||
file.WriteLineToFile(wxString::Format("%i %s %s %s", i, ft.ToSMPTE(current->Start, sep), ft.ToSMPTE(current->End, sep), current->Text));
|
file.WriteLineToFile(wxString::Format("%i %s %s %s", i, ft.ToSMPTE(current->Start), ft.ToSMPTE(current->End), current->Text));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -109,7 +109,7 @@ void MicroDVDSubtitleFormat::ReadFile(AssFile *target, wxString const& filename,
|
||||||
}
|
}
|
||||||
|
|
||||||
// If it wasn't an fps line, ask the user for it
|
// If it wasn't an fps line, ask the user for it
|
||||||
fps = AskForFPS().FPS();
|
fps = AskForFPS(true, false);
|
||||||
if (!fps.IsLoaded()) return;
|
if (!fps.IsLoaded()) return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -125,7 +125,7 @@ void MicroDVDSubtitleFormat::ReadFile(AssFile *target, wxString const& filename,
|
||||||
}
|
}
|
||||||
|
|
||||||
void MicroDVDSubtitleFormat::WriteFile(const AssFile *src, wxString const& filename, wxString const& encoding) const {
|
void MicroDVDSubtitleFormat::WriteFile(const AssFile *src, wxString const& filename, wxString const& encoding) const {
|
||||||
agi::vfr::Framerate fps = AskForFPS().FPS();
|
agi::vfr::Framerate fps = AskForFPS(true, false);
|
||||||
if (!fps.IsLoaded()) return;
|
if (!fps.IsLoaded()) return;
|
||||||
|
|
||||||
AssFile copy(*src);
|
AssFile copy(*src);
|
||||||
|
|
|
@ -64,10 +64,8 @@ bool TranStationSubtitleFormat::CanWriteFile(wxString const& filename) const {
|
||||||
}
|
}
|
||||||
|
|
||||||
void TranStationSubtitleFormat::WriteFile(const AssFile *src, wxString const& filename, wxString const& encoding) const {
|
void TranStationSubtitleFormat::WriteFile(const AssFile *src, wxString const& filename, wxString const& encoding) const {
|
||||||
FractionalTime ft = AskForFPS(true);
|
agi::vfr::Framerate fps = AskForFPS(false, true);
|
||||||
if (!ft.FPS().IsLoaded()) return;
|
if (!fps.IsLoaded()) return;
|
||||||
|
|
||||||
TextFileWriter file(filename, encoding);
|
|
||||||
|
|
||||||
// Convert to TranStation
|
// Convert to TranStation
|
||||||
AssFile copy(*src);
|
AssFile copy(*src);
|
||||||
|
@ -76,12 +74,14 @@ void TranStationSubtitleFormat::WriteFile(const AssFile *src, wxString const& fi
|
||||||
RecombineOverlaps(copy.Line);
|
RecombineOverlaps(copy.Line);
|
||||||
MergeIdentical(copy.Line);
|
MergeIdentical(copy.Line);
|
||||||
|
|
||||||
|
SmpteFormatter ft(fps);
|
||||||
|
TextFileWriter file(filename, encoding);
|
||||||
AssDialogue *prev = 0;
|
AssDialogue *prev = 0;
|
||||||
for (std::list<AssEntry*>::iterator it = copy.Line.begin(); it != copy.Line.end(); ++it) {
|
for (std::list<AssEntry*>::iterator it = copy.Line.begin(); it != copy.Line.end(); ++it) {
|
||||||
AssDialogue *cur = dynamic_cast<AssDialogue*>(*it);
|
AssDialogue *cur = dynamic_cast<AssDialogue*>(*it);
|
||||||
|
|
||||||
if (prev && cur) {
|
if (prev && cur) {
|
||||||
file.WriteLineToFile(ConvertLine(©, prev, &ft, cur->Start));
|
file.WriteLineToFile(ConvertLine(©, prev, fps, ft, cur->Start));
|
||||||
file.WriteLineToFile("");
|
file.WriteLineToFile("");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -91,13 +91,13 @@ void TranStationSubtitleFormat::WriteFile(const AssFile *src, wxString const& fi
|
||||||
|
|
||||||
// flush last line
|
// flush last line
|
||||||
if (prev)
|
if (prev)
|
||||||
file.WriteLineToFile(ConvertLine(©, prev, &ft, -1));
|
file.WriteLineToFile(ConvertLine(©, prev, fps, ft, -1));
|
||||||
|
|
||||||
// Every file must end with this line
|
// Every file must end with this line
|
||||||
file.WriteLineToFile("SUB[");
|
file.WriteLineToFile("SUB[");
|
||||||
}
|
}
|
||||||
|
|
||||||
wxString TranStationSubtitleFormat::ConvertLine(AssFile *file, AssDialogue *current, FractionalTime *ft, int nextl_start) const {
|
wxString TranStationSubtitleFormat::ConvertLine(AssFile *file, AssDialogue *current, agi::vfr::Framerate const& fps, SmpteFormatter const& ft, int nextl_start) const {
|
||||||
int valign = 0;
|
int valign = 0;
|
||||||
const char *halign = " "; // default is centered
|
const char *halign = " "; // default is centered
|
||||||
const char *type = "N"; // no special style
|
const char *type = "N"; // no special style
|
||||||
|
@ -120,9 +120,9 @@ wxString TranStationSubtitleFormat::ConvertLine(AssFile *file, AssDialogue *curr
|
||||||
// start of next one, since the end timestamp is inclusive and the lines
|
// start of next one, since the end timestamp is inclusive and the lines
|
||||||
// would overlap if left as is.
|
// would overlap if left as is.
|
||||||
if (nextl_start > 0 && end == nextl_start)
|
if (nextl_start > 0 && end == nextl_start)
|
||||||
end = ft->FPS().TimeAtFrame(ft->FPS().FrameAtTime(end, agi::vfr::END) - 1, agi::vfr::END);
|
end = fps.TimeAtFrame(fps.FrameAtTime(end, agi::vfr::END) - 1, agi::vfr::END);
|
||||||
|
|
||||||
wxString header = wxString::Format("SUB[%i%s%s ", valign, halign, type) + ft->ToSMPTE(current->Start) + ">" + ft->ToSMPTE(end) + "]\r\n";
|
wxString header = wxString::Format("SUB[%i%s%s ", valign, halign, type) + ft.ToSMPTE(current->Start) + ">" + ft.ToSMPTE(end) + "]\r\n";
|
||||||
|
|
||||||
// Process text
|
// Process text
|
||||||
wxString lineEnd = "\r\n";
|
wxString lineEnd = "\r\n";
|
||||||
|
|
|
@ -37,6 +37,7 @@
|
||||||
#include "subtitle_format.h"
|
#include "subtitle_format.h"
|
||||||
|
|
||||||
class AssDialogue;
|
class AssDialogue;
|
||||||
|
class SmpteFormatter;
|
||||||
|
|
||||||
/// DOCME
|
/// DOCME
|
||||||
/// @class TranStationSubtitleFormat
|
/// @class TranStationSubtitleFormat
|
||||||
|
@ -44,7 +45,7 @@ class AssDialogue;
|
||||||
///
|
///
|
||||||
/// DOCME
|
/// DOCME
|
||||||
class TranStationSubtitleFormat : public SubtitleFormat {
|
class TranStationSubtitleFormat : public SubtitleFormat {
|
||||||
wxString ConvertLine(AssFile *file, AssDialogue *line, FractionalTime *ft, int nextl_start) const;
|
wxString ConvertLine(AssFile *file, AssDialogue *line, agi::vfr::Framerate const& fps, SmpteFormatter const& ft, int nextl_start) const;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
TranStationSubtitleFormat();
|
TranStationSubtitleFormat();
|
||||||
|
|
|
@ -445,3 +445,327 @@ TEST(lagi_vfr, duplicate_timestamps) {
|
||||||
EXPECT_EQ(2, fps.FrameAtTime(199, EXACT));
|
EXPECT_EQ(2, fps.FrameAtTime(199, EXACT));
|
||||||
EXPECT_EQ(3, fps.FrameAtTime(200, EXACT));
|
EXPECT_EQ(3, fps.FrameAtTime(200, EXACT));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#define EXPECT_SMPTE(eh, em, es, ef) \
|
||||||
|
EXPECT_EQ(eh, h); \
|
||||||
|
EXPECT_EQ(em, m); \
|
||||||
|
EXPECT_EQ(es, s); \
|
||||||
|
EXPECT_EQ(ef, f)
|
||||||
|
|
||||||
|
TEST(lagi_vfr, to_smpte_ntsc) {
|
||||||
|
Framerate fps;
|
||||||
|
ASSERT_NO_THROW(fps = Framerate(30000, 1001));
|
||||||
|
|
||||||
|
EXPECT_TRUE(fps.NeedsDropFrames());
|
||||||
|
|
||||||
|
int h = -1, m = -1, s = -1, f = -1;
|
||||||
|
|
||||||
|
ASSERT_NO_THROW(fps.SmpteAtFrame(0, &h, &m, &s, &f));
|
||||||
|
EXPECT_SMPTE(0, 0, 0, 0);
|
||||||
|
|
||||||
|
ASSERT_NO_THROW(fps.SmpteAtFrame(1, &h, &m, &s, &f));
|
||||||
|
EXPECT_SMPTE(0, 0, 0, 1);
|
||||||
|
|
||||||
|
ASSERT_NO_THROW(fps.SmpteAtFrame(29, &h, &m, &s, &f));
|
||||||
|
EXPECT_SMPTE(0, 0, 0, 29);
|
||||||
|
|
||||||
|
ASSERT_NO_THROW(fps.SmpteAtFrame(30, &h, &m, &s, &f));
|
||||||
|
EXPECT_SMPTE(0, 0, 1, 0);
|
||||||
|
|
||||||
|
ASSERT_NO_THROW(fps.SmpteAtFrame(1799, &h, &m, &s, &f));
|
||||||
|
EXPECT_SMPTE(0, 0, 59, 29);
|
||||||
|
|
||||||
|
ASSERT_NO_THROW(fps.SmpteAtFrame(1800, &h, &m, &s, &f));
|
||||||
|
EXPECT_SMPTE(0, 1, 0, 2);
|
||||||
|
|
||||||
|
ASSERT_NO_THROW(fps.SmpteAtFrame(3597, &h, &m, &s, &f));
|
||||||
|
EXPECT_SMPTE(0, 1, 59, 29);
|
||||||
|
|
||||||
|
ASSERT_NO_THROW(fps.SmpteAtFrame(3598, &h, &m, &s, &f));
|
||||||
|
EXPECT_SMPTE(0, 2, 0, 2);
|
||||||
|
|
||||||
|
ASSERT_NO_THROW(fps.SmpteAtFrame(5396, &h, &m, &s, &f));
|
||||||
|
EXPECT_SMPTE(0, 3, 0, 2);
|
||||||
|
|
||||||
|
ASSERT_NO_THROW(fps.SmpteAtFrame(7194, &h, &m, &s, &f));
|
||||||
|
EXPECT_SMPTE(0, 4, 0, 2);
|
||||||
|
|
||||||
|
ASSERT_NO_THROW(fps.SmpteAtFrame(107892, &h, &m, &s, &f));
|
||||||
|
EXPECT_SMPTE(1, 0, 0, 0);
|
||||||
|
|
||||||
|
for (int i = 0; i < 60 * 60 * 10; ++i) {
|
||||||
|
ASSERT_NO_THROW(fps.SmpteAtTime(i * 1000, &h, &m, &s, &f));
|
||||||
|
ASSERT_NEAR(i, h * 3600 + m * 60 + s, 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(lagi_vfr, to_smpte_double_ntsc) {
|
||||||
|
Framerate fps;
|
||||||
|
ASSERT_NO_THROW(fps = Framerate(60000, 1001));
|
||||||
|
|
||||||
|
EXPECT_TRUE(fps.NeedsDropFrames());
|
||||||
|
|
||||||
|
int h = -1, m = -1, s = -1, f = -1;
|
||||||
|
|
||||||
|
ASSERT_NO_THROW(fps.SmpteAtFrame(0, &h, &m, &s, &f));
|
||||||
|
EXPECT_SMPTE(0, 0, 0, 0);
|
||||||
|
|
||||||
|
ASSERT_NO_THROW(fps.SmpteAtFrame(1, &h, &m, &s, &f));
|
||||||
|
EXPECT_SMPTE(0, 0, 0, 1);
|
||||||
|
|
||||||
|
ASSERT_NO_THROW(fps.SmpteAtFrame(59, &h, &m, &s, &f));
|
||||||
|
EXPECT_SMPTE(0, 0, 0, 59);
|
||||||
|
|
||||||
|
ASSERT_NO_THROW(fps.SmpteAtFrame(60, &h, &m, &s, &f));
|
||||||
|
EXPECT_SMPTE(0, 0, 1, 0);
|
||||||
|
|
||||||
|
ASSERT_NO_THROW(fps.SmpteAtFrame(3599, &h, &m, &s, &f));
|
||||||
|
EXPECT_SMPTE(0, 0, 59, 59);
|
||||||
|
|
||||||
|
ASSERT_NO_THROW(fps.SmpteAtFrame(3600, &h, &m, &s, &f));
|
||||||
|
EXPECT_SMPTE(0, 1, 0, 4);
|
||||||
|
|
||||||
|
ASSERT_NO_THROW(fps.SmpteAtFrame(7195, &h, &m, &s, &f));
|
||||||
|
EXPECT_SMPTE(0, 1, 59, 59);
|
||||||
|
|
||||||
|
ASSERT_NO_THROW(fps.SmpteAtFrame(7196, &h, &m, &s, &f));
|
||||||
|
EXPECT_SMPTE(0, 2, 0, 4);
|
||||||
|
|
||||||
|
ASSERT_NO_THROW(fps.SmpteAtFrame(215784, &h, &m, &s, &f));
|
||||||
|
EXPECT_SMPTE(1, 0, 0, 0);
|
||||||
|
|
||||||
|
for (int i = 0; i < 60 * 60 * 10; ++i) {
|
||||||
|
ASSERT_NO_THROW(fps.SmpteAtTime(i * 1000, &h, &m, &s, &f));
|
||||||
|
ASSERT_NEAR(i, h * 3600 + m * 60 + s, 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(lagi_vfr, to_smpte_pal) {
|
||||||
|
Framerate fps;
|
||||||
|
ASSERT_NO_THROW(fps = Framerate(25, 1));
|
||||||
|
|
||||||
|
EXPECT_FALSE(fps.NeedsDropFrames());
|
||||||
|
|
||||||
|
int h = -1, m = -1, s = -1, f = -1;
|
||||||
|
|
||||||
|
ASSERT_NO_THROW(fps.SmpteAtFrame(0, &h, &m, &s, &f));
|
||||||
|
EXPECT_SMPTE(0, 0, 0, 0);
|
||||||
|
|
||||||
|
ASSERT_NO_THROW(fps.SmpteAtFrame(1, &h, &m, &s, &f));
|
||||||
|
EXPECT_SMPTE(0, 0, 0, 1);
|
||||||
|
|
||||||
|
ASSERT_NO_THROW(fps.SmpteAtFrame(24, &h, &m, &s, &f));
|
||||||
|
EXPECT_SMPTE(0, 0, 0, 24);
|
||||||
|
|
||||||
|
ASSERT_NO_THROW(fps.SmpteAtFrame(25, &h, &m, &s, &f));
|
||||||
|
EXPECT_SMPTE(0, 0, 1, 0);
|
||||||
|
|
||||||
|
ASSERT_NO_THROW(fps.SmpteAtFrame(1499, &h, &m, &s, &f));
|
||||||
|
EXPECT_SMPTE(0, 0, 59, 24);
|
||||||
|
|
||||||
|
ASSERT_NO_THROW(fps.SmpteAtFrame(1500, &h, &m, &s, &f));
|
||||||
|
EXPECT_SMPTE(0, 1, 0, 0);
|
||||||
|
|
||||||
|
ASSERT_NO_THROW(fps.SmpteAtFrame(25 * 60 * 60, &h, &m, &s, &f));
|
||||||
|
EXPECT_SMPTE(1, 0, 0, 0);
|
||||||
|
|
||||||
|
for (int i = 0; i < 60 * 60 * 10; ++i) {
|
||||||
|
ASSERT_NO_THROW(fps.SmpteAtTime(i * 1000, &h, &m, &s, &f));
|
||||||
|
ASSERT_EQ(i, h * 3600 + m * 60 + s);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// this test is different from the above due to that the exact frames which are
|
||||||
|
// skipped are undefined, so instead it tests that the error never exceeds the
|
||||||
|
// limit
|
||||||
|
TEST(lagi_vfr, to_smpte_decimated) {
|
||||||
|
Framerate fps;
|
||||||
|
ASSERT_NO_THROW(fps = Framerate(24000, 1001));
|
||||||
|
|
||||||
|
EXPECT_TRUE(fps.NeedsDropFrames());
|
||||||
|
|
||||||
|
int h = -1, m = -1, s = -1, f = -1;
|
||||||
|
|
||||||
|
ASSERT_NO_THROW(fps.SmpteAtFrame(0, &h, &m, &s, &f));
|
||||||
|
EXPECT_SMPTE(0, 0, 0, 0);
|
||||||
|
|
||||||
|
ASSERT_NO_THROW(fps.SmpteAtFrame(1, &h, &m, &s, &f));
|
||||||
|
EXPECT_SMPTE(0, 0, 0, 1);
|
||||||
|
|
||||||
|
for (int frame = 0; frame < 100000; ++frame) {
|
||||||
|
ASSERT_NO_THROW(fps.SmpteAtFrame(frame, &h, &m, &s, &f));
|
||||||
|
int expected_time = fps.TimeAtFrame(frame);
|
||||||
|
int real_time = int((h * 3600 + m * 60 + s + f / 24.0) * 1000.0);
|
||||||
|
ASSERT_NEAR(expected_time, real_time, 600.0 / fps.FPS());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(lagi_vfr, to_smpte_manydrop) {
|
||||||
|
Framerate fps;
|
||||||
|
ASSERT_NO_THROW(fps = Framerate(24, 11));
|
||||||
|
|
||||||
|
EXPECT_TRUE(fps.NeedsDropFrames());
|
||||||
|
|
||||||
|
int h = -1, m = -1, s = -1, f = -1;
|
||||||
|
|
||||||
|
ASSERT_NO_THROW(fps.SmpteAtFrame(0, &h, &m, &s, &f));
|
||||||
|
EXPECT_SMPTE(0, 0, 0, 0);
|
||||||
|
|
||||||
|
ASSERT_NO_THROW(fps.SmpteAtFrame(1, &h, &m, &s, &f));
|
||||||
|
EXPECT_SMPTE(0, 0, 0, 1);
|
||||||
|
|
||||||
|
for (int frame = 0; frame < 1000; ++frame) {
|
||||||
|
ASSERT_NO_THROW(fps.SmpteAtFrame(frame, &h, &m, &s, &f));
|
||||||
|
int expected_time = fps.TimeAtFrame(frame);
|
||||||
|
int real_time = int((h * 3600 + m * 60 + s + f / 3.0) * 1000.0);
|
||||||
|
ASSERT_NEAR(expected_time, real_time, 600.0 / fps.FPS());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(lagi_vfr, from_smpte_ntsc) {
|
||||||
|
Framerate fps;
|
||||||
|
ASSERT_NO_THROW(fps = Framerate(30000, 1001));
|
||||||
|
|
||||||
|
EXPECT_EQ(0, fps.FrameAtSmpte(0, 0, 0, 0));
|
||||||
|
EXPECT_EQ(1, fps.FrameAtSmpte(0, 0, 0, 1));
|
||||||
|
EXPECT_EQ(29, fps.FrameAtSmpte(0, 0, 0, 29));
|
||||||
|
EXPECT_EQ(30, fps.FrameAtSmpte(0, 0, 1, 0));
|
||||||
|
EXPECT_EQ(1799, fps.FrameAtSmpte(0, 0, 59, 29));
|
||||||
|
EXPECT_EQ(1800, fps.FrameAtSmpte(0, 1, 0, 0));
|
||||||
|
EXPECT_EQ(1800, fps.FrameAtSmpte(0, 1, 0, 1));
|
||||||
|
EXPECT_EQ(1800, fps.FrameAtSmpte(0, 1, 0, 2));
|
||||||
|
EXPECT_EQ(3597, fps.FrameAtSmpte(0, 1, 59, 29));
|
||||||
|
EXPECT_EQ(3598, fps.FrameAtSmpte(0, 2, 0, 0));
|
||||||
|
EXPECT_EQ(3598, fps.FrameAtSmpte(0, 2, 0, 1));
|
||||||
|
EXPECT_EQ(3598, fps.FrameAtSmpte(0, 2, 0, 2));
|
||||||
|
EXPECT_EQ(5396, fps.FrameAtSmpte(0, 3, 0, 2));
|
||||||
|
EXPECT_EQ(7194, fps.FrameAtSmpte(0, 4, 0, 2));
|
||||||
|
EXPECT_EQ(107892, fps.FrameAtSmpte(1, 0, 0, 0));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(lagi_vfr, from_smpte_double_ntsc) {
|
||||||
|
Framerate fps;
|
||||||
|
ASSERT_NO_THROW(fps = Framerate(60000, 1001));
|
||||||
|
|
||||||
|
EXPECT_TRUE(fps.NeedsDropFrames());
|
||||||
|
|
||||||
|
EXPECT_EQ(0, fps.FrameAtSmpte(0, 0, 0, 0));
|
||||||
|
EXPECT_EQ(1, fps.FrameAtSmpte(0, 0, 0, 1));
|
||||||
|
EXPECT_EQ(59, fps.FrameAtSmpte(0, 0, 0, 59));
|
||||||
|
EXPECT_EQ(60, fps.FrameAtSmpte(0, 0, 1, 0));
|
||||||
|
EXPECT_EQ(3599, fps.FrameAtSmpte(0, 0, 59, 59));
|
||||||
|
EXPECT_EQ(3600, fps.FrameAtSmpte(0, 1, 0, 4));
|
||||||
|
EXPECT_EQ(7195, fps.FrameAtSmpte(0, 1, 59, 59));
|
||||||
|
EXPECT_EQ(7196, fps.FrameAtSmpte(0, 2, 0, 4));
|
||||||
|
EXPECT_EQ(10792, fps.FrameAtSmpte(0, 3, 0, 0));
|
||||||
|
EXPECT_EQ(10792, fps.FrameAtSmpte(0, 3, 0, 1));
|
||||||
|
EXPECT_EQ(10792, fps.FrameAtSmpte(0, 3, 0, 2));
|
||||||
|
EXPECT_EQ(10792, fps.FrameAtSmpte(0, 3, 0, 3));
|
||||||
|
EXPECT_EQ(10792, fps.FrameAtSmpte(0, 3, 0, 4));
|
||||||
|
EXPECT_EQ(10793, fps.FrameAtSmpte(0, 3, 0, 5));
|
||||||
|
EXPECT_EQ(215784, fps.FrameAtSmpte(1, 0, 0, 0));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(lagi_vfr, from_smpte_pal) {
|
||||||
|
Framerate fps;
|
||||||
|
ASSERT_NO_THROW(fps = Framerate(25, 1));
|
||||||
|
|
||||||
|
EXPECT_FALSE(fps.NeedsDropFrames());
|
||||||
|
|
||||||
|
EXPECT_EQ(0, fps.FrameAtSmpte(0, 0, 0, 0));
|
||||||
|
EXPECT_EQ(1, fps.FrameAtSmpte(0, 0, 0, 1));
|
||||||
|
EXPECT_EQ(24, fps.FrameAtSmpte(0, 0, 0, 24));
|
||||||
|
EXPECT_EQ(25, fps.FrameAtSmpte(0, 0, 1, 0));
|
||||||
|
EXPECT_EQ(1499, fps.FrameAtSmpte(0, 0, 59, 24));
|
||||||
|
EXPECT_EQ(1500, fps.FrameAtSmpte(0, 1, 0, 0));
|
||||||
|
EXPECT_EQ(25 * 60 * 60, fps.FrameAtSmpte(1, 0, 0, 0));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(lagi_vfr, roundtrip_smpte_ntsc) {
|
||||||
|
Framerate fps;
|
||||||
|
ASSERT_NO_THROW(fps = Framerate(30000, 1001));
|
||||||
|
|
||||||
|
int h = -1, m = -1, s = -1, f = -1;
|
||||||
|
|
||||||
|
for (int i = 0; i < 100000; ++i) {
|
||||||
|
ASSERT_NO_THROW(fps.SmpteAtFrame(i, &h, &m, &s, &f));
|
||||||
|
ASSERT_EQ(i, fps.FrameAtSmpte(h, m, s, f));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(lagi_vfr, roundtrip_smpte_pal) {
|
||||||
|
Framerate fps;
|
||||||
|
ASSERT_NO_THROW(fps = Framerate(25, 1));
|
||||||
|
|
||||||
|
int h = -1, m = -1, s = -1, f = -1;
|
||||||
|
|
||||||
|
for (int i = 0; i < 100000; ++i) {
|
||||||
|
ASSERT_NO_THROW(fps.SmpteAtFrame(i, &h, &m, &s, &f));
|
||||||
|
ASSERT_EQ(i, fps.FrameAtSmpte(h, m, s, f));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(lagi_vfr, roundtrip_smpte_manydrop) {
|
||||||
|
Framerate fps;
|
||||||
|
ASSERT_NO_THROW(fps = Framerate(20, 11));
|
||||||
|
|
||||||
|
int h = -1, m = -1, s = -1, f = -1;
|
||||||
|
|
||||||
|
for (int i = 0; i < 10000; ++i) {
|
||||||
|
ASSERT_NO_THROW(fps.SmpteAtFrame(i, &h, &m, &s, &f));
|
||||||
|
ASSERT_EQ(i, fps.FrameAtSmpte(h, m, s, f));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(lagi_vfr, roundtrip_smpte_decimated) {
|
||||||
|
Framerate fps;
|
||||||
|
ASSERT_NO_THROW(fps = Framerate(24000, 1001));
|
||||||
|
|
||||||
|
int h = -1, m = -1, s = -1, f = -1;
|
||||||
|
|
||||||
|
for (int i = 0; i < 100000; ++i) {
|
||||||
|
ASSERT_NO_THROW(fps.SmpteAtFrame(i, &h, &m, &s, &f));
|
||||||
|
ASSERT_EQ(i, fps.FrameAtSmpte(h, m, s, f));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(lagi_vfr, to_smpte_ntsc_nodrop) {
|
||||||
|
Framerate fps;
|
||||||
|
ASSERT_NO_THROW(fps = Framerate(30000, 1001, false));
|
||||||
|
|
||||||
|
int h = -1, m = -1, s = -1, f = -1;
|
||||||
|
|
||||||
|
for (int i = 0; i < 100000; ++i) {
|
||||||
|
ASSERT_NO_THROW(fps.SmpteAtFrame(i, &h, &m, &s, &f));
|
||||||
|
ASSERT_EQ(i, h * 60 * 60 * 30 + m * 60 * 30 + s * 30 + f);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(lagi_vfr, from_smpte_ntsc_nodrop) {
|
||||||
|
Framerate fps;
|
||||||
|
ASSERT_NO_THROW(fps = Framerate(30000, 1001, false));
|
||||||
|
|
||||||
|
int h = 0, m = 0, s = 0, f = 0;
|
||||||
|
|
||||||
|
int i = 0;
|
||||||
|
while (h < 10) {
|
||||||
|
if (f >= 30) {
|
||||||
|
f = 0;
|
||||||
|
++s;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (s >= 60) {
|
||||||
|
s = 0;
|
||||||
|
++m;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (m >= 60) {
|
||||||
|
m = 0;
|
||||||
|
++h;
|
||||||
|
}
|
||||||
|
|
||||||
|
ASSERT_EQ(i, fps.FrameAtSmpte(h, m, s, f));
|
||||||
|
++i;
|
||||||
|
++f;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in a new issue