Tag Archives: AppleScript

AppleScript to write Finder tag from folder name

Published / by Kevin / Leave a Comment

This is a weird one that came from a request on reddit.

Not really useful (to me) as intended, but I envision it could be re-worked to a FolderAction that would help tag photos. Especially if coupled with a file sorter. But incorporating the tag writing into the sorter would be much more sane.

The commented out do shell script lines were meant as a way to read existing tags and then add the folder name after them. That didn’t work out thanks to xattr -p spitting out hexadecimal instead of a plist array, which is the format used when writing the tags. WTF, Apple…

-- http://strawhousepig.net/
on run
    set _drop to {}
    set end of _drop to (choose folder)
    my do_it(_drop)
end run

on open _drop
    my do_it(_drop)
end open

on do_it(_drop)
    display dialog "WARNING: This script will overwrite ALL tags of files in or targeted from the opened folder with the name of the opened folder." with icon 0
    repeat with d in _drop
        if folder of (info for d) is true then
            set f to {}
            try
                tell application "Finder"
                    set _tag to "<string>" & name of (info for d) & "</string>"
                    set _files to (every item in d)
                    repeat with f in _files
                        if alias of (info for f as alias) is true then
                            set f to original item of f as alias
                        end if
                        -- 'xattr -p' will print the value for a named metadata ID. Naturally that value is printed as hexadecimal. :|
                        --                      set f_plist to (do shell script "xattr -p com.apple.metadata:_kMDItemUserTags " & quoted form of POSIX path of (f as alias))
                        --                      set _tag to (do shell script "echo \"" & f_plist & "\" | egrep -o \"<string>*</string>\"") & _tag
                        set tag_plist to "<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\"><plist version=\"1.0\"><array>" & _tag & "</array></plist>"
                        do shell script "xattr -w com.apple.metadata:_kMDItemUserTags " & quoted form of tag_plist & " " & quoted form of (POSIX path of f)

                    end repeat
                end tell
            on error _err
                display dialog _err
            end try
        end if
    end repeat
end do_it

Parsing e-mail for info

Published / by Kevin / Leave a Comment

[UPDATE] Added a version that works on OS X 10.4 to the bottom of this post. Does some different logging and is tighter, so I’ll have to go back to the original and update again.

This was done earlier, but I have since had to change it to match changes in the incoming data. Some of which is a big mystery. In particular a new line character that is input as the “line separator” Unicode character. It would end up in the clipboard as line feed, so I just started plugging in white space character id’s until one hit the mark. Lucky me.
Continue reading

Convert PDF to grayscale using GhostScript

Published / by Kevin / Leave a Comment

[UPDATE] Huge caveat! While this is really meant to deal with RGB documents or objects, it does also convert CMYK and spot colors to grayscale. Unfortunately spot colors remain as separate plates that retain their name. This results in them being printed in color though on screen they appear gray. At least the colors (Pantone) and printer I tested on took spot channels and ripped them according to what they are named. If your printer does not have it’s own rendering library that matches your spot color names then I’m guessing they will print as grayscale.

It is possible to rasterize the art, which results in a pure grayscale, but you need such a high resolution even a simple business card with type and line art increases in size at least 30 times. I knew this was all too easy…

This script / app / droplet will convert a PDF or PostScript (.ps) document from whatever colorspace it lives in to grayscale. Has not been thoroughly tested. I mean, I ran it against a couple of files. One to work out the gs command, the other to test if it actually made a grayscale duplicate. That file was generated by MS Word so was of course RGB. The converted document was in fact true grayscale. I would say it converted to what you would get if you used a gray gamma 2.2 profile.
Continue reading

Convert PDF to JPEG using (old version of) Acrobat

Published / by Kevin / Leave a Comment

This script will use Acrobat’s built in conversion settings (set in Acrobat’s preferences) to convert an open PDF to a new JPEG file and place that file in the folder of said PDF. If a file with the same name (including extension) exists it will prompt for a new name, at which point you may enter a new name or keep it the same and replace the old file.

Not allowing Acrobat to name the new file because, on Macintosh, Acrobat has a ridiculously low character limit when auto-generating a file.

Has not been tested with multi-page PDF’s, but I do not think it will handle them correctly [it does not]. Acrobat will do it, but the extra file shuffling will not. Also unknown if having a window (document) minimized will cause any shenanigans.

