Category Archives: Code

Copy video create date from original file

This is the companion script for: Video re-encode script

It uses exiftool to copy the video date atom(s) and setfile (is or was included with Apple’s Developer Tools) to change the file system dates. It will more than likely throw exiftool “File not found” errors and I’m not quite sure why. It has never failed to actually work despite this in my experience, however be very careful to not use this on your original source videos.

Hopefully the settings and prompts make sense.

(* http://strawhousepig.net/

This uses Apple's command line developer tools command 'setfile' to set the filesytem dates.

https://developer.apple.com/download/more/

I believe an Apple Developer account is required for that, but I am not sure.

Uses 'exiftool' to set track date atoms.

https://www.sno.phy.queensu.ca/~phil/exiftool/

*)

-- Set to 'false' to omit initial dialog prompts. Does not affect final confirmation prompt.
property prompt_me : true

-- UTC timezone. Leave blank ("") to not use the timezone setting.
property tzone : "-8:00"

on run
  do_it(false)
end run

on do_it(redo)
  repeat
    if tzone is not "" then set tzone to "-timezone="" & tzone & "" " -- There is a trailing space here.    
    if prompt_me is true then
      if button returned of (display dialog "Choose a TARGET file to set creation and modification dates of.") is "OK" then
        set newfile to choose file with prompt "Select TARGET file to set creation and modified dates of:"
      end if
    else
      set newfile to choose file with prompt "Select TARGET file to set creation and modified dates of:"
    end if
    
    set filepath to newfile as string
    if prompt_me is true then
      if button returned of (display dialog "Choose a SOURCE file to *get* creation and modification dates from.") is "OK" then
        set oldfile to choose file with prompt "Select SOURCE file to get dates from: " & filepath
      end if
    else
      set oldfile to choose file with prompt "Select SOURCE file to get dates from: " & filepath
    end if
    set newfilename to name of (info for of newfile)
    if newfilename is not (name of (info for of oldfile)) then
      set warn_button to button returned of (display alert "File names do not match. Proceed?" as warning buttons {"Cancel", "Start Over", "Proceed"})
      if warn_button is "Cancel" then
        return
      else
        if warn_button is "Start Over" then
          set redo to true
          exit repeat
        end if
      end if
    end if
    tell application "System Events" to set [c_date, m_date] to [creation date of oldfile, modification date of oldfile]
    set [p_date_c, p_date_m] to [c_date, m_date] -- Saving "pretty" date for dialog prompt.
    set c_date to ((month of c_date as integer) & "/" & day of c_date & "/" & year of c_date & " " & hours of c_date & ":" & minutes of c_date & ":" & seconds of c_date) as string
    log c_date
    set m_date to ((month of m_date as integer) & "/" & day of m_date & "/" & year of m_date & " " & hours of m_date & ":" & minutes of m_date & ":" & seconds of m_date) as string
    log m_date
    if button returned of (display dialog "This will set the creation and modification dates of file " & filepath & " to:" & return & return & p_date_c & return & p_date_m & return & return & "Proceed?") is "OK" then
      try
        -- Have to include full path to exiftool binary because AppleScript uses the Bourne shell which doesn't have /usr/local/bin in its path? *rolls eyes*
        do shell script "/usr/local/bin/exiftool " & tzone & "-*date="`/usr/local/bin/exiftool -time:CreateDate " & quoted form of POSIX path of oldfile & "`" -wm w " & quoted form of POSIX path of newfile
      on error theErr
        -- Will throw a "File not found" error if this takes over a certain amount of time. rm must be running faster than exiftool can finish?
        display dialog "The 'exiftool' command said: " & theErr
      end try
      delay 1
      try
        -- Ready for this, exiftool adds "_original" to the end of the file it's going to modify tags/atoms of. This is still 'newfile' but with a new name.
        -- If we manually add "_original" to 'newfile' rm will throw an error looking for newfile_original_original... Wha?
        do shell script "rm " & quoted form of POSIX path of newfile
      on error theErr
        display dialog "The 'rm' command said: " & theErr
      end try
      delay 1
      try
        -- Ready again? The original path to 'newfile' is valid again for this... Wha??
        do shell script "setfile -d '" & c_date & "' -m '" & m_date & "' " & quoted form of POSIX path of newfile
      on error theErr
        display dialog "The 'setfile' command said: " & theErr
      end try
    end if
    exit repeat
  end repeat
  if redo then do_it(false)
end do_it

Walk down a directory to label (and reveal) zero byte files

_count is not working and I’m not sure why. :/

The walkFolder() function is recursive, so be careful where you choose to run this. Made to help identify corrupt old archives that have been copied (and copied) from Mac to WINDOWS to CDs and back.

