mediafile 0.1.7 → 0.1.8
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/bin/music_cp +12 -2
- data/lib/mediafile/bulkmediacopy.rb +22 -28
- data/lib/mediafile/mediafile.rb +99 -55
- data/lib/mediafile/version.rb +1 -1
- data/lib/mediafile.rb +38 -1
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: c0ac9d9cb6df74d620341c0410167100c8ab9bb9
|
4
|
+
data.tar.gz: 21b7b5e10a2dbffea4222758f48ce2176e36e117
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: fea4af286c2c6ffe85078a0d8064742baf58229c3c179f177eaed5151f5fe3e8eccdda321b081525f3f0ddb7e9453740c2ca9a5676d3a1e292b5ae4c2d48ee63
|
7
|
+
data.tar.gz: 894d6b89e8b32ebd7e34a7dc336579e7e209fff6381d2ed5b34b5c3acede6b6dbf5baba06600620095990da235de2000113bb10545440f692077d901479a7af0
|
data/bin/music_cp
CHANGED
@@ -19,12 +19,14 @@ opt_files = {
|
|
19
19
|
}
|
20
20
|
dest = "."
|
21
21
|
verbose = false
|
22
|
+
debug = false
|
22
23
|
progress = true
|
23
24
|
count = `grep -c '^processor' /proc/cpuinfo`.strip.to_i/2|1
|
24
25
|
transcode = { flac: :mp3, wav: :mp3 }
|
25
26
|
exclude_patterns = []
|
26
27
|
album_artist = nil
|
27
28
|
file_types = "{flac,mp3,MP3,FLAC,wav,WAV,m4a,M4A}"
|
29
|
+
yes = nil
|
28
30
|
|
29
31
|
opts = OptionParser.new do |opt|
|
30
32
|
|
@@ -58,6 +60,10 @@ opts = OptionParser.new do |opt|
|
|
58
60
|
opt.on("-v", "--[no-]verbose", "Be verbose") do |v|
|
59
61
|
verbose = v
|
60
62
|
end
|
63
|
+
opt.on("--debug", "Show debug output. Also enables verbose.") do
|
64
|
+
debug = true
|
65
|
+
verbose = true
|
66
|
+
end
|
61
67
|
opt.on("-t", "--threads NUM",
|
62
68
|
"Number of threads to spawn, useful for transcoding. Default: #{count}" ) do |n|
|
63
69
|
count = n.to_i
|
@@ -69,6 +75,9 @@ opts = OptionParser.new do |opt|
|
|
69
75
|
puts MediaFile::VERSION
|
70
76
|
exit 0
|
71
77
|
end
|
78
|
+
opt.on("-y", "--yes", "Don't ask before running.") do
|
79
|
+
yes = "yes"
|
80
|
+
end
|
72
81
|
opt.on_tail("-h", "--help", "Show this message") do
|
73
82
|
warn opt
|
74
83
|
exit
|
@@ -132,8 +141,8 @@ files.each { |l| puts "\t"+l }
|
|
132
141
|
puts "#{files.count} files total"
|
133
142
|
puts "The following transcode table will be used:"
|
134
143
|
puts transcode.any? ? transcode : 'none'
|
135
|
-
puts "Do you wish proceed? (Y/n)"
|
136
|
-
y = gets
|
144
|
+
puts "Do you wish to proceed? (Y/n)"
|
145
|
+
y = yes || gets
|
137
146
|
if /n/i.match(y)
|
138
147
|
puts "User cancel."
|
139
148
|
exit
|
@@ -143,6 +152,7 @@ copier = MediaFile::BulkMediaCopy.new(
|
|
143
152
|
files,
|
144
153
|
destination_root: dest,
|
145
154
|
verbose: verbose,
|
155
|
+
debug: debug,
|
146
156
|
transcode: transcode,
|
147
157
|
progress: progress,
|
148
158
|
album_artist: album_artist
|
@@ -1,10 +1,15 @@
|
|
1
|
+
# vim:et sw=2 ts=2
|
2
|
+
|
3
|
+
require 'mediafile'
|
1
4
|
module MediaFile; class BulkMediaCopy
|
5
|
+
include ::MediaFile
|
2
6
|
def initialize(source,
|
3
7
|
album_artist: nil,
|
4
8
|
destination_root: ".",
|
5
9
|
progress: false,
|
6
10
|
transcode: {},
|
7
|
-
verbose: false
|
11
|
+
verbose: false,
|
12
|
+
debug: false)
|
8
13
|
source = case source
|
9
14
|
when String
|
10
15
|
[source]
|
@@ -18,14 +23,8 @@ module MediaFile; class BulkMediaCopy
|
|
18
23
|
@destination_root = destination_root
|
19
24
|
@verbose = verbose
|
20
25
|
@progress = progress
|
21
|
-
@
|
22
|
-
|
23
|
-
base_dir: @destination_root,
|
24
|
-
force_album_artist: album_artist,
|
25
|
-
verbose: @verbose,
|
26
|
-
printer: proc{ |msg| self.safe_print( msg ) }
|
27
|
-
)
|
28
|
-
}
|
26
|
+
@album_artist = album_artist
|
27
|
+
@work = get_work(source)
|
29
28
|
@width = [@work.count.to_s.size, 2].max
|
30
29
|
@name_width = @work.max{ |a,b| a.name.size <=> b.name.size }.name.size
|
31
30
|
@transcode = transcode
|
@@ -59,29 +58,24 @@ module MediaFile; class BulkMediaCopy
|
|
59
58
|
end
|
60
59
|
end
|
61
60
|
|
62
|
-
def safe_print(message='')
|
63
|
-
locked {
|
64
|
-
print block_given? ? yield : message
|
65
|
-
}
|
66
|
-
end
|
67
|
-
|
68
61
|
private
|
69
62
|
|
70
|
-
def
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
63
|
+
def get_work(source)
|
64
|
+
source.map { |s|
|
65
|
+
MediaFile.new(
|
66
|
+
s,
|
67
|
+
base_dir: @destination_root,
|
68
|
+
force_album_artist: @album_artist,
|
69
|
+
verbose: @verbose,
|
70
|
+
debug: @debug,
|
71
|
+
)
|
72
|
+
}
|
78
73
|
end
|
79
74
|
|
80
75
|
def mcopy(max)
|
81
76
|
raise "Argument must repond to :times" unless max.respond_to? :times
|
82
77
|
raise "I haven't any work..." unless @work
|
83
|
-
|
84
|
-
@semaphore = Mutex.new
|
78
|
+
initialize_threads(max)
|
85
79
|
queue = Queue.new
|
86
80
|
@work.each { |s| queue << s }
|
87
81
|
threads = []
|
@@ -93,7 +87,7 @@ module MediaFile; class BulkMediaCopy
|
|
93
87
|
end
|
94
88
|
end
|
95
89
|
threads.each { |t| t.join }
|
96
|
-
|
90
|
+
cleanup
|
97
91
|
end
|
98
92
|
|
99
93
|
def scopy
|
@@ -105,7 +99,7 @@ module MediaFile; class BulkMediaCopy
|
|
105
99
|
|
106
100
|
def copy(mediafile)
|
107
101
|
dest = mediafile.out_path transcode_table: @transcode
|
108
|
-
|
102
|
+
lock {
|
109
103
|
return unless copy_check? mediafile.source_md5, mediafile.source, dest
|
110
104
|
}
|
111
105
|
|
@@ -117,7 +111,7 @@ module MediaFile; class BulkMediaCopy
|
|
117
111
|
err = true
|
118
112
|
end
|
119
113
|
|
120
|
-
|
114
|
+
lock {
|
121
115
|
@count += 1
|
122
116
|
if @progress
|
123
117
|
left = @work.count - @count
|
data/lib/mediafile/mediafile.rb
CHANGED
@@ -1,23 +1,24 @@
|
|
1
|
-
#!/usr/bin/env ruby
|
2
1
|
# vim:et sw=2 ts=2
|
3
2
|
|
3
|
+
require 'mediafile'
|
4
4
|
module MediaFile; class MediaFile
|
5
|
+
include ::MediaFile
|
5
6
|
|
6
7
|
attr_reader :source, :type, :name, :base_dir
|
7
8
|
|
8
9
|
def initialize(path,
|
9
10
|
base_dir: '.',
|
10
11
|
force_album_artist: nil,
|
11
|
-
|
12
|
-
|
12
|
+
verbose: false,
|
13
|
+
debug: false)
|
13
14
|
@base_dir = base_dir
|
14
15
|
@destinations = Hash.new{ |k,v| k[v] = {} }
|
15
16
|
@force_album_artist = force_album_artist
|
16
17
|
@name = File.basename( path, File.extname( path ) )
|
17
|
-
@printer = printer
|
18
18
|
@source = path
|
19
19
|
@type = path[/(\w+)$/].downcase.to_sym
|
20
20
|
@verbose = verbose
|
21
|
+
@debug = debug
|
21
22
|
end
|
22
23
|
|
23
24
|
def source_md5
|
@@ -35,22 +36,30 @@ module MediaFile; class MediaFile
|
|
35
36
|
def copy(dest: @base_dir, transcode_table: {})
|
36
37
|
destination = out_path base_dir: dest, transcode_table: transcode_table
|
37
38
|
temp_dest = tmp_path base_dir: dest, transcode_table: transcode_table
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
raise e
|
39
|
+
lock{
|
40
|
+
if File.exists?(destination)
|
41
|
+
warn "File has already been transfered #{@source} => #{destination}" if @verbose
|
42
|
+
return
|
43
|
+
end
|
44
|
+
if File.exists?(temp_dest)
|
45
|
+
warn "File transfer is already in progress for #{@source} => #{temp_dest} => #{destination}"
|
46
|
+
warn "This shouldn't happen! Check to make sure it was really copied."
|
47
|
+
return
|
48
48
|
end
|
49
|
+
FileUtils.mkdir_p File.dirname destination
|
50
|
+
FileUtils.touch temp_dest
|
51
|
+
}
|
52
|
+
begin
|
53
|
+
transcode_table.has_key?(@type) ?
|
54
|
+
transcode(transcode_table, temp_dest) :
|
55
|
+
really_copy(@source, temp_dest)
|
56
|
+
FileUtils.mv temp_dest, destination
|
57
|
+
rescue => e
|
58
|
+
FileUtils.rm temp_dest if File.exists? temp_dest
|
59
|
+
raise e
|
49
60
|
end
|
50
|
-
|
51
|
-
|
52
|
-
def printit(msg)
|
53
|
-
@printer.call msg
|
61
|
+
ensure
|
62
|
+
FileUtils.rm temp_dest if File.exists? temp_dest
|
54
63
|
end
|
55
64
|
|
56
65
|
def to_s
|
@@ -75,6 +84,7 @@ module MediaFile; class MediaFile
|
|
75
84
|
def really_copy(src,dest)
|
76
85
|
FileUtils.cp(src, dest)
|
77
86
|
set_album_artist(dest)
|
87
|
+
set_comment_and_title(dest)
|
78
88
|
end
|
79
89
|
|
80
90
|
def set_decoder()
|
@@ -131,7 +141,7 @@ module MediaFile; class MediaFile
|
|
131
141
|
def transcode(trans , destination)
|
132
142
|
to = trans[@type]
|
133
143
|
if to == @type
|
134
|
-
|
144
|
+
safe_print "Attempting to transcode to the same format #{@source} from #{@type} to #{to}"
|
135
145
|
end
|
136
146
|
FileUtils.mkdir_p File.dirname destination
|
137
147
|
|
@@ -139,13 +149,13 @@ module MediaFile; class MediaFile
|
|
139
149
|
|
140
150
|
encoder = set_encoder(to, destination)
|
141
151
|
|
142
|
-
|
152
|
+
safe_print "Decoder: '#{decoder.join(' ')}'\nEncoder: '#{encoder.join(' ')}'" if @verbose
|
143
153
|
|
144
154
|
pipes = Hash[[:encoder,:decoder].zip IO.pipe]
|
145
155
|
#readable, writeable = IO.pipe
|
146
156
|
pids = {
|
147
|
-
spawn(*decoder, :out=>pipes[:decoder], :err=>"/
|
148
|
-
spawn(*encoder, :in =>pipes[:encoder], :err=>"/
|
157
|
+
spawn(*decoder, :out=>pipes[:decoder], :err=>"/tmp/decoder.err") => :decoder,
|
158
|
+
spawn(*encoder, :in =>pipes[:encoder], :err=>"/tmp/encoder.err") => :encoder,
|
149
159
|
}
|
150
160
|
tpids = pids.keys
|
151
161
|
err = []
|
@@ -169,7 +179,7 @@ module MediaFile; class MediaFile
|
|
169
179
|
end
|
170
180
|
}
|
171
181
|
rescue Timeout::Error
|
172
|
-
|
182
|
+
safe_print "Timeout exceeded!\n" << tpids.map { |p|
|
173
183
|
Process.kill 15, p
|
174
184
|
Process.kill 9, p
|
175
185
|
"#{p} #{Process.wait2( p )[1]}"
|
@@ -178,7 +188,7 @@ module MediaFile; class MediaFile
|
|
178
188
|
raise
|
179
189
|
end
|
180
190
|
if err.any?
|
181
|
-
|
191
|
+
safe_print "###\nError transcoding #{@source}: #{err.map{ |it,stat|
|
182
192
|
"#{it} EOT:#{stat.exitstatus} #{stat}" }.join(" and ") }\n###\n"
|
183
193
|
exit 1
|
184
194
|
end
|
@@ -191,14 +201,10 @@ module MediaFile; class MediaFile
|
|
191
201
|
@relpath ||= (
|
192
202
|
read_tags
|
193
203
|
dest = File.join(
|
194
|
-
[
|
195
|
-
word
|
204
|
+
[@album_artist, @album].map { |word|
|
205
|
+
clean_string(word)
|
196
206
|
}
|
197
207
|
)
|
198
|
-
bool=true
|
199
|
-
dest.gsub(/\s/,"_").gsub(/[,:)\]\[('"@$^*<>?!]/,"").gsub(/_[&]_/,"_and_").split('').map{ |c|
|
200
|
-
b = bool; bool = c.match('/|_'); b ? c.capitalize : c
|
201
|
-
}.join('').gsub(/__+/,'_')
|
202
208
|
)
|
203
209
|
end
|
204
210
|
|
@@ -207,33 +213,45 @@ module MediaFile; class MediaFile
|
|
207
213
|
@newname ||= (
|
208
214
|
read_tags
|
209
215
|
bool = true
|
210
|
-
file = (
|
216
|
+
file = clean_string(
|
211
217
|
case
|
212
218
|
when (@disc_number && (@track > 0) && @title) && !(@disc_total && @disc_total == 1)
|
213
219
|
"%1d_%02d-" % [@disc_number, @track] + @title
|
214
220
|
when (@track > 0 && @title)
|
215
221
|
"%02d-" % @track + @title
|
216
|
-
when @title
|
222
|
+
when @title && @title != ""
|
217
223
|
@title
|
218
224
|
else
|
219
225
|
@name
|
220
226
|
end
|
221
|
-
)
|
222
|
-
/^\.+|\.+$/,""
|
223
|
-
).gsub(
|
224
|
-
/\//,"_"
|
225
|
-
).gsub(
|
226
|
-
/\s/,"_"
|
227
|
-
).gsub(
|
228
|
-
/[,:)\]\[('"@$^*<>?!=]/,""
|
229
|
-
).gsub(
|
230
|
-
/_[&]_/,"_and_"
|
231
|
-
).split('').map{ |c|
|
232
|
-
b = bool; bool = c.match('/|_'); b ? c.capitalize : c
|
233
|
-
}.join('').gsub(/__+/,'_')
|
227
|
+
)
|
234
228
|
)
|
235
229
|
end
|
236
230
|
|
231
|
+
def clean_string(my_string)
|
232
|
+
my_string ||= ""
|
233
|
+
t = my_string.gsub(
|
234
|
+
/^\.+|\.+$/,""
|
235
|
+
).gsub(
|
236
|
+
/\//,"_"
|
237
|
+
).gsub(
|
238
|
+
/\s/,"_"
|
239
|
+
).gsub(
|
240
|
+
/[,:;)\]\[('"@$^*<>?!=]/,""
|
241
|
+
).gsub(
|
242
|
+
/^[.]/,''
|
243
|
+
).gsub(
|
244
|
+
/_?[&]_?/,"_and_"
|
245
|
+
).split('_').map{ |c|
|
246
|
+
puts "DEBUG: capitalize: '#{c}'" if @debug
|
247
|
+
"_and_" == c ? c : c.capitalize
|
248
|
+
}.join('_').gsub(
|
249
|
+
/__+/,'_'
|
250
|
+
).gsub(/^[.]/, '')
|
251
|
+
puts "DEBUG: clean_string: '#{my_string} => '#{t}'" if @debug
|
252
|
+
t == "" ? "UNKNOWN" : t
|
253
|
+
end
|
254
|
+
|
237
255
|
def tmp_file_name
|
238
256
|
"." + new_file_name
|
239
257
|
end
|
@@ -271,27 +289,51 @@ module MediaFile; class MediaFile
|
|
271
289
|
tag.add_frame(frame)
|
272
290
|
f.save
|
273
291
|
else
|
274
|
-
|
292
|
+
safe_print("##########\nNo tag returned for #{@name}: #{@source}\n#############\n\n")
|
275
293
|
end
|
276
294
|
end
|
277
295
|
end
|
278
296
|
end
|
279
297
|
|
298
|
+
def set_comment_and_title(file)
|
299
|
+
klass = (@type == :mp3) ? TagLib::MPEG::File : TagLib::FileRef
|
300
|
+
method = (@type == :mp3) ? :id3v2_tag : :tag
|
301
|
+
|
302
|
+
klass.send(:open, file) do |f|
|
303
|
+
tag = if (@type == :mp3)
|
304
|
+
f.send(method, true)
|
305
|
+
else
|
306
|
+
f.send(method)
|
307
|
+
end
|
308
|
+
tag.comment = "#{@comment}"
|
309
|
+
tag.title = (@title || @name.gsub('_',' ')) unless tag.title && tag.title != ""
|
310
|
+
if (@type == :mp3)
|
311
|
+
f.save(TagLib::MPEG::File::ID3v2)
|
312
|
+
else
|
313
|
+
f.save
|
314
|
+
end
|
315
|
+
end
|
316
|
+
end
|
317
|
+
|
280
318
|
def read_tags
|
281
319
|
return if @red
|
282
|
-
@album =
|
320
|
+
@album = nil
|
321
|
+
@artist= nil
|
322
|
+
@title = nil
|
323
|
+
@genre = nil
|
324
|
+
@year = nil
|
283
325
|
@track = 0
|
284
|
-
@comment = ""
|
326
|
+
@comment = "MediaFile from source: #{@source}\n"
|
285
327
|
TagLib::FileRef.open(@source) do |file|
|
286
328
|
unless file.null?
|
287
329
|
tag = file.tag
|
288
|
-
@album = tag.album if tag.album
|
289
|
-
@artist = tag.artist if tag.artist
|
290
|
-
@title = tag.title if tag.title
|
291
|
-
@genre = tag.genre if tag.genre
|
292
|
-
@comment
|
293
|
-
@track = tag.track if tag.track
|
294
|
-
@year = tag.year if tag.year
|
330
|
+
@album = tag.album if tag.album && tag.album != ""
|
331
|
+
@artist = tag.artist if tag.artist && tag.artist != ""
|
332
|
+
@title = tag.title if tag.title && tag.title != ""
|
333
|
+
@genre = tag.genre if tag.genre && tag.genre != ""
|
334
|
+
@comment+= tag.comment if tag.comment && tag.comment != ""
|
335
|
+
@track = tag.track if tag.track && tag.track != ""
|
336
|
+
@year = tag.year if tag.year && tag.year != ""
|
295
337
|
end
|
296
338
|
end
|
297
339
|
@album_artist = @artist
|
@@ -335,6 +377,8 @@ module MediaFile; class MediaFile
|
|
335
377
|
else
|
336
378
|
@album_artist ||= @artist
|
337
379
|
end
|
380
|
+
puts "DEBUG: album:'#{@album}', artist:'#{@artist}'" +
|
381
|
+
" @title:'#{@title}' @genre:'#{@genre}' @year:'#{@year}'" if @debug
|
338
382
|
@red = true
|
339
383
|
end
|
340
384
|
end; end
|
data/lib/mediafile/version.rb
CHANGED
data/lib/mediafile.rb
CHANGED
@@ -25,4 +25,41 @@ module MediaFile
|
|
25
25
|
"Transcoding may not work in all cases."
|
26
26
|
end
|
27
27
|
|
28
|
-
|
28
|
+
private
|
29
|
+
|
30
|
+
@@thread_count = 1
|
31
|
+
@@semaphore = nil
|
32
|
+
@@initialized = false
|
33
|
+
|
34
|
+
def initialize_threads(count = 1)
|
35
|
+
return if @@initialized
|
36
|
+
@@initialized = 1
|
37
|
+
@@thread_count = count
|
38
|
+
if @@thread_count > 1
|
39
|
+
require 'thread'
|
40
|
+
@@semaphore = Mutex.new
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
def safe_print(message = '')
|
45
|
+
lock {
|
46
|
+
print block_given? ? yield : message
|
47
|
+
}
|
48
|
+
end
|
49
|
+
|
50
|
+
def cleanup
|
51
|
+
@@semaphore = nil
|
52
|
+
true
|
53
|
+
end
|
54
|
+
|
55
|
+
def lock
|
56
|
+
if @@semaphore
|
57
|
+
@@semaphore.synchronize {
|
58
|
+
yield
|
59
|
+
}
|
60
|
+
else
|
61
|
+
yield
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: mediafile
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.1.
|
4
|
+
version: 0.1.8
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Jeff Harvey-Smith
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2017-01-02 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: taglib-ruby
|