ruby-audioinfo 0.3.3 → 0.5.4
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 +5 -5
- data/.github/workflows/ruby.yml +53 -0
- data/.gitignore +1 -0
- data/.rubocop.yml +10 -0
- data/.rubocop_todo.yml +116 -0
- data/CODE_OF_CONDUCT.md +84 -0
- data/Gemfile +12 -0
- data/Gemfile.lock +76 -0
- data/History.txt +26 -2
- data/README.md +56 -0
- data/Rakefile +11 -35
- data/bin/console +15 -0
- data/codecov.yml +1 -0
- data/lib/audioinfo.rb +250 -267
- data/lib/audioinfo/album.rb +112 -116
- data/lib/audioinfo/case_insensitive_hash.rb +3 -1
- data/lib/audioinfo/mpcinfo.rb +81 -85
- data/lib/audioinfo/version.rb +5 -0
- data/ruby-audioinfo.gemspec +35 -0
- metadata +72 -88
- data/.gemtest +0 -0
- data/Manifest.txt +0 -9
- data/README.rdoc +0 -46
- data/test/mpcinfo.rb +0 -8
- data/test/test_audioinfo.rb +0 -35
- data/test/test_case_insensitive_hash.rb +0 -28
- data/test/test_helper.rb +0 -1
data/Rakefile
CHANGED
|
@@ -1,40 +1,16 @@
|
|
|
1
|
-
#
|
|
1
|
+
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require '
|
|
3
|
+
require 'bundler/gem_tasks'
|
|
4
|
+
require 'rake/testtask'
|
|
4
5
|
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
developer "Guillaume Pierronnet", "guillaume.pierronnet@gmail.com"
|
|
10
|
-
developer 'Marcello Barnaba', "unknown"
|
|
11
|
-
remote_rdoc_dir = ''
|
|
12
|
-
rdoc_locations << "rubyforge.org:/var/www/gforge-projects/ruby-audioinfo/"
|
|
13
|
-
|
|
14
|
-
self.extra_rdoc_files = FileList["*.rdoc"]
|
|
15
|
-
#history_file = "History.txt"
|
|
16
|
-
self.readme_file = "README.rdoc"
|
|
17
|
-
self.test_globs = ["test/test_*.rb"]
|
|
18
|
-
self.rsync_args = "-rv --delete"
|
|
19
|
-
|
|
20
|
-
extra_deps << ['ruby-mp3info', '>= 0.8']
|
|
21
|
-
extra_deps << ['ruby-ogginfo', '>= 0.6.13']
|
|
22
|
-
extra_deps << ['mp4info', '>= 1.7.3']
|
|
23
|
-
extra_deps << ['moumar-wmainfo-rb', '>= 0.7']
|
|
24
|
-
extra_deps << ['flacinfo-rb', '>= 0.4']
|
|
25
|
-
extra_deps << ['apetag', '>= 1.1.4']
|
|
26
|
-
#extra_dev_deps << ["rake", ">=0"]
|
|
6
|
+
Rake::TestTask.new(:test) do |t|
|
|
7
|
+
t.libs << 'test'
|
|
8
|
+
t.libs << 'lib'
|
|
9
|
+
t.test_files = FileList['test/**/*_test.rb']
|
|
27
10
|
end
|
|
28
11
|
|
|
29
|
-
|
|
30
|
-
# vim: syntax=Ruby
|
|
31
|
-
require 'rubygems'
|
|
32
|
-
require "rake/rdoctask"
|
|
12
|
+
require 'rubocop/rake_task'
|
|
33
13
|
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
rd.rdoc_files.include("README.rdoc", "History.txt", "lib/**/*.rb")
|
|
38
|
-
rd.title = "ruby-audioinfo #{AudioInfo::VERSION}"
|
|
39
|
-
end
|
|
40
|
-
=end
|
|
14
|
+
RuboCop::RakeTask.new
|
|
15
|
+
|
|
16
|
+
task default: %i[test rubocop]
|
data/bin/console
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
require 'bundler/setup'
|
|
5
|
+
require 'audioinfo'
|
|
6
|
+
|
|
7
|
+
# You can add fixtures and/or initialization code here to make experimenting
|
|
8
|
+
# with your gem easier. You can also use a different console, if you like.
|
|
9
|
+
|
|
10
|
+
# (If you use this, don't forget to add pry to your Gemfile!)
|
|
11
|
+
# require "pry"
|
|
12
|
+
# Pry.start
|
|
13
|
+
|
|
14
|
+
require 'irb'
|
|
15
|
+
IRB.start(__FILE__)
|
data/codecov.yml
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
comment: false
|
data/lib/audioinfo.rb
CHANGED
|
@@ -1,52 +1,45 @@
|
|
|
1
|
-
#
|
|
2
|
-
require "stringio"
|
|
1
|
+
# frozen_string_literal: true
|
|
3
2
|
|
|
4
|
-
require
|
|
5
|
-
require "ogginfo"
|
|
6
|
-
require "wmainfo"
|
|
7
|
-
require "mp4info"
|
|
8
|
-
require "flacinfo"
|
|
9
|
-
require "apetag"
|
|
3
|
+
require 'stringio'
|
|
10
4
|
|
|
11
|
-
|
|
5
|
+
require 'mp3info'
|
|
6
|
+
require 'ogginfo'
|
|
7
|
+
require 'wmainfo'
|
|
8
|
+
require 'mp4info'
|
|
9
|
+
require 'flacinfo'
|
|
10
|
+
require 'apetag'
|
|
11
|
+
require 'wavefile'
|
|
12
12
|
|
|
13
|
-
|
|
14
|
-
require "audioinfo/case_insensitive_hash"
|
|
13
|
+
$LOAD_PATH << __dir__
|
|
15
14
|
|
|
16
|
-
|
|
15
|
+
require 'audioinfo/mpcinfo'
|
|
16
|
+
require 'audioinfo/case_insensitive_hash'
|
|
17
|
+
require 'audioinfo/version'
|
|
17
18
|
|
|
18
|
-
class
|
|
19
|
-
if RUBY_VERSION[0..2] == "1.8"
|
|
20
|
-
RUBY_1_8 = true
|
|
21
|
-
require "iconv"
|
|
22
|
-
else
|
|
23
|
-
RUBY_1_8 = false
|
|
24
|
-
end
|
|
25
|
-
|
|
26
|
-
MUSICBRAINZ_FIELDS = {
|
|
27
|
-
"trmid" => "TRM Id",
|
|
28
|
-
"artistid" => "Artist Id",
|
|
29
|
-
"albumid" => "Album Id",
|
|
30
|
-
"albumtype" => "Album Type",
|
|
31
|
-
"albumstatus" => "Album Status",
|
|
32
|
-
"albumartistid" => "Album Artist Id",
|
|
33
|
-
"sortname" => "Sort Name",
|
|
34
|
-
"trackid" => "Track Id"
|
|
35
|
-
}
|
|
19
|
+
class AudioInfoError < StandardError; end
|
|
36
20
|
|
|
37
|
-
|
|
21
|
+
class AudioInfo
|
|
22
|
+
MUSICBRAINZ_FIELDS = {
|
|
23
|
+
'trmid' => 'TRM Id',
|
|
24
|
+
'artistid' => 'Artist Id',
|
|
25
|
+
'albumid' => 'Album Id',
|
|
26
|
+
'albumtype' => 'Album Type',
|
|
27
|
+
'albumstatus' => 'Album Status',
|
|
28
|
+
'albumartistid' => 'Album Artist Id',
|
|
29
|
+
'sortname' => 'Sort Name',
|
|
30
|
+
'trackid' => 'Track Id'
|
|
31
|
+
}.freeze
|
|
38
32
|
|
|
39
|
-
|
|
33
|
+
SUPPORTED_EXTENSIONS = %w[mp3 ogg opus spx mpc wma mp4 aac m4a flac wav].freeze
|
|
40
34
|
|
|
41
|
-
attr_reader :path, :extension, :musicbrainz_infos, :tracknum, :bitrate, :vbr
|
|
42
|
-
attr_reader :artist, :album, :title, :length, :date
|
|
35
|
+
attr_reader :path, :extension, :musicbrainz_infos, :tracknum, :bitrate, :vbr, :artist, :album, :title, :length, :date
|
|
43
36
|
|
|
44
37
|
# Part of testing API - you should not use this directly
|
|
45
38
|
attr_reader :info
|
|
46
|
-
|
|
39
|
+
|
|
47
40
|
# "block version" of #new()
|
|
48
41
|
def self.open(*args)
|
|
49
|
-
audio_info =
|
|
42
|
+
audio_info = new(*args)
|
|
50
43
|
ret = nil
|
|
51
44
|
if block_given?
|
|
52
45
|
begin
|
|
@@ -62,163 +55,161 @@ class AudioInfo
|
|
|
62
55
|
|
|
63
56
|
# test whether +path+ is a valid and supported audiofile
|
|
64
57
|
def self.is_audio_file?(path)
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
return false
|
|
70
|
-
end
|
|
58
|
+
AudioInfo.new(path)
|
|
59
|
+
true
|
|
60
|
+
rescue AudioInfoError
|
|
61
|
+
false
|
|
71
62
|
end
|
|
72
63
|
|
|
73
64
|
# open the file with path +fn+
|
|
74
|
-
def initialize(filename)
|
|
75
|
-
raise(AudioInfoError,
|
|
65
|
+
def initialize(filename, extension = nil)
|
|
66
|
+
raise(AudioInfoError, 'path is nil') if filename.nil?
|
|
67
|
+
|
|
76
68
|
@path = filename
|
|
77
69
|
ext = File.extname(@path)
|
|
78
|
-
|
|
79
|
-
|
|
70
|
+
@extension = extension || (ext && ext[1..-1].downcase)
|
|
71
|
+
raise(AudioInfoError, 'cannot find extension') if @extension.empty?
|
|
72
|
+
|
|
80
73
|
@musicbrainz_infos = {}
|
|
81
74
|
|
|
82
75
|
begin
|
|
83
76
|
case @extension
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
end
|
|
102
|
-
end
|
|
103
|
-
end
|
|
104
|
-
@bitrate = @info.bitrate
|
|
105
|
-
i = @info.tag.tracknum
|
|
106
|
-
@tracknum = (i.is_a?(Array) ? i.last : i).to_i
|
|
107
|
-
@length = @info.length.to_i
|
|
108
|
-
@date = @info.tag["date"]
|
|
109
|
-
@vbr = @info.vbr
|
|
110
|
-
@info.close
|
|
111
|
-
|
|
112
|
-
when 'ogg'
|
|
113
|
-
@info = OggInfo.new(filename)
|
|
114
|
-
default_fill_musicbrainz_fields
|
|
115
|
-
default_tag_fill
|
|
116
|
-
@bitrate = @info.bitrate/1000
|
|
117
|
-
@tracknum = @info.tag.tracknumber.to_i
|
|
118
|
-
@length = @info.length.to_i
|
|
119
|
-
@date = @info.tag["date"]
|
|
120
|
-
@vbr = true
|
|
121
|
-
@info.close
|
|
122
|
-
|
|
123
|
-
when 'mpc'
|
|
124
|
-
fill_ape_tag(filename)
|
|
125
|
-
|
|
126
|
-
mpc_info = MpcInfo.new(filename)
|
|
127
|
-
@bitrate = mpc_info.infos['bitrate']/1000
|
|
128
|
-
@length = mpc_info.infos['length']
|
|
129
|
-
|
|
130
|
-
when 'ape'
|
|
131
|
-
fill_ape_tag(filename)
|
|
132
|
-
|
|
133
|
-
when 'wma'
|
|
134
|
-
@info = WmaInfo.new(filename, :encoding => 'utf-8')
|
|
135
|
-
@artist = @info.tags["Author"]
|
|
136
|
-
@album = @info.tags["AlbumTitle"]
|
|
137
|
-
@title = @info.tags["Title"]
|
|
138
|
-
@tracknum = @info.tags["TrackNumber"].to_i
|
|
139
|
-
@date = @info.tags["Year"]
|
|
140
|
-
@bitrate = @info.info["bitrate"]
|
|
141
|
-
@length = @info.info["playtime_seconds"]
|
|
142
|
-
MUSICBRAINZ_FIELDS.each do |key, original_key|
|
|
143
|
-
@musicbrainz_infos[key] =
|
|
144
|
-
@info.info["MusicBrainz/" + original_key.tr(" ", "")] ||
|
|
145
|
-
@info.info["MusicBrainz/" + original_key]
|
|
146
|
-
end
|
|
147
|
-
|
|
148
|
-
when 'mp4', 'aac', 'm4a'
|
|
149
|
-
@extension = 'mp4'
|
|
150
|
-
@info = MP4Info.open(filename)
|
|
151
|
-
@artist = @info.ART
|
|
152
|
-
@album = @info.ALB
|
|
153
|
-
@title = @info.NAM
|
|
154
|
-
@tracknum = ( t = @info.TRKN ) ? t.first : 0
|
|
155
|
-
@date = @info.DAY
|
|
156
|
-
@bitrate = @info.BITRATE
|
|
157
|
-
@length = @info.SECS
|
|
158
|
-
mapping = MUSICBRAINZ_FIELDS.invert
|
|
159
|
-
|
|
160
|
-
faad_info(filename).match(/^MusicBrainz (.+)$/) do
|
|
161
|
-
name, value = $1.split(/: /, 2)
|
|
162
|
-
key = mapping[name]
|
|
163
|
-
@musicbrainz_infos[key] = value
|
|
164
|
-
end
|
|
165
|
-
|
|
166
|
-
when 'flac'
|
|
167
|
-
@info = FlacInfo.new(filename)
|
|
168
|
-
# Unfortunately, FlacInfo doesn't allow us to fiddle inside
|
|
169
|
-
# their class, so we have to brute force it. Any other
|
|
170
|
-
# solution (e.g. creating another abstraction or getting
|
|
171
|
-
# methods) lands up being more messy and brittle.
|
|
172
|
-
@info.instance_variable_set('@tags', CaseInsensitiveHash.new(@info.tags))
|
|
173
|
-
|
|
174
|
-
get_tag = proc do |name|
|
|
175
|
-
if t = @info.tags[name]
|
|
176
|
-
t.dup.force_encoding("utf-8")
|
|
177
|
-
else
|
|
178
|
-
nil
|
|
77
|
+
when 'mp3'
|
|
78
|
+
@info = Mp3Info.new(filename)
|
|
79
|
+
default_tag_fill
|
|
80
|
+
# "TXXX"=>
|
|
81
|
+
# ["MusicBrainz TRM Id\000",
|
|
82
|
+
# "MusicBrainz Artist Id\000aba64937-3334-4c65-90a1-4e6b9d4d7ada",
|
|
83
|
+
# "MusicBrainz Album Id\000e1a223c1-cbc2-427f-a192-5d22fefd7c4c",
|
|
84
|
+
# "MusicBrainz Album Type\000album",
|
|
85
|
+
# "MusicBrainz Album Status\000official",
|
|
86
|
+
# "MusicBrainz Album Artist Id\000"]
|
|
87
|
+
|
|
88
|
+
if (arr = @info.tag2['TXXX']).is_a?(Array)
|
|
89
|
+
fields = MUSICBRAINZ_FIELDS.invert
|
|
90
|
+
arr.each do |val|
|
|
91
|
+
if val =~ /^MusicBrainz (.+)\000(.*)$/
|
|
92
|
+
short_name = fields[Regexp.last_match(1)]
|
|
93
|
+
@musicbrainz_infos[short_name] = Regexp.last_match(2).gsub("\xEF\xBB\xBF".force_encoding('UTF-8'), '')
|
|
179
94
|
end
|
|
180
95
|
end
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
96
|
+
end
|
|
97
|
+
@bitrate = @info.bitrate
|
|
98
|
+
i = @info.tag.tracknum
|
|
99
|
+
@tracknum = (i.is_a?(Array) ? i.last : i).to_i
|
|
100
|
+
@length = @info.length.to_i
|
|
101
|
+
@date = @info.tag['date']
|
|
102
|
+
@vbr = @info.vbr
|
|
103
|
+
@info.close
|
|
104
|
+
|
|
105
|
+
when 'ogg', 'opus', 'spx'
|
|
106
|
+
@info = OggInfo.new(filename)
|
|
107
|
+
default_fill_musicbrainz_fields
|
|
108
|
+
default_tag_fill
|
|
109
|
+
@bitrate = @info.bitrate / 1000
|
|
110
|
+
@tracknum = @info.tag.tracknumber.to_i
|
|
111
|
+
@length = @info.length.to_i
|
|
112
|
+
@date = @info.tag['date']
|
|
113
|
+
@vbr = true
|
|
114
|
+
@info.close
|
|
115
|
+
|
|
116
|
+
when 'mpc'
|
|
117
|
+
fill_ape_tag(filename)
|
|
118
|
+
mpc_info = MpcInfo.new(filename)
|
|
119
|
+
@bitrate = mpc_info.infos['bitrate'] / 1000
|
|
120
|
+
@length = mpc_info.infos['length']
|
|
121
|
+
|
|
122
|
+
when 'ape'
|
|
123
|
+
fill_ape_tag(filename)
|
|
124
|
+
|
|
125
|
+
when 'wma'
|
|
126
|
+
@info = WmaInfo.new(filename, encoding: 'utf-8')
|
|
127
|
+
@artist = @info.tags['Author']
|
|
128
|
+
@album = @info.tags['AlbumTitle']
|
|
129
|
+
@title = @info.tags['Title']
|
|
130
|
+
@tracknum = @info.tags['TrackNumber'].to_i
|
|
131
|
+
@date = @info.tags['Year']
|
|
132
|
+
@bitrate = @info.info['bitrate']
|
|
133
|
+
@length = @info.info['playtime_seconds']
|
|
134
|
+
MUSICBRAINZ_FIELDS.each do |key, original_key|
|
|
135
|
+
@musicbrainz_infos[key] =
|
|
136
|
+
@info.info["MusicBrainz/#{original_key.tr(' ', '')}"] ||
|
|
137
|
+
@info.info["MusicBrainz/#{original_key}"]
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
when 'mp4', 'aac', 'm4a'
|
|
141
|
+
@extension = 'mp4'
|
|
142
|
+
@info = MP4Info.open(filename)
|
|
143
|
+
@artist = @info.ART
|
|
144
|
+
@album = @info.ALB
|
|
145
|
+
@title = @info.NAM
|
|
146
|
+
@tracknum = (t = @info.TRKN) ? t.first : 0
|
|
147
|
+
@date = @info.DAY
|
|
148
|
+
@bitrate = @info.BITRATE
|
|
149
|
+
@length = @info.SECS
|
|
150
|
+
mapping = MUSICBRAINZ_FIELDS.invert
|
|
151
|
+
|
|
152
|
+
faad_info(filename).match(/^MusicBrainz (.+)$/) do
|
|
153
|
+
name, value = Regexp.last_match(1).split(/: /, 2)
|
|
154
|
+
key = mapping[name]
|
|
155
|
+
@musicbrainz_infos[key] = value
|
|
156
|
+
end
|
|
157
|
+
|
|
158
|
+
when 'flac'
|
|
159
|
+
@info = FlacInfo.new(filename)
|
|
160
|
+
# Unfortunately, FlacInfo doesn't allow us to fiddle inside
|
|
161
|
+
# their class, so we have to brute force it. Any other
|
|
162
|
+
# solution (e.g. creating another abstraction or getting
|
|
163
|
+
# methods) lands up being more messy and brittle.
|
|
164
|
+
@info.instance_variable_set('@tags', CaseInsensitiveHash.new(@info.tags))
|
|
165
|
+
|
|
166
|
+
get_tag = proc do |name|
|
|
167
|
+
if t = @info.tags[name]
|
|
168
|
+
t.dup.force_encoding('utf-8')
|
|
195
169
|
end
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
170
|
+
end
|
|
171
|
+
|
|
172
|
+
@artist = get_tag.call('artist')
|
|
173
|
+
@album = get_tag.call('album')
|
|
174
|
+
@title = get_tag.call('title')
|
|
175
|
+
@tracknum = @info.tags['tracknumber'].to_i
|
|
176
|
+
@date = get_tag.call('date')
|
|
177
|
+
@bitrate = 0
|
|
178
|
+
@length = @info.streaminfo['total_samples'] / @info.streaminfo['samplerate'].to_f
|
|
179
|
+
@bitrate = File.size(filename).to_f * 8 / @length / 1024 if @length.positive?
|
|
180
|
+
@info.tags.each do |tagname, _tagvalue|
|
|
181
|
+
next unless tagname =~ /^musicbrainz_(.+)$/
|
|
182
|
+
|
|
183
|
+
@musicbrainz_infos[Regexp.last_match(1)] = get_tag.call(tagname)
|
|
184
|
+
end
|
|
185
|
+
@musicbrainz_infos['trmid'] = @info.tags['musicip_puid']
|
|
186
|
+
# default_fill_musicbrainz_fields
|
|
201
187
|
|
|
202
|
-
|
|
203
|
-
@
|
|
188
|
+
when 'wav'
|
|
189
|
+
@info = WaveFile::Reader.info(filename)
|
|
190
|
+
@length = @info.duration.hours * 3600 + @info.duration.minutes * 60 + @info.duration.seconds +
|
|
191
|
+
@info.duration.milliseconds * 0.001
|
|
192
|
+
@bitrate = File.size(filename) * 8 / @length / 1024
|
|
193
|
+
|
|
194
|
+
else
|
|
195
|
+
raise(AudioInfoError, "unsupported extension '.#{@extension}'")
|
|
204
196
|
end
|
|
205
197
|
|
|
206
|
-
@
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
rescue
|
|
198
|
+
@tracknum = nil if @tracknum&.zero?
|
|
199
|
+
|
|
200
|
+
@musicbrainz_infos.delete_if { |_k, v| v.nil? }
|
|
201
|
+
@hash = { 'artist' => @artist,
|
|
202
|
+
'album' => @album,
|
|
203
|
+
'title' => @title,
|
|
204
|
+
'tracknum' => @tracknum,
|
|
205
|
+
'date' => @date,
|
|
206
|
+
'length' => @length,
|
|
207
|
+
'bitrate' => @bitrate }
|
|
208
|
+
rescue StandardError, Mp3InfoError, OggInfoError, ApeTagError => e
|
|
217
209
|
raise AudioInfoError, e.to_s, e.backtrace
|
|
218
210
|
end
|
|
219
211
|
|
|
220
212
|
@needs_commit = false
|
|
221
|
-
|
|
222
213
|
end
|
|
223
214
|
|
|
224
215
|
# set the title of the file
|
|
@@ -275,121 +266,113 @@ class AudioInfo
|
|
|
275
266
|
def close
|
|
276
267
|
if @needs_commit
|
|
277
268
|
case @info
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
end
|
|
288
|
-
end
|
|
289
|
-
when OggInfo
|
|
290
|
-
OggInfo.open(@path) do |ogg|
|
|
291
|
-
{ "artist" => @artist,
|
|
292
|
-
"album" => @album,
|
|
293
|
-
"title" => @title,
|
|
294
|
-
"tracknumber" => @tracknum}.each do |k,v|
|
|
295
|
-
ogg.tag[k] = v.to_s
|
|
296
|
-
end
|
|
297
|
-
if @picture
|
|
298
|
-
ogg.picture = @picture
|
|
299
|
-
end
|
|
300
|
-
end
|
|
301
|
-
|
|
302
|
-
when ApeTag
|
|
303
|
-
ape = ApeTag.new(@path)
|
|
304
|
-
ape.update do |fields|
|
|
305
|
-
fields["Artist"] = @artist
|
|
306
|
-
fields["Album"] = @album
|
|
307
|
-
fields["Title"] = @title
|
|
308
|
-
fields["Track"] = @tracknum.to_s
|
|
269
|
+
when Mp3Info
|
|
270
|
+
Mp3Info.open(@path) do |info|
|
|
271
|
+
info.tag.artist = @artist
|
|
272
|
+
info.tag.title = @title
|
|
273
|
+
info.tag.album = @album
|
|
274
|
+
info.tag.tracknum = @tracknum
|
|
275
|
+
if @picture
|
|
276
|
+
info.tag2.remove_pictures
|
|
277
|
+
info.tag2.add_picture(File.binread(@picture))
|
|
309
278
|
end
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
279
|
+
end
|
|
280
|
+
when OggInfo
|
|
281
|
+
OggInfo.open(@path) do |ogg|
|
|
282
|
+
{ 'artist' => @artist,
|
|
283
|
+
'album' => @album,
|
|
284
|
+
'title' => @title,
|
|
285
|
+
'tracknumber' => @tracknum }.each do |k, v|
|
|
286
|
+
ogg.tag[k] = v.to_s
|
|
287
|
+
end
|
|
288
|
+
ogg.picture = @picture if @picture
|
|
289
|
+
end
|
|
290
|
+
|
|
291
|
+
when ApeTag
|
|
292
|
+
ape = ApeTag.new(@path)
|
|
293
|
+
ape.update do |fields|
|
|
294
|
+
fields['Artist'] = @artist
|
|
295
|
+
fields['Album'] = @album
|
|
296
|
+
fields['Title'] = @title
|
|
297
|
+
fields['Track'] = @tracknum.to_s
|
|
298
|
+
end
|
|
299
|
+
else
|
|
300
|
+
have_metaflac = system('which metaflac > /dev/null')
|
|
301
|
+
have_ffmpeg = system('which ffmpeg > /dev/null')
|
|
302
|
+
if have_metaflac && @info.is_a?(FlacInfo)
|
|
303
|
+
tags = { 'ARTIST' => @artist,
|
|
304
|
+
'ALBUM' => @album,
|
|
305
|
+
'TITLE' => @title,
|
|
306
|
+
'TRACKNUMBER' => @tracknum }.inject([]) do |tags, (key, value)|
|
|
307
|
+
tags + ['--set-tag', "#{key}=#{value}"]
|
|
308
|
+
end
|
|
309
|
+
tag_with_shell_command('metaflac', '--remove-all', :src)
|
|
310
|
+
tag_with_shell_command('metaflac', tags, :src)
|
|
311
|
+
elsif have_ffmpeg
|
|
312
|
+
tags = { 'artist' => @artist,
|
|
313
|
+
'album' => @album,
|
|
314
|
+
'title' => @title }.inject([]) do |tags, (key, value)|
|
|
315
|
+
tags + ['-metadata', "#{key}=#{value}"]
|
|
331
316
|
end
|
|
317
|
+
tag_with_shell_command('ffmpeg', '-y', '-i', :src, '-loglevel', 'quiet', tags, :dst)
|
|
318
|
+
else
|
|
319
|
+
raise(AudioInfoError, 'implement me')
|
|
320
|
+
end
|
|
332
321
|
end
|
|
333
|
-
|
|
334
322
|
end
|
|
335
323
|
@needs_commit
|
|
336
324
|
end
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
"musicbrainz_albumartistid"=>"89ad4ac3-39f7-470e-963a-56509c546377"}>
|
|
352
|
-
=end
|
|
325
|
+
# {"musicbrainz_albumstatus"=>"official",
|
|
326
|
+
# "artist"=>"Jill Scott",
|
|
327
|
+
# "replaygain_track_gain"=>"-3.29 dB",
|
|
328
|
+
# "tracknumber"=>"1",
|
|
329
|
+
# "title"=>"A long walk (A touch of Jazz Mix)..Jazzanova Love Beats...",
|
|
330
|
+
# "musicbrainz_sortname"=>"Scott, Jill",
|
|
331
|
+
# "musicbrainz_artistid"=>"b1fb6a18-1626-4011-80fb-eaf83dfebcb6",
|
|
332
|
+
# "musicbrainz_albumid"=>"cb2ad8c7-4a02-4e46-ae9a-c7c2463c7235",
|
|
333
|
+
# "replaygain_track_peak"=>"0.82040048",
|
|
334
|
+
# "musicbrainz_albumtype"=>"compilation",
|
|
335
|
+
# "album"=>"...Mixing (Jazzanova)",
|
|
336
|
+
# "musicbrainz_trmid"=>"1ecec0a6-c7c3-4179-abea-ef12dabc7cbd",
|
|
337
|
+
# "musicbrainz_trackid"=>"0a368e63-dddf-441f-849c-ca23f9cb2d49",
|
|
338
|
+
# "musicbrainz_albumartistid"=>"89ad4ac3-39f7-470e-963a-56509c546377"}>
|
|
353
339
|
|
|
354
340
|
# check if the file is correctly tagged by MusicBrainz
|
|
355
341
|
def mb_tagged?
|
|
356
|
-
|
|
342
|
+
!@musicbrainz_infos.empty?
|
|
357
343
|
end
|
|
358
344
|
|
|
359
|
-
private
|
|
345
|
+
private
|
|
360
346
|
|
|
361
347
|
def sanitize(input)
|
|
362
348
|
s = input.is_a?(Array) ? input.first : input
|
|
363
|
-
s.
|
|
349
|
+
s.delete("\000")
|
|
364
350
|
end
|
|
365
351
|
|
|
366
352
|
def default_fill_musicbrainz_fields(tags = @info.tag)
|
|
367
|
-
MUSICBRAINZ_FIELDS.
|
|
353
|
+
MUSICBRAINZ_FIELDS.each_key do |field|
|
|
368
354
|
val = tags["musicbrainz_#{field}"]
|
|
369
355
|
@musicbrainz_infos[field] = val if val
|
|
370
356
|
end
|
|
371
357
|
end
|
|
372
358
|
|
|
373
359
|
def default_tag_fill(tags = @info.tag)
|
|
374
|
-
%w
|
|
375
|
-
instance_variable_set(
|
|
360
|
+
%w[artist album title].each do |v|
|
|
361
|
+
instance_variable_set("@#{v}".to_sym, sanitize(tags[v] || ''))
|
|
376
362
|
end
|
|
377
363
|
end
|
|
378
364
|
|
|
379
365
|
def fill_ape_tag(filename)
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
hash[k.downcase] = v ? v.first : nil
|
|
384
|
-
hash
|
|
385
|
-
end
|
|
386
|
-
default_fill_musicbrainz_fields(tags)
|
|
387
|
-
default_tag_fill(tags)
|
|
388
|
-
|
|
389
|
-
@date = tags["year"]
|
|
390
|
-
@tracknum = tags['track'].to_i
|
|
391
|
-
rescue ApeTagError
|
|
366
|
+
@info = ApeTag.new(filename)
|
|
367
|
+
tags = @info.fields.each_with_object({}) do |(k, v), hash|
|
|
368
|
+
hash[k.downcase] = v ? v.first : nil
|
|
392
369
|
end
|
|
370
|
+
default_fill_musicbrainz_fields(tags)
|
|
371
|
+
default_tag_fill(tags)
|
|
372
|
+
|
|
373
|
+
@date = tags['year']
|
|
374
|
+
@tracknum = tags['track'].to_i
|
|
375
|
+
rescue ApeTagError
|
|
393
376
|
end
|
|
394
377
|
|
|
395
378
|
def faad_info(file)
|
|
@@ -399,8 +382,8 @@ class AudioInfo
|
|
|
399
382
|
fork do
|
|
400
383
|
stdout.close
|
|
401
384
|
stderr.close
|
|
402
|
-
|
|
403
|
-
|
|
385
|
+
$stdout.reopen(stdout_w)
|
|
386
|
+
$stderr.reopen(stderr_w)
|
|
404
387
|
exec 'faad', '-i', file
|
|
405
388
|
end
|
|
406
389
|
|
|
@@ -418,7 +401,7 @@ class AudioInfo
|
|
|
418
401
|
end
|
|
419
402
|
|
|
420
403
|
def shell_escape(s)
|
|
421
|
-
"'
|
|
404
|
+
"'#{s.gsub(/'/) { "'\\''" }}'"
|
|
422
405
|
end
|
|
423
406
|
|
|
424
407
|
def tag_with_shell_command(*command_arr)
|
|
@@ -428,10 +411,10 @@ class AudioInfo
|
|
|
428
411
|
end.flatten
|
|
429
412
|
end
|
|
430
413
|
|
|
431
|
-
hash = {:
|
|
414
|
+
hash = { src: @path }
|
|
432
415
|
if command_arr.include?(:dst)
|
|
433
|
-
Tempfile.open([
|
|
434
|
-
cmd = expand_command.call(hash.merge(:
|
|
416
|
+
Tempfile.open(['ruby-audioinfo', ".#{@extension}"]) do |tf|
|
|
417
|
+
cmd = expand_command.call(hash.merge(dst: tf.path))
|
|
435
418
|
tf.close
|
|
436
419
|
if system(*cmd)
|
|
437
420
|
FileUtils.mv(tf.path, @path)
|