Also note this is written for Acrobat 7. Why? Because old hardware needs old software.

[UPDATE] Changed to allow multiple selections.

tell application "Adobe Acrobat 7.0 Professional"
try
  set _docs to name of every document
  if (count of _docs) is greater than 1 then
    set _docs to (choose from list _docs with prompt "Select file(s) to convert:" with multiple selections allowed) as list
  end if
  repeat with i in _docs
    set _doc to (every document whose name is i)
    set _path to file alias of item 1 of _doc
    set _temp to (path to temporary items folder as text) & "acrobat_tmp_jpg"
    save item 1 of _doc to file _temp using conversion "com.adobe.acrobat.jpeg"
    tell application "Finder"
      set _name to (text items 1 thru -5 of i) & ".jpg"
      set _proof to ((container of file _path) as text) & _name
      if (exists _proof) then
        set _name to text returned of (display dialog "File '" & _name & "' already exists. Please enter a new name (or leave the same to replace):" default answer _name as text)
        set _proof to ((container of file _path) as text) & _name
      end if
      do shell script "mv " & quoted form of POSIX path of _temp & " " & quoted form of POSIX path of _proof
      reveal file _proof
    end tell
  end repeat
end try
end tell

Video re-encode script

Published / by Kevin / Leave a Comment

The purpose of this long, arduous venture was to find a way to simply reduce the size of video files recorded with a Canon ELPH 330 HS digicam. This camera, and surely others, records 720p, 30fps video at a bitrate of 24Mbps, which is at least twice as much as should be necessary. Remember this is a consumer level point-&-shoot whose main draws were: 10x optical zoom; pocketable. This high bitrate results in a 6 minute video being over 1GB. Too much.
Continue reading

Copy video create date from original file

Published / by Kevin / Leave a Comment

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

Published / by Kevin / Leave a Comment

_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

Published / by Kevin / Leave a Comment

[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

Auto-eject drive Folder Action

Published / by Kevin / Leave a Comment

This script is meant to auto-eject a drive after the amount of time set in the script. It will prompt you first, so you may cancel at that time.

Why would this be useful? I have a Garmin Edge 130 bicycle computer. I had been using my phone, but every app/service has binding arbitration garbage in their 50 page click contract (aka baloney), even so far as with Strava flat out stating they may delete your account if you opt-out of it. Via real mail, of course. Outrageous. So I went with Garmin, who actually have a much smaller click contract. Like a breath of fresh air.

At any rate, my wife also has a Garmin Forerunner 35 watch and, like my Edge 130, includes a drive which mounts on your desktop when plugged into a computer. I believe this drive is used for manually installing watch faces or “widgets” and data screens. Although Garmin makes software to load those sort of things.

We typically plug these devices into a computer to charge them and when they are unplugged the Finder complains about the drive having not been ejected properly and file damage or something. Who even reads those things? That’s where this script comes in and why the trigger is “GARMIN”.

This script is meant to be attached as a Folder Action on the Volumes folder.

-- http://strawhousepig.net/

-- Name of disks to watch out for.
property triggers : {"GARMIN"}

-- Number of seconds until the script executes its mission unless user cancels.
property TKO : 30

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

on run
  do_it()
end run

on do_it()
  tell application "Finder"
    try
      set these_hds to every disk whose name is in triggers
      if these_hds is not {} then
        set now to current date
        display dialog "The following volumes will be ejected in " & (TKO as text) & " seconds." & return & return & these_hds as text with icon 2 giving up after TKO
        set the_res to the result
        if gave up of the_res is true then
          my inspected_detected_and_ejected(these_hds)
        else
          set now_now to current date
          delay TKO - (now_now - now)
          my inspected_detected_and_ejected(these_hds)
        end if
      end if
    on error theErr number theNum
      if theNum is not -128 then display dialog theErr giving up  after TKO -- Error -128 is the cancelled by user error.
    end try
  end tell
end do_it

on inspected_detected_and_ejected(these_hds)
  repeat with this_hd in these_hds
    try
      tell application "Finder" to eject this_hd
    end try
  end repeat
end inspected_detected_and_ejected

Notable is that this script delays the full set time whether the user clicks “OK” or not.