Rewrite vfr.cpp in libaegisub with tests. Not yet used by Aegisub itself.

Originally committed to SVN as r4661.
This commit is contained in:
Thomas Goyne 2010-07-07 05:24:16 +00:00
parent 929fa83dd9
commit 9322f95071
44 changed files with 37900 additions and 6 deletions

View file

@ -226,6 +226,10 @@
RelativePath="..\..\libaegisub\common\validator.cpp"
>
</File>
<File
RelativePath="..\..\libaegisub\common\vfr.cpp"
>
</File>
</Filter>
<Filter
Name="Windows"
@ -344,6 +348,10 @@
RelativePath="..\..\libaegisub\include\libaegisub\io.h"
>
</File>
<File
RelativePath="..\..\libaegisub\include\libaegisub\line_iterator.h"
>
</File>
<File
RelativePath="..\..\libaegisub\include\libaegisub\log.h"
>
@ -384,6 +392,10 @@
RelativePath="..\..\libaegisub\include\libaegisub\validator.h"
>
</File>
<File
RelativePath="..\..\libaegisub\include\libaegisub\vfr.h"
>
</File>
</Filter>
<File
RelativePath="..\..\libaegisub\lagi_pre.h"

View file

@ -86,6 +86,7 @@
/>
<Tool
Name="VCPostBuildEventTool"
CommandLine="cd &quot;$(ExecutableOutDir)&quot;&#x0D;&#x0A;&quot;$(ProjectDir)\..\..\tests\setup.bat&quot; &quot;$(ProjectDir)\..\..\tests&quot;&#x0D;&#x0A;"
/>
</Configuration>
<Configuration
@ -248,6 +249,10 @@
RelativePath="..\..\tests\libaegisub_util.cpp"
>
</File>
<File
RelativePath="..\..\tests\libaegisub_vfr.cpp"
>
</File>
</Files>
<Globals>
</Globals>

View file

@ -30,6 +30,7 @@ libaegisub_2_2_la_SOURCES = \
common/option_visit.cpp \
common/log.cpp \
common/validator.cpp \
common/vfr.cpp \
unix/util.cpp \
unix/io.cpp \
unix/access.cpp \

View file

