Merge branch 'feature/69-moho' into develop
This commit is contained in:
commit
460378bf50
|
@ -2,6 +2,7 @@
|
|||
|
||||
## 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.
|
||||
|
||||
## 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:
|
||||
|
||||
* *Adobe After Effects* (see <<afterEffects,below>>)
|
||||
* *Moho* and *OpenToonz* (see <<moho,below>>)
|
||||
* *Spine* by Esoteric Software (see <<spine,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])
|
||||
|
@ -44,6 +45,13 @@ You can use Rhubarb Lip Sync to animate dialog right from Adobe After Effects. F
|
|||
|
||||
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 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:
|
||||
|
||||
[cols="2,5"]
|
||||
[cols="2,5a"]
|
||||
|===
|
||||
| Option | Description
|
||||
|
||||
|
@ -136,7 +144,7 @@ The following command-line options are the most common:
|
|||
_Default value: ``pocketSphinx``_
|
||||
|
||||
| `-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``_
|
||||
|
||||
|
@ -161,6 +169,33 @@ _Default value: ``GHX``_
|
|||
|
||||
| `-h`, `--help`
|
||||
| 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 ====
|
||||
|
|
Binary file not shown.
After Width: | Height: | Size: 106 KiB |
|
@ -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
|
||||
|
|
|
@ -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() {
|
||||
return member_data {
|
||||
{ ExportFormat::Dat, "dat" },
|
||||
{ ExportFormat::Tsv, "tsv" },
|
||||
{ ExportFormat::Xml, "xml" },
|
||||
{ ExportFormat::Json, "json" }
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
#include "tools/EnumConverter.h"
|
||||
|
||||
enum class ExportFormat {
|
||||
Dat,
|
||||
Tsv,
|
||||
Xml,
|
||||
Json
|
||||
|
|
|
@ -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<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) {
|
||||
case ExportFormat::Dat:
|
||||
return make_unique<DatExporter>(targetShapeSet, datFrameRate, datUsePrestonBlair);
|
||||
case ExportFormat::Tsv:
|
||||
return make_unique<TsvExporter>();
|
||||
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<double> datFrameRate(
|
||||
"", "datFrameRate", "Only for dat exporter: the desired frame rate.",
|
||||
false, 24.0, "number", cmd
|
||||
);
|
||||
|
||||
auto exportFormats = vector<ExportFormat>(ExportFormatConverter::get().getValues());
|
||||
tclap::ValuesConstraint<ExportFormat> exportFormatConstraint(exportFormats);
|
||||
tclap::ValueArg<ExportFormat> exportFormat(
|
||||
|
@ -222,6 +240,13 @@ int main(int platformArgc, char* platformArgv[]) {
|
|||
path inputFilePath(inputFileName.getValue());
|
||||
ShapeSet targetShapeSet = getTargetShapeSet(extendedShapes.getValue());
|
||||
|
||||
unique_ptr<Exporter> 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> exporter = createExporter(exportFormat.getValue());
|
||||
optional<boost::filesystem::ofstream> outputFile;
|
||||
if (outputFileName.isSet()) {
|
||||
outputFile = boost::in_place(outputFileName.getValue());
|
||||
|
|
Loading…
Reference in New Issue