Restructured rhubarb-exporters

This commit is contained in:
Daniel Wolf 2016-11-16 11:35:27 +01:00
parent 3e34425c11
commit 289b7ba56e
14 changed files with 200 additions and 158 deletions

View File

@ -275,8 +275,15 @@ target_link_libraries(rhubarb-core
# ... rhubarb-exporters # ... rhubarb-exporters
add_library(rhubarb-exporters add_library(rhubarb-exporters
src/exporters/Exporter.cpp
src/exporters/Exporter.h src/exporters/Exporter.h
src/exporters/exporterTools.cpp
src/exporters/exporterTools.h
src/exporters/JsonExporter.cpp
src/exporters/JsonExporter.h
src/exporters/TsvExporter.cpp
src/exporters/TsvExporter.h
src/exporters/XmlExporter.cpp
src/exporters/XmlExporter.h
) )
target_include_directories(rhubarb-exporters PUBLIC "src/exporters") target_include_directories(rhubarb-exporters PUBLIC "src/exporters")
target_link_libraries(rhubarb-exporters target_link_libraries(rhubarb-exporters
@ -379,7 +386,11 @@ target_link_libraries(rhubarb-tools
) )
# Define Rhubarb executable # Define Rhubarb executable
add_executable(rhubarb src/main.cpp) add_executable(rhubarb
src/main.cpp
src/ExportFormat.cpp
src/ExportFormat.h
)
target_include_directories(rhubarb PUBLIC "src") target_include_directories(rhubarb PUBLIC "src")
target_link_libraries(rhubarb target_link_libraries(rhubarb
rhubarb-exporters rhubarb-exporters

28
src/ExportFormat.cpp Normal file
View File

@ -0,0 +1,28 @@
#include "ExportFormat.h"
using std::string;
ExportFormatConverter& ExportFormatConverter::get() {
static ExportFormatConverter converter;
return converter;
}
string ExportFormatConverter::getTypeName() {
return "ExportFormat";
}
EnumConverter<ExportFormat>::member_data ExportFormatConverter::getMemberData() {
return member_data{
{ ExportFormat::TSV, "TSV" },
{ ExportFormat::XML, "XML" },
{ ExportFormat::JSON, "JSON" }
};
}
std::ostream& operator<<(std::ostream& stream, ExportFormat value) {
return ExportFormatConverter::get().write(stream, value);
}
std::istream& operator>>(std::istream& stream, ExportFormat& value) {
return ExportFormatConverter::get().read(stream, value);
}

21
src/ExportFormat.h Normal file
View File

@ -0,0 +1,21 @@
#pragma once
#include "EnumConverter.h"
enum class ExportFormat {
TSV,
XML,
JSON
};
class ExportFormatConverter : public EnumConverter<ExportFormat> {
public:
static ExportFormatConverter& get();
protected:
std::string getTypeName() override;
member_data getMemberData() override;
};
std::ostream& operator<<(std::ostream& stream, ExportFormat value);
std::istream& operator>>(std::istream& stream, ExportFormat& value);

View File

@ -1,122 +0,0 @@
#include "Exporter.h"
#include <boost/property_tree/ptree.hpp>
#include <boost/property_tree/xml_parser.hpp>
#include <tools.h>
#include <boost/version.hpp>
using std::string;
using boost::property_tree::ptree;
ExportFormatConverter& ExportFormatConverter::get() {
static ExportFormatConverter converter;
return converter;
}
string ExportFormatConverter::getTypeName() {
return "ExportFormat";
}
EnumConverter<ExportFormat>::member_data ExportFormatConverter::getMemberData() {
return member_data{
{ ExportFormat::TSV, "TSV" },
{ ExportFormat::XML, "XML" },
{ ExportFormat::JSON, "JSON" }
};
}
std::ostream& operator<<(std::ostream& stream, ExportFormat value) {
return ExportFormatConverter::get().write(stream, value);
}
std::istream& operator>>(std::istream& stream, ExportFormat& value) {
return ExportFormatConverter::get().read(stream, value);
}
// Makes sure there is at least one mouth shape
std::vector<Timed<Shape>> dummyShapeIfEmpty(const Timeline<Shape>& shapes) {
std::vector<Timed<Shape>> result;
std::copy(shapes.begin(), shapes.end(), std::back_inserter(result));
if (result.empty()) {
// Add zero-length empty mouth
result.push_back(Timed<Shape>(0_cs, 0_cs, Shape::A));
}
return result;
}
void TSVExporter::exportShapes(const boost::filesystem::path& inputFilePath, const ContinuousTimeline<Shape>& shapes, std::ostream& outputStream) {
UNUSED(inputFilePath);
// Output shapes with start times
for (auto& timedShape : shapes) {
outputStream << formatDuration(timedShape.getStart()) << "\t" << timedShape.getValue() << "\n";
}
// Output closed mouth with end time
outputStream << formatDuration(shapes.getRange().getEnd()) << "\t" << Shape::X << "\n";
}
void XMLExporter::exportShapes(const boost::filesystem::path& inputFilePath, const ContinuousTimeline<Shape>& shapes, std::ostream& outputStream) {
ptree tree;
// Add metadata
tree.put("rhubarbResult.metadata.soundFile", inputFilePath.string());
tree.put("rhubarbResult.metadata.duration", formatDuration(shapes.getRange().getDuration()));
// Add mouth cues
for (auto& timedShape : dummyShapeIfEmpty(shapes)) {
ptree& mouthCueElement = tree.add("rhubarbResult.mouthCues.mouthCue", timedShape.getValue());
mouthCueElement.put("<xmlattr>.start", formatDuration(timedShape.getStart()));
mouthCueElement.put("<xmlattr>.end", formatDuration(timedShape.getEnd()));
}
#if BOOST_VERSION < 105600 // Support legacy syntax
using writer_setting = boost::property_tree::xml_writer_settings<char>;
#else
using writer_setting = boost::property_tree::xml_writer_settings<string>;
#endif
write_xml(outputStream, tree, writer_setting(' ', 2));
}
string escapeJSONString(const string& s) {
string result;
for (char c : s) {
switch (c) {
case '"': result += "\\\""; break;
case '\\': result += "\\\\"; break;
case '\b': result += "\\b"; break;
case '\f': result += "\\f"; break;
case '\n': result += "\\n"; break;
case '\r': result += "\\r"; break;
case '\t': result += "\\t"; break;
default:
if (c <= '\x1f') {
result += fmt::format("\\u{0:04x}", c);
} else {
result += c;
}
}
}
return result;
}
void JSONExporter::exportShapes(const boost::filesystem::path& inputFilePath, const ContinuousTimeline<Shape>& shapes, std::ostream& outputStream) {
// Export as JSON.
// I'm not using a library because the code is short enough without one and it lets me control the formatting.
outputStream << "{\n";
outputStream << " \"metadata\": {\n";
outputStream << " \"soundFile\": \"" << escapeJSONString(inputFilePath.string()) << "\",\n";
outputStream << " \"duration\": " << formatDuration(shapes.getRange().getDuration()) << "\n";
outputStream << " },\n";
outputStream << " \"mouthCues\": [\n";
bool isFirst = true;
for (auto& timedShape : dummyShapeIfEmpty(shapes)) {
if (!isFirst) outputStream << ",\n";
isFirst = false;
outputStream << " { \"start\": " << formatDuration(timedShape.getStart())
<< ", \"end\": " << formatDuration(timedShape.getEnd())
<< ", \"value\": \"" << timedShape.getValue() << "\" }";
}
outputStream << "\n";
outputStream << " ]\n";
outputStream << "}\n";
}

View File

@ -3,43 +3,9 @@
#include <Shape.h> #include <Shape.h>
#include "ContinuousTimeline.h" #include "ContinuousTimeline.h"
#include <boost/filesystem/path.hpp> #include <boost/filesystem/path.hpp>
#include <iostream>
enum class ExportFormat {
TSV,
XML,
JSON
};
class ExportFormatConverter : public EnumConverter<ExportFormat> {
public:
static ExportFormatConverter& get();
protected:
std::string getTypeName() override;
member_data getMemberData() override;
};
std::ostream& operator<<(std::ostream& stream, ExportFormat value);
std::istream& operator>>(std::istream& stream, ExportFormat& value);
class Exporter { class Exporter {
public: public:
virtual ~Exporter() {} virtual ~Exporter() {}
virtual void exportShapes(const boost::filesystem::path& inputFilePath, const ContinuousTimeline<Shape>& shapes, std::ostream& outputStream) = 0; virtual void exportShapes(const boost::filesystem::path& inputFilePath, const ContinuousTimeline<Shape>& shapes, std::ostream& outputStream) = 0;
}; };
class TSVExporter : public Exporter {
public:
void exportShapes(const boost::filesystem::path& inputFilePath, const ContinuousTimeline<Shape>& shapes, std::ostream& outputStream) override;
};
class XMLExporter : public Exporter {
public:
void exportShapes(const boost::filesystem::path& inputFilePath, const ContinuousTimeline<Shape>& shapes, std::ostream& outputStream) override;
};
class JSONExporter : public Exporter {
public:
void exportShapes(const boost::filesystem::path& inputFilePath, const ContinuousTimeline<Shape>& shapes, std::ostream& outputStream) override;
};

View File

@ -0,0 +1,48 @@
#include "JsonExporter.h"
#include "exporterTools.h"
using std::string;
string escapeJSONString(const string& s) {
string result;
for (char c : s) {
switch (c) {
case '"': result += "\\\""; break;
case '\\': result += "\\\\"; break;
case '\b': result += "\\b"; break;
case '\f': result += "\\f"; break;
case '\n': result += "\\n"; break;
case '\r': result += "\\r"; break;
case '\t': result += "\\t"; break;
default:
if (c <= '\x1f') {
result += fmt::format("\\u{0:04x}", c);
} else {
result += c;
}
}
}
return result;
}
void JSONExporter::exportShapes(const boost::filesystem::path& inputFilePath, const ContinuousTimeline<Shape>& shapes, std::ostream& outputStream) {
// Export as JSON.
// I'm not using a library because the code is short enough without one and it lets me control the formatting.
outputStream << "{\n";
outputStream << " \"metadata\": {\n";
outputStream << " \"soundFile\": \"" << escapeJSONString(inputFilePath.string()) << "\",\n";
outputStream << " \"duration\": " << formatDuration(shapes.getRange().getDuration()) << "\n";
outputStream << " },\n";
outputStream << " \"mouthCues\": [\n";
bool isFirst = true;
for (auto& timedShape : dummyShapeIfEmpty(shapes)) {
if (!isFirst) outputStream << ",\n";
isFirst = false;
outputStream << " { \"start\": " << formatDuration(timedShape.getStart())
<< ", \"end\": " << formatDuration(timedShape.getEnd())
<< ", \"value\": \"" << timedShape.getValue() << "\" }";
}
outputStream << "\n";
outputStream << " ]\n";
outputStream << "}\n";
}

View File

@ -0,0 +1,8 @@
#pragma once
#include "Exporter.h"
class JSONExporter : public Exporter {
public:
void exportShapes(const boost::filesystem::path& inputFilePath, const ContinuousTimeline<Shape>& shapes, std::ostream& outputStream) override;
};

View File

@ -0,0 +1,13 @@
#include "TsvExporter.h"
void TSVExporter::exportShapes(const boost::filesystem::path& inputFilePath, const ContinuousTimeline<Shape>& shapes, std::ostream& outputStream) {
UNUSED(inputFilePath);
// Output shapes with start times
for (auto& timedShape : shapes) {
outputStream << formatDuration(timedShape.getStart()) << "\t" << timedShape.getValue() << "\n";
}
// Output closed mouth with end time
outputStream << formatDuration(shapes.getRange().getEnd()) << "\t" << Shape::X << "\n";
}

View File

@ -0,0 +1,9 @@
#pragma once
#include "Exporter.h"
class TSVExporter : public Exporter {
public:
void exportShapes(const boost::filesystem::path& inputFilePath, const ContinuousTimeline<Shape>& shapes, std::ostream& outputStream) override;
};

View File

@ -0,0 +1,29 @@
#include "XmlExporter.h"
#include <boost/property_tree/ptree.hpp>
#include <boost/property_tree/xml_parser.hpp>
#include "exporterTools.h"
using std::string;
using boost::property_tree::ptree;
void XMLExporter::exportShapes(const boost::filesystem::path& inputFilePath, const ContinuousTimeline<Shape>& shapes, std::ostream& outputStream) {
ptree tree;
// Add metadata
tree.put("rhubarbResult.metadata.soundFile", inputFilePath.string());
tree.put("rhubarbResult.metadata.duration", formatDuration(shapes.getRange().getDuration()));
// Add mouth cues
for (auto& timedShape : dummyShapeIfEmpty(shapes)) {
ptree& mouthCueElement = tree.add("rhubarbResult.mouthCues.mouthCue", timedShape.getValue());
mouthCueElement.put("<xmlattr>.start", formatDuration(timedShape.getStart()));
mouthCueElement.put("<xmlattr>.end", formatDuration(timedShape.getEnd()));
}
#if BOOST_VERSION < 105600 // Support legacy syntax
using writer_setting = boost::property_tree::xml_writer_settings<char>;
#else
using writer_setting = boost::property_tree::xml_writer_settings<string>;
#endif
write_xml(outputStream, tree, writer_setting(' ', 2));
}

View File

@ -0,0 +1,8 @@
#pragma once
#include "Exporter.h"
class XMLExporter : public Exporter {
public:
void exportShapes(const boost::filesystem::path& inputFilePath, const ContinuousTimeline<Shape>& shapes, std::ostream& outputStream) override;
};

View File

@ -0,0 +1,12 @@
#include "exporterTools.h"
// Makes sure there is at least one mouth shape
std::vector<Timed<Shape>> dummyShapeIfEmpty(const Timeline<Shape>& shapes) {
std::vector<Timed<Shape>> result;
std::copy(shapes.begin(), shapes.end(), std::back_inserter(result));
if (result.empty()) {
// Add zero-length empty mouth
result.push_back(Timed<Shape>(0_cs, 0_cs, Shape::A));
}
return result;
}

View File

@ -0,0 +1,7 @@
#pragma once
#include "Shape.h"
#include "Timeline.h"
// Makes sure there is at least one mouth shape
std::vector<Timed<Shape>> dummyShapeIfEmpty(const Timeline<Shape>& shapes);

View File

@ -16,6 +16,10 @@
#include "exceptions.h" #include "exceptions.h"
#include "textFiles.h" #include "textFiles.h"
#include "rhubarbLib.h" #include "rhubarbLib.h"
#include "ExportFormat.h"
#include "TsvExporter.h"
#include "XmlExporter.h"
#include "JsonExporter.h"
using std::exception; using std::exception;
using std::string; using std::string;