mediafile 0.1.7 → 0.1.8
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.
- 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
|