mediafile 0.0.3
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/bin/music_cp +112 -0
- data/lib/mediafile/bulkmediacopy.rb +148 -0
- data/lib/mediafile/mediafile.rb +284 -0
- data/lib/mediafile/version.rb +3 -0
- data/lib/mediafile.rb +15 -0
- metadata +72 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 119b0a54c4c60f5422faa73912fefbcfd97390ec
|
4
|
+
data.tar.gz: b7d0d780c5a49fe57b7adbd9125723316d8bfa3b
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 9995f98f87546a394e5d26494442d1ecd3109a297d8e7596f36494ff41ff3b7eff4484fc23a17984a7ebdb42d65989271d4b5c6d702411632234b407ee72902d
|
7
|
+
data.tar.gz: 99e25a1163b071e326826a490b26e145b68fd67522b40ba591d2b8b1ff921fd37dc0fa856a3ad7d9d1d6cb51cc18a98d989f9fa85897924bc4c25cbc6c25c628
|
data/bin/music_cp
ADDED
@@ -0,0 +1,112 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# vim:et sw=2 ts=2
|
3
|
+
#
|
4
|
+
|
5
|
+
require 'fileutils'
|
6
|
+
require 'optparse'
|
7
|
+
require 'digest/md5'
|
8
|
+
require 'timeout'
|
9
|
+
require 'mediafile'
|
10
|
+
|
11
|
+
PROGNAME = File.basename($0)
|
12
|
+
|
13
|
+
def die(msg)
|
14
|
+
abort "#{PROGNAME}: #{msg}"
|
15
|
+
end
|
16
|
+
|
17
|
+
kill = me = now = false
|
18
|
+
files = []
|
19
|
+
dest = "."
|
20
|
+
verbose = false
|
21
|
+
progress = true
|
22
|
+
count = `grep -c '^processor' /proc/cpuinfo`.strip.to_i/2|1
|
23
|
+
transcode = { flac: :mp3, wav: :mp3 }
|
24
|
+
exclude_patterns = []
|
25
|
+
file_types = "{flac,mp3,MP3,FLAC,wav,WAV,m4a,M4A}"
|
26
|
+
opts = OptionParser.new do |opt|
|
27
|
+
|
28
|
+
opt.on("-f", "--file FILE|DIR", "File or directory to copy.",
|
29
|
+
"If given a directory, will grab all files within non-recursively") do |e|
|
30
|
+
e.split(",").each do |f|
|
31
|
+
if File.file? f
|
32
|
+
files << f
|
33
|
+
elsif File.directory? f
|
34
|
+
files.concat Dir.glob( f + "/*.#{file_types}")
|
35
|
+
else
|
36
|
+
warn "#{f} is not a file or a directory!"
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
opt.on("-r", "--recursive DIR", "Directory to recursively scan and copy") do |r|
|
41
|
+
raise "Directory '#{r}' does not exist" unless File.directory? r
|
42
|
+
files.concat Dir.glob( r + "/**/*.#{file_types}")
|
43
|
+
end
|
44
|
+
opt.on("-d", "--destination PATH", "Where to copy file to. Default: '#{dest}'",
|
45
|
+
"Will be created if it doesn't exist.") do |d|
|
46
|
+
dest = d
|
47
|
+
end
|
48
|
+
opt.on("--transcode <from=to[,from1=to1]>", "A comma-seperated series of name=value pairs.",
|
49
|
+
"Default is #{transcode.to_a.map{|i| i.join("=")}.join(",")}") do |fmt|
|
50
|
+
kill = true
|
51
|
+
transcode = Hash[ *fmt.split(",").map{|e| e.split("=").map{ |t| t.downcase.to_sym } }.flatten ]
|
52
|
+
end
|
53
|
+
opt.on("-c", "--copy", "Turn off transcoding.") do
|
54
|
+
transcode = {}
|
55
|
+
me = true
|
56
|
+
end
|
57
|
+
opt.on("--[no-]progress", "Set show progress true/false. Default is #{progress}") do |t|
|
58
|
+
progress = t
|
59
|
+
end
|
60
|
+
opt.on("--exclude PATTERN", "-x PATTERN", String, "Exclude files that match the given pattern.",
|
61
|
+
"Can specify more than once, file is excluded if any pattern matches") do |p|
|
62
|
+
exclude_patterns << p
|
63
|
+
end
|
64
|
+
opt.on("-v", "--[no-]verbose", "Be verbose") do |v|
|
65
|
+
verbose = v
|
66
|
+
end
|
67
|
+
opt.on("-t", "--threads NUM",
|
68
|
+
"Number of threads to spawn, useful for transcoding. Default: #{count}" ) do |n|
|
69
|
+
count = n.to_i
|
70
|
+
end
|
71
|
+
opt.on_tail("-h", "--help", "Show this message") do
|
72
|
+
warn opt
|
73
|
+
exit
|
74
|
+
end
|
75
|
+
begin
|
76
|
+
opt.parse!
|
77
|
+
if kill and me
|
78
|
+
raise OptionParser::InvalidOption.new("--copy and --transcode are conflicting")
|
79
|
+
end
|
80
|
+
rescue OptionParser::InvalidOption
|
81
|
+
warn "#{PROGNAME}: #{$!}"
|
82
|
+
die opt
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
files = files.uniq.sort
|
87
|
+
if exclude_patterns.any?
|
88
|
+
pattern = Regexp.new(exclude_patterns.join("|"))
|
89
|
+
files.delete_if { |el| pattern.match el }
|
90
|
+
end
|
91
|
+
if files.empty?
|
92
|
+
warn "No file specified, exiting"
|
93
|
+
warn opts
|
94
|
+
exit
|
95
|
+
end
|
96
|
+
puts "Full list of files to transfer to #{dest}:"
|
97
|
+
files.each { |l| puts "\t"+l }
|
98
|
+
puts "#{files.count} files total"
|
99
|
+
puts "The following transcode table will be used:"
|
100
|
+
puts transcode.any? ? transcode : 'none'
|
101
|
+
puts "Do you wish procede? (Y/n)"
|
102
|
+
y = gets
|
103
|
+
if /n/i.match(y)
|
104
|
+
puts "User cancel."
|
105
|
+
exit
|
106
|
+
end
|
107
|
+
puts "Begin copy to #{dest}"
|
108
|
+
copier = MediaFile::BulkMediaCopy.new(files, destination_root: dest, verbose: verbose, transcode: transcode, progress: progress)
|
109
|
+
|
110
|
+
copier.run count
|
111
|
+
|
112
|
+
puts "Complete."
|
@@ -0,0 +1,148 @@
|
|
1
|
+
module MediaFile; class BulkMediaCopy
|
2
|
+
def initialize( source, destination_root: ".", verbose: false, progress: false, transcode: {} )
|
3
|
+
source = case source
|
4
|
+
when String
|
5
|
+
[source]
|
6
|
+
when Array
|
7
|
+
source
|
8
|
+
else
|
9
|
+
raise "Bad value for required first arg 'source': '#{source.class}'. Should be String or Array."
|
10
|
+
end
|
11
|
+
|
12
|
+
@copies = Hash.new { |h,k| h[k] = [] }
|
13
|
+
@destination_root = destination_root
|
14
|
+
@verbose = verbose
|
15
|
+
@progress = progress
|
16
|
+
@work = source.map { |s|
|
17
|
+
MediaFile.new(s,
|
18
|
+
base_dir: @destination_root,
|
19
|
+
verbose: @verbose,
|
20
|
+
|
21
|
+
printer: proc{ |msg| self.safe_print( msg ) }
|
22
|
+
)
|
23
|
+
}
|
24
|
+
@width = [@work.count.to_s.size, 2].max
|
25
|
+
@name_width = @work.max{ |a,b| a.name.size <=> b.name.size }.name.size
|
26
|
+
@transcode = transcode
|
27
|
+
@count = 0
|
28
|
+
@failed = []
|
29
|
+
end
|
30
|
+
|
31
|
+
def run(max=4)
|
32
|
+
puts "%#{@width + 8}s, %#{@width + 8}s,%#{@width + 8}s, %-#{@name_width}s => Destination Path" % [
|
33
|
+
"Remaining",
|
34
|
+
"Workers",
|
35
|
+
"Complete",
|
36
|
+
"File Name"
|
37
|
+
]
|
38
|
+
puts "%#{@width}d ( 100%%), %#{@width}d (%4.1f%%), %#{@width}d ( 0.0%%)" % [
|
39
|
+
@work.count,
|
40
|
+
0,
|
41
|
+
0,
|
42
|
+
0
|
43
|
+
]
|
44
|
+
max > 1 ? mcopy(max) : scopy
|
45
|
+
dupes = @copies.select{ |k,a| a.size > 1 }
|
46
|
+
if dupes.any?
|
47
|
+
puts "dupes"
|
48
|
+
require 'pp'
|
49
|
+
pp dupes
|
50
|
+
end
|
51
|
+
if @failed.any?
|
52
|
+
puts "Some files timed out"
|
53
|
+
@failed.each { |f| puts f.to_s }
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
def safe_print(message='')
|
58
|
+
locked {
|
59
|
+
puts block_given? ? yield : message
|
60
|
+
}
|
61
|
+
end
|
62
|
+
|
63
|
+
private
|
64
|
+
|
65
|
+
def locked
|
66
|
+
if @semaphore
|
67
|
+
@semaphore.synchronize {
|
68
|
+
yield
|
69
|
+
}
|
70
|
+
else
|
71
|
+
yield
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
def mcopy(max)
|
76
|
+
raise "Argument must repond to :times" unless max.respond_to? :times
|
77
|
+
raise "I haven't any work..." unless @work
|
78
|
+
require 'thread'
|
79
|
+
@semaphore = Mutex.new
|
80
|
+
queue = Queue.new
|
81
|
+
@work.each { |s| queue << s }
|
82
|
+
threads = []
|
83
|
+
max.times do
|
84
|
+
threads << Thread.new do
|
85
|
+
while ( s = queue.pop(true) rescue nil)
|
86
|
+
copy s
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|
90
|
+
threads.each { |t| t.join }
|
91
|
+
@semaphore = nil
|
92
|
+
end
|
93
|
+
|
94
|
+
def scopy
|
95
|
+
raise "I haven't any work..." unless @work
|
96
|
+
@work.each do |f|
|
97
|
+
copy f
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
def copy(mediafile)
|
102
|
+
dest = mediafile.out_path transcode_table: @transcode
|
103
|
+
locked {
|
104
|
+
return unless copy_check? mediafile.source_md5, mediafile.source, dest
|
105
|
+
}
|
106
|
+
|
107
|
+
err = false
|
108
|
+
begin
|
109
|
+
mediafile.copy transcode_table: @transcode
|
110
|
+
rescue Timeout::Error
|
111
|
+
@failed << mediafile
|
112
|
+
err = true
|
113
|
+
end
|
114
|
+
|
115
|
+
locked {
|
116
|
+
@count += 1
|
117
|
+
if @progress
|
118
|
+
left = @work.count - @count
|
119
|
+
left_perc = left == 0 ? left : left.to_f / @work.count * 100
|
120
|
+
cur = @copies.count - @count
|
121
|
+
cur_perc = cur == 0 ? cur : cur.to_f / left * 100 # @work.count * 100
|
122
|
+
c = cur_perc == 100
|
123
|
+
finished = @count.to_f / @work.count * 100
|
124
|
+
f = finished == 100.0
|
125
|
+
puts "%#{@width}d (%4.1f%%), %#{@width}d (%4.#{c ? 0 : 1}f%%), %#{@width}d (%4.#{f ? 0 : 1}f%%) %-#{@name_width}s => %-s" % [
|
126
|
+
left,
|
127
|
+
left_perc,
|
128
|
+
cur,
|
129
|
+
cur_perc,
|
130
|
+
@count,
|
131
|
+
finished,
|
132
|
+
(mediafile.name + (err ? " **" : "") ),
|
133
|
+
mediafile.out_path(transcode_table:@transcode)
|
134
|
+
]
|
135
|
+
end
|
136
|
+
}
|
137
|
+
|
138
|
+
end
|
139
|
+
|
140
|
+
def copy_check?(md5,name,dest)
|
141
|
+
# if multi-threaded, need to lock before calling
|
142
|
+
@copies[md5] << "#{name} => #{dest}"
|
143
|
+
# return true if this is the only one
|
144
|
+
@copies[md5].count == 1
|
145
|
+
end
|
146
|
+
|
147
|
+
end; end
|
148
|
+
|
@@ -0,0 +1,284 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# vim:et sw=2 ts=2
|
3
|
+
|
4
|
+
module MediaFile; class MediaFile
|
5
|
+
|
6
|
+
attr_reader :source, :type, :name, :base_dir
|
7
|
+
|
8
|
+
def initialize(path, verbose: false, base_dir: '.', printer: proc {|msg| puts msg})
|
9
|
+
@source = path
|
10
|
+
@base_dir = base_dir
|
11
|
+
@verbose = verbose
|
12
|
+
@name = File.basename( @source, File.extname( @source ) )
|
13
|
+
@type = path[/(\w+)$/].downcase.to_sym
|
14
|
+
@printer = printer
|
15
|
+
@destinations = Hash.new{ |k,v| k[v] = {} }
|
16
|
+
end
|
17
|
+
|
18
|
+
def source_md5
|
19
|
+
@source_md5 ||= Digest::MD5.hexdigest( @source )
|
20
|
+
end
|
21
|
+
|
22
|
+
def out_path(base_dir: @base_dir, transcode_table: {})
|
23
|
+
@destinations[base_dir][transcode_table] ||= File.join(
|
24
|
+
base_dir,
|
25
|
+
relative_path,
|
26
|
+
new_file_name,
|
27
|
+
) << ".#{transcode_table[@type] || @type}"
|
28
|
+
end
|
29
|
+
|
30
|
+
def copy(dest: @base_dir, transcode_table: {})
|
31
|
+
destination = out_path base_dir: dest, transcode_table: transcode_table
|
32
|
+
temp_dest = tmp_path base_dir: dest, transcode_table: transcode_table
|
33
|
+
unless File.exists? destination
|
34
|
+
FileUtils.mkdir_p File.dirname destination
|
35
|
+
begin
|
36
|
+
if transcode_table.has_key? @type
|
37
|
+
transcode transcode_table, temp_dest
|
38
|
+
else
|
39
|
+
FileUtils.cp @source, temp_dest
|
40
|
+
end
|
41
|
+
FileUtils.mv temp_dest, destination
|
42
|
+
rescue => e
|
43
|
+
FileUtils.rm temp_dest if File.exists? temp_dest
|
44
|
+
raise e
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
def printit(msg)
|
50
|
+
@printer.call msg
|
51
|
+
end
|
52
|
+
|
53
|
+
def to_s
|
54
|
+
"#{@source}"
|
55
|
+
end
|
56
|
+
|
57
|
+
def self.tags(*args)
|
58
|
+
args.each do |arg|
|
59
|
+
define_method arg do
|
60
|
+
read_tags
|
61
|
+
instance_variable_get "@#{arg}"
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
tags :album, :artist, :album_artist, :title, :genre, :year, :track, :comment, :disc_number, :disc_total
|
67
|
+
|
68
|
+
private
|
69
|
+
|
70
|
+
def set_decoder()
|
71
|
+
case @type
|
72
|
+
when :flac
|
73
|
+
%W{flac -c -s -d #{@source}}
|
74
|
+
when :mp3
|
75
|
+
#%W{lame --decode #{@source} -}
|
76
|
+
%W{sox #{@source} -t wav -}
|
77
|
+
when :m4a
|
78
|
+
%W{ffmpeg -i #{@source} -f wav -}
|
79
|
+
when :wav
|
80
|
+
%W{cat #{@source}}
|
81
|
+
else
|
82
|
+
raise "Unknown type '#{@type}'. Cannot set decoder"
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
def set_encoder(to,destination)
|
87
|
+
comment = "; Transcoded by MediaFile on #{Time.now}"
|
88
|
+
case to
|
89
|
+
when :flac
|
90
|
+
raise "Please don't transcode to flac. It is broken right now"
|
91
|
+
%W{flac -7 -V -s -o #{destination}} +
|
92
|
+
(@artist ? ["-T", "artist=#{@artist}"] : [] ) +
|
93
|
+
(@title ? ["-T", "title=#{@title}"] : [] ) +
|
94
|
+
(@album ? ["-T", "album=#{@album}"] : [] ) +
|
95
|
+
(@track > 0 ? ["-T", "tracknumber=#{@track}"] : [] ) +
|
96
|
+
(@year ? ["-T", "date=#{@year}"] : [] ) +
|
97
|
+
(@genre ? ["-T", "genre=#{@genre}"] : [] ) +
|
98
|
+
["-T", "comment=" + @comment + comment ] +
|
99
|
+
(@album_artist ? ["-T", "albumartist=#{@album_artist}"] : [] ) +
|
100
|
+
(@disc_number ? ["-T", "discnumber=#{@disc_number}"] : [] ) +
|
101
|
+
["-"]
|
102
|
+
when :mp3
|
103
|
+
%W{/usr/bin/lame --quiet --preset extreme -h --add-id3v2 --id3v2-only} +
|
104
|
+
(@title ? ["--tt", @title] : [] ) +
|
105
|
+
(@artist ? ["--ta", @artist]: [] ) +
|
106
|
+
(@album ? ["--tl", @album] : [] ) +
|
107
|
+
(@track > 0 ? ["--tn", @track.to_s]: [] ) +
|
108
|
+
(@year ? ["--ty", @year.to_s ] : [] ) +
|
109
|
+
(@genre ? ["--tg", @genre ]: [] ) +
|
110
|
+
["--tc", @comment + comment ] +
|
111
|
+
(@album_artist ? ["--tv", "TPE2=#{@album_artist}"] : [] ) +
|
112
|
+
(@disc_number ? ["--tv", "TPOS=#{@disc_number}"] : [] ) +
|
113
|
+
["-", destination]
|
114
|
+
when :wav
|
115
|
+
%W{dd of=#{destination}}
|
116
|
+
else
|
117
|
+
raise "Unknown target '#{to}'. Cannot set encoder"
|
118
|
+
end
|
119
|
+
end
|
120
|
+
|
121
|
+
def transcode(trans , destination)
|
122
|
+
to = trans[@type]
|
123
|
+
printit "Attempting to transcode to the same format #{@source} from #{@type} to #{to}" if to == @type
|
124
|
+
FileUtils.mkdir_p File.dirname destination
|
125
|
+
|
126
|
+
decoder = set_decoder
|
127
|
+
|
128
|
+
encoder = set_encoder(to, destination)
|
129
|
+
|
130
|
+
printit "Decoder: '#{decoder.join(' ')}'\nEncoder: '#{encoder.join(' ')}'" if @verbose
|
131
|
+
|
132
|
+
pipes = Hash[[:encoder,:decoder].zip IO.pipe]
|
133
|
+
#readable, writeable = IO.pipe
|
134
|
+
pids = {
|
135
|
+
spawn(*decoder, :out=>pipes[:decoder], :err=>"/dev/null") => :decoder,
|
136
|
+
spawn(*encoder, :in =>pipes[:encoder], :err=>"/dev/null") => :encoder,
|
137
|
+
}
|
138
|
+
tpids = pids.keys
|
139
|
+
err = []
|
140
|
+
begin
|
141
|
+
Timeout::timeout(60 * ( File.size(@source) / 1024 / 1024 /2 ) ) {
|
142
|
+
#Timeout::timeout(3 ) {
|
143
|
+
while tpids.any? do
|
144
|
+
sleep 0.2
|
145
|
+
tpids.delete_if do |pid|
|
146
|
+
ret = false
|
147
|
+
p, stat = Process.wait2 pid, Process::WNOHANG
|
148
|
+
if stat
|
149
|
+
pipes[pids[pid]].close unless pipes[pids[pid]].closed?
|
150
|
+
ret = true
|
151
|
+
end
|
152
|
+
if stat and stat.exitstatus and stat.exitstatus != 0
|
153
|
+
err << [ pids[pid], stat ]
|
154
|
+
end
|
155
|
+
ret
|
156
|
+
end
|
157
|
+
end
|
158
|
+
}
|
159
|
+
rescue Timeout::Error
|
160
|
+
printit "Timeout exceeded!\n" << tpids.map { |p|
|
161
|
+
Process.kill 15, p
|
162
|
+
Process.kill 9, p
|
163
|
+
"#{p} #{Process.wait2( p )[1]}"
|
164
|
+
}.join(", ")
|
165
|
+
FileUtils.rm [destination]
|
166
|
+
raise
|
167
|
+
end
|
168
|
+
if err.any?
|
169
|
+
printit "Error with #{err.map{|it,stat| "#{it} EOT:#{stat.exitstatus} #{stat}" }.join(" and ")}"
|
170
|
+
#raise "Error #{@source} #{err}"
|
171
|
+
end
|
172
|
+
end
|
173
|
+
|
174
|
+
# directory names cannot end with a '.'
|
175
|
+
# it breaks windows (really!)
|
176
|
+
|
177
|
+
def relative_path
|
178
|
+
return @relpath if @relpath
|
179
|
+
read_tags
|
180
|
+
dest = File.join(
|
181
|
+
[(@album_artist||"UNKNOWN"), (@album||"UNKNOWN")].map { |word|
|
182
|
+
word.gsub(/^\.+|\.+$/,"").gsub(/\//,"_")
|
183
|
+
}
|
184
|
+
)
|
185
|
+
bool=true
|
186
|
+
@relpath = dest.gsub(/\s/,"_").gsub(/[,:)\]\[('"@$^*<>?!]/,"").gsub(/_[&]_/,"_and_").split('').map{ |c|
|
187
|
+
b = bool; bool = c.match('/|_'); b ? c.capitalize : c
|
188
|
+
}.join('').gsub(/__+/,'_')
|
189
|
+
end
|
190
|
+
|
191
|
+
def new_file_name
|
192
|
+
# this doesn't include the extension.
|
193
|
+
@newname ||= (
|
194
|
+
read_tags
|
195
|
+
bool=true
|
196
|
+
file= ( case
|
197
|
+
when (@disc_number && (@track > 0) && @title) && !(@disc_total && @disc_total == 1)
|
198
|
+
"%1d_%02d-" % [@disc_number, @track] + @title
|
199
|
+
when (@track > 0 && @title)
|
200
|
+
"%02d-" % @track + @title
|
201
|
+
when @title
|
202
|
+
@title
|
203
|
+
else
|
204
|
+
@name
|
205
|
+
end).gsub(
|
206
|
+
/^\.+|\.+$/,""
|
207
|
+
).gsub(
|
208
|
+
/\//,"_"
|
209
|
+
).gsub(
|
210
|
+
/\s/,"_"
|
211
|
+
).gsub(
|
212
|
+
/[,:)\]\[('"@$^*<>?!]/,""
|
213
|
+
).gsub(
|
214
|
+
/_[&]_/,"_and_"
|
215
|
+
).split('').map{ |c|
|
216
|
+
b = bool; bool = c.match('/|_'); b ? c.capitalize : c
|
217
|
+
}.join('')
|
218
|
+
)
|
219
|
+
end
|
220
|
+
|
221
|
+
def tmp_file_name
|
222
|
+
"." + new_file_name
|
223
|
+
end
|
224
|
+
|
225
|
+
def tmp_path(base_dir: @base_dir, transcode_table: {})
|
226
|
+
File.join(
|
227
|
+
base_dir,
|
228
|
+
relative_path,
|
229
|
+
tmp_file_name,
|
230
|
+
) << ".#{transcode_table[@type] || @type}"
|
231
|
+
end
|
232
|
+
|
233
|
+
def read_tags
|
234
|
+
return if @red
|
235
|
+
@album = @artist= @title = @genre = @year = nil
|
236
|
+
@track = 0
|
237
|
+
@comment = ""
|
238
|
+
TagLib::FileRef.open(@source) do |file|
|
239
|
+
unless file.null?
|
240
|
+
tag = file.tag
|
241
|
+
@album = tag.album if tag.album
|
242
|
+
@artist = tag.artist if tag.artist
|
243
|
+
@title = tag.title if tag.title
|
244
|
+
@genre = tag.genre if tag.genre
|
245
|
+
@comment= tag.comment if tag.comment
|
246
|
+
@track = tag.track if tag.track
|
247
|
+
@year = tag.year if tag.year
|
248
|
+
end
|
249
|
+
end
|
250
|
+
@album_artist = @artist
|
251
|
+
case @type
|
252
|
+
when :m4a
|
253
|
+
TagLib::MP4::File.open(@source) do |file|
|
254
|
+
@disc_number = file.tag.item_list_map["disk"].to_int_pair[0]
|
255
|
+
end
|
256
|
+
when :flac
|
257
|
+
TagLib::FLAC::File.open(@source) do |file|
|
258
|
+
if tag = file.xiph_comment
|
259
|
+
[
|
260
|
+
[:album_artist, ['ALBUMARTIST', 'ALBUM ARTIST', 'ALBUM_ARTIST'], :to_s ],
|
261
|
+
[:disc_number, ['DISCNUMBER'], :to_i ],
|
262
|
+
[:disc_total, ['DISCTOTAL'], :to_i ]
|
263
|
+
].each do |field,list,func|
|
264
|
+
val = list.collect{ |i| tag.field_list_map[i] }.select{|i| i }.first
|
265
|
+
instance_variable_set("@#{field}", val[0].send(func)) if val
|
266
|
+
end
|
267
|
+
end
|
268
|
+
end
|
269
|
+
when :mp3
|
270
|
+
TagLib::MPEG::File.open(@source) do |file|
|
271
|
+
tag = file.id3v2_tag
|
272
|
+
if tag
|
273
|
+
[['TPE2', :@album_artist, :to_s], ['TPOS', :@disc_number, :to_i]].each do |t,v,m|
|
274
|
+
if tag.frame_list(t).first and tag.frame_list(t).first.to_s.size > 0
|
275
|
+
instance_variable_set( v, "#{tag.frame_list(t).first}".send( m.to_sym) )
|
276
|
+
end
|
277
|
+
end
|
278
|
+
end
|
279
|
+
end
|
280
|
+
end
|
281
|
+
@red = true
|
282
|
+
end
|
283
|
+
end; end
|
284
|
+
|
data/lib/mediafile.rb
ADDED
@@ -0,0 +1,15 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# vim:et sw=2 ts=2
|
3
|
+
|
4
|
+
require 'fileutils'
|
5
|
+
require 'digest/md5'
|
6
|
+
require 'timeout'
|
7
|
+
require 'taglib'
|
8
|
+
require 'mediafile/version'
|
9
|
+
|
10
|
+
module MediaFile
|
11
|
+
|
12
|
+
autoload :MediaFile, 'mediafile/mediafile.rb'
|
13
|
+
autoload :BulkMediaCopy, 'mediafile/bulkmediacopy.rb'
|
14
|
+
|
15
|
+
end
|
metadata
ADDED
@@ -0,0 +1,72 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: mediafile
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.3
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Jeff Harvey-Smith
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2014-04-07 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: taglib-ruby
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - ~>
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '0.6'
|
20
|
+
- - '>='
|
21
|
+
- !ruby/object:Gem::Version
|
22
|
+
version: 0.6.0
|
23
|
+
type: :runtime
|
24
|
+
prerelease: false
|
25
|
+
version_requirements: !ruby/object:Gem::Requirement
|
26
|
+
requirements:
|
27
|
+
- - ~>
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
version: '0.6'
|
30
|
+
- - '>='
|
31
|
+
- !ruby/object:Gem::Version
|
32
|
+
version: 0.6.0
|
33
|
+
description: Parse media file metadata, copy or transcode mediafiles.
|
34
|
+
email:
|
35
|
+
- jharveysmith@gmail.com
|
36
|
+
executables:
|
37
|
+
- music_cp
|
38
|
+
extensions: []
|
39
|
+
extra_rdoc_files: []
|
40
|
+
files:
|
41
|
+
- ./bin/music_cp
|
42
|
+
- ./lib/mediafile.rb
|
43
|
+
- ./lib/mediafile/bulkmediacopy.rb
|
44
|
+
- ./lib/mediafile/mediafile.rb
|
45
|
+
- ./lib/mediafile/version.rb
|
46
|
+
- bin/music_cp
|
47
|
+
homepage: https://github.com/seginoviax/mediafile
|
48
|
+
licenses:
|
49
|
+
- MIT
|
50
|
+
metadata: {}
|
51
|
+
post_install_message:
|
52
|
+
rdoc_options: []
|
53
|
+
require_paths:
|
54
|
+
- lib
|
55
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
56
|
+
requirements:
|
57
|
+
- - ~>
|
58
|
+
- !ruby/object:Gem::Version
|
59
|
+
version: '2'
|
60
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
61
|
+
requirements:
|
62
|
+
- - '>='
|
63
|
+
- !ruby/object:Gem::Version
|
64
|
+
version: '0'
|
65
|
+
requirements: []
|
66
|
+
rubyforge_project:
|
67
|
+
rubygems_version: 2.2.2
|
68
|
+
signing_key:
|
69
|
+
specification_version: 4
|
70
|
+
summary: Parse media file metadata.
|
71
|
+
test_files: []
|
72
|
+
has_rdoc:
|