this bit of Sed magic relies on the fact that we happen to write
the almost correct class name of a test into the header comment.
HOWTO:
for F in $(find tests -type f \( -name '*.cpp' \) -exec egrep -q '§§TODO§§' {} \; -print);
do sed -r -i -e'
2 {h;x;s/\s+(.+)\(Test\).*$/\\ref \1_test/;x};
/§§TODO§§/ {s/§§TODO§§//;G;s/\n//}'
$F;
done
303 lines
11 KiB
C++
303 lines
11 KiB
C++
/*
|
|
TimeFormats(Test) - timecode handling and formatting
|
|
|
|
Copyright (C) Lumiera.org
|
|
2010, Hermann Vosseler <Ichthyostega@web.de>
|
|
|
|
This program is free software; you can redistribute it and/or
|
|
modify it under the terms of the GNU General Public License as
|
|
published by the Free Software Foundation; either version 2 of
|
|
the License, or (at your option) any later version.
|
|
|
|
This program is distributed in the hope that it will be useful,
|
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
GNU General Public License for more details.
|
|
|
|
You should have received a copy of the GNU General Public License
|
|
along with this program; if not, write to the Free Software
|
|
Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
|
|
|
|
* *****************************************************/
|
|
|
|
/** @file time-formats-test.cpp
|
|
** unit test \ref TimeFormats_test
|
|
*/
|
|
|
|
|
|
#include "lib/test/run.hpp"
|
|
//#include "lib/test/test-helper.hpp"
|
|
#include "proc/asset/meta/time-grid.hpp"
|
|
#include "lib/time/timequant.hpp"
|
|
#include "lib/time/timecode.hpp"
|
|
#include "lib/time/mutation.hpp"
|
|
#include "lib/format-cout.hpp"
|
|
#include "lib/util.hpp"
|
|
|
|
#include <boost/lexical_cast.hpp>
|
|
#include <string>
|
|
#include <cstdlib>
|
|
|
|
using boost::lexical_cast;
|
|
using util::isnil;
|
|
using std::rand;
|
|
using std::string;
|
|
|
|
|
|
namespace lib {
|
|
namespace time{
|
|
namespace test{
|
|
|
|
using proc::asset::meta::TimeGrid;
|
|
using format::Frames;
|
|
using format::Smpte;
|
|
|
|
namespace{
|
|
const FrameCnt MAX_FRAME = 265*24*60*60*25;
|
|
|
|
string
|
|
generateRandomFrameNr()
|
|
{
|
|
FrameCnt frameNr(0);
|
|
while (!frameNr)
|
|
frameNr = rand() % (2*MAX_FRAME) - MAX_FRAME;
|
|
|
|
return lexical_cast<string>(frameNr)+"#";
|
|
}
|
|
}
|
|
|
|
|
|
|
|
/****************************************************//**
|
|
* @test verify handling of grid aligned timecode values.
|
|
* - full cycle from parsing to formatting
|
|
* - mutating the components of timecode
|
|
* - some formatting corner cases
|
|
* - formatting in various formats
|
|
*/
|
|
class TimeFormats_test : public Test
|
|
{
|
|
virtual void
|
|
run (Arg)
|
|
{
|
|
TimeGrid::build("pal0", FrameRate::PAL);
|
|
|
|
checkTimecodeUsageCycle ();
|
|
// checkFrames ();
|
|
// checkSeconds ();
|
|
// checkHms ();
|
|
checkSmpte();
|
|
// checkDropFrame();
|
|
// checkCopyAssignments();
|
|
}
|
|
|
|
|
|
/** @test demonstrate a full usage cycle of timecode and time values.
|
|
* Starting with a textual representation according to a specific timecode format,
|
|
* and based on the knowledge of the implicit underlying time grid (coordinate system,
|
|
* here with origin=0 and framerate=25fps), this timecode string may be parsed.
|
|
* This brings us (back) to the very origin, which is a raw TimeValue (internal time).
|
|
* Now, this time value might be manipulated, compared to other values etc.
|
|
* Anyway, at some point these time values need to be related to some time scale again,
|
|
* leading to quantised time values, which — finally — can be cast into a timecode format
|
|
* for external representation again, thus closing the circle.
|
|
*/
|
|
void
|
|
checkTimecodeUsageCycle ()
|
|
{
|
|
string quellCode = generateRandomFrameNr();
|
|
PQuant refScale = Quantiser::retrieve("pal0");
|
|
|
|
// get internal (raw) time value
|
|
TimeValue t1 = format::Frames::parse(quellCode, *refScale);
|
|
ENSURE (0 != t1);
|
|
|
|
// manipulating
|
|
TimeVar v1(t1);
|
|
v1 += Time(FSecs(6,5));
|
|
CHECK (t1 < v1);
|
|
|
|
// quantising into an external grid
|
|
QuTime q1 (t1, "pal0");
|
|
CHECK (q1 == t1);
|
|
|
|
// further mutations (here nudge by +5 grid steps)
|
|
QuTime q2 = q1;
|
|
q2.accept (Mutation::nudge(+5));
|
|
CHECK (q1 < q2);
|
|
|
|
// converting (back) into a timecode format
|
|
FrameNr frames1(q1);
|
|
FrameNr frames2(q2);
|
|
CHECK (5 == frames2 - frames1);
|
|
|
|
q2.accept (Mutation::changeTime(v1));
|
|
CHECK (30 == q2.formatAs<Frames>() - frames1);
|
|
|
|
CHECK (quellCode == string(frames1));
|
|
CHECK (quellCode != string(frames2));
|
|
|
|
showTimeCode (frames1);
|
|
showTimeCode (frames2);
|
|
showTimeCode (q2.formatAs<Smpte>());
|
|
}
|
|
|
|
|
|
template<class TC>
|
|
void
|
|
showTimeCode (TC timecode)
|
|
{
|
|
cout << timecode.describe()<<"=\""<<timecode<<"\" time = "<< timecode.getTime() << endl;
|
|
}
|
|
|
|
void
|
|
checkFrames ()
|
|
{
|
|
UNIMPLEMENTED ("verify frame count time format");
|
|
}
|
|
|
|
|
|
void
|
|
checkSeconds ()
|
|
{
|
|
UNIMPLEMENTED ("verify fractional seconds as timecode format");
|
|
}
|
|
|
|
|
|
void
|
|
checkHms ()
|
|
{
|
|
UNIMPLEMENTED ("verify hour-minutes-seconds-millis timecode");
|
|
}
|
|
|
|
|
|
/** @test detailed coverage of SMPTE timecode representation.
|
|
* Using a scale grid with PAL framerate; this test doesn't
|
|
* cover the handling of drop-frame timecode.
|
|
* - creating a timecode representation causes frame quantisation
|
|
* - the components of SMPTE timecode can be accessed and manipulated
|
|
* - timecode can be incremented/decremented as a whole
|
|
* - we allow extension of the scale towards negative values
|
|
* - for these, the representation is flipped and the negative
|
|
* orientation only indicated through the sign field.
|
|
*/
|
|
void
|
|
checkSmpte ()
|
|
{
|
|
Time raw(555,23,42,5);
|
|
QuTime t1 (raw, "pal0");
|
|
SmpteTC smpte(t1);
|
|
|
|
cout << "----SMPTE-----" << endl;
|
|
showTimeCode(smpte);
|
|
CHECK (" 5:42:23:13" == string(smpte));
|
|
CHECK (raw - Time(35,0) == smpte.getTime()); // timecode value got quantised towards next lower frame
|
|
CHECK (13 == smpte.frames);
|
|
CHECK (23 == smpte.secs);
|
|
CHECK (42 == smpte.mins);
|
|
CHECK ( 5 == smpte.hours);
|
|
CHECK ( 1 == smpte.sgn);
|
|
CHECK ("SMPTE" == smpte.describe());
|
|
|
|
++smpte;
|
|
CHECK (" 5:42:23:14" == string(smpte));
|
|
smpte.frames += 12;
|
|
CHECK (" 5:42:24:01" == string(smpte));
|
|
smpte.secs = -120;
|
|
CHECK (" 5:40:00:01" == string(smpte));
|
|
CHECK (smpte.mins-- == 40);
|
|
CHECK (--smpte.mins == 38);
|
|
CHECK (" 5:38:00:01" == string(smpte));
|
|
|
|
TimeVar tx = smpte.getTime();
|
|
CHECK (tx == Time(0,0,38,5) + Time(FSecs(1,25)));
|
|
|
|
// Extended SMPTE: extension of the axis beyond origin towards negative values
|
|
smpte.hours -= 6;
|
|
CHECK ("- 0:21:59:24"== string(smpte)); // representation is symmetrical to origin
|
|
CHECK (tx - Time(0,0,0,6) == smpte.getTime()); // Continuous time axis
|
|
|
|
CHECK (-1 == smpte.sgn); // Note: for these negative (extended) SMPTE...
|
|
CHECK (smpte.mins > 0); // ...the representation is really flipped around zero
|
|
CHECK (smpte.secs > 0);
|
|
CHECK (smpte.frames > 0);
|
|
tx = smpte.getTime();
|
|
++smpte.frames; // now *increasing* the frame value
|
|
CHECK ("- 0:22:00:00"== string(smpte)); // means decreasing the resulting time
|
|
CHECK (smpte.getTime() == tx - Time(1000/25,0,0,0));
|
|
++smpte; // but the orientation of the increment on the *whole* TC values is unaltered
|
|
CHECK ("- 0:21:59:24"== string(smpte)); // so this actually *advanced* time by one frame
|
|
CHECK (tx == smpte.getTime());
|
|
CHECK (tx < TimeValue(0));
|
|
|
|
smpte.mins -= 2*60; // now lets flip the representation again...
|
|
CHECK (" 1:38:00:01"== string(smpte));
|
|
CHECK (+1 == smpte.sgn);
|
|
CHECK (smpte.getTime() > 0);
|
|
CHECK (tx + Time(0,0,0,2) == smpte.getTime());
|
|
smpte.secs -= 2*60*60; // and again...
|
|
CHECK (tx == smpte.getTime());
|
|
CHECK ("- 0:21:59:24"== string(smpte));
|
|
|
|
smpte.sgn += 123; // just flip the sign
|
|
CHECK (" 0:21:59:24"== string(smpte));
|
|
CHECK (tx == -smpte.getTime());
|
|
CHECK (+1 == smpte.sgn); // sign value is limited to +1 / -1
|
|
|
|
smpte.secs.setValueRaw(61); // set "wrong" value, bypassing normalisation
|
|
CHECK (smpte.secs == 61);
|
|
CHECK (smpte.getTime() == Time(1000*24/25, 01, 22));
|
|
CHECK (smpte.secs == 61); // calculated value is correct, but doesn't change state
|
|
CHECK (" 0:21:61:24"== string(smpte));
|
|
smpte.rebuild(); // but rebuilding the value includes normalisation
|
|
CHECK (smpte.secs == 1);
|
|
CHECK (smpte.mins == 22);
|
|
CHECK (" 0:22:01:24"== string(smpte));
|
|
|
|
smpte.frames.setValueRaw (25);
|
|
CHECK (" 0:22:01:25"== string(smpte));
|
|
smpte.hours = -1; // flipped representation handles denormalised values properly
|
|
CHECK ("- 0:37:58:00"== string(smpte));
|
|
|
|
smpte.mins.setValueRaw (59);
|
|
smpte.secs.setValueRaw (61);
|
|
smpte.frames.setValueRaw(-26); // provoke multiple over/underflows...
|
|
smpte.hours.setValueRaw (-2);
|
|
CHECK ("--2:59:61:-26"==string(smpte));
|
|
tx = smpte.getTime();
|
|
CHECK (tx == -1*(Time(0,61,59) - Time(0,0,0,2) - Time(FSecs(26,25))));
|
|
smpte.invertOrientation();
|
|
CHECK (" 1:00:00:01"== string(smpte));
|
|
CHECK (tx == smpte.getTime()); // applying invertOrientation() doesn't change the value
|
|
|
|
smpte.frames.setValueRaw(-1);
|
|
tx -= Time(FSecs(2,25));
|
|
CHECK (tx == smpte.getTime());
|
|
CHECK (" 1:00:00:-1"== string(smpte));
|
|
smpte.invertOrientation(); // invoking on positive should create double negated representation
|
|
CHECK ("--1:00:00:01"== string(smpte)); // and here especially also causes a series of overflows
|
|
CHECK (tx == smpte.getTime()); // but without affecting the overall value
|
|
}
|
|
|
|
|
|
void
|
|
checkDropFrame ()
|
|
{
|
|
UNIMPLEMENTED ("verify especially SMPTE-drop-frame timecode");
|
|
}
|
|
|
|
|
|
void
|
|
checkCopyAssignments ()
|
|
{
|
|
UNIMPLEMENTED ("verify Timecode values can be copied and assigned properly");
|
|
}
|
|
};
|
|
|
|
|
|
/** Register this test class... */
|
|
LAUNCHER (TimeFormats_test, "unit common");
|
|
|
|
|
|
|
|
}}} // namespace lib::time::test
|