TuttleOFX  1
Sequence.cpp
Go to the documentation of this file.
00001 #include "Sequence.hpp"
00002 #include "Folder.hpp"
00003 #include "File.hpp"
00004 
00005 #include "detail/FileNumbers.hpp"
00006 
00007 #include <boost/regex.hpp>
00008 #include <boost/unordered_map.hpp>
00009 #include <boost/lambda/lambda.hpp>
00010 #include <boost/foreach.hpp>
00011 #include <set>
00012 
00013 #include <ostream>
00014 
00015 namespace sequenceParser {
00016 
00017 namespace bfs = boost::filesystem;
00018 
00019 /// All regex to recognize a pattern
00020 // common used pattern with # or @
00021 static const boost::regex regexPatternStandard( "(.*?)" // anything but without priority
00022                                                                                                 "\\[?" // if pattern is myimage[####].jpg, don't capture []
00023                                                                                                 "(#+|@+)" // we capture all # or @
00024                                                                                                 "\\]?" // possible end of []
00025                                                                                                 "(.*?)" // anything
00026                                                                                                 );
00027 // C style pattern
00028 static const boost::regex regexPatternCStyle( "(.*?)" // anything but without priority
00029                                                                                           "\\[?" // if pattern is myimage[%04d].jpg, don't capture []
00030                                                                                           "%([0-9]*)d" // we capture the padding value (eg. myimage%04d.jpg)
00031                                                                                           "\\]?" // possible end of []
00032                                                                                           "(.*?)" // anything
00033                                                                                           );
00034 // image name
00035 static const boost::regex regexPatternFrame( "(.*?" // anything but without priority
00036                                                                                          "[_\\.]?)" // if multiple numbers, the number surround with . _ get priority (eg. seq1shot04myimage.0123.jpg -> 0123)
00037                                                                                          "\\[?" // if pattern is myimage[0001].jpg, don't capture []
00038                                                                                          "([0-9]+)" // one frame number, can only be positive ( 0012 )
00039                                                                                          "\\]?" // possible end of []
00040                                                                                          "([_\\.]?" // if multiple numbers, the number surround with . _ get priority (eg. seq1shot04myimage.0123.jpg -> 0123)
00041                                                                                          ".*\\.?" //
00042                                                                                          ".*?)" // anything
00043                                                                                          );
00044 
00045 // image name with negative indexes
00046 static const boost::regex regexPatternFrameNeg( "(.*?" // anything but without priority
00047                                                                                                 "[_\\.]?)" // if multiple numbers, the number surround with . _ get priority (eg. seq1shot04myimage.0123.jpg -> 0123)
00048                                                                                                 "\\[?" // if pattern is myimage[0001].jpg, don't capture []
00049                                                                                                 "([\\-\\+]?[0-9]+)" // one frame number, can be positive or negative values ( -0012 or +0012 or 0012)
00050                                                                                                 "\\]?" // possible end of []
00051                                                                                                 "([_\\.]?" // if multiple numbers, the number surround with . _ get priority (eg. seq1shot04myimage.0123.jpg -> 0123)
00052                                                                                                 ".*\\.?" //
00053                                                                                                 ".*?)" // anything
00054                                                                                                 );
00055 
00056 Sequence::~Sequence()
00057 {
00058 }
00059 
00060 Sequence::Sequence( const boost::filesystem::path& directory, const EMaskOptions options, const EPattern accept ) :
00061 FileObject( directory, eMaskTypeSequence, options )
00062 {
00063         clear();
00064         initFromDetection( accept );
00065 }
00066 
00067 bool Sequence::isIn( const std::string& filename, Time& time, std::string& timeStr )
00068 {
00069         std::size_t min = _prefix.size() + _suffix.size();
00070 
00071         if( filename.size() <= min )
00072                 return false;
00073 
00074         if( filename.substr( 0, _prefix.size() ) != _prefix || filename.substr( filename.size() - _suffix.size(), _suffix.size() ) != _suffix )
00075                 return false;
00076 
00077         try
00078         {
00079                 timeStr = filename.substr( _prefix.size(), filename.size() - _suffix.size() - _prefix.size() );
00080                 time = boost::lexical_cast<Time > ( timeStr );
00081         }
00082         catch( ... )
00083         {
00084                 return false;
00085         }
00086         return true;
00087 }
00088 
00089 Sequence::EPattern Sequence::checkPattern( const std::string& pattern )
00090 {
00091         if( regex_match( pattern.c_str(), regexPatternStandard ) )
00092         {
00093                 return ePatternStandard;
00094         }
00095         else if( regex_match( pattern.c_str(), regexPatternCStyle ) )
00096         {
00097                 return ePatternCStyle;
00098         }
00099         else if( ( _options & eMaskOptionsNegativeIndexes ) && regex_match( pattern.c_str(), regexPatternFrameNeg ) )
00100         {
00101                 return ePatternFrameNeg;
00102         }
00103         else if( regex_match( pattern.c_str(), regexPatternFrame ) )
00104         {
00105                 return ePatternFrame;
00106         }
00107         return ePatternNone;
00108 }
00109 
00110 /**
00111  * @brief This function creates a regex from the pattern,
00112  *        and init internal values.
00113  * @param[in] pattern
00114  * @param[in] accept
00115  * @param[out] prefix
00116  * @param[out] suffix
00117  * @param[out] padding
00118  * @param[out] strictPadding
00119  */
00120 bool Sequence::retrieveInfosFromPattern( const std::string& filePattern, const EPattern& accept, std::string& prefix, std::string& suffix, std::size_t& padding, bool& strictPadding ) const
00121 {
00122         boost::cmatch matches;
00123         //std::cout << filePattern << " / " << prefix << " + " << padding << " + " << suffix << std::endl;
00124         if( ( accept & ePatternStandard ) && regex_match( filePattern.c_str(), matches, regexPatternStandard ) )
00125         {
00126                 std::string paddingStr( matches[2].first, matches[2].second );
00127                 padding = paddingStr.size();
00128                 strictPadding = ( paddingStr[0] == '#' );
00129         }
00130         else if( ( accept & ePatternCStyle ) && regex_match( filePattern.c_str(), matches, regexPatternCStyle ) )
00131         {
00132                 std::string paddingStr( matches[2].first, matches[2].second );
00133                 padding = paddingStr.size() == 0 ? 0 : boost::lexical_cast<std::size_t > ( paddingStr ); // if no padding value: %d -> padding = 0
00134                 strictPadding = false;
00135         }
00136         else if( ( accept & ePatternFrame ) && regex_match( filePattern.c_str(), matches, regexPatternFrame ) )
00137         {
00138                 std::string frame( matches[2].first, matches[2].second );
00139                 // Time t = boost::lexical_cast<Time>( frame );
00140                 padding = frame.size();
00141                 strictPadding = false;
00142         }
00143         else if( ( accept & ePatternFrameNeg ) && regex_match( filePattern.c_str(), matches, regexPatternFrameNeg ) )
00144         {
00145                 std::string frame( matches[2].first, matches[2].second );
00146                 // Time t = boost::lexical_cast<Time>( frame );
00147                 padding = frame.size();
00148                 strictPadding = false;
00149         }
00150         else
00151         {
00152                 // this is a file, not a sequence
00153                 return false;
00154         }
00155         prefix = std::string( matches[1].first, matches[1].second );
00156         suffix = std::string( matches[3].first, matches[3].second );
00157         return true;
00158 }
00159 
00160 void Sequence::init( const std::string& prefix, const std::size_t padding, const std::string& suffix, const Time firstTime, const Time lastTime, const Time step, const bool strictPadding )
00161 {
00162         _prefix = prefix;
00163         _padding = padding;
00164         _suffix = suffix;
00165         _firstTime = firstTime;
00166         _lastTime = lastTime;
00167         _step = step;
00168         _strictPadding = strictPadding;
00169         _nbFiles = 0;
00170 }
00171 
00172 bool Sequence::init( const std::string& pattern, const Time firstTime, const Time lastTime, const Time step, const EPattern accept )
00173 {
00174         if( !retrieveInfosFromPattern( pattern, accept, _prefix, _suffix, _padding, _strictPadding ) )
00175                 return false; // not regognize as a pattern, maybe a still file
00176         _firstTime = firstTime;
00177         _lastTime = lastTime;
00178         _step = step;
00179         _nbFiles = 0;
00180         //std::cout << "init => " <<  _firstTime << " > " << _lastTime << " : " << _nbFiles << std::endl;
00181         return true;
00182 }
00183 
00184 bool Sequence::initFromDetection( const std::string& pattern, const EPattern accept )
00185 {
00186         clear();
00187         setDirectoryFromPath( pattern );
00188 
00189         if( !retrieveInfosFromPattern( boost::filesystem::path( pattern ).filename().string(), accept, _prefix, _suffix, _padding, _strictPadding ) )
00190                 return false; // not recognized as a pattern, maybe a still file
00191         if( !boost::filesystem::exists( _directory ) )
00192                 return true; // an empty sequence
00193 
00194         std::vector<std::string> allTimesStr;
00195         std::vector<Time> allTimes;
00196         bfs::directory_iterator itEnd;
00197 
00198         for( bfs::directory_iterator iter( _directory ); iter != itEnd; ++iter )
00199         {
00200                 // we don't make this check, which can take long time on big sequences (>1000 files)
00201                 // depending on your filesystem, we may need to do a stat() for each file
00202                 //      if( bfs::is_directory( iter->status() ) )
00203                 //          continue; // skip directories
00204                 Time time;
00205                 std::string timeStr;
00206 
00207                 // if the file is inside the sequence
00208                 if( isIn( iter->path().filename().string(), time, timeStr ) )
00209                 {
00210                         // create a big vector of all times in our sequence
00211                         allTimesStr.push_back( timeStr );
00212                         allTimes.push_back( time );
00213                 }
00214         }
00215         if( allTimes.size() < 2 )
00216         {
00217                 if( allTimes.size() == 1 )
00218                 {
00219                         _firstTime = _lastTime = allTimes.front();
00220                 }
00221                 //std::cout << "empty => " <<  _firstTime << " > " << _lastTime << " : " << _nbFiles << std::endl;
00222                 return true; // an empty sequence
00223         }
00224         std::sort( allTimes.begin(), allTimes.end() );
00225         extractStep( allTimes );
00226         extractPadding( allTimesStr );
00227         extractIsStrictPadding( allTimesStr, _padding );
00228         _firstTime = allTimes.front();
00229         _lastTime = allTimes.back();
00230         _nbFiles = allTimes.size();
00231         //std::cout << _firstTime << " > " << _lastTime << " : " << _nbFiles << std::endl;
00232         return true; // a real file sequence
00233 }
00234 
00235 /**
00236  * @brief Find the biggest common step from a set of all steps.
00237  */
00238 void Sequence::extractStep( const std::set<std::size_t>& steps )
00239 {
00240         if( steps.size() == 1 )
00241         {
00242                 _step = *steps.begin();
00243                 return;
00244         }
00245         std::set<std::size_t> allSteps;
00246         for( std::set<std::size_t>::const_iterator itA = steps.begin(), itB = ++steps.begin(), itEnd = steps.end(); itB != itEnd; ++itA, ++itB )
00247         {
00248                 allSteps.insert( greatestCommonDivisor( *itB, *itA ) );
00249         }
00250         extractStep( allSteps );
00251 }
00252 
00253 /**
00254  * @brief Extract step from a sorted vector of time values.
00255  */
00256 void Sequence::extractStep( const std::vector<Time>& times )
00257 {
00258         if( times.size() <= 1 )
00259         {
00260                 _step = 1;
00261                 return;
00262         }
00263         std::set<std::size_t> allSteps;
00264         for( std::vector<Time>::const_iterator itA = times.begin(), itB = ++times.begin(), itEnd = times.end(); itB != itEnd; ++itA, ++itB )
00265         {
00266                 allSteps.insert( *itB - *itA );
00267         }
00268         extractStep( allSteps );
00269 }
00270 
00271 /**
00272  * @brief Extract step from a sorted vector of time values.
00273  */
00274 void Sequence::extractStep( const std::vector<detail::FileNumbers>::const_iterator& timesBegin, const std::vector<detail::FileNumbers>::const_iterator& timesEnd, const std::size_t i )
00275 {
00276         if( std::distance( timesBegin, timesEnd ) <= 1 )
00277         {
00278                 _step = 1;
00279                 return;
00280         }
00281         std::set<std::size_t> allSteps;
00282         for( std::vector<detail::FileNumbers>::const_iterator itA = timesBegin, itB = boost::next(timesBegin), itEnd = timesEnd; itB != itEnd; ++itA, ++itB )
00283         {
00284                 allSteps.insert( itB->getTime( i ) - itA->getTime( i ) );
00285         }
00286         extractStep( allSteps );
00287 }
00288 
00289 std::size_t Sequence::getPaddingFromStringNumber( const std::string& timeStr )
00290 {
00291         if( timeStr.size() > 1 )
00292         {
00293                 // if the number is signed, this charater does not count as padding.
00294                 if( timeStr[0] == '-' || timeStr[0] == '+' )
00295                 {
00296                         return timeStr.size() - 1;
00297                 }
00298         }
00299         return timeStr.size();
00300 }
00301 
00302 /**
00303  * @brief extract the padding from a vector of frame numbers
00304  * @param[in] timesStr vector of frame numbers in string format
00305  */
00306 void Sequence::extractPadding( const std::vector<std::string>& timesStr )
00307 {
00308         BOOST_ASSERT( timesStr.size() > 0 );
00309         const std::size_t padding = getPaddingFromStringNumber( timesStr.front() );
00310 
00311         BOOST_FOREACH( const std::string& s, timesStr )
00312         {
00313                 if( padding != getPaddingFromStringNumber( s ) )
00314                 {
00315                         _padding = 0;
00316                         return;
00317                 }
00318         }
00319         _padding = padding;
00320 }
00321 
00322 void Sequence::extractPadding( const std::vector<detail::FileNumbers>::const_iterator& timesBegin, const std::vector<detail::FileNumbers>::const_iterator& timesEnd, const std::size_t i )
00323 {
00324         BOOST_ASSERT( timesBegin != timesEnd );
00325 
00326         std::set<std::size_t> padding;
00327         std::set<std::size_t> nbDigits;
00328         
00329         for( std::vector<detail::FileNumbers>::const_iterator s = timesBegin;
00330                  s != timesEnd;
00331                  ++s )
00332         {
00333                 padding.insert( s->getPadding(i) );
00334                 nbDigits.insert( s->getNbDigits(i) );
00335         }
00336         
00337         std::set<std::size_t> pad = padding;
00338         pad.erase(0);
00339         
00340         if( pad.size() == 0 )
00341         {
00342                 _padding = 0;
00343         }
00344         else if( pad.size() == 1 )
00345         {
00346                 _padding = *pad.begin();
00347         }
00348         else
00349         {
00350                 // @todo multi-padding !
00351                 // need to split into multiple sequences !
00352                 _padding = 0;
00353         }
00354 }
00355 
00356 /**
00357  * @brief return if the padding is strict (at least one frame begins with a '0' padding character).
00358  * @param[in] timesStr vector of frame numbers in string format
00359  * @param[in] padding previously detected padding
00360  */
00361 void Sequence::extractIsStrictPadding( const std::vector<std::string>& timesStr, const std::size_t padding )
00362 {
00363         if( padding == 0 )
00364         {
00365                 _strictPadding = false;
00366                 return;
00367         }
00368 
00369         BOOST_FOREACH( const std::string& s, timesStr )
00370         {
00371                 if( s[0] == '0' )
00372                 {
00373                         _strictPadding = true;
00374                         return;
00375                 }
00376         }
00377         _strictPadding = false;
00378 }
00379 
00380 void Sequence::extractIsStrictPadding( const std::vector<detail::FileNumbers>& times, const std::size_t i, const std::size_t padding )
00381 {
00382         if( padding == 0 )
00383         {
00384                 _strictPadding = false;
00385                 return;
00386         }
00387 
00388         BOOST_FOREACH( const detail::FileNumbers& s, times )
00389         {
00390                 if( s.getString( i )[0] == '0' )
00391                 {
00392                         _strictPadding = true;
00393                         return;
00394                 }
00395         }
00396         _strictPadding = false;
00397 }
00398 
00399 std::ostream& Sequence::getCout( std::ostream& os ) const
00400 {
00401         bfs::path dir;
00402         if( showAbsolutePath() )
00403         {
00404                 dir = bfs::absolute( _directory );
00405                 dir = boost::regex_replace( dir.string(), boost::regex( "/\\./" ), "/" );
00406         }
00407         os << std::left;
00408         if( showProperties() )
00409         {
00410                 os << std::setw( PROPERTIES_WIDTH ) << "s ";
00411         }
00412         if( showRelativePath() )
00413         {
00414                 dir = _directory;
00415                 dir = boost::regex_replace( dir.string(), boost::regex( "/\\./" ), "/" );
00416                 os << std::setw( NAME_WIDTH_WITH_DIR ) << _kColorSequence + ( dir / getStandardPattern() ).string() + _kColorStd;
00417         }
00418         else
00419         {
00420                 os << std::setw( NAME_WIDTH ) << _kColorSequence + ( dir / getStandardPattern() ).string() + _kColorStd;
00421         }
00422 
00423         os << " [" << getFirstTime() << ":" << getLastTime();
00424         if( getStep() != 1 )
00425                 os << "x" << getStep();
00426         os << "] " << getNbFiles() << " file" << ( ( getNbFiles() > 1 ) ? "s" : "" );
00427         if( hasMissingFile() )
00428         {
00429                 os << ", " << _kColorError << getNbMissingFiles() << " missing file" << ( ( getNbMissingFiles() > 1 ) ? "s" : "" ) << _kColorStd;
00430         }
00431         return os;
00432 }
00433 
00434 std::vector<boost::filesystem::path> Sequence::getFiles() const
00435 {
00436         std::vector<boost::filesystem::path> allPaths;
00437         for( Time t = getFirstTime(); t <= getLastTime(); t += getStep() )
00438         {
00439                 allPaths.push_back( getAbsoluteFilenameAt( t ) );
00440         }
00441         return allPaths;
00442 }
00443 
00444 }