/////////////////////////////////////////////////////////////////////////////// // // The contents of this file are subject to the Mozilla Public License // Version 1.1 (the "License"); you may not use this file except in // compliance with the License. You may obtain a copy of the License at // http://www.mozilla.org/MPL/ // // Software distributed under the License is distributed on an "AS IS" // basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the // License for the specific language governing rights and limitations // under the License. // // The Original Code is MP4v2. // // The Initial Developer of the Original Code is Kona Blend. // Portions created by Kona Blend are Copyright (C) 2008. // All Rights Reserved. // // Contributors: // Kona Blend, kona8lend@@gmail.com // /////////////////////////////////////////////////////////////////////////////// #include "libutil/impl.h" namespace mp4v2 { namespace util { /////////////////////////////////////////////////////////////////////////////// Timecode::Timecode( const Timecode& obj ) : _scale ( 1.0 ) , _duration ( 0 ) , _format ( FRAME ) , _svalue ( "" ) , _hours ( 0 ) , _minutes ( 0 ) , _seconds ( 0 ) , _subseconds ( 0 ) , scale ( _scale ) , duration ( _duration ) , format ( _format ) , svalue ( _svalue ) , hours ( _hours ) , minutes ( _minutes ) , seconds ( _seconds ) , subseconds ( _subseconds ) { operator=( obj ); } /////////////////////////////////////////////////////////////////////////////// Timecode::Timecode( const string& time_, double scale_ ) : _scale ( scale_ < 1.0 ? 1.0 : scale_ ) , _duration ( 0 ) , _format ( FRAME ) , _svalue ( "" ) , _hours ( 0 ) , _minutes ( 0 ) , _seconds ( 0 ) , _subseconds ( 0 ) , scale ( _scale ) , duration ( _duration ) , format ( _format ) , svalue ( _svalue ) , hours ( _hours ) , minutes ( _minutes ) , seconds ( _seconds ) , subseconds ( _subseconds ) { parse( time_ ); } /////////////////////////////////////////////////////////////////////////////// Timecode::Timecode( uint64_t duration_, double scale_ ) : _scale ( scale_ < 1.0 ? 1.0 : scale_ ) , _duration ( 0 ) , _format ( FRAME ) , _svalue ( "" ) , _hours ( 0 ) , _minutes ( 0 ) , _seconds ( 0 ) , _subseconds ( 0 ) , scale ( _scale ) , duration ( _duration ) , format ( _format ) , svalue ( _svalue ) , hours ( _hours ) , minutes ( _minutes ) , seconds ( _seconds ) , subseconds ( _subseconds ) { setDuration( duration_ ); } /////////////////////////////////////////////////////////////////////////////// uint64_t Timecode::convertDuration( const Timecode& obj ) const { if( _scale == obj._scale ) return obj._duration; return static_cast( ( _scale / obj._scale ) * obj._duration ); } /////////////////////////////////////////////////////////////////////////////// Timecode& Timecode::operator=( const Timecode& rhs ) { _scale = rhs._scale; _duration = rhs._duration; _format = FRAME; _svalue = rhs._svalue; _hours = rhs._hours; _minutes = rhs._minutes; _seconds = rhs._seconds; _subseconds = rhs._subseconds; return *this; } /////////////////////////////////////////////////////////////////////////////// Timecode& Timecode::operator+=( const Timecode& rhs ) { uint64_t dur = _duration + convertDuration( rhs ); // overflow check if( dur < _duration ) dur = numeric_limits::max(); setDuration( dur ); return *this; } /////////////////////////////////////////////////////////////////////////////// Timecode& Timecode::operator-=( const Timecode& rhs ) { uint64_t dur = _duration - convertDuration( rhs ); // underflow check if( dur > _duration ) dur = 0; setDuration( dur ); return *this; } /////////////////////////////////////////////////////////////////////////////// bool Timecode::operator<( const Timecode& obj ) const { return _duration < convertDuration( obj ); } /////////////////////////////////////////////////////////////////////////////// bool Timecode::operator<=( const Timecode& obj ) const { return _duration <= convertDuration( obj ); } /////////////////////////////////////////////////////////////////////////////// bool Timecode::operator>( const Timecode& obj ) const { return _duration < convertDuration( obj ); } /////////////////////////////////////////////////////////////////////////////// bool Timecode::operator>=( const Timecode& obj ) const { return _duration < convertDuration( obj ); } /////////////////////////////////////////////////////////////////////////////// bool Timecode::operator!=( const Timecode& obj ) const { return _duration != convertDuration( obj ); } /////////////////////////////////////////////////////////////////////////////// bool Timecode::operator==( const Timecode& obj ) const { return _duration == convertDuration( obj ); } /////////////////////////////////////////////////////////////////////////////// Timecode Timecode::operator+( const Timecode& obj ) const { Timecode t( *this ); t += obj; return t; } /////////////////////////////////////////////////////////////////////////////// Timecode Timecode::operator-( const Timecode& obj ) const { Timecode t( *this ); t -= obj; return t; } /////////////////////////////////////////////////////////////////////////////// bool Timecode::parse( const string& time, string* outError ) { string outErrorPlacebo; string& error = outError ? *outError : outErrorPlacebo; error.clear(); _format = FRAME; _hours = 0; _minutes = 0; _seconds = 0; _subseconds = 0; // bail if empty if( time.empty() ) { recompute(); return false; } // count number of ':' int nsect = 0; int nsemi = 0; int ndot = 0; const string::size_type max = time.length(); for( string::size_type i = 0; i < max; i++ ) { switch( time[i] ) { case ':': nsect++; break; case ';': if( nsemi++ ) { error = "too many semicolons"; return true; } nsect++; break; case '.': if( ndot++ ) { error = "too many periods"; return true; } nsect++; break; default: break; } } // bail if impossible number of sections if( nsect > 3 ) { recompute(); error = "too many sections"; return true; } enum Target { HOURS, MINUTES, SECONDS, SUBSECONDS, }; // setup target before parsing Target target; uint64_t* tvalue; switch( nsect ) { default: case 0: target = SUBSECONDS; tvalue = &_subseconds; break; case 1: target = SECONDS; tvalue = &_seconds; break; case 2: target = MINUTES; tvalue = &_minutes; break; case 3: target = HOURS; tvalue = &_hours; break; } istringstream convert; string tbuffer; for( string::size_type i = 0; i < max; i++ ) { const char c = time[i]; switch( c ) { case ':': switch( target ) { case HOURS: convert.clear(); convert.str( tbuffer ); if( !tbuffer.empty() && !(convert >> *tvalue) ) { error = "failed to convert integer"; return true; } tbuffer.clear(); target = MINUTES; tvalue = &_minutes; break; case MINUTES: convert.clear(); convert.str( tbuffer ); if( !tbuffer.empty() && !(convert >> *tvalue) ) { error = "failed to convert integer"; return true; } tbuffer.clear(); target = SECONDS; tvalue = &_seconds; break; case SECONDS: convert.clear(); convert.str( tbuffer ); if( !tbuffer.empty() && !(convert >> *tvalue) ) { error = "failed to convert integer"; return true; } tbuffer.clear(); target = SUBSECONDS; tvalue = &_subseconds; break; default: case SUBSECONDS: error = "unexpected char ':'"; return true; } break; case '.': { if( target != SECONDS ) { error = "unexpected char '.'"; return true; } _format = DECIMAL; convert.clear(); convert.str( tbuffer ); if( !tbuffer.empty() && !(convert >> *tvalue) ) { error = "failed to convert integer"; return true; } tbuffer.clear(); target = SUBSECONDS; tvalue = &_subseconds; break; } case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': tbuffer += c; if( tbuffer.length() > 16 ) { error = "overflow"; return true; } break; default: error = "unexpected char '"; error += c; error += "'"; return true; } } // apply final section if( !tbuffer.empty() ) { convert.clear(); convert.str( tbuffer ); if( !tbuffer.empty() && !(convert >> *tvalue) ) { error = "failed to convert integer"; return true; } } // special post processing switch( _format ) { case FRAME: default: break; case DECIMAL: { double div = std::pow( 10.0, static_cast(tbuffer.length()) ); if( div < 1.0 ) div = 1.0; *tvalue = static_cast( static_cast(*tvalue) / div * std::ceil( _scale )); break; } } recompute(); return false; } /////////////////////////////////////////////////////////////////////////////// void Timecode::recompute() { // case: 29.97 becomes 30.0 // case: 30.0 becomes 30.0 const uint64_t iscale = uint64_t( std::ceil( _scale )); if( _subseconds > iscale - 1 ) { uint64_t n = _subseconds / iscale; _seconds += n; _subseconds -= n * iscale; } if( _seconds > 59 ) { uint64_t n = _seconds / 60; _minutes += n; _seconds -= n * 60; } if( _minutes > 59 ) { uint64_t n = _minutes / 60; _hours += n; _minutes -= n * 60; } _duration = _subseconds + (iscale * _seconds) + (iscale * _minutes * 60) + (iscale * _hours * 3600); ostringstream oss; oss << setfill('0') << right << setw(2) << _hours << ':' << setw(2) << _minutes << ':' << setw(2) << _seconds; switch( _format ) { case FRAME: oss << ':' << setw(2) << setfill( '0' ) << _subseconds; break; case DECIMAL: { oss << '.' << setw(3) << setfill( '0' ) << static_cast(_subseconds / _scale * 1000.0 + 0.5); break; } } _svalue = oss.str(); } /////////////////////////////////////////////////////////////////////////////// void Timecode::reset() { setDuration( 0 ); } /////////////////////////////////////////////////////////////////////////////// void Timecode::setDuration( uint64_t duration_, double scale_ ) { if( scale_ != 0.0 ) { _scale = scale_; if( _scale < 1.0 ) _scale = 1.0; } _duration = duration_; const uint64_t iscale = uint64_t( std::ceil( _scale )); uint64_t i = _duration; _hours = i / (iscale * 3600); i -= (iscale * 3600 * _hours); _minutes = i / (iscale * 60); i -= (iscale * 60 * _minutes); _seconds = i / iscale; i -= (iscale * _seconds); _subseconds = i; recompute(); } /////////////////////////////////////////////////////////////////////////////// void Timecode::setFormat( Format format_ ) { _format = format_; recompute(); } /////////////////////////////////////////////////////////////////////////////// void Timecode::setHours( uint64_t hours_ ) { _hours = hours_; recompute(); } /////////////////////////////////////////////////////////////////////////////// void Timecode::setMinutes( uint64_t minutes_ ) { _minutes = minutes_; recompute(); } /////////////////////////////////////////////////////////////////////////////// void Timecode::setScale( double scale_ ) { const double oldscale = _scale; _scale = scale_; if( _scale < 1.0 ) _scale = 1.0; _subseconds = static_cast( (_scale / oldscale) * _subseconds ); recompute(); } /////////////////////////////////////////////////////////////////////////////// void Timecode::setSeconds( uint64_t seconds_ ) { _seconds = seconds_; recompute(); } /////////////////////////////////////////////////////////////////////////////// void Timecode::setSubseconds( uint64_t subseconds_ ) { _subseconds = subseconds_; recompute(); } /////////////////////////////////////////////////////////////////////////////// }} // namespace mp4v2::util