tmk/cpp/hashing/filehasher.cpp (163 lines of code) (raw):

// ================================================================ // Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved // ================================================================ #include <tmk/cpp/algo/tmkfv.h> #include <tmk/cpp/hashing/bufferhashers.h> #include <tmk/cpp/io/tmkio.h> #include <stdio.h> #include <memory> #include <iomanip> #include <sstream> const char* POPEN_MODE = #if defined(_WIN32) "rb"; #else "r"; #endif using namespace std; namespace facebook { namespace tmk { namespace hashing { bool hashVideo( int downsampleFrameDimension, std::string ffmpegGeneratorCommand, io::TMKFramewiseAlgorithm tmkFramewiseAlgorithm, int resampleFramesPerSecond, facebook::tmk::algo::TMKFeatureVectors& tmkFeatureVectors, bool verbose, const char* argv0) { if (verbose) { fprintf(stderr, "%s\n", ffmpegGeneratorCommand.c_str()); } FILE* inputFp = popen(ffmpegGeneratorCommand.c_str(), POPEN_MODE); if (inputFp == nullptr) { fprintf(stderr, "%s: ffmpeg to generate video stream failed\n", argv0); return false; } // ---------------------------------------------------------------- std::unique_ptr<tmk::hashing::AbstractFrameBufferHasher> phasher = tmk::hashing::FrameBufferHasherFactory::createFrameHasher( tmkFramewiseAlgorithm, downsampleFrameDimension, downsampleFrameDimension); if (phasher == nullptr) { fprintf(stderr, "Error: Phasher is null"); return false; } int frameFeatureDimension = phasher->getFeatureDimension(); // ---------------------------------------------------------------- // Allocate this and re-use it over frames, rather than allocating/freeing // on each frame. std::unique_ptr<uint8_t[]> rawFrameBuffer( new uint8_t[downsampleFrameDimension * downsampleFrameDimension * 3]); std::vector<float> feature(frameFeatureDimension); std::vector<int> periods = facebook::tmk::algo::TMKFeatureVectors::makePoullotPeriods(); std::vector<float> fourierCoefficients = facebook::tmk::algo::TMKFeatureVectors::makePoullotFourierCoefficients(); tmkFeatureVectors = facebook::tmk::algo::TMKFeatureVectors( tmkFramewiseAlgorithm, resampleFramesPerSecond, periods, fourierCoefficients, frameFeatureDimension); try { bool eof = false; while (!feof(inputFp)) { bool read_rc = facebook::tmk::io::readRGBTriples( rawFrameBuffer.get(), downsampleFrameDimension, downsampleFrameDimension, inputFp, eof); if (eof) { break; } if (!read_rc) { perror("fread"); fprintf( stderr, "%s: failed to read frame buffer %d.\n", argv0, tmkFeatureVectors.getFrameFeatureCount()); return false; } if (!phasher->hashFrame(rawFrameBuffer.get(), feature)) { fprintf( stderr, "%s: failed to hash frame buffer %d.\n", argv0, tmkFeatureVectors.getFrameFeatureCount()); return false; } tmkFeatureVectors.ingestFrameFeature( feature, tmkFeatureVectors.getFrameFeatureCount()); } } catch (const exception& e) { fprintf(stderr, "%s: failed to download and hash video.\n", argv0); fprintf(stderr, "%s\n", e.what()); return false; } tmkFeatureVectors.finishFrameFeatureIngest(); // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // This includes failure to exec the subordinate process. int pclose_rc = pclose(inputFp); if (pclose_rc != 0) { fprintf(stderr, "%s: ffmpeg pclose return code %d.\n", argv0, pclose_rc); return false; } return true; } bool hashEverstoreVideoFile( const std::string& inputEverstoreHandle, io::TMKFramewiseAlgorithm tmkFramewiseAlgorithm, const std::string& ffmpegPath, const std::string& everstorePath, int resampleFramesPerSecond, facebook::tmk::algo::TMKFeatureVectors& tmkFeatureVectors, bool verbose, const char* argv0) { int downsampleFrameDimension = tmk::hashing::FrameBufferHasherFactory::getFrameHasherDownscaleDimension( tmkFramewiseAlgorithm); std::string everstoreCommand = everstorePath + " --input_everstore_handle=" + inputEverstoreHandle; std::string ffmpegGeneratorCommand = everstoreCommand + " | " + ffmpegPath + " -f mp4 " + " -i " + "pipe: " + " -s " + std::to_string(downsampleFrameDimension) + ":" + std::to_string(downsampleFrameDimension) + " -an -f rawvideo -c:v rawvideo -pix_fmt rgb24 -r " + std::to_string(resampleFramesPerSecond) + " pipe:1"; return hashVideo( downsampleFrameDimension, ffmpegGeneratorCommand, tmkFramewiseAlgorithm, resampleFramesPerSecond, tmkFeatureVectors, verbose, argv0); } bool hashVideoFile( const std::string& inputVideoFileName, io::TMKFramewiseAlgorithm tmkFramewiseAlgorithm, const std::string& ffmpegPath, int resampleFramesPerSecond, facebook::tmk::algo::TMKFeatureVectors& tmkFeatureVectors, bool verbose, const char* argv0) { int downsampleFrameDimension = tmk::hashing::FrameBufferHasherFactory::getFrameHasherDownscaleDimension( tmkFramewiseAlgorithm); // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // These are the parameters for the ffmpeg that we will use to created // the video stream. We want to use popen so we can build a binary instead // of a script to ease the process of adding the executable to a package. // It's essential for cross-company hash-sharing that we execute as close // to the same code our partners will as possible. // // This is like: // // output_width=64 // output_height=64 // output_fps=15 // ffmpeg -i "$1" \ // -s ${output_width}:${output_height} -an -f rawvideo -c:v rawvideo \ // -pix_fmt rgb24 -r $output_fps pipe:1 // add single quotes around the file name and then escape any single quotes inside it // example: my file's.mp4 -> 'my file'\''s.mp4' std::stringstream ss; ss << quoted(inputVideoFileName); std::string escapedInputVideoFileName = ss.str(); std::string ffmpegLogLevel = verbose ? "" : "-loglevel warning -hide_banner -stats"; std::string command = ffmpegPath + " " + ffmpegLogLevel + " -nostdin -i " + escapedInputVideoFileName + " -s " + std::to_string(downsampleFrameDimension) + ":" + std::to_string(downsampleFrameDimension) + " -an -f rawvideo -c:v rawvideo -pix_fmt rgb24 -r " + std::to_string(resampleFramesPerSecond) + " pipe:1"; return hashVideo( downsampleFrameDimension, command, tmkFramewiseAlgorithm, resampleFramesPerSecond, tmkFeatureVectors, verbose, argv0); } } // namespace hashing } // namespace tmk } // namespace facebook