#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(); } }