flacinfo-rb 0.1
Sign up to get free protection for your applications and to get access to all the features.
- data/README +35 -0
- data/lib/flacinfo.rb +410 -0
- metadata +46 -0
data/README
ADDED
@@ -0,0 +1,35 @@
|
|
1
|
+
:: flacinfo-rb ::
|
2
|
+
Author: Darren Kirby
|
3
|
+
mailto:bulliver@badcomputer.org
|
4
|
+
License: Ruby
|
5
|
+
|
6
|
+
= Quick API docs =
|
7
|
+
|
8
|
+
== Initializing ==
|
9
|
+
|
10
|
+
require 'flacinfo'
|
11
|
+
foo = FlacInfo.new("someSong.flac")
|
12
|
+
|
13
|
+
== Public attributes ==
|
14
|
+
|
15
|
+
streaminfo :: hash of STREAMINFO block metadata
|
16
|
+
seektable :: hash of arrays of seek points
|
17
|
+
comment :: array of VORBIS COMMENT block metadata
|
18
|
+
tags :: user-friendly hash of Vorbis comment metadata key=value pairs
|
19
|
+
application :: hash of APPLICATION block metadata
|
20
|
+
padding :: hash of PADDING block metadata (currently just ['block_size'])
|
21
|
+
cuesheet :: hash of CUESHEET block metadata (currently just ['block_size'])
|
22
|
+
flac_file :: hash of APPLICATION Id 0x41544348 (Flac File) metadata if present
|
23
|
+
|
24
|
+
== Public methods ==
|
25
|
+
|
26
|
+
print_streaminfo :: pretty-print streaminfo hash
|
27
|
+
hastag('str') :: returns true if tags['str'] exists
|
28
|
+
print_tags :: pretty-print tags hash
|
29
|
+
print_seektable :: pretty-print seektable hash
|
30
|
+
meta_flac :: prints all META BLOCKS. (Mostly) equivelant to 'metaflac --list'
|
31
|
+
raw_data_dump(?) :: if passed a filename it will dump flac_file['raw_data'] to that file,
|
32
|
+
otherwise it will dump it to the console (even if binary!)
|
33
|
+
|
34
|
+
For more/different documentation see http://badcomputer.org/unix/code/flacinfo/
|
35
|
+
|
data/lib/flacinfo.rb
ADDED
@@ -0,0 +1,410 @@
|
|
1
|
+
# = Description
|
2
|
+
#
|
3
|
+
# flacinfo-rb gives you access to low level information on Flac files.
|
4
|
+
# * It parses stream information (STREAMINFO).
|
5
|
+
# * It parses Vorbis comments (VORBIS_COMMENT).
|
6
|
+
# * It parses the seek table (SEEKTABLE).
|
7
|
+
# * It parses the 'application metadata block' (APPLICATION).
|
8
|
+
# * If (APPLICATION) is ID 0x41544348 (Flac File)
|
9
|
+
# then we can parse that too.
|
10
|
+
# * It recognizes (but does not yet parse) the cue sheet (CUESHEET TRACK).
|
11
|
+
#
|
12
|
+
# = Copyright and Disclaimer
|
13
|
+
# Copyright:: (c) 2006 Darren Kirby
|
14
|
+
# FlacInfo is free software.
|
15
|
+
# No warranty is provided and the author cannot accept responsibility
|
16
|
+
# for lost or damaged files.
|
17
|
+
# License:: Ruby
|
18
|
+
# Author:: Darren Kirby (mailto:bulliver@badcomputer.org)
|
19
|
+
# Website:: http://badcomputer.org/unix/code/flacinfo/
|
20
|
+
#
|
21
|
+
# = More information
|
22
|
+
#
|
23
|
+
# * There is an example irb session that shows typical usage at
|
24
|
+
# http://badcomputer.org/unix/code/flacinfo/
|
25
|
+
# * The Flac spec is at:
|
26
|
+
# http://flac.sourceforge.net/format.html
|
27
|
+
# * The Vorbis Comment spec is at:
|
28
|
+
# http://www.xiph.org/vorbis/doc/v-comment.html
|
29
|
+
|
30
|
+
|
31
|
+
# FlacInfoError is raised when an error occurs parsing the Flac file.
|
32
|
+
# It will print an additional error string stating where the error occured.
|
33
|
+
class FlacInfoError < StandardError
|
34
|
+
end
|
35
|
+
|
36
|
+
# Note: STREAMINFO is the only block guaranteed to be present.
|
37
|
+
# Other attributes will be present, but empty if the associated block is not present in the Flac file.
|
38
|
+
class FlacInfo
|
39
|
+
|
40
|
+
# Hash of values extracted from the STREAMINFO block. Keys are 'samplerate', 'bits_per_sample', 'total_samples'
|
41
|
+
# 'channels', 'minimum_frame', 'maximum_frame', 'minimum_block', 'maximum_block', 'md5', and 'block_size'
|
42
|
+
attr_reader :streaminfo
|
43
|
+
|
44
|
+
# Hash of values extracted from the SEEKTABLE block. Keys are 'seek_points', 'block_size' and 'points'.
|
45
|
+
# 'points' is another hash whose keys start at 0 and end at ('seek_points' - 1). Each "seektable['points'][n]" hash
|
46
|
+
# contains an array whose values are [sample number, stream offset, number of frame samples] of each seek point.
|
47
|
+
attr_reader :seektable
|
48
|
+
|
49
|
+
# Array of "name=value" strings extracted from the VORBIS_COMMENT block. This is just the contents, metadata is in 'tags'.
|
50
|
+
attr_reader :comment
|
51
|
+
|
52
|
+
# Hash of the 'comment' values separated into "key => value" pairs as well as 'vendor_tag' and 'block_size'.
|
53
|
+
attr_reader :tags
|
54
|
+
|
55
|
+
# Hash of values extracted from the APPLICATION block. Keys are 'name', 'ID', and 'block_size'.
|
56
|
+
attr_reader :application
|
57
|
+
|
58
|
+
# Hash of values extracted from the PADDING block. Just one key: 'block_size'.
|
59
|
+
attr_reader :padding
|
60
|
+
|
61
|
+
# Hash of values extracted from the CUESHEET block. Just one key: 'block_size'.
|
62
|
+
attr_reader :cuesheet
|
63
|
+
|
64
|
+
# Hash of values extracted from an APPLICATION block if it is type 0x41544348 (Flac File).
|
65
|
+
# Keys are 'description', 'mime_type', and 'raw_data'.
|
66
|
+
attr_reader :flac_file
|
67
|
+
|
68
|
+
# FlacInfo is the main class for parsing Flac files
|
69
|
+
#
|
70
|
+
# :call-seq:
|
71
|
+
# FlacInfo.new(file) -> FlacInfo instance
|
72
|
+
#
|
73
|
+
def initialize(filename)
|
74
|
+
@filename = filename
|
75
|
+
parse_flac_meta_blocks
|
76
|
+
end
|
77
|
+
|
78
|
+
# Returns true if @tags[<string>] has a value, false otherwise.
|
79
|
+
#
|
80
|
+
# :call-seq:
|
81
|
+
# FlacInfo.hastag?(tag) -> bool
|
82
|
+
#
|
83
|
+
def hastag?(tag)
|
84
|
+
@tags["#{tag}"] ? true : false
|
85
|
+
end
|
86
|
+
|
87
|
+
# Prettyprint comment hash.
|
88
|
+
#
|
89
|
+
# :call-seq:
|
90
|
+
# FlacInfo.print_tags -> nil
|
91
|
+
#
|
92
|
+
def print_tags
|
93
|
+
@tags.each_pair { |key,val| puts "#{key}: #{val}" }
|
94
|
+
nil
|
95
|
+
end
|
96
|
+
|
97
|
+
# Prettyprint streaminfo hash
|
98
|
+
#
|
99
|
+
# :call-seq:
|
100
|
+
# FlacInfo.print_streaminfo -> nil
|
101
|
+
#
|
102
|
+
def print_streaminfo
|
103
|
+
@streaminfo.each_pair { |key,val| puts "#{key}: #{val}" }
|
104
|
+
nil
|
105
|
+
end
|
106
|
+
|
107
|
+
# Prettyprint the seektable
|
108
|
+
#
|
109
|
+
# :call-seq:
|
110
|
+
# FlacInfo.print_seektable -> nil
|
111
|
+
#
|
112
|
+
def print_seektable
|
113
|
+
puts " seek points: #{@seektable['seek_points']}"
|
114
|
+
n = 0
|
115
|
+
@seektable['seek_points'].times do
|
116
|
+
print " point #{n}: sample number: #{@seektable['points'][n][0]}, "
|
117
|
+
print "stream offset: #{@seektable['points'][n][1]}, "
|
118
|
+
print "frame samples: #{@seektable['points'][n][2]}\n"
|
119
|
+
n += 1
|
120
|
+
end
|
121
|
+
nil
|
122
|
+
end
|
123
|
+
|
124
|
+
# This method produces output (mostly) identical to 'metaflac --list'
|
125
|
+
#
|
126
|
+
# :call-seq:
|
127
|
+
# FlacInfo.meta_flac -> nil
|
128
|
+
#
|
129
|
+
def meta_flac
|
130
|
+
n = 0
|
131
|
+
@metadata_blocks.each do |block|
|
132
|
+
puts "METADATA block ##{n}"
|
133
|
+
puts " type: #{block[1]} (#{block[0].upcase})"
|
134
|
+
puts " is last: #{block[2] == 0 ? "false" : "true"}"
|
135
|
+
case block[1]
|
136
|
+
when 0
|
137
|
+
meta_stream
|
138
|
+
when 1
|
139
|
+
meta_padd
|
140
|
+
when 2
|
141
|
+
meta_app
|
142
|
+
when 3
|
143
|
+
meta_seek
|
144
|
+
when 4
|
145
|
+
meta_vorb
|
146
|
+
when 5
|
147
|
+
meta_cue
|
148
|
+
end
|
149
|
+
n += 1
|
150
|
+
end
|
151
|
+
nil
|
152
|
+
end
|
153
|
+
|
154
|
+
# Dumps the contents of flac_file['raw_data']
|
155
|
+
#
|
156
|
+
# :call-seq:
|
157
|
+
# FlacInfo.raw_data_dump() -> nil
|
158
|
+
# FlacInfo.raw_data_dump(outfile) -> nil
|
159
|
+
#
|
160
|
+
# If passed with 'outfile', the data will be written to a file with that name
|
161
|
+
# otherwise it is written to the console (even if binary!).
|
162
|
+
#
|
163
|
+
def raw_data_dump(outfile = nil)
|
164
|
+
if @flac_file == {}
|
165
|
+
raise FlacInfoError, "Flac File data not present"
|
166
|
+
end
|
167
|
+
if outfile == nil
|
168
|
+
puts @flac_file['raw_data']
|
169
|
+
else
|
170
|
+
if @flac_file['mime_type'] =~ /text/
|
171
|
+
f = File.new(outfile, "w")
|
172
|
+
f.write(@flac_file['raw_data'])
|
173
|
+
f.close
|
174
|
+
else
|
175
|
+
f = File.new(outfile, "wb")
|
176
|
+
f.write(@flac_file['raw_data'])
|
177
|
+
f.close
|
178
|
+
end
|
179
|
+
end
|
180
|
+
end
|
181
|
+
|
182
|
+
|
183
|
+
private
|
184
|
+
# The following six methods are just helpers for meta_flac
|
185
|
+
def meta_stream
|
186
|
+
puts " length: #{@streaminfo['block_size']}"
|
187
|
+
puts " minumum blocksize: #{@streaminfo['minimum_block']} samples"
|
188
|
+
puts " maximum blocksize: #{@streaminfo['maximum_block']} samples"
|
189
|
+
puts " minimum framesize: #{@streaminfo['minimum_frame']} bytes"
|
190
|
+
puts " maximum framesize: #{@streaminfo['maximum_frame']} bytes"
|
191
|
+
puts " sample rate: #{@streaminfo['samplerate']} Hz"
|
192
|
+
puts " channels: #{@streaminfo['channels']}"
|
193
|
+
puts " bits-per-sample: #{@streaminfo['bits_per_sample']}"
|
194
|
+
puts " total samples: #{@streaminfo['total_samples']}"
|
195
|
+
puts " MD5 signature: #{@streaminfo['md5']}"
|
196
|
+
end
|
197
|
+
|
198
|
+
def meta_padd
|
199
|
+
puts " length: #{@padding['block_size']}"
|
200
|
+
end
|
201
|
+
|
202
|
+
def meta_app
|
203
|
+
puts " length: #{@application['block_size']}"
|
204
|
+
puts " id: #{@application['ID']}"
|
205
|
+
puts " application name: #{@application['name']}"
|
206
|
+
if @application['ID'] == "41544348"
|
207
|
+
puts " description: #{@flac_file['description']}"
|
208
|
+
puts " mime type: #{@flac_file['mime_type']}"
|
209
|
+
# Don't want to dump binary data
|
210
|
+
if @flac_file['mime_type'] =~ /text/
|
211
|
+
puts " raw data:"
|
212
|
+
puts @flac_file['raw_data']
|
213
|
+
else
|
214
|
+
puts "'Flac File' data may be binary. Use 'raw_data_dump' to see it"
|
215
|
+
end
|
216
|
+
else
|
217
|
+
puts " raw data"
|
218
|
+
puts @application['raw_data']
|
219
|
+
end
|
220
|
+
end
|
221
|
+
|
222
|
+
def meta_seek
|
223
|
+
puts " length: #{@seektable['block_size']}"
|
224
|
+
print_seektable
|
225
|
+
end
|
226
|
+
|
227
|
+
def meta_vorb
|
228
|
+
puts " length: #{@tags['block_size']}"
|
229
|
+
puts " length: #{@tags['vendor_tag']}"
|
230
|
+
puts " comments: #{@comment.size}"
|
231
|
+
n = 0
|
232
|
+
@comment.each do |c|
|
233
|
+
puts " comment[#{n}]: #{c}"
|
234
|
+
n += 1
|
235
|
+
end
|
236
|
+
end
|
237
|
+
|
238
|
+
def meta_cue
|
239
|
+
puts " length: #{@cuesheet['block_size']}"
|
240
|
+
end
|
241
|
+
|
242
|
+
# This is where the 'real' parsing starts.
|
243
|
+
def parse_flac_meta_blocks
|
244
|
+
@fp = File.new(@filename, "rb")
|
245
|
+
@streaminfo = {}
|
246
|
+
@comment = {}
|
247
|
+
@seektable = {}
|
248
|
+
@padding = {}
|
249
|
+
@application = {}
|
250
|
+
@cuesheet = {}
|
251
|
+
|
252
|
+
typetable = { 0 => "streaminfo", 1 => "padding", 2 => "application",
|
253
|
+
3 => "seektable", 4 => "vorbis_comment", 5 => "cuesheet" }
|
254
|
+
|
255
|
+
header = @fp.read(4)
|
256
|
+
# First 4 bytes must be 0x66, 0x4C, 0x61, and 0x43
|
257
|
+
if header != 'fLaC'
|
258
|
+
raise FlacInfoError, "#{@filename} does not appear to be a valid Flac file"
|
259
|
+
end
|
260
|
+
|
261
|
+
@metadata_blocks = []
|
262
|
+
lastheader = 0
|
263
|
+
n = 0
|
264
|
+
until lastheader == 1
|
265
|
+
# first bit = Last-metadata-block flag
|
266
|
+
# bits 2-7 = BLOCK_TYPE. See typetable above
|
267
|
+
block_header = @fp.read(1).unpack("B*")[0]
|
268
|
+
lastheader = block_header[0].to_i & 1
|
269
|
+
type = sprintf("%u", "0b#{block_header[1..7]}").to_i
|
270
|
+
@metadata_blocks[n] = ["#{typetable[type]}", type, lastheader]
|
271
|
+
self.send "parse_#{typetable[type]}"
|
272
|
+
n += 1
|
273
|
+
end
|
274
|
+
end
|
275
|
+
|
276
|
+
def parse_seektable
|
277
|
+
begin
|
278
|
+
@seektable['block_size'] = @fp.read(3).reverse.unpack("v*")[0]
|
279
|
+
@seektable['seek_points'] = @seektable['block_size'] / 18
|
280
|
+
n = 0
|
281
|
+
@seektable['points'] = {}
|
282
|
+
@seektable['seek_points'].times do
|
283
|
+
pt_arr = []
|
284
|
+
pt_arr << @fp.read(8).reverse.unpack("V*")[0]
|
285
|
+
pt_arr << @fp.read(8).reverse.unpack("V*")[0]
|
286
|
+
pt_arr << @fp.read(2).reverse.unpack("v*")[0]
|
287
|
+
@seektable['points'][n] = pt_arr
|
288
|
+
n += 1
|
289
|
+
end
|
290
|
+
rescue
|
291
|
+
raise FlacInfoError, "Could not parse seek table"
|
292
|
+
end
|
293
|
+
end
|
294
|
+
|
295
|
+
# Not parsed yet, I have no flacs with a cuesheet!
|
296
|
+
def parse_cuesheet
|
297
|
+
@cuesheet['block_size'] = @fp.read(3).reverse.unpack("v*")[0]
|
298
|
+
@fp.seek(@cuesheet['block_size'], IO::SEEK_CUR)
|
299
|
+
end
|
300
|
+
|
301
|
+
def parse_application
|
302
|
+
begin
|
303
|
+
@application['block_size'] = @fp.read(3).reverse.unpack("v*")[0]
|
304
|
+
@application['ID'] = @fp.read(4).unpack("H*")[0]
|
305
|
+
|
306
|
+
# See http://flac.sourceforge.net/id.html
|
307
|
+
app_id = {"41544348" => "Flac File", "43756573" => "GoldWave Cue Points",
|
308
|
+
"4D754D4C" => "MusicML", "46696361" => "CUE Splitter",
|
309
|
+
"46746F6C" => "flac-tools", "5346464C" => "Sound Font FLAC",
|
310
|
+
"7065656D" => "Parseable Embedded Extensible Metadata", "74756E65" => "TagTuner",
|
311
|
+
"786D6364" => "xmcd"}
|
312
|
+
|
313
|
+
@application['name'] = "#{app_id[@application['ID']]}"
|
314
|
+
|
315
|
+
# We only know how to parse data from 'Flac File'...
|
316
|
+
if @application['ID'] = "41544348"
|
317
|
+
parse_flac_file_contents(@application['block_size'] - 4)
|
318
|
+
else
|
319
|
+
@application['raw_data'] = @fp.read(@application['block_size'] - 4)
|
320
|
+
end
|
321
|
+
rescue
|
322
|
+
raise FlacInfoError, "Could not parse application block"
|
323
|
+
end
|
324
|
+
end
|
325
|
+
|
326
|
+
# Unlike most values in the Flac header
|
327
|
+
# the Vorbis comments are in LSB order
|
328
|
+
#
|
329
|
+
# @comment is an array of values according to the official spec implementation
|
330
|
+
# @tags is a more user-friendly data structure with the values
|
331
|
+
# separated into key=value pairs
|
332
|
+
def parse_vorbis_comment
|
333
|
+
begin
|
334
|
+
@tags = {}
|
335
|
+
@tags['block_size'] = @fp.read(3).reverse.unpack("v*")[0]
|
336
|
+
vendor_length = @fp.read(4).unpack("V")[0]
|
337
|
+
@tags['vendor_tag'] = @fp.read(vendor_length)
|
338
|
+
user_comment_list_length = @fp.read(4).unpack("V")[0]
|
339
|
+
@comment = []
|
340
|
+
n = 0
|
341
|
+
user_comment_list_length.times do
|
342
|
+
length = @fp.read(4).unpack("V")[0]
|
343
|
+
@comment[n] = @fp.read(length)
|
344
|
+
n += 1
|
345
|
+
end
|
346
|
+
@comment.each do |c|
|
347
|
+
k,v = c.split("=")
|
348
|
+
# Vorbis spec says we can have more than one identical comment ie:
|
349
|
+
# comment[0]="Artist=Charlie Parker"
|
350
|
+
# comment[1]="Artist=Miles Davis"
|
351
|
+
# so we just append the second and subsequent values to the first
|
352
|
+
if @tags.has_key?(k)
|
353
|
+
@tags[k] = "#{@tags[k]}, #{v}"
|
354
|
+
else
|
355
|
+
@tags[k] = v
|
356
|
+
end
|
357
|
+
end
|
358
|
+
rescue
|
359
|
+
raise FlacInfoError, "Could not parse Vorbis comments block"
|
360
|
+
end
|
361
|
+
end
|
362
|
+
|
363
|
+
# padding is just a bunch of '0' bytes
|
364
|
+
def parse_padding
|
365
|
+
@padding['block_size'] = @fp.read(3).reverse.unpack("v*")[0]
|
366
|
+
@fp.seek(@padding['block_size'], IO::SEEK_CUR)
|
367
|
+
end
|
368
|
+
|
369
|
+
def parse_streaminfo
|
370
|
+
begin
|
371
|
+
# Length (in bytes) of metadata to follow (not including header)
|
372
|
+
@streaminfo['block_size'] = @fp.read(3).reverse.unpack("v*")[0]
|
373
|
+
@streaminfo['minimum_block'] = @fp.read(2).reverse.unpack("v*")[0]
|
374
|
+
@streaminfo['maximum_block'] = @fp.read(2).reverse.unpack("v*")[0]
|
375
|
+
@streaminfo['minimum_frame'] = @fp.read(3).reverse.unpack("v*")[0]
|
376
|
+
@streaminfo['maximum_frame'] = @fp.read(3).reverse.unpack("v*")[0]
|
377
|
+
|
378
|
+
# 64 bits in MSB order
|
379
|
+
bitstring = @fp.read(8).unpack("B*")[0]
|
380
|
+
# 20 bits :: Sample rate in Hz.
|
381
|
+
@streaminfo['samplerate'] = sprintf("%u", "0b#{bitstring[0..19]}").to_i
|
382
|
+
# 3 bits :: (number of channels)-1
|
383
|
+
@streaminfo['channels'] = sprintf("%u", "0b#{bitstring[20..22]}").to_i + 1
|
384
|
+
# 5 bits :: (bits per sample)-1
|
385
|
+
@streaminfo['bits_per_sample'] = sprintf("%u", "0b#{bitstring[23..27]}").to_i + 1
|
386
|
+
# 36 bits :: Total samples in stream.
|
387
|
+
@streaminfo['total_samples'] = sprintf("%u", "0b#{bitstring[28..63]}").to_i
|
388
|
+
|
389
|
+
# 128 bits :: MD5 signature of the unencoded audio data.
|
390
|
+
@streaminfo['md5'] = @fp.read(16).unpack("H32")[0]
|
391
|
+
rescue
|
392
|
+
raise FlacInfoError, "Could not parse stream info block"
|
393
|
+
end
|
394
|
+
end
|
395
|
+
|
396
|
+
# See http://firestuff.org/flacfile/
|
397
|
+
def parse_flac_file_contents(size)
|
398
|
+
begin
|
399
|
+
@flac_file = {}
|
400
|
+
desc_length = @fp.read(1).unpack("C")[0]
|
401
|
+
@flac_file['description'] = @fp.read(desc_length)
|
402
|
+
mime_length = @fp.read(1).reverse.unpack("C")[0]
|
403
|
+
@flac_file['mime_type'] = @fp.read(mime_length)
|
404
|
+
size = size - 2 - desc_length - mime_length
|
405
|
+
@flac_file['raw_data'] = @fp.read(size)
|
406
|
+
rescue
|
407
|
+
raise FlacInfoError, "Could not parse Flac File data"
|
408
|
+
end
|
409
|
+
end
|
410
|
+
end
|
metadata
ADDED
@@ -0,0 +1,46 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
rubygems_version: 0.8.11
|
3
|
+
specification_version: 1
|
4
|
+
name: flacinfo-rb
|
5
|
+
version: !ruby/object:Gem::Version
|
6
|
+
version: "0.1"
|
7
|
+
date: 2006-09-18 00:00:00 -07:00
|
8
|
+
summary: Pure Ruby lib for accessing metadata (including Vorbis tags) from Flac files
|
9
|
+
require_paths:
|
10
|
+
- lib
|
11
|
+
email: bulliver@badcomputer.org
|
12
|
+
homepage: http://badcomputer.org/unix/code/flacinfo/
|
13
|
+
rubyforge_project:
|
14
|
+
description:
|
15
|
+
autorequire: flacinfo
|
16
|
+
default_executable:
|
17
|
+
bindir: bin
|
18
|
+
has_rdoc: true
|
19
|
+
required_ruby_version: !ruby/object:Gem::Version::Requirement
|
20
|
+
requirements:
|
21
|
+
- - ">"
|
22
|
+
- !ruby/object:Gem::Version
|
23
|
+
version: 0.0.0
|
24
|
+
version:
|
25
|
+
platform: ruby
|
26
|
+
signing_key:
|
27
|
+
cert_chain:
|
28
|
+
authors:
|
29
|
+
- Darren Kirby
|
30
|
+
files:
|
31
|
+
- README
|
32
|
+
- lib/flacinfo.rb
|
33
|
+
test_files: []
|
34
|
+
|
35
|
+
rdoc_options: []
|
36
|
+
|
37
|
+
extra_rdoc_files:
|
38
|
+
- README
|
39
|
+
executables: []
|
40
|
+
|
41
|
+
extensions: []
|
42
|
+
|
43
|
+
requirements: []
|
44
|
+
|
45
|
+
dependencies: []
|
46
|
+
|