You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

592 lines
15 KiB

///////////////////////////////////////////////////////////////////////////////
//
// 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