on walkFolder(f, _count)
  list folder f
  repeat with i in the result
    set _item to (alias (f & i))
    if folder of (info for _item) then
      walkFolder(f & i & ":", _count)
    else
      if first character of i is not "." then
        if size of (info for _item) is 0 then
          tell application "Finder"
            set label index of _item to 7 -- Gray. Perhaps adding a spotlight comment would be better...
          reveal _item -- Comment this out if you expect a lot of results.
          end tell
          set _count to _count + 1
        end if
      end if
    end if
  end repeat
  return _count
end walkFolder

on run {}
  set _count to 0
  try
    tell application "Finder" to set cwd to folder of window 1 as alias
  on error
    set cwd to path to desktop folder
  end try
  set theFolder to (choose folder "Select a folder to list:" default location cwd) as string
  my walkFolder(theFolder, _count)
  display dialog (_count as text) & " files found to be 0 bytes" giving up after 600
end run

Apologies for formatting errors as I’m editing this post on Safari from OS X 10.5 which doesn’t work well with WordPress’ auto-drafting.

AppleScript Folder Action to auto-trim folder

[UPDATE] This does not work as expected. Doesn’t seem to run as a plain AppleScript and when re-worked into an Automator folder action workflow (word salad anyone?) it might work… It has and it also hasn’t. But, even when it has I have the sneaking suspicion iCloud does not propagate deletes from macOS. Wouldn’t be the only inexplicable behavior from that service.

[UPDATE]This has been moved to a Google Drive folder. I’ve had enough of ghost folders taking up all the space. The principle is however the same, except now it just works. Boom. Roasted.

I have a security camera that FTPs a new folder everyday into a local iCloud folder on my home server. FYI, the real location of your iCloud Drive is within ~/Library/Mobile Documents, which is a package folder.

This storage process has not proved ideal since iCloud doesn’t handle automation very well. It works with Finder windows and Save dialogs, but background processes not so much. The result for me has been constant reminders that my iCloud drive is “full” despite deleting the bulk of folders. Most of the space never comes back and many times folders just “deleted” reappear.

Maybe adding another automated process to this soup is not going to help and may make things worse (will bird even notice when items are removed this way?), but what the hell. This script will delete files oldest first until the number of items it counted when it was called reaches the “keep” count (set to “5” here).

This may be attached to any folder, and items may be sent to the Trash or deleted immediately with rm. but be aware that it does not send files to the Trash. They are removed with rm.

-- http://strawhousepig.net/

-- Number of items to keep.
property keep : 5

-- Number of seconds until the script may run again (incase of runaway process adding to folder).
property cycle : 10

-- plist to store time last run.
property the_dom : "applescript.folderaction.trimfolder"

-- Should items be deleted immediately (set to false) or sent to the Trash (set to true)?
property trashit : true

on adding folder items to this_folder after receiving added_items
  do_it(this_folder)
end adding folder items to

on run
  set this_folder to choose folder
  do_it(this_folder)
end run

on do_it(this_folder)
set the_key to "epoch"
set the_val to (do shell script "date '+%s'")
  if my read_array(the_dom, the_key, the_val, cycle) is true then return
  do shell script "defaults write " & the_dom & " " & the_key & " " & quoted form of the_val
  tell application "Finder"
    set _items to items of this_folder
    set _items to (sort _items by creation date)
  end tell
  if (count of _items) is greater than keep then
    try
      repeat until (count of _items) is equal to keep
        if trashit is true then
          delete item 1 of _items
        else
          do shell script "rm -R " & quoted form of POSIX path of (item 1 of _items as alias)
        end if
        set _items to rest of _items
      end repeat
    on error theErr number theNum
      if theNum is not -128 then display dialog theErr giving up after 60
    end try
  end if
end do_it

on read_array(the_dom, the_key, the_val, cycle)
  try
    set last_run to (do shell script "defaults read " & the_dom & " " & the_key)
  on error -- Will error if there is nothing to read.
    return false
  end try
  if the_val - last_run is less than cycle then
    return true
  else
    return false
  end if
end read_array

Sync folders when drive is plugged in.

[UPDATE] Didn’t realize how dangerous this is if you actually need to get a backup file(s) off the drive rather than restore from catastrophic failure. Now prompts to confirm sync before proceeding. Should also prompt to eject when complete.

The previous AppleScript post is an earlier version of nearly the same thing. I posted it here to be able to write a new script for where I work. Its intention is to keep an on-site duplicate of an off-site backup. This can of course be used for simple backups and/or folder syncing operations if given the appropriate trigger(s).

