Rewrite vfr.cpp in libaegisub with tests. Not yet used by Aegisub itself.
Originally committed to SVN as r4661.
This commit is contained in:
parent
929fa83dd9
commit
9322f95071
44 changed files with 37900 additions and 6 deletions
|
@ -226,6 +226,10 @@
|
||||||
RelativePath="..\..\libaegisub\common\validator.cpp"
|
RelativePath="..\..\libaegisub\common\validator.cpp"
|
||||||
>
|
>
|
||||||
</File>
|
</File>
|
||||||
|
<File
|
||||||
|
RelativePath="..\..\libaegisub\common\vfr.cpp"
|
||||||
|
>
|
||||||
|
</File>
|
||||||
</Filter>
|
</Filter>
|
||||||
<Filter
|
<Filter
|
||||||
Name="Windows"
|
Name="Windows"
|
||||||
|
@ -344,6 +348,10 @@
|
||||||
RelativePath="..\..\libaegisub\include\libaegisub\io.h"
|
RelativePath="..\..\libaegisub\include\libaegisub\io.h"
|
||||||
>
|
>
|
||||||
</File>
|
</File>
|
||||||
|
<File
|
||||||
|
RelativePath="..\..\libaegisub\include\libaegisub\line_iterator.h"
|
||||||
|
>
|
||||||
|
</File>
|
||||||
<File
|
<File
|
||||||
RelativePath="..\..\libaegisub\include\libaegisub\log.h"
|
RelativePath="..\..\libaegisub\include\libaegisub\log.h"
|
||||||
>
|
>
|
||||||
|
@ -384,6 +392,10 @@
|
||||||
RelativePath="..\..\libaegisub\include\libaegisub\validator.h"
|
RelativePath="..\..\libaegisub\include\libaegisub\validator.h"
|
||||||
>
|
>
|
||||||
</File>
|
</File>
|
||||||
|
<File
|
||||||
|
RelativePath="..\..\libaegisub\include\libaegisub\vfr.h"
|
||||||
|
>
|
||||||
|
</File>
|
||||||
</Filter>
|
</Filter>
|
||||||
<File
|
<File
|
||||||
RelativePath="..\..\libaegisub\lagi_pre.h"
|
RelativePath="..\..\libaegisub\lagi_pre.h"
|
||||||
|
|
|
@ -86,6 +86,7 @@
|
||||||
/>
|
/>
|
||||||
<Tool
|
<Tool
|
||||||
Name="VCPostBuildEventTool"
|
Name="VCPostBuildEventTool"
|
||||||
|
CommandLine="cd "$(ExecutableOutDir)"
"$(ProjectDir)\..\..\tests\setup.bat" "$(ProjectDir)\..\..\tests"
"
|
||||||
/>
|
/>
|
||||||
</Configuration>
|
</Configuration>
|
||||||
<Configuration
|
<Configuration
|
||||||
|
@ -248,6 +249,10 @@
|
||||||
RelativePath="..\..\tests\libaegisub_util.cpp"
|
RelativePath="..\..\tests\libaegisub_util.cpp"
|
||||||
>
|
>
|
||||||
</File>
|
</File>
|
||||||
|
<File
|
||||||
|
RelativePath="..\..\tests\libaegisub_vfr.cpp"
|
||||||
|
>
|
||||||
|
</File>
|
||||||
</Files>
|
</Files>
|
||||||
<Globals>
|
<Globals>
|
||||||
</Globals>
|
</Globals>
|
||||||
|
|
|
@ -30,6 +30,7 @@ libaegisub_2_2_la_SOURCES = \
|
||||||
common/option_visit.cpp \
|
common/option_visit.cpp \
|
||||||
common/log.cpp \
|
common/log.cpp \
|
||||||
common/validator.cpp \
|
common/validator.cpp \
|
||||||
|
common/vfr.cpp \
|
||||||
unix/util.cpp \
|
unix/util.cpp \
|
||||||
unix/io.cpp \
|
unix/io.cpp \
|
||||||
unix/access.cpp \
|
unix/access.cpp \
|
||||||
|
|
307
aegisub/libaegisub/common/vfr.cpp
Normal file
307
aegisub/libaegisub/common/vfr.cpp
Normal 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];
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
|
@ -38,7 +38,7 @@ namespace agi {
|
||||||
|
|
||||||
/// @class line_iterator
|
/// @class line_iterator
|
||||||
/// @brief An iterator over lines in a stream
|
/// @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> {
|
class line_iterator : public std::iterator<std::input_iterator_tag, OutputType> {
|
||||||
std::istream *stream; ///< Stream to iterator over
|
std::istream *stream; ///< Stream to iterator over
|
||||||
bool valid; ///< Are there any more values to read?
|
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) {
|
void line_iterator<OutputType>::getline(std::string &str) {
|
||||||
union {
|
union {
|
||||||
int32_t chr;
|
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() {
|
void line_iterator<OutputType>::next() {
|
||||||
if (!valid) return;
|
if (!valid) return;
|
||||||
if (!stream->good()) {
|
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) {
|
inline bool line_iterator<OutputType>::convert(std::string &str) {
|
||||||
std::istringstream ss(str);
|
std::istringstream ss(str);
|
||||||
ss >> value;
|
ss >> value;
|
||||||
|
@ -210,7 +210,7 @@ inline bool line_iterator<std::string>::convert(std::string &str) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
template<typename T>
|
template<class T>
|
||||||
void swap(agi::line_iterator<T> &lft, agi::line_iterator<T> &rgt) {
|
void swap(agi::line_iterator<T> &lft, agi::line_iterator<T> &rgt) {
|
||||||
lft.swap(rgt);
|
lft.swap(rgt);
|
||||||
}
|
}
|
||||||
|
|
142
aegisub/libaegisub/include/libaegisub/vfr.h
Normal file
142
aegisub/libaegisub/include/libaegisub/vfr.h
Normal 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;
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
|
@ -27,6 +27,8 @@
|
||||||
#include <deque>
|
#include <deque>
|
||||||
#include <fstream>
|
#include <fstream>
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
|
#include <iterator>
|
||||||
|
#include <numeric>
|
||||||
#include <map>
|
#include <map>
|
||||||
#ifdef _WIN32
|
#ifdef _WIN32
|
||||||
#include <memory>
|
#include <memory>
|
||||||
|
|
|
@ -15,7 +15,8 @@ run_SOURCES = \
|
||||||
libaegisub_iconv.cpp \
|
libaegisub_iconv.cpp \
|
||||||
libaegisub_line_iterator.cpp \
|
libaegisub_line_iterator.cpp \
|
||||||
libaegisub_mru.cpp \
|
libaegisub_mru.cpp \
|
||||||
libaegisub_util.cpp
|
libaegisub_util.cpp \
|
||||||
|
libaegisub_vfr.cpp
|
||||||
|
|
||||||
run_SOURCES += \
|
run_SOURCES += \
|
||||||
*.h
|
*.h
|
||||||
|
|
378
aegisub/tests/libaegisub_vfr.cpp
Normal file
378
aegisub/tests/libaegisub_vfr.cpp
Normal 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"));
|
||||||
|
}
|
|
@ -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 {"Integer" : [{"int" : 1}, {"int" : 1}]} > data/option_array_integer
|
||||||
echo {"Double" : [{"double" : 2.1}, {"double" : 2.1}]} > data/option_array_double
|
echo {"Double" : [{"double" : 2.1}, {"double" : 2.1}]} > data/option_array_double
|
||||||
echo {"Bool" : [{"bool" : true}, {"bool" : true}]} > data/option_array_bool
|
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
|
||||||
|
|
|
@ -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 '{"Integer" : [{"int" : 1}, {"int" : 1}]}' > data/option_array_integer
|
||||||
echo '{"Double" : [{"double" : 2.1}, {"double" : 2.1}]}' > data/option_array_double
|
echo '{"Double" : [{"double" : 2.1}, {"double" : 2.1}]}' > data/option_array_double
|
||||||
echo '{"Bool" : [{"bool" : true}, {"bool" : true}]}' > data/option_array_bool
|
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/
|
||||||
|
|
0
aegisub/tests/vfr/empty.txt
Normal file
0
aegisub/tests/vfr/empty.txt
Normal file
2
aegisub/tests/vfr/v1_assume_30.txt
Normal file
2
aegisub/tests/vfr/v1_assume_30.txt
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
# timecode format v1
|
||||||
|
Assume 30
|
3
aegisub/tests/vfr/v1_assume_30_with_override.txt
Normal file
3
aegisub/tests/vfr/v1_assume_30_with_override.txt
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
# timecode format v1
|
||||||
|
Assume 30
|
||||||
|
20,50,19
|
3
aegisub/tests/vfr/v1_assume_int.txt
Normal file
3
aegisub/tests/vfr/v1_assume_int.txt
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
# timecode format v1
|
||||||
|
Assume 25
|
||||||
|
0,10,30
|
2
aegisub/tests/vfr/v1_assume_over_1000.txt
Normal file
2
aegisub/tests/vfr/v1_assume_over_1000.txt
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
# timecode format v1
|
||||||
|
Assume 1000.1
|
3
aegisub/tests/vfr/v1_bad_seperators.txt
Normal file
3
aegisub/tests/vfr/v1_bad_seperators.txt
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
# timecode format v1
|
||||||
|
Assume 29.970030
|
||||||
|
0;20;24.2
|
3
aegisub/tests/vfr/v1_end_less_than_start.txt
Normal file
3
aegisub/tests/vfr/v1_end_less_than_start.txt
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
# timecode format v1
|
||||||
|
Assume 29.970030
|
||||||
|
20,0,25
|
3
aegisub/tests/vfr/v1_float_frame_number.txt
Normal file
3
aegisub/tests/vfr/v1_float_frame_number.txt
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
# timecode format v1
|
||||||
|
Assume 29.970030
|
||||||
|
0.0,10.0,25
|
4
aegisub/tests/vfr/v1_fully_contained.txt
Normal file
4
aegisub/tests/vfr/v1_fully_contained.txt
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
# timecode format v1
|
||||||
|
Assume 29.970030
|
||||||
|
0,50,10
|
||||||
|
10,40,10
|
255
aegisub/tests/vfr/v1_mode5.txt
Normal file
255
aegisub/tests/vfr/v1_mode5.txt
Normal 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
|
3
aegisub/tests/vfr/v1_negative_start_of_range.txt
Normal file
3
aegisub/tests/vfr/v1_negative_start_of_range.txt
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
# timecode format v1
|
||||||
|
Assume 29.970030
|
||||||
|
-10,10,25
|
4
aegisub/tests/vfr/v1_out_of_order.txt
Normal file
4
aegisub/tests/vfr/v1_out_of_order.txt
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
# timecode format v1
|
||||||
|
Assume 29.970030
|
||||||
|
50,100,25
|
||||||
|
0,10,25
|
3
aegisub/tests/vfr/v1_override_over_1000.txt
Normal file
3
aegisub/tests/vfr/v1_override_over_1000.txt
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
# timecode format v1
|
||||||
|
Assume 29.970030
|
||||||
|
50,100,1000.1
|
3
aegisub/tests/vfr/v1_override_zero.txt
Normal file
3
aegisub/tests/vfr/v1_override_zero.txt
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
# timecode format v1
|
||||||
|
Assume 29.970030
|
||||||
|
0,10,0
|
4
aegisub/tests/vfr/v1_start_end_overlap.txt
Normal file
4
aegisub/tests/vfr/v1_start_end_overlap.txt
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
# timecode format v1
|
||||||
|
Assume 29.970030
|
||||||
|
0,1,25
|
||||||
|
1,2,25
|
7
aegisub/tests/vfr/v1_start_equals_end.txt
Normal file
7
aegisub/tests/vfr/v1_start_equals_end.txt
Normal 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
|
3
aegisub/tests/vfr/v1_too_few_parts.txt
Normal file
3
aegisub/tests/vfr/v1_too_few_parts.txt
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
# timecode format v1
|
||||||
|
Assume 29.970030
|
||||||
|
0,10
|
3
aegisub/tests/vfr/v1_too_many_parts.txt
Normal file
3
aegisub/tests/vfr/v1_too_many_parts.txt
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
# timecode format v1
|
||||||
|
Assume 29.970030
|
||||||
|
0,10,24,8
|
3
aegisub/tests/vfr/v1_whitespace.txt
Normal file
3
aegisub/tests/vfr/v1_whitespace.txt
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
# timecode format v1
|
||||||
|
Assume 29.970030
|
||||||
|
0, 10 , 25
|
101
aegisub/tests/vfr/v2_100_frames_30_fps.txt
Normal file
101
aegisub/tests/vfr/v2_100_frames_30_fps.txt
Normal 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
|
101
aegisub/tests/vfr/v2_100_frames_30_with_override.txt
Normal file
101
aegisub/tests/vfr/v2_100_frames_30_with_override.txt
Normal 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
|
10
aegisub/tests/vfr/v2_1fps.txt
Normal file
10
aegisub/tests/vfr/v2_1fps.txt
Normal 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
|
15
aegisub/tests/vfr/v2_comments.txt
Normal file
15
aegisub/tests/vfr/v2_comments.txt
Normal 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
|
1
aegisub/tests/vfr/v2_empty.txt
Normal file
1
aegisub/tests/vfr/v2_empty.txt
Normal file
|
@ -0,0 +1 @@
|
||||||
|
# timecode format v2
|
11
aegisub/tests/vfr/v2_len_3_10.txt
Normal file
11
aegisub/tests/vfr/v2_len_3_10.txt
Normal 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
36446
aegisub/tests/vfr/v2_mode5.txt
Normal file
File diff suppressed because it is too large
Load diff
4
aegisub/tests/vfr/v2_nolen.txt
Normal file
4
aegisub/tests/vfr/v2_nolen.txt
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
# timecode format v2
|
||||||
|
0.00000
|
||||||
|
100.00000
|
||||||
|
200.00000
|
11
aegisub/tests/vfr/v2_number_in_comment.txt
Normal file
11
aegisub/tests/vfr/v2_number_in_comment.txt
Normal 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
|
4
aegisub/tests/vfr/v2_out_of_order.txt
Normal file
4
aegisub/tests/vfr/v2_out_of_order.txt
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
# timecode format v2
|
||||||
|
0000.00000
|
||||||
|
2000.00000
|
||||||
|
1000.00000
|
5
aegisub/tests/vfr/validate_base.txt
Normal file
5
aegisub/tests/vfr/validate_base.txt
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
# timecode format v2
|
||||||
|
0
|
||||||
|
100
|
||||||
|
200
|
||||||
|
300
|
5
aegisub/tests/vfr/validate_different.txt
Normal file
5
aegisub/tests/vfr/validate_different.txt
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
# timecode format v2
|
||||||
|
0
|
||||||
|
100
|
||||||
|
201
|
||||||
|
300
|
6
aegisub/tests/vfr/validate_longer.txt
Normal file
6
aegisub/tests/vfr/validate_longer.txt
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
# timecode format v2
|
||||||
|
0
|
||||||
|
100
|
||||||
|
200
|
||||||
|
300
|
||||||
|
400
|
4
aegisub/tests/vfr/validate_shorter.txt
Normal file
4
aegisub/tests/vfr/validate_shorter.txt
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
# timecode format v2
|
||||||
|
0
|
||||||
|
100
|
||||||
|
200
|
Loading…
Reference in a new issue