ipod_db 0.2.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
data/.gitignore ADDED
@@ -0,0 +1,2 @@
1
+ pkg/
2
+ Gemfile.lock
data/.travis.yml ADDED
@@ -0,0 +1,8 @@
1
+ language: ruby
2
+ rvm:
3
+ - "1.9.3"
4
+ - rbx-19mode
5
+
6
+ before_install:
7
+ - sudo apt-get update -qq
8
+ - sudo apt-get install -qq libtag1-dev
data/Gemfile ADDED
@@ -0,0 +1,2 @@
1
+ source "https://rubygems.org"
2
+ gemspec
data/Guardfile ADDED
@@ -0,0 +1,13 @@
1
+ # A sample Guardfile
2
+ # More info at https://github.com/guard/guard#readme
3
+
4
+ guard 'bundler' do
5
+ watch('Gemfile')
6
+ end
7
+
8
+ guard 'minitest' do
9
+ # with Minitest::Spec
10
+ watch(%r|^spec/(.*)_spec\.rb|)
11
+ watch(%r|^lib/(.*)([^/]+)\.rb|) { |m| "spec/#{m[1]}#{m[2]}_spec.rb" }
12
+ watch(%r|^spec/spec_helper\.rb|) { "spec" }
13
+ end
data/HISTORY ADDED
@@ -0,0 +1,9 @@
1
+ I used to do the same sort of thing and more with a bunch of python scripts.
2
+ My cheapo mass-storage mp3 player has died recently and my son has donated
3
+ (voluntarily) his Shuffle to me, but python scripts were all bit-rot and
4
+ refused to work and I haven't touched python for several years already, so
5
+ I rewrote the main script in ruby. The reading part of python version still
6
+ worked so I made it dump what it read in ruby-esque format for testing
7
+ (that's test_data.rb). The test_data isn't very exciting though (all
8
+ tracks are in default state without bookmarks).
9
+
data/LICENSE ADDED
@@ -0,0 +1,24 @@
1
+ This is free and unencumbered software released into the public domain.
2
+
3
+ Anyone is free to copy, modify, publish, use, compile, sell, or
4
+ distribute this software, either in source code form or as a compiled
5
+ binary, for any purpose, commercial or non-commercial, and by any
6
+ means.
7
+
8
+ In jurisdictions that recognize copyright laws, the author or authors
9
+ of this software dedicate any and all copyright interest in the
10
+ software to the public domain. We make this dedication for the benefit
11
+ of the public at large and to the detriment of our heirs and
12
+ successors. We intend this dedication to be an overt act of
13
+ relinquishment in perpetuity of all present and future rights to this
14
+ software under copyright law.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
19
+ IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
20
+ OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
21
+ ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
22
+ OTHER DEALINGS IN THE SOFTWARE.
23
+
24
+ For more information, please refer to <http://unlicense.org/>
data/README.md ADDED
@@ -0,0 +1,132 @@
1
+ NAME
2
+ ====
3
+
4
+ ipod_db v0.2.4
5
+
6
+ SYNOPSIS
7
+ ========
8
+
9
+ ipod_db (sync|ls|rm) [options]+
10
+
11
+ DESCRIPTION
12
+ ===========
13
+
14
+ A couple of tools for working with iPod Shuffle (2nd gen) database from
15
+ command line. Each subcommand understands -h/--help flag.
16
+
17
+ PARAMETERS
18
+ ==========
19
+
20
+ --version, -v
21
+ show package version and exit
22
+ --help, -h
23
+
24
+ AUTHOR
25
+ ======
26
+
27
+ artm <femistofel@gmail.com>
28
+
29
+
30
+ [![Build Status](https://travis-ci.org/artm/ipod_db.png)](https://travis-ci.org/artm/ipod_db)
31
+
32
+
33
+ SUBCOMMAND: sync
34
+ ================
35
+
36
+ SYNOPSIS
37
+ ========
38
+
39
+ ipod_db sync [options]+
40
+
41
+ DESCRIPTION
42
+ ===========
43
+
44
+ Update the iPod database. Given directories of bookmarkable and non-bookmarkable
45
+ media 'ipod' will find all supported tracks add them to the iPod database so
46
+ the device is aware of their existance.
47
+
48
+ The tracks under 'books' folder will get "riffled" - tracks from the same folder
49
+ are spread out in the list so they don't follow each other if possible. IN THE
50
+ FUTURE it is planned to allow configuring of track groups which are treated like
51
+ single folders - e.g. to spread out all SciAm's "60 second somthing" podcasts along
52
+ the playlist.
53
+
54
+ It is perfectly possible to have other directories full of tracks in device's
55
+ subconscious - e.g. when time-sharing the device among members of a poor
56
+ family. Just make sure you update the database using your directories when
57
+ receiving it from a relation.
58
+
59
+ iPod remembers playback position on bookmarkable media and the 'ipod' goes
60
+ out of its way to preserve the bookmarks. It also removes bookmarkable files
61
+ from shuffle list.
62
+
63
+ I configure gpodder to place podcast files inside IPOD/books directory and delete
64
+ them after syncing. Having copied podcasts I run 'ipod sync' to update the
65
+ database on the device and it's ready for consumption.
66
+
67
+ PARAMETERS
68
+ ==========
69
+
70
+ --version, -v
71
+ show package version and exit
72
+ --books=books, -b (0 ~> books=books)
73
+ subdirectory of ipod with bookmarkable media
74
+ --songs=songs, -s (0 ~> songs=songs)
75
+ subdirectory of ipod with non-bookmarkable media
76
+ --help, -h
77
+
78
+ SUBCOMMAND: ls
79
+ ==============
80
+
81
+ SYNOPSIS
82
+ ========
83
+
84
+ ipod_db ls [options]+
85
+
86
+ DESCRIPTION
87
+ ===========
88
+
89
+ produce a colorful listing of the tracks in the ipod database
90
+
91
+ PARAMETERS
92
+ ==========
93
+
94
+ --version, -v
95
+ show package version and exit
96
+ --help, -h
97
+
98
+ SUBCOMMAND: rm
99
+ ==============
100
+
101
+ SYNOPSIS
102
+ ========
103
+
104
+ ipod_db rm track track* [options]+
105
+
106
+ DESCRIPTION
107
+ ===========
108
+
109
+ Remove tracks from the device by their numbers (that's why ls
110
+ displays numbers: so it's easier to select them for rm).
111
+
112
+ PARAMETERS
113
+ ==========
114
+
115
+ track (-2 -> track)
116
+ track numbers to delete from device (ranges like 2-5 are accepted
117
+ too).
118
+ --version, -v
119
+ show package version and exit
120
+ --help, -h
121
+
122
+ HISTORY
123
+ =======
124
+
125
+ I used to do the same sort of thing and more with a bunch of python scripts.
126
+ My cheapo mass-storage mp3 player has died recently and my son has donated
127
+ (voluntarily) his Shuffle to me, but python scripts were all bit-rot and
128
+ refused to work and I haven't touched python for several years already, so
129
+ I rewrote the main script in ruby. The reading part of python version still
130
+ worked so I made it dump what it read in ruby-esque format for testing
131
+ (that's test_data.rb). The test_data isn't very exciting though (all
132
+ tracks are in default state without bookmarks).
data/Rakefile ADDED
@@ -0,0 +1,41 @@
1
+ task :default => [:test, :build]
2
+ task :readme do
3
+ readme = `bin/ipod help`
4
+
5
+ readme += <<-__
6
+
7
+ [![Build Status](https://travis-ci.org/artm/ipod_db.png)](https://travis-ci.org/artm/ipod_db)
8
+
9
+ __
10
+
11
+ %w(sync ls rm).each do |subcommand|
12
+ readme += "\nSUBCOMMAND: #{subcommand}\n"
13
+
14
+ rejecting = false
15
+ rejects = %w(name author)
16
+ readme += `bin/ipod #{subcommand} -h`.split("\n").reject do |line|
17
+ if line =~ /^\w/
18
+ rejecting = rejects.include? line.downcase
19
+ else
20
+ rejecting
21
+ end
22
+ end.join "\n"
23
+ end
24
+
25
+ readme += "\nHISTORY\n"
26
+ readme += IO.read('HISTORY').split("\n").map{|s| " #{s}"}.join("\n")
27
+
28
+ File.open('README.md','w') do |io|
29
+ io.puts readme.gsub(/^\w.*$/) {|m| "#{$&}\n#{'=' * $&.length}\n" }.gsub(/^ /,' ')
30
+ end
31
+ end
32
+
33
+ Rake::TestTask.new { |t|
34
+ t.libs << 'spec'
35
+ t.pattern = 'spec/*_spec.rb'
36
+ }
37
+
38
+ BEGIN {
39
+ require 'bundler/gem_tasks'
40
+ require 'rake/testtask'
41
+ }
data/TODO ADDED
@@ -0,0 +1,3 @@
1
+ - allow grouping some podcasts so their episodes are spread out (via config field)
2
+ - display totals in ls output: total time and bytes; bytes left on device etc
3
+
data/bin/ipod ADDED
@@ -0,0 +1,245 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ Main {
4
+ version IpodDB::VERSION
5
+
6
+ description <<-__
7
+ A couple of tools for working with iPod Shuffle (2nd gen) database from
8
+ command line. Each subcommand understands -h/--help flag.
9
+ __
10
+
11
+ author 'artm <femistofel@gmail.com>'
12
+
13
+ name 'ipod_db'
14
+ config ipod_root: "/media/#{ENV['USER']}/IPOD"
15
+ option('version','v') {
16
+ description 'show package version and exit'
17
+ }
18
+ IgnorePlaybackPosUnder = 10
19
+ IgnoreProgressUnder = 0.01
20
+
21
+ def run
22
+ if param['version'].given?
23
+ puts IpodDB::VERSION
24
+ else
25
+ help!
26
+ end
27
+ end
28
+
29
+ mode('sync') {
30
+ description <<-__
31
+ Update the iPod database. Given directories of bookmarkable and non-bookmarkable
32
+ media '#{program}' will find all supported tracks add them to the iPod database so
33
+ the device is aware of their existance.
34
+
35
+ The tracks under 'books' folder will get "riffled" - tracks from the same folder
36
+ are spread out in the list so they don't follow each other if possible. IN THE
37
+ FUTURE it is planned to allow configuring of track groups which are treated like
38
+ single folders - e.g. to spread out all SciAm's "60 second somthing" podcasts along
39
+ the playlist.
40
+
41
+ It is perfectly possible to have other directories full of tracks in device's
42
+ subconscious - e.g. when time-sharing the device among members of a poor
43
+ family. Just make sure you update the database using your directories when
44
+ receiving it from a relation.
45
+
46
+ iPod remembers playback position on bookmarkable media and the '#{program}' goes
47
+ out of its way to preserve the bookmarks. It also removes bookmarkable files
48
+ from shuffle list.
49
+
50
+ I configure gpodder to place podcast files inside IPOD/books directory and delete
51
+ them after syncing. Having copied podcasts I run 'ipod sync' to update the
52
+ database on the device and it's ready for consumption.
53
+ __
54
+
55
+ option('books','b') {
56
+ argument_required
57
+ default 'books'
58
+ description 'subdirectory of ipod with bookmarkable media'
59
+ }
60
+ option('songs','s') {
61
+ argument_required
62
+ default 'songs'
63
+ description 'subdirectory of ipod with non-bookmarkable media'
64
+ }
65
+ def run
66
+ load_ipod_db
67
+ sync
68
+ end
69
+ }
70
+
71
+ mode('ls') {
72
+ description 'produce a colorful listing of the tracks in the ipod database'
73
+
74
+ def run
75
+ load_ipod_db
76
+ ls
77
+ end
78
+
79
+ }
80
+
81
+ mode('rm') {
82
+ description <<-__
83
+ Remove tracks from the device by their numbers (that's why ls
84
+ displays numbers: so it's easier to select them for rm).
85
+ __
86
+ argument('track') {
87
+ arity -2
88
+ description <<-__
89
+ track numbers to delete from device (ranges like
90
+ 2-5 are accepted too).
91
+ __
92
+ }
93
+ def run
94
+ load_ipod_db
95
+ rm
96
+ end
97
+ }
98
+
99
+ def sync
100
+ books_path = params['books'].value
101
+ songs_path = params['songs'].value
102
+ books = collect_tracks books_path
103
+ books = riffle(books)
104
+ songs = collect_tracks songs_path
105
+ @ipod_db.update books: books, songs: songs
106
+ @ipod_db.save
107
+ ls
108
+ end
109
+
110
+ def ls
111
+ @ipod_db.each_track_with_index {|track,i| list_track i, track}
112
+ end
113
+
114
+ def rm
115
+ tracks = parse_track_list(params['track'].values).map{|i,t| [i,t.filename]}
116
+ puts "The following tracks are selected for removal:"
117
+ tracks.each { |i,path| puts " %2d. %s" % [i,path.green] }
118
+ if agree "Are you sure you want them gone (Y/n)?", true
119
+ FileUtils.rm tracks.map{|i,path|resolve_ipod_path path}
120
+ end
121
+ sync
122
+ end
123
+
124
+ def ipod_path path
125
+ '/' + Pathname.new(path).relative_path_from(Pathname.new @ipod_root).to_s
126
+ end
127
+
128
+ def resolve_ipod_path ipath
129
+ File.join @ipod_root, ipath
130
+ end
131
+
132
+ def track_length( path )
133
+ TagLib::FileRef.open(path){|file| file.audio_properties.length}
134
+ end
135
+
136
+ def load_ipod_db
137
+ @ipod_root = config['ipod_root']
138
+ unless IpodDB.looks_like_ipod? @ipod_root
139
+ fatal { "#{@ipod_root} does not appear to be a mounted ipod" }
140
+ exit exit_failure
141
+ end
142
+ @ipod_db = IpodDB.new @ipod_root
143
+ end
144
+
145
+ def collect_tracks path
146
+ begin
147
+ tracks = []
148
+ Find.find(resolve_ipod_path path) do |filepath|
149
+ tracks << ipod_path(filepath) if track? filepath
150
+ end
151
+ tracks
152
+ rescue Errno::ENOENT
153
+ []
154
+ end
155
+ end
156
+
157
+ def track? path
158
+ IpodDB::ExtToFileType.include? File.extname(path)
159
+ end
160
+
161
+ def track_info track
162
+ info = Map.new
163
+ info['playcount'] = track.playcount if track.playcount > 0
164
+ info['skipcount'] = track.skippedcount if track.skippedcount > 0
165
+ if track.bookmarkflag && track.bookmarktime > 0
166
+ pos = track.bookmarktime * 0.256 # ipod keeps time in 256 ms increments
167
+ abs_path = resolve_ipod_path track.filename
168
+ total_time = track_length(abs_path)
169
+ if pos > IgnorePlaybackPosUnder && pos / total_time >= IgnoreProgressUnder
170
+ info['pos'] = Pretty.seconds pos
171
+ info['total'] = Pretty.seconds total_time
172
+ # and cache seconds
173
+ track['pos'] = pos
174
+ track['total_time'] = total_time
175
+ end
176
+ end
177
+ info
178
+ end
179
+
180
+ def list_track i, track
181
+ abs_path = resolve_ipod_path track.filename
182
+ exists = File.exists?(abs_path)
183
+ track_color = exists ? :green : :red
184
+
185
+ listing_entry = "%2d: %s" % [i,track.filename.apply_format( color: track_color )]
186
+ listing_entry = listing_entry.bold if @ipod_db.playback_state.trackno == i
187
+ puts listing_entry
188
+
189
+ return unless exists
190
+
191
+ info = track_info(track)
192
+ if track.include? 'pos'
193
+ progress = ProgressBar.create(
194
+ format: " [%b #{info.pos.yellow} %P%%%i] #{info.total.yellow}",
195
+ starting_at: track.pos,
196
+ total: track.total_time,
197
+ )
198
+ puts
199
+ info.delete :pos
200
+ info.delete :total
201
+ end
202
+ if info.count > 0
203
+ puts " " + info.map{|label,value| "#{label}: #{value.to_s.yellow}"}.join(" ")
204
+ end
205
+ end
206
+
207
+ def parse_track_list args
208
+ tracks = Map.new
209
+ args.each do |arg|
210
+ case arg
211
+ when /^\d+$/
212
+ n = arg.to_i
213
+ tracks[n] = @ipod_db[n]
214
+ when /^(\d+)-(\d+)$/
215
+ (Regexp.last_match(1)..Regexp.last_match(2)).each do |n_str|
216
+ n = n_str.to_i
217
+ tracks[n] = @ipod_db[n]
218
+ end
219
+ end
220
+ end
221
+ tracks
222
+ end
223
+
224
+ def riffle paths
225
+ bins = paths.group_by{|path| File.dirname(path)}.values
226
+ Spread.spread *bins
227
+ end
228
+
229
+ }
230
+
231
+ BEGIN {
232
+ require 'main'
233
+ require 'find'
234
+ require 'pathname'
235
+ require 'smart_colored/extend'
236
+ require 'map'
237
+ require 'taglib'
238
+ require 'ruby-progressbar'
239
+ require 'highline/import'
240
+
241
+ # our own
242
+ require 'ipod_db'
243
+ require 'pretty'
244
+ require 'spread'
245
+ }