ruby-audioinfo 0.4 → 0.5.5
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 +7 -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 +22 -2
- data/README.md +56 -0
- data/Rakefile +11 -35
- data/bin/console +15 -0
- data/codecov.yml +1 -0
- data/lib/audioinfo.rb +262 -285
- 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.7']
|
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,163 @@ 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', 'opus', 'spx'
|
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).scan(/^MusicBrainz (.+): (.+)$/) do |match|
|
153
|
+
name, value = match
|
154
|
+
key = mapping[name]
|
155
|
+
next unless key
|
156
|
+
|
157
|
+
@musicbrainz_infos[key] = value.strip.gsub("\u0000", '')
|
158
|
+
end
|
159
|
+
|
160
|
+
when 'flac'
|
161
|
+
@info = FlacInfo.new(filename)
|
162
|
+
# Unfortunately, FlacInfo doesn't allow us to fiddle inside
|
163
|
+
# their class, so we have to brute force it. Any other
|
164
|
+
# solution (e.g. creating another abstraction or getting
|
165
|
+
# methods) lands up being more messy and brittle.
|
166
|
+
@info.instance_variable_set('@tags', CaseInsensitiveHash.new(@info.tags))
|
167
|
+
|
168
|
+
get_tag = proc do |name|
|
169
|
+
if t = @info.tags[name]
|
170
|
+
t.dup.force_encoding('utf-8')
|
195
171
|
end
|
196
|
-
|
197
|
-
|
198
|
-
|
199
|
-
|
200
|
-
|
172
|
+
end
|
173
|
+
|
174
|
+
@artist = get_tag.call('artist')
|
175
|
+
@album = get_tag.call('album')
|
176
|
+
@title = get_tag.call('title')
|
177
|
+
@tracknum = @info.tags['tracknumber'].to_i
|
178
|
+
@date = get_tag.call('date')
|
179
|
+
@bitrate = 0
|
180
|
+
@length = @info.streaminfo['total_samples'] / @info.streaminfo['samplerate'].to_f
|
181
|
+
@bitrate = File.size(filename).to_f * 8 / @length / 1024 if @length.positive?
|
182
|
+
@info.tags.each do |tagname, _tagvalue|
|
183
|
+
next unless tagname =~ /^musicbrainz_(.+)$/
|
184
|
+
|
185
|
+
@musicbrainz_infos[Regexp.last_match(1)] = get_tag.call(tagname)
|
186
|
+
end
|
187
|
+
@musicbrainz_infos['trmid'] = @info.tags['musicip_puid']
|
188
|
+
# default_fill_musicbrainz_fields
|
201
189
|
|
202
|
-
|
203
|
-
@
|
190
|
+
when 'wav'
|
191
|
+
@info = WaveFile::Reader.info(filename)
|
192
|
+
@length = @info.duration.hours * 3600 + @info.duration.minutes * 60 + @info.duration.seconds +
|
193
|
+
@info.duration.milliseconds * 0.001
|
194
|
+
@bitrate = File.size(filename) * 8 / @length / 1024
|
195
|
+
|
196
|
+
else
|
197
|
+
raise(AudioInfoError, "unsupported extension '.#{@extension}'")
|
204
198
|
end
|
205
199
|
|
206
|
-
@
|
207
|
-
|
208
|
-
|
209
|
-
|
210
|
-
|
211
|
-
|
212
|
-
|
213
|
-
|
214
|
-
|
215
|
-
|
216
|
-
rescue
|
200
|
+
@tracknum = nil if @tracknum&.zero?
|
201
|
+
|
202
|
+
@musicbrainz_infos.delete_if { |_k, v| v.nil? }
|
203
|
+
@hash = { 'artist' => @artist,
|
204
|
+
'album' => @album,
|
205
|
+
'title' => @title,
|
206
|
+
'tracknum' => @tracknum,
|
207
|
+
'date' => @date,
|
208
|
+
'length' => @length,
|
209
|
+
'bitrate' => @bitrate }
|
210
|
+
rescue StandardError, Mp3InfoError, OggInfoError, ApeTagError => e
|
217
211
|
raise AudioInfoError, e.to_s, e.backtrace
|
218
212
|
end
|
219
213
|
|
220
214
|
@needs_commit = false
|
221
|
-
|
222
215
|
end
|
223
216
|
|
224
217
|
# set the title of the file
|
@@ -275,150 +268,134 @@ class AudioInfo
|
|
275
268
|
def close
|
276
269
|
if @needs_commit
|
277
270
|
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
|
271
|
+
when Mp3Info
|
272
|
+
Mp3Info.open(@path) do |info|
|
273
|
+
info.tag.artist = @artist
|
274
|
+
info.tag.title = @title
|
275
|
+
info.tag.album = @album
|
276
|
+
info.tag.tracknum = @tracknum
|
277
|
+
if @picture
|
278
|
+
info.tag2.remove_pictures
|
279
|
+
info.tag2.add_picture(File.binread(@picture))
|
309
280
|
end
|
310
|
-
|
311
|
-
|
312
|
-
|
313
|
-
|
314
|
-
|
315
|
-
|
316
|
-
|
317
|
-
|
318
|
-
tags + ["--set-tag", "#{key}=#{value.to_s}"]
|
319
|
-
end
|
320
|
-
tag_with_shell_command("metaflac", "--remove-all", :src)
|
321
|
-
tag_with_shell_command("metaflac", tags, :src)
|
322
|
-
elsif have_ffmpeg
|
323
|
-
tags = {"artist" => @artist,
|
324
|
-
"album" => @album,
|
325
|
-
"title" => @title}.inject([]) do |tags, (key, value)|
|
326
|
-
tags + ["-metadata", "#{key}=#{value.to_s}"]
|
327
|
-
end
|
328
|
-
tag_with_shell_command("ffmpeg", "-y", "-i", :src, "-loglevel", "quiet", tags, :dst)
|
329
|
-
else
|
330
|
-
raise(AudioInfoError, "implement me")
|
281
|
+
end
|
282
|
+
when OggInfo
|
283
|
+
OggInfo.open(@path) do |ogg|
|
284
|
+
{ 'artist' => @artist,
|
285
|
+
'album' => @album,
|
286
|
+
'title' => @title,
|
287
|
+
'tracknumber' => @tracknum }.each do |k, v|
|
288
|
+
ogg.tag[k] = v.to_s
|
331
289
|
end
|
290
|
+
ogg.picture = @picture if @picture
|
291
|
+
end
|
292
|
+
|
293
|
+
when ApeTag
|
294
|
+
ape = ApeTag.new(@path)
|
295
|
+
ape.update do |fields|
|
296
|
+
fields['Artist'] = @artist
|
297
|
+
fields['Album'] = @album
|
298
|
+
fields['Title'] = @title
|
299
|
+
fields['Track'] = @tracknum.to_s
|
300
|
+
end
|
301
|
+
else
|
302
|
+
have_metaflac = system('which metaflac > /dev/null')
|
303
|
+
have_ffmpeg = system('which ffmpeg > /dev/null')
|
304
|
+
if have_metaflac && @info.is_a?(FlacInfo)
|
305
|
+
tags = { 'ARTIST' => @artist,
|
306
|
+
'ALBUM' => @album,
|
307
|
+
'TITLE' => @title,
|
308
|
+
'TRACKNUMBER' => @tracknum }.inject([]) do |tags, (key, value)|
|
309
|
+
tags + ['--set-tag', "#{key}=#{value}"]
|
310
|
+
end
|
311
|
+
tag_with_shell_command('metaflac', '--remove-all', :src)
|
312
|
+
tag_with_shell_command('metaflac', tags, :src)
|
313
|
+
elsif have_ffmpeg
|
314
|
+
tags = { 'artist' => @artist,
|
315
|
+
'album' => @album,
|
316
|
+
'title' => @title }.inject([]) do |tags, (key, value)|
|
317
|
+
tags + ['-metadata', "#{key}=#{value}"]
|
318
|
+
end
|
319
|
+
tag_with_shell_command('ffmpeg', '-y', '-i', :src, '-loglevel', 'quiet', tags, :dst)
|
320
|
+
else
|
321
|
+
raise(AudioInfoError, 'implement me')
|
322
|
+
end
|
332
323
|
end
|
333
|
-
|
334
324
|
end
|
335
325
|
@needs_commit
|
336
326
|
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
|
327
|
+
# {"musicbrainz_albumstatus"=>"official",
|
328
|
+
# "artist"=>"Jill Scott",
|
329
|
+
# "replaygain_track_gain"=>"-3.29 dB",
|
330
|
+
# "tracknumber"=>"1",
|
331
|
+
# "title"=>"A long walk (A touch of Jazz Mix)..Jazzanova Love Beats...",
|
332
|
+
# "musicbrainz_sortname"=>"Scott, Jill",
|
333
|
+
# "musicbrainz_artistid"=>"b1fb6a18-1626-4011-80fb-eaf83dfebcb6",
|
334
|
+
# "musicbrainz_albumid"=>"cb2ad8c7-4a02-4e46-ae9a-c7c2463c7235",
|
335
|
+
# "replaygain_track_peak"=>"0.82040048",
|
336
|
+
# "musicbrainz_albumtype"=>"compilation",
|
337
|
+
# "album"=>"...Mixing (Jazzanova)",
|
338
|
+
# "musicbrainz_trmid"=>"1ecec0a6-c7c3-4179-abea-ef12dabc7cbd",
|
339
|
+
# "musicbrainz_trackid"=>"0a368e63-dddf-441f-849c-ca23f9cb2d49",
|
340
|
+
# "musicbrainz_albumartistid"=>"89ad4ac3-39f7-470e-963a-56509c546377"}>
|
353
341
|
|
354
342
|
# check if the file is correctly tagged by MusicBrainz
|
355
343
|
def mb_tagged?
|
356
|
-
|
344
|
+
!@musicbrainz_infos.empty?
|
357
345
|
end
|
358
346
|
|
359
|
-
private
|
347
|
+
private
|
360
348
|
|
361
349
|
def sanitize(input)
|
362
350
|
s = input.is_a?(Array) ? input.first : input
|
363
|
-
s.
|
351
|
+
s.delete("\000")
|
364
352
|
end
|
365
353
|
|
366
354
|
def default_fill_musicbrainz_fields(tags = @info.tag)
|
367
|
-
MUSICBRAINZ_FIELDS.
|
355
|
+
MUSICBRAINZ_FIELDS.each_key do |field|
|
368
356
|
val = tags["musicbrainz_#{field}"]
|
369
357
|
@musicbrainz_infos[field] = val if val
|
370
358
|
end
|
371
359
|
end
|
372
360
|
|
373
361
|
def default_tag_fill(tags = @info.tag)
|
374
|
-
%w
|
375
|
-
instance_variable_set(
|
362
|
+
%w[artist album title].each do |v|
|
363
|
+
instance_variable_set("@#{v}".to_sym, sanitize(tags[v] || ''))
|
376
364
|
end
|
377
365
|
end
|
378
366
|
|
379
367
|
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
|
368
|
+
@info = ApeTag.new(filename)
|
369
|
+
tags = @info.fields.each_with_object({}) do |(k, v), hash|
|
370
|
+
hash[k.downcase] = v ? v.first : nil
|
392
371
|
end
|
393
|
-
|
372
|
+
default_fill_musicbrainz_fields(tags)
|
373
|
+
default_tag_fill(tags)
|
394
374
|
|
395
|
-
|
396
|
-
|
397
|
-
|
375
|
+
@date = tags['year']
|
376
|
+
@tracknum = tags['track'].to_i
|
377
|
+
rescue ApeTagError
|
378
|
+
end
|
398
379
|
|
399
|
-
|
400
|
-
|
401
|
-
stderr.close
|
402
|
-
STDOUT.reopen(stdout_w)
|
403
|
-
STDERR.reopen(stderr_w)
|
404
|
-
exec 'faad', '-i', file
|
405
|
-
end
|
380
|
+
def faad_info(path)
|
381
|
+
require 'open3'
|
406
382
|
|
407
|
-
|
408
|
-
|
409
|
-
pid, status = Process.wait2
|
383
|
+
output = ''
|
384
|
+
status = nil
|
410
385
|
|
411
|
-
|
412
|
-
|
413
|
-
|
414
|
-
|
386
|
+
begin
|
387
|
+
Open3.popen3('faad', '-i', path) do |_stdin, _stdout, stderr, wait_thr|
|
388
|
+
output = stderr.read.chomp
|
389
|
+
status = wait_thr.value
|
390
|
+
end
|
391
|
+
rescue StandardError
|
392
|
+
end
|
415
393
|
|
416
|
-
|
417
|
-
status.exitstatus.zero? ? err : ''
|
394
|
+
status&.exitstatus&.zero? ? output : ''
|
418
395
|
end
|
419
396
|
|
420
397
|
def shell_escape(s)
|
421
|
-
"'
|
398
|
+
"'#{s.gsub(/'/) { "'\\''" }}'"
|
422
399
|
end
|
423
400
|
|
424
401
|
def tag_with_shell_command(*command_arr)
|
@@ -428,10 +405,10 @@ class AudioInfo
|
|
428
405
|
end.flatten
|
429
406
|
end
|
430
407
|
|
431
|
-
hash = {:
|
408
|
+
hash = { src: @path }
|
432
409
|
if command_arr.include?(:dst)
|
433
|
-
Tempfile.open([
|
434
|
-
cmd = expand_command.call(hash.merge(:
|
410
|
+
Tempfile.open(['ruby-audioinfo', ".#{@extension}"]) do |tf|
|
411
|
+
cmd = expand_command.call(hash.merge(dst: tf.path))
|
435
412
|
tf.close
|
436
413
|
if system(*cmd)
|
437
414
|
FileUtils.mv(tf.path, @path)
|