diff --git a/rhubarb/CMakeLists.txt b/rhubarb/CMakeLists.txt index 41507c3..17b257f 100644 --- a/rhubarb/CMakeLists.txt +++ b/rhubarb/CMakeLists.txt @@ -360,6 +360,8 @@ target_link_libraries(rhubarb-core # ... rhubarb-exporters add_library(rhubarb-exporters + src/exporters/DatExporter.cpp + src/exporters/DatExporter.h src/exporters/Exporter.h src/exporters/exporterTools.cpp src/exporters/exporterTools.h diff --git a/rhubarb/src/exporters/DatExporter.cpp b/rhubarb/src/exporters/DatExporter.cpp new file mode 100644 index 0000000..cf3bf3f --- /dev/null +++ b/rhubarb/src/exporters/DatExporter.cpp @@ -0,0 +1,72 @@ +#include "DatExporter.h" +#include "animation/targetShapeSet.h" +#include + +using std::chrono::duration; +using std::chrono::duration_cast; +using std::string; + +DatExporter::DatExporter(const ShapeSet& targetShapeSet, double frameRate, bool convertToPrestonBlair) : + frameRate(frameRate), + convertToPrestonBlair(convertToPrestonBlair), + prestonBlairShapeNames { + { Shape::A, "MBP" }, + { Shape::B, "etc" }, + { Shape::C, "E" }, + { Shape::D, "AI" }, + { Shape::E, "O" }, + { Shape::F, "U" }, + { Shape::G, "FV" }, + { Shape::H, "L" }, + { Shape::X, "rest" }, + } +{ + // Animation works with a fixed frame rate of 100. + // Downsampling to much less than 25 fps may result in dropped frames. + // Upsampling to more than 100 fps doesn't make sense. + const double minFrameRate = 24.0; + const double maxFrameRate = 100.0; + + if (frameRate < minFrameRate || frameRate > maxFrameRate) { + throw std::runtime_error(fmt::format("Frame rate must be between {} and {} fps.", minFrameRate, maxFrameRate)); + } + + if (convertToPrestonBlair) { + for (Shape shape : targetShapeSet) { + if (prestonBlairShapeNames.find(shape) == prestonBlairShapeNames.end()) { + throw std::runtime_error(fmt::format("Mouth shape {} cannot be converted to Preston Blair shape names.")); + } + } + } +} + +void DatExporter::exportAnimation(const ExporterInput& input, std::ostream& outputStream) { + outputStream << "MohoSwitch1" << "\n"; + + // Output shapes with start times + int lastFrameNumber = 0; + for (auto& timedShape : input.animation) { + const int frameNumber = toFrameNumber(timedShape.getStart()); + if (frameNumber == lastFrameNumber) continue; + + const string shapeName = toString(timedShape.getValue()); + outputStream << frameNumber << " " << shapeName << "\n"; + lastFrameNumber = frameNumber; + } + + // Output closed mouth with end time + int frameNumber = toFrameNumber(input.animation.getRange().getEnd()); + if (frameNumber == lastFrameNumber) ++frameNumber; + const string shapeName = toString(convertToTargetShapeSet(Shape::X, input.targetShapeSet)); + outputStream << frameNumber << " " << shapeName << "\n"; +} + +string DatExporter::toString(Shape shape) const { + return convertToPrestonBlair + ? prestonBlairShapeNames.at(shape) + : boost::lexical_cast(shape); +} + +int DatExporter::toFrameNumber(centiseconds time) const { + return 1 + static_cast(frameRate * duration_cast>(time).count()); +} diff --git a/rhubarb/src/exporters/DatExporter.h b/rhubarb/src/exporters/DatExporter.h new file mode 100644 index 0000000..af6284e --- /dev/null +++ b/rhubarb/src/exporters/DatExporter.h @@ -0,0 +1,21 @@ +#pragma once + +#include "Exporter.h" +#include "core/Shape.h" +#include +#include + +// Exporter for Moho's switch data file format +class DatExporter : public Exporter { +public: + DatExporter(const ShapeSet& targetShapeSet, double frameRate, bool convertToPrestonBlair); + void exportAnimation(const ExporterInput& input, std::ostream& outputStream) override; + +private: + int toFrameNumber(centiseconds time) const; + std::string toString(Shape shape) const; + + double frameRate; + bool convertToPrestonBlair; + std::map prestonBlairShapeNames; +}; diff --git a/rhubarb/src/rhubarb/ExportFormat.cpp b/rhubarb/src/rhubarb/ExportFormat.cpp index 6619f3f..9e15e39 100644 --- a/rhubarb/src/rhubarb/ExportFormat.cpp +++ b/rhubarb/src/rhubarb/ExportFormat.cpp @@ -13,6 +13,7 @@ string ExportFormatConverter::getTypeName() { EnumConverter::member_data ExportFormatConverter::getMemberData() { return member_data { + { ExportFormat::Dat, "dat" }, { ExportFormat::Tsv, "tsv" }, { ExportFormat::Xml, "xml" }, { ExportFormat::Json, "json" } diff --git a/rhubarb/src/rhubarb/ExportFormat.h b/rhubarb/src/rhubarb/ExportFormat.h index 1ff9392..8591c04 100644 --- a/rhubarb/src/rhubarb/ExportFormat.h +++ b/rhubarb/src/rhubarb/ExportFormat.h @@ -3,6 +3,7 @@ #include "tools/EnumConverter.h" enum class ExportFormat { + Dat, Tsv, Xml, Json diff --git a/rhubarb/src/rhubarb/main.cpp b/rhubarb/src/rhubarb/main.cpp index e873422..4e512a9 100644 --- a/rhubarb/src/rhubarb/main.cpp +++ b/rhubarb/src/rhubarb/main.cpp @@ -18,6 +18,7 @@ #include "tools/textFiles.h" #include "lib/rhubarbLib.h" #include "ExportFormat.h" +#include "exporters/DatExporter.h" #include "exporters/TsvExporter.h" #include "exporters/XmlExporter.h" #include "exporters/JsonExporter.h" @@ -82,8 +83,15 @@ unique_ptr createRecognizer(RecognizerType recognizerType) { } } -unique_ptr createExporter(ExportFormat exportFormat) { +unique_ptr createExporter( + ExportFormat exportFormat, + const ShapeSet& targetShapeSet, + double datFrameRate, + bool datUsePrestonBlair +) { switch (exportFormat) { + case ExportFormat::Dat: + return make_unique(targetShapeSet, datFrameRate, datUsePrestonBlair); case ExportFormat::Tsv: return make_unique(); case ExportFormat::Xml: @@ -172,6 +180,16 @@ int main(int platformArgc, char* platformArgv[]) { false, string(), "string", cmd ); + tclap::SwitchArg datUsePrestonBlair( + "", "datUsePrestonBlair", "Only for dat exporter: uses the Preston Blair mouth shape names.", + cmd, false + ); + + tclap::ValueArg datFrameRate( + "", "datFrameRate", "Only for dat exporter: the desired frame rate.", + false, 24.0, "number", cmd + ); + auto exportFormats = vector(ExportFormatConverter::get().getValues()); tclap::ValuesConstraint exportFormatConstraint(exportFormats); tclap::ValueArg exportFormat( @@ -222,6 +240,13 @@ int main(int platformArgc, char* platformArgv[]) { path inputFilePath(inputFileName.getValue()); ShapeSet targetShapeSet = getTargetShapeSet(extendedShapes.getValue()); + unique_ptr exporter = createExporter( + exportFormat.getValue(), + targetShapeSet, + datFrameRate.getValue(), + datUsePrestonBlair.getValue() + ); + logging::log(StartEntry(inputFilePath)); logging::debugFormat("Command line: {}", join(args | transformed([](string arg) { return fmt::format("\"{}\"", arg); }), " ")); @@ -246,7 +271,6 @@ int main(int platformArgc, char* platformArgv[]) { logging::info("Done animating."); // Export animation - unique_ptr exporter = createExporter(exportFormat.getValue()); optional outputFile; if (outputFileName.isSet()) { outputFile = boost::in_place(outputFileName.getValue());