billimek.com

Jeff Billimek's Journal at billimek.com

applescript to automatically remove old and import new economist audiobook

Every week I download a new copy of The Economist audiobook and its a pain to get into iTunes. So I came up with this applescript tonight after putting the kids to bed. It was also a good opportunity to learn how to use applescript.

It will check to see if there is a new version based on a path passed into the script (I intend to run it from the command line via osascript) – otherwise it will default to a generic path. Once it determines that there is a new Economist and it actually contains files, it will remove the current version from iTunes and add the new version. It will adjust the artist metadata as well as the genre, EQ preset, and make the tracks behave as if they are like an audiobook. There doesn’t seem to be a way correctly change the ‘Media Kind’ to Audiobook as one could do by hand in the options for the tracks. I think this is a bug with applescript based on some googling. If anyone knows a workaround for this please let me know.

-- delete any pre-existing tracks of artist "The Economist"
on deleteOldEconomist()
	tell application "iTunes"
		set results to (every track of library playlist 1 whose artist is "The Economist")
		repeat with i in results
			set songFile to location of i
			delete i
			tell application "Finder" to delete songFile
		end repeat
	end tell
end deleteOldEconomist

-- import Economist if it exists
on importEconomist(filesFolder)
	-- Get a list of all the files to add
	tell application "Finder"
		set fileCount to count files of entire contents of filesFolder
		if fileCount > 1 then
			set selectedfiles to the name of every file of filesFolder
			set filesFolder to filesFolder as string
			repeat with i from 1 to fileCount
				set selectedfile to item i of selectedfiles
				if the selectedfile contains ".mp3" then
					set filePath to {filesFolder & selectedfile} as string
					-- Now have itunes import each file
					tell application "iTunes"
						-- create a temporary playlist to import the file
						set userPlaylist to (make user playlist with properties {name:"economist_import"})
						add alias filePath to userPlaylist
						set selectedtracks to (tracks of userPlaylist)
						repeat with selectedtrack in selectedtracks
							set genre of selectedtrack to "Audiobook"
							set EQ of selectedtrack to "Spoken Word"
							set shufflable of selectedtrack to false
							set bookmarkable of selectedtrack to true
							set album artist of selectedtrack to "The Economist"
							set artist of selectedtrack to "The Economist"
							-- there appears to be a bug with applescript in which there is no way to change the "Media Kind" of the file to something like 'Audiobook'
							--set kind of selectedtrack to Books
						end repeat
						-- delete the temporarily-created playlist
						if exists (some user playlist whose name is "economist_import") then delete (some user playlist whose name is "economist_import")
					end tell
					-- delete the imported file from the source
					tell application "Finder"
						delete item filePath
					end tell
				end if
			end repeat
		end if
	end tell
end importEconomist

on run argv
	set startTime to (get current date)
	-- if no arguments passed to the command line, default location
	if (count argv) is 0 then
		set defaultPath to "/Volumes/Media/Downloads/Economist"
	else
		set defaultPath to item 1 of argv
	end if
	try
		set filesFolder to alias (defaultPath as POSIX file)
	on error
		return (defaultPath as string) & " doesn't exist."
	end try
	tell application "Finder"
		set fileCount to count files of entire contents of filesFolder
	end tell
	if fileCount > 1 then
		deleteOldEconomist()
	end if
	importEconomist(filesFolder)
	set endTime to (get current date)
	set duration to endTime - startTime
	return "Process took " & duration & " seconds."
end run

And here is how it looks from the command line:

(jeff@illian:~)
[09:48 PM:0]$ osascript ~/Documents/Development/import\ economist.scpt ~/Downloads/Economist
Process took 157 seconds.

new home

I moved billimek.com from a self-hosted solution (hosted at site5.com) to being hosted on wordpress.com.  It’s a lot less expensive and easier to maintain – especially since I’m not really doing much with this site anymore!  It was a pretty smooth transition considering I needed to migrate the DNS settings for the google apps (for email) without interruption.

I also changed the behavior of the main ‘landing page’ to be a bunch of links instead of the journal entries.

vix reversal trigger in python

I wrote a python script to send an email whenever a particular type of VIX reversal indicator triggers. Basically if the following conditions are met, it will send an email:
* If the VIX index has a NEW 15-day high today and closed below the open, then it suggests a buy indicator.
* If the VIX index has a NEW 15-day low today and closed above the open, then it suggests a sell indicator.

I decided to write this in python since I’ve never used it before. It was a fun learning experience. I was a bit taken back by the lack of statement terminators and the indenting rules, but in all I like it better than perl.

This script runs every weekday at 4:15pm (since the yahoo stock quotes are delayed 15 minutes).

