07 December 2017

Automation + Scheduling file and application opening...

How do you automate the scheduling of a script/application launching (or any kind of file opening)?

macOS provides at least two solutions when it comes to scheduling file opening and application launching. One is the "
open file alert" thing in Calendar and the other is launchd.

If you don't need to automate the creation of open file alerts and have no problem creating them by hand, you'll do just fine with Calendar. There are tons of articles on the web that describe how to do it. But that is not the subject of this article...



The problem with Calendar


As I discovered over the course of the last weekend when I (at long last) decided to automate my invoicing by programmatically creating invoice launch triggers for each job that I created with my usual script, it is not possible to script Calendar to create proper open file alerts.

Since Calendar became a sandboxed application (which is a good thing), it is not possible anymore to specify the path to the file you want to open with AppleScript. Specifically, the problem is the following: when you check Calendar's dictionary in your script editor, you can see that an open file alert object has three properties: a filepath, a trigger date and a trigger interval. You can set the last two with Applescript, but setting filepath is not allowed. You just can't. Period. And it's not even worth wasting time trying.

In fact, Calendar does not even return the filepath of an open file alert that you'd have successfully created manually. The only thing that's returned is an empty string. The result is that you can only partially script the creation of the open file alert, without the most important part, which is specifying the file that you want to open at the specified time. So you're back to manually completing the creation of the alert, which defeats the purpose of automating this task in the first place.


The solution? launchd!


Calendar being out of the picture, the only solution that remains is launchd.
According to people who know, launchd is extremely powerful, and I'm only going to very slightly scratch the surface of what it can do here, which is still sufficient for what we want to accomplish: create a thing that launches the application/script of your choice at the time you specified and using that creation process in a script.

launchd is not just a process launcher, it is the process launcher on macOS. In fact, launchd is the very first process that is started when you launch your Mac. Once it has launched, it launches, or loads for later launching, all the processes that Mac needs to run properly. launchd also has a number of other roles that go beyond what it relevant here and what I can understand of my machine's internals...

Using launchd to manually schedule processes is very different from doing so in Calendar. launchd only works from the command line, its settings require manipulating XML based plist files, and such files require knowledge of a number of terms specific to the launchd format. For our purpose launchd requires what is called a "launch agent".

Launch agents define the actions to take and their timing in XML Property List files (plist). User created agents are located in ~/Library/LaunchAgents and are "loaded" by launchctl which is the only way the user can interact with the agents and with launchd.

To automate the creation of the equivalent of Calendar's "open file alert", we'll need to create an agent that would look like:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>Label</key>
<string>org.mac4translators.launchd.test</string>
<key>ProgramArguments</key>
<array>
<string>/usr/bin/osascript</string>
<string>~/MyScripts/mypresents.scrpt</string>
</array>
<key>StartCalendarInterval</key>
<dict>
<key>Day</key>
<integer>25</integer>
<key>Hour</key>
<integer>00</integer>
<key>Minute</key>
<integer>00</integer>
<key>Month</key>
<string>December</string>
</dict>
</dict>
</plist>

Then we need to load it, and unload it when it has accomplished its mission.

Using
launchd from AppleScript is not very different from scripting Calendar. It only involves a different set of objects, but not different concepts.

The following code, adapted from what can be found on the web already, creates the agent above and puts it in the location from where we will be able to load it in
launchd. The comments should be sufficient for you to understand what is going on and to create your own launch agents. The highlighted parts are the parts where we'll need to adapt contents so as to truly automate the creation of other agents.

use AppleScript version "2.4" -- Yosemite (10.10) or later
use scripting additions

# Create the file path where the launchd agent plist will be created. As written in the man page, it is a convention that the name of the file should be Label.plist. See below for what "Label" is.
set the launch_agent_path to "~/Library/LaunchAgents/org.mac4translators.launchd.test.plist" #

