| /////////////////////////////////////////////////////////////////////////////// |
| // |
| // 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 = std::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 << std::setfill('0') << right << std::setw(2) << _hours << ':' |
| << std::setw(2) << _minutes << ':' << std::setw(2) << _seconds; |
| |
| switch( _format ) { |
| case FRAME: |
| oss << ':' << std::setw(2) << std::setfill('0') << _subseconds; |
| break; |
| |
| case DECIMAL: |
| { |
| oss << '.' << std::setw(3) << std::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 |