Personal Auto Playlist

Writing apps for the iPhone is a real pleasure since I began exploring the use of the Swift programming language invented at Apple. One app I have wanted personally for a long time automates the process of creating a music playlist with my own ideas for picking songs randomly based on star ratings and time since I last listened to a song. I explore the concept in some detail at Personal Auto Playlist. This app empowers my iPhone to create the playlist I formerly depended on iTunes and an Applescript program to accomplish.

Personal Auto Playlist

Flagging Apple Photos

Apple’s old iPhotos application for Mac OS X had a convenient feature that allowed me to flag ūüŹī photos for subsequent actions: comparing, editing, exporting, and preparing to share.¬†The new Apple Photos application does not support flagging. All the flags in my photo library were turned into the “Flagged” keyword when I upgraded from iPhoto to Photos. The new¬†Favorite ‚̧ԳŹ marking feature may do what I want. I should take some time to familiarize myself with its usage but until that time, I need a way to easily set and clear the “Flagged” keyword¬†from individual and groups of photos.

Sure, I could bring up the info box for a photo and edit the keyword field to add or remove the “Flagged” keyword. That however is too many steps to repeat for more than a few photos. Scripting to the rescue.

I wrote a couple of AppleScript services that extend the flagging feature to Photos. It is not as convenient as clicking the flag icon in the corner of a photograph, but it is ready at hand from the Photos > Services menu.

The Scripts

This first script is called¬†Flag and, true to its name, it adds the “Flagged” keyword to all currently selected photos in the Photos application window. The name is important, because that is¬†how the command will appear in the Photos > Services menu.

-- Add "Flagged" keyword to selected Photos

tell application "Photos"
  set selected to the selection
  if selected = {} then
    display alert "No photos selected to flag" giving up after 10
  else
    repeat with photo in selected
      set tags to the keywords of photo
      if tags = missing value then
        set tags to {"Flagged"}
      else if tags does not contain "Flagged" then
        copy "Flagged" to the end of tags
      end if
      set the keywords of photo to tags
    end repeat
    display alert "Photos flagged" giving up after 10
  end if
end tell

The other script, appropriately named¬†Unflag, removes the “Flagged” keyword from¬†the currently selected photos.

-- Remove "Flagged" keyword from selected Photos

tell application "Photos"
  set selected to the selection
  if selected = {} then
    display alert "No photos selected to unflag" giving up after 10
  else
    repeat with photo in selected
      set tags to the keywords of photo
      if tags ‚Ȇ missing value and tags contains "Flagged" then
        set newtags to {}
        repeat with tag in tags
          if tag as string is not "Flagged" then
            set end of newtags to tag
          end if
        end repeat
        set the keywords of photo to newtags
      end if
    end repeat
    display alert "Photos unflagged" giving up after 10
  end if
end tell

Installation

These scripts are best used as Automator Workflow services installed in the ~/Library/Services folder.

Run Automator, and choose “Service” as the type of your new document.

Screen Shot 2015-12-28 at 12.17.19 PM.png

Edit the name of the service at the top of the Automator title bar. Change its name to Flag.

Modify the type of information provided to the service at the top of the workflow pane as follows.

Screen Shot 2015-12-28 at 12.21.00 PM.png

Drag a “Run AppleScript” action from the utilities library into the workflow¬†pane then replace the (* Your script goes here *) comment with the¬†Flag AppleScript code listed above.

Screen Shot 2015-12-28 at 12.24.06 PM.png

Close the workflow editor window. Then quit Automator. Run Automator again and cancel out of any requests to open or create a document.

From the File > Open Recent menu, choose Flag. Automator will ask if you want to install the Flag service. Click the Install button.

Screen Shot 2015-12-28 at 12.34.16 PM.png

Repeat these steps to create and install the Unflag service.

You now have two services that will appear in the Photos > Services menu: Flag and Unflag.

Using the Services

Fire up Photos, then select one or more photos.

Choose Photos > Services > Flag. A brief alert will pop up that says the photos have been flagged.

Screen Shot 2015-12-28 at 12.53.34 PM.png

You can bring up the info dialog for one of the selected photos and you will see that the “Flagged” keyword has been added to the photo.

You can use the Photos > Services > Unflag menu command to unflag selected photos.

Now to see if¬†Favorite¬†‚̧ԳŹ is good for anything.

Flagging Apple Photos

Extend string.match to Test and Capture Patterns

[This is a reprint of my posting to inspired-lua.org back in May 27, 2013.]

Pattern matching is a powerful feature of Lua’s standard string library. I use it often to automate text file conversion and reporting. For instance, I use pattern matching in a Lua script to format and email my weekly report.

I have maintained a weekly diary of projects I’ve worked on, accomplishments, meetings attended, plans, business travel, and other information since 1988. Each week is logged in a simple text file. This means that despite the evolution of text editors and file media over the decades, I can still read my original diary files. The Lua script that prepares my weekly report scans my latest diary file and uses pattern matches to extract and format an email that adheres to the current reporting standards where I work.