# System Events and it's plist suite are the tools we need to create the plist itself
tell application "System Events"
# Create an empty property list
set the launch_agent to make new property list item with properties {kind:record}
# Create a new property list file using the empty launch_agent as contents and the launch_agent_path as location.
set this_launch_agent to make new property list file with properties {contents:launch_agent, name:launch_agent_path}
# Now we work inside the property list, that starts empty
tell property list items of this_launch_agent
# Add new property list items consecutively, from the end of the file. The first item is "Label", which should be a unique identifier: you can add a date, or a job name to make it truly unique. As written above, the "Label" value should be the name of the file before the .plist extension
make new property list item at its end with properties {kind:string, name:"Label", value:"org.mac4translators.launchd.test"} #
# The second item is the arguments to the command that will launch your program. The command is "exec", so in the present case, think of launchd as really launching "exec /usr/bin/osascript ~/MyScripts/mypresents.scrpt", which ends up running the mypresents.scrpt script (check "man osascript" for more information on running your scripts from the command line).
make new property list item at its end with properties {kind:record, name:"ProgramArguments", value:{"/usr/bin/osascript", "~/MyScripts/mypresents.scrpt"}} #
# The third item is the date/time you want the thing launched. Notice that this is the "Start Calendar Interval" value. It is not a unique date which is the reason why the "Year" argument is not supported. Your agent will thus run at least once a year, unless it is unloaded after having accomplished its mission. "Month" and "Day" are put between vertical bars to avoid conflict with AppleScript because AppleScript thinks they are AS terminology, but here they're only XML values in the plist file.
make new property list item at its end with properties {kind:list, name:"StartCalendarInterval", value:{|Month|:"December", |Day|:25, Hour:0, Minute:0}} #
end tell
end tell

When you run this code, you should get an XML plist that looks like the one above.

So, we now have a way to programmatically create our launch agent. But to create a new one we'll have to edit the above code and change the highlighted parts, which is still way more complicated than editing a plain XML plist file. That's where real automation of the process comes into play.

