-
-
Save psobot/81635e6cbc933b7e8862 to your computer and use it in GitHub Desktop.
import os | |
import sys | |
import time | |
import atomac | |
import subprocess | |
if len(sys.argv) < 2: | |
print "Usage: bouncer.py <path_to_logic_project> (<path_to_logic_project>)" | |
os.exit(1) | |
bundleId = 'com.apple.logic10' | |
for project in sys.argv[1:]: | |
projectName = project.split('/')[-1].replace('.logicx', '') | |
filename = projectName + ".wav" | |
print "Opening %s..." % project | |
# Open a project file | |
subprocess.call(['open', project]) | |
print "Activating Logic Pro X..." | |
logic = atomac.getAppRefByBundleId(bundleId) | |
logic.activate() | |
print "Waiting for project '%s' to open..." % projectName | |
while len(filter(lambda x: projectName in x.AXTitle, logic.windows())) == 0: | |
time.sleep(0.1) | |
# Wait for the window to load | |
time.sleep(1) | |
print "Triggering bounce operation..." | |
logic.activate() | |
logic.sendGlobalKeyWithModifiers('b', [atomac.AXKeyCodeConstants.COMMAND]) | |
print "Waiting for bounce window..." | |
bounce_window = None | |
while not bounce_window: | |
bounce_window = filter(lambda x: ('Output 1-2' in x.AXTitle) or | |
('Bounce' in x.AXTitle), | |
logic.windows()) | |
time.sleep(0.1) | |
bounce_window = bounce_window[0] | |
print "Selecting output formats..." | |
qualityScrollArea = bounce_window.findFirst(AXRole='AXScrollArea') | |
qualityTable = qualityScrollArea.findFirst(AXRole='AXTable') | |
for row in qualityTable.findAll(AXRole='AXRow'): | |
rowName = row.findFirst(AXRole='AXTextField').AXValue | |
checkbox = row.findFirst(AXRole='AXCheckBox') | |
if rowName == 'PCM': | |
if checkbox.AXValue is 0: | |
print "Selected %s output format." % rowName | |
checkbox.Press() | |
else: | |
print "%s output format selected." % rowName | |
elif checkbox.AXValue is 1: | |
print "Deselected %s output format." % rowName | |
checkbox.Press() | |
print "Pressing Bounce button..." | |
bounce_button = bounce_window.findFirst(AXRole="AXButton", | |
AXTitle="Bounce") | |
if not bounce_button: | |
bounce_button = bounce_window.findFirst( | |
AXRole="AXButton", | |
AXTitle="OK" | |
) | |
bounce_button.Press() | |
bounce_window = None | |
# bounce_window is now gone and we have a modal dialog about saving | |
print "Waiting for save window..." | |
save_window = None | |
while not save_window: | |
save_window = filter(lambda x: ('Output 1-2' in x.AXTitle) or | |
('Bounce' in x.AXTitle), | |
logic.windows()) | |
time.sleep(0.1) | |
save_window = save_window[0] | |
print "Entering filename..." | |
filenameBox = save_window.findFirst(AXRole="AXTextField") | |
filenameBox.AXValue = filename | |
print "Pressing 'Bounce' on the save window..." | |
bounce_button = save_window.findFirst(AXRole="AXButton", AXTitle="Bounce") | |
bounce_button.Press() | |
# Check to see if we got a "this file already exists" dialog | |
if len(save_window.sheets()) > 0: | |
print "Allowing overwriting of existing file..." | |
overwrite_sheet = save_window.sheets()[0] | |
overwrite_sheet.findFirst(AXRole="AXButton", | |
AXTitle=u"Replace").Press() | |
print "Bouncing '%s'..." % projectName | |
# All UI calls will block now, because Logic blocks the UI thread while bouncing | |
while len(logic.windows()) > 1: | |
time.sleep(0.1) | |
print "Waiting for Logic to regain its senses..." | |
time.sleep(2) | |
# Done - should be saved now. | |
# Close the window with command-option-W | |
logic.activate() | |
time.sleep(1) | |
print "Closing project '%s'..." % projectName | |
logic.sendGlobalKeyWithModifiers('w', [ | |
atomac.AXKeyCodeConstants.COMMAND, atomac.AXKeyCodeConstants.OPTION | |
]) | |
print "Waiting for the 'do you want to save changes' window..." | |
save_window = None | |
attempts = 0 | |
while not save_window and attempts < 20: | |
save_window = filter(lambda x: '' == x.AXTitle, logic.windows()) | |
time.sleep(0.1) | |
attempts += 1 | |
if save_window: | |
print "Saying 'No, I don't want to save changes'..." | |
save_window = save_window[0] | |
logic.activate() | |
# Click the "Don't Save" button | |
filter(lambda x: 'Don' in x.AXTitle, save_window.findAll(AXRole="AXButton"))[0].Press() | |
print "Waiting for all Logic windows to close..." | |
while len(logic.windows()) > 0: | |
time.sleep(0.5) | |
print "Terminating Logic." | |
atomac.terminateAppByBundleId(bundleId) |
This could be done for sure - the same library I'm using here (atomac) could be used to click the bypass button on any channel strip, or any other button for that matter. Unfortunately I don't quite have time to investigate making a change like that, but it shouldn't require a huge amount of modification to this script. Best of luck!
For folks who visit this in the future and can't get this to work: atomac requires python2 and its dependency, pyobjc, requires python3. To work around this, you can use the fork atomacos which uses python3 as well.
Thanks @psobot for sharing—this helped a ton. I've adapted this script for transposing and subsequently bouncing music using the updated library: https://github.com/gzinck/logic-automator
Can logic be installed and run from an AWS ec2 instance?
@Scylla2020 Logic Pro only runs on macOS, so likely not. If you can find a VPS provider that will let you run macOS, nothing should prevent you from running this script.
Ah, that's a shame. Thought as much. Hopefully they allow it in the future! Thanks for the reply.