@ -0,0 +1,307 @@
// Copyright (c) 2010, Thomas Goyne <plorkyeran@aegisub.org>
//
// Permission to use, copy, modify, and distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
// copyright notice and this permission notice appear in all copies.
//
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
// WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
// MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
// ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
// WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
// ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
// OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
//
// $Id$
/// @file vfr.cpp
/// @brief Framerate handling of all sorts
/// @ingroup libaegisub video_input
#include "libaegisub/vfr.h"
#ifndef LAGI_PRE
#include <algorithm>
#include <functional>
#include <iterator>
#include <numeric>
#endif
#include "libaegisub/charset.h"
#include "libaegisub/io.h"
#include "libaegisub/line_iterator.h"
namespace std {
template<> void swap(agi::vfr::Framerate &lft, agi::vfr::Framerate &rgt) throw() {
lft.swap(rgt);
}
}
namespace agi {
namespace vfr {
static int is_increasing(int prev, int cur) {
if (prev >= cur) {
throw UnorderedTimecodes("Timecodes are out of order or too close together");
}
return cur;
}
/// @brief Verify that timecodes monotonically increase
/// @param timecodes List of timecodes to check
static void validate_timecodes(std::vector<int> const& timecodes) {
if (timecodes.size() <= 1) {
throw TooFewTimecodes("Must have at least two timecodes to do anything useful");
}
std::accumulate(timecodes.begin()+1, timecodes.end(), timecodes.front(), is_increasing);
}
// A "start,end,fps" line in a v1 timecode file
struct TimecodeRange {
int start;
int end;
double fps;
double time;
bool operator<(TimecodeRange cmp) {
return start < cmp.start;
}
TimecodeRange() : fps(0.) { }
};
/// @brief Parse a single line of a v1 timecode file
/// @param str Line to parse
/// @return The line in TimecodeRange form, or TimecodeRange() if it's a comment
static TimecodeRange v1_parse_line(std::string const& str) {
if (str.empty() || str[0] == '#') return TimecodeRange();
std::istringstream ss(str);
TimecodeRange range;
char comma1, comma2;
ss >> range.start >> comma1 >> range.end >> comma2 >> range.fps;
if (ss.fail() || comma1 != ',' || comma2 != ',' || !ss.eof()) {
throw MalformedLine(str);
}
if (range.start < 0 || range.end < 0) {
throw UnorderedTimecodes("Cannot specify frame rate for negative frames.");
}
if (range.end < range.start) {
throw UnorderedTimecodes("End frame must be greater than or equal to start frame");
}
if (range.fps <= 0.) {
throw BadFPS("FPS must be greater than zero");
}
if (range.fps > 1000.) {
// This is our limitation, not mkvmerge's
// mkvmerge uses nanoseconds internally
throw BadFPS("FPS must be at most 1000");
}
return range;
}
/// @brief Is the timecode range a comment line?
static bool v1_invalid_timecode(TimecodeRange const& range) {
return range.fps == 0.;
}
/// @brief Generate override ranges for all frames with assumed fpses
/// @param ranges List with ranges which is mutated
/// @param fps Assumed fps to use for gaps
static void v1_fill_range_gaps(std::list<TimecodeRange> &ranges, double fps) {
// Range for frames between start and first override
if (ranges.empty() || ranges.front().start > 0) {
TimecodeRange range;
range.fps = fps;
range.start = 0;
range.end = ranges.empty() ? 0 : ranges.front().start - 1;
ranges.push_front(range);
}
std::list<TimecodeRange>::iterator cur = ++ranges.begin();
std::list<TimecodeRange>::iterator prev = ranges.begin();
for (; cur != ranges.end(); ++cur, ++prev) {
if (prev->end >= cur->start) {
// mkvmerge allows overlapping timecode ranges, but does completely
// broken things with them
throw UnorderedTimecodes("Override ranges must not overlap");
}
if (prev->end + 1 < cur->start) {
TimecodeRange range;
range.fps = fps;
range.start = prev->end + 1;
range.end = cur->start - 1;
ranges.insert(cur, range);
++prev;
}
}
}
/// @brief Parse a v1 timecode file
/// @param file Iterator of lines in the file
/// @param line Header of file with assumed fps
/// @param[out] timecodes Vector filled with frame start times
/// @param[out] time Unrounded time of the last frame
/// @return Assumed fps
static double v1_parse(line_iterator<std::string> file, std::string line, std::vector<int> &timecodes, double &time) {
using namespace std;
double fps = atof(line.substr(7).c_str());
if (fps <= 0.) throw BadFPS("Assumed FPS must be greater than zero");
if (fps > 1000.) throw BadFPS("Assumed FPS must not be greater than 1000");
list<TimecodeRange> ranges;
transform(file, line_iterator<string>(), back_inserter(ranges), v1_parse_line);
ranges.erase(remove_if(ranges.begin(), ranges.end(), v1_invalid_timecode), ranges.end());
ranges.sort();
v1_fill_range_gaps(ranges, fps);
timecodes.reserve(ranges.back().end);
time = 0.;
for (list<TimecodeRange>::iterator cur = ranges.begin(); cur != ranges.end(); ++cur) {
for (int frame = cur->start; frame <= cur->end; frame++) {
timecodes.push_back(int(time + .5));
time += 1000. / cur->fps;
}
}
timecodes.push_back(int(time + .5));
return fps;
}
Framerate::Framerate(Framerate const& that)
: fps(that.fps)
, last(that.last)
, timecodes(that.timecodes)
{
}
Framerate::Framerate(double fps) : fps(fps), last(0.) {
if (fps < 0.) throw BadFPS("FPS must be greater than zero");
if (fps > 1000.) throw BadFPS("FPS must not be greater than 1000");
}
Framerate::Framerate(std::vector<int> const& timecodes)
: timecodes(timecodes)
{
validate_timecodes(timecodes);
fps = timecodes.size() / (timecodes.back() / 1000.);
last = timecodes.back();
}
Framerate::~Framerate() {
}
void Framerate::swap(Framerate &right) throw() {
std::swap(fps, right.fps);
std::swap(last, right.last);
std::swap(timecodes, right.timecodes);
}
Framerate &Framerate::operator=(Framerate right) {
std::swap(*this, right);
return *this;
}
Framerate &Framerate::operator=(double fps) {
return *this = Framerate(fps);
}
bool Framerate::operator==(Framerate const& right) const {
return fps == right.fps && timecodes == right.timecodes;
}
Framerate::Framerate(std::string const& filename) : fps(0.) {
using namespace std;
auto_ptr<ifstream> file(agi::io::Open(filename));
string encoding = agi::charset::Detect(filename);
string line = *line_iterator<string>(*file, encoding);
if (line == "# timecode format v2") {
copy(line_iterator<int>(*file, encoding), line_iterator<int>(), back_inserter(timecodes));
validate_timecodes(timecodes);
fps = timecodes.size() / (timecodes.back() / 1000.);
return;
}
if (line == "# timecode format v1" || line.substr(0, 7) == "Assume ") {
if (line[0] == '#') {
line = *line_iterator<string>(*file, encoding);
}
fps = v1_parse(line_iterator<string>(*file, encoding), line, timecodes, last);
return;
}
throw UnknownFormat(line);
}
void Framerate::Save(std::string const& filename, int length) const {
agi::io::Save file(filename);
std::ofstream &out = file.Get();
out << "# timecode format v2\n";
std::copy(timecodes.begin(), timecodes.end(), std::ostream_iterator<int>(out, "\n"));
for (int written = timecodes.size(); written < length; ++written) {
out << TimeAtFrame(written) << std::endl;
}
}
static int round(double value) {
return int(value + .5);
}
int Framerate::FrameAtTime(int ms, Time type) const {
// With X ms per frame, this should return 0 for:
// EXACT: [0, X]
// START: [-X, 0]
// END: [1, X + 1]
// There are two properties we take advantage of here:
// 1. START and END's ranges are adjacent, meaning doing the calculations
// for END and adding one gives us START
// 2. END is EXACT plus one ms, meaning we can subtract one ms to get EXACT
// Combining these allows us to easily calculate START and END in terms of
// EXACT
if (type == START) {
return FrameAtTime(ms - 1) + 1;
}
if (type == END) {
return FrameAtTime(ms - 1);
}
if (timecodes.empty()) {
return (int)floor(ms * fps / 1000.);
}
if (ms < timecodes.front()) {
return (int)floor((ms - timecodes.front()) * fps / 1000.);
}
if (ms > timecodes.back()) {
return round((ms - timecodes.back()) * fps / 1000.) + timecodes.size() - 1;
}
return std::distance(std::lower_bound(timecodes.rbegin(), timecodes.rend(), ms, std::greater<int>()), timecodes.rend()) - 1;
}
int Framerate::TimeAtFrame(int frame, Time type) const {
if (type == START) {
int prev = TimeAtFrame(frame - 1);
int cur = TimeAtFrame(frame);
// + 1 as these need to round up for the case of two frames 1 ms apart
return prev + (cur - prev + 1) / 2;
}
if (type == END) {
int cur = TimeAtFrame(frame);
int next = TimeAtFrame(frame + 1);
return cur + (next - cur + 1) / 2;
}
if (timecodes.empty()) {
return (int)ceil(frame / fps * 1000.);
}
if (frame < 0) {
return (int)ceil(frame / fps * 1000.) + timecodes.front();
}
if (frame >= (signed)timecodes.size()) {
return round((frame - timecodes.size() + 1) * 1000. / fps + last);
}
return timecodes[frame];
}
}
}

View file

