TuttleOFX
1
|
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 }