| /////////////////////////////////////////////////////////////////////////////// | |
| // | |
| // 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<uint64_t>( ( _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<long long>::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<double>(tbuffer.length()) ); | |
| if( div < 1.0 ) | |
| div = 1.0; | |
| *tvalue = static_cast<uint64_t>( static_cast<double>(*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<uint64_t>(_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<uint64_t>( (_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 |