@ -38,7 +38,7 @@ namespace agi {
/// @class line_iterator
/// @brief An iterator over lines in a stream
template<typename OutputType = std::string>
template<class OutputType = std::string>
class line_iterator : public std::iterator<std::input_iterator_tag, OutputType> {
std::istream *stream; ///< Stream to iterator over
bool valid; ///< Are there any more values to read?
@ -151,7 +151,7 @@ public:
}
};
template<typename OutputType>
template<class OutputType>
void line_iterator<OutputType>::getline(std::string &str) {
union {
int32_t chr;
@ -180,7 +180,7 @@ void line_iterator<OutputType>::getline(std::string &str) {
}
}
template<typename OutputType>
template<class OutputType>
void line_iterator<OutputType>::next() {
if (!valid) return;
if (!stream->good()) {
@ -198,7 +198,7 @@ void line_iterator<OutputType>::next() {
}
}
template<typename OutputType>
template<class OutputType>
inline bool line_iterator<OutputType>::convert(std::string &str) {
std::istringstream ss(str);
ss >> value;
@ -210,7 +210,7 @@ inline bool line_iterator<std::string>::convert(std::string &str) {
return true;
}
template<typename T>
template<class T>
void swap(agi::line_iterator<T> &lft, agi::line_iterator<T> &rgt) {
lft.swap(rgt);
}

View file

@ -0,0 +1,142 @@
// Copyright (c) 2010, Thomas Goyne <plorkyeran@aegisub.org>
//
// Permission to use, copy, modify, and distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
// copyright notice and this permission notice appear in all copies.
//
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
// WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
// MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
// ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
// WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
// ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
// OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
//
// $Id$
/// @file vfr.h
/// @brief Framerate handling of all sorts
/// @ingroup libaegisub video_input
#pragma once
#if !defined(AGI_PRE) && !defined(LAGI_PRE)
#include <string>
#include <vector>
#endif
#include <libaegisub/exception.h>
namespace agi {
namespace vfr {
enum Time {
/// Use the actual frame times
/// With 1 FPS video, frame 0 is [0, 999] ms
EXACT,
/// Calculate based on the rules for start times of lines
/// Lines are first visible on the first frame with start time less than
/// or equal to the start time; thus with 1.0 FPS video, frame 0 is
/// [-999, 0] ms
START,
/// Calculate based on the rules for end times of lines
/// Lines are last visible on the last frame with start time less than the
/// end time; thus with 1.0 FPS video, frame 0 is [1, 1000] ms
END
};
DEFINE_BASE_EXCEPTION_NOINNER(Error, Exception)
/// FPS specified is not a valid frame rate
DEFINE_SIMPLE_EXCEPTION_NOINNER(BadFPS, Error, "vfr/badfps")
/// Unknown timecode file format
DEFINE_SIMPLE_EXCEPTION_NOINNER(UnknownFormat, Error, "vfr/timecodes/unknownformat")
/// Invalid line encountered in a timecode file
DEFINE_SIMPLE_EXCEPTION_NOINNER(MalformedLine, Error, "vfr/timecodes/malformed")
/// Timecode file or vector has too few timecodes to be usable
DEFINE_SIMPLE_EXCEPTION_NOINNER(TooFewTimecodes, Error, "vfr/timecodes/toofew")
/// Timecode file or vector has timecodes that are not monotonically increasing
DEFINE_SIMPLE_EXCEPTION_NOINNER(UnorderedTimecodes, Error, "vfr/timecodes/order")
/// @class Framerate
/// @brief Class for managing everything related to converting frames to times
/// or vice versa
class Framerate {
/// Average FPS for v2, assumed FPS for v1, fps for CFR
double fps;
/// Unrounded time of the last frame in a v1 override range. Needed to
/// match mkvmerge's rounding
double last;
/// Start time in milliseconds of each frame
std::vector<int> timecodes;
public:
/// Copy constructor
Framerate(Framerate const&);
/// @brief VFR from timecodes file
/// @param filename File with v1 or v2 timecodes
///
/// Note that loading a v1 timecode file with Assume X and no overrides is
/// not the same thing as CFR X. When timecodes are loaded from a file,
/// mkvmerge-style rounding is applied, while setting a constant frame rate
/// uses truncation.
Framerate(std::string const& filename);
/// @brief CFR constructor
/// @param fps Frames per second or 0 for unloaded
Framerate(double fps = 0.);
/// @brief VFR from frame times
/// @param timecodes Vector of frame start times in milliseconds
Framerate(std::vector<int> const& timecodes);
~Framerate();
/// Atomic assignment operator
Framerate &operator=(Framerate);
/// Atomic CFR assignment operator
Framerate &operator=(double);
/// Helper function for the std::swap specialization
void swap(Framerate &right) throw();
/// @brief Get the frame visible at a given time
/// @param ms Time in milliseconds
/// @param type Time mode
///
/// When type is EXACT, the frame returned is the frame visible at the given
/// time; when it is START or END it is the frame on which a line with that
/// start/end time would first/last be visible
int FrameAtTime(int ms, Time type = EXACT) const;
/// @brief Get the time at a given frame
/// @param frame Frame number
/// @param type Time mode
///
/// When type is EXACT, the frame's exact start time is returned; START and
/// END give a time somewhere within the range that will result in the line
/// starting/ending on that frame
///
/// With v2 timecodes, frames outside the defined range are not an error
/// and are guaranteed to be monotonically increasing/decreasing values
/// which when passed to FrameAtTime will return the original frame; they
/// are not guaranteed to be sensible or useful for any other purpose
///
/// v1 timecodes and CFR do not have a defined range, and will give sensible
/// results for all frame numbers
int TimeAtFrame(int frame, Time type = EXACT) const;
/// @brief Save the current time codes to a file as v2 timecodes
/// @param file File name
/// @param length Minimum number of frames to output
///
/// The length parameter is only particularly useful for v1 timecodes (and
/// CFR, but saving CFR timecodes is a bit silly). Extra timecodes generated
/// to hit length with v2 timecodes will monotonically increase but may not
/// be otherwise sensible.
void Save(std::string const& file, int length = -1) const;
bool IsVFR() const {return !timecodes.empty(); }
bool IsLoaded() const { return !timecodes.empty() || fps; };
double FPS() const { return fps; }
/// @brief Equality operator
/// @attention O(n) when both arguments are VFR
bool operator==(Framerate const& right) const;
};
}
}

View file

@ -27,6 +27,8 @@
#include <deque>
#include <fstream>
#include <iostream>
#include <iterator>
#include <numeric>
#include <map>
#ifdef _WIN32
#include <memory>

View file

@ -15,7 +15,8 @@ run_SOURCES = \
libaegisub_iconv.cpp \
libaegisub_line_iterator.cpp \
libaegisub_mru.cpp \
libaegisub_util.cpp
libaegisub_util.cpp \
libaegisub_vfr.cpp
run_SOURCES += \
*.h

View file

@ -0,0 +1,378 @@
// Copyright (c) 2010, Thomas Goyne <plorkyeran@aegisub.org>
//
// Permission to use, copy, modify, and distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
// copyright notice and this permission notice appear in all copies.
//
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
// WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
// MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
// ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
// WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
// ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
// OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
//
// $Id$
/// @file libaegisub_vfr.cpp
/// @brief agi::vfr::Framerate tests
/// @ingroup video_input
#include <libaegisub/vfr.h>
#include <fstream>
#include <iterator>
#include "main.h"
#include "util.h"
using namespace agi::vfr;
using namespace util;
#define EXPECT_RANGE(low, high, test) \
EXPECT_LE(low, test); \
EXPECT_GE(high, test)
TEST(lagi_vfr, constructors_good) {
EXPECT_NO_THROW(Framerate(1.));
EXPECT_NO_THROW(Framerate(Framerate(1.)));
EXPECT_NO_THROW(Framerate(make_vector<int>(2, 0, 10)));
EXPECT_NO_THROW(Framerate("data/vfr/in/v1_start_equals_end.txt"));
EXPECT_NO_THROW(Framerate("data/vfr/in/v1_whitespace.txt"));
EXPECT_NO_THROW(Framerate("data/vfr/in/v1_assume_int.txt"));
EXPECT_NO_THROW(Framerate("data/vfr/in/v1_out_of_order.txt"));
}
TEST(lagi_vfr, constructors_bad_cfr) {
EXPECT_THROW(Framerate(-1.), BadFPS);
EXPECT_THROW(Framerate(1000.1), BadFPS);
}
TEST(lagi_vfr, constructors_bad_timecodes) {
EXPECT_THROW(Framerate(make_vector<int>(0)), TooFewTimecodes);
EXPECT_THROW(Framerate(make_vector<int>(1, 0)), TooFewTimecodes);
EXPECT_THROW(Framerate(make_vector<int>(2, 10, 0)), UnorderedTimecodes);
}
TEST(lagi_vfr, constructors_bad_v1) {
EXPECT_THROW(Framerate("data/vfr/in/v1_bad_seperators.txt"), MalformedLine);
EXPECT_THROW(Framerate("data/vfr/in/v1_too_few_parts.txt"), MalformedLine);
EXPECT_THROW(Framerate("data/vfr/in/v1_too_many_parts.txt"), MalformedLine);
EXPECT_THROW(Framerate("data/vfr/in/v1_float_frame_number.txt"), MalformedLine);
EXPECT_THROW(Framerate("data/vfr/in/v1_start_end_overlap.txt"), UnorderedTimecodes);
EXPECT_THROW(Framerate("data/vfr/in/v1_fully_contained.txt"), UnorderedTimecodes);
EXPECT_THROW(Framerate("data/vfr/in/v1_assume_over_1000.txt"), BadFPS);
EXPECT_THROW(Framerate("data/vfr/in/v1_override_over_1000.txt"), BadFPS);
EXPECT_THROW(Framerate("data/vfr/in/v1_override_zero.txt"), BadFPS);
EXPECT_THROW(Framerate("data/vfr/in/v1_negative_start_of_range.txt"), UnorderedTimecodes);
EXPECT_THROW(Framerate("data/vfr/in/v1_end_less_than_start.txt"), UnorderedTimecodes);
}
TEST(lagi_vfr, constructors_bad_v2) {
EXPECT_THROW(Framerate("data/vfr/in/v2_empty.txt"), TooFewTimecodes);
EXPECT_THROW(Framerate("data/vfr/in/v2_out_of_order.txt"), UnorderedTimecodes);
EXPECT_THROW(Framerate("data/vfr/in/empty.txt"), UnknownFormat);
}
TEST(lagi_vfr, cfr_frame_at_time_exact) {
Framerate fps;
ASSERT_NO_THROW(fps = Framerate(1.));
EXPECT_EQ(0, fps.FrameAtTime(0));
EXPECT_EQ(0, fps.FrameAtTime(999));
EXPECT_EQ(1, fps.FrameAtTime(1000));
EXPECT_EQ(1, fps.FrameAtTime(1999));
EXPECT_EQ(100, fps.FrameAtTime(100000));
EXPECT_EQ(-1, fps.FrameAtTime(-1));
EXPECT_EQ(-1, fps.FrameAtTime(-1000));
EXPECT_EQ(-2, fps.FrameAtTime(-1001));
}
TEST(lagi_vfr, cfr_frame_at_time_start) {
Framerate fps;
ASSERT_NO_THROW(fps = Framerate(1.));
EXPECT_EQ(0, fps.FrameAtTime(0, START));
EXPECT_EQ(1, fps.FrameAtTime(1, START));
EXPECT_EQ(1, fps.FrameAtTime(1000, START));
EXPECT_EQ(2, fps.FrameAtTime(1001, START));
EXPECT_EQ(100, fps.FrameAtTime(100000, START));
EXPECT_EQ(0, fps.FrameAtTime(-1, START));
EXPECT_EQ(0, fps.FrameAtTime(-999, START));
EXPECT_EQ(-1, fps.FrameAtTime(-1000, START));
EXPECT_EQ(-1, fps.FrameAtTime(-1999, START));
}
TEST(lagi_vfr, cfr_frame_at_time_end) {
Framerate fps;
ASSERT_NO_THROW(fps = Framerate(1.));
EXPECT_EQ(-1, fps.FrameAtTime(0, END));
EXPECT_EQ(0, fps.FrameAtTime(1, END));
EXPECT_EQ(0, fps.FrameAtTime(1000, END));
EXPECT_EQ(1, fps.FrameAtTime(1001, END));
EXPECT_EQ(99, fps.FrameAtTime(100000, END));
EXPECT_EQ(-1, fps.FrameAtTime(-1, END));
EXPECT_EQ(-1, fps.FrameAtTime(-999, END));
EXPECT_EQ(-2, fps.FrameAtTime(-1000, END));
EXPECT_EQ(-2, fps.FrameAtTime(-1999, END));
}
TEST(lagi_vfr, cfr_time_at_frame_exact) {
Framerate fps;
ASSERT_NO_THROW(fps = Framerate(1.));
EXPECT_EQ( 0, fps.TimeAtFrame(0));
EXPECT_EQ( 1000, fps.TimeAtFrame(1));
EXPECT_EQ( 2000, fps.TimeAtFrame(2));
EXPECT_EQ(-1000, fps.TimeAtFrame(-1));
}
TEST(lagi_vfr, cfr_time_at_frame_start) {
Framerate fps;
ASSERT_NO_THROW(fps = Framerate(1.));
EXPECT_RANGE( -999, 0, fps.TimeAtFrame( 0, START));
EXPECT_RANGE( 1, 1000, fps.TimeAtFrame( 1, START));
EXPECT_RANGE( 1001, 2000, fps.TimeAtFrame( 2, START));
EXPECT_RANGE(-1999, -1000, fps.TimeAtFrame(-1, START));
}
TEST(lagi_vfr, cfr_time_at_frame_end) {
Framerate fps;
ASSERT_NO_THROW(fps = Framerate(1.));
EXPECT_RANGE( 1, 1000, fps.TimeAtFrame( 0, END));
EXPECT_RANGE(1001, 2000, fps.TimeAtFrame( 1, END));
EXPECT_RANGE(2001, 3000, fps.TimeAtFrame( 2, END));
EXPECT_RANGE(-999, 0, fps.TimeAtFrame(-1, END));
}
TEST(lagi_vfr, cfr_round_trip_exact) {
Framerate fps;
ASSERT_NO_THROW(fps = Framerate(1.));
for (int i = -10; i < 11; i++) {
EXPECT_EQ(i, fps.FrameAtTime(fps.TimeAtFrame(i)));
}
}
TEST(lagi_vfr, cfr_round_trip_start) {
Framerate fps;
ASSERT_NO_THROW(fps = Framerate(1.));
for (int i = -10; i < 11; i++) {
EXPECT_EQ(i, fps.FrameAtTime(fps.TimeAtFrame(i, START), START));
}
}
TEST(lagi_vfr, cfr_round_trip_end) {
Framerate fps;
ASSERT_NO_THROW(fps = Framerate(1.));
for (int i = -10; i < 11; i++) {
EXPECT_EQ(i, fps.FrameAtTime(fps.TimeAtFrame(i, END), END));
}
}
TEST(lagi_vfr, vfr_round_trip_exact) {
Framerate fps;
ASSERT_NO_THROW(fps = Framerate(make_vector<int>(7, 0, 1000, 1500, 2000, 2001, 2002, 2003)));
for (int i = -10; i < 11; i++) {
EXPECT_EQ(i, fps.FrameAtTime(fps.TimeAtFrame(i)));
}
}
TEST(lagi_vfr, vfr_round_trip_start) {
Framerate fps;
ASSERT_NO_THROW(fps = Framerate(make_vector<int>(7, 0, 1000, 1500, 2000, 2001, 2002, 2003)));
for (int i = -10; i < 11; i++) {
EXPECT_EQ(i, fps.FrameAtTime(fps.TimeAtFrame(i, START), START));
}
}
TEST(lagi_vfr, vfr_round_trip_end) {
Framerate fps;
ASSERT_NO_THROW(fps = Framerate(make_vector<int>(7, 0, 1000, 1500, 2000, 2001, 2002, 2003)));
for (int i = -10; i < 11; i++) {
EXPECT_EQ(i, fps.FrameAtTime(fps.TimeAtFrame(i, END), END));
}
}
TEST(lagi_vfr, vfr_time_at_frame_exact) {
Framerate fps;
ASSERT_NO_THROW(fps = Framerate(make_vector<int>(5, 0, 1000, 1500, 2000, 2001)));
EXPECT_EQ(0, fps.TimeAtFrame(0));
EXPECT_EQ(1000, fps.TimeAtFrame(1));
EXPECT_EQ(1500, fps.TimeAtFrame(2));
EXPECT_EQ(2000, fps.TimeAtFrame(3));
EXPECT_EQ(2001, fps.TimeAtFrame(4));
}
TEST(lagi_vfr, vfr_time_at_frame_start) {
Framerate fps;
ASSERT_NO_THROW(fps = Framerate(make_vector<int>(7, 0, 1000, 1500, 2000, 2001, 2002, 2003)));
EXPECT_GE(0, fps.TimeAtFrame(0, START));
EXPECT_RANGE(1, 1000, fps.TimeAtFrame(1, START));
EXPECT_RANGE(1001, 1500, fps.TimeAtFrame(2, START));
EXPECT_RANGE(1501, 2000, fps.TimeAtFrame(3, START));
EXPECT_EQ(2001, fps.TimeAtFrame(4, START));
EXPECT_EQ(2002, fps.TimeAtFrame(5, START));
EXPECT_LE(2003, fps.TimeAtFrame(6, START));
}
TEST(lagi_vfr, vfr_time_at_frame_end) {
Framerate fps;
ASSERT_NO_THROW(fps = Framerate(make_vector<int>(7, 0, 1000, 1500, 2000, 2001, 2002, 2003)));
EXPECT_RANGE(1, 1000, fps.TimeAtFrame(0, END));
EXPECT_RANGE(1001, 1500, fps.TimeAtFrame(1, END));
EXPECT_RANGE(1501, 2000, fps.TimeAtFrame(2, END));
EXPECT_EQ(2001, fps.TimeAtFrame(3, END));
EXPECT_EQ(2002, fps.TimeAtFrame(4, END));
EXPECT_EQ(2003, fps.TimeAtFrame(5, END));
EXPECT_LE(2004, fps.TimeAtFrame(6, END));
}
TEST(lagi_vfr, vfr_time_at_frame_outside_range) {
Framerate fps;
ASSERT_NO_THROW(fps = Framerate(make_vector<int>(3, 0, 100, 200)));
EXPECT_GT(0, fps.TimeAtFrame(-1));
EXPECT_EQ(0, fps.TimeAtFrame(0));
EXPECT_EQ(100, fps.TimeAtFrame(1));
EXPECT_EQ(200, fps.TimeAtFrame(2));
EXPECT_LT(200, fps.TimeAtFrame(3));
int prev = fps.TimeAtFrame(3);
for (int i = 4; i < 10; i++) {
EXPECT_LT(prev, fps.TimeAtFrame(i));
prev = fps.TimeAtFrame(i);
}
}
TEST(lagi_vfr, vfr_frame_at_time_exact) {
Framerate fps;
ASSERT_NO_THROW(fps = Framerate(make_vector<int>(7, 0, 1000, 1500, 2000, 2001, 2002, 2003)));
EXPECT_GT(0, fps.FrameAtTime(-1));
EXPECT_EQ(0, fps.FrameAtTime(0));
EXPECT_EQ(0, fps.FrameAtTime(999));
EXPECT_EQ(1, fps.FrameAtTime(1000));
EXPECT_EQ(1, fps.FrameAtTime(1499));
EXPECT_EQ(2, fps.FrameAtTime(1500));
EXPECT_EQ(2, fps.FrameAtTime(1999));
EXPECT_EQ(3, fps.FrameAtTime(2000));
EXPECT_EQ(4, fps.FrameAtTime(2001));
EXPECT_EQ(5, fps.FrameAtTime(2002));
EXPECT_EQ(6, fps.FrameAtTime(2003));
EXPECT_LE(6, fps.FrameAtTime(2004));
}
TEST(lagi_vfr, vfr_frame_at_time_start) {
Framerate fps;
ASSERT_NO_THROW(fps = Framerate(make_vector<int>(7, 0, 1000, 1500, 2000, 2001, 2002, 2003)));
EXPECT_GE(0, fps.FrameAtTime(-1, START));
EXPECT_EQ(0, fps.FrameAtTime(0, START));
EXPECT_EQ(1, fps.FrameAtTime(1, START));
EXPECT_EQ(1, fps.FrameAtTime(1000, START));
EXPECT_EQ(2, fps.FrameAtTime(1001, START));
EXPECT_EQ(2, fps.FrameAtTime(1500, START));
EXPECT_EQ(3, fps.FrameAtTime(1501, START));
EXPECT_EQ(3, fps.FrameAtTime(2000, START));
EXPECT_EQ(4, fps.FrameAtTime(2001, START));
EXPECT_EQ(5, fps.FrameAtTime(2002, START));
EXPECT_EQ(6, fps.FrameAtTime(2003, START));
EXPECT_LE(6, fps.FrameAtTime(2004, START));
}
bool validate_save(std::string const& goodFilename, std::string const& checkFilename, int v2Lines = -1, bool allowLonger = false) {
std::ifstream good(goodFilename.c_str());
std::ifstream check(checkFilename.c_str());
EXPECT_TRUE(good.good());
EXPECT_TRUE(check.good());
std::string good_header;
std::string check_header;
std::getline(good, good_header);
std::getline(check, check_header);
// istream_iterator rather than line_reader because we never write comments
// or empty lines in timecode files
std::istream_iterator<double> good_iter(good);
std::istream_iterator<double> check_iter(check);
std::istream_iterator<double> end;
int line = 0;
for (; good_iter != end; ++good_iter, ++check_iter, ++line) {
if (check_iter == end) return false;
if (v2Lines < 0 || line < v2Lines) {
if (*good_iter != *check_iter) return false;
}
}
// v1 timecodes with the end of a range past the end of the video are valid,
// and when saving those there will be too many timecodes in the v2 file
if (!allowLonger && check_iter != end) return false;
return true;
}
TEST(lagi_vfr, validate_save) {
EXPECT_TRUE(validate_save("data/vfr/in/validate_base.txt", "data/vfr/in/validate_base.txt"));
EXPECT_FALSE(validate_save("data/vfr/in/validate_base.txt", "data/vfr/in/validate_different.txt"));
EXPECT_FALSE(validate_save("data/vfr/in/validate_base.txt", "data/vfr/in/validate_shorter.txt"));
EXPECT_FALSE(validate_save("data/vfr/in/validate_base.txt", "data/vfr/in/validate_longer.txt"));
EXPECT_TRUE(validate_save("data/vfr/in/validate_base.txt", "data/vfr/in/validate_longer.txt", -1, true));
}
TEST(lagi_vfr, save_vfr_nolen) {
Framerate fps;
ASSERT_NO_THROW(fps = Framerate(make_vector<int>(3, 0, 100, 200)));
ASSERT_NO_THROW(fps.Save("data/vfr/out/v2_nolen.txt"));
EXPECT_TRUE(validate_save("data/vfr/in/v2_nolen.txt", "data/vfr/out/v2_nolen.txt"));
}
TEST(lagi_vfr, save_vfr_len) {
Framerate fps;
ASSERT_NO_THROW(fps = Framerate(make_vector<int>(3, 0, 100, 200)));
ASSERT_NO_THROW(fps.Save("data/vfr/out/v2_len_3_10.txt", 10));
EXPECT_TRUE(validate_save("data/vfr/in/v2_len_3_10.txt", "data/vfr/out/v2_len_3_10.txt", 3));
}
TEST(lagi_vfr, load_v2) {
Framerate fps;
ASSERT_NO_THROW(fps = Framerate("data/vfr/in/v2_1fps.txt"));
for (int i = 0; i < 9; i++) {
EXPECT_EQ(i * 1000, fps.TimeAtFrame(i));
}
}
TEST(lagi_vfr, load_v2_comments) {
Framerate fps;
ASSERT_NO_THROW(fps = Framerate("data/vfr/in/v2_comments.txt"));
for (int i = 0; i < 9; i++) {
EXPECT_EQ(i * 1000, fps.TimeAtFrame(i));
}
}
TEST(lagi_vfr, load_v2_number_in_comment) {
Framerate fps;
ASSERT_NO_THROW(fps = Framerate("data/vfr/in/v2_number_in_comment.txt"));
for (int i = 0; i < 9; i++) {
EXPECT_EQ(i * 1000, fps.TimeAtFrame(i));
}
}
TEST(lagi_vfr, load_v1_save_v2) {
Framerate fps;
ASSERT_NO_THROW(fps = Framerate("data/vfr/in/v1_mode5.txt"));
EXPECT_NO_THROW(fps.Save("data/vfr/out/v2_mode5.txt"));
EXPECT_TRUE(validate_save("data/vfr/in/v2_mode5.txt", "data/vfr/out/v2_mode5.txt", -1, true));
}
TEST(lagi_vfr, load_v1_save_v2_len) {
Framerate fps;
ASSERT_NO_THROW(fps = Framerate("data/vfr/in/v1_assume_30.txt"));
ASSERT_NO_THROW(fps.Save("data/vfr/out/v2_100_frames_30_fps.txt", 100));
EXPECT_TRUE(validate_save("data/vfr/in/v2_100_frames_30_fps.txt", "data/vfr/out/v2_100_frames_30_fps.txt"));
}
TEST(lagi_vfr, load_v1_save_v2_ovr) {
Framerate fps;
ASSERT_NO_THROW(fps = Framerate("data/vfr/in/v1_assume_30_with_override.txt"));
ASSERT_NO_THROW(fps.Save("data/vfr/out/v2_100_frames_30_with_override.txt", 100));
EXPECT_TRUE(validate_save("data/vfr/in/v2_100_frames_30_with_override.txt", "data/vfr/out/v2_100_frames_30_with_override.txt"));
}

View file

@ -38,3 +38,9 @@ echo {"String" : [{"string" : "This is a test"}, {"string" : "This is a test"}]}
echo {"Integer" : [{"int" : 1}, {"int" : 1}]} > data/option_array_integer
echo {"Double" : [{"double" : 2.1}, {"double" : 2.1}]} > data/option_array_double
echo {"Bool" : [{"bool" : true}, {"bool" : true}]} > data/option_array_bool
mkdir data\vfr
mkdir data\vfr\in
mkdir data\vfr\out
xcopy %1\vfr data\vfr\in

View file

@ -38,3 +38,8 @@ echo '{"String" : [{"string" : "This is a test"}, {"string" : "This is a test"}]
echo '{"Integer" : [{"int" : 1}, {"int" : 1}]}' > data/option_array_integer
echo '{"Double" : [{"double" : 2.1}, {"double" : 2.1}]}' > data/option_array_double
echo '{"Bool" : [{"bool" : true}, {"bool" : true}]}' > data/option_array_bool
mkdir data/vfr
mkdir data/vfr/in
mkdir data/vfr/out
cp `dirname $0`/vfr/* data/vfr/in/

View file

View file

@ -0,0 +1,2 @@
# timecode format v1
Assume 30

View file

@ -0,0 +1,3 @@
# timecode format v1
Assume 30
20,50,19

View file

@ -0,0 +1,3 @@
# timecode format v1
Assume 25
0,10,30

View file

@ -0,0 +1,2 @@
# timecode format v1
Assume 1000.1

View file

@ -0,0 +1,3 @@
# timecode format v1
Assume 29.970030
0;20;24.2

View file

@ -0,0 +1,3 @@
# timecode format v1
Assume 29.970030
20,0,25

View file

@ -0,0 +1,3 @@
# timecode format v1
Assume 29.970030
0.0,10.0,25

View file

@ -0,0 +1,4 @@
# timecode format v1
Assume 29.970030
0,50,10
10,40,10

View file

@ -0,0 +1,255 @@
# timecode format v1
Assume 29.970030
# TDecimate v1.0.3 by tritical
# Mode 5 - Auto-generated mkv timecodes file
10,93,23.976024
99,146,23.976024
157,164,23.976024
175,178,23.976024
219,238,23.976024
329,340,23.976024
401,424,23.976024
455,466,23.976024
532,543,23.976024
569,584,23.976024
605,616,23.976024
647,658,23.976024
664,683,23.976024
714,721,23.976024
747,762,23.976024
768,775,23.976024
801,808,23.976024
824,827,23.976024
883,890,23.976024
926,945,23.976024
951,962,23.976024
1013,1028,23.976024
1114,1129,23.976024
1145,1156,23.976024
1182,1205,23.976024
1256,1271,23.976024
1292,1307,23.976024
1318,1321,23.976024
1362,1381,23.976024
1402,1409,23.976024
1455,1470,23.976024
1491,1494,23.976024
1520,1531,23.976024
1572,1579,23.976024
1605,1608,23.976024
1619,1654,23.976024
3110,3141,23.976024
3177,3208,23.976024
3214,3216,17.982018
3547,3554,23.976024
3560,3563,23.976024
3569,3571,17.982018
3627,3634,23.976024
3645,3656,23.976024
3662,3673,23.976024
3684,3763,23.976024
3804,3807,23.976024
3818,3825,23.976024
4231,4398,23.976024
4409,4436,23.976024
4502,4569,23.976024
4625,4860,23.976024
4931,5394,23.976024
5405,5532,23.976024
5573,5608,23.976024
5619,5670,23.976024
5746,5853,23.976024
5899,5958,23.976024
5974,6529,23.976024
6555,6658,23.976024
6669,6752,23.976024
6758,6809,23.976024
6860,6887,23.976024
6898,7257,23.976024
7313,7324,23.976024
7330,7801,23.976024
7817,7820,23.976024
7836,7839,23.976024
7855,7858,23.976024
7874,7897,23.976024
7993,8168,23.976024
8304,8911,23.976024
8917,9148,23.976024
9219,9254,23.976024
9275,9334,23.976024
9365,9412,23.976024
9418,9445,23.976024
9481,9552,23.976024
9558,9569,23.976024
9575,9602,23.976024
9608,9703,23.976024
9779,9822,23.976024
9843,10010,23.976024
10016,10027,23.976024
10073,10076,23.976024
10087,10090,23.976024
10101,10140,23.976024
10156,10159,23.976024
10175,10386,23.976024
10397,10420,23.976024
10431,10698,23.976024
10724,10843,23.976024
10864,11079,23.976024
11085,11260,23.976024
11281,11456,23.976024
11477,11916,23.976024
12012,12547,23.976024
12578,12581,23.976024
12592,12795,23.976024
12806,13177,23.976024
13193,13196,23.976024
13317,13432,23.976024
13463,13538,23.976024
13614,13685,23.976024
13751,13938,23.976024
13954,13957,23.976024
13973,13976,23.976024
13992,13995,23.976024
14011,14114,23.976024
14200,14387,23.976024
14803,14894,23.976024
14950,14953,23.976024
15074,15089,23.976024
15130,15145,23.976024
15196,15207,23.976024
15638,15681,23.976024
15782,15917,23.976024
16003,16010,23.976024
16016,16023,23.976024
16064,16223,23.976024
16439,16574,23.976024
16815,16818,23.976024
16824,17091,23.976024
17282,17385,23.976024
17461,17488,23.976024
17494,17545,23.976024
17746,17937,23.976024
17958,18193,23.976024
18264,18375,23.976024
18386,18401,23.976024
18412,18463,23.976024
18529,18572,23.976024
18633,18836,23.976024
18902,19049,23.976024
19055,19062,23.976024
19068,19115,23.976024
19156,19611,23.976024
19772,19779,23.976024
19790,19905,23.976024
19971,20070,23.976024
20076,20103,23.976024
20119,20134,23.976024
20140,20211,23.976024
20217,20408,23.976024
20424,21271,23.976024
21367,21478,23.976024
21534,21761,23.976024
21847,21870,23.976024
21876,22363,23.976024
22524,22675,23.976024
22686,22741,23.976024
22817,22832,23.976024
22883,22890,23.976024
22911,23334,23.976024
23500,23599,23.976024
23610,23949,23.976024
23955,24018,23.976024
24024,24131,23.976024
24192,24227,23.976024
24473,24616,23.976024
24767,25078,23.976024
25124,25403,23.976024
25409,25536,23.976024
25612,25899,23.976024
25945,25992,23.976024
26043,26166,23.976024
26547,26790,23.976024
27036,27099,23.976024
27105,27120,23.976024
27166,27169,23.976024
27175,27182,23.976024
27188,27191,23.976024
27202,27517,23.976024
27568,27779,23.976024
27795,27802,23.976024
27823,27838,23.976024
27844,27939,23.976024
28000,28579,23.976024
28685,28812,23.976024
28833,28836,23.976024
28867,28870,23.976024
28876,28963,23.976024
28969,28988,23.976024
28994,28997,23.976024
29008,29015,23.976024
29021,29036,23.976024
29047,29066,23.976024
29072,29219,23.976024
29265,29268,23.976024
29284,29287,23.976024
29298,29313,23.976024
29329,29496,23.976024
29502,29505,23.976024
29541,29544,23.976024
29555,29558,23.976024
29669,29672,23.976024
29683,29686,23.976024
29707,29710,23.976024
29721,29808,23.976024
29814,29845,23.976024
29901,29976,23.976024
30067,30074,23.976024
30085,30092,23.976024
30103,30222,23.976024
30318,30697,23.976024
30703,30742,23.976024
30748,30811,23.976024
30817,30872,23.976024
30928,31019,23.976024
31025,31200,23.976024
31206,31217,23.976024
31223,31330,23.976024
31331,31333,17.982018
31334,31353,23.976024
31359,31362,23.976024
31368,31679,23.976024
31720,31763,23.976024
31769,32036,23.976024
32077,32088,23.976024
32094,32149,23.976024
32155,32182,23.976024
32208,32215,23.976024
32221,32240,23.976024
32246,32277,23.976024
32283,32306,23.976024
32312,32843,23.976024
32869,32896,23.976024
32912,32927,23.976024
32938,32961,23.976024
33107,33122,23.976024
33148,33223,23.976024
33229,33236,23.976024
33257,33260,23.976024
33271,33274,23.976024
33285,33312,23.976024
33323,33330,23.976024
33336,33339,23.976024
33345,33352,23.976024
33383,33402,23.976024
33468,33471,23.976024
33557,33560,23.976024
33566,33585,23.976024
33651,33714,23.976024
33725,33740,23.976024
33751,33782,23.976024
33788,34047,23.976024
34053,36444,23.976024
# vfr stats: 71.60% film 28.40% video
# vfr stats: 30461 - film 12080 - video 42541 - total
# vfr stats: longest vid section - 1455 frames
# vfr stats: # of detected vid sections - 245

View file

@ -0,0 +1,3 @@
# timecode format v1
Assume 29.970030
-10,10,25

View file

@ -0,0 +1,4 @@
# timecode format v1
Assume 29.970030
50,100,25
0,10,25

View file

@ -0,0 +1,3 @@
# timecode format v1
Assume 29.970030
50,100,1000.1

View file

@ -0,0 +1,3 @@
# timecode format v1
Assume 29.970030
0,10,0

View file

@ -0,0 +1,4 @@
# timecode format v1
Assume 29.970030
0,1,25
1,2,25

View file

@ -0,0 +1,7 @@
# timecode format v1
Assume 29.970030
0,0,25
1,1,25
2,2,25
3,3,25
4,4,25

View file

@ -0,0 +1,3 @@
# timecode format v1
Assume 29.970030
0,10

View file

@ -0,0 +1,3 @@
# timecode format v1
Assume 29.970030
0,10,24,8

View file

@ -0,0 +1,3 @@
# timecode format v1
Assume 29.970030
0, 10 , 25

View file

@ -0,0 +1,101 @@
# timecode format v2
0.000000
33.000000
67.000000
100.000000
133.000000
167.000000
200.000000
233.000000
267.000000
300.000000
333.000000
367.000000
400.000000
433.000000
467.000000
500.000000
533.000000
567.000000
600.000000
633.000000
667.000000
700.000000
733.000000
767.000000
800.000000
833.000000
867.000000
900.000000
933.000000
967.000000
1000.000000
1033.000000
1067.000000
1100.000000
1133.000000
1167.000000
1200.000000
1233.000000
1267.000000
1300.000000
1333.000000
1367.000000
1400.000000
1433.000000
1467.000000
1500.000000
1533.000000
1567.000000
1600.000000
1633.000000
1667.000000
1700.000000
1733.000000
1767.000000
1800.000000
1833.000000
1867.000000
1900.000000
1933.000000
1967.000000
2000.000000
2033.000000
2067.000000
2100.000000
2133.000000
2167.000000
2200.000000
2233.000000
2267.000000
2300.000000
2333.000000
2367.000000
2400.000000
2433.000000
2467.000000
2500.000000
2533.000000
2567.000000
2600.000000
2633.000000
2667.000000
2700.000000
2733.000000
2767.000000
2800.000000
2833.000000
2867.000000
2900.000000
2933.000000
2967.000000
3000.000000
3033.000000
3067.000000
3100.000000
3133.000000
3167.000000
3200.000000
3233.000000
3267.000000
3300.000000

View file

@ -0,0 +1,101 @@
# timecode format v2
0
33
67
100
133
167
200
233
267
300
333
367
400
433
467
500
533
567
600
633
667
719
772
825
877
930
982
1035
1088
1140
1193
1246
1298
1351
1404
1456
1509
1561
1614
1667
1719
1772
1825
1877
1930
1982
2035
2088
2140
2193
2246
2298
2332
2365
2398
2432
2465
2498
2532
2565
2598
2632
2665
2698
2732
2765
2798
2832
2865
2898
2932
2965
2998
3032
3065
3098
3132
3165
3198
3232
3265
3298
3332
3365
3398
3432
3465
3498
3532
3565
3598
3632
3665
3698
3732
3765
3798
3832
3865
3898

View file

@ -0,0 +1,10 @@
# timecode format v2
0000.00000
1000.00000
2000.00000
3000.00000
4000.00000
5000.00000
6000.00000
7000.00000
8000.00000

View file

@ -0,0 +1,15 @@
# timecode format v2
0000.00000
# why does this format even have comments?
1000.00000
# I can see them being vaguely useful in v1 timecodes
2000.00000
# I guess we could stick the name of the writing program in a comment
3000.00000
# but really, what's the point?
4000.00000
#nospace
5000.00000
6000.00000
7000.00000
8000.00000

View file

@ -0,0 +1 @@
# timecode format v2

View file

@ -0,0 +1,11 @@
# timecode format v2
0.00000
100.00000
200.00000
201.00000
202.00000
203.00000
204.00000
205.00000
206.00000
207.00000

36446
aegisub/tests/vfr/v2_mode5.txt Normal file

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,4 @@
# timecode format v2
0.00000
100.00000
200.00000

View file

@ -0,0 +1,11 @@
# timecode format v2
0000.00000
#1000.00000
1000.00000
2000.00000
3000.00000
4000.00000
5000.00000
6000.00000
7000.00000
8000.00000

View file

@ -0,0 +1,4 @@
# timecode format v2
0000.00000
2000.00000
1000.00000

View file

@ -0,0 +1,5 @@
# timecode format v2
0
100
200
300

View file

@ -0,0 +1,5 @@
# timecode format v2
0
100
201
300

View file

@ -0,0 +1,6 @@
# timecode format v2
0
100
200
300
400

View file

@ -0,0 +1,4 @@
# timecode format v2
0
100
200