ipod_db 0.2.8 → 0.2.9
Sign up to get free protection for your applications and to get access to all the features.
- data/Guardfile +1 -0
- data/README.md +1 -1
- data/bin/ipod +41 -58
- data/ipod_db.gemspec +1 -1
- data/lib/ipod/track.rb +54 -0
- data/lib/ipod_db/version.rb +1 -1
- data/spec/ipod/track_spec.rb +68 -0
- data/spec/spec_helper.rb +16 -1
- metadata +8 -5
data/Guardfile
CHANGED
data/README.md
CHANGED
data/bin/ipod
CHANGED
@@ -2,38 +2,29 @@
|
|
2
2
|
|
3
3
|
Main {
|
4
4
|
version IpodDB::VERSION
|
5
|
+
author 'artm <femistofel@gmail.com>'
|
6
|
+
name 'ipod_db'
|
5
7
|
|
6
8
|
description <<-__
|
7
9
|
A couple of tools for working with iPod Shuffle (2nd gen) database from
|
8
10
|
command line. Each subcommand understands -h/--help flag.
|
9
11
|
__
|
10
12
|
|
11
|
-
author 'artm <femistofel@gmail.com>'
|
12
|
-
|
13
|
-
name 'ipod_db'
|
14
|
-
config(
|
15
|
-
ipod_root: "/media/#{ENV['USER']}/IPOD",
|
16
|
-
group_together: [
|
17
|
-
# Just an example
|
18
|
-
[ "60-Second", "Science Talk", "This Week in Science" ],
|
19
|
-
[ "StarShipSofa", "Tales To Terrify" ],
|
20
|
-
])
|
21
13
|
option('books','b') {
|
22
14
|
argument_required
|
23
15
|
default 'books'
|
24
16
|
description 'subdirectory of ipod with bookmarkable media'
|
25
17
|
}
|
18
|
+
|
26
19
|
option('songs','s') {
|
27
20
|
argument_required
|
28
21
|
default 'songs'
|
29
22
|
description 'subdirectory of ipod with non-bookmarkable media'
|
30
23
|
}
|
24
|
+
|
31
25
|
option('version','v') {
|
32
26
|
description 'show package version and exit'
|
33
27
|
}
|
34
|
-
IgnorePlaybackPosUnder = 10
|
35
|
-
IgnoreProgressUnder = 0.01
|
36
|
-
FinishedProgress = 0.97
|
37
28
|
|
38
29
|
def run
|
39
30
|
if param['version'].given?
|
@@ -96,7 +87,7 @@ Main {
|
|
96
87
|
track numbers to delete from device (ranges like
|
97
88
|
2-5 are accepted too). As a special case, the word
|
98
89
|
'done' means all finished tracks, i.e. with play count
|
99
|
-
above zero or progress above #{ (100*FinishedProgress).floor }%
|
90
|
+
above zero or progress above #{ (100*Ipod::Track::FinishedProgress).floor }%
|
100
91
|
__
|
101
92
|
}
|
102
93
|
option('pretend','n') {
|
@@ -123,7 +114,9 @@ Main {
|
|
123
114
|
end
|
124
115
|
|
125
116
|
def ls
|
126
|
-
@ipod_db.each_track_with_index
|
117
|
+
@ipod_db.each_track_with_index do |track,i|
|
118
|
+
list_track i, Ipod::Track.new(track.merge(ipod_root: @ipod_root))
|
119
|
+
end
|
127
120
|
end
|
128
121
|
|
129
122
|
def rm
|
@@ -133,8 +126,8 @@ Main {
|
|
133
126
|
unless @pretend
|
134
127
|
if agree "Are you sure you want them gone (Y/n)?", true
|
135
128
|
FileUtils.rm tracks.map{|i,path|resolve_ipod_path path}
|
129
|
+
sync
|
136
130
|
end
|
137
|
-
sync
|
138
131
|
end
|
139
132
|
end
|
140
133
|
|
@@ -146,10 +139,6 @@ Main {
|
|
146
139
|
File.join @ipod_root, ipath
|
147
140
|
end
|
148
141
|
|
149
|
-
def track_length( path )
|
150
|
-
TagLib::FileRef.open(path){|file| file.audio_properties.length}
|
151
|
-
end
|
152
|
-
|
153
142
|
def load_ipod_db
|
154
143
|
@ipod_root = config['ipod_root']
|
155
144
|
unless IpodDB.looks_like_ipod? @ipod_root
|
@@ -189,48 +178,35 @@ Main {
|
|
189
178
|
IpodDB::ExtToFileType.include? File.extname(path)
|
190
179
|
end
|
191
180
|
|
192
|
-
def track_info track
|
193
|
-
info = Map.new
|
194
|
-
info['playcount'] = track.playcount if track.fetch('playcount',0) > 0
|
195
|
-
info['skipcount'] = track.skippedcount if track.fetch('skippedcount',0) > 0
|
196
|
-
if track.fetch('bookmarktime',0) > 0
|
197
|
-
abs_path = resolve_ipod_path track.filename
|
198
|
-
total_time = track_length(abs_path)
|
199
|
-
# ipod keeps time in 256 ms increments
|
200
|
-
pos = [total_time, track.bookmarktime * 0.256].min
|
201
|
-
progress = pos / total_time
|
202
|
-
if pos > IgnorePlaybackPosUnder && progress >= IgnoreProgressUnder
|
203
|
-
info['pos'] = Pretty.seconds pos
|
204
|
-
info['total'] = Pretty.seconds total_time
|
205
|
-
info['progress'] = progress
|
206
|
-
end
|
207
|
-
end
|
208
|
-
info
|
209
|
-
end
|
210
|
-
|
211
181
|
def list_track i, track
|
212
|
-
|
213
|
-
exists = File.exists?(abs_path)
|
214
|
-
track_color = exists ? :green : :red
|
215
|
-
|
216
|
-
listing_entry = "%2d: %s" % [i,track.filename.apply_format( color: track_color )]
|
182
|
+
listing_entry = "%2d: %s" % [i,track.filename.apply_format( color: track_color(track))]
|
217
183
|
listing_entry = listing_entry.bold if @ipod_db.playback_state.trackno == i
|
218
184
|
puts listing_entry
|
185
|
+
display_progress track
|
186
|
+
end
|
187
|
+
|
188
|
+
def track_color track
|
189
|
+
if track.exists? then
|
190
|
+
if track.finished?
|
191
|
+
:cyan
|
192
|
+
else
|
193
|
+
:green
|
194
|
+
end
|
195
|
+
else
|
196
|
+
:red
|
197
|
+
end
|
198
|
+
end
|
219
199
|
|
220
|
-
|
200
|
+
def display_progress track
|
201
|
+
if track.exists? and track.pos >= IgnoreProgressUnder and
|
202
|
+
track.progress.between?(IgnoreProgressUnder,Ipod::Track::FinishedProgress)
|
221
203
|
|
222
|
-
info = track_info(track)
|
223
|
-
if info.include? 'progress'
|
224
204
|
progress = ProgressBar.create(
|
225
|
-
format: " [%b #{
|
226
|
-
starting_at: (100*
|
205
|
+
format: " [%b #{Pretty.seconds(track.pos).yellow} %P%%%i] #{Pretty.seconds(track.length).yellow}",
|
206
|
+
starting_at: (100*track.progress),
|
227
207
|
)
|
228
208
|
puts
|
229
209
|
end
|
230
|
-
['pos','total','progress'].each{|f| info.delete(f)}
|
231
|
-
if info.count > 0
|
232
|
-
puts " " + info.map{|label,value| "#{label}: #{value.to_s.yellow}"}.join(" ")
|
233
|
-
end
|
234
210
|
end
|
235
211
|
|
236
212
|
def parse_track_list args
|
@@ -254,10 +230,6 @@ Main {
|
|
254
230
|
tracks
|
255
231
|
end
|
256
232
|
|
257
|
-
def finished? track
|
258
|
-
track.fetch('playcount',0) > 0 || track_info(track).fetch('progress',0) >= FinishedProgress
|
259
|
-
end
|
260
|
-
|
261
233
|
def spread paths, group_lambda = lambda {|path| track_group(path)}
|
262
234
|
bins = paths.group_by &group_lambda
|
263
235
|
bins.each do |key, bin|
|
@@ -274,6 +246,17 @@ Main {
|
|
274
246
|
end
|
275
247
|
File.dirname(track)
|
276
248
|
end
|
249
|
+
|
250
|
+
IgnorePlaybackPosUnder = 10
|
251
|
+
IgnoreProgressUnder = 0.01
|
252
|
+
|
253
|
+
config({
|
254
|
+
ipod_root: "/media/#{ENV['USER']}/IPOD",
|
255
|
+
group_together: [
|
256
|
+
# Just an example
|
257
|
+
[ "60-Second", "Science Talk", "This Week in Science" ],
|
258
|
+
[ "StarShipSofa", "Tales To Terrify" ],
|
259
|
+
]})
|
277
260
|
}
|
278
261
|
|
279
262
|
BEGIN {
|
@@ -282,7 +265,6 @@ BEGIN {
|
|
282
265
|
require 'pathname'
|
283
266
|
require 'smart_colored/extend'
|
284
267
|
require 'map'
|
285
|
-
require 'taglib'
|
286
268
|
require 'ruby-progressbar'
|
287
269
|
require 'highline/import'
|
288
270
|
require 'fileutils'
|
@@ -290,6 +272,7 @@ BEGIN {
|
|
290
272
|
|
291
273
|
# our own
|
292
274
|
require 'ipod_db'
|
275
|
+
require 'ipod/track'
|
293
276
|
require 'pretty'
|
294
277
|
require 'spread'
|
295
278
|
}
|
data/ipod_db.gemspec
CHANGED
@@ -29,7 +29,7 @@ Gem::Specification.new do |s|
|
|
29
29
|
s.add_development_dependency 'rake'
|
30
30
|
s.add_development_dependency 'bundler', '~> 1.3'
|
31
31
|
# not sure where this belongs, they are part of my development process
|
32
|
-
s.add_development_dependency '
|
32
|
+
s.add_development_dependency 'minitest-reporters'
|
33
33
|
s.add_development_dependency 'guard'
|
34
34
|
s.add_development_dependency 'guard-minitest'
|
35
35
|
s.add_development_dependency 'guard-bundler'
|
data/lib/ipod/track.rb
ADDED
@@ -0,0 +1,54 @@
|
|
1
|
+
require 'ostruct'
|
2
|
+
require 'taglib'
|
3
|
+
require 'fileutils'
|
4
|
+
|
5
|
+
module Ipod
|
6
|
+
class Track < OpenStruct
|
7
|
+
FinishedProgress = 0.97
|
8
|
+
SecondsPerTick = 0.256
|
9
|
+
|
10
|
+
def absolute_path
|
11
|
+
File.join ipod_root, filename
|
12
|
+
end
|
13
|
+
|
14
|
+
def exists?
|
15
|
+
@exists ||= File.exists? absolute_path
|
16
|
+
end
|
17
|
+
|
18
|
+
def finished?
|
19
|
+
playcount > 0 or progress > FinishedProgress
|
20
|
+
end
|
21
|
+
|
22
|
+
def progress
|
23
|
+
pos / length
|
24
|
+
end
|
25
|
+
|
26
|
+
def pos
|
27
|
+
Track.ticks_to_sec(bookmarktime)
|
28
|
+
end
|
29
|
+
|
30
|
+
def length
|
31
|
+
#
|
32
|
+
# if file is writable TagLib opens it read-write and appears to write something when the file
|
33
|
+
# gets closed, or in any case closing a file open read-write is slow on slow media.
|
34
|
+
#
|
35
|
+
# if file is readonly TagLib still opens it but closing is much faster.
|
36
|
+
#
|
37
|
+
old_stat = File::Stat.new(absolute_path)
|
38
|
+
begin
|
39
|
+
FileUtils.chmod('a-w', absolute_path)
|
40
|
+
TagLib::FileRef.open(absolute_path){|file| file.audio_properties.length}
|
41
|
+
ensure
|
42
|
+
FileUtils.chmod(old_stat.mode, absolute_path)
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
def self.sec_to_ticks(sec)
|
47
|
+
sec / SecondsPerTick
|
48
|
+
end
|
49
|
+
|
50
|
+
def self.ticks_to_sec(ticks)
|
51
|
+
ticks * SecondsPerTick
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
data/lib/ipod_db/version.rb
CHANGED
@@ -0,0 +1,68 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
require "ipod/track"
|
3
|
+
|
4
|
+
describe Ipod::Track do
|
5
|
+
|
6
|
+
describe "#exists?" do
|
7
|
+
|
8
|
+
it "knows if file exists" do
|
9
|
+
track = Ipod::Track.new filename: "/books/chapter1.mp3", ipod_root: "/mount/ipod"
|
10
|
+
|
11
|
+
File.mock_stub :exists?, true, ["/mount/ipod/books/chapter1.mp3"] do
|
12
|
+
assert track.exists?
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
it "knows if file doesn't exist" do
|
17
|
+
track = Ipod::Track.new filename: "/books/chapter1.mp3", ipod_root: "/mount/ipod"
|
18
|
+
|
19
|
+
File.mock_stub :exists?, false, ["/mount/ipod/books/chapter1.mp3"] do
|
20
|
+
refute track.exists?
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
end
|
25
|
+
|
26
|
+
describe '#finished?' do
|
27
|
+
|
28
|
+
it 'is true if playcount > 0' do
|
29
|
+
track = Ipod::Track.new playcount: 1
|
30
|
+
assert track.finished?
|
31
|
+
end
|
32
|
+
|
33
|
+
it 'is true if progress above threshold' do
|
34
|
+
track = Ipod::Track.new playcount: 0, bookmarktime: Ipod::Track.sec_to_ticks(99)
|
35
|
+
track.stub :length, 100 do
|
36
|
+
assert track.finished?
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
it 'is false if progress below threshold' do
|
41
|
+
track = Ipod::Track.new playcount: 0, bookmarktime: Ipod::Track.sec_to_ticks(5)
|
42
|
+
track.stub :length, 100 do
|
43
|
+
refute track.finished?
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
end
|
48
|
+
|
49
|
+
describe '#pos' do
|
50
|
+
|
51
|
+
it 'represents bookmarktime in seconds' do
|
52
|
+
track = Ipod::Track.new bookmarktime: 1
|
53
|
+
assert_in_delta track.pos, Ipod::Track::SecondsPerTick
|
54
|
+
end
|
55
|
+
|
56
|
+
end
|
57
|
+
|
58
|
+
describe '#progress' do
|
59
|
+
|
60
|
+
it 'represents progress in track' do
|
61
|
+
track = Ipod::Track.new bookmarktime: Ipod::Track.sec_to_ticks(25)
|
62
|
+
track.stub :length, 100 do
|
63
|
+
assert_in_delta track.progress, 0.25
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
end
|
68
|
+
end
|
data/spec/spec_helper.rb
CHANGED
@@ -1,5 +1,9 @@
|
|
1
1
|
require 'minitest/autorun'
|
2
|
-
require
|
2
|
+
require "minitest/reporters"
|
3
|
+
MiniTest::Reporters.use! [
|
4
|
+
MiniTest::Reporters::DefaultReporter.new,
|
5
|
+
MiniTest::Reporters::GuardReporter.new,
|
6
|
+
]
|
3
7
|
$LOAD_PATH.push "File.dirname(__FILE__)/../lib"
|
4
8
|
require 'bindata'
|
5
9
|
|
@@ -51,3 +55,14 @@ module Enumerable
|
|
51
55
|
ds
|
52
56
|
end
|
53
57
|
end
|
58
|
+
|
59
|
+
class Object
|
60
|
+
def mock_stub name, retval, args=[], &block
|
61
|
+
mock = MiniTest::Mock.new
|
62
|
+
mock.expect name, retval, args
|
63
|
+
stub name, proc{|*args| mock.method_missing(name,*args)} do
|
64
|
+
yield
|
65
|
+
end
|
66
|
+
mock.verify
|
67
|
+
end
|
68
|
+
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: ipod_db
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.2.
|
4
|
+
version: 0.2.9
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -9,7 +9,7 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2013-04-
|
12
|
+
date: 2013-04-13 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: bindata
|
@@ -172,7 +172,7 @@ dependencies:
|
|
172
172
|
- !ruby/object:Gem::Version
|
173
173
|
version: '1.3'
|
174
174
|
- !ruby/object:Gem::Dependency
|
175
|
-
name:
|
175
|
+
name: minitest-reporters
|
176
176
|
requirement: !ruby/object:Gem::Requirement
|
177
177
|
none: false
|
178
178
|
requirements:
|
@@ -303,10 +303,12 @@ files:
|
|
303
303
|
- doc/ITunesDB - wikiPodLinux.html
|
304
304
|
- ipod_db.gemspec
|
305
305
|
- lib/bindata/itypes.rb
|
306
|
+
- lib/ipod/track.rb
|
306
307
|
- lib/ipod_db.rb
|
307
308
|
- lib/ipod_db/version.rb
|
308
309
|
- lib/pretty.rb
|
309
310
|
- lib/spread.rb
|
311
|
+
- spec/ipod/track_spec.rb
|
310
312
|
- spec/ipod_db_spec.rb
|
311
313
|
- spec/spec_helper.rb
|
312
314
|
- spec/spread_spec.rb
|
@@ -332,7 +334,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
332
334
|
version: '0'
|
333
335
|
segments:
|
334
336
|
- 0
|
335
|
-
hash:
|
337
|
+
hash: 4257127508463692162
|
336
338
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
337
339
|
none: false
|
338
340
|
requirements:
|
@@ -341,7 +343,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
341
343
|
version: '0'
|
342
344
|
segments:
|
343
345
|
- 0
|
344
|
-
hash:
|
346
|
+
hash: 4257127508463692162
|
345
347
|
requirements: []
|
346
348
|
rubyforge_project:
|
347
349
|
rubygems_version: 1.8.23
|
@@ -349,6 +351,7 @@ signing_key:
|
|
349
351
|
specification_version: 3
|
350
352
|
summary: ipod database access
|
351
353
|
test_files:
|
354
|
+
- spec/ipod/track_spec.rb
|
352
355
|
- spec/ipod_db_spec.rb
|
353
356
|
- spec/spec_helper.rb
|
354
357
|
- spec/spread_spec.rb
|