To have this script run automagically attach it as a Folder Action to the Volumes folder (/Volumes).

Also, as the previous post, this script calls an rsync binary in a custom install location. Please be aware of that.

Because I can: rsync manual

-- The name of the volume to watch for.
property trigger : "Backup"

-- The paths of the folders to sync. Drag-n-drop supported (choose "Alias").
property theFolders : {"/Do not/forget to/add quotes around", "/And/commas between/items"}

-- rsync options. I use '-rtE' (recursive, preserve times, copy Extended attributes/resource forks)
property rsync_options : "-rtE --delete" -- For mirroring the contents of source dir.
-- property rsync_options : "-rtE --ignore-existing" -- For keeping deleted files on backup. WARNING: also dupes files moved within the source dir.
-- See the rsync manual (man rsync) for more info.

property skip : {} -- Leave empty. Meant to hold items from theFolders which gave rsync an error so we can start over but ignore items in this list.

on adding folder items to this_folder after receiving added_items
  try
    repeat with i in added_items
      if name of (info for i) is trigger then
        if the result of (display dialog trigger & " drive connected. Would you like to sync available source folders now?" giving up after 600) is not false then
          do_it(skip)
          do shell script "logger 'Sync backup folders: Complete'"
          ejectit(trigger)
        end if
        exit repeat
      end if
    end repeat
  on error theErr
    do shell script "logger 'Sync backup folders: " & theErr & "'"
  end try
end adding folder items to

on run
  do_it(skip)
  do shell script "logger 'Sync backup folders: Complete'"
  ejectit(trigger)
end run

on ejectit(trigger)
  if the result of (display dialog "Backup sync complete. Eject drive '" & trigger & "'?") is not false then
    tell application "Finder" to eject trigger
  end if
end ejectit

