(* script: Podcasts-to-pyTivo.scpt
Pushes audio and video podcasts from iTunes to to TiVo using pyTivo
Creates pyTivo's metadata .txt files from the info in iTunes
Deletes podcasts from iTunes library and moves them to Trash
Converts audio podcasts to video podcasts, with a static image
Growls that podcasts pushed to TiVo, if Growl available
Podcasts are .hidden from pulling to TiVo while processing
Script run by user's launchctl at :15 and :45 minutes past the hour
*)
global scriptName
set scriptName to "Podcasts-to-pyTivo Script"
set pyTivoSharePath to "/path/to/pytivo/shared/folder/"
set pyTivoShareName to "Name of that share in pyTivo"
set tsn to "1234567890A1B2C"
set podcastImage to "/path/to/podcastImage.jpg"
set pathToAtomicParsley to "/opt/local/bin/atomicparsley"
set pathToCurl to "/usr/bin/curl"
set pathToFfmpeg to "/opt/local/bin/ffmpeg"
set trashCan to "/Users/username/.Trash/"
set uniquePrefix to ".xyzzy-podcast-"
set nl to ASCII character 10
-- Move any previously pushed podcasts to the Trash
try
do shell script "mv " & quoted form of (pyTivoSharePath & uniquePrefix) & "* " & trashCan
end try
-- Get the podcasts database IDs. Create their pyTivo metadata files
set audioIDList to {}
set videoIDList to {}
tell application "iTunes"
-- Get the index number of the "Podcasts" playlist. Ensure it exists & is not empty.
if (the library playlist) is {} then return
repeat with thisPlaylist in playlists
if name of thisPlaylist as string is "Podcasts" then
set podcastPlaylistId to (index of thisPlaylist as number)
exit repeat
end if
end repeat
try
podcastPlaylistId
on error
return
end try
if (tracks of item (podcastPlaylistId) of every playlist) is {} then
return
end if
-- Process each podcast
repeat with thisPodcastID in tracks of (item (podcastPlaylistId) of every playlist)
repeat 1 times
-- Skip podcasts that are still downloading (their address will exist)
if (address of thisPodcastID exists) then
exit repeat
end if
-- Skip podcasts with filetype ".m4a"
if (location of thisPodcastID as string) ends with ".m4a" then
my growl_error(scriptName & " Error", ¬
"Unhandled filetype m4a for podcast \"" & the name of thisPodcastID & "\"")
exit repeat
end if
-- Create lists of audio & video podcasts to process after relocking iTunes library
set podcastURL to POSIX path of (location of thisPodcastID as string)
if podcastURL ends with ".mp3" then
set audioIDList to audioIDList & {database ID of thisPodcastID}
else
set videoIDList to videoIDList & {database ID of thisPodcastID}
end if
-- Start copying the podcast to the pytivoSharePath
do shell script "cp -n " & quoted form of podcastURL ¬
& " " & quoted form of (pyTivoSharePath & uniquePrefix ¬
& my get_filename(podcastURL)) & " &> /dev/null &"
-- Build the pyTivo metadata for the podcast
set desc to description of thisPodcastID as string
if (length of (comment of thisPodcastID as string) > length of desc) then
set desc to comment of thisPodcastID as string
end if
if (length of (long description of thisPodcastID as string) > length of desc) then
set desc to long description of thisPodcastID as string
end if
set md to "title : Podcasts" & nl
set md to (md & "episodeTitle : " & (album of thisPodcastID as string) ¬
& " : " & name of thisPodcastID as string) & nl
set md to md & "description : " & desc & nl
set md to md & "vActor : " & (artist of thisPodcastID as string) & nl
set md to md & "vGenre : " & (genre of thisPodcastID as string) & nl
set md to md & "movieYear : " & (year of thisPodcastID as string) & nl
-- Open the metadata file and write to it
set metadataURL to pyTivoSharePath & uniquePrefix ¬
& my get_filename(podcastURL) & ".txt"
if metadataURL ends with ".mp3.txt" then
set metadataURL to my replace_text(metadataURL, ".mp3.txt", ".mp4.txt")
end if
set fileRef to open for access metadataURL with write permission
write my tivo_encode(md) to fileRef
close access fileRef
end repeat
end repeat
end tell
-- Deal with the video podcasts, from the list created above
repeat with dbid in videoIDList
tell application "iTunes"
set videoID to (some track whose database ID is dbid)
set fileName to uniquePrefix & my get_filename(POSIX path of (location of videoID as string))
set growlMsg to (album of videoID as string) & " : " & (name of videoID as string)
end tell
set videoURL to pyTivoSharePath & fileName
-- Wait to finish copying the video podcast from iTunes to pyTivoSharePath
my wait_until_filesize_constant(videoURL)
-- Strip moov atom, if there is one (qtfaststart pukes otherwise)
try
set moov to do shell script pathToAtomicParsley & " " & quoted form of (videoURL) & " -t"
if (moov is not "") then
do shell script pathToAtomicParsley & " " & ¬
quoted form of (videoURL) & " --metaEnema --overWrite"
end if
on error errStr
my growl_error(scriptName & " Error", "Strip moov: " & errStr)
end try
-- Push the video to TiVo
try
do shell script pathToCurl & " --silent " & quoted form of ("http://localhost:9032/TiVoConnect?Command=Push&Container=" & my html_encode(pyTivoShareName) & "&tsn=" & tsn & "&File=/" & my html_encode(fileName))
on error errStr
my growl_error(scriptName & " Error", "Pushing video podcast: " & errStr)
end try
my growl_msg("Pushing video podcast to TiVo", growlMsg)
end repeat
-- Deal with the audio podcasts, from list created above
repeat with dbid in audioIDList
tell application "iTunes"
set audioID to (some track whose database ID is dbid)
set audioFileName to uniquePrefix ¬
& my get_filename(POSIX path of (location of audioID as string))
set growlMsg to (album of audioID as string) & " : " & (name of audioID as string)
set aDuration to duration of audioID
end tell
set audioURL to pyTivoSharePath & audioFileName
set videoFileName to replace_text(audioFileName, ".mp3", ".mp4")
set videoURL to pyTivoSharePath & videoFileName
-- Wait to finish copying the audio podcast from iTunes to pyTivoSharePath
my wait_until_filesize_constant(audioURL)
-- Convert the audio podcast to video podcast with static image, using ffmpeg
try
do shell script "nice " & pathToFfmpeg & " -i " & quoted form of audioURL & ¬
" -s 640x480 -ab 192k -r 29.97 -loop_input -i " & quoted form of podcastImage & ¬
" -t " & aDuration & " -vcodec mpeg4 -threads 2 -v -1 " & ¬
quoted form of videoURL & " &> /dev/null &"
on error errStr
my growl_error(scriptName & " Error", "Converting audio to video: " & errStr)
end try
-- Wait for ffmpeg to finish converting the audio podcast to a video
my wait_until_filesize_constant(videoURL)
-- Push the new video podcast, which had been an audio podcast, to TiVo
try
do shell script pathToCurl & " --silent " & quoted form of ("http://localhost:9032/TiVoConnect?Command=Push&Container=" & my html_encode(pyTivoShareName) & "&tsn=" & tsn & "&File=/" & my html_encode(videoFileName))
on error errStr
my growl_error(scriptName & " Error", "Pushing audio podcast: " & errStr)
end try
my growl_msg("Pushing audio podcast to TiVo", growlMsg)
end repeat
-- Remove podcasts from iTunes Library, trash their files, and delete their subfolders
repeat with dbid in audioIDList & videoIDList
-- Remove track from Library
tell application "iTunes"
set podcastID to (some track whose database ID is dbid)
set loc to POSIX path of (location of podcastID as string)
if not (podcast of podcastID) then
my growl_error(scriptName & " Warning", ¬
"Check iTunes. Removed track without podcast flag: \"" & ¬
(name of podcastID as string) & "\".")
end if
-- Ensure that iTunes will not unsubscribe podcasts as not being played regularly
set played count of podcastID to (played count of podcastID) + 1
set unplayed of podcastID to false
set played date of podcastID to current date
delete podcastID
end tell
-- Trash file. Remove subfolder. Ensure that path includes "/Podcasts/"
if loc contains "/Podcasts/" then
try
do shell script "mv " & quoted form of (loc) & " " & trashCan
on error errStr
my growl_error(scriptName & " Error", "Trash podcast: " & errStr)
end try
try
do shell script "rmdir " & quoted form of (my replace_text(loc, my get_filename(loc), ""))
end try
else
my growl_error(scriptName & " Error", "Podcast? File not trashed: " & loc)
end if
end repeat
-- FUNCTIONS
on replace_text(someStr, findStr, replaceStr)
set prevTIDs to AppleScript's text item delimiters
set AppleScript's text item delimiters to findStr
set tmpStr to text items of someStr
set AppleScript's text item delimiters to replaceStr
set newStr to "" & tmpStr
set AppleScript's text item delimiters to prevTIDs
return newStr as string
end replace_text
on get_filename(someLoc)
set prevTIDs to AppleScript's text item delimiters
set AppleScript's text item delimiters to "/"
set tmpStr to last text item of someLoc
set AppleScript's text item delimiters to prevTIDs
return tmpStr as string
end get_filename
on wait_until_filesize_constant(somePosixFile)
set size1 to 0
set size2 to 1
repeat while size2 is not equal to size1
set size1 to size2
delay 5
set size2 to size of (info for POSIX file somePosixFile)
end repeat
end wait_until_filesize_constant
on html_encode(someStr)
-- there's gotta be a better way...
set the xcharacters to {}
repeat with x in someStr
set x to the contents of x
if x = (ASCII character 32) then
set the end of the xcharacters to "%20" -- space
else if x = (ASCII character 37) then
set the end of the xcharacters to "%25" -- %
else if x = (ASCII character 38) then
set the end of the xcharacters to "%26" -- &
else if x = (ASCII character 40) then
set the end of the xcharacters to "%28" -- (
else if x = (ASCII character 41) then
set the end of the xcharacters to "%29" -- )
else if x = (ASCII character 91) then
set the end of the xcharacters to "%5B" -- [
else if x = (ASCII character 93) then
set the end of the xcharacters to "%5D" -- ]
else
set the end of the xcharacters to x
end if
end repeat
return (the xcharacters) as string
end html_encode
on tivo_encode(someStr)
set the xcharacters to {}
repeat with x in someStr
set x to the contents of x
if x = "‘" or x = "’" then
set the end of the xcharacters to "'"
else if x = "“" or x = "”" then
set the end of the xcharacters to "\""
else if x = "…" then
set the end of the xcharacters to "..."
else
set the end of the xcharacters to x
end if
end repeat
return (the xcharacters) as string
end tivo_encode
on growl_msg(myTitle, myText)
tell application "System Events"
if ((count of (every process whose name is "GrowlHelperApp")) > 0) then
my growl_prepare()
tell application "GrowlHelperApp"
notify with name "Success Notification" title myTitle description myText ¬
application name scriptName
end tell
end if
end tell
end growl_msg
on growl_error(myTitle, myText)
tell application "System Events"
if ((count of (every process whose name is "GrowlHelperApp")) > 0) then
my growl_prepare()
tell application "GrowlHelperApp"
notify with name "Error Notification" title myTitle description myText ¬
application name scriptName with sticky
end tell
end if
end tell
end growl_error
on growl_prepare()
tell application "GrowlHelperApp"
set the allNotificationsList to {"Success Notification", "Error Notification"}
set the enabledNotificationsList to allNotificationsList
register as application scriptName all notifications allNotificationsList ¬
default notifications enabledNotificationsList icon of application "iTunes"
end tell
end growl_prepare |