/* TimeFormats(Test) - timecode handling and formatting Copyright (C) 2010, Hermann Vosseler   **Lumiera** 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. See the file COPYING for further details. * *****************************************************************/ /** @file time-formats-test.cpp ** unit test \ref TimeFormats_test */ #include "lib/test/run.hpp" //#include "lib/test/test-helper.hpp" #include "steam/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 using boost::lexical_cast; using util::isnil; using std::string; namespace lib { namespace time{ namespace test{ using steam::asset::meta::TimeGrid; using util::toString; using format::Frames; using format::Smpte; namespace{ const FrameCnt MAX_FRAME = 265*24*60*60*25; string generateRandomFrameNr() { FrameCnt frameNr(0); while (!frameNr) frameNr = rani(2*MAX_FRAME) - MAX_FRAME; return toString(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) { seedRand(); 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 srcCode = generateRandomFrameNr(); PQuant refScale = Quantiser::retrieve("pal0"); // get internal (raw) time value TimeValue t1 = format::Frames::parse (srcCode, *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() - frames1); // q2 == v1 == t1 + (6*5)/(5*5)sec CHECK (srcCode == string(frames1)); CHECK (srcCode != string(frames2)); showTimeCode (frames1); showTimeCode (frames2); showTimeCode (q2.formatAs()); } template void showTimeCode (TC timecode) { cout << timecode.describe()<<"=\""< 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