Port iTunes appscript to applescript. #178 #181

Closed
wants to merge 3 commits into
from
View
@@ -39,6 +39,15 @@ task :start do
Kernel.exec "bundle exec foreman start"
end
+desc "Warm up song cache"
+task :warm_cache do
+ # Cache all Song data.
+ songs = `osascript -e 'tell application "iTunes" to get persistent ID of every track'`.chomp.split(", ")
+ songs.each do |id|
+ Play::Song.find(id)
+ end
+end
+
namespace :redis do
desc "Wipe all data in redis"
task :reset do
View
@@ -4,18 +4,25 @@ class App < Sinatra::Base
get "/images/art/:id.png" do
content_type 'image/png'
+ cached_artwork = "/tmp/play-artwork/#{params[:id]}.png"
- song = Song.find(params[:id])
- art = song.album_art_data if song
-
- if art
+ if File.exists? cached_artwork
response['Cache-Control'] = 'public, max-age=2500000'
etag params[:id]
- art
+ send_file cached_artwork, :disposition => 'inline'
else
- dir = File.dirname(File.expand_path(__FILE__))
- send_file "#{dir}/../frontend/public/images/art-placeholder.png",
- :disposition => 'inline'
+ song = Song.find(params[:id])
+ art = song.album_art_data if song
+
+ if art
+ FileUtils.mkdir_p("/tmp/play-artwork")
+ File.write(cached_artwork, art)
+ art
+ else
+ dir = File.dirname(File.expand_path(__FILE__))
+ send_file "#{dir}/../frontend/public/images/art-placeholder.png",
+ :disposition => 'inline'
+ end
end
end
View
@@ -22,20 +22,19 @@ def initialize(name,artist)
#
# Returns an Array of Songs.
def self.songs_by_name(name)
- Player.library.file_tracks[Appscript.its.album.eq(name)].get.map do |record|
- Song.new(record.persistent_ID.get)
+ songs = `osascript -e 'tell application "iTunes" to get persistent ID of every track whose album is \"#{name}\"'`.chomp.split(", ")
+ if songs.empty?
+ nil
+ else
+ songs.map { |id| Song.find(id) }
end
end
# The songs attached to this album.
#
# Returns an Array of Songs.
def songs
- Player.library.file_tracks[
- Appscript.its.album.eq(name).and(Appscript.its.artist.eq(artist))
- ].get.map do |record|
- Song.new(record.persistent_ID.get)
- end
+ Album.songs_by_name(self.name)
end
# Zips up an album and stashes in it a temporary directory.
@@ -17,12 +17,11 @@ def initialize(name)
#
# Returns an Array of Songs.
def songs
- if name
- Player.app.tracks[Appscript.its.artist.contains(name)].get.map do |record|
- Song.initialize_from_record(record)
- end
+ songs = `osascript -e 'tell application "iTunes" to get persistent ID of every track whose artist is \"#{self.name}\"'`.chomp.split(", ")
+ if songs.empty?
+ nil
else
- []
+ songs.map { |id| Song.find(id) }
end
end
View
@@ -1,50 +1,47 @@
module Play
class Player
- # The application we're using. iTunes, dummy.
- #
- # Returns an Appscript instance of the music app.
- def self.app
- Appscript.app('iTunes')
- end
-
- # All songs in the library.
- def self.library
- app.playlists['Library'].get
- end
-
# Play the music.
def self.play
- app.play
+ `osascript -e 'tell application "iTunes" to play'`
end
# Pause the music.
def self.pause
- app.pause
+ `osascript -e 'tell application "iTunes" to pause'`
+ end
+
+ # Get current state.
+ #
+ # Returns symbol.
+ def self.state
+ `osascript -e 'tell application "iTunes" to get player state'`.chomp.to_sym
end
# Is there music currently playing?
def self.paused?
- state = app.player_state.get
state == :paused
end
# Maybe today is the day the music stopped.
def self.stop
- app.stop
+ `osascript -e 'tell application "iTunes" to stop'`
end
# Play the next song.
#
# Returns the new song.
def self.play_next
- app.next_track
+ `osascript -e 'tell application "iTunes" to play next track'`
now_playing
end
# Play the previous song.
+ #
+ # Returns the new song.
def self.play_previous
- app.previous_track
+ `osascript -e 'tell application "iTunes" to play previous track'`
+ now_playing
end
# Get the current numeric volume.
@@ -69,7 +66,7 @@ def self.system_volume=(setting)
#
# Returns an Integer from 0-100.
def self.app_volume
- app.sound_volume.get
+ `osascript -e 'tell application "iTunes" to get sound volume'`.chomp.to_i
end
# Set the app volume.
@@ -79,7 +76,7 @@ def self.app_volume
#
# Returns the current volume setting.
def self.app_volume=(setting)
- app.sound_volume.set(setting)
+ `osascript -e 'tell application "iTunes" to set sound volume to #{setting}'`
setting
end
@@ -94,13 +91,22 @@ def self.say(message)
self.app_volume = previous
end
+ # Get persistent id of current track.
+ #
+ # Returns string.
+ def self.current_track
+ `osascript -e 'tell application "iTunes" to get persistent ID of current track'`.chomp
+ end
+
# Currently-playing song.
#
# Returns a Song.
def self.now_playing
- Song.new(app.current_track.persistent_ID.get)
- rescue Appscript::CommandError
- nil
+ if state == :playing
+ Song.find(current_track)
+ else
+ nil
+ end
end
# Search all songs for a keyword.
@@ -115,20 +121,28 @@ def self.now_playing
# Returns an Array of matching Songs.
def self.search(keyword)
# Exact Artist match.
- songs = library.tracks[Appscript.its.artist.eq(keyword)].get
- return songs.map{|record| Song.new(record.persistent_ID.get)} if songs.size != 0
+ songs = `osascript -e 'tell application "iTunes" to get persistent ID of every track whose artist is \"#{keyword}\"' 2>&1`.chomp.split(", ")
+ if $? == 0 && !songs.empty?
+ return songs.map { |id| Song.find(id) }
+ end
# Exact Album match.
- songs = library.tracks[Appscript.its.album.eq(keyword)].get
- return songs.map{|record| Song.new(record.persistent_ID.get)} if songs.size != 0
+ songs = `osascript -e 'tell application "iTunes" to get persistent ID of every track whose album is \"#{keyword}\"' 2>&1`.chomp.split(", ")
+ if $? == 0 && !songs.empty?
+ return songs.map { |id| Song.find(id) }
+ end
# Exact Song match.
- songs = library.tracks[Appscript.its.name.eq(keyword)].get
- return songs.map{|record| Song.new(record.persistent_ID.get)} if songs.size != 0
+ songs = `osascript -e 'tell application "iTunes" to get persistent ID of every track whose name is \"#{keyword}\"' 2>&1`.chomp.split(", ")
+ if $? == 0 && !songs.empty?
+ return songs.map { |id| Song.find(id) }
+ end
# Fuzzy Song match.
- songs = library.tracks[Appscript.its.name.contains(keyword)].get
- songs.map{|record| Song.new(record.persistent_ID.get)}
+ songs = `osascript -e 'tell application "iTunes" to get persistent ID of every track whose name contains \"#{keyword}\"' 2>&1`.chomp.split(", ")
+ if $? == 0 && !songs.empty?
+ return songs.map { |id| Song.find(id) }
+ end
end
end
View
@@ -11,15 +11,11 @@ def self.name
'iTunes DJ'
end
- # The Playlist object that the Queue resides in.
- #
- # Returns an Appscript::Reference to the Playlist.
- def self.playlist
- Player.app.playlists[name].get
- end
-
# Get the queue start offset for the iTunes DJ playlist.
#
+ # Bug: When player is stopped with no song, returns 0 resulting
+ # in queue including any song history from iTunes DJ.
+ #
# iTunes DJ keeps the current song in the playlist and
# x number of songs that have played. This method returns
# the current song index in the playlist. Using this we
@@ -33,7 +29,11 @@ def self.playlist
#
# Returns Integer offset to queued songs.
def self.playlist_offset
- Player.app.current_track.index.get
+ if [:playing, :paused].include?(Player.state)
+ `osascript -e 'tell application "iTunes" to get index of current track'`.chomp.to_i
+ else
+ 0
+ end
end
# Public: Adds a song to the Queue.
@@ -42,7 +42,7 @@ def self.playlist_offset
#
# Returns a Boolean of whether the song was added.
def self.add_song(song)
- Player.app.add(song.record.location.get, :to => playlist.get)
+ `osascript -e 'tell application "iTunes" to add add (get location of first track whose persistent ID is \"#{song.id}\") to playlist \"#{name}\"'`
end
# Public: Removes a song from the Queue.
@@ -51,50 +51,48 @@ def self.add_song(song)
#
# Returns a Boolean of whether the song was removed maybe.
def self.remove_song(song)
- Play::Queue.playlist.tracks[
- Appscript.its.persistent_ID.eq(song.id)
- ].first.delete
+ `osascript -e 'tell application "iTunes" to delete (first track of playlist \"#{name}\" whose persistent ID is \"#{song.id}\")'`
end
# Clear the queue. Shit's pretty destructive.
#
- # Returns who the fuck knows.
+ # Returns nil.
def self.clear
- Play::Queue.playlist.tracks.get.each { |record| record.delete }
+ `osascript -e 'tell application "iTunes" to delete (every track of playlist \"#{name}\")'`
+ nil
end
# Ensure that we're currently playing on the Play playlist. Don't let anyone
# else use iTunes, because fuck 'em.
#
# Returns nil.
def self.ensure_playlist
- if Play::Player.app.current_playlist.get.name.get != name
- Play::Player.app.playlists[name].get.play
+ current_playlist = `osascript -e 'tell application "iTunes to get name of current playlist'`.chomp
+ if current_playlist != name
+ `osascript -e 'tell application "iTunes" to play playlist \"#{name}\"'`
end
- rescue Exception => e
- # just in case!
+ nil
end
# The songs currently in the Queue.
#
# Returns an Array of Songs.
def self.songs
- songs = playlist.tracks.get.map do |record|
- Song.find(record.persistent_ID.get)
+ songs = `osascript -e 'tell application "iTunes" to get persistent ID of every track of playlist \"#{name}\"'`.chomp.split(", ")
+ if songs.empty?
+ nil
+ else
+ songs.map! { |id| Song.find(id) }
+ songs.slice(playlist_offset, songs.length - playlist_offset)
end
- songs.slice(playlist_offset, songs.length - playlist_offset)
- rescue Exception => e
- # just in case!
- nil
end
# Is this song queued up to play?
#
# Returns a Boolean.
def self.queued?(song)
- Play::Queue.playlist.tracks[
- Appscript.its.persistent_ID.eq(song.id)
- ].get.size != 0
+ queued = `osascript -e 'tell application "iTunes" to get exists (first track of playlist \"#{name}\" whose persistent ID is \"#{song.id}\")'`.chomp.to_sym
+ queued == :true
end
# Returns the context of this Queue as JSON. This contains all of the songs
Oops, something went wrong.