Script runs on Windows and OS X

This commit is contained in:
Daniel Wolf 2017-07-01 23:12:19 +02:00
parent 8093258e76
commit 2f1586624a
1 changed files with 81 additions and 46 deletions

View File

@ -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();