pyTivo Discussion Forum Forum Index pyTivo Discussion Forum
Answers and the development of pyTivo a TiVo transcoding server
 
 FAQFAQ   SearchSearch   MemberlistMemberlist   UsergroupsUsergroups   RegisterRegister 
 ProfileProfile   Log in to check your private messagesLog in to check your private messages   Log inLog in 

OSX: Script completely automates pushing podcasts to TiVo

 
Post new topic   Reply to topic    pyTivo Discussion Forum Forum Index -> Other Apps
 View previous topic :: View next topic  
Author Message
Minckster



Joined: 02 Oct 2008
Posts: 58

PostPosted: Tue Jul 21, 2009 12:34 am    Post subject: OSX: Script completely automates pushing podcasts to TiVo Reply with quote

OSX: Here's a script that completely automates pushing audio and video podcasts from iTunes to TiVo's Now Playing List. It allows me to use iTunes to subscribe to podcasts, while enjoying them on my TiVo but without the work of transferring them one-by-one. They just appear within a "Podcasts" folder.

The Script has the following features:
  • Finds new audio and video podcasts in iTunes and pushes them to TiVo
  • 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
  • Growl notifies that podcasts pushed to TiVo, if Growl's available
  • Podcasts are .hidden from pulling to TiVo while processing
Although the script is written in AppleScript, there aren't too many features that are OSX-specific, so it should be possible to translate it to other scripting languages.

To use this script, OSX-users should install Growl and have to install AtomicParsley and ffmpeg. Everything else comes pre-installed on OSX. I installed AtomicParsley and ffmpeg with MacPorts, using the exact steps provided after the script. There are also some suggestions there for alternate sources. Within the script, adjust the values of "set pyTivoSharePath" to "set trashCan" in the first several lines. My podcastImage is attached.

Code:
(*   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

It would be very easy to extend this script to include iTunes' movie trailers, but they're not pushing well ("broken pipe" errors).

To install AtomicParsley and ffmpeg with MacPorts, I used these commands, after installing MacPorts, of course.
Code:
$ sudo port selfupdate
$ sudo port install AtomicParsley
$ sudo port install lame
$ sudo port install ffmpeg +lame +libogg +vorbis +theora +faac +faad +xvid +x264 +a52 +dt

You could use the ffmpeg within pyTivoX by installing that app, even if you don't use it. PyTivoX's ffmpeg is at "/Applications/pyTivoX.app/Contents/Resources/ffmpeg.bin". There's an AtomicParsley within MetaX. It's at "/Applications/MetaX.app/Contents/Resources/AtomicParsley32".

Caveat: OSX Tiger users may need to install an updated version of curl at /opt/local/bin/curl with MacPorts. I have a vague memory that Tiger's curl at /usr/bin/curl didn't work well or at all.

Here's the local.username.podcastsToPyTivo.plist that runs the script at :15 and :45 minutes past the hour. It goes in ~/Library/LaunchAgents/
Code:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
        <key>Label</key>
        <string>local.username.podcastsToPyTivo</string>
        <key>ProgramArguments</key>
        <array>
                <string>/usr/bin/osascript</string>
                <string>/Users/username/Scripts/Podcasts-to-pyTivo.scpt</string>
        </array>
        <key>StartCalendarInterval</key>
        <dict>
                <key>Minute</key>
                <integer>15</integer>
                <key>Minute</key>
                <integer>45</integer>
        </dict>
</dict>
</plist>



podcastImage.jpg
 Description:
The static image displayed for audio podcasts that are converted to videos for pyTivo. It's 640x480 pixels.
 Filesize:  22.03 KB
 Viewed:  1117 Time(s)

podcastImage.jpg


Back to top
View user's profile Send private message
Display posts from previous:   
Post new topic   Reply to topic    pyTivo Discussion Forum Forum Index -> Other Apps All times are GMT
Page 1 of 1

 
Jump to:  
You cannot post new topics in this forum
You cannot reply to topics in this forum
You cannot edit your posts in this forum
You cannot delete your posts in this forum
You cannot vote in polls in this forum
You cannot attach files in this forum
You can download files in this forum
Site is in NO WAY affiliated with TiVo Inc

Powered by phpBB © 2001, 2005 phpBB Group
phpBB SEO

Get pytivo at SourceForge.net. Fast, secure and Free Open Source software downloads
[ Time: 0.8816s ][ Queries: 15 (0.0234s) ][ GZIP on - Debug on ]