vitunes-10.5 0.4.8
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +14 -0
- data/MIT-LICENSE.txt +21 -0
- data/NOTES +246 -0
- data/README.markdown +195 -0
- data/Rakefile +70 -0
- data/TODO +4 -0
- data/bin/vitunes +10 -0
- data/bin/vitunes-help +11 -0
- data/bin/vitunes-install +9 -0
- data/coverage.markdown +5 -0
- data/iTunes.h +507 -0
- data/lib/plugin.erb +10 -0
- data/lib/vitunes/version.rb +3 -0
- data/lib/vitunes-tool-objc +0 -0
- data/lib/vitunes.rb +30 -0
- data/lib/vitunes.vim +381 -0
- data/vitunes/main.m +294 -0
- data/vitunes/vitunes-Prefix.pch +7 -0
- data/vitunes/vitunes.1 +79 -0
- data/vitunes.gemspec +28 -0
- data/vitunes.xcodeproj/project.pbxproj +233 -0
- data/vitunes.xcodeproj/project.xcworkspace/contents.xcworkspacedata +7 -0
- metadata +96 -0
data/lib/vitunes.vim
ADDED
@@ -0,0 +1,381 @@
|
|
1
|
+
" Vim script that lets you search and play iTunes tracks from Vim
|
2
|
+
" Maintainer: Daniel Choi <dhchoi@gmail.com>
|
3
|
+
" License: MIT License (c) 2011 Daniel Choi
|
4
|
+
"
|
5
|
+
" Buid this path with plugin install program
|
6
|
+
|
7
|
+
if exists("g:vitunes_tool")
|
8
|
+
let s:vitunes_tool = g:vitunes_tool
|
9
|
+
else
|
10
|
+
" This is the development version (specific to D Choi's setup)
|
11
|
+
let s:vitunes_tool = 'dev/vitunes '
|
12
|
+
" Maybe I should make this a relative path
|
13
|
+
endif
|
14
|
+
if !exists("g:mapleader")
|
15
|
+
let g:mapleader = ","
|
16
|
+
endif
|
17
|
+
|
18
|
+
let s:searchPrompt = "Search iTunes Music Library: "
|
19
|
+
let s:getPlaylistsCommand = s:vitunes_tool . "playlists"
|
20
|
+
let s:selectPlaylistPrompt = "Select playlist: "
|
21
|
+
let s:getArtistsCommand = s:vitunes_tool . "group artist | uniq "
|
22
|
+
let s:selectArtistPrompt = "Select artist: "
|
23
|
+
let s:getGenresCommand = s:vitunes_tool . "group genre | uniq "
|
24
|
+
let s:selectGenrePrompt = "Select genre: "
|
25
|
+
let s:getAlbumsCommand = s:vitunes_tool . "group album | uniq "
|
26
|
+
let s:selectAlbumPrompt = "Select album: "
|
27
|
+
let s:addTracksToPlaylistPrompt = "Add track(s) to this playlist: "
|
28
|
+
|
29
|
+
let s:currentPlaylist = ''
|
30
|
+
let s:lastPlaylist = ''
|
31
|
+
let s:selectedTrackIds = []
|
32
|
+
|
33
|
+
let s:musicStoreURL = "http://www.amazon.com/gp/redirect.html?ie=UTF8&location=http%3A%2F%2Fwww.amazon.com%2FMP3-Music-Download%2Fb%3Fie%3DUTF8%26node%3D163856011%26ref_%3Dtopnav_storetab_dmusic%23&tag=instantwatche-20&linkCode=ur2&camp=1789&creative=390957"
|
34
|
+
|
35
|
+
func! s:trimString(string)
|
36
|
+
let string = substitute(a:string, '\s\+$', '', '')
|
37
|
+
return substitute(string, '^\s\+', '', '')
|
38
|
+
endfunc
|
39
|
+
|
40
|
+
function! s:collectTrackIds(startline, endline)
|
41
|
+
let trackIds = []
|
42
|
+
let lnum = a:startline
|
43
|
+
while lnum <= a:endline
|
44
|
+
let trackId = matchstr(getline(lnum), '\d\+$')
|
45
|
+
call add(trackIds, trackId)
|
46
|
+
let lnum += 1
|
47
|
+
endwhile
|
48
|
+
return trackIds
|
49
|
+
endfunc
|
50
|
+
|
51
|
+
function! s:runCommand(command)
|
52
|
+
" echom a:command " can use for debugging
|
53
|
+
let res = system(a:command)
|
54
|
+
return res
|
55
|
+
endfunction
|
56
|
+
|
57
|
+
function! ViTunesStatusLine()
|
58
|
+
return "%<%f\ Press ".g:mapleader."? for help. "."%r%=%-14.(%l,%c%V%)\ %P"
|
59
|
+
endfunction
|
60
|
+
|
61
|
+
" the main window
|
62
|
+
function! ViTunes()
|
63
|
+
rightbelow split ViTunesBuffer
|
64
|
+
setlocal cursorline
|
65
|
+
setlocal nowrap
|
66
|
+
setlocal textwidth=0
|
67
|
+
setlocal buftype=nofile
|
68
|
+
setlocal bufhidden=hide
|
69
|
+
setlocal noswapfile
|
70
|
+
noremap <buffer> <Leader>s :call <SID>openQueryWindow()<cr>
|
71
|
+
noremap <buffer> <Leader>p :call <SID>openPlaylistDropdown()<cr>
|
72
|
+
noremap <buffer> <Leader>a :call <SID>openArtistDropdown()<cr>
|
73
|
+
noremap <buffer> <Leader>g :call <SID>openGenreDropdown()<cr>
|
74
|
+
noremap <buffer> <Leader>A :call <SID>openAlbumDropdown()<cr>
|
75
|
+
noremap <buffer> <Leader>c :call <SID>openAddToPlaylistDropDown()<cr>
|
76
|
+
noremap <buffer> <BS> :call <SID>deleteTracksFromPlaylist()<CR> "
|
77
|
+
noremap <buffer> <Leader>P :call <SID>gotoCurrentPlaylist()<cr>
|
78
|
+
|
79
|
+
noremap <buffer> > :call <SID>nextTrack()<cr>
|
80
|
+
noremap <buffer> < :call <SID>prevTrack()<cr>
|
81
|
+
noremap <buffer> >> :call <SID>itunesControl("nextTrack")<cr>
|
82
|
+
noremap <buffer> << :call <SID>itunesControl("backTrack")<cr>
|
83
|
+
|
84
|
+
noremap <buffer> . :call <SID>currentTrackAndPlaylist()<cr>
|
85
|
+
|
86
|
+
noremap <buffer> <Space> :call <SID>itunesControl("playpause")<cr>
|
87
|
+
noremap <buffer> - :call <SID>changeVolume("volumeDown")<cr>
|
88
|
+
noremap <buffer> + :call <SID>changeVolume("volumeUp")<cr>
|
89
|
+
noremap <buffer> = :call <SID>changeVolume("volumeUp")<cr>
|
90
|
+
|
91
|
+
noremap <buffer> <Leader>i :close<CR>
|
92
|
+
noremap <buffer> <Leader>? :call <SID>help()<CR>
|
93
|
+
noremap <buffer> <Leader>z :call <SID>musicStore()<CR>
|
94
|
+
|
95
|
+
noremap <buffer> <cr> :call <SID>playTrack()<cr>
|
96
|
+
setlocal nomodifiable
|
97
|
+
setlocal statusline=%!ViTunesStatusLine()
|
98
|
+
|
99
|
+
command! -buffer -bar -nargs=1 NewPlaylist call <SID>newPlaylist(<f-args>)
|
100
|
+
|
101
|
+
if line('$') == 1 " buffer empty
|
102
|
+
let msg = "Welcome to ViTunes\n\nPress ".g:mapleader."? for help"
|
103
|
+
setlocal modifiable
|
104
|
+
silent! 1,$delete
|
105
|
+
silent! put =msg
|
106
|
+
silent! 1delete
|
107
|
+
setlocal nomodifiable
|
108
|
+
endif
|
109
|
+
endfunction
|
110
|
+
|
111
|
+
function! s:help()
|
112
|
+
" This just displays the README
|
113
|
+
let res = system("vitunes-help")
|
114
|
+
echo res
|
115
|
+
endfunction
|
116
|
+
|
117
|
+
|
118
|
+
function! s:itunesControl(command)
|
119
|
+
let res = s:runCommand(s:vitunes_tool . "itunes ".a:command)
|
120
|
+
echom res
|
121
|
+
endfunction
|
122
|
+
|
123
|
+
function! s:changeVolume(command)
|
124
|
+
let res = s:runCommand(s:vitunes_tool.a:command)
|
125
|
+
echom res
|
126
|
+
endfunction
|
127
|
+
|
128
|
+
function! s:playTrack()
|
129
|
+
let trackID = matchstr(getline(line('.')), '\d\+$')
|
130
|
+
if (trackID == '')
|
131
|
+
return
|
132
|
+
endif
|
133
|
+
let command = ""
|
134
|
+
if (s:currentPlaylist != '')
|
135
|
+
let command = s:vitunes_tool . "playTrackIDFromPlaylist ".trackID.' '.shellescape(s:currentPlaylist)
|
136
|
+
else
|
137
|
+
let command = s:vitunes_tool . "playTrackID " . trackID
|
138
|
+
endif
|
139
|
+
"echom command
|
140
|
+
call system(command)
|
141
|
+
call s:currentTrackAndPlaylist()
|
142
|
+
endfunc
|
143
|
+
|
144
|
+
function! s:hasTrackID(line)
|
145
|
+
if a:line > line('$')
|
146
|
+
return 0
|
147
|
+
end
|
148
|
+
let res = matchstr(getline(a:line), '\d\+$')
|
149
|
+
if res != ''
|
150
|
+
return 1
|
151
|
+
else
|
152
|
+
return 0
|
153
|
+
end
|
154
|
+
endfunction
|
155
|
+
|
156
|
+
" move up or down the visible list of tracks
|
157
|
+
function! s:nextTrack()
|
158
|
+
if s:hasTrackID(line('.') + 1)
|
159
|
+
normal j
|
160
|
+
call s:playTrack()
|
161
|
+
" call s:itunesControl("nextTrack")
|
162
|
+
endif
|
163
|
+
endfunction
|
164
|
+
|
165
|
+
function! s:prevTrack()
|
166
|
+
if s:hasTrackID(line('.') - 1)
|
167
|
+
normal k
|
168
|
+
call s:playTrack()
|
169
|
+
" call s:itunesControl("previousTrack")
|
170
|
+
endif
|
171
|
+
endfunction
|
172
|
+
|
173
|
+
function! s:currentTrackAndPlaylist()
|
174
|
+
let res1 = s:runCommand(s:vitunes_tool . "itunes currentTrack")
|
175
|
+
" fix this on obj-c side later; we just need to make sure this doesn't
|
176
|
+
" spill over to another line.
|
177
|
+
let res2 = s:runCommand(s:vitunes_tool . "itunes currentPlaylist")
|
178
|
+
echo res1[0:60] . ' | '.res2[0:30]
|
179
|
+
endfunction
|
180
|
+
|
181
|
+
function! s:openQueryWindow()
|
182
|
+
leftabove split SearchLibrary
|
183
|
+
setlocal textwidth=0
|
184
|
+
setlocal buftype=nofile
|
185
|
+
setlocal noswapfile
|
186
|
+
setlocal modifiable
|
187
|
+
resize 1
|
188
|
+
inoremap <buffer> <cr> <Esc>:call <SID>submitQueryOrSelection('search')<cr>
|
189
|
+
noremap <buffer> <cr> <Esc>:call <SID>submitQueryOrSelection('search')<cr>
|
190
|
+
noremap <buffer> q <Esc>:close
|
191
|
+
inoremap <buffer> <Esc> <Esc>:close<CR>
|
192
|
+
noremap <buffer> <Esc> <Esc>:close<CR>
|
193
|
+
let s:selectionPrompt = s:searchPrompt " this makes sure we parse the query correctly
|
194
|
+
call setline(1, s:searchPrompt)
|
195
|
+
normal $
|
196
|
+
call feedkeys("a", "t")
|
197
|
+
endfunction
|
198
|
+
|
199
|
+
function! GenericCompletion(findstart, base)
|
200
|
+
if a:findstart
|
201
|
+
let prompt = s:selectionPrompt
|
202
|
+
let start = len(prompt)
|
203
|
+
return start
|
204
|
+
else
|
205
|
+
if (a:base == '')
|
206
|
+
return s:selectionList
|
207
|
+
else
|
208
|
+
let res = []
|
209
|
+
" find tracks matching a:base
|
210
|
+
for m in s:selectionList
|
211
|
+
" why doesn't case insensitive flag work?
|
212
|
+
if m =~ '^\c' . a:base
|
213
|
+
call add(res, m)
|
214
|
+
endif
|
215
|
+
endfor
|
216
|
+
return res
|
217
|
+
endif
|
218
|
+
endif
|
219
|
+
endfun
|
220
|
+
|
221
|
+
function! s:commonDropDownConfig()
|
222
|
+
setlocal textwidth=0
|
223
|
+
setlocal completefunc=GenericCompletion
|
224
|
+
setlocal buftype=nofile
|
225
|
+
setlocal noswapfile
|
226
|
+
setlocal modifiable
|
227
|
+
resize 1
|
228
|
+
noremap <buffer> q <Esc>:close<cr>
|
229
|
+
inoremap <buffer> <Esc> <Esc>:close<cr>
|
230
|
+
call setline(1, s:selectionPrompt)
|
231
|
+
inoremap <buffer> <Tab> <C-x><C-u>
|
232
|
+
normal $
|
233
|
+
endfunction
|
234
|
+
|
235
|
+
" Drop downs
|
236
|
+
function! s:openPlaylistDropdown()
|
237
|
+
leftabove split ChoosePlaylist
|
238
|
+
inoremap <silent> <buffer> <cr> <Esc>:call <SID>submitQueryOrSelection('playlistTracks')<CR>
|
239
|
+
let s:selectionPrompt = s:selectPlaylistPrompt
|
240
|
+
call s:commonDropDownConfig()
|
241
|
+
let s:selectionList = split(system(s:getPlaylistsCommand), '\n')
|
242
|
+
if (s:lastPlaylist != '')
|
243
|
+
call insert(s:selectionList, s:lastPlaylist);
|
244
|
+
endif
|
245
|
+
call feedkeys("a\<c-x>\<c-u>\<c-p>", 't')
|
246
|
+
endfunction
|
247
|
+
|
248
|
+
function! s:openArtistDropdown()
|
249
|
+
leftabove split ChooseArtist
|
250
|
+
inoremap <silent> <buffer> <cr> <Esc>:call <SID>submitQueryOrSelection('artist')<CR>
|
251
|
+
let s:selectionPrompt = s:selectArtistPrompt
|
252
|
+
call s:commonDropDownConfig()
|
253
|
+
let s:selectionList = split(system(s:getArtistsCommand), '\n')
|
254
|
+
call feedkeys("a\<c-x>\<c-u>\<c-p>", 't')
|
255
|
+
endfunction
|
256
|
+
|
257
|
+
function! s:openGenreDropdown()
|
258
|
+
leftabove split ChooseGenre
|
259
|
+
inoremap <silent> <buffer> <cr> <Esc>:call <SID>submitQueryOrSelection('genre')<CR>
|
260
|
+
let s:selectionPrompt = s:selectGenrePrompt
|
261
|
+
call <SID>commonDropDownConfig()
|
262
|
+
let s:selectionList = split(system(s:getGenresCommand), '\n')
|
263
|
+
call feedkeys("a\<c-x>\<c-u>\<c-p>", 't')
|
264
|
+
endfunction
|
265
|
+
|
266
|
+
function! s:openAlbumDropdown()
|
267
|
+
leftabove split ChooseAlbum
|
268
|
+
inoremap <silent> <buffer> <cr> <Esc>:call <SID>submitQueryOrSelection('album')<CR>
|
269
|
+
let s:selectionPrompt = s:selectAlbumPrompt
|
270
|
+
call <SID>commonDropDownConfig()
|
271
|
+
let s:selectionList = split(system(s:getAlbumsCommand), '\n')
|
272
|
+
call feedkeys("a\<c-x>\<c-u>\<c-p>", 't')
|
273
|
+
endfunction
|
274
|
+
|
275
|
+
function! s:openAddToPlaylistDropDown() range
|
276
|
+
let s:selectedTrackIds = s:collectTrackIds(a:firstline, a:lastline)
|
277
|
+
leftabove split ChoosePlaylist
|
278
|
+
inoremap <silent> <buffer> <cr> <Esc>:call <SID>submitQueryOrSelection('addTracksToPlaylist')<CR>
|
279
|
+
let s:selectionPrompt = s:addTracksToPlaylistPrompt
|
280
|
+
echom s:selectionPrompt
|
281
|
+
call <SID>commonDropDownConfig()
|
282
|
+
let s:selectionList = split(system(s:getPlaylistsCommand), '\n')
|
283
|
+
if (s:lastPlaylist != '')
|
284
|
+
call insert(s:selectionList, s:lastPlaylist)
|
285
|
+
endif
|
286
|
+
call feedkeys("a\<c-x>\<c-u>\<c-p>", 't')
|
287
|
+
endfunction
|
288
|
+
|
289
|
+
" selection window pick or search window query
|
290
|
+
function! s:submitQueryOrSelection(command)
|
291
|
+
if (getline('.') =~ '^\s*$')
|
292
|
+
close
|
293
|
+
return
|
294
|
+
endif
|
295
|
+
let query = getline('.')[len(s:selectionPrompt):] " get(split(getline('.'), ':\s*'), 1)
|
296
|
+
close
|
297
|
+
" echom query
|
298
|
+
if (len(query) == 0 || query =~ '^\s*$')
|
299
|
+
return
|
300
|
+
endif
|
301
|
+
if a:command == 'artist'
|
302
|
+
let query = substitute(query, "'", "\'", '')
|
303
|
+
let bcommand = s:vitunes_tool."predicate ".shellescape("artist == '".query."'")
|
304
|
+
elseif a:command == 'genre'
|
305
|
+
let bcommand = s:vitunes_tool."predicate ".shellescape("genre == '".query."'")
|
306
|
+
elseif a:command == 'album'
|
307
|
+
let bcommand = s:vitunes_tool."predicate ".shellescape("album == '".query."'")
|
308
|
+
elseif a:command == 'addTracksToPlaylist'
|
309
|
+
let trackIds = join(s:selectedTrackIds, ',')
|
310
|
+
let bcommand = s:vitunes_tool.a:command." ".trackIds." ".shellescape(query)
|
311
|
+
else
|
312
|
+
let bcommand = s:vitunes_tool . a:command . ' ' . shellescape(query)
|
313
|
+
end
|
314
|
+
echom bcommand
|
315
|
+
let res = s:runCommand(bcommand)
|
316
|
+
if a:command == 'addTracksToPlaylist'
|
317
|
+
redraw
|
318
|
+
echom "Added to playlist '".query."'"
|
319
|
+
return
|
320
|
+
endif
|
321
|
+
setlocal modifiable
|
322
|
+
silent! 1,$delete
|
323
|
+
silent! put =res
|
324
|
+
silent! 1delete
|
325
|
+
setlocal nomodifiable
|
326
|
+
" position cursor at 1st track
|
327
|
+
normal 3G
|
328
|
+
if (a:command == 'playlistTracks') "
|
329
|
+
let s:currentPlaylist = query
|
330
|
+
let s:lastPlaylist = query
|
331
|
+
else
|
332
|
+
let s:currentPlaylist = ''
|
333
|
+
endif
|
334
|
+
endfunction
|
335
|
+
|
336
|
+
function! s:deleteTracksFromPlaylist() range
|
337
|
+
let s:selectedTrackIds = s:collectTrackIds(a:firstline, a:lastline)
|
338
|
+
let trackIds = join(s:selectedTrackIds, ',')
|
339
|
+
let playlist = s:lastPlaylist
|
340
|
+
let bcommand = s:vitunes_tool."deleteTracksFromPlaylist ".trackIds." ".shellescape(playlist)
|
341
|
+
let res = s:runCommand(bcommand)
|
342
|
+
setlocal modifiable
|
343
|
+
exec "silent! ".a:firstline.",".a:lastline."delete"
|
344
|
+
redraw
|
345
|
+
setlocal nomodifiable
|
346
|
+
echom bcommand
|
347
|
+
return
|
348
|
+
endfunc
|
349
|
+
|
350
|
+
function! s:newPlaylist(name)
|
351
|
+
let command = s:vitunes_tool.'newPlaylist '.shellescape(a:name)
|
352
|
+
let res = system(s:vitunes_tool.'newPlaylist '.shellescape(a:name))
|
353
|
+
echom res
|
354
|
+
endfunction
|
355
|
+
|
356
|
+
function! s:gotoCurrentPlaylist()
|
357
|
+
let playlist = s:runCommand(s:vitunes_tool . "itunes currentPlaylist")
|
358
|
+
if playlist != ''
|
359
|
+
let bcommand = s:vitunes_tool.'playlistTracks '.shellescape(playlist)
|
360
|
+
let res = s:runCommand(bcommand)
|
361
|
+
setlocal modifiable
|
362
|
+
silent! 1,$delete
|
363
|
+
silent! put =res
|
364
|
+
silent! 1delete
|
365
|
+
setlocal nomodifiable
|
366
|
+
normal 3G
|
367
|
+
let s:currentPlaylist = playlist
|
368
|
+
let s:lastPlaylist = playlist
|
369
|
+
endif
|
370
|
+
endfunction
|
371
|
+
|
372
|
+
function! s:musicStore()
|
373
|
+
call system("open ".shellescape(s:musicStoreURL))
|
374
|
+
endfunc
|
375
|
+
|
376
|
+
nnoremap <silent> <leader>i :call ViTunes()<cr>
|
377
|
+
nnoremap <silent> <leader>I :call ViTunes()<cr>:only<CR>
|
378
|
+
|
379
|
+
|
380
|
+
let g:ViTunesLoaded = 1
|
381
|
+
|
data/vitunes/main.m
ADDED
@@ -0,0 +1,294 @@
|
|
1
|
+
//
|
2
|
+
// main.m
|
3
|
+
// vitunes
|
4
|
+
//
|
5
|
+
// Created by Daniel Choi on 6/29/11.
|
6
|
+
// Copyright 2011 by Daniel Choi. All rights reserved.
|
7
|
+
//
|
8
|
+
#import "iTunes.h"
|
9
|
+
#import <Foundation/Foundation.h>
|
10
|
+
|
11
|
+
static iTunesApplication *iTunes;
|
12
|
+
static iTunesPlaylist *libraryPlaylist;
|
13
|
+
static iTunesSource *library;
|
14
|
+
int const ARTIST_COL = 26;
|
15
|
+
int const TRACK_NAME_COL = 33;
|
16
|
+
int const GENRE_COL = 15;
|
17
|
+
int const KIND_COL = 29;
|
18
|
+
int const TIME_COL = 7;
|
19
|
+
int const YEAR_COL = 4;
|
20
|
+
int const ALBUM_COL = 30;
|
21
|
+
|
22
|
+
NSString *trackListHeader() {
|
23
|
+
NSString *artist = [@"Artist" stringByPaddingToLength:ARTIST_COL withString:@" " startingAtIndex:0];
|
24
|
+
NSString *name = [@"Track" stringByPaddingToLength:TRACK_NAME_COL withString:@" " startingAtIndex:0];
|
25
|
+
NSString *genre = [@"Genre" stringByPaddingToLength:GENRE_COL withString:@" " startingAtIndex:0];
|
26
|
+
NSString *time = [@"Time" stringByPaddingToLength:TIME_COL withString:@" " startingAtIndex:0];
|
27
|
+
NSString *kind = [@"Kind" stringByPaddingToLength:KIND_COL withString:@" " startingAtIndex:0];
|
28
|
+
NSString *year = [@"Year" stringByPaddingToLength:YEAR_COL withString:@" " startingAtIndex:0];
|
29
|
+
NSString *album = [@"Album" stringByPaddingToLength:ALBUM_COL withString:@" " startingAtIndex:0];
|
30
|
+
|
31
|
+
NSString *artistSpacer = [@"-" stringByPaddingToLength:ARTIST_COL withString:@"-" startingAtIndex:0];
|
32
|
+
NSString *nameSpacer = [@"-" stringByPaddingToLength:TRACK_NAME_COL withString:@"-" startingAtIndex:0];
|
33
|
+
NSString *genreSpacer = [@"-" stringByPaddingToLength:GENRE_COL withString:@"-" startingAtIndex:0];
|
34
|
+
NSString *timeSpacer = [@"-" stringByPaddingToLength:TIME_COL withString:@"-" startingAtIndex:0];
|
35
|
+
NSString *kindSpacer = [@"-" stringByPaddingToLength:KIND_COL withString:@"-" startingAtIndex:0];
|
36
|
+
NSString *yearSpacer = [@"-" stringByPaddingToLength:YEAR_COL withString:@"-" startingAtIndex:0];
|
37
|
+
NSString *albumSpacer = [@"-" stringByPaddingToLength:ALBUM_COL withString:@"-" startingAtIndex:0];
|
38
|
+
|
39
|
+
NSString *headers = [NSString stringWithFormat: @"%@ | %@ | %@ | %@ | %@ | %@ | %@ | DatabaseID", artist, name, album, year, genre, time, kind ];
|
40
|
+
NSString *divider = [NSString stringWithFormat: @"%@-|-%@-|-%@-|-%@-|-%@-|-%@-|-%@-|-----------",
|
41
|
+
artistSpacer, nameSpacer, albumSpacer, yearSpacer, genreSpacer, timeSpacer, kindSpacer ];
|
42
|
+
return [NSString stringWithFormat:@"%@\n%@", headers, divider];
|
43
|
+
};
|
44
|
+
|
45
|
+
NSString *formatTrackForDisplay(iTunesTrack *track) {
|
46
|
+
NSString *artist = [track.artist stringByPaddingToLength:ARTIST_COL withString:@" " startingAtIndex:0];
|
47
|
+
NSString *name = [track.name stringByPaddingToLength:TRACK_NAME_COL withString:@" " startingAtIndex:0];
|
48
|
+
NSString *genre = [track.genre stringByPaddingToLength:GENRE_COL withString:@" " startingAtIndex:0];
|
49
|
+
NSString *timeValue = track.time != nil ? track.time : @" ";
|
50
|
+
NSString *time = [timeValue stringByPaddingToLength:TIME_COL withString:@" " startingAtIndex:0];
|
51
|
+
NSString *kind = [track.kind stringByPaddingToLength:KIND_COL withString:@" " startingAtIndex:0];
|
52
|
+
NSString *year;
|
53
|
+
if (track.year != 0) {
|
54
|
+
year = [NSString stringWithFormat:@"%d", track.year];
|
55
|
+
} else {
|
56
|
+
year = @" ";
|
57
|
+
}
|
58
|
+
NSString *album = [track.album stringByPaddingToLength:ALBUM_COL withString:@" " startingAtIndex:0];
|
59
|
+
return [NSString stringWithFormat: @"%@ | %@ | %@ | %@ | %@ | %@ | %@ | %d",
|
60
|
+
artist,
|
61
|
+
name,
|
62
|
+
album,
|
63
|
+
year,
|
64
|
+
genre,
|
65
|
+
time,
|
66
|
+
kind,
|
67
|
+
track.databaseID
|
68
|
+
];
|
69
|
+
}
|
70
|
+
|
71
|
+
SBElementArray *search(NSArray *args) {
|
72
|
+
NSString *query = [args componentsJoinedByString:@" "];
|
73
|
+
return [libraryPlaylist searchFor:query only:iTunesESrAAll];
|
74
|
+
}
|
75
|
+
|
76
|
+
NSNumber *convertNSStringToNumber(NSString *s) {
|
77
|
+
NSNumberFormatter * f = [[NSNumberFormatter alloc] init];
|
78
|
+
[f setNumberStyle:NSNumberFormatterDecimalStyle];
|
79
|
+
NSNumber *result = [f numberFromString:s];
|
80
|
+
[f release];
|
81
|
+
return result;
|
82
|
+
}
|
83
|
+
|
84
|
+
iTunesTrack *findTrackID(NSString *trackID) {
|
85
|
+
NSNumber *databaseId = convertNSStringToNumber(trackID);
|
86
|
+
NSArray *xs = [[libraryPlaylist tracks] filteredArrayUsingPredicate: [NSPredicate predicateWithFormat:@"databaseID == %@", databaseId]];
|
87
|
+
iTunesTrack* t = [xs objectAtIndex:0];
|
88
|
+
return t;
|
89
|
+
}
|
90
|
+
|
91
|
+
void playTrackID(NSString *trackID) {
|
92
|
+
iTunesTrack* t = findTrackID(trackID);
|
93
|
+
NSLog(@"Playing track: %@", [t name]);
|
94
|
+
// TODO report a missing track
|
95
|
+
[t playOnce:true]; // false would play next song on list after this one finishes
|
96
|
+
}
|
97
|
+
|
98
|
+
void playTrackIDFromPlaylist(NSString *trackID, NSString *playlistName) {
|
99
|
+
NSNumber *databaseId = convertNSStringToNumber(trackID);
|
100
|
+
iTunesPlaylist *playlist = [[library playlists] objectWithName:playlistName];
|
101
|
+
NSArray *xs = [[playlist tracks] filteredArrayUsingPredicate: [NSPredicate predicateWithFormat:@"databaseID == %@", databaseId]];
|
102
|
+
if ([xs count] == 0) {
|
103
|
+
NSLog(@"Could not find trackID: %@ in playlist: %@. Playing from Library.", trackID, playlistName);
|
104
|
+
playTrackID(trackID);
|
105
|
+
return;
|
106
|
+
} else {
|
107
|
+
iTunesTrack* t = [xs objectAtIndex:0];
|
108
|
+
NSLog(@"Playing track: %@ from playlist: %@", [t name], playlistName);
|
109
|
+
[t playOnce:false]; // play playlist continuously
|
110
|
+
}
|
111
|
+
}
|
112
|
+
|
113
|
+
void addTracksToPlaylistName(NSString *trackIds, NSString *playlistName) {
|
114
|
+
iTunesPlaylist *playlist = [[library playlists] objectWithName:playlistName];
|
115
|
+
for (NSString *trackID in [trackIds componentsSeparatedByString:@","]) {
|
116
|
+
iTunesTrack* t = findTrackID(trackID);
|
117
|
+
NSLog(@"Adding track: %@ to playlist: %@", [t name], [playlist name]);
|
118
|
+
[t duplicateTo:playlist];
|
119
|
+
}
|
120
|
+
}
|
121
|
+
|
122
|
+
void deleteTracksFromPlaylistName(NSString *trackIds, NSString *playlistName) {
|
123
|
+
iTunesPlaylist *playlist = [[library playlists] objectWithName:playlistName];
|
124
|
+
for (NSString *trackID in [trackIds componentsSeparatedByString:@","]) {
|
125
|
+
NSNumber *databaseId = convertNSStringToNumber(trackID);
|
126
|
+
NSArray *xs = [[playlist tracks] filteredArrayUsingPredicate: [NSPredicate predicateWithFormat:@"databaseID == %@", databaseId]];
|
127
|
+
iTunesTrack* t = [xs objectAtIndex:0];
|
128
|
+
NSLog(@"Removing track: %@ from playlist: %@", [t name], [playlist name]);
|
129
|
+
[t delete];
|
130
|
+
}
|
131
|
+
}
|
132
|
+
|
133
|
+
void groupTracksBy(NSString *property) {
|
134
|
+
// gets list of all e.g. artists, genres
|
135
|
+
// NOTE year won't work yet
|
136
|
+
// NOTE pipe the output through uniq
|
137
|
+
NSArray *results = [[[libraryPlaylist tracks] arrayByApplyingSelector:NSSelectorFromString(property)]
|
138
|
+
filteredArrayUsingPredicate: [NSPredicate predicateWithFormat:@"%@ != ''", property]];
|
139
|
+
NSArray *sortedResults = [results sortedArrayUsingSelector:@selector(localizedCaseInsensitiveCompare:)];
|
140
|
+
for (NSString *s in sortedResults) {
|
141
|
+
printf("%s\n", [s cStringUsingEncoding: NSUTF8StringEncoding]);
|
142
|
+
}
|
143
|
+
}
|
144
|
+
|
145
|
+
void printTracks(NSArray *tracks) {
|
146
|
+
printf("%s\n", [trackListHeader() cStringUsingEncoding: NSUTF8StringEncoding]);
|
147
|
+
for (iTunesTrack *track in tracks) {
|
148
|
+
printf("%s\n", [formatTrackForDisplay(track) cStringUsingEncoding: NSUTF8StringEncoding]);
|
149
|
+
}
|
150
|
+
}
|
151
|
+
|
152
|
+
void playlists() {
|
153
|
+
for (iTunesPlaylist *p in [library playlists]) {
|
154
|
+
printf("%s\n", [p.name cStringUsingEncoding: NSUTF8StringEncoding]);
|
155
|
+
}
|
156
|
+
}
|
157
|
+
|
158
|
+
void playlistTracks(NSString *playlistName) {
|
159
|
+
iTunesPlaylist *playlist = [[library playlists] objectWithName:playlistName];
|
160
|
+
printTracks([playlist tracks]);
|
161
|
+
}
|
162
|
+
|
163
|
+
void playPlaylist(NSString *playlistName) {
|
164
|
+
iTunesPlaylist *playlist = [[library playlists] objectWithName:playlistName];
|
165
|
+
if (playlist == nil) {
|
166
|
+
NSLog(@"No playlist found: %@", playlistName);
|
167
|
+
}
|
168
|
+
[playlist playOnce:true];
|
169
|
+
}
|
170
|
+
|
171
|
+
void tracksMatchingPredicate(NSString *predString) {
|
172
|
+
// predicate can be something like "artist == 'U2'"
|
173
|
+
NSArray *tracks = [[libraryPlaylist tracks]
|
174
|
+
filteredArrayUsingPredicate: [NSPredicate predicateWithFormat:predString]];
|
175
|
+
printTracks(tracks);
|
176
|
+
}
|
177
|
+
|
178
|
+
// This dispatches to any methods on iTunesApplication with no parameters.
|
179
|
+
// If method returns an iTunesItem, its 'name' property will be called and
|
180
|
+
// displayed.
|
181
|
+
void itunes(NSString *command) {
|
182
|
+
SEL selector = NSSelectorFromString(command);
|
183
|
+
id result = [iTunes performSelector:selector];
|
184
|
+
if (result) {
|
185
|
+
if ([result respondsToSelector:@selector(name)]) {
|
186
|
+
NSString *s;
|
187
|
+
// Note that the real classname is ITunesTrack, not iTunesTrack;
|
188
|
+
// For current Track
|
189
|
+
if ([[result className] isEqual:@"ITunesTrack"]) {
|
190
|
+
if ([result name ] == nil) {
|
191
|
+
printf("No current track");
|
192
|
+
return;
|
193
|
+
}
|
194
|
+
s = [NSString stringWithFormat:@"\"%@\" by %@",
|
195
|
+
[((iTunesTrack *)result) name],
|
196
|
+
[((iTunesTrack *)result) artist]
|
197
|
+
];
|
198
|
+
printf("%s", [s cStringUsingEncoding: NSUTF8StringEncoding]);
|
199
|
+
} else if ([[result className] isEqual:@"ITunesPlaylist"]) {
|
200
|
+
if ([result name ] == nil) {
|
201
|
+
printf("No current playlist");
|
202
|
+
return;
|
203
|
+
}
|
204
|
+
s = [NSString stringWithFormat:@"%@", [((iTunesPlaylist *)result) name]];
|
205
|
+
printf("%s", [s cStringUsingEncoding: NSUTF8StringEncoding]);
|
206
|
+
} else {
|
207
|
+
s = ((iTunesItem *)result).name;
|
208
|
+
printf("%s\n", [s cStringUsingEncoding: NSUTF8StringEncoding]);
|
209
|
+
}
|
210
|
+
}
|
211
|
+
}
|
212
|
+
}
|
213
|
+
|
214
|
+
void turnVolume(NSString *direction) {
|
215
|
+
NSInteger currentVolume = iTunes.soundVolume;
|
216
|
+
NSInteger increment = 8;
|
217
|
+
if (currentVolume < 100 && [direction isEqual:@"up"]) {
|
218
|
+
iTunes.soundVolume += increment;
|
219
|
+
} else if (currentVolume > 0 && [direction isEqual:@"down"]) {
|
220
|
+
iTunes.soundVolume -= increment;
|
221
|
+
}
|
222
|
+
printf("Changing volume %d -> %d", (int)currentVolume, (int)iTunes.soundVolume);
|
223
|
+
}
|
224
|
+
|
225
|
+
void newPlaylist(NSString *name) {
|
226
|
+
// note that we have to force realize it with get to check for missing
|
227
|
+
iTunesPlaylist *existingPlaylist = [[[library playlists] objectWithName:name] get];
|
228
|
+
if (existingPlaylist != nil) {
|
229
|
+
printf("%s already exists", [[existingPlaylist name] cStringUsingEncoding: NSUTF8StringEncoding]);
|
230
|
+
return;
|
231
|
+
}
|
232
|
+
NSDictionary *props = [NSDictionary dictionaryWithObject:name forKey:@"name"];
|
233
|
+
iTunesPlaylist *playlist = [[[iTunes classForScriptingClass:@"user playlist"] alloc] initWithProperties:props];
|
234
|
+
[[library playlists] addObject:playlist];
|
235
|
+
//[playlist reveal];
|
236
|
+
printf("Created new playlist: %s", [name cStringUsingEncoding:NSUTF8StringEncoding]);
|
237
|
+
}
|
238
|
+
|
239
|
+
int main (int argc, const char * argv[]) {
|
240
|
+
NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];
|
241
|
+
NSArray *rawArgs = [[NSProcessInfo processInfo] arguments];
|
242
|
+
NSString *action;
|
243
|
+
NSArray *args;
|
244
|
+
if ([rawArgs count] < 2) {
|
245
|
+
action = @"search";
|
246
|
+
args = [NSArray arrayWithObject:@"bach"];
|
247
|
+
} else {
|
248
|
+
action = [rawArgs objectAtIndex:1];
|
249
|
+
NSRange aRange;
|
250
|
+
aRange.location = 2;
|
251
|
+
aRange.length = [rawArgs count] - 2;
|
252
|
+
args = [rawArgs subarrayWithRange:aRange];
|
253
|
+
}
|
254
|
+
iTunes = [SBApplication applicationWithBundleIdentifier:@"com.apple.iTunes"];
|
255
|
+
library = [[iTunes sources] objectAtIndex:0];
|
256
|
+
libraryPlaylist = [[library playlists] objectAtIndex:0];
|
257
|
+
|
258
|
+
|
259
|
+
if ([action isEqual: @"search"]) {
|
260
|
+
printTracks(search(args));
|
261
|
+
} else if ([action isEqual: @"playTrackID"]) {
|
262
|
+
playTrackID([args objectAtIndex:0]);
|
263
|
+
} else if ([action isEqual: @"playTrackIDFromPlaylist"]) {
|
264
|
+
playTrackIDFromPlaylist([args objectAtIndex:0], [args objectAtIndex:1]);
|
265
|
+
} else if ([action isEqual: @"group"]) {
|
266
|
+
groupTracksBy([args objectAtIndex:0]);
|
267
|
+
} else if ([action isEqual: @"predicate"]) {
|
268
|
+
tracksMatchingPredicate([args objectAtIndex:0]);
|
269
|
+
} else if ([action isEqual: @"playlists"]) {
|
270
|
+
playlists();
|
271
|
+
} else if ([action isEqual: @"playlistTracks"]) {
|
272
|
+
playlistTracks([args objectAtIndex:0]);
|
273
|
+
} else if ([action isEqual: @"playPlaylist"]) {
|
274
|
+
playPlaylist([args objectAtIndex:0]);
|
275
|
+
} else if ([action isEqual: @"addTracksToPlaylist"]) {
|
276
|
+
// make sure to quote first arg
|
277
|
+
addTracksToPlaylistName([args objectAtIndex:0], [args objectAtIndex:1]);
|
278
|
+
} else if ([action isEqual: @"deleteTracksFromPlaylist"]) {
|
279
|
+
// make sure to quote first arg
|
280
|
+
deleteTracksFromPlaylistName([args objectAtIndex:0], [args objectAtIndex:1]);
|
281
|
+
} else if ([action isEqual: @"volumeUp"]) {
|
282
|
+
turnVolume(@"up");
|
283
|
+
} else if ([action isEqual: @"volumeDown"]) {
|
284
|
+
turnVolume(@"down");
|
285
|
+
} else if ([action isEqual: @"newPlaylist"]) {
|
286
|
+
newPlaylist([args objectAtIndex:0]);
|
287
|
+
} else if ([action isEqual: @"itunes"]) {
|
288
|
+
// argument is an action for iTunesApplication to perform
|
289
|
+
itunes([args objectAtIndex:0]);
|
290
|
+
}
|
291
|
+
[pool drain];
|
292
|
+
return 0;
|
293
|
+
}
|
294
|
+
|