#include #include #include #include #include #include #include "timing.h" #include "converter.h" #include "procs.h" #include "config.h" namespace Converter { ///\brief The base constructor Converter::Converter(){ fillFFMpegEncoders(); } ///\brief A function that fill the internal variables with values provided by examing ffmpeg output /// ///Checks for the following encoders: /// - AAC /// - H264 /// - MP3 void Converter::fillFFMpegEncoders(){ std::vector cmd; cmd.reserve(3); cmd.push_back((char*)"ffmpeg"); cmd.push_back((char*)"-encoders"); cmd.push_back(NULL); int outFD = -1; Util::Procs::StartPiped("FFMpegInfo", &cmd[0], 0, &outFD, 0); while( Util::Procs::isActive("FFMpegInfo")){ Util::sleep(100); } FILE * outFile = fdopen( outFD, "r" ); char * fileBuf = 0; size_t fileBufLen = 0; while ( !(feof(outFile) || ferror(outFile)) && (getline(&fileBuf, &fileBufLen, outFile) != -1)){ if (strstr(fileBuf, "aac") || strstr(fileBuf, "AAC")){ strtok(fileBuf, " \t"); allCodecs["ffmpeg"][strtok(NULL, " \t")] = "aac"; } if (strstr(fileBuf, "h264") || strstr(fileBuf, "H264")){ strtok(fileBuf, " \t"); allCodecs["ffmpeg"][strtok(NULL, " \t")] = "h264"; } if (strstr(fileBuf, "mp3") || strstr(fileBuf, "MP3")){ strtok(fileBuf, " \t"); allCodecs["ffmpeg"][strtok(NULL, " \t")] = "mp3"; } } fclose( outFile ); } ///\brief A function to obtain all available codecs that have been obtained from the encoders. ///\return A reference to the allCodecs member. converterInfo & Converter::getCodecs(){ return allCodecs; } ///\brief A function to obtain the available encoders in JSON format. ///\return A JSON::Value containing all encoder:codec pairs. JSON::Value Converter::getEncoders(){ JSON::Value result; for (converterInfo::iterator convIt = allCodecs.begin(); convIt != allCodecs.end(); convIt++){ for (codecInfo::iterator codIt = convIt->second.begin(); codIt != convIt->second.end(); codIt++){ if (codIt->second == "h264"){ result[convIt->first]["video"][codIt->first] = codIt->second; }else{ result[convIt->first]["audio"][codIt->first] = codIt->second; } } } return result; } ///\brief Looks in a given path for all files that could be converted ///\param myPath The location to look at, this should be a folder. ///\return A JSON::Value containing all media files in the location, with their corresponding metadata values. JSON::Value Converter::queryPath(std::string myPath){ char const * cmd[3] = {0, 0, 0}; std::string mistPath = Util::getMyPath() + "MistInfo"; cmd[0] = mistPath.c_str(); JSON::Value result; DIR * Dirp = opendir(myPath.c_str()); struct stat StatBuf; if (Dirp){ dirent * entry; while ((entry = readdir(Dirp))){ if (stat(std::string(myPath + "/" + entry->d_name).c_str(), &StatBuf) == -1){ continue; } if ((StatBuf.st_mode & S_IFREG) == 0){ continue; } std::string fileName = entry->d_name; std::string mijnPad = std::string(myPath + (myPath[myPath.size()-1] == '/' ? "" : "/") + entry->d_name); cmd[1] = mijnPad.c_str(); result[fileName] = JSON::fromString(Util::Procs::getOutputOf((char* const*)cmd)); } } return result; } ///\brief Start a conversion with the given parameters ///\param name The name to use for logging the conversion. ///\param parameters The parameters, accepted are the following: /// - input The input url /// - output The output url /// - encoder The encoder to use /// - video An object containing video parameters, if not existant no video will be output. Values are: /// - width The width of the resulting video /// - height The height of the resulting video /// - codec The codec to encode video in, or copy to use the current codec /// - fpks The framerate in fps * 1000 /// - audio An object containing audio parameters, if not existant no audio will be output. Values are: /// - codec The codec to encode audio in, or copy to use the current codec /// - samplerate The target samplerate for the audio, in hz void Converter::startConversion(std::string name, JSON::Value parameters) { if ( !parameters.isMember("input")){ statusHistory[name] = "No input file supplied"; return; } if ( !parameters.isMember("output")){ statusHistory[name] = "No output file supplied"; return; } struct stat statBuf; std::string outPath = parameters["output"].asString(); outPath = outPath.substr(0, outPath.rfind('/')); int statRes = stat(outPath.c_str(), & statBuf); if (statRes == -1 || !S_ISDIR(statBuf.st_mode)){ statusHistory[name] = "Output path is either non-existent, or not a path."; return; } if ( !parameters.isMember("encoder")){ statusHistory[name] = "No encoder specified"; return; } if (allCodecs.find(parameters["encoder"]) == allCodecs.end()){ statusHistory[name] = "Can not find encoder " + parameters["encoder"].asString(); return; } if (parameters.isMember("video")){ if (parameters["video"].isMember("width") && !parameters["video"].isMember("height")){ statusHistory[name] = "No height parameter given"; return; } if (parameters["video"].isMember("height") && !parameters["video"].isMember("width")){ statusHistory[name] = "No width parameter given"; return; } } std::stringstream encoderCommand; if (parameters["encoder"] == "ffmpeg"){ encoderCommand << "ffmpeg -i "; encoderCommand << parameters["input"].asString() << " "; if (parameters.isMember("video")){ if ( !parameters["video"].isMember("codec") || parameters["video"]["codec"] == "copy"){ encoderCommand << "-vcodec copy "; }else{ codecInfo::iterator vidCodec = allCodecs["ffmpeg"].find(parameters["video"]["codec"]); if (vidCodec == allCodecs["ffmpeg"].end()){ statusHistory[name] = "Can not find video codec " + parameters["video"]["codec"].asString(); return; } encoderCommand << "-vcodec " << vidCodec->first << " "; if (parameters["video"]["codec"].asString() == "h264"){ //Enforce baseline encoderCommand << "-preset slow -profile:v baseline -level 30 "; } if (parameters["video"].isMember("fpks")){ encoderCommand << "-r " << parameters["video"]["fpks"].asInt() / 1000 << " "; } if (parameters["video"].isMember("width")){ encoderCommand << "-s " << parameters["video"]["width"].asInt() << "x" << parameters["video"]["height"].asInt() << " "; } ///\todo Keyframe interval (different in older and newer versions of ffmpeg?) } }else{ encoderCommand << "-vn "; } if (parameters.isMember("audio")){ if ( !parameters["audio"].isMember("codec")){ encoderCommand << "-acodec copy "; }else{ codecInfo::iterator audCodec = allCodecs["ffmpeg"].find(parameters["audio"]["codec"]); if (audCodec == allCodecs["ffmpeg"].end()){ statusHistory[name] = "Can not find audio codec " + parameters["audio"]["codec"].asString(); return; } if (audCodec->second == "aac"){ encoderCommand << "-strict -2 "; } encoderCommand << "-acodec " << audCodec->first << " "; if (parameters["audio"].isMember("samplerate")){ encoderCommand << "-ar " << parameters["audio"]["samplerate"].asInt() << " "; } } }else{ encoderCommand << "-an "; } encoderCommand << "-f flv -"; } int statusFD = -1; Util::Procs::StartPiped2(name,encoderCommand.str(),Util::getMyPath() + "MistFLV2DTSC -o " + parameters["output"].asString(),0,0,&statusFD,0); parameters["statusFD"] = statusFD; allConversions[name] = parameters; allConversions[name]["status"]["duration"] = "?"; allConversions[name]["status"]["progress"] = 0; allConversions[name]["status"]["frame"] = 0; allConversions[name]["status"]["time"] = 0; } ///\brief Updates the internal status of the converter class. /// ///Will check for each running conversion whether it is still running, and update its status accordingly void Converter::updateStatus(){ if (allConversions.size()){ std::map::iterator cIt; bool hasChanged = true; while (hasChanged && allConversions.size()){ hasChanged = false; for (cIt = allConversions.begin(); cIt != allConversions.end(); cIt++){ if (Util::Procs::isActive(cIt->first)){ int statusFD = dup(cIt->second["statusFD"].asInt()); fsync( statusFD ); FILE* statusFile = fdopen( statusFD, "r" ); char * fileBuf = 0; size_t fileBufLen = 0; fseek(statusFile,0,SEEK_END); std::string line; int totalTime = 0; do{ getdelim(&fileBuf, &fileBufLen, '\r', statusFile); line = fileBuf; if (line.find("Duration") != std::string::npos){ int curOffset = line.find("Duration: ") + 10; totalTime += atoi(line.substr(curOffset, 2).c_str()) * 60 * 60 * 1000; totalTime += atoi(line.substr(curOffset+3, 2).c_str()) * 60 * 1000; totalTime += atoi(line.substr(curOffset+6, 2).c_str()) *1000; totalTime += atoi(line.substr(curOffset+9, 2).c_str()) * 10; cIt->second["duration"] = totalTime; } }while ( !feof(statusFile) && line.find("frame") != 0);//"frame" is the fist word on an actual status line of ffmpeg if ( !feof(statusFile)){ cIt->second["status"] = parseFFMpegStatus( line ); cIt->second["status"]["duration"] = cIt->second["duration"]; cIt->second["status"]["progress"] = (cIt->second["status"]["time"].asInt() * 100) / cIt->second["duration"].asInt(); }else{ line.erase(line.end()-1); line = line.substr( line.rfind("\n") + 1 ); cIt->second["status"] = line; } free(fileBuf); fclose(statusFile); }else{ if (statusHistory.find( cIt->first ) == statusHistory.end()){ statusHistory[cIt->first] = "Conversion successful, running DTSCFix"; Util::Procs::Start(cIt->first+"DTSCFix",Util::getMyPath() + "MistDTSCFix " + cIt->second["output"].asString()); } allConversions.erase(cIt); hasChanged = true; break; } } } } if(statusHistory.size()){ std::map::iterator sIt; for (sIt = statusHistory.begin(); sIt != statusHistory.end(); sIt++){ if (statusHistory[sIt->first].find("DTSCFix") != std::string::npos){ if (Util::Procs::isActive(sIt->first+"DTSCFIX")){ continue; } statusHistory[sIt->first] = "Conversion successful"; } } } } ///\brief Parses a single ffmpeg status line into a JSON format ///\param statusLine The current status of ffmpeg ///\return A JSON::Value with the following values set: /// - frame The current last encoded frame /// - time The current last encoded timestamp JSON::Value Converter::parseFFMpegStatus(std::string statusLine){ JSON::Value result; int curOffset = statusLine.find("frame=") + 6; result["frame"] = atoi(statusLine.substr(curOffset, statusLine.find("fps=") - curOffset).c_str() ); curOffset = statusLine.find("time=") + 5; int myTime = 0; myTime += atoi(statusLine.substr(curOffset, 2).c_str()) * 60 * 60 * 1000; myTime += atoi(statusLine.substr(curOffset+3, 2).c_str()) * 60 * 1000; myTime += atoi(statusLine.substr(curOffset+6, 2).c_str()) *1000; myTime += atoi(statusLine.substr(curOffset+9, 2).c_str()) * 10; result["time"] = myTime; return result; } ///\brief Obtain the current internal status of the conversion class ///\return A JSON::Value with the status of each conversion JSON::Value Converter::getStatus(){ updateStatus(); JSON::Value result; if (allConversions.size()){ for (std::map::iterator cIt = allConversions.begin(); cIt != allConversions.end(); cIt++){ result[cIt->first] = cIt->second["status"]; result[cIt->first]["details"] = cIt->second; } } if (statusHistory.size()){ std::map::iterator sIt; for (sIt = statusHistory.begin(); sIt != statusHistory.end(); sIt++){ result[sIt->first] = sIt->second; } } return result; } ///\brief Clears the status history of all conversions void Converter::clearStatus(){ statusHistory.clear(); } }