Restructured rhubarb-exporters
This commit is contained in:
parent
3e34425c11
commit
289b7ba56e
|
@ -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
|
||||||
|
|
|
@ -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);
|
||||||
|
}
|
|
@ -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);
|
|
@ -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";
|
|
||||||
}
|
|
|
@ -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;
|
|
||||||
};
|
|
||||||
|
|
|
@ -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";
|
||||||
|
}
|
|
@ -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;
|
||||||
|
};
|
|
@ -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";
|
||||||
|
}
|
|
@ -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;
|
||||||
|
};
|
||||||
|
|
|
@ -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));
|
||||||
|
}
|
|
@ -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;
|
||||||
|
};
|
|
@ -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;
|
||||||
|
}
|
|
@ -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);
|
|
@ -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;
|
||||||
|
|
Loading…
Reference in New Issue