ipod_db 0.2.8 → 0.2.9
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/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
|