#include #include #include #include #include "audio/WaveFileReader.h" #include "phoneExtraction.h" #include "mouthAnimation.h" #include "appInfo.h" #include "NiceCmdLineOutput.h" #include "ProgressBar.h" #include "logging.h" #include #include "Exporter.h" #include "ContinuousTimeline.h" #include #include "stringTools.h" #include #include #include "parallel.h" using std::exception; using std::string; using std::u32string; using std::vector; using std::unique_ptr; using std::make_unique; using std::shared_ptr; using std::make_shared; using std::map; using std::chrono::duration; using std::chrono::duration_cast; using boost::filesystem::path; using boost::adaptors::transformed; namespace tclap = TCLAP; string getMessage(const exception& e) { string result(e.what()); try { std::rethrow_if_nested(e); } catch(const exception& innerException) { result += "\n" + getMessage(innerException); } catch(...) {} return result; } unique_ptr createAudioClip(path filePath) { try { return std::make_unique(filePath); } catch (...) { std::throw_with_nested(std::runtime_error(fmt::format("Could not open sound file '{0}'.", filePath.string()))); } } // Tell TCLAP how to handle our types namespace TCLAP { template<> struct ArgTraits { typedef ValueLike ValueCategory; }; template<> struct ArgTraits { typedef ValueLike ValueCategory; }; } shared_ptr addPausableStdErrSink(logging::Level minLevel) { auto stdErrSink = make_shared(make_shared()); auto pausableSink = make_shared(stdErrSink); auto levelFilter = make_shared(pausableSink, minLevel); logging::addSink(levelFilter); return pausableSink; } void addFileSink(path path, logging::Level minLevel) { auto file = make_shared(); file->exceptions(std::ifstream::failbit | std::ifstream::badbit); file->open(path); auto FileSink = make_shared(file, make_shared()); auto levelFilter = make_shared(FileSink, minLevel); logging::addSink(levelFilter); } u32string readTextFile(path filePath) { if (!exists(filePath)) { throw std::invalid_argument(fmt::format("File {} does not exist.", filePath)); } try { boost::filesystem::ifstream file; file.exceptions(std::ifstream::failbit | std::ifstream::badbit); file.open(filePath); string utf8Text((std::istreambuf_iterator(file)), std::istreambuf_iterator()); try { return utf8ToUtf32(utf8Text); } catch (...) { std::throw_with_nested(std::runtime_error(fmt::format("File encoding is not ASCII or UTF-8.", filePath))); } } catch (...) { std::throw_with_nested(std::runtime_error(fmt::format("Error reading file {0}.", filePath))); } } int main(int argc, char *argv[]) { #ifdef _DEBUG auto pausableStderrSink = addPausableStdErrSink(logging::Level::Warn); pausableStderrSink->pause(); #endif // Define command-line parameters const char argumentValueSeparator = ' '; tclap::CmdLine cmd(appName, argumentValueSeparator, appVersion); cmd.setExceptionHandling(false); cmd.setOutput(new NiceCmdLineOutput()); auto logLevels = vector(logging::LevelConverter::get().getValues()); tclap::ValuesConstraint logLevelConstraint(logLevels); tclap::ValueArg logLevel("", "logLevel", "The minimum log level to log", false, logging::Level::Debug, &logLevelConstraint, cmd); tclap::ValueArg logFileName("", "logFile", "The log file path.", false, string(), "string", cmd); tclap::ValueArg maxThreadCount("", "threads", "The maximum number of worker threads to use.", false, getProcessorCoreCount(), "number", cmd); tclap::ValueArg dialogFile("d", "dialogFile", "A file containing the text of the dialog.", false, string(), "string", cmd); auto exportFormats = vector(ExportFormatConverter::get().getValues()); tclap::ValuesConstraint exportFormatConstraint(exportFormats); tclap::ValueArg exportFormat("f", "exportFormat", "The export format.", false, ExportFormat::TSV, &exportFormatConstraint, cmd); tclap::UnlabeledValueArg inputFileName("inputFile", "The input file. Must be a sound file in WAVE format.", true, "", "string", cmd); try { auto resumeLogging = gsl::finally([&]() { std::cerr << std::endl << std::endl; #ifdef _DEBUG pausableStderrSink->resume(); #endif std::cerr << std::endl; }); // Parse command line cmd.parse(argc, argv); if (maxThreadCount.getValue() < 1) { throw std::runtime_error("Thread count must be 1 or higher."); } // Set up log file if (logFileName.isSet()) { addFileSink(path(logFileName.getValue()), logLevel.getValue()); } logging::infoFormat("Application startup. Command line: {}", join( vector(argv, argv + argc) | transformed([](char* arg) { return fmt::format("\"{}\"", arg); }), " ")); // Detect phones const int columnWidth = 30; std::cerr << std::left; std::cerr << std::setw(columnWidth) << "Analyzing input file"; BoundedTimeline phones(TimeRange::zero()); { ProgressBar progressBar; phones = detectPhones( *createAudioClip(inputFileName.getValue()), dialogFile.isSet() ? readTextFile(path(dialogFile.getValue())) : boost::optional(), maxThreadCount.getValue(), progressBar); } std::cerr << "Done" << std::endl; // Generate mouth shapes std::cerr << std::setw(columnWidth) << "Generating mouth shapes"; ContinuousTimeline shapes = animate(phones); std::cerr << "Done" << std::endl; std::cerr << std::endl; // Export unique_ptr exporter; switch (exportFormat.getValue()) { case ExportFormat::TSV: exporter = make_unique(); break; case ExportFormat::XML: exporter = make_unique(); break; case ExportFormat::JSON: exporter = make_unique(); break; default: throw std::runtime_error("Unknown export format."); } exporter->exportShapes(path(inputFileName.getValue()), shapes, std::cout); logging::info("Exiting application normally."); return 0; } catch (tclap::ArgException& e) { // Error parsing command-line args. cmd.getOutput()->failure(cmd, e); std::cerr << std::endl; logging::error("Invalid command line. Exiting application with error code."); return 1; } catch (tclap::ExitException&) { // A built-in TCLAP command (like --help) has finished. Exit application. std::cerr << std::endl; logging::info("Exiting application after help-like command."); return 0; } catch (const exception& e) { // Generic error string message = getMessage(e); std::cerr << "An error occurred.\n" << message << std::endl; logging::errorFormat("Exiting application with error: {}", message); return 1; } }