Merge branch 'feature/69-moho' into develop
This commit is contained in:
commit
460378bf50
|
@ -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
|
||||||
|
|
39
README.adoc
39
README.adoc
|
@ -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 ====
|
||||||
|
|
Binary file not shown.
After Width: | Height: | Size: 106 KiB |
|
@ -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
|
||||||
|
|
|
@ -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());
|
||||||
|
}
|
|
@ -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;
|
||||||
|
};
|
|
@ -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" }
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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());
|
||||||
|
|
Loading…
Reference in New Issue