Future enhancement ideas are to include the 15-day chart from stockcharts.com as an inline attachment (I have a bash script which already does this so it shouldn’t be that hard to port), as well as proper cleanup & organization of the code once/if I ever learn more python.

#!/usr/bin/env python

import sys
import datetime
import smtplib
import ystockquote

from email.mime.text import MIMEText

# ticker to look up
ticker = '^VIX'
# number of days of historical data to fetch (not counting today)
days_back = 14

email_from = 'redacted'
email_to = 'redacted'

# yesterday's date
end = datetime.date.today() + datetime.timedelta(days= -1)
# 30 days ago from today
start = datetime.date.today() + datetime.timedelta(days= -30)

# fetch the historical data from yahoo
data = ystockquote.get_historical_prices(ticker, start.strftime("%Y%m%d"), end.strftime("%Y%m%d"))

# validate that we have enough historical data to continue
if len(data) = float(high):
            return False
    return True

def isNewLow(low, data):
    """
    Returns True if the 'low' is lower than any of the lows in the data
    array passed in. Otherwise returns False
    """
    for i in range(1, (days_back + 1)):
        if float(data[i][3])  float(open)

def isCurrentLowerThanOpen(current, open):
    """
    Simple check to see if the current price is lower than the open price
    """
    return float(current) This is a VIX BUY indicator!'
        subject = 'VIX BUY indicator triggered'
    else:
        body = 'Today is a new 15-day low (' + str(low) + ') & the current price (' + str(current) + ') is higher than the open (' + str(open) + ')\n\n====>This is a VIX SELL indicator!'
        subject = 'VIX SELL indicator triggered'
    msg = MIMEText(body)
    msg['Subject'] = subject
    msg['From'] = email_from
    msg['To'] = email_to
    s = smtplib.SMTP('localhost')
    s.sendmail(email_from, email_to, msg.as_string())
    s.quit()

I took the ystockquote.py from Corey Goldberg and had to extend it slightly to include some additional information:
* day’s high price
* day’s low price
* last trade time
* last ‘real time’ trade + time (this doesn’t appear to be real time after all)
#!/usr/bin/env python
#
#  Copyright (c) 2007-2008, Corey Goldberg (corey@goldb.org)
#
#  license: GNU LGPL
#
#  This library is free software; you can redistribute it and/or
#  modify it under the terms of the GNU Lesser General Public
#  License as published by the Free Software Foundation; either
#  version 2.1 of the License, or (at your option) any later version.

import urllib

"""
This is the "ystockquote" module.

This module provides a Python API for retrieving stock data from Yahoo Finance.

sample usage:
>>> import ystockquote
>>> print ystockquote.get_price('GOOG')
529.46
"""

def __request(symbol, stat):
    url = 'http://finance.yahoo.com/d/quotes.csv?s=%s&f=%s' % (symbol, stat)
    return urllib.urlopen(url).read().strip().strip('"')

def get_all(symbol):
    """
    Get all available quote data for the given ticker symbol.

    Returns a dictionary.
    """
    values = __request(symbol, 'l1c1va2xj1b4j4dyekjm3m4rr5p5p6s7oahagat1k1').split(',')
    data = {}
    data['price'] = values[0]
    data['change'] = values[1]
    data['volume'] = values[2]
    data['avg_daily_volume'] = values[3]
    data['stock_exchange'] = values[4]
    data['market_cap'] = values[5]
    data['book_value'] = values[6]
    data['ebitda'] = values[7]
    data['dividend_per_share'] = values[8]
    data['dividend_yield'] = values[9]
    data['earnings_per_share'] = values[10]
    data['52_week_high'] = values[11]
    data['52_week_low'] = values[12]
    data['50day_moving_avg'] = values[13]
    data['200day_moving_avg'] = values[14]
    data['price_earnings_ratio'] = values[15]
    data['price_earnings_growth_ratio'] = values[16]
    data['price_sales_ratio'] = values[17]
    data['price_book_ratio'] = values[18]
    data['short_ratio'] = values[19]
    data['open'] = values[20]
    data['days_high'] = values[21]
    data['days_low'] = values[22]
    data['last_trade_time'] = values[23]
    data['last_trade_real_time'] = values[24]
    return data

def get_price(symbol):
    return __request(symbol, 'l1')

def get_change(symbol):
    return __request(symbol, 'c1')

def get_volume(symbol):
    return __request(symbol, 'v')

def get_avg_daily_volume(symbol):
    return __request(symbol, 'a2')

def get_stock_exchange(symbol):
    return __request(symbol, 'x')

def get_market_cap(symbol):
    return __request(symbol, 'j1')

def get_book_value(symbol):
    return __request(symbol, 'b4')

def get_ebitda(symbol):
    return __request(symbol, 'j4')

