From b365c4c1d590839c80f1cc0c445111909bc639ea Mon Sep 17 00:00:00 2001 From: Daniel Wolf Date: Mon, 9 Dec 2024 08:31:59 +0100 Subject: [PATCH] Indent code files with spaces rather than tabs --- CMakeLists.txt | 14 +- extras/AdobeAfterEffects/CMakeLists.txt | 8 +- extras/AdobeAfterEffects/Rhubarb Lip Sync.jsx | 1204 +++++++++-------- extras/EsotericSoftwareSpine/CMakeLists.txt | 16 +- .../src/main/kotlin/AnimationFileModel.kt | 182 +-- .../src/main/kotlin/AudioFileModel.kt | 308 ++--- .../src/main/kotlin/ErrorProperty.kt | 108 +- .../src/main/kotlin/MainApp.kt | 22 +- .../src/main/kotlin/MainModel.kt | 70 +- .../src/main/kotlin/MainView.kt | 446 +++--- .../src/main/kotlin/MouthNaming.kt | 80 +- .../src/main/kotlin/MouthShape.kt | 20 +- .../src/main/kotlin/RhubarbTask.kt | 278 ++-- .../src/main/kotlin/SpineJson.kt | 256 ++-- .../src/main/kotlin/classLocation.kt | 92 +- .../src/main/kotlin/main.kt | 2 +- .../src/main/kotlin/tools.kt | 80 +- .../src/test/kotlin/SpineJsonTest.kt | 108 +- extras/MagixVegas/CMakeLists.txt | 14 +- extras/MagixVegas/Debug Rhubarb.cs | 534 ++++---- extras/MagixVegas/Import Rhubarb.cs | 356 ++--- rhubarb/CMakeLists.txt | 672 ++++----- rhubarb/src/animation/ShapeRule.cpp | 98 +- rhubarb/src/animation/ShapeRule.h | 16 +- rhubarb/src/animation/animationRules.cpp | 250 ++-- rhubarb/src/animation/animationRules.h | 12 +- rhubarb/src/animation/mouthAnimation.cpp | 48 +- rhubarb/src/animation/mouthAnimation.h | 4 +- rhubarb/src/animation/pauseAnimation.cpp | 72 +- rhubarb/src/animation/roughAnimation.cpp | 76 +- rhubarb/src/animation/staticSegments.cpp | 322 ++--- rhubarb/src/animation/staticSegments.h | 4 +- rhubarb/src/animation/targetShapeSet.cpp | 66 +- rhubarb/src/animation/targetShapeSet.h | 8 +- rhubarb/src/animation/timingOptimization.cpp | 386 +++--- rhubarb/src/animation/tweening.cpp | 84 +- rhubarb/src/audio/AudioClip.cpp | 64 +- rhubarb/src/audio/AudioClip.h | 110 +- rhubarb/src/audio/AudioSegment.cpp | 26 +- rhubarb/src/audio/AudioSegment.h | 18 +- rhubarb/src/audio/DcOffset.cpp | 88 +- rhubarb/src/audio/DcOffset.h | 20 +- rhubarb/src/audio/OggVorbisFileReader.cpp | 216 +-- rhubarb/src/audio/OggVorbisFileReader.h | 18 +- rhubarb/src/audio/SampleRateConverter.cpp | 84 +- rhubarb/src/audio/SampleRateConverter.h | 22 +- rhubarb/src/audio/WaveFileReader.cpp | 876 ++++++------ rhubarb/src/audio/WaveFileReader.h | 42 +- rhubarb/src/audio/audioFileReading.cpp | 32 +- rhubarb/src/audio/ioTools.h | 66 +- rhubarb/src/audio/processing.cpp | 72 +- rhubarb/src/audio/processing.h | 14 +- rhubarb/src/audio/voiceActivityDetection.cpp | 122 +- rhubarb/src/audio/voiceActivityDetection.h | 4 +- rhubarb/src/audio/waveFileWriting.cpp | 64 +- rhubarb/src/core/Phone.cpp | 128 +- rhubarb/src/core/Phone.h | 128 +- rhubarb/src/core/Shape.cpp | 64 +- rhubarb/src/core/Shape.h | 40 +- rhubarb/src/exporters/DatExporter.cpp | 94 +- rhubarb/src/exporters/DatExporter.h | 14 +- rhubarb/src/exporters/Exporter.h | 24 +- rhubarb/src/exporters/JsonExporter.cpp | 40 +- rhubarb/src/exporters/JsonExporter.h | 2 +- rhubarb/src/exporters/TsvExporter.cpp | 28 +- rhubarb/src/exporters/TsvExporter.h | 2 +- rhubarb/src/exporters/XmlExporter.cpp | 42 +- rhubarb/src/exporters/XmlExporter.h | 2 +- rhubarb/src/exporters/exporterTools.cpp | 18 +- rhubarb/src/exporters/exporterTools.h | 4 +- rhubarb/src/lib/rhubarbLib.cpp | 36 +- rhubarb/src/lib/rhubarbLib.h | 24 +- rhubarb/src/logging/Entry.cpp | 44 +- rhubarb/src/logging/Entry.h | 18 +- rhubarb/src/logging/Formatter.h | 10 +- rhubarb/src/logging/Level.cpp | 46 +- rhubarb/src/logging/Level.h | 36 +- rhubarb/src/logging/Sink.h | 10 +- rhubarb/src/logging/formatters.cpp | 22 +- rhubarb/src/logging/formatters.h | 20 +- rhubarb/src/logging/logging.cpp | 50 +- rhubarb/src/logging/logging.h | 42 +- rhubarb/src/logging/sinks.cpp | 40 +- rhubarb/src/logging/sinks.h | 42 +- .../src/recognition/PhoneticRecognizer.cpp | 158 +-- rhubarb/src/recognition/PhoneticRecognizer.h | 12 +- .../recognition/PocketSphinxRecognizer.cpp | 486 +++---- .../src/recognition/PocketSphinxRecognizer.h | 12 +- rhubarb/src/recognition/Recognizer.h | 14 +- rhubarb/src/recognition/g2p.cpp | 172 +-- rhubarb/src/recognition/languageModels.cpp | 262 ++-- rhubarb/src/recognition/languageModels.h | 4 +- rhubarb/src/recognition/pocketSphinxTools.cpp | 348 ++--- rhubarb/src/recognition/pocketSphinxTools.h | 26 +- rhubarb/src/recognition/tokenization.cpp | 178 +-- rhubarb/src/recognition/tokenization.h | 4 +- rhubarb/src/rhubarb/ExportFormat.cpp | 22 +- rhubarb/src/rhubarb/ExportFormat.h | 14 +- rhubarb/src/rhubarb/RecognizerType.cpp | 18 +- rhubarb/src/rhubarb/RecognizerType.h | 10 +- rhubarb/src/rhubarb/main.cpp | 422 +++--- rhubarb/src/rhubarb/semanticEntries.cpp | 22 +- rhubarb/src/rhubarb/semanticEntries.h | 22 +- rhubarb/src/rhubarb/sinks.cpp | 192 +-- rhubarb/src/rhubarb/sinks.h | 38 +- rhubarb/src/time/BoundedTimeline.h | 108 +- rhubarb/src/time/ContinuousTimeline.h | 64 +- rhubarb/src/time/TimeRange.cpp | 82 +- rhubarb/src/time/TimeRange.h | 56 +- rhubarb/src/time/Timed.h | 184 +-- rhubarb/src/time/Timeline.h | 536 ++++---- rhubarb/src/time/centiseconds.cpp | 6 +- rhubarb/src/time/centiseconds.h | 6 +- rhubarb/src/time/timedLogging.h | 26 +- rhubarb/src/tools/EnumConverter.h | 140 +- rhubarb/src/tools/Lazy.h | 82 +- rhubarb/src/tools/NiceCmdLineOutput.cpp | 116 +- rhubarb/src/tools/NiceCmdLineOutput.h | 14 +- rhubarb/src/tools/ObjectPool.h | 54 +- rhubarb/src/tools/ProgressBar.cpp | 110 +- rhubarb/src/tools/ProgressBar.h | 40 +- rhubarb/src/tools/TablePrinter.cpp | 86 +- rhubarb/src/tools/TablePrinter.h | 18 +- rhubarb/src/tools/array.h | 46 +- rhubarb/src/tools/exceptions.cpp | 14 +- rhubarb/src/tools/fileTools.cpp | 36 +- rhubarb/src/tools/nextCombination.h | 58 +- rhubarb/src/tools/pairs.h | 28 +- rhubarb/src/tools/parallel.h | 168 +-- rhubarb/src/tools/platformTools.cpp | 166 +-- rhubarb/src/tools/progress.cpp | 76 +- rhubarb/src/tools/progress.h | 40 +- rhubarb/src/tools/stringTools.cpp | 278 ++-- rhubarb/src/tools/stringTools.h | 40 +- rhubarb/src/tools/textFiles.cpp | 30 +- rhubarb/src/tools/tools.cpp | 18 +- rhubarb/src/tools/tools.h | 74 +- rhubarb/src/tools/tupleHash.h | 58 +- rhubarb/tests/BoundedTimelineTests.cpp | 168 +-- rhubarb/tests/ContinuousTimelineTests.cpp | 176 +-- rhubarb/tests/LazyTests.cpp | 96 +- rhubarb/tests/TimelineTests.cpp | 586 ++++---- rhubarb/tests/WaveFileReaderTests.cpp | 252 ++-- rhubarb/tests/g2pTests.cpp | 40 +- rhubarb/tests/pairsTests.cpp | 34 +- rhubarb/tests/stringToolsTests.cpp | 98 +- rhubarb/tests/tokenizationTests.cpp | 106 +- 147 files changed, 8098 insertions(+), 8096 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index c36c622..b6ada99 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -14,17 +14,17 @@ add_subdirectory("extras/EsotericSoftwareSpine") # Install misc. files install( - FILES README.adoc LICENSE.md CHANGELOG.md - DESTINATION . + FILES README.adoc LICENSE.md CHANGELOG.md + DESTINATION . ) # Configure CPack function(get_short_system_name variable) - if ("${CMAKE_SYSTEM_NAME}" STREQUAL "Darwin") - set(${variable} "macOS" PARENT_SCOPE) - else() - set(${variable} "${CMAKE_SYSTEM_NAME}" PARENT_SCOPE) - endif() + if ("${CMAKE_SYSTEM_NAME}" STREQUAL "Darwin") + set(${variable} "macOS" PARENT_SCOPE) + else() + set(${variable} "${CMAKE_SYSTEM_NAME}" PARENT_SCOPE) + endif() endfunction() set(CPACK_PACKAGE_NAME ${appName}) diff --git a/extras/AdobeAfterEffects/CMakeLists.txt b/extras/AdobeAfterEffects/CMakeLists.txt index 0e8283e..ee32c2b 100644 --- a/extras/AdobeAfterEffects/CMakeLists.txt +++ b/extras/AdobeAfterEffects/CMakeLists.txt @@ -1,11 +1,11 @@ cmake_minimum_required(VERSION 3.2) set(afterEffectsFiles - "Rhubarb Lip Sync.jsx" - "README.adoc" + "Rhubarb Lip Sync.jsx" + "README.adoc" ) install( - FILES ${afterEffectsFiles} - DESTINATION "extras/AdobeAfterEffects" + FILES ${afterEffectsFiles} + DESTINATION "extras/AdobeAfterEffects" ) diff --git a/extras/AdobeAfterEffects/Rhubarb Lip Sync.jsx b/extras/AdobeAfterEffects/Rhubarb Lip Sync.jsx index 6383b38..6ec29b5 100644 --- a/extras/AdobeAfterEffects/Rhubarb Lip Sync.jsx +++ b/extras/AdobeAfterEffects/Rhubarb Lip Sync.jsx @@ -1,109 +1,111 @@ -// Polyfill for Object.assign -"function"!=typeof Object.assign&&(Object.assign=function(a,b){"use strict";if(null==a)throw new TypeError("Cannot convert undefined or null to object");for(var c=Object(a),d=1;d>>0;if("function"!=typeof r)throw new TypeError(r+" is not a function");for(arguments.length>1&&(t=arguments[1]),n=new Array(i),o=0;o>>0;if("function"!=typeof r)throw new TypeError(r+" is not a function");for(arguments.length>1&&(t=arguments[1]),n=new Array(i),o=0;o>>0;if("function"!=typeof r)throw new TypeError;for(arguments.length>1&&(e=t),n=0;n>>0;if("function"!=typeof r)throw new TypeError;for(arguments.length>1&&(e=t),n=0;n>>0,o=arguments[1],e=0;e>>0,o=arguments[1],e=0;e>>0;if("function"!=typeof r)throw new TypeError;for(var i=[],o=arguments.length>=2?arguments[1]:void 0,n=0;n>>0;if("function"!=typeof r)throw new TypeError;for(var i=[],o=arguments.length>=2?arguments[1]:void 0,n=0;n>>0;if("function"!=typeof a)throw new TypeError(a+" is not a function");for(arguments.length>1&&(c=b),d=0;d>>0;if("function"!=typeof a)throw new TypeError(a+" is not a function");for(arguments.length>1&&(c=b),d=0;d>>0;if(0===n)return!1;for(var i=0|t,o=Math.max(i>=0?i:n-Math.abs(i),0);o>>0;if(0===n)return!1;for(var i=0|t,o=Math.max(i>=0?i:n-Math.abs(i),0);o>>0;if(0===i)return-1;var o=0|t;if(o>=i)return-1;for(n=Math.max(o>=0?o:i-Math.abs(o),0);n>>0;if(0===i)return-1;var o=0|t;if(o>=i)return-1;for(n=Math.max(o>=0?o:i-Math.abs(o),0);n>>0,t=arguments.length>=2?arguments[1]:void 0,n=0;n>>0,t=arguments.length>=2?arguments[1]:void 0,n=0;n= width ? n : new Array(width - n.length + 1).join(z) + n; + z = z || '0'; + n = String(n); + return n.length >= width ? n : new Array(width - n.length + 1).join(z) + n; } // Checks whether scripts are allowed to write files by creating and deleting a dummy file function canWriteFiles() { - try { - var file = new File(); - file.open('w'); - file.writeln(''); - file.close(); - file.remove(); - return true; - } catch (e) { - return false; - } + try { + var file = new File(); + file.open('w'); + file.writeln(''); + file.close(); + file.remove(); + return true; + } catch (e) { + return false; + } } function frameToTime(frameNumber, compItem) { - return frameNumber * compItem.frameDuration; + return frameNumber * compItem.frameDuration; } function timeToFrame(time, compItem) { - return time * compItem.frameRate; + return time * compItem.frameRate; } // To prevent rounding errors var epsilon = 0.001; function isFrameVisible(compItem, frameNumber) { - if (!compItem) return false; + if (!compItem) return false; - var time = frameToTime(frameNumber + epsilon, compItem); - var videoLayers = toArrayBase1(compItem.layers).filter(function(layer) { - return layer.hasVideo; - }); - var result = videoLayers.find(function(layer) { - return layer.activeAtTime(time); - }); - return Boolean(result); + var time = frameToTime(frameNumber + epsilon, compItem); + var videoLayers = toArrayBase1(compItem.layers).filter(function(layer) { + return layer.hasVideo; + }); + var result = videoLayers.find(function(layer) { + return layer.activeAtTime(time); + }); + return Boolean(result); } var appName = 'Rhubarb Lip Sync'; @@ -111,93 +113,93 @@ var appName = 'Rhubarb Lip Sync'; var settingsFilePath = Folder.userData.fullName + '/rhubarb-ae-settings.json'; function readTextFile(fileOrPath) { - var filePath = fileOrPath.fsName || fileOrPath; - var file = new File(filePath); - function check() { - if (file.error) throw new Error('Error reading file "' + filePath + '": ' + file.error); - } - try { - file.open('r'); check(); - file.encoding = 'UTF-8'; check(); - var result = file.read(); check(); - return result; - } finally { - file.close(); check(); - } + var filePath = fileOrPath.fsName || fileOrPath; + var file = new File(filePath); + function check() { + if (file.error) throw new Error('Error reading file "' + filePath + '": ' + file.error); + } + try { + file.open('r'); check(); + file.encoding = 'UTF-8'; check(); + var result = file.read(); check(); + return result; + } finally { + file.close(); check(); + } } function writeTextFile(fileOrPath, text) { - var filePath = fileOrPath.fsName || fileOrPath; - var file = new File(filePath); - function check() { - if (file.error) throw new Error('Error writing file "' + filePath + '": ' + file.error); - } - try { - file.open('w'); check(); - file.encoding = 'UTF-8'; check(); - file.write(text); check(); - } finally { - file.close(); check(); - } + var filePath = fileOrPath.fsName || fileOrPath; + var file = new File(filePath); + function check() { + if (file.error) throw new Error('Error writing file "' + filePath + '": ' + file.error); + } + try { + file.open('w'); check(); + file.encoding = 'UTF-8'; check(); + file.write(text); check(); + } finally { + file.close(); check(); + } } function readSettingsFile() { - try { - return JSON.parse(readTextFile(settingsFilePath)); - } catch (e) { - return {}; - } + try { + return JSON.parse(readTextFile(settingsFilePath)); + } catch (e) { + return {}; + } } function writeSettingsFile(settings) { - try { - writeTextFile(settingsFilePath, JSON.stringify(settings, null, 2)); - } catch (e) { - alert('Error persisting settings. ' + e.message); - } + try { + writeTextFile(settingsFilePath, JSON.stringify(settings, null, 2)); + } catch (e) { + alert('Error persisting settings. ' + e.message); + } } var osIsWindows = (system.osName || $.os).match(/windows/i); // Depending on the operating system, the syntax for escaping command-line arguments differs. function cliEscape(argument) { - return osIsWindows - ? '"' + argument + '"' - : "'" + argument.replace(/'/g, "'\\''") + "'"; + return osIsWindows + ? '"' + argument + '"' + : "'" + argument.replace(/'/g, "'\\''") + "'"; } function exec(command) { - return system.callSystem(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)); - } + 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'; @@ -210,68 +212,68 @@ var rhubarbPath = osIsWindows ? 'rhubarb.exe' : '/usr/local/bin/rhubarb'; // This code relies on the fact that, contrary to the language specification, all major JavaScript // implementations keep object properties in insertion order. function createResourceString(tree) { - var result = JSON.stringify(tree, null, 2); - result = result.replace(/(\{\s*)"__type__":\s*"(\w+)",?\s*/g, '$2 $1'); - return result; + var result = JSON.stringify(tree, null, 2); + result = result.replace(/(\{\s*)"__type__":\s*"(\w+)",?\s*/g, '$2 $1'); + return result; } // Object containing functions to create control description trees. // For instance, `controls.StaticText({ text: 'Hello world' })` // returns `{ __type__: StaticText, text: 'Hello world' }`. var controlFunctions = (function() { - var controlTypes = [ - // Strangely, 'dialog' and 'palette' need to start with a lower-case character - ['Dialog', 'dialog'], ['Palette', 'palette'], - 'Panel', 'Group', 'TabbedPanel', 'Tab', 'Button', 'IconButton', 'Image', 'StaticText', - 'EditText', 'Checkbox', 'RadioButton', 'Progressbar', 'Slider', 'Scrollbar', 'ListBox', - 'DropDownList', 'TreeView', 'ListItem', 'FlashPlayer' - ]; - var result = {}; - controlTypes.forEach(function(type){ - var isArray = Array.isArray(type); - var key = isArray ? type[0] : type; - var value = isArray ? type[1] : type; - result[key] = function(options) { - return Object.assign({ __type__: value }, options); - }; - }); - return result; + var controlTypes = [ + // Strangely, 'dialog' and 'palette' need to start with a lower-case character + ['Dialog', 'dialog'], ['Palette', 'palette'], + 'Panel', 'Group', 'TabbedPanel', 'Tab', 'Button', 'IconButton', 'Image', 'StaticText', + 'EditText', 'Checkbox', 'RadioButton', 'Progressbar', 'Slider', 'Scrollbar', 'ListBox', + 'DropDownList', 'TreeView', 'ListItem', 'FlashPlayer' + ]; + var result = {}; + controlTypes.forEach(function(type){ + var isArray = Array.isArray(type); + var key = isArray ? type[0] : type; + var value = isArray ? type[1] : type; + result[key] = function(options) { + return Object.assign({ __type__: value }, options); + }; + }); + return result; })(); // Returns the path of a project item within the project function getItemPath(item) { - if (item === app.project.rootFolder) { - return '/'; - } + if (item === app.project.rootFolder) { + return '/'; + } - var result = item.name; - while (item.parentFolder !== app.project.rootFolder) { - result = item.parentFolder.name + ' / ' + result; - item = item.parentFolder; - } - return '/ ' + result; + var result = item.name; + while (item.parentFolder !== app.project.rootFolder) { + result = item.parentFolder.name + ' / ' + result; + item = item.parentFolder; + } + return '/ ' + result; } // Selects the item within an item control whose text matches the specified text. // If no such item exists, selects the first item, if present. function selectByTextOrFirst(itemControl, text) { - var targetItem = toArray(itemControl.items).find(function(item) { - return item.text === text; - }); - if (!targetItem && itemControl.items.length) { - targetItem = itemControl.items[0]; - } - if (targetItem) { - itemControl.selection = targetItem; - } + var targetItem = toArray(itemControl.items).find(function(item) { + return item.text === text; + }); + if (!targetItem && itemControl.items.length) { + targetItem = itemControl.items[0]; + } + if (targetItem) { + itemControl.selection = targetItem; + } } function getAudioFileProjectItems() { - var result = toArrayBase1(app.project.items).filter(function(item) { - var isAudioFootage = item instanceof FootageItem && item.hasAudio && !item.hasVideo; - return isAudioFootage; - }); - return result; + var result = toArrayBase1(app.project.items).filter(function(item) { + var isAudioFootage = item instanceof FootageItem && item.hasAudio && !item.hasVideo; + return isAudioFootage; + }); + return result; } var mouthShapeNames = 'ABCDEFGHX'.split(''); @@ -281,478 +283,478 @@ var basicMouthShapeNames = mouthShapeNames.slice(0, basicMouthShapeCount); var extendedMouthShapeNames = mouthShapeNames.slice(basicMouthShapeCount); function getMouthCompHelpTip() { - var result = 'A composition containing the mouth shapes, one drawing per frame. They must be ' - + 'arranged as follows:\n'; - mouthShapeNames.forEach(function(mouthShapeName, i) { - var isOptional = i >= basicMouthShapeCount; - result += '\n00:' + pad(i, 2) + '\t' + mouthShapeName + (isOptional ? ' (optional)' : ''); - }); - return result; + var result = 'A composition containing the mouth shapes, one drawing per frame. They must be ' + + 'arranged as follows:\n'; + mouthShapeNames.forEach(function(mouthShapeName, i) { + var isOptional = i >= basicMouthShapeCount; + result += '\n00:' + pad(i, 2) + '\t' + mouthShapeName + (isOptional ? ' (optional)' : ''); + }); + return result; } function createExtendedShapeCheckboxes() { - var result = {}; - extendedMouthShapeNames.forEach(function(shapeName) { - result[shapeName.toLowerCase()] = controlFunctions.Checkbox({ - text: shapeName, - helpTip: 'Controls whether to use the optional ' + shapeName + ' shape.' - }); - }); - return result; + var result = {}; + extendedMouthShapeNames.forEach(function(shapeName) { + result[shapeName.toLowerCase()] = controlFunctions.Checkbox({ + text: shapeName, + helpTip: 'Controls whether to use the optional ' + shapeName + ' shape.' + }); + }); + return result; } function createDialogWindow() { - var resourceString; - with (controlFunctions) { - resourceString = createResourceString( - Dialog({ - text: appName, - settings: Group({ - orientation: 'column', - alignChildren: ['left', 'top'], - audioFile: Group({ - label: StaticText({ - text: 'Audio file:', - // If I don't explicitly activate a control, After Effects has trouble - // with keyboard focus, so I can't type in the text edit field below. - active: true - }), - value: DropDownList({ - helpTip: 'An audio file containing recorded dialog.\n' - + 'This field shows all audio files that exist in ' - + 'your After Effects project.' - }) - }), - recognizer: Group({ - label: StaticText({ text: 'Recognizer:' }), - value: DropDownList({ - helpTip: 'The dialog recognizer.' - }) - }), - dialogText: Group({ - label: StaticText({ text: 'Dialog text (optional):' }), - value: EditText({ - properties: { multiline: true }, - characters: 60, - minimumSize: [0, 100], - helpTip: 'For better animation results, you can specify the text of ' - + 'the recording here. This field is optional.' - }) - }), - mouthComp: Group({ - label: StaticText({ text: 'Mouth composition:' }), - value: DropDownList({ helpTip: getMouthCompHelpTip() }) - }), - extendedMouthShapes: Group( - Object.assign( - { label: StaticText({ text: 'Extended mouth shapes:' }) }, - createExtendedShapeCheckboxes() - ) - ), - targetFolder: Group({ - label: StaticText({ text: 'Target folder:' }), - value: DropDownList({ - helpTip: 'The project folder in which to create the animation ' - + 'composition. The composition will be named like the audio file.' - }) - }), - frameRate: Group({ - label: StaticText({ text: 'Frame rate:' }), - value: EditText({ - characters: 8, - helpTip: 'The frame rate for the animation.' - }), - auto: Checkbox({ - text: 'From mouth composition', - helpTip: 'If checked, the animation will use the same frame rate as ' - + 'the mouth composition.' - }) - }) - }), - separator: Group({ preferredSize: ['', 3] }), - buttons: Group({ - alignment: 'right', - animate: Button({ - properties: { name: 'ok' }, - text: 'Animate' - }), - cancel: Button({ - properties: { name: 'cancel' }, - text: 'Cancel' - }) - }) - }) - ); - } + var resourceString; + with (controlFunctions) { + resourceString = createResourceString( + Dialog({ + text: appName, + settings: Group({ + orientation: 'column', + alignChildren: ['left', 'top'], + audioFile: Group({ + label: StaticText({ + text: 'Audio file:', + // If I don't explicitly activate a control, After Effects has trouble + // with keyboard focus, so I can't type in the text edit field below. + active: true + }), + value: DropDownList({ + helpTip: 'An audio file containing recorded dialog.\n' + + 'This field shows all audio files that exist in ' + + 'your After Effects project.' + }) + }), + recognizer: Group({ + label: StaticText({ text: 'Recognizer:' }), + value: DropDownList({ + helpTip: 'The dialog recognizer.' + }) + }), + dialogText: Group({ + label: StaticText({ text: 'Dialog text (optional):' }), + value: EditText({ + properties: { multiline: true }, + characters: 60, + minimumSize: [0, 100], + helpTip: 'For better animation results, you can specify the text of ' + + 'the recording here. This field is optional.' + }) + }), + mouthComp: Group({ + label: StaticText({ text: 'Mouth composition:' }), + value: DropDownList({ helpTip: getMouthCompHelpTip() }) + }), + extendedMouthShapes: Group( + Object.assign( + { label: StaticText({ text: 'Extended mouth shapes:' }) }, + createExtendedShapeCheckboxes() + ) + ), + targetFolder: Group({ + label: StaticText({ text: 'Target folder:' }), + value: DropDownList({ + helpTip: 'The project folder in which to create the animation ' + + 'composition. The composition will be named like the audio file.' + }) + }), + frameRate: Group({ + label: StaticText({ text: 'Frame rate:' }), + value: EditText({ + characters: 8, + helpTip: 'The frame rate for the animation.' + }), + auto: Checkbox({ + text: 'From mouth composition', + helpTip: 'If checked, the animation will use the same frame rate as ' + + 'the mouth composition.' + }) + }) + }), + separator: Group({ preferredSize: ['', 3] }), + buttons: Group({ + alignment: 'right', + animate: Button({ + properties: { name: 'ok' }, + text: 'Animate' + }), + cancel: Button({ + properties: { name: 'cancel' }, + text: 'Cancel' + }) + }) + }) + ); + } - // Create window and child controls - var window = new Window(resourceString); - var controls = { - audioFile: window.settings.audioFile.value, - dialogText: window.settings.dialogText.value, - recognizer: window.settings.recognizer.value, - mouthComp: window.settings.mouthComp.value, - targetFolder: window.settings.targetFolder.value, - frameRate: window.settings.frameRate.value, - autoFrameRate: window.settings.frameRate.auto, - animateButton: window.buttons.animate, - cancelButton: window.buttons.cancel - }; - extendedMouthShapeNames.forEach(function(shapeName) { - controls['mouthShape' + shapeName] = - window.settings.extendedMouthShapes[shapeName.toLowerCase()]; - }); + // Create window and child controls + var window = new Window(resourceString); + var controls = { + audioFile: window.settings.audioFile.value, + dialogText: window.settings.dialogText.value, + recognizer: window.settings.recognizer.value, + mouthComp: window.settings.mouthComp.value, + targetFolder: window.settings.targetFolder.value, + frameRate: window.settings.frameRate.value, + autoFrameRate: window.settings.frameRate.auto, + animateButton: window.buttons.animate, + cancelButton: window.buttons.cancel + }; + extendedMouthShapeNames.forEach(function(shapeName) { + controls['mouthShape' + shapeName] = + window.settings.extendedMouthShapes[shapeName.toLowerCase()]; + }); - // Add audio file options - getAudioFileProjectItems().forEach(function(projectItem) { - var listItem = controls.audioFile.add('item', getItemPath(projectItem)); - listItem.projectItem = projectItem; - }); + // Add audio file options + getAudioFileProjectItems().forEach(function(projectItem) { + var listItem = controls.audioFile.add('item', getItemPath(projectItem)); + listItem.projectItem = projectItem; + }); - // Add recognizer options - const recognizerOptions = [ - { text: 'PocketSphinx (use for English recordings)', value: 'pocketSphinx' }, - { text: 'Phonetic (use for non-English recordings)', value: 'phonetic' } - ]; - recognizerOptions.forEach(function(option) { - var listItem = controls.recognizer.add('item', option.text); - listItem.value = option.value; - }); + // Add recognizer options + const recognizerOptions = [ + { text: 'PocketSphinx (use for English recordings)', value: 'pocketSphinx' }, + { text: 'Phonetic (use for non-English recordings)', value: 'phonetic' } + ]; + recognizerOptions.forEach(function(option) { + var listItem = controls.recognizer.add('item', option.text); + listItem.value = option.value; + }); - // Add mouth composition options - var comps = toArrayBase1(app.project.items).filter(function (item) { - return item instanceof CompItem; - }); - comps.forEach(function(projectItem) { - var listItem = controls.mouthComp.add('item', getItemPath(projectItem)); - listItem.projectItem = projectItem; - }); + // Add mouth composition options + var comps = toArrayBase1(app.project.items).filter(function (item) { + return item instanceof CompItem; + }); + comps.forEach(function(projectItem) { + var listItem = controls.mouthComp.add('item', getItemPath(projectItem)); + listItem.projectItem = projectItem; + }); - // Add target folder options - var projectFolders = toArrayBase1(app.project.items).filter(function (item) { - return item instanceof FolderItem; - }); - projectFolders.unshift(app.project.rootFolder); - projectFolders.forEach(function(projectFolder) { - var listItem = controls.targetFolder.add('item', getItemPath(projectFolder)); - listItem.projectItem = projectFolder; - }); + // Add target folder options + var projectFolders = toArrayBase1(app.project.items).filter(function (item) { + return item instanceof FolderItem; + }); + projectFolders.unshift(app.project.rootFolder); + projectFolders.forEach(function(projectFolder) { + var listItem = controls.targetFolder.add('item', getItemPath(projectFolder)); + listItem.projectItem = projectFolder; + }); - // Load persisted settings - var settings = readSettingsFile(); - selectByTextOrFirst(controls.audioFile, settings.audioFile); - controls.dialogText.text = settings.dialogText || ''; - selectByTextOrFirst(controls.recognizer, settings.recognizer); - selectByTextOrFirst(controls.mouthComp, settings.mouthComp); - extendedMouthShapeNames.forEach(function(shapeName) { - controls['mouthShape' + shapeName].value = - (settings.extendedMouthShapes || {})[shapeName.toLowerCase()]; - }); - selectByTextOrFirst(controls.targetFolder, settings.targetFolder); - controls.frameRate.text = settings.frameRate || ''; - controls.autoFrameRate.value = settings.autoFrameRate; + // Load persisted settings + var settings = readSettingsFile(); + selectByTextOrFirst(controls.audioFile, settings.audioFile); + controls.dialogText.text = settings.dialogText || ''; + selectByTextOrFirst(controls.recognizer, settings.recognizer); + selectByTextOrFirst(controls.mouthComp, settings.mouthComp); + extendedMouthShapeNames.forEach(function(shapeName) { + controls['mouthShape' + shapeName].value = + (settings.extendedMouthShapes || {})[shapeName.toLowerCase()]; + }); + selectByTextOrFirst(controls.targetFolder, settings.targetFolder); + controls.frameRate.text = settings.frameRate || ''; + controls.autoFrameRate.value = settings.autoFrameRate; - // Align controls - window.onShow = function() { - // Give uniform width to all labels - var groups = toArray(window.settings.children); - var labelWidths = groups.map(function(group) { return group.children[0].size.width; }); - var maxLabelWidth = Math.max.apply(Math, labelWidths); - groups.forEach(function (group) { - group.children[0].size.width = maxLabelWidth; - }); + // Align controls + window.onShow = function() { + // Give uniform width to all labels + var groups = toArray(window.settings.children); + var labelWidths = groups.map(function(group) { return group.children[0].size.width; }); + var maxLabelWidth = Math.max.apply(Math, labelWidths); + groups.forEach(function (group) { + group.children[0].size.width = maxLabelWidth; + }); - // Give uniform width to inputs - var valueWidths = groups.map(function(group) { - return last(group.children).bounds.right - group.children[1].bounds.left; - }); - var maxValueWidth = Math.max.apply(Math, valueWidths); - groups.forEach(function (group) { - var multipleControls = group.children.length > 2; - if (!multipleControls) { - group.children[1].size.width = maxValueWidth; - } - }); + // Give uniform width to inputs + var valueWidths = groups.map(function(group) { + return last(group.children).bounds.right - group.children[1].bounds.left; + }); + var maxValueWidth = Math.max.apply(Math, valueWidths); + groups.forEach(function (group) { + var multipleControls = group.children.length > 2; + if (!multipleControls) { + group.children[1].size.width = maxValueWidth; + } + }); - window.layout.layout(true); - }; + window.layout.layout(true); + }; - var updating = false; + var updating = false; - function update() { - if (updating) return; + function update() { + if (updating) return; - updating = true; - try { - // Handle auto frame rate - var autoFrameRate = controls.autoFrameRate.value; - controls.frameRate.enabled = !autoFrameRate; - if (autoFrameRate) { - // Take frame rate from mouth comp - var mouthComp = (controls.mouthComp.selection || {}).projectItem; - controls.frameRate.text = mouthComp ? mouthComp.frameRate : ''; - } else { - // Sanitize frame rate - var sanitizedFrameRate = controls.frameRate.text.match(/\d*\.?\d*/)[0]; - if (sanitizedFrameRate !== controls.frameRate.text) { - controls.frameRate.text = sanitizedFrameRate; - } - } + updating = true; + try { + // Handle auto frame rate + var autoFrameRate = controls.autoFrameRate.value; + controls.frameRate.enabled = !autoFrameRate; + if (autoFrameRate) { + // Take frame rate from mouth comp + var mouthComp = (controls.mouthComp.selection || {}).projectItem; + controls.frameRate.text = mouthComp ? mouthComp.frameRate : ''; + } else { + // Sanitize frame rate + var sanitizedFrameRate = controls.frameRate.text.match(/\d*\.?\d*/)[0]; + if (sanitizedFrameRate !== controls.frameRate.text) { + controls.frameRate.text = sanitizedFrameRate; + } + } - // Store settings - var settings = { - audioFile: (controls.audioFile.selection || {}).text, - recognizer: (controls.recognizer.selection || {}).text, - dialogText: controls.dialogText.text, - mouthComp: (controls.mouthComp.selection || {}).text, - extendedMouthShapes: {}, - targetFolder: (controls.targetFolder.selection || {}).text, - frameRate: Number(controls.frameRate.text), - autoFrameRate: controls.autoFrameRate.value - }; - extendedMouthShapeNames.forEach(function(shapeName) { - settings.extendedMouthShapes[shapeName.toLowerCase()] = - controls['mouthShape' + shapeName].value; - }); - writeSettingsFile(settings); - } finally { - updating = false; - } - } + // Store settings + var settings = { + audioFile: (controls.audioFile.selection || {}).text, + recognizer: (controls.recognizer.selection || {}).text, + dialogText: controls.dialogText.text, + mouthComp: (controls.mouthComp.selection || {}).text, + extendedMouthShapes: {}, + targetFolder: (controls.targetFolder.selection || {}).text, + frameRate: Number(controls.frameRate.text), + autoFrameRate: controls.autoFrameRate.value + }; + extendedMouthShapeNames.forEach(function(shapeName) { + settings.extendedMouthShapes[shapeName.toLowerCase()] = + controls['mouthShape' + shapeName].value; + }); + writeSettingsFile(settings); + } finally { + updating = false; + } + } - // Validate user input. Possible return values: - // * Non-empty string: Validation failed. Show error message. - // * Empty string: Validation failed. Don't show error message. - // * Undefined: Validation succeeded. - function validate() { - // Check input values - if (!controls.audioFile.selection) return 'Please select an audio file.'; - if (!controls.mouthComp.selection) return 'Please select a mouth composition.'; - if (!controls.targetFolder.selection) return 'Please select a target folder.'; - if (Number(controls.frameRate.text) < 12) { - return 'Please enter a frame rate of at least 12 fps.'; - } + // Validate user input. Possible return values: + // * Non-empty string: Validation failed. Show error message. + // * Empty string: Validation failed. Don't show error message. + // * Undefined: Validation succeeded. + function validate() { + // Check input values + if (!controls.audioFile.selection) return 'Please select an audio file.'; + if (!controls.mouthComp.selection) return 'Please select a mouth composition.'; + if (!controls.targetFolder.selection) return 'Please select a target folder.'; + if (Number(controls.frameRate.text) < 12) { + return 'Please enter a frame rate of at least 12 fps.'; + } - // Check mouth shape visibility - var comp = controls.mouthComp.selection.projectItem; - for (var i = 0; i < mouthShapeCount; i++) { - var shapeName = mouthShapeNames[i]; - var required = i < basicMouthShapeCount || controls['mouthShape' + shapeName].value; - if (required && !isFrameVisible(comp, i)) { - return 'The mouth comp does not seem to contain an image for shape ' - + shapeName + ' at frame ' + i + '.'; - } - } + // Check mouth shape visibility + var comp = controls.mouthComp.selection.projectItem; + for (var i = 0; i < mouthShapeCount; i++) { + var shapeName = mouthShapeNames[i]; + var required = i < basicMouthShapeCount || controls['mouthShape' + shapeName].value; + if (required && !isFrameVisible(comp, i)) { + return 'The mouth comp does not seem to contain an image for shape ' + + shapeName + ' at frame ' + i + '.'; + } + } - if (!comp.preserveNestedFrameRate) { - var fix = Window.confirm( - 'The setting "Preserve frame rate when nested or in render queue" is not active ' - + 'for the mouth composition. This can result in incorrect animation.\n\n' - + 'Activate this setting now?', - false, - 'Fix composition setting?'); - if (fix) { - app.beginUndoGroup(appName + ': Mouth composition setting'); - comp.preserveNestedFrameRate = true; - app.endUndoGroup(); - } else { - return ''; - } - } + if (!comp.preserveNestedFrameRate) { + var fix = Window.confirm( + 'The setting "Preserve frame rate when nested or in render queue" is not active ' + + 'for the mouth composition. This can result in incorrect animation.\n\n' + + 'Activate this setting now?', + false, + 'Fix composition setting?'); + if (fix) { + app.beginUndoGroup(appName + ': Mouth composition setting'); + comp.preserveNestedFrameRate = true; + app.endUndoGroup(); + } else { + return ''; + } + } - // Check for correct Rhubarb version - var version = exec(rhubarbPath + ' --version') || ''; - var match = version.match(/Rhubarb Lip Sync version ((\d+)\.(\d+).(\d+)(-[0-9A-Za-z-.]+)?)/); - if (!match) { - var instructions = osIsWindows - ? 'Make sure your PATH environment variable contains the ' + appName + ' ' - + 'application directory.' - : 'Make sure you have created this file as a symbolic link to the ' + appName + ' ' - + 'executable (rhubarb).'; - return 'Cannot find executable file "' + rhubarbPath + '". \n' + instructions; - } - var versionString = match[1]; - var major = Number(match[2]); - var minor = Number(match[3]); - var requiredMajor = 1; - var minRequiredMinor = 9; - if (major != requiredMajor || minor < minRequiredMinor) { - return 'This script requires ' + appName + ' ' + requiredMajor + '.' + minRequiredMinor - + '.0 or a later ' + requiredMajor + '.x version. ' - + 'Your installed version is ' + versionString + ', which is not compatible.'; - } - } + // Check for correct Rhubarb version + var version = exec(rhubarbPath + ' --version') || ''; + var match = version.match(/Rhubarb Lip Sync version ((\d+)\.(\d+).(\d+)(-[0-9A-Za-z-.]+)?)/); + if (!match) { + var instructions = osIsWindows + ? 'Make sure your PATH environment variable contains the ' + appName + ' ' + + 'application directory.' + : 'Make sure you have created this file as a symbolic link to the ' + appName + ' ' + + 'executable (rhubarb).'; + return 'Cannot find executable file "' + rhubarbPath + '". \n' + instructions; + } + var versionString = match[1]; + var major = Number(match[2]); + var minor = Number(match[3]); + var requiredMajor = 1; + var minRequiredMinor = 9; + if (major != requiredMajor || minor < minRequiredMinor) { + return 'This script requires ' + appName + ' ' + requiredMajor + '.' + minRequiredMinor + + '.0 or a later ' + requiredMajor + '.x version. ' + + 'Your installed version is ' + versionString + ', which is not compatible.'; + } + } - function generateMouthCues(audioFileFootage, recognizer, dialogText, mouthComp, extendedMouthShapeNames, - targetProjectFolder, frameRate) - { - var basePath = Folder.temp.fsName + '/' + createGuid(); - var dialogFile = new File(basePath + '.txt'); - var logFile = new File(basePath + '.log'); - var jsonFile = new File(basePath + '.json'); - try { - // Create text file containing dialog - writeTextFile(dialogFile, dialogText); + function generateMouthCues(audioFileFootage, recognizer, dialogText, mouthComp, extendedMouthShapeNames, + targetProjectFolder, frameRate) + { + var basePath = Folder.temp.fsName + '/' + createGuid(); + var dialogFile = new File(basePath + '.txt'); + var logFile = new File(basePath + '.log'); + var jsonFile = new File(basePath + '.json'); + try { + // Create text file containing dialog + writeTextFile(dialogFile, dialogText); - // Create command line - var commandLine = rhubarbPath - + ' --dialogFile ' + cliEscape(dialogFile.fsName) - + ' --recognizer ' + recognizer - + ' --exportFormat json' - + ' --extendedShapes ' + cliEscape(extendedMouthShapeNames.join('')) - + ' --logFile ' + cliEscape(logFile.fsName) - + ' --logLevel fatal' - + ' --output ' + cliEscape(jsonFile.fsName) - + ' ' + cliEscape(audioFileFootage.file.fsName); + // Create command line + var commandLine = rhubarbPath + + ' --dialogFile ' + cliEscape(dialogFile.fsName) + + ' --recognizer ' + recognizer + + ' --exportFormat json' + + ' --extendedShapes ' + cliEscape(extendedMouthShapeNames.join('')) + + ' --logFile ' + cliEscape(logFile.fsName) + + ' --logLevel fatal' + + ' --output ' + cliEscape(jsonFile.fsName) + + ' ' + cliEscape(audioFileFootage.file.fsName); - // Run Rhubarb - execInWindow(commandLine); + // Run Rhubarb + execInWindow(commandLine); - // Check log for fatal errors - if (logFile.exists) { - var fatalLog = readTextFile(logFile).trim(); - if (fatalLog) { - // Try to extract only the actual error message - var match = fatalLog.match(/\[Fatal\] ([\s\S]*)/); - var message = match ? match[1] : fatalLog; - throw new Error('Error running ' + appName + '.\n' + message); - } - } + // Check log for fatal errors + if (logFile.exists) { + var fatalLog = readTextFile(logFile).trim(); + if (fatalLog) { + // Try to extract only the actual error message + var match = fatalLog.match(/\[Fatal\] ([\s\S]*)/); + var message = match ? match[1] : fatalLog; + throw new Error('Error running ' + appName + '.\n' + message); + } + } - var result; - try { - result = JSON.parse(readTextFile(jsonFile)); - } catch (e) { - throw new Error('No animation result. Animation was probably canceled.'); - } - return result; - } finally { - dialogFile.remove(); - logFile.remove(); - jsonFile.remove(); - } - } + var result; + try { + result = JSON.parse(readTextFile(jsonFile)); + } catch (e) { + throw new Error('No animation result. Animation was probably canceled.'); + } + return result; + } finally { + dialogFile.remove(); + logFile.remove(); + jsonFile.remove(); + } + } - function animateMouthCues(mouthCues, audioFileFootage, mouthComp, targetProjectFolder, - frameRate) - { - // Find an unconflicting comp name - // ... strip extension, if present - var baseName = audioFileFootage.name.match(/^(.*?)(\..*)?$/i)[1]; - var compName = baseName; - // ... add numeric suffix, if needed - var existingItems = toArrayBase1(targetProjectFolder.items); - var counter = 1; - while (existingItems.some(function(item) { return item.name === compName; })) { - counter++; - compName = baseName + ' ' + counter; - } + function animateMouthCues(mouthCues, audioFileFootage, mouthComp, targetProjectFolder, + frameRate) + { + // Find an unconflicting comp name + // ... strip extension, if present + var baseName = audioFileFootage.name.match(/^(.*?)(\..*)?$/i)[1]; + var compName = baseName; + // ... add numeric suffix, if needed + var existingItems = toArrayBase1(targetProjectFolder.items); + var counter = 1; + while (existingItems.some(function(item) { return item.name === compName; })) { + counter++; + compName = baseName + ' ' + counter; + } - // Create new comp - var comp = targetProjectFolder.items.addComp(compName, mouthComp.width, mouthComp.height, - mouthComp.pixelAspect, audioFileFootage.duration, frameRate); + // Create new comp + var comp = targetProjectFolder.items.addComp(compName, mouthComp.width, mouthComp.height, + mouthComp.pixelAspect, audioFileFootage.duration, frameRate); - // Show new comp - comp.openInViewer(); + // Show new comp + comp.openInViewer(); - // Add audio layer - comp.layers.add(audioFileFootage); + // Add audio layer + comp.layers.add(audioFileFootage); - // Add mouth layer - var mouthLayer = comp.layers.add(mouthComp); - mouthLayer.timeRemapEnabled = true; - mouthLayer.outPoint = comp.duration; + // Add mouth layer + var mouthLayer = comp.layers.add(mouthComp); + mouthLayer.timeRemapEnabled = true; + mouthLayer.outPoint = comp.duration; - // Animate mouth layer - var timeRemap = mouthLayer['Time Remap']; - // Enabling time remapping automatically adds two keys. Remove the second. - timeRemap.removeKey(2); - mouthCues.mouthCues.forEach(function(mouthCue) { - // Round down keyframe time. In animation, earlier is better than later. - // Set keyframe time to *just before* the exact frame to prevent rounding errors - var frame = Math.floor(timeToFrame(mouthCue.start, comp)); - var time = frame !== 0 ? frameToTime(frame - epsilon, comp) : 0; - // Set remapped time to *just after* the exact frame to prevent rounding errors - var mouthCompFrame = mouthShapeNames.indexOf(mouthCue.value); - var remappedTime = frameToTime(mouthCompFrame + epsilon, mouthComp); - timeRemap.setValueAtTime(time, remappedTime); - }); - for (var i = 1; i <= timeRemap.numKeys; i++) { - timeRemap.setInterpolationTypeAtKey(i, KeyframeInterpolationType.HOLD); - } - } + // Animate mouth layer + var timeRemap = mouthLayer['Time Remap']; + // Enabling time remapping automatically adds two keys. Remove the second. + timeRemap.removeKey(2); + mouthCues.mouthCues.forEach(function(mouthCue) { + // Round down keyframe time. In animation, earlier is better than later. + // Set keyframe time to *just before* the exact frame to prevent rounding errors + var frame = Math.floor(timeToFrame(mouthCue.start, comp)); + var time = frame !== 0 ? frameToTime(frame - epsilon, comp) : 0; + // Set remapped time to *just after* the exact frame to prevent rounding errors + var mouthCompFrame = mouthShapeNames.indexOf(mouthCue.value); + var remappedTime = frameToTime(mouthCompFrame + epsilon, mouthComp); + timeRemap.setValueAtTime(time, remappedTime); + }); + for (var i = 1; i <= timeRemap.numKeys; i++) { + timeRemap.setInterpolationTypeAtKey(i, KeyframeInterpolationType.HOLD); + } + } - function animate(audioFileFootage, recognizer, dialogText, mouthComp, extendedMouthShapeNames, - targetProjectFolder, frameRate) - { - try { - var mouthCues = generateMouthCues(audioFileFootage, recognizer, dialogText, mouthComp, - extendedMouthShapeNames, targetProjectFolder, frameRate); + function animate(audioFileFootage, recognizer, dialogText, mouthComp, extendedMouthShapeNames, + targetProjectFolder, frameRate) + { + try { + var mouthCues = generateMouthCues(audioFileFootage, recognizer, dialogText, mouthComp, + extendedMouthShapeNames, targetProjectFolder, frameRate); - app.beginUndoGroup(appName + ': Animation'); - animateMouthCues(mouthCues, audioFileFootage, mouthComp, targetProjectFolder, - frameRate); - app.endUndoGroup(); - } catch (e) { - Window.alert(e.message, appName, true); - return; - } - } + app.beginUndoGroup(appName + ': Animation'); + animateMouthCues(mouthCues, audioFileFootage, mouthComp, targetProjectFolder, + frameRate); + app.endUndoGroup(); + } catch (e) { + Window.alert(e.message, appName, true); + return; + } + } - // Handle changes - update(); - controls.audioFile.onChange = update; - controls.recognizer.onChange = update; - controls.dialogText.onChanging = update; - controls.mouthComp.onChange = update; - extendedMouthShapeNames.forEach(function(shapeName) { - controls['mouthShape' + shapeName].onClick = update; - }); - controls.targetFolder.onChange = update; - controls.frameRate.onChanging = update; - controls.autoFrameRate.onClick = update; + // Handle changes + update(); + controls.audioFile.onChange = update; + controls.recognizer.onChange = update; + controls.dialogText.onChanging = update; + controls.mouthComp.onChange = update; + extendedMouthShapeNames.forEach(function(shapeName) { + controls['mouthShape' + shapeName].onClick = update; + }); + controls.targetFolder.onChange = update; + controls.frameRate.onChanging = update; + controls.autoFrameRate.onClick = update; - // Handle animation - controls.animateButton.onClick = function() { - var validationError = validate(); - if (typeof validationError === 'string') { - if (validationError) { - Window.alert(validationError, appName, true); - } - } else { - window.close(); - animate( - controls.audioFile.selection.projectItem, - controls.recognizer.selection.value, - controls.dialogText.text || '', - controls.mouthComp.selection.projectItem, - extendedMouthShapeNames.filter(function(shapeName) { - return controls['mouthShape' + shapeName].value; - }), - controls.targetFolder.selection.projectItem, - Number(controls.frameRate.text) - ); - } - }; + // Handle animation + controls.animateButton.onClick = function() { + var validationError = validate(); + if (typeof validationError === 'string') { + if (validationError) { + Window.alert(validationError, appName, true); + } + } else { + window.close(); + animate( + controls.audioFile.selection.projectItem, + controls.recognizer.selection.value, + controls.dialogText.text || '', + controls.mouthComp.selection.projectItem, + extendedMouthShapeNames.filter(function(shapeName) { + return controls['mouthShape' + shapeName].value; + }), + controls.targetFolder.selection.projectItem, + Number(controls.frameRate.text) + ); + } + }; - // Handle cancelation - controls.cancelButton.onClick = function() { - window.close(); - }; + // Handle cancelation + controls.cancelButton.onClick = function() { + window.close(); + }; - return window; + return window; } function checkPreconditions() { - if (!canWriteFiles()) { - Window.alert('This script requires file system access.\n\n' - + 'Please enable Preferences > General > Allow Scripts to Write Files and Access Network.', - appName, true); - return false; - } - return true; + if (!canWriteFiles()) { + Window.alert('This script requires file system access.\n\n' + + 'Please enable Preferences > General > Allow Scripts to Write Files and Access Network.', + appName, true); + return false; + } + return true; } if (checkPreconditions()) { - createDialogWindow().show(); + createDialogWindow().show(); } diff --git a/extras/EsotericSoftwareSpine/CMakeLists.txt b/extras/EsotericSoftwareSpine/CMakeLists.txt index 2c32021..56f2f42 100644 --- a/extras/EsotericSoftwareSpine/CMakeLists.txt +++ b/extras/EsotericSoftwareSpine/CMakeLists.txt @@ -1,18 +1,18 @@ cmake_minimum_required(VERSION 3.2) add_custom_target( - rhubarbForSpine ALL - "./gradlew" "build" - WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}" - COMMENT "Building Rhubarb for Spine through Gradle." + rhubarbForSpine ALL + "./gradlew" "build" + WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}" + COMMENT "Building Rhubarb for Spine through Gradle." ) install( - DIRECTORY "build/libs/" - DESTINATION "extras/EsotericSoftwareSpine" + DIRECTORY "build/libs/" + DESTINATION "extras/EsotericSoftwareSpine" ) install( - FILES README.adoc - DESTINATION "extras/EsotericSoftwareSpine" + FILES README.adoc + DESTINATION "extras/EsotericSoftwareSpine" ) diff --git a/extras/EsotericSoftwareSpine/src/main/kotlin/AnimationFileModel.kt b/extras/EsotericSoftwareSpine/src/main/kotlin/AnimationFileModel.kt index cb8edd5..484644f 100644 --- a/extras/EsotericSoftwareSpine/src/main/kotlin/AnimationFileModel.kt +++ b/extras/EsotericSoftwareSpine/src/main/kotlin/AnimationFileModel.kt @@ -14,112 +14,112 @@ import tornadofx.setValue import java.util.concurrent.ExecutorService class AnimationFileModel(val parentModel: MainModel, animationFilePath: Path, private val executor: ExecutorService) { - val spineJson = SpineJson(animationFilePath) + val spineJson = SpineJson(animationFilePath) - val slotsProperty = SimpleObjectProperty>() - private var slots: ObservableList by slotsProperty + val slotsProperty = SimpleObjectProperty>() + private var slots: ObservableList by slotsProperty - val mouthSlotProperty: SimpleStringProperty = SimpleStringProperty().alsoListen { - val mouthSlot = this.mouthSlot - val mouthNaming = if (mouthSlot != null) - MouthNaming.guess(spineJson.getSlotAttachmentNames(mouthSlot)) - else null - this.mouthNaming = mouthNaming + val mouthSlotProperty: SimpleStringProperty = SimpleStringProperty().alsoListen { + val mouthSlot = this.mouthSlot + val mouthNaming = if (mouthSlot != null) + MouthNaming.guess(spineJson.getSlotAttachmentNames(mouthSlot)) + else null + this.mouthNaming = mouthNaming - mouthShapes = if (mouthSlot != null && mouthNaming != null) { - val mouthNames = spineJson.getSlotAttachmentNames(mouthSlot) - MouthShape.values().filter { mouthNames.contains(mouthNaming.getName(it)) } - } else listOf() + mouthShapes = if (mouthSlot != null && mouthNaming != null) { + val mouthNames = spineJson.getSlotAttachmentNames(mouthSlot) + MouthShape.values().filter { mouthNames.contains(mouthNaming.getName(it)) } + } else listOf() - mouthSlotError = if (mouthSlot != null) - null - else - "No slot with mouth drawings specified." - } - private var mouthSlot: String? by mouthSlotProperty + mouthSlotError = if (mouthSlot != null) + null + else + "No slot with mouth drawings specified." + } + private var mouthSlot: String? by mouthSlotProperty - val mouthSlotErrorProperty = SimpleStringProperty() - private var mouthSlotError: String? by mouthSlotErrorProperty + val mouthSlotErrorProperty = SimpleStringProperty() + private var mouthSlotError: String? by mouthSlotErrorProperty - val mouthNamingProperty = SimpleObjectProperty() - private var mouthNaming: MouthNaming? by mouthNamingProperty + val mouthNamingProperty = SimpleObjectProperty() + private var mouthNaming: MouthNaming? by mouthNamingProperty - val mouthShapesProperty = SimpleObjectProperty>().alsoListen { - mouthShapesError = getMouthShapesErrorString() - } - var mouthShapes: List by mouthShapesProperty - private set + val mouthShapesProperty = SimpleObjectProperty>().alsoListen { + mouthShapesError = getMouthShapesErrorString() + } + var mouthShapes: List by mouthShapesProperty + private set - val mouthShapesErrorProperty = SimpleStringProperty() - private var mouthShapesError: String? by mouthShapesErrorProperty + val mouthShapesErrorProperty = SimpleStringProperty() + private var mouthShapesError: String? by mouthShapesErrorProperty - val audioFileModelsProperty = SimpleListProperty( - spineJson.audioEvents - .map { event -> - var audioFileModel: AudioFileModel? = null - val reportResult: (List) -> Unit = - { result -> saveAnimation(audioFileModel!!.animationName, event.name, result) } - audioFileModel = AudioFileModel(event, this, executor, reportResult) - return@map audioFileModel - } - .asObservable() - ) - val audioFileModels: ObservableList by audioFileModelsProperty + val audioFileModelsProperty = SimpleListProperty( + spineJson.audioEvents + .map { event -> + var audioFileModel: AudioFileModel? = null + val reportResult: (List) -> Unit = + { result -> saveAnimation(audioFileModel!!.animationName, event.name, result) } + audioFileModel = AudioFileModel(event, this, executor, reportResult) + return@map audioFileModel + } + .asObservable() + ) + val audioFileModels: ObservableList by audioFileModelsProperty - val busyProperty = SimpleBooleanProperty().apply { - bind(object : BooleanBinding() { - init { - for (audioFileModel in audioFileModels) { - super.bind(audioFileModel.busyProperty) - } - } - override fun computeValue(): Boolean { - return audioFileModels.any { it.busy } - } - }) - } - val busy by busyProperty + val busyProperty = SimpleBooleanProperty().apply { + bind(object : BooleanBinding() { + init { + for (audioFileModel in audioFileModels) { + super.bind(audioFileModel.busyProperty) + } + } + override fun computeValue(): Boolean { + return audioFileModels.any { it.busy } + } + }) + } + val busy by busyProperty - val validProperty = SimpleBooleanProperty().apply { - val errorProperties = arrayOf(mouthSlotErrorProperty, mouthShapesErrorProperty) - bind(object : BooleanBinding() { - init { - super.bind(*errorProperties) - } - override fun computeValue(): Boolean { - return errorProperties.all { it.value == null } - } - }) - } + val validProperty = SimpleBooleanProperty().apply { + val errorProperties = arrayOf(mouthSlotErrorProperty, mouthShapesErrorProperty) + bind(object : BooleanBinding() { + init { + super.bind(*errorProperties) + } + override fun computeValue(): Boolean { + return errorProperties.all { it.value == null } + } + }) + } - private fun saveAnimation(animationName: String, audioEventName: String, mouthCues: List) { - spineJson.createOrUpdateAnimation(mouthCues, audioEventName, animationName, mouthSlot!!, mouthNaming!!) - spineJson.save() - } + private fun saveAnimation(animationName: String, audioEventName: String, mouthCues: List) { + spineJson.createOrUpdateAnimation(mouthCues, audioEventName, animationName, mouthSlot!!, mouthNaming!!) + spineJson.save() + } - init { - slots = spineJson.slots.asObservable() - mouthSlot = spineJson.guessMouthSlot() - } + init { + slots = spineJson.slots.asObservable() + mouthSlot = spineJson.guessMouthSlot() + } - private fun getMouthShapesErrorString(): String? { - val missingBasicShapes = MouthShape.basicShapes - .filter{ !mouthShapes.contains(it) } - if (missingBasicShapes.isEmpty()) return null + private fun getMouthShapesErrorString(): String? { + val missingBasicShapes = MouthShape.basicShapes + .filter{ !mouthShapes.contains(it) } + if (missingBasicShapes.isEmpty()) return null - val result = StringBuilder() - val missingShapesString = missingBasicShapes.joinToString() - result.appendln( - if (missingBasicShapes.count() > 1) - "Mouth shapes $missingShapesString are missing." - else - "Mouth shape $missingShapesString is missing." - ) + val result = StringBuilder() + val missingShapesString = missingBasicShapes.joinToString() + result.appendln( + if (missingBasicShapes.count() > 1) + "Mouth shapes $missingShapesString are missing." + else + "Mouth shape $missingShapesString is missing." + ) - val first = MouthShape.basicShapes.first() - val last = MouthShape.basicShapes.last() - result.append("At least the basic mouth shapes $first-$last need corresponding image attachments.") - return result.toString() - } + val first = MouthShape.basicShapes.first() + val last = MouthShape.basicShapes.last() + result.append("At least the basic mouth shapes $first-$last need corresponding image attachments.") + return result.toString() + } } \ No newline at end of file diff --git a/extras/EsotericSoftwareSpine/src/main/kotlin/AudioFileModel.kt b/extras/EsotericSoftwareSpine/src/main/kotlin/AudioFileModel.kt index 632782c..58b4ec2 100644 --- a/extras/EsotericSoftwareSpine/src/main/kotlin/AudioFileModel.kt +++ b/extras/EsotericSoftwareSpine/src/main/kotlin/AudioFileModel.kt @@ -16,181 +16,181 @@ import java.util.concurrent.ExecutorService import java.util.concurrent.Future class AudioFileModel( - audioEvent: SpineJson.AudioEvent, - private val parentModel: AnimationFileModel, - private val executor: ExecutorService, - private val reportResult: (List) -> Unit + audioEvent: SpineJson.AudioEvent, + private val parentModel: AnimationFileModel, + private val executor: ExecutorService, + private val reportResult: (List) -> Unit ) { - private val spineJson = parentModel.spineJson + private val spineJson = parentModel.spineJson - private val audioFilePath: Path = spineJson.audioDirectoryPath.resolve(audioEvent.relativeAudioFilePath) + private val audioFilePath: Path = spineJson.audioDirectoryPath.resolve(audioEvent.relativeAudioFilePath) - val eventNameProperty = SimpleStringProperty(audioEvent.name) - val eventName: String by eventNameProperty + val eventNameProperty = SimpleStringProperty(audioEvent.name) + val eventName: String by eventNameProperty - val displayFilePathProperty = SimpleStringProperty(audioEvent.relativeAudioFilePath) + val displayFilePathProperty = SimpleStringProperty(audioEvent.relativeAudioFilePath) - val animationNameProperty = SimpleStringProperty().apply { - val mainModel = parentModel.parentModel - bind(object : ObjectBinding() { - init { - super.bind( - mainModel.animationPrefixProperty, - eventNameProperty, - mainModel.animationSuffixProperty - ) - } - override fun computeValue(): String { - return mainModel.animationPrefix + eventName + mainModel.animationSuffix - } - }) - } - val animationName: String by animationNameProperty + val animationNameProperty = SimpleStringProperty().apply { + val mainModel = parentModel.parentModel + bind(object : ObjectBinding() { + init { + super.bind( + mainModel.animationPrefixProperty, + eventNameProperty, + mainModel.animationSuffixProperty + ) + } + override fun computeValue(): String { + return mainModel.animationPrefix + eventName + mainModel.animationSuffix + } + }) + } + val animationName: String by animationNameProperty - val dialogProperty = SimpleStringProperty(audioEvent.dialog) - private val dialog: String? by dialogProperty + val dialogProperty = SimpleStringProperty(audioEvent.dialog) + private val dialog: String? by dialogProperty - val animationProgressProperty = SimpleObjectProperty(null) - var animationProgress: Double? by animationProgressProperty - private set + val animationProgressProperty = SimpleObjectProperty(null) + var animationProgress: Double? by animationProgressProperty + private set - private val animatedProperty = SimpleBooleanProperty().apply { - bind(object : ObjectBinding() { - init { - super.bind(animationNameProperty, parentModel.spineJson.animationNames) - } - override fun computeValue(): Boolean { - return parentModel.spineJson.animationNames.contains(animationName) - } - }) - } - private var animated by animatedProperty + private val animatedProperty = SimpleBooleanProperty().apply { + bind(object : ObjectBinding() { + init { + super.bind(animationNameProperty, parentModel.spineJson.animationNames) + } + override fun computeValue(): Boolean { + return parentModel.spineJson.animationNames.contains(animationName) + } + }) + } + private var animated by animatedProperty - private val futureProperty = SimpleObjectProperty?>() - private var future by futureProperty + private val futureProperty = SimpleObjectProperty?>() + private var future by futureProperty - val audioFileStateProperty = SimpleObjectProperty().apply { - bind(object : ObjectBinding() { - init { - super.bind(animatedProperty, futureProperty, animationProgressProperty) - } - override fun computeValue(): AudioFileState { - return if (future != null) { - if (animationProgress != null) - if (future!!.isCancelled) - AudioFileState(AudioFileStatus.Canceling) - else - AudioFileState(AudioFileStatus.Animating, animationProgress) - else - AudioFileState(AudioFileStatus.Pending) - } else { - if (animated) - AudioFileState(AudioFileStatus.Done) - else - AudioFileState(AudioFileStatus.NotAnimated) - } - } - }) - } + val audioFileStateProperty = SimpleObjectProperty().apply { + bind(object : ObjectBinding() { + init { + super.bind(animatedProperty, futureProperty, animationProgressProperty) + } + override fun computeValue(): AudioFileState { + return if (future != null) { + if (animationProgress != null) + if (future!!.isCancelled) + AudioFileState(AudioFileStatus.Canceling) + else + AudioFileState(AudioFileStatus.Animating, animationProgress) + else + AudioFileState(AudioFileStatus.Pending) + } else { + if (animated) + AudioFileState(AudioFileStatus.Done) + else + AudioFileState(AudioFileStatus.NotAnimated) + } + } + }) + } - val busyProperty = SimpleBooleanProperty().apply { - bind(object : BooleanBinding() { - init { - super.bind(futureProperty) - } - override fun computeValue(): Boolean { - return future != null - } + val busyProperty = SimpleBooleanProperty().apply { + bind(object : BooleanBinding() { + init { + super.bind(futureProperty) + } + override fun computeValue(): Boolean { + return future != null + } - }) - } - val busy by busyProperty + }) + } + val busy by busyProperty - val actionLabelProperty = SimpleStringProperty().apply { - bind(object : StringBinding() { - init { - super.bind(futureProperty) - } - override fun computeValue(): String { - return if (future != null) - "Cancel" - else - "Animate" - } - }) - } + val actionLabelProperty = SimpleStringProperty().apply { + bind(object : StringBinding() { + init { + super.bind(futureProperty) + } + override fun computeValue(): String { + return if (future != null) + "Cancel" + else + "Animate" + } + }) + } - fun performAction() { - if (future == null) { - if (animated) { - Alert(Alert.AlertType.CONFIRMATION).apply { - headerText = "Animation '$animationName' already exists." - contentText = "Do you want to replace the existing animation?" - val result = showAndWait() - if (result.get() != ButtonType.OK) { - return - } - } - } + fun performAction() { + if (future == null) { + if (animated) { + Alert(Alert.AlertType.CONFIRMATION).apply { + headerText = "Animation '$animationName' already exists." + contentText = "Do you want to replace the existing animation?" + val result = showAndWait() + if (result.get() != ButtonType.OK) { + return + } + } + } - startAnimation() - } else { - cancelAnimation() - } - } + startAnimation() + } else { + cancelAnimation() + } + } - private fun startAnimation() { - val wrapperTask = Runnable { - val recognizer = parentModel.parentModel.recognizer.value - val extendedMouthShapes = parentModel.mouthShapes.filter { it.isExtended }.toSet() - val reportProgress: (Double?) -> Unit = { - progress -> runAndWait { this@AudioFileModel.animationProgress = progress } - } - val rhubarbTask = RhubarbTask(audioFilePath, recognizer, dialog, extendedMouthShapes, reportProgress) - try { - try { - val result = rhubarbTask.call() - runAndWait { - reportResult(result) - } - } finally { - runAndWait { - animationProgress = null - future = null - } - } - } catch (e: InterruptedException) { - } catch (e: Exception) { - e.printStackTrace(System.err) + private fun startAnimation() { + val wrapperTask = Runnable { + val recognizer = parentModel.parentModel.recognizer.value + val extendedMouthShapes = parentModel.mouthShapes.filter { it.isExtended }.toSet() + val reportProgress: (Double?) -> Unit = { + progress -> runAndWait { this@AudioFileModel.animationProgress = progress } + } + val rhubarbTask = RhubarbTask(audioFilePath, recognizer, dialog, extendedMouthShapes, reportProgress) + try { + try { + val result = rhubarbTask.call() + runAndWait { + reportResult(result) + } + } finally { + runAndWait { + animationProgress = null + future = null + } + } + } catch (e: InterruptedException) { + } catch (e: Exception) { + e.printStackTrace(System.err) - Platform.runLater { - Alert(Alert.AlertType.ERROR).apply { - headerText = "Error performing lip sync for event '$eventName'." - contentText = if (e is EndUserException) - e.message - else - ("An internal error occurred.\n" - + "Please report an issue, including the following information.\n" - + getStackTrace(e)) - show() - } - } - } - } - future = executor.submit(wrapperTask) - } + Platform.runLater { + Alert(Alert.AlertType.ERROR).apply { + headerText = "Error performing lip sync for event '$eventName'." + contentText = if (e is EndUserException) + e.message + else + ("An internal error occurred.\n" + + "Please report an issue, including the following information.\n" + + getStackTrace(e)) + show() + } + } + } + } + future = executor.submit(wrapperTask) + } - private fun cancelAnimation() { - future?.cancel(true) - } + private fun cancelAnimation() { + future?.cancel(true) + } } enum class AudioFileStatus { - NotAnimated, - Pending, - Animating, - Canceling, - Done + NotAnimated, + Pending, + Animating, + Canceling, + Done } data class AudioFileState(val status: AudioFileStatus, val progress: Double? = null) \ No newline at end of file diff --git a/extras/EsotericSoftwareSpine/src/main/kotlin/ErrorProperty.kt b/extras/EsotericSoftwareSpine/src/main/kotlin/ErrorProperty.kt index 3f20a63..78b8bf1 100644 --- a/extras/EsotericSoftwareSpine/src/main/kotlin/ErrorProperty.kt +++ b/extras/EsotericSoftwareSpine/src/main/kotlin/ErrorProperty.kt @@ -14,67 +14,67 @@ import tornadofx.rectangle import tornadofx.removeFromParent fun renderErrorIndicator(): Node { - return Group().apply { - isManaged = false - circle { - radius = 7.0 - fill = Color.ORANGERED - } - rectangle { - x = -1.0 - y = -5.0 - width = 2.0 - height = 7.0 - fill = Color.WHITE - } - rectangle { - x = -1.0 - y = 3.0 - width = 2.0 - height = 2.0 - fill = Color.WHITE - } - } + return Group().apply { + isManaged = false + circle { + radius = 7.0 + fill = Color.ORANGERED + } + rectangle { + x = -1.0 + y = -5.0 + width = 2.0 + height = 7.0 + fill = Color.WHITE + } + rectangle { + x = -1.0 + y = 3.0 + width = 2.0 + height = 2.0 + fill = Color.WHITE + } + } } fun Parent.errorProperty() : StringProperty { - return properties.getOrPut("rhubarb.errorProperty", { - val errorIndicator: Node = renderErrorIndicator() - val tooltip = Tooltip() - val property = SimpleStringProperty() + return properties.getOrPut("rhubarb.errorProperty", { + val errorIndicator: Node = renderErrorIndicator() + val tooltip = Tooltip() + val property = SimpleStringProperty() - fun updateTooltipVisibility() { - if (tooltip.text.isNotEmpty() && isFocused) { - val bounds = localToScreen(boundsInLocal) - tooltip.show(scene.window, bounds.minX + 5, bounds.maxY + 2) - } else { - tooltip.hide() - } - } + fun updateTooltipVisibility() { + if (tooltip.text.isNotEmpty() && isFocused) { + val bounds = localToScreen(boundsInLocal) + tooltip.show(scene.window, bounds.minX + 5, bounds.maxY + 2) + } else { + tooltip.hide() + } + } - focusedProperty().addListener({ - _: ObservableValue, _: Boolean, _: Boolean -> - updateTooltipVisibility() - }) + focusedProperty().addListener({ + _: ObservableValue, _: Boolean, _: Boolean -> + updateTooltipVisibility() + }) - property.addListener({ - _: ObservableValue, _: String?, newValue: String? -> + property.addListener({ + _: ObservableValue, _: String?, newValue: String? -> - if (newValue != null) { - this.addChildIfPossible(errorIndicator) + if (newValue != null) { + this.addChildIfPossible(errorIndicator) - tooltip.text = newValue - Tooltip.install(this, tooltip) - updateTooltipVisibility() - } else { - errorIndicator.removeFromParent() + tooltip.text = newValue + Tooltip.install(this, tooltip) + updateTooltipVisibility() + } else { + errorIndicator.removeFromParent() - tooltip.text = "" - tooltip.hide() - Tooltip.uninstall(this, tooltip) - updateTooltipVisibility() - } - }) - return@getOrPut property - }) as StringProperty + tooltip.text = "" + tooltip.hide() + Tooltip.uninstall(this, tooltip) + updateTooltipVisibility() + } + }) + return@getOrPut property + }) as StringProperty } \ No newline at end of file diff --git a/extras/EsotericSoftwareSpine/src/main/kotlin/MainApp.kt b/extras/EsotericSoftwareSpine/src/main/kotlin/MainApp.kt index d82c65e..9cd02b6 100644 --- a/extras/EsotericSoftwareSpine/src/main/kotlin/MainApp.kt +++ b/extras/EsotericSoftwareSpine/src/main/kotlin/MainApp.kt @@ -8,18 +8,18 @@ import java.lang.reflect.Method import javax.swing.ImageIcon class MainApp : App(MainView::class) { - override fun start(stage: Stage) { - super.start(stage) - setIcon() - } + override fun start(stage: Stage) { + super.start(stage) + setIcon() + } - private fun setIcon() { - // Set icon for windows - for (iconSize in listOf(16, 32, 48, 256)) { - addStageIcon(Image(this.javaClass.getResourceAsStream("/icon-$iconSize.png"))) - } + private fun setIcon() { + // Set icon for windows + for (iconSize in listOf(16, 32, 48, 256)) { + addStageIcon(Image(this.javaClass.getResourceAsStream("/icon-$iconSize.png"))) + } - // OS X requires the dock icon to be changed separately. + // OS X requires the dock icon to be changed separately. // Not all JDKs contain the class com.apple.eawt.Application, so we have to use reflection. val classLoader = this.javaClass.classLoader try { @@ -37,6 +37,6 @@ class MainApp : App(MainView::class) { } catch (e: Exception) { // Works only on OS X } - } + } } diff --git a/extras/EsotericSoftwareSpine/src/main/kotlin/MainModel.kt b/extras/EsotericSoftwareSpine/src/main/kotlin/MainModel.kt index 6378aad..c1afff9 100644 --- a/extras/EsotericSoftwareSpine/src/main/kotlin/MainModel.kt +++ b/extras/EsotericSoftwareSpine/src/main/kotlin/MainModel.kt @@ -13,51 +13,51 @@ import java.nio.file.Paths import java.util.concurrent.ExecutorService class MainModel(private val executor: ExecutorService) { - val filePathStringProperty = SimpleStringProperty(getDefaultPathString()).alsoListen { value -> - filePathError = getExceptionMessage { - animationFileModel = null - if (value.isNullOrBlank()) { - throw EndUserException("No input file specified.") - } + val filePathStringProperty = SimpleStringProperty(getDefaultPathString()).alsoListen { value -> + filePathError = getExceptionMessage { + animationFileModel = null + if (value.isNullOrBlank()) { + throw EndUserException("No input file specified.") + } - val path = try { - val trimmed = value.removeSurrounding("\"") - Paths.get(trimmed) - } catch (e: InvalidPathException) { - throw EndUserException("Not a valid file path.") - } + val path = try { + val trimmed = value.removeSurrounding("\"") + Paths.get(trimmed) + } catch (e: InvalidPathException) { + throw EndUserException("Not a valid file path.") + } - if (!Files.exists(path)) { - throw EndUserException("File does not exist.") - } + if (!Files.exists(path)) { + throw EndUserException("File does not exist.") + } - animationFileModel = AnimationFileModel(this, path, executor) - } - } + animationFileModel = AnimationFileModel(this, path, executor) + } + } - val filePathErrorProperty = SimpleStringProperty() - private var filePathError: String? by filePathErrorProperty + val filePathErrorProperty = SimpleStringProperty() + private var filePathError: String? by filePathErrorProperty - val animationFileModelProperty = SimpleObjectProperty() - var animationFileModel by animationFileModelProperty - private set + val animationFileModelProperty = SimpleObjectProperty() + var animationFileModel by animationFileModelProperty + private set - val recognizersProperty = SimpleObjectProperty>(FXCollections.observableArrayList( - Recognizer("pocketSphinx", "PocketSphinx (use for English recordings)"), - Recognizer("phonetic", "Phonetic (use for non-English recordings)") - )) - private var recognizers: ObservableList by recognizersProperty + val recognizersProperty = SimpleObjectProperty>(FXCollections.observableArrayList( + Recognizer("pocketSphinx", "PocketSphinx (use for English recordings)"), + Recognizer("phonetic", "Phonetic (use for non-English recordings)") + )) + private var recognizers: ObservableList by recognizersProperty - val recognizerProperty = SimpleObjectProperty(recognizers[0]) - var recognizer: Recognizer by recognizerProperty + val recognizerProperty = SimpleObjectProperty(recognizers[0]) + var recognizer: Recognizer by recognizerProperty - val animationPrefixProperty = SimpleStringProperty("say_") - var animationPrefix: String by animationPrefixProperty + val animationPrefixProperty = SimpleStringProperty("say_") + var animationPrefix: String by animationPrefixProperty - val animationSuffixProperty = SimpleStringProperty("") - var animationSuffix: String by animationSuffixProperty + val animationSuffixProperty = SimpleStringProperty("") + var animationSuffix: String by animationSuffixProperty - private fun getDefaultPathString() = FX.application.parameters.raw.firstOrNull() + private fun getDefaultPathString() = FX.application.parameters.raw.firstOrNull() } class Recognizer(val value: String, val description: String) diff --git a/extras/EsotericSoftwareSpine/src/main/kotlin/MainView.kt b/extras/EsotericSoftwareSpine/src/main/kotlin/MainView.kt index abdd109..f5ee29a 100644 --- a/extras/EsotericSoftwareSpine/src/main/kotlin/MainView.kt +++ b/extras/EsotericSoftwareSpine/src/main/kotlin/MainView.kt @@ -23,235 +23,235 @@ import java.io.File import java.util.concurrent.Executors class MainView : View() { - private val executor = Executors.newSingleThreadExecutor() - private val mainModel = MainModel(executor) + private val executor = Executors.newSingleThreadExecutor() + private val mainModel = MainModel(executor) - init { - title = "Rhubarb Lip Sync for Spine" - } + init { + title = "Rhubarb Lip Sync for Spine" + } - override val root = form { - var filePathTextField: TextField? = null - var filePathButton: Button? = null + override val root = form { + var filePathTextField: TextField? = null + var filePathButton: Button? = null - val fileModelProperty = mainModel.animationFileModelProperty + val fileModelProperty = mainModel.animationFileModelProperty - minWidth = 800.0 - prefWidth = 1000.0 - fieldset("Settings") { - disableProperty().bind(fileModelProperty.select { it!!.busyProperty }) - field("Spine JSON file") { - filePathTextField = textfield { - textProperty().bindBidirectional(mainModel.filePathStringProperty) - errorProperty().bind(mainModel.filePathErrorProperty) - } - filePathButton = button("...") - } - field("Mouth slot") { - combobox { - itemsProperty().bind(fileModelProperty.select { it!!.slotsProperty }) - valueProperty().bindBidirectional(fileModelProperty.select { it!!.mouthSlotProperty }) - errorProperty().bind(fileModelProperty.select { it!!.mouthSlotErrorProperty }) - } - } - field("Mouth naming") { - label { - textProperty().bind( - fileModelProperty - .select { it!!.mouthNamingProperty } - .select { SimpleStringProperty(it.displayString) } - ) - } - } - field("Mouth shapes") { - hbox { - errorProperty().bind(fileModelProperty.select { it!!.mouthShapesErrorProperty }) - gridpane { - hgap = 10.0 - vgap = 3.0 - row { - label("Basic:") - for (shape in MouthShape.basicShapes) { - renderShapeCheckbox(shape, fileModelProperty, this) - } - } - row { - label("Extended:") - for (shape in MouthShape.extendedShapes) { - renderShapeCheckbox(shape, fileModelProperty, this) - } - } - } - } - } - field("Dialog recognizer") { - combobox { - itemsProperty().bind(mainModel.recognizersProperty) - this.converter = object : StringConverter() { - override fun toString(recognizer: Recognizer?): String { - return recognizer?.description ?: "" - } - override fun fromString(string: String?): Recognizer { - throw NotImplementedError() - } - } - valueProperty().bindBidirectional(mainModel.recognizerProperty) - } - } - field("Animation naming") { - textfield { - maxWidth = 100.0 - textProperty().bindBidirectional(mainModel.animationPrefixProperty) - } - label("