What we need to feed the script is the following:

  1. a unique "label" for the agent, used in above
  2. the thing to launch (as we'd launch it from the command line), used in above
  3. the trigger date/interval,  used in above
So, now the script becomes:

use AppleScript version "2.4" -- Yosemite (10.10) or later
use scripting additions

# We'll need to work with specific text items delimiters later so let's store the defaults in a safe place
set savedTextDelimiters to AppleScript's text item delimiters

# We need 3 items to create the Launch Agent:
# 1) A unique label. You can use a job name that you fetch from another script.
set agent_label to text returned of (display dialog "Launch Agent Label?" default answer (short user name of (system info)) & "." & (time of (current date) as text))
set the launch_agent_path to "~/Library/LaunchAgents/" & agent_label & ".plist"

# 2) The command to launch your application/script, as you'd type it in Terminal. Anything that goes on the command line will work. For ex: open -a "Microsoft Word" ~/Documents/myInvoiceTemplate.docx
set agent_command to text returned of (display dialog "Launch Agent Command?" default answer "osascript ~/MyAppleScripts/myInvoicingScript.scrpt")
set AppleScript's text item delimiters to {" "}
set agent_parameters to get text items of agent_command

# 3) The trigger date. "Year" is not something that you can set, so just leave it out.
set agent_trigger_date to text returned of (display dialog "Launch Agent Trigger Date?" default answer "mm/dd/hr/mn")
set AppleScript's text item delimiters to {"/"}
set agent_trigger_date_fields to get text items of agent_trigger_date

# And we return the text delimiters to their default value
set AppleScript's text item delimiters to savedTextDelimiters

# Now we ask System Events and it's plist suite to create the plist itself and to fill it with the data we just fed it. The code being the same as above, comments are left out.

tell application "System Events"
set the launch_agent to make new property list item with properties {kind:record}
set this_launch_agent to make new property list file with properties {contents:launch_agent, name:launch_agent_path}
tell property list items of this_launch_agent
make new property list item at its end with properties {kind:string, name:"Label", value:agent_label}
make new property list item at its end with properties {kind:record, name:"ProgramArguments", value:agent_parameters}
make new property list item at its end with properties {kind:list, name:"StartCalendarInterval", value:{|Month|:item 1 of agent_trigger_date_fields, |Day|:item 2 of agent_trigger_date_fields, Hour:item 3 of agent_trigger_date_fields, Minute:item 4 of agent_trigger_date_fields}}
end tell
end tell

# We have created the launch agent where it can now be loaded into the system to be launched at the date we specified. We just need to make sure everything is fine and then load it with launchctl:

# Open the agent with TextEdit, or change the command to open with your editor of choice.
do shell script "open -e " & launch_agent_path

# Ask whether the agent is fine, and if yes, load it.
tell application "System Events"
tell (first process whose frontmost is true)
set reallyLaunch to button returned of (display alert "Are you fine with this Launch Agent?")
end tell
end tell

if reallyLaunch is "OK" then
set load_agent to "launchctl load " & launch_agent_path
do shell script load_agent
end if

Et voilà! Now we eventually need to separately find a way to regularly check the agents that we created and unload/remove them from ~/Library/LaunchAgents/ after their mission is accomplished... You can for example match the name of the agent to the name of your job and use do shell script to unload and remove the agent once the invoice has been created.

The resulting system is not exactly as elegant as just automating the creation of an "open file alert" in Calendar, but since that is not possible anymore, we don't have much choice.

Update (12/11):
It appears that I had not fully understood the Program key that I had mentioned in the first version of this article. I've removed references to it, but you can still use it in place of  ProgramArguments if your command runs on its own and does not take arguments. The command must be in your path to be discovered by which, but if you've understood the code so far you should be able to change it so that it fits your configuration.

The plist would require the following key:

<key>Program</key>

<string>/Users/[your account]/bin/[your command]</string>

And you'd need the following code (first listing) to generate it:

make new property list item at its end with properties {kind:string, name:"Program", value:"/Users/[your account]/bin/[your command]"} #

Or this code in the second listing:

set agent_command to text returned of (display dialog "Launch Agent Command?" default answer "[your command]")
set AppleScript's text item delimiters to {" "}
set agent_parameters to get text items of agent_command
set agent_program to do shell script "which " & item 1 of agent_parameters
...
make new property list item at its end with properties {kind:string, name:"Program", value:agent_program}


References:
launchd was released in 2005 and included in Tiger that same year. If you want to know more about it, read the following links (pretty much all the places from where I gathered information to write this article):

launchd Wikipedia page:

launchd plist format (man page)

launchd description (man page)

launchctl description (man page)

You can also access the man pages on your local system (
man launchd, man launchctl, man launch.plist)

Apple Developer documentation about launchd:

"Daemons and Agents", Apple Technical Note TN2083

Detailed overview of launchd by Jonathan Levin, who  wrote "the" book on OSX internals:

The chapter from that book that covers launchd (which also happens to be in free access):

A very thorough and very well designed introduction to launchd, by soma-zone, the creators of LaunchControl, "the only full featured launchd GUI for macOS" (I can't download it so I don't know if it is scriptable).

An online tool to easily create launchd agents (and check their syntax before you automate all that with AppleScript):

"How to Use launchd to Run Scripts on Schedule in macOS"

Some AppleScript code to write property lists:

A property list applescript library (not really needed for our purposes, but just so that you know):

Lingon is a FOSS but inactive utility (development has continued on the paid version) that helps you create, visualize and manage the launch agents that run on your machine, it is not applescriptable:

Discussions:
The discussions that triggered the writing of this of this article...

AppleScript official list
https://lists.apple.com/archives/applescript-users/2017/Nov/msg00171.html

AppleScript unofficial list
https://apple-dev.groups.io/g/applescript/topic/7325421#154

Script Debugger user forum
http://forum.latenightsw.com/t/calendar-open-file-alert/839

06 December 2017

Distraction-free (pseudo) mode in TextEdit

I like TextEdit because it is a simple RTF editor that yet offers a number of cool word processing features. Only one thing that it doesn't have is one of those "distraction free" modes where not only do you work full screen, but you also have nice margins around the screen.

Well, it was trivial enough to emulate that with TextEdit itself, I wonder why it took me so long to figure it out.
  1. View > Enter Full Screen
  2. Format > Wrap to Window
That's pretty much it.

The problem is that the "window" size is constrained by the paper size you choose for printing. So you just need to create a paper size that fills your screen:
  1. File > Page Setup...
  2. in Paper Sizes, select Manage Custom Sizes
  3. Create your own big paper size.
My "Grand Écran" size is 450 x 5,000 mm with 0 mm margins.

You can create a System Service with a shortcut by using AppleScript and Automator to switch any TextEdit page into this mode, or revert to a "normal" mode, or just manually select the paper size when you start a new document, or even use it as your default paper size... Et voilà!


24 November 2017

Capture (really) everywhere

Capturing thoughts is an excellent first step to organizing them. And Emacs provides one of the best organization solution in the computer world: org-mode.

When you work in Emacs all the time, you can capture your thoughts and sort them in org-mode from any place thanks to a global shortcut (C-c c by default). If you do not work in Emacs all the time, you can use various shortcuts to put Emacs front and hit C-c c to open a capture template.

My solution to accelerate the process is to use AppleScript to put Emacs in the front and type the capture template opening command.

use AppleScript version "2.4" -- Yosemite (10.10) or later
use scripting additions

try
tell application "System Events" to tell process "Emacs" to set frontmost to true
on error
tell application "/Path/to/Emacs.app"
activate
delay 2
end tell
end try

tell application "/Path/to/Emacs.app"
tell application "System Events"
delay 0.1
keystroke "c" using {control down}
keystroke "c"
delay 0.1
end tell
end tell

I save the above code as an application, with a name I can readily call and that won't conflict with a commonly used word on my machine. I chose ">Capture".

Once the application is saved, calling it and launching it from Spotlight is trivial. The default shortcut for Spotlight is Command+Space. Then you start typing the first characters of your application name and when it appears, you hit Enter.

On my system I hit Ctrl+Space, then >C, and Spotlight shows >Capture. I hit Enter and Emacs comes to the front and has launched the capture template selector. I'm in Emacs now and I can do whatever I want there.

You may run into an issue where Emacs gets a different key even though you ordered AppleScript to hit C-c c. Emacs comes up with an error message like "C-s-268632079 is undefined". On my machine I had 2 such undefined "keys". The trivial solution is to add the keys to your .emacs.el file so that they are associated to org-capture:

(global-set-key (kbd "<C-s-268632079>") 'org-capture)
(global-set-key (kbd "<C-s-268632067>") 'org-capture)

Et voilà! Go capture your thoughts now!

29 October 2017

IceCat on High Sierra

The other day I received a general mail from the Free Software Foundation informing me that even if Safari on High Sierra had pretty good privacy and security features, IceCat, the GNU version of Firefox, was even better.

I was interested and checked the provided links to see what that browser was. I'm not a super fan of Mozilla stuff, but if it's GNU, then why not.

The problem is that the links did not point at any binary or downloadable package. The only thing I could find was the FTP server, with builds for Linux.

Update: It looks like there are builds of IceCat for the Mac, up to version 38.8.0 (released in May '16) on that page.

I wrote to the FSF back to tell them that if they wanted to promote better solutions for macOS users, it would be more effective if the solutions were actually available, either as binaries, or as instructions to build the software.

After checking a bit further, I found this page with instructions to build an 11 months old version on El Capitan: A way to build IceCat 45.5.1 on MacOS El Capitan.

I decided to try with the latest version of IceCat (52.3) on my High Sierra machine, and here are the amended instructions.

If you do not want to go through the build process, I have uploaded the resulting icecat-52.3.0.en-US.mac64.dmg package here, but without ANY warranty about the contents and security, and privacy and everything that you REALLY should care about when you install new software on your machine...

Instructions:

  1. Prepare the development environment:
    1. install Xcode and Homebrew
    2. install yasm with Homebrew
    3. install autoconf@2.13 with Homebrew
    4. if you miss dependencies, the configure step below will tell you and you just need to get them from Homebrew
  2. Download IceCat Source & configure the compilation
    1. wget https://ftp.gnu.org/gnu/gnuzilla/52.3.0/icecat-52.3.0-gnu1.tar.bz2
    2. tar xjvf icecat-52.3.0-gnu1.tar.bz2
    3. cd icecat-52.3.0
    4. mkdir objdir
    5. mkdir -p ./browser/branding/unofficial/moz.build
    6. cd objdir
    7. ../configure --with-l10n-base=../l10n --enable-official-branding
  3. That's it for the preparations. On a side note, the other configure options found in the original instructions were all refused. If configure does not protest, you're ready. If it does, you either miss dependencies (see step 1.4. above), or you have other issues on your machine. Check the net for error messages, fix the errors, and you should be fine. Now comes compiling time, and it's going to take quite some time, so get a book ready.
    1. make
    2. make package
  4. When make package quits without complaining, you're ready. IceCat is in the dist folder. There is one that works out of the box, and one that comes as a .dmg (icecat-52.3.0.en-US.mac64.dmg on my machine). Either seems to work fine.
During the make process, you may run into an error that says something like:
...(stuff) ... CLOBBER dependency dropped ... (more stuff) ...

and the script proposes you: 
To ignore this message, touch "/Users/suzume/Documents/Code/tmp/icecat-52.3.0/configure"

Well just do as suggested:
touch "/Users/suzume/Documents/Code/tmp/icecat-52.3.0/configure"

and relaunch the build process with make. That worked for me.

Ok, make and make package have worked, you have an IceCat build and a .dmg package into /dist, and you want to start and try the thing. Just launch it and enjoy the web, à la GNU.

Now, you may run into a problem when you want to go to a site. The first time you use IceCat, every time you want to go to a site, a error page gets displayed with the message "Content Encoding Error" (etc.)

The fix is really easy: quit IceCat and relaunch it. That's it.

Et voilà! You can now enjoy a (slightly) more secure and privacy aware GNU browser on your Mac! Have fun!

28 October 2017

Transcription software for free?

About 4 years ago I got a job where I needed to transcribe about 40 hours of interviews. I wrote most of this article then and let it to rest until now. The solution I propose is a workable solution for transcribing audio/video and can also can be used as a practical introduction to Applescript. I just tested everything in High Sierra, with the current versions of all the mentioned applications.

This article also demonstrates how a few macOS technologies can be put together to create a very robust and integrated solution in a number of very easy steps. The idea is:

  1. Find a process that you need to automate
  2. Use Applescript to code the automation
  3. Use Automator to create a system-wide service to access the automation
  4. Use System Preferences to assign a shortcut to the service, either available system-wide, or only in a given application
Step 3. and 4. are just one way to access the automation. There are many other ways.

Update (the day after...): a comment on reddit says that the title is not accurate because I do not propose speech recognition. For people not familiar with transcription, plain speech recognition is not a solution because it requires two things: good sound quality and that the software be used to the voice. That is not the case with most transcription situations. But, it is possible to dictate the audio that you are listening to, in which case you'll need the same tools as described here, and you just have to add macOS dictation to the workflow if you want to stick to macOS bundled software.

Homemade transcription software...


Transcribing 40 hours of interviews is a lot of time in front of the machine and even though the only thing you need is a text editor and an audio file player the lack of default integration between the two can make you waste a lot of time on tedious manipulations.

The problem is going from the text editor to the audio player each time you need to pause the stream, step backward, or start the stream again. Then you need to get the Time Code, write it at the beginning of the line and start transcribing again.

There seems to be plenty of professional solutions for this where you can start/pause/rewind the audio player with foot pedals directly from the text editor.

However, if you’re used to your keyboard, doing that directly from the text editor of your choice using shortcuts and without ever leaving the editor would also be a totally satisfying solution.

It is possible to use the media buttons available on your keyboard (except for inserting the Time Code). You can work in the editor and pause/resume and do a few other things directly with the keys that Apple has provided. Although, There are a few problems with that.

The first is that you may have set your keyboard to ignore the Apple supplied keys and directly access the Function keys instead. If you’ve done that, you’ll have to hit the fn key and the corresponding Function key to access the media function you need.

Another problem is that the media keys are not conveniently located on the keyboard. Accessing them often will require that you move your hand from your basic typing position and this will slow you down.

The last and most important problem is that you can’t easily and precisely rewind the file to go back to a moment you did not properly hear…

The media buttons are really made to easily play media, and not to do transcription, so we need a different solution.

One solution would be to have direct access to the appropriate functions of the media player while it runs in the background through shortcut keys while the text editor remains in the foreground.

Here is what I came up with, using the following bundled software/solutions:

  • TextEdit
  • QuickTime Player
  • Applescript / Script Editor
  • Automator
  • System Preferences

TextEdit will be used to type the text. It comes with everything you need to write plain text and you can also use it as a minimalist word processor. You can use anything else as a replacement. The only condition is that the software you use supports macOS Services. Unless you use a very exotic text editor or a virtual environment, that should not be an issue.

QuickTime Player supports a number of audio file formats and its functions can be accessed through Applescript. In the second part of this article I'll use VNC instead because it is more convenient.

Applescript is the Apple solution to link all the applications together. Script Editor is the Apple bundled editor where you can code in Applescript.

Automator is how we will create the “Services” to send commands to QuickTime Player while working in TextEdit.

System Preferences will allow us to assign a shortcut to access the services we just created.

Quicktime Player and Applescript


First of all, we need to play a document in QuickTime Player. Instead of pressing the Play button every time we want to start QuickTime Player, we're going to automate that with AppleScript.

1) Open Script Editor
2) Select File > Open Dictionary, then select QuickTime Player from the list of applications that is displayed

What you see now is the list of QuickTime Player functions that you can access from Applescript. You’ll notice a “QuickTime Player Suite” where player specific functions are documented. There, you can see that the “play” command requires a “document” to run.

3) Select File > New and type the following command:

tell application "QuickTime Player" to play front document

Now, open an audio file with QuickTime Player (do not start playing it yet), go back to your script and click the “Run” button in the Script Editor window (the grey arrow pointing right in the toolbar). You should now see the QuickTime Player start playing your file.

Pretty cool right?

If we just replaced clicking on "Play" in QuickTime Player by clicking on "Run" in Script Editor, we would not have advanced much. We now need to package all that in a way that removes the need to click on a button.

Automator


4) Open Automator and select File > New. Select “Service” and in the search field on the left, above the list of possible actions, enter “Applescript”. You should end up with the command “Run AppleScript”.

