Merge branch 'feature/69-moho' into develop

This commit is contained in:
Daniel Wolf 2019-07-03 21:08:02 +02:00
commit 460378bf50
9 changed files with 161 additions and 4 deletions

View File

@ -2,6 +2,7 @@
## Unreleased ## Unreleased
* **Added** switch data file exporter for Moho (formerly Anime Studio) and OpenToonz ([issue #69](https://github.com/DanielSWolf/rhubarb-lip-sync/issues/69))
* **Improved** animation rule for OW sound: animating it as E-F rather than F. * **Improved** animation rule for OW sound: animating it as E-F rather than F.
## Version 1.9.1 ## Version 1.9.1

View File

@ -23,6 +23,7 @@ Rhubarb Lip Sync allows you to quickly create 2D mouth animation from voice reco
Rhubarb Lip Sync integrates with the following applications: Rhubarb Lip Sync integrates with the following applications:
* *Adobe After Effects* (see <<afterEffects,below>>) * *Adobe After Effects* (see <<afterEffects,below>>)
* *Moho* and *OpenToonz* (see <<moho,below>>)
* *Spine* by Esoteric Software (see <<spine,below>>) * *Spine* by Esoteric Software (see <<spine,below>>)
* *Vegas Pro* by Magix (see <<vegas,below>>) * *Vegas Pro* by Magix (see <<vegas,below>>)
* *Visionaire Studio* (see https://www.visionaire-studio.net/forum/thread/mouth-animation-using-rhubarb-lip-sync[external link]) * *Visionaire Studio* (see https://www.visionaire-studio.net/forum/thread/mouth-animation-using-rhubarb-lip-sync[external link])
@ -44,6 +45,13 @@ You can use Rhubarb Lip Sync to animate dialog right from Adobe After Effects. F
image:img/after-effects.png[] image:img/after-effects.png[]
[[moho]]
=== Moho and OpenToonz
Rhubarb Lip Sync can create .dat switch data files, which are understood by Moho and OpenToonz. You can set the frame rate using the `--datFrameRate` option; to control the shape names, use the `--datUsePrestonBlair` flag. For more details, see <<options>>.
image:img/moho.png[]
[[spine]] [[spine]]
=== Spine by Esoteric Software === Spine by Esoteric Software
@ -123,7 +131,7 @@ Rhubarb Lip Sync is a command-line tool that is currently available for Windows
The following command-line options are the most common: The following command-line options are the most common:
[cols="2,5"] [cols="2,5a"]
|=== |===
| Option | Description | Option | Description
@ -136,7 +144,7 @@ The following command-line options are the most common:
_Default value: ``pocketSphinx``_ _Default value: ``pocketSphinx``_
| `-f` _<format>_, `--exportFormat` _<format>_ | `-f` _<format>_, `--exportFormat` _<format>_
| The export format. Options: `tsv` (tab-separated values, see <<tsv,details>>), `xml` (see <<xml,details>>), `json` (see <<json,details>>). | The export format. Options: `tsv` (tab-separated values, see <<tsv,details>>), `xml` (see <<xml,details>>), `json` (see <<json,details>>), `dat` (see <<moho>>).
_Default value: ``tsv``_ _Default value: ``tsv``_
@ -161,6 +169,33 @@ _Default value: ``GHX``_
| `-h`, `--help` | `-h`, `--help`
| Displays usage information and exits. | Displays usage information and exits.
| `--datFrameRate` _number_
| Only valid when using the `dat` export format. Controls the frame rate for the output file.
_Default value: 24_
| `--datUsePrestonBlair`
| Only valid when using the `dat` export format. Uses Preston Blair mouth shapes names instead of the default alphabetical ones. This applies the following mapping:
!===
! Alphabetic name ! Preston Blair name
! A ! MBP
! B ! etc
! C ! E
! D ! AI
! E ! O
! F ! U
! G ! FV
! H ! L
! X ! rest
!===
CAUTION: This mapping is only applied when exporting, _after_ the recording has been animated. To control which mouth shapes to use, use the <<extendedShapes,`extendedShapes`>> option _with the alphabetic names_.
TIP: For optimal results, make sure your mouth drawings follow the guidelines in the <<mouth-shapes>> section. This is easier if you stick to the alphabetic names instead of the Preston Blair names. The only situation where you _need_ to use the Preston Blair names is when you're using OpenToonz, because OpenToonz only supports the Preston Blair names.
|=== |===
==== Advanced command-line options ==== ==== Advanced command-line options ====

BIN
img/moho.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 106 KiB

View File

@ -360,6 +360,8 @@ target_link_libraries(rhubarb-core
# ... rhubarb-exporters # ... rhubarb-exporters
add_library(rhubarb-exporters add_library(rhubarb-exporters
src/exporters/DatExporter.cpp
src/exporters/DatExporter.h
src/exporters/Exporter.h src/exporters/Exporter.h
src/exporters/exporterTools.cpp src/exporters/exporterTools.cpp
src/exporters/exporterTools.h src/exporters/exporterTools.h

View File

@ -0,0 +1,72 @@
#include "DatExporter.h"
#include "animation/targetShapeSet.h"
#include <boost/lexical_cast.hpp>
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<std::string>(shape);
}
int DatExporter::toFrameNumber(centiseconds time) const {
return 1 + static_cast<int>(frameRate * duration_cast<duration<double>>(time).count());
}

View File

@ -0,0 +1,21 @@
#pragma once
#include "Exporter.h"
#include "core/Shape.h"
#include <map>
#include <string>
// 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<Shape, std::string> prestonBlairShapeNames;
};

View File

@ -13,6 +13,7 @@ string ExportFormatConverter::getTypeName() {
EnumConverter<ExportFormat>::member_data ExportFormatConverter::getMemberData() { EnumConverter<ExportFormat>::member_data ExportFormatConverter::getMemberData() {
return member_data { return member_data {
{ ExportFormat::Dat, "dat" },
{ ExportFormat::Tsv, "tsv" }, { ExportFormat::Tsv, "tsv" },
{ ExportFormat::Xml, "xml" }, { ExportFormat::Xml, "xml" },
{ ExportFormat::Json, "json" } { ExportFormat::Json, "json" }

View File

@ -3,6 +3,7 @@
#include "tools/EnumConverter.h" #include "tools/EnumConverter.h"
enum class ExportFormat { enum class ExportFormat {
Dat,
Tsv, Tsv,
Xml, Xml,
Json Json

View File

@ -18,6 +18,7 @@
#include "tools/textFiles.h" #include "tools/textFiles.h"
#include "lib/rhubarbLib.h" #include "lib/rhubarbLib.h"
#include "ExportFormat.h" #include "ExportFormat.h"
#include "exporters/DatExporter.h"
#include "exporters/TsvExporter.h" #include "exporters/TsvExporter.h"
#include "exporters/XmlExporter.h" #include "exporters/XmlExporter.h"
#include "exporters/JsonExporter.h" #include "exporters/JsonExporter.h"
@ -82,8 +83,15 @@ unique_ptr<Recognizer> createRecognizer(RecognizerType recognizerType) {
} }
} }
unique_ptr<Exporter> createExporter(ExportFormat exportFormat) { unique_ptr<Exporter> createExporter(
ExportFormat exportFormat,
const ShapeSet& targetShapeSet,
double datFrameRate,
bool datUsePrestonBlair
) {
switch (exportFormat) { switch (exportFormat) {
case ExportFormat::Dat:
return make_unique<DatExporter>(targetShapeSet, datFrameRate, datUsePrestonBlair);
case ExportFormat::Tsv: case ExportFormat::Tsv:
return make_unique<TsvExporter>(); return make_unique<TsvExporter>();
case ExportFormat::Xml: case ExportFormat::Xml:
@ -172,6 +180,16 @@ int main(int platformArgc, char* platformArgv[]) {
false, string(), "string", cmd false, string(), "string", cmd
); );
tclap::SwitchArg datUsePrestonBlair(
"", "datUsePrestonBlair", "Only for dat exporter: uses the Preston Blair mouth shape names.",
cmd, false
);
tclap::ValueArg<double> datFrameRate(
"", "datFrameRate", "Only for dat exporter: the desired frame rate.",
false, 24.0, "number", cmd
);
auto exportFormats = vector<ExportFormat>(ExportFormatConverter::get().getValues()); auto exportFormats = vector<ExportFormat>(ExportFormatConverter::get().getValues());
tclap::ValuesConstraint<ExportFormat> exportFormatConstraint(exportFormats); tclap::ValuesConstraint<ExportFormat> exportFormatConstraint(exportFormats);
tclap::ValueArg<ExportFormat> exportFormat( tclap::ValueArg<ExportFormat> exportFormat(
@ -222,6 +240,13 @@ int main(int platformArgc, char* platformArgv[]) {
path inputFilePath(inputFileName.getValue()); path inputFilePath(inputFileName.getValue());
ShapeSet targetShapeSet = getTargetShapeSet(extendedShapes.getValue()); ShapeSet targetShapeSet = getTargetShapeSet(extendedShapes.getValue());
unique_ptr<Exporter> exporter = createExporter(
exportFormat.getValue(),
targetShapeSet,
datFrameRate.getValue(),
datUsePrestonBlair.getValue()
);
logging::log(StartEntry(inputFilePath)); logging::log(StartEntry(inputFilePath));
logging::debugFormat("Command line: {}", logging::debugFormat("Command line: {}",
join(args | transformed([](string arg) { return fmt::format("\"{}\"", arg); }), " ")); join(args | transformed([](string arg) { return fmt::format("\"{}\"", arg); }), " "));
@ -246,7 +271,6 @@ int main(int platformArgc, char* platformArgv[]) {
logging::info("Done animating."); logging::info("Done animating.");
// Export animation // Export animation
unique_ptr<Exporter> exporter = createExporter(exportFormat.getValue());
optional<boost::filesystem::ofstream> outputFile; optional<boost::filesystem::ofstream> outputFile;
if (outputFileName.isSet()) { if (outputFileName.isSet()) {
outputFile = boost::in_place(outputFileName.getValue()); outputFile = boost::in_place(outputFileName.getValue());