mediafile 0.0.3
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 +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:
|