5) Drag that command to the right side of the Automator panel. You can now see a “(* Your script goes here *)” where you paste what you just typed in the AppleScript Editor (“tell application "QuickTime Player" to play front document”).

6) Now, there are a few things you need to tweak to make that run properly. Above the command block you just created there is a “Service receives selected [text]” drop menu. Click on [text] and go down the list to select [no input]. Indeed, your code does not need any input to run, so [no input] is the right choice. Leave the “in [any application]” as is since you want to be able to use that service from any application that supports services, not only from TextEdit.

We’re almost done. Once you save the service (give it a name like “QuickTime Play” or anything that makes sense to you), it should appear in the “Services” menu that’s available from the Application menu next to the Apple menu (top left of the screen).

So, the service is available, you can work in your text editor and select it from the menu and it will play the front QuickTime Player document, but just like you want to avoid having to click around, you want to access that service by using a keyboard shortcut so that you don’t have to leave the keyboard while typing.

System Preferences and Services shortcuts


7) At the bottom of the Services menu you’ll find a “Services Preferences” item. Select it and you’ll find yourself in the Keyboard Shortcut section of the System Preferences. Down at the bottom of the list, in the “General” section, you’ll find your newly created service with an “add shortcut” button on the right. Click that button and enter the shortcut you want, but be careful not to use something that’s already used in your text editor. The best way to check that is to open a file in your text editor and to try the shortcut you’re thinking about in various contexts (on selected text, between letters, etc.) If nothing happens, it means you can assign it to the service.

