Class: Itch
- Inherits:
-
Object
- Object
- Itch
- Defined in:
- lib/itch.rb
Overview
A helper that controls the iTunes application.
Instance Attribute Summary collapse
-
#interface ⇒ Object
readonly
The COM/OLE interface to iTunes.
Class Method Summary collapse
-
.create_itunes_interface ⇒ Object
Create an OLE/COM link to iTunes.
Instance Method Summary collapse
-
#delete_playlists(config) ⇒ Object
Delete specified playlists.
-
#get_playlists(config) ⇒ Object
Get playlists that will be operated on.
-
#get_tracks(config, playlists) ⇒ Object
Find requested tracks.
-
#initialize(itunes_interface) ⇒ Itch
constructor
Create a new helper, and assign it an iTunes interface to use.
-
#parse_options(arguments) ⇒ Object
Parse arguments and get a configuration.
-
#perform_general_operations(config) ⇒ Object
Perform the operations specified in the config that affect iTunes itself.
-
#perform_track_operations(config, tracks) ⇒ Object
Perform the operations specified in the config that affect the selected tracks.
-
#set_default_options(config = Hash.new) ⇒ Object
Set defaults for an existing set of options.
-
#track_info(track, format) ⇒ Object
Take the specified format string with %x flags, and substitute info from the given track.
Constructor Details
#initialize(itunes_interface) ⇒ Itch
Create a new helper, and assign it an iTunes interface to use.
17 18 19 |
# File 'lib/itch.rb', line 17 def initialize(itunes_interface) @interface = itunes_interface end |
Instance Attribute Details
#interface ⇒ Object (readonly)
The COM/OLE interface to iTunes.
13 14 15 |
# File 'lib/itch.rb', line 13 def interface @interface end |
Class Method Details
.create_itunes_interface ⇒ Object
Create an OLE/COM link to iTunes.
23 24 25 26 27 28 29 |
# File 'lib/itch.rb', line 23 def Itch.create_itunes_interface begin WIN32OLE.new('iTunes.Application') or raise "Couldn't take control of iTunes." rescue WIN32OLERuntimeError => exception raise exception.exception("Couldn't take control of iTunes. Could a prior instance be shutting down?") end end |
Instance Method Details
#delete_playlists(config) ⇒ Object
Delete specified playlists.
211 212 213 214 215 |
# File 'lib/itch.rb', line 211 def delete_playlists (config) with_option_values('delete-playlist', config) do |name| find_playlist(name).delete end end |
#get_playlists(config) ⇒ Object
Get playlists that will be operated on.
182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 |
# File 'lib/itch.rb', line 182 def get_playlists (config) playlists = Array.new playlists << @interface.LibraryPlaylist if config['library'] if config['current-playlist'] playlists << @interface.CurrentPlaylist or raise "Can't find current playlist." end if config['all-playlists'] if library_playlists = @interface.LibrarySource.Playlists library_playlists.each {|playlist| playlists << playlist} else raise "No playlists found." end end with_option_values('playlist', config) do |name| playlists << find_playlist(name) or raise "Can't find playlist #{name}" end #If creation of playlist(s) requested, create them and add to the collection. with_option_values('create-playlist', config) do |name| playlists << @interface.CreatePlaylist(name) or raise "Can't create playlist #{name}" end playlists end |
#get_tracks(config, playlists) ⇒ Object
Find requested tracks.
219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 |
# File 'lib/itch.rb', line 219 def get_tracks (config, playlists) tracks = [] #Add current track to list if desired. if config['current-track'] current_track = @interface.CurrentTrack or raise "No currently active/playing track." #"x << y.CurrentTrack or raise" won't work; nil is false but [nil] is true. tracks << current_track end #Add highlighted tracks to list if desired. if config['selected-tracks'] if selected_tracks = @interface.SelectedTracks selected_tracks.each {|track| tracks << track} else raise "No tracks selected." end end #Find tracks in selected playlists. playlists.each do |playlist| #If all tracks are to be processed, select them all. if config['all-tracks'] playlist.Tracks.each {|track| tracks << track} #Otherwise, see if user wishes to search for tracks. else search_playlist = lambda {|option, field_id| with_option_values(option, config) do |terms| if (results = playlist.Search(terms, field_id)) results.each {|track| tracks << track} end end } search_playlist.call('find', 0) search_playlist.call('visible-find', 1) search_playlist.call('find-artist', 2) search_playlist.call('find-album', 3) search_playlist.call('find-composer', 4) search_playlist.call('find-track-name', 5) end #Import files to library and add resulting tracks to list. with_option_values('add-file', config) do |path| tracks.concat(add_file(File.(path), playlist)) end end tracks end |
#parse_options(arguments) ⇒ Object
Parse arguments and get a configuration.
33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 |
# File 'lib/itch.rb', line 33 def (arguments) #Config will hold parsed option values. config = Hash.new #Set up option parser. = OptionParser.new #Modify option parser to wrap descriptions. class << alias old_on on def on (*opts, &block) opts.map! do |option| if option.class == String and option.length > 40 option = option.scan(/\S.{0,40}\S(?=\s|$)|\S+/) end option end old_on(*opts.flatten, &block) end end #Set up valid options. .separator("Program help:") .on("-h", "--help", TrueClass, "Display program help.") { puts .help exit } .separator("Playback controls:") .on("-p", "--play-pause", TrueClass, "If currently paused, begin playing. If currently playing, pause playback.") {|value| config['play-pause'] = value} .on("--pause", TrueClass, "Pause playback.") {|value| config['pause'] = value} .on("--play", TrueClass, "Play the current track.") {|value| config['play'] = value} .on("-s", "--stop", TrueClass, "Stop playback.") {|value| config['stop'] = value} .on("-n", "--next-track", TrueClass, "Go to the next track.") {|value| config['next-track'] = value} .on("-N", "--previous-track", TrueClass, "Go to the previous track.") {|value| config['previous-track'] = value} .on("-m", "--mute", TrueClass, "Mute the audio.") {|value| config['mute'] = value} .on("-M", "--unmute", TrueClass, "Unmute the audio.") {|value| config['unmute'] = value} .on("-v", "--volume number", Integer, "Set the volume to X percentage points.") {|value| config['volume'] = value} .on("--volume-down [number]", Integer, "Decrease the volume by X percentage points (default 10).") {|value| config['volume-down'] = value || 10} .on("--volume-up [number]", Integer, "Increase the volume by X percentage points (default 10).") {|value| config['volume-up'] = value || 10} .on("--scan-to seconds", Integer, "Scan to an offset X seconds within the current track.") {|value| config['scan-to'] = value} .on("--scan-backwards [seconds]", Integer, "Scan backwards X seconds within the current track (default 10).") {|value| config['scan-backwards'] = value || 10} .on("--scan-forwards [seconds]", Integer, "Scan forwards X seconds within the current track (default 10).") {|value| config['scan-forwards'] = value || 10} .on("--play-file name", Object, "Play the specified file or folder.") {|value| config['play-file'] = value} .separator("Info on selected tracks:") .on("-i", "--print-info format", Object, "For each track, print information in the given format. If the following strings appear in the given format, they will be replaced with the corresponding track information:", %q#"%a": artist#, %q#"%e": encoding#, %q#"%A": album#, %q#"%b": BPM (beats per minute)#, %q#"%c": composer#, %q#"%C": comment#, %q#"%d": disc number#, %q#"%D": disc count#, %q#"%E": Enabled status ("enabled" or "disabled")#, %q#"%l": location (file name/URL)#, %q#"%p": play count#, %q#"%q": equalizer#, %q#"%g": genre#, %q#"%G": grouping#, %q#"%n": name (title)#, %q#"%r": rating#, %q#"%s": skip count#, %q#"%t": track number#, %q#"%T": track count#, %q#"%v": volume adjustment#, %q#"%y": year#, %q#"%%": percent sign# ) {|value| config['print-info'] = value} .separator("General iTunes controls:") .on("-a", "--add-file name", Object, "Add the specified file or folder to the library.") {|value| (config['add-file'] ||= []) << value} .on("-q", "--quit", TrueClass, "Exit iTunes.") {|value| config['quit'] = value} .on("--open-url url", Object, "Open the given URL.") {|value| config['open-url'] = value} .on("--goto-store-home-page", TrueClass, "Go to the Store.") {|value| config['goto-store-home-page'] = value} .on("--update-ipod", TrueClass, "Update the iPod.") {|value| config['update-ipod'] = value} .separator("Playlist selection:") .on("--library", TrueClass, "Operation will include the entire iTunes library. Used by default unless other libraries are selected.") {|value| config['library'] = value} .on("--current-playlist", TrueClass, "Operation will include the current playlist.") {|value| config['current-playlist'] = value} .on("--playlist name", Object, "Operation will include the named playlist. (This option can occur more than once.)") {|value| (config['playlist'] ||= []) << value} .on("--all-playlists", TrueClass, "Operation will include all playlists.") {|value| config['all-playlists'] = value} .on("--create-playlist name", Object, "Create a playlist with the specified name and include it in the operation. (This option can occur more than once.)") {|value| (config['create-playlist'] ||= []) << value} .on("--delete-playlist name", Object, "Delete the playlist with the specified name. (This option can occur more than once.)") {|value| (config['delete-playlist'] ||= []) << value} .separator("Track selection:") .on("-f", "--find string", Object, "Operation will include all tracks in the specified playlist(s) where any field matches the specified string. (This option can occur more than once.)") {|value| (config['find'] ||= []) << value} .on("-c", "--current-track", TrueClass, "Operation will inclue the current track.") {|value| config['current-track'] = value} .on("--selected-tracks", TrueClass, "Operation will include the selected tracks.") {|value| config['selected-tracks'] = value} .on("--all-tracks", TrueClass, "Operation will include all tracks in the specified playlist(s).") {|value| config['all-tracks'] = value} .on("--visible-find string", Object, "Operation will include all tracks in the specified playlist(s) where any visible field matches the specified string. (This option can occur more than once.)") {|value| (config['visible-find'] ||= []) << value} .on("--find-artist string", Object, "Operation will include all tracks in the specified playlist(s) where the artist matches the specified string. (This option can occur more than once.)") {|value| (config['find-artist'] ||= []) << value} .on("--find-album string", Object, "Operation will include all tracks in the specified playlist(s) where the album matches the specified string. (This option can occur more than once.)") {|value| (config['find-album'] ||= []) << value} .on("--find-composer string", Object, "Operation will include all tracks in the specified playlist(s) where the composer matches the specified string. (This option can occur more than once.)") {|value| (config['find-composer'] ||= []) << value} .on("--find-track-name string", Object, "Operation will include all tracks in the specified playlist(s) where the track name matches the specified string. (This option can occur more than once.)") {|value| (config['find-track-name'] ||= []) << value} .on("-F", "--play-found", TrueClass, "Play the first of the selected tracks.") {|value| config['play-found'] = value} .separator("Operations on selected tracks:") .on("--set-artist name", Object, "Set the artist for each track.") {|value| config['set-artist'] = value} .on("--set-album name", Object, "Set the album for each track.") {|value| config['set-album'] = value} .on("--set-bpm number", Integer, "Set the beats per minute for each track.") {|value| config['set-bpm'] = value} .on("--set-comment string", Object, "Set the comment for each track.") {|value| config['set-comment'] = value} .on("--set-composer string", Object, "Set the composer for each track.") {|value| config['set-composer'] = value} .on("--set-disc-number number", Integer, "For each track, set the disc number. (Used with multi-disc albums.)") {|value| config['set-disc-number'] = value} .on("--set-disc-count number", Integer, "For each track, set the number of discs in the album. (Used with multi-disc albums.)") {|value| config['set-disc-count'] = value} .on("--set-enabled", TrueClass, "Enable the check box for each track.") {|value| config['set-enabled'] = value} .on("--set-disabled", TrueClass, "Disable the check box for each track.") {|value| config['set-disabled'] = value} .on("--set-eq name", Object, "Set the equalizer to the named preset. Use 'None' to disable.") {|value| config['set-eq'] = value} .on("--set-genre name", Object, "Set the genre for each track.") {|value| config['set-genre'] = value} .on("--set-grouping string", Object, "Set the grouping for each track.") {|value| config['set-grouping'] = value} .on("--set-name name", Object, "Set the name (title) for each track.") {|value| config['set-name'] = value} .on("--set-play-count number", Integer, "Set the play count for each track.") {|value| config['set-play-count'] = value} .on("--set-rating number", Integer, "Set the rating for each track. Valid values are 0 through 5.") {|value| config['set-rating'] = value} .on("--set-skip-count number", Integer, "Set the skip count for each track.") {|value| config['set-skip-count'] = value} .on("--set-track-number number", Integer, "For each track, set its album track number.") {|value| config['set-track-number'] = value} .on("--set-track-count number", Integer, "For each track, set the number of tracks on its album.") {|value| config['set-track-count'] = value} .on("--set-track-volume percent", Integer, "Set the volume adjustment percentage for each track, from -100 to 100. Negative numbers decrease the volume, positive numbers increase it. 0 means no adjustment.") {|value| config['set-track-volume'] = value} .on("--set-year number", Integer, "Set the year of publication for each track.") {|value| config['set-year'] = value} .on("--add-to-playlist name", Object, "Add selected tracks to the given playlist.") {|value| config['add-to-playlist'] = value} .separator("Troubleshooting:") .on("--debug", TrueClass, "When an error occurs, show a more detailed message.") {|value| config['debug'] = value} #Parse the options, printing usage if parsing fails. .parse(arguments) rescue puts "#{$!}\nType '#{$0} --help' for valid options." config end |
#perform_general_operations(config) ⇒ Object
Perform the operations specified in the config that affect iTunes itself.
274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 |
# File 'lib/itch.rb', line 274 def perform_general_operations (config) specified?(config, 'mute') {@interface.Mute = 1} specified?(config, 'unmute') {@interface.Mute = 0} specified?(config, 'next-track') {@interface.NextTrack} specified?(config, 'scan-backwards') {|v| @interface.PlayerPosition -= v} specified?(config, 'scan-forwards') {|v| @interface.PlayerPosition += v} specified?(config, 'scan-to') {|v| @interface.PlayerPosition = v} specified?(config, 'volume-down') {|v| @interface.SoundVolume -= v} specified?(config, 'volume-up') {|v| @interface.SoundVolume += v} specified?(config, 'volume') {|v| @interface.SoundVolume = v} specified?(config, 'play-file') {|v| @interface.PlayFile(v)} specified?(config, 'open-url') {|v| @interface.OpenURL(v)} specified?(config, 'goto-store-home-page') {@interface.GotoMusicStoreHomePage} specified?(config, 'update-ipod') {@interface.UpdateIPod} specified?(config, 'pause') {@interface.Pause} specified?(config, 'play-pause') {@interface.PlayPause} specified?(config, 'previous-track') {@interface.PreviousTrack} specified?(config, 'stop') {@interface.Stop} specified?(config, 'play') {@interface.Play} specified?(config, 'quit') {@interface.Quit} end |
#perform_track_operations(config, tracks) ⇒ Object
Perform the operations specified in the config that affect the selected tracks.
300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 |
# File 'lib/itch.rb', line 300 def perform_track_operations (config, tracks) #Operate on selected tracks. tracks.each do |track| specified?(config, 'set-artist') {|v| track.Artist = v} specified?(config, 'set-album') {|v| track.Album = v} specified?(config, 'set-bpm') {|v| track.BPM = v} specified?(config, 'set-comment') {|v| track.Comment = v} specified?(config, 'set-composer') {|v| track.Composer = v} specified?(config, 'set-disc-count') {|v| track.DiscCount = v} specified?(config, 'set-disc-number') {|v| track.DiscNumber = v} specified?(config, 'set-enabled') {|v| track.Enabled = true} specified?(config, 'set-disabled') {|v| track.Enabled = false} specified?(config, 'set-eq') {|v| track.EQ = v} specified?(config, 'set-genre') {|v| track.Genre = v} specified?(config, 'set-grouping') {|v| track.Grouping = v} specified?(config, 'set-name') {|v| track.Name = v} specified?(config, 'set-play-count') {|v| track.PlayedCount = v} specified?(config, 'set-rating') {|v| track.Rating = v * 20} #Rating is stored as a number from 1-100 internally, but 1-5 in interface. specified?(config, 'set-skip-count') {|v| track.SkippedCount = v} specified?(config, 'set-track-count') {|v| track.TrackCount = v} specified?(config, 'set-track-number') {|v| track.TrackNumber = v} specified?(config, 'set-track-volume') {|v| track.VolumeAdjustment = v} specified?(config, 'set-year') {|v| track.Year = v} specified?(config, 'print-info') {|v| puts track_info(track, v)} specified?(config, 'set-artist') {|v| track.Artist = v} specified?(config, 'add-to-playlist') {|v| find_playlist(v).AddTrack(track)} end #Play first of selected tracks if requested. specified?(config, 'play-found') {tracks[0].Play unless tracks.empty?} end |
#set_default_options(config = Hash.new) ⇒ Object
Set defaults for an existing set of options.
167 168 169 170 171 172 173 174 175 176 177 178 179 |
# File 'lib/itch.rb', line 167 def (config=Hash.new) config['library'] = true unless ( config.has_key?('library') or config.has_key?('current-playlist') or config.has_key?('playlist') or config.has_key?('all-playlists') or config.has_key?('create-playlist') ) config end |
#track_info(track, format) ⇒ Object
Take the specified format string with %x flags, and substitute info from the given track.
336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 |
# File 'lib/itch.rb', line 336 def track_info (track, format) #Double percent signs should not be substituted, so work around them. segments = format.split(/%%/) #Substitute track info for markers. output_segments = segments.map do |segment| segment.gsub!(/%a/) {track.Artist} segment.gsub!(/%e/) {track.KindAsString} #The encoding. segment.gsub!(/%A/) {track.Album} segment.gsub!(/%b/) {track.BPM.to_s} segment.gsub!(/%c/) {track.Composer} segment.gsub!(/%C/) {track.Comment} segment.gsub!(/%d/) {track.DiscNumber.to_s} segment.gsub!(/%D/) {track.DiscCount.to_s} segment.gsub!(/%E/) {track.Enabled == 1 ? 'enabled' : 'disabled'} segment.gsub!(/%l/) {track.Location} segment.gsub!(/%p/) {track.PlayedCount.to_s} segment.gsub!(/%q/) {track.EQ} segment.gsub!(/%g/) {track.Genre} segment.gsub!(/%G/) {track.Grouping} segment.gsub!(/%n/) {track.Name} segment.gsub!(/%r/) {(track.Rating.to_i / 20).to_s} segment.gsub!(/%s/) {track.SkippedCount.to_s} segment.gsub!(/%t/) {track.TrackNumber.to_s} segment.gsub!(/%T/) {track.TrackCount.to_s} segment.gsub!(/%v/) {volume = track.VolumeAdjustment; (1 .. 98).include?(volume) ? volume.next.to_s : volume.to_s} segment.gsub!(/%y/) {track.Year.to_s} segment end #Replace double percent signs with single percent signs and return. output = output_segments.join('%') output += '%' if format =~ /%%$/ #split() doesn't create final empty field if string ends in a delimiter. output end |