def get_dividend_per_share(symbol):
    return __request(symbol, 'd')

def get_dividend_yield(symbol):
    return __request(symbol, 'y')

def get_earnings_per_share(symbol):
    return __request(symbol, 'e')

def get_52_week_high(symbol):
    return __request(symbol, 'k')

def get_52_week_low(symbol):
    return __request(symbol, 'j')

def get_50day_moving_avg(symbol):
    return __request(symbol, 'm3')

def get_200day_moving_avg(symbol):
    return __request(symbol, 'm4')

def get_price_earnings_ratio(symbol):
    return __request(symbol, 'r')

def get_price_earnings_growth_ratio(symbol):
    return __request(symbol, 'r5')

def get_price_sales_ratio(symbol):
    return __request(symbol, 'p5')

def get_price_book_ratio(symbol):
    return __request(symbol, 'p6')

def get_short_ratio(symbol):
    return __request(symbol, 's7')

def get_open(symbol):
    return __request(symbol, 'o')

def get_high(symbol):
    return __request(symbol, 'h')

def get_low(symbol):
    return __request(symbol, 'g')

def get_last_trade_time(symbol):
    return __request(symbol, 't1')

def get_last_real_time_trade(symbol):
    return __request(symbol, 'k1')

def get_historical_prices(symbol, start_date, end_date):
    """
    Get historical prices for the given ticker symbol.
    Date format is 'YYYYMMDD'

    Returns a nested list.
    """
    url = 'http://ichart.yahoo.com/table.csv?s=%s&' % symbol + \
          'd=%s&' % str(int(end_date[4:6]) - 1) + \
          'e=%s&' % str(int(end_date[6:8])) + \
          'f=%s&' % str(int(end_date[0:4])) + \
          'g=d&' + \
          'a=%s&' % str(int(start_date[4:6]) - 1) + \
          'b=%s&' % str(int(start_date[6:8])) + \
          'c=%s&' % str(int(start_date[0:4])) + \
          'ignore=.csv'
    days = urllib.urlopen(url).readlines()
    data = [day[:-2].split(',') for day in days]
    return data

daily vix chart

A bash script to email an inline image of the previous 15-day ^VIX index chart:
daily_stocks.sh

#!/bin/bash
# requires: basename,date,sed,sendmail,uuencode,wget
# portions of this taken from http://www.zedwood.com/article/103/bash-send-mail-with-an-attachment

function fappend {
    echo "$2">>$1;
}
DATE=`date +%F`

# CHANGE THESE
SYMBOL="\$VIX"
DAYS="15"
URL="http://stockcharts.com/c-sc/sc?s=${SYMBOL}&p=D&yr=0&mn=0&dy=${DAYS}&i=p16348210875&r=4872"
TOEMAIL=redacted
FREMAIL=redacted
SUBJECT="Daily ${SYMBOL} for `date +%c`";
ATTACHMENT="/tmp/${SYMBOL}-${DATE}.png"

# DON'T CHANGE ANYTHING BELOW
TMP="/tmp/tmpfil_123"$RANDOM;
FILENAME=`basename $ATTACHMENT`

# Fetch the daily chart and put it into ${TMP}.png
/opt/local/bin/wget -q -O ${TMP}.png "${URL}"

rm -rf $TMP;
cat ${TMP}.png | uuencode -m $FILENAME>$TMP;
sed -i -e '1,1d' $TMP;#removes first & last lines from $TMP
DATA=`cat $TMP`

rm -rf ${TMP}.png
rm -rf $TMP;
fappend $TMP "From: $FREMAIL";
fappend $TMP "To: $TOEMAIL";
fappend $TMP "Reply-To: $FREMAIL";
fappend $TMP "Subject: $SUBJECT";
fappend $TMP "Content-Type: image/png; name=\"$FILENAME\"";
fappend $TMP "Content-Transfer-Encoding: base64";
fappend $TMP "Content-Disposition: inline; filename=\"$FILENAME\";";
fappend $TMP "";
fappend $TMP "$DATA";
fappend $TMP "";
fappend $TMP "";
fappend $TMP "";
fappend $TMP "";
#cat $TMP>out.txt
cat $TMP|/usr/sbin/sendmail -t;
rm $TMP;

# sleep for 10 seconds to prevent launchd cleanup
sleep 10

Now, the launchd plist (think cron for OSX) to invoke the script every weekday at 4pm:
com.billimek.stocks.plist




        AbandonProcessGroup
        
        Disabled
        
        Label
        com.billimek.stocks
        LowPriorityIO
        
        Nice
        1
        ProgramArguments
        
                /usr/local/bin/daily_stocks.sh
        
    StartCalendarInterval
    
           
            Hour
            16
            Minute
            00
            Weekday
            1
        
        
            Hour
            16
            Minute
            00
            Weekday
            2
        
        
            Hour
            16
            Minute
            00
            Weekday
            3
        
        
            Hour
            16
            Minute
            00
            Weekday
            4
        
        
            Hour
            16
            Minute
            00
            Weekday
            5
        
    
        UserName
        jeff