8) Enter TextEdit or the editor you're working with and hit the shortcut. Your audio file should be playing in the background, and you have not left the editor.

After "Play": "Pause" and "Rewind"


We have created a “Play” service. Now we need to have a “Pause” service and a “Rewind” one. The only difference is the AppleScript command that we’ll use.

If the “Play” command was:
tell application "QuickTime Player" to play front document

the “Pause” command will be:
tell application "QuickTime Player" to pause front document

and the “Rewind” command (let’s say 5 seconds backward) will be:
tell application "QuickTime Player" to step backward front document by 20

The “20” is 20 “steps” and after testing a bit it seems that one step is 1/4 of a second, so 5 seconds will be 20 steps.

We seem to be almost done, but there are 2 problems with the above commands.

1) The Rewind command also pauses the file. But if you want to step backward to re-listen a part you did not hear clearly, you want to have the command resume play right after you’ve rewound the file. The solution is, well, to ask QuickTime Player to resume playing after rewinding the file... The new “Rewind” command would look like this:

tell application "QuickTime Player"
step backward front document by 20
play front document
end tell

2) You’ll notice that there is a small time difference between the time you pause and the time you resume the play. Depending of when you stop and resume the playing, the time lag seems to vary between a few hundredth and a few tenths of second. It seems that’s the way QuickTime works and that’s a problem if you need to resume playing when somebody is talking: a 0.2 to 0.3 second gap is enough to miss a sound and misunderstand a word. Now that we know that “step backward” pauses the file, we can use it to pause the file at a satisfying time position, like 2 step before QuickTime Player actually paused (that should be enough). The new “Pause” command would now loo like this:

tell application "QuickTime Player" to step backward front document by 2

We already have a Service and a shortcut to access “Play”, so we just need to follow the same procedure to create the “Pause” and the “Rewind” services.

Now we can start working on our files…

Time codes...


But what about inserting time codes in your document ?

Here is the code from which you’ll be able to create your own service:

tell application "QuickTime Player" to set QTPTIME to (current time of front document)
set MIN to (QTPTIME div 60 as integer)
set SEC to (QTPTIME - (MIN * 60) as integer)

if MIN < 10 then set MIN to 0 & MIN
if SEC < 10 then set SEC to 0 & SEC

set TC to (("TC: " & MIN & ":" & SEC) as text)

set the clipboard to (TC as text)

current time” is the current time of the document being played. The value is given in seconds. So to  convert that into a time code we need some basic arithmetics, which we find in line 2 and 3.

The results are expressed as “normal” numbers and so, for numbers smaller than 10 we’ll need to add a “0” to the number so that the time code has always 2 digits, like “01:01” for “1 minute 1 second”. That is in line 4 and 5.

Then we need to create the time code strings, on line 6. The concatenation command is “&” as we saw in line 4 and 5.

The resulting string that is put into the clipboard would be something like:

TC: 01:01

The last line stores the time code into the OS clipboard so that you can paste it wherever (and whenever) you want in your document. You can use the standard Command+V shortcut to do so.