The string.match¬†function is useful for both testing if a pattern exists in a string and for extracting substrings that match a pattern enclosed in parentheses. Lua calls these substrings ‚Äúcaptures‚ÄĚ. Often I want to do both simultaneously‚Äďtest for a pattern in an if¬†statement and capture substrings. For instance, in my weekly report generator, I have a bit of code:

if line:match("^Weekly Report.+(%d%d)/(%d%d)/(%d%d%d%d)") then
    local month = _1
    local day   = _2
    local year  = _3
    email:setSubject(("John Powers - %s-%s-%s Weekly Report"):format(year, month, day))

The first line checks to see if variable line¬†starts with the text ‚ÄúWeekly Report‚ÄĚ and contains a date. If it matches, as a side effect it also sets global variables _1, _2, and _3 to the captures, i.e. the month, day, and year captured from string line. The built-in definition of string.match¬†does not have this side effect. But we can extend string.match¬†to gain this new capability.

Here is the code I used to modify string.match.

do
  local smatch = string.match     -- keep the original definition of string.match

  -- String matching function
  -- Same results as string:match but as a side effect
  -- places the captures in global variables _1, _2, ...

  function string:match(pat, ...)
    local matches = {smatch(self, pat, ...)} -- call the original match to do the work
    for i = 1, #matches do                   -- #matches == 0 if no matches
      _G["_" .. i] = matches[i]              -- assign captures to global variables
    end
    return unpack(matches)                   -- return original results
  end
end

Note the use of a do … end block. This creates a block that limits the scope of local variable smatch. Only the new function string.match can call it.

Placing captures into global variables is nothing new. Anyone familiar with the AWK, Perl, and Ruby scripting languages will recognize this feature right away.

Extend string.match to Test and Capture Patterns

Once Through a Shuffled iTunes Playlist

I’ll admit I am set in my ways. I experimented with iTunes playlists on my iPod back in the day and finally settled on a pattern of usage that I continue to this day on my iPhone. Or I did until Apple Music arrived with iOS 8.4 to stir things up.

I created¬†a smart playlist (actually a set of smart lists that work together) to manage my favorite songs. I use star ratings to determine how often I want to hear a song. I set rules for when at the earliest to hear a song again. I don’t want to hear from 2-star songs for 48 weeks, 3-star songs for¬†13 weeks, 4-star songs for¬†seven weeks, and 5-star songs for¬†eight days. The playlist is set to shuffle. The songs I like most I would come up every week or so.

Apple’s definition of¬†shuffle changed with the introduction of Apple Music in their 8.4 release of iOS. It used to mean play each song in a shuffled playlist one time through. Now it means randomly pick another song from the playlist to add to the Up Next play order. I could hear the same song a couple or more times while taking a long walk or driving to or from work.

Here is my solution to getting back my shuffled playlist. I created an AppleScript program that when executed turns a¬†playlist into another playlist with the songs in shuffled order. I turn off “shuffle” in my iPhone Music app to play this playlist‚ÄĒthe songs are already in random order. Use the Script Editor application to enter and compile this script. Let’s call this script Shuffle Playlist.

The script

tell application "iTunes"
  -- Get the name of the currently selected playlist
  set thePlaylist to the name of the view of front browser window
  set newPlaylist to "-" & thePlaylist

  -- Get list of tracks
  set pl_tracks to tracks of playlist thePlaylist
  set i to count of pl_tracks
 
  -- Randomize list of tracks
  repeat while i ‚Č• 2
    set j to random number from 1 to i
    tell pl_tracks to set {item i, item j} to {item j, item i}
    set i to i - 1
  end repeat

  -- Remove old shuffled playlist
  try
    delete user playlist newPlaylist
  end try

  -- Create new empty shuffled playlist
  set pl_shuffled to make user playlist with properties {name:newPlaylist}

  -- Add randomized tracks to playlist
  repeat with tr in pl_tracks
    duplicate tr to pl_shuffled
  end repeat
end tell

display alert "Shuffled playlist " & newPlaylist & " created" giving up after 10

The script names the new playlist the same as the old playlist name with a hyphen added to the front.

Using the script

Drop this script into folder ~/Library/iTunes/Scripts. Hold down the option key while clicking on the Finder Go menu to see the normally hidden Library folder.

Now whenever you fire up iTunes, a new Scripts menu appears wedged between the Window and Help menus. The menu title looks somewhat like a scroll.

Click on the Playlists tab to make sure the list of playlists is visible in the left panel. I have a playlist named “Favorites”, the smart list I mentioned above.

Now, select menu Scripts > Shuffle Playlist. A new playlist will appear in the playlist panel, “-Favorites” in my example.

If you take a peek in the new -playlist, you will see the same song names but in a new random order. Sync with your iPhone and now you have a playlist that you can hear in random order without repeats. Make sure you turn off shuffle when you listen to the -playlist.

Once Through a Shuffled iTunes Playlist