on do_it(skip)
  tell application "System Events" to set trigger_path to POSIX path of every disk whose name is trigger -- When a drive is mounted and its previous mount point was not removed the new mount point will be given a unique number to follow the name (which isn't shown in the Finder), e.g. "/Volumes/Backup 1". This asks the System for the path rather than us blindly using text.
  if trigger_path is {} then
    display dialog "The drive '" & trigger & "' does not appear to be mounted. :("
    return
  end if
  set rsync_options to rsync_options & " "
  repeat with i in theFolders
    tell application "Finder"
      if not (exists (i as POSIX file)) then
        set end of skip to i as text
      end if
    end tell
    log skip
    if i is not in skip then
      try
        do shell script "/usr/local/bin/rsync " & rsync_options & quoted form of i & " '" & trigger_path & "/'"
        set end of skip to i as text
      on error the_err
        if i is last item in theFolders then
          display dialog "rsync encountered a problem: " & the_err
        else
          if button returned of (display dialog "rsync encountered a problem: " & the_err & return buttons {"Cancel", "Continue"}) is "Continue" then
            set end of skip to i as text
            do_it(skip)
          end if
        end if
      end try
    end if
  end repeat
  set skip to {}
end do_it

This script includes error handling that isn’t well tested. Getting it to work with it at all was a bit of a trick (particularly if not (exists (i as POSIX file)) since if not (exists POSIX file i) does not work as expected). So that whole “skip” idea may not work as intended if you are asked to continue. The last do_it(skip) call may need to be my do_it(skip) instead.

Auto-backup of Photos Library.photoslibrary when portable drive is attached

This script relies on Folder Actions to fire it once it is attached to /Volumes and the appropriately named hard drive is plugged in. I use it to update my off-site photo backup drive.

Settings in the script are commented with instructions, which are hopefully clear enough. It does mention iPhoto, because it was written when iPhoto was a thing. A good thing, a great thing even. Now… *meh*

The options for rsync are fairly standard (recursive, preserve times, extended attributes, and skip files already on the backup). The path to rsync is from OS X shipping with a version of rsync that was unbearably slow and me installing a newer version. I’m not sure when Apple ditched the older rsync version, but it sure wasn’t soon enough. I’m leaving it here for example purposes, but this script will fail if you don’t have an rsync binary there.

-- The name of the physical disk.
property top_drive : "Portable HDD"

-- The name of the disk image file.
-- If it is beyond root of top_drive, the (non-POSIX) path will need to be added. ie "backups:iPhoto backup.dmg"
property iPhoto_dmg : "Photos backup.sparseimage"

-- The name of the disk image drive.
property iPhoto_drive : "Photos backup"

-- POSIX path to iPhoto Library (Default is ~/Pictures/iPhoto Library).
property iPhoto_lib : quoted form of (POSIX path of ((path to pictures folder) as text) & "Photos Library.photoslibrary")

-- rsync options
property rsync_options : "-rtE --ignore-existing"

-- Path to rsync binary.
property rsync_path : "/usr/local/bin/" -- Leave blank ("") if not using a user installed version of rsync.

on logErr(the_msg, the_num)
  do shell script "logger '" & "iPhoto backup: " & the_msg & " (" & the_num & ")'"
end logErr

on adding folder items to this_folder after receiving added_items
  set new_item to item 1 of added_items as text
  if new_item is (top_drive & ":") then
    tell application "Finder"
      try
        open top_drive & ":" & iPhoto_dmg as alias
        delay 10
      on error errMsg number errNum
        my logErr(errMsg, errNum)
      end try
    end tell
    set n to 0
    try
      set rsync_options to rsync_options & " "
      repeat until n = 10
        tell application "Finder" to set all_drives to name of every disk
        if iPhoto_drive is in all_drives then
          set start_time to current date
          set rsync_it to (do shell script rsync_path & "rsync " & rsync_options & iPhoto_lib & " " & quoted form of ("/Volumes/" & iPhoto_drive))
          set total_time to (current date) - start_time
          my logErr("Done", "" & total_time & " sec")
          exit repeat
        else
          delay 6
          set n to n + 1
        end if
      end repeat
      tell application "Finder" to eject disk iPhoto_drive
      display dialog "iPhoto backup complete." & return & return & "You may now eject the disk "" & top_drive & ""." with icon 2
    on error errMsg number errNum
      my logErr(errMsg, errNum)
      display dialog "Oops! An error occurred. :(" & return & return & errNum & " : " & errMsg with icon 0
    end try
  end if
end adding folder items to

Using Netatalk & CUPS

Using 20+ year old hardware is as much a curse as it is a blessing.

Set-Up:

Imagesetter (AccuSet 1000) – RIP (PowerMac 7500) – CUPS server (Raspberry Pi) – Clients (OS X 10.5, macOS 10.12)

The OS X 10.5 client currently serves as an MDNS print server for the newer OS machine since that is the last Macintosh OS version to speak AppleTalk, which the even older RIP requires (thankfully via ethernet).

In this day and age OS X 10.5 (Intel) has nothing close to a modern web browser. Thanks to the incredible persons at floodgap.com, 10.5 on PPC does in the wonderful TenFourFox, which (as its name implies) means OS X 10.4 has a more modern web browser than 10.6. Hell, Mac OS 9 has Classilla (progenitor of TenFourFox). In a perfect world I would use the 10.5 machine as the server for a newer, betterer everyday workstation, but that money doesnt exist. So out comes the Raspberry Pi.
Continue reading

Postfix PCRE TLD body check

This is used to reject spam e-mail containing a URL to the top level domains .date or .loan, and can of course be modified to include others. If you feel your users may ever have a legitimate need to be contacted about these domains it would be best to not use this.

Some familiarity with perl compatible regular expressions in postfix will be helpful.

/https?://([a-z,0-9,-]+.){1,3}(date|loan)// REJECT

This pattern matches both http and https protocols, and links containing from 1 to 3 subdomains of the TLD (http://3.2.example.com/ or https://example.com/ would match were .com the target).

Insert that line into your custom body checks file and no more spam du jour. Enjoy.

Parse e-mail for UPS tracking numbers

This AppleScript will parse the selected e-mail in Mail.app for UPS tracking numbers and will open the tracking page of the UPS website in your default browser.

This only looks at a single e-mail, but could be made to repeat through multiple selections.

tell application "Mail"
  set _messages to selection as list
  set input to content of item 1 of _messages
  try
    open location my parse_nums(input)
  on error
    display dialog "No UPS tracking info found."
  end try
end tell

on parse_nums(input)
  set input_new to ""
  set output to ""
  set input to every word of input
  repeat with i in input
    try
      if first character of i is "1" and second character of i is "Z" then --Every UPS # I've seen starts with '1Z'
        set input_new to input_new & i & "%0D%0A" as string -- 
URL encoded carriage return line feed needed for multiple entries.
      end if
    end try
  end repeat
-- Now to remove the last set of new line characters.
  repeat with c in characters 1 through ((count of characters of input_new) - 6) of input_new
    set output to output & c
  end repeat
  set the_URL to "https://wwwapps.ups.com/WebTracking/track?track.x=Track&trackNums=" & output
  return the_URL
end parse_nums

Set creation and modification date

This is to correct file times on photos that were imported from cameras with bad date settings or when they have been transferred to-and-fro and picked up the wrong data some where or after re-encoding high bit rate video to save a few GBs. [This does not alter the date atoms encoded in (most?) video files. ffmpeg can do that, however.]
Continue reading