Now, if you’ve done everything right, you should have something like this in your Services menu:



Et voilà !

An strong alternative to QuickTime Player: VLC


There is an alternative to QuickTime Player that has none of the problems we just described. The software is VLC. It is Free Software and is available directly from the development site. You can make donations to contribute to the development too.

VLC can play a lot of media formats. If you open VLC’s AppleScript dictionary (step 2. above) you’ll see that the “play” command works on the current playlist item and also pauses the stream when it is running. Trying VLC, you can see that when it pauses, it resumes from the same position, so that you don’t have to think of a workaround like we did for the above “Pause” service. Also, the “step backward” command does not stop the stream. If the stream is playing then “step backward” just steps backward and proceeds with playing the file. If the file is paused, then stepping back will keep it paused.

Thanks to VLC’s behaviour we can reduce our 3 above scripts to 2. One would be “play/pause”, the other would be “Step backward”.

The code looks like that:

Play/Pause:
tell application "VLC" to play

Step Backward:
tell application "VLC" to step backward of 2

(the default is 10 seconds, the AppleScript dictionary gives 4 possible values which are, after testing “of 1” for 3 seconds, “of 2” for 10 seconds, “of 3 for 1 minute” and “of 4” for 5 minutes.

VLC makes it simpler to code the solution, but if you want to work only with macOS bundled software, it is also possible to work around QuickTime Player issues, as we've seen above.

There are a lot of areas where the above code can be improved, but the solutions we have work well enough and can be the basis for a lot of other relatively simple developments.


Update (11/9):

I've changed the code a bit after testing on a real job (in VLC).

First, the time code:

tell application "VLC" to set VLCTIME to (current time as integer)
set MIN to (VLCTIME div 60 as integer)
set SEC to (VLCTIME - (MIN * 60) as integer)
if MIN < 10 then set MIN to 0 & MIN
if SEC < 10 then set SEC to 0 & SEC
set TC to (("
" & "TC: " & MIN & ":" & SEC & "
") as text)
set TCstring to (TC as text)

return TCstring

What I changed here is the TC string: I added a line break at the beginning and at the end of the TC string, so that I don't have to insert them myself. That way, I can insert the time code at the end of any line I've just finished and I get

  • a line break → move to the next line
  • a time code → insert the time code
  • another line break → move to the next line

so that I can start to type right away.

I have changed the way the Automator service works too. Instead of feeding the clipboard, and pasting the time code myself, I ask the code to return the string (return TCstring) and I checked the [Output replaces selected text] box at the top of the Automator actions list.



That way the returned value is automatically pasted where I have the cursor.

Another modification, minor this one, in the Step Backward function: I changed the delay from 2 to 1, which is in fact way enough when you just need to clarify a sound. A delay of 2 requires that you wait too long before you can resume typing.

tell application "VLC" to step backward of 1

Now, you must really be careful about the shortcuts so that the don't interfere with normal navigation in the text.

I chose the following:

Control + ]  for "VLC Play+Pause"
Control + [  for "VLC Rewind"
Control + ↓  for "VLC Time Code"

I've just finished transcribing a short 6 minutes interview with this setting and everything worked like a charm.