This is what it looks like as an email on my iphone:
daily vix example email

Jeff's photography metadata statistics

Some fun statistics from my photograph library (lightroom) that contains photographs from 2002 through present-day:

48,711 photos total
5,839 with 3 stars (good enough to publish)

—Top 6 camera models used:

  • 20,486: Canon EOS 5D
  • 10,698: Canon EOS 10D
  • 10,429: Canon EOS 20D
  • 2,837: Minolta Dimage 7i
  • 2,593: Canon PowerShot D=S410
  • 512: Canon EOS 1Ds Mark III

—Top 7 Lenses used:

  • 8,712: EF17-40mm f/4L USM 8,712
  • 7,317: EF50mm f/1.8
  • 7,038: EF70-200mm f/2.8L IF USM
  • 6,949: EF28-135mm IF USM
  • 5,153: 15mm Fisheye
  • 3,592: EF100mm f/2.8L Macro USM
  • 1,239: EF24-70mm USM

—Top 7 names tagged:

  • 6,318: Ansley Billimek
  • 5,067: Jennifer Billimek
  • 3,191: Jeff Billimek
  • 1,674: Larry Billimek
  • 1,405: Tucker Billimek
  • 1,173: Brinley Billimek
  • 1,140: Lily Billimek

nom nom nom

So Jen and I went through the starbucks drive-thru and they gave us a free sample of their cinnamon roll.  Jen didn’t want any so I gladly ate it.  It was good and I made cookie-monster sounds as I ate, “Nom Nom Nom”.  Jen gave me that look again.  I was all, “well at least I’m not making pig noises as I eat”.  And then I had a funny thought which I shared with Jen: “Why don’t we go to a fancy restaurant and make squealing noises like pigs while we eat?”  As I imagined this in my minds-eye it seemed really funny and I started laughing hysterically.  Jen didn’t find it funny though.  Jen was all, “I’m glad you can make yourself laugh.”

working too many hours

So after I went back to sleep this morning I got a call a couple of hours later from my director.  Aparantly there were some problems with the sever and he needed my help.  I worked on this until about 6pm.  I worked from home all day today.

Jen had taken my car to work today and brought it home washed.  It looked great and I was so excited that it was nice and clean!  She also had gone shopping and bought some new tennis shirts for me and some new tennis skirts for herself.  I made tofu rubens for dinner tonight.  Jennifer thought that I was a little grumpy but I explained that I was just tired.

I’m a terrible cook and managed to make the tofu chewy.  This is something that Jennifer has never seen done before.  After dinner we watched an episode of ’24′.

production support

When I got home from work today I took a nap as I was very tired.  I went to bed at 10pm and woke up at 1:40am to do more work on the server migration.  There were issues and it took longer than expected.  I didn’t finish until about 5am.  I went to bed but then was paged an hour later.  There were more problems.

I worked on this some more while Jennifer woke up, showered, dressed, and left.  I continued to work until about 7am at which point I went to bed again.

memorial day

Because it is Memorial Day, Jennifer had off from work.  I didn’t.  She was still in bed when I left this morning at 8:30am.  It was very quiet at work today.  This evening I spent a lot of time working on a unix server patch failover.  I ended up working from 11pm until 3am.  Kumar helped too.

more dogsitting

Sydney woke Jennifer and I up this morning – we aren’t used to that.  Jen started to freak out because she couldn’t find any coffee in the house and had to leave right away to go find some.  She came back a little while later with some coffee and some doughnuts for me.  How nice!

We sat outside on the back deck and talked.  It was a nice morning out but there was a lot of smoke in the air form the huge fires in south Georgia.  Jen made plans to go over to Scott’s house to help him paint.  She asked that I stay behind and keep and eye on the dogs.  I’m all for that!

So I played Gears of War again for hours and made it all the way to act 4 but then got tired of playing.  So I switched over to Guitar Hero.  All day long the dogs slept.  Is that all they do is sleep all day long?  Next I worked on the laptop some.  OpenVPN works great – it’s like I’ve never left my home network (only that access is slower).

I packed up and went home.  Jen was still at Scott’s house so I worked on laundry and did some work on the PC downstairs.  Jen got home and we went out to dinner.  First we tried to go over to M&T but they are closed on Sunday so instead we went to Marlow’s tavern in vinings.  It was nice out so we sat outside.

Follow

Get every new post delivered to your Inbox.