Script runs on Windows and OS X
This commit is contained in:
parent
8093258e76
commit
2f1586624a
|
@ -95,7 +95,7 @@ var epsilon = 0.001;
|
||||||
|
|
||||||
function isFrameVisible(compItem, frameNumber) {
|
function isFrameVisible(compItem, frameNumber) {
|
||||||
if (!compItem) return false;
|
if (!compItem) return false;
|
||||||
|
|
||||||
var time = frameToTime(frameNumber + epsilon, compItem);
|
var time = frameToTime(frameNumber + epsilon, compItem);
|
||||||
var videoLayers = toArrayBase1(compItem.layers).filter(function(layer) {
|
var videoLayers = toArrayBase1(compItem.layers).filter(function(layer) {
|
||||||
return layer.hasVideo;
|
return layer.hasVideo;
|
||||||
|
@ -106,8 +106,7 @@ function isFrameVisible(compItem, frameNumber) {
|
||||||
return Boolean(result);
|
return Boolean(result);
|
||||||
}
|
}
|
||||||
|
|
||||||
// On Windows, this is C:\ProgramData
|
var settingsFilePath = Folder.userData.fullName + '/rhubarb-ae-settings.json';
|
||||||
var settingsFilePath = Folder.appData.fullName + '/rhubarb-ae-settings.json';
|
|
||||||
|
|
||||||
function readTextFile(fileOrPath) {
|
function readTextFile(fileOrPath) {
|
||||||
var filePath = fileOrPath.fsName || fileOrPath;
|
var filePath = fileOrPath.fsName || fileOrPath;
|
||||||
|
@ -156,17 +155,51 @@ function writeSettingsFile(settings) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function exec(command, options) {
|
var osIsWindows = (system.osName || $.os).match(/windows/i);
|
||||||
var showWindow = (options || {}).showWindow;
|
|
||||||
var osIsWindows = (system.osName || $.os).match(/windows/i);
|
// Depending on the operating system, the syntax for escaping command-line arguments differs.
|
||||||
|
function cliEscape(argument) {
|
||||||
// On Windows, calling a console application directly will hide the console window. Calling it
|
return osIsWindows
|
||||||
// through cmd.exe will show it.
|
? '"' + argument + '"'
|
||||||
// I don't know whether there's something similar for OS X. I only own the Windows version of
|
: "'" + argument.replace(/'/g, "'\\''") + "'";
|
||||||
// After Effects.
|
|
||||||
return system.callSystem(showWindow && osIsWindows ? 'cmd /C "' + command + '"' : command);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function exec(command) {
|
||||||
|
return system.callSystem(command);
|
||||||
|
}
|
||||||
|
|
||||||
|
function execInWindow(command) {
|
||||||
|
if (osIsWindows) {
|
||||||
|
system.callSystem('cmd /C "' + command + '"');
|
||||||
|
} else {
|
||||||
|
// I didn't think it could be so complicated on OS X to open a new Terminal window,
|
||||||
|
// execute a command, then close the Terminal window.
|
||||||
|
// If you know a better solution, let me know!
|
||||||
|
var escapedCommand = command.replace(/"/g, '\\"');
|
||||||
|
var appleScript = '\
|
||||||
|
tell application "Terminal" \
|
||||||
|
-- Quit terminal \
|
||||||
|
-- Yes, that\'s undesirable if there was an open window before. \
|
||||||
|
-- But all solutions I could find were at least as hacky. \
|
||||||
|
quit \
|
||||||
|
-- Open terminal \
|
||||||
|
activate \
|
||||||
|
-- Run command in new tab \
|
||||||
|
set newTab to do script ("' + escapedCommand + '") \
|
||||||
|
-- Wait until command is done \
|
||||||
|
tell newTab \
|
||||||
|
repeat while busy \
|
||||||
|
delay 0.1 \
|
||||||
|
end repeat \
|
||||||
|
end tell \
|
||||||
|
quit \
|
||||||
|
end tell';
|
||||||
|
exec('osascript -e ' + cliEscape(appleScript));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var rhubarbPath = osIsWindows ? 'rhubarb.exe' : '/usr/local/bin/rhubarb';
|
||||||
|
|
||||||
// ExtendScript's resource strings are a pain to write.
|
// ExtendScript's resource strings are a pain to write.
|
||||||
// This function allows them to be written in JSON notation, then converts them into the required
|
// This function allows them to be written in JSON notation, then converts them into the required
|
||||||
// format.
|
// format.
|
||||||
|
@ -208,7 +241,7 @@ function getItemPath(item) {
|
||||||
if (item === app.project.rootFolder) {
|
if (item === app.project.rootFolder) {
|
||||||
return '/';
|
return '/';
|
||||||
}
|
}
|
||||||
|
|
||||||
var result = item.name;
|
var result = item.name;
|
||||||
while (item.parentFolder !== app.project.rootFolder) {
|
while (item.parentFolder !== app.project.rootFolder) {
|
||||||
result = item.parentFolder.name + ' / ' + result;
|
result = item.parentFolder.name + ' / ' + result;
|
||||||
|
@ -235,7 +268,7 @@ function getWaveFileProjectItems() {
|
||||||
var result = toArrayBase1(app.project.items).filter(function(item) {
|
var result = toArrayBase1(app.project.items).filter(function(item) {
|
||||||
var isAudioFootage = item instanceof FootageItem && item.hasAudio && !item.hasVideo;
|
var isAudioFootage = item instanceof FootageItem && item.hasAudio && !item.hasVideo;
|
||||||
if (!isAudioFootage) return false;
|
if (!isAudioFootage) return false;
|
||||||
|
|
||||||
var isWaveFile = item.file && item.file.exists && item.file.name.match(/\.wav$/i);
|
var isWaveFile = item.file && item.file.exists && item.file.name.match(/\.wav$/i);
|
||||||
return isWaveFile;
|
return isWaveFile;
|
||||||
});
|
});
|
||||||
|
@ -363,13 +396,13 @@ function createDialogWindow() {
|
||||||
controls['mouthShape' + shapeName] =
|
controls['mouthShape' + shapeName] =
|
||||||
window.settings.extendedMouthShapes[shapeName.toLowerCase()];
|
window.settings.extendedMouthShapes[shapeName.toLowerCase()];
|
||||||
});
|
});
|
||||||
|
|
||||||
// Add audio file options
|
// Add audio file options
|
||||||
getWaveFileProjectItems().forEach(function(projectItem) {
|
getWaveFileProjectItems().forEach(function(projectItem) {
|
||||||
var listItem = controls.audioFile.add('item', getItemPath(projectItem));
|
var listItem = controls.audioFile.add('item', getItemPath(projectItem));
|
||||||
listItem.projectItem = projectItem;
|
listItem.projectItem = projectItem;
|
||||||
});
|
});
|
||||||
|
|
||||||
// Add mouth composition options
|
// Add mouth composition options
|
||||||
var comps = toArrayBase1(app.project.items).filter(function (item) {
|
var comps = toArrayBase1(app.project.items).filter(function (item) {
|
||||||
return item instanceof CompItem;
|
return item instanceof CompItem;
|
||||||
|
@ -396,7 +429,7 @@ function createDialogWindow() {
|
||||||
selectByTextOrFirst(controls.mouthComp, settings.mouthComp);
|
selectByTextOrFirst(controls.mouthComp, settings.mouthComp);
|
||||||
extendedMouthShapeNames.forEach(function(shapeName) {
|
extendedMouthShapeNames.forEach(function(shapeName) {
|
||||||
controls['mouthShape' + shapeName].value =
|
controls['mouthShape' + shapeName].value =
|
||||||
settings.extendedMouthShapes[shapeName.toLowerCase()];
|
(settings.extendedMouthShapes || {})[shapeName.toLowerCase()];
|
||||||
});
|
});
|
||||||
selectByTextOrFirst(controls.targetFolder, settings.targetFolder);
|
selectByTextOrFirst(controls.targetFolder, settings.targetFolder);
|
||||||
controls.frameRate.text = settings.frameRate || '';
|
controls.frameRate.text = settings.frameRate || '';
|
||||||
|
@ -431,7 +464,7 @@ function createDialogWindow() {
|
||||||
|
|
||||||
function update() {
|
function update() {
|
||||||
if (updating) return;
|
if (updating) return;
|
||||||
|
|
||||||
updating = true;
|
updating = true;
|
||||||
try {
|
try {
|
||||||
// Handle auto frame rate
|
// Handle auto frame rate
|
||||||
|
@ -448,7 +481,7 @@ function createDialogWindow() {
|
||||||
controls.frameRate.text = sanitizedFrameRate;
|
controls.frameRate.text = sanitizedFrameRate;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Store settings
|
// Store settings
|
||||||
var settings = {
|
var settings = {
|
||||||
audioFile: (controls.audioFile.selection || {}).text,
|
audioFile: (controls.audioFile.selection || {}).text,
|
||||||
|
@ -481,7 +514,7 @@ function createDialogWindow() {
|
||||||
if (Number(controls.frameRate.text) < 12) {
|
if (Number(controls.frameRate.text) < 12) {
|
||||||
return 'Please enter a frame rate of at least 12 fps.';
|
return 'Please enter a frame rate of at least 12 fps.';
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check mouth shape visibility
|
// Check mouth shape visibility
|
||||||
var comp = controls.mouthComp.selection.projectItem;
|
var comp = controls.mouthComp.selection.projectItem;
|
||||||
for (var i = 0; i < mouthShapeCount; i++) {
|
for (var i = 0; i < mouthShapeCount; i++) {
|
||||||
|
@ -492,7 +525,7 @@ function createDialogWindow() {
|
||||||
+ shapeName + ' at frame ' + i + '.';
|
+ shapeName + ' at frame ' + i + '.';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!comp.preserveNestedFrameRate) {
|
if (!comp.preserveNestedFrameRate) {
|
||||||
var fix = Window.confirm(
|
var fix = Window.confirm(
|
||||||
'The setting "Preserve frame rate when nested or in render queue" is not active '
|
'The setting "Preserve frame rate when nested or in render queue" is not active '
|
||||||
|
@ -508,22 +541,24 @@ function createDialogWindow() {
|
||||||
return '';
|
return '';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check for correct Rhubarb version
|
// Check for correct Rhubarb version
|
||||||
var version = exec('rhubarb --version', { showWindow: false }) || '';
|
var version = exec(rhubarbPath + ' --version') || '';
|
||||||
var match = version.match(/Rhubarb Lip Sync version ((\d+)\.(\d+).(\d+))/);
|
var match = version.match(/Rhubarb Lip Sync version ((\d+)\.(\d+).(\d+))/);
|
||||||
if (!match) {
|
if (!match) {
|
||||||
var isWindows = (system.osName || $.os).match(/windows/i);
|
var instructions = osIsWindows
|
||||||
return 'Cannot find executable file "' + (isWindows ? 'rhubarb.exe' : 'rhubarb') + '". '
|
? 'Make sure your PATH environment variable contains the Rhubarb Lip-Sync '
|
||||||
+ 'Make sure your PATH environment variable contains the Rhubarb Lip-Sync '
|
+ 'application directory.'
|
||||||
+ 'application directory.';
|
: 'Make sure you have created this file as a symbolic link to the Rhubarb Lip-Sync '
|
||||||
|
+ 'executable (rhubarb).';
|
||||||
|
return 'Cannot find executable file "' + rhubarbPath + '". \n' + instructions;
|
||||||
}
|
}
|
||||||
var versionString = match[1];
|
var versionString = match[1];
|
||||||
var major = Number(match[2]);
|
var major = Number(match[2]);
|
||||||
var minor = Number(match[3]);
|
var minor = Number(match[3]);
|
||||||
if (major != 1 || minor < 6) {
|
if (major != 1 || minor < 6) {
|
||||||
return 'This script requires Rhubarb Lip-Sync 1.6.0 or a later 1.x version. '
|
return 'This script requires Rhubarb Lip-Sync 1.6.0 or a later 1.x version. '
|
||||||
'Your installed version is ' + versionString + ', which is not compatible.';
|
+ 'Your installed version is ' + versionString + ', which is not compatible.';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -537,20 +572,20 @@ function createDialogWindow() {
|
||||||
try {
|
try {
|
||||||
// Create text file containing dialog
|
// Create text file containing dialog
|
||||||
writeTextFile(dialogFile, dialogText);
|
writeTextFile(dialogFile, dialogText);
|
||||||
|
|
||||||
// Create command line
|
// Create command line
|
||||||
var commandLine = 'rhubarb'
|
var commandLine = rhubarbPath
|
||||||
+ ' --dialogFile "' + dialogFile.fsName + '"'
|
+ ' --dialogFile ' + cliEscape(dialogFile.fsName)
|
||||||
+ ' --exportFormat json'
|
+ ' --exportFormat json'
|
||||||
+ ' --extendedShapes "' + extendedMouthShapeNames.join('') + '"'
|
+ ' --extendedShapes ' + cliEscape(extendedMouthShapeNames.join(''))
|
||||||
+ ' --logFile "' + logFile.fsName + '"'
|
+ ' --logFile ' + cliEscape(logFile.fsName)
|
||||||
+ ' --logLevel fatal'
|
+ ' --logLevel fatal'
|
||||||
+ ' --output "' + jsonFile.fsName + '"'
|
+ ' --output ' + cliEscape(jsonFile.fsName)
|
||||||
+ ' "' + audioFileFootage.file.fsName + '"';
|
+ ' ' + cliEscape(audioFileFootage.file.fsName);
|
||||||
|
|
||||||
// Run Rhubarb
|
// Run Rhubarb
|
||||||
exec(commandLine, { showWindow: true });
|
execInWindow(commandLine);
|
||||||
|
|
||||||
// Check log for fatal errors
|
// Check log for fatal errors
|
||||||
if (logFile.exists) {
|
if (logFile.exists) {
|
||||||
var fatalLog = readTextFile(logFile).trim();
|
var fatalLog = readTextFile(logFile).trim();
|
||||||
|
@ -561,7 +596,7 @@ function createDialogWindow() {
|
||||||
throw new Error('Error running Rhubarb Lip-Sync.\n' + message);
|
throw new Error('Error running Rhubarb Lip-Sync.\n' + message);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var result;
|
var result;
|
||||||
try {
|
try {
|
||||||
result = JSON.parse(readTextFile(jsonFile));
|
result = JSON.parse(readTextFile(jsonFile));
|
||||||
|
@ -591,22 +626,22 @@ function createDialogWindow() {
|
||||||
counter++;
|
counter++;
|
||||||
compName = baseName + ' ' + counter;
|
compName = baseName + ' ' + counter;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create new comp
|
// Create new comp
|
||||||
var comp = targetProjectFolder.items.addComp(compName, mouthComp.width, mouthComp.height,
|
var comp = targetProjectFolder.items.addComp(compName, mouthComp.width, mouthComp.height,
|
||||||
mouthComp.pixelAspect, audioFileFootage.duration, frameRate);
|
mouthComp.pixelAspect, audioFileFootage.duration, frameRate);
|
||||||
|
|
||||||
// Show new comp
|
// Show new comp
|
||||||
comp.openInViewer();
|
comp.openInViewer();
|
||||||
|
|
||||||
// Add audio layer
|
// Add audio layer
|
||||||
comp.layers.add(audioFileFootage);
|
comp.layers.add(audioFileFootage);
|
||||||
|
|
||||||
// Add mouth layer
|
// Add mouth layer
|
||||||
var mouthLayer = comp.layers.add(mouthComp);
|
var mouthLayer = comp.layers.add(mouthComp);
|
||||||
mouthLayer.timeRemapEnabled = true;
|
mouthLayer.timeRemapEnabled = true;
|
||||||
mouthLayer.outPoint = comp.duration;
|
mouthLayer.outPoint = comp.duration;
|
||||||
|
|
||||||
// Animate mouth layer
|
// Animate mouth layer
|
||||||
var timeRemap = mouthLayer['Time Remap'];
|
var timeRemap = mouthLayer['Time Remap'];
|
||||||
// Enabling time remapping automatically adds two keys. Remove the second.
|
// Enabling time remapping automatically adds two keys. Remove the second.
|
||||||
|
@ -654,7 +689,7 @@ function createDialogWindow() {
|
||||||
controls.targetFolder.onChange = update;
|
controls.targetFolder.onChange = update;
|
||||||
controls.frameRate.onChanging = update;
|
controls.frameRate.onChanging = update;
|
||||||
controls.autoFrameRate.onClick = update;
|
controls.autoFrameRate.onClick = update;
|
||||||
|
|
||||||
// Handle animation
|
// Handle animation
|
||||||
controls.animateButton.onClick = function() {
|
controls.animateButton.onClick = function() {
|
||||||
var validationError = validate();
|
var validationError = validate();
|
||||||
|
@ -676,7 +711,7 @@ function createDialogWindow() {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// Handle cancelation
|
// Handle cancelation
|
||||||
controls.cancelButton.onClick = function() {
|
controls.cancelButton.onClick = function() {
|
||||||
window.close();
|
window.close();
|
||||||
|
|
Loading…
Reference in New Issue