Class: FlacInfo

Inherits:
Object
  • Object
show all
Defined in:
lib/flacinfo.rb

Overview

STREAMINFO is the only block guaranteed to be present in the Flac file. All attributes will be present but empty if the associated block is not present in the Flac file, except for ‘picture’ which will have the key ‘n’ with the value ‘0’. All ‘offset’ and ‘block_size’ values do not include the block header. All block headers are 4 bytes no matter the type, so if you need the offset including the header, subtract 4. If you need the size including the header, add 4.

Constant Summary collapse

STANDARD_FIELD_NAMES =

A list of ‘standard field names’ according to the Vorbis Comment specification. It is certainly possible to use a non-standard name, but the spec recommends against it. See: www.xiph.org/vorbis/doc/v-comment.html

%w[TITLE VERSION ALBUM TRACKNUMBER ARTIST PERFORMER COPYRIGHT LICENSE
ORGANIZATION DESCRIPTION GENRE DATE LOCATION CONTACT ISRC].freeze

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(filename) ⇒ FlacInfo

FlacInfo is the class for parsing Flac files.

:call-seq:

FlacInfo.new(file)   -> FlacInfo instance


143
144
145
146
# File 'lib/flacinfo.rb', line 143

def initialize(filename)
  @filename = filename
  parse_flac_meta_blocks
end

Instance Attribute Details

#applicationObject (readonly)

Hash of values extracted from the APPLICATION block. Keys are:

‘offset’

The APPLICATION block’s offset from the beginning of the file (not including the block header).

‘block_size’

The size of the APPLICATION block (not including the block header).

‘ID’

Registered application ID. See flac.sourceforge.net/id.html

‘name’

Name of the registered application ID.



102
103
104
# File 'lib/flacinfo.rb', line 102

def application
  @application
end

#commentObject

Array of “name=value” strings extracted from the VORBIS_COMMENT block. This is just the contents, metadata is in ‘tags’. You should not normally operate on this array directly. Rather, use the comment_add and comment_del methods to make changes.



89
90
91
# File 'lib/flacinfo.rb', line 89

def comment
  @comment
end

#cuesheetObject (readonly)

Hash of values extracted from the CUESHEET block. Keys are:

‘offset’

The CUESHEET block’s offset from the beginning of the file (not including the block header).

‘block_size’

The size of the CUESHEET block (not including the block header).



112
113
114
# File 'lib/flacinfo.rb', line 112

def cuesheet
  @cuesheet
end

#flac_fileObject (readonly)

Hash of values extracted from an APPLICATION block if it is type 0x41544348 (Flac File). Keys are:

‘description’

A brief text description of the contents.

‘mime_type’

The Mime type of the contents.

‘raw_data’

The contents. May be binary.



136
137
138
# File 'lib/flacinfo.rb', line 136

def flac_file
  @flac_file
end

#paddingObject (readonly)

Hash of values extracted from the PADDING block. Keys are:

‘offset’

The PADDING block’s offset from the beginning of the file (not including the block header).

‘block_size’

The size of the PADDING block (not including the block header).



107
108
109
# File 'lib/flacinfo.rb', line 107

def padding
  @padding
end

#pictureObject (readonly)

Hash of values extracted from one or more PICTURE blocks. This hash always includes the key ‘n’ which is the number of PICTURE blocks found, else ‘0’. For each block found there will be an integer key starting from 1. Each of these is a hash which contains the keys:

‘offset’

The PICTURE block’s offset from the beginning of the file (not including the block header).

‘block_size’

The size of the PICTURE block (not including the block header).

‘type_int’

The picture type according to the ID3v2 APIC frame.

‘type_string’

A text value representing the picture type.

‘description_string’

A text description of the picture.

‘mime_type’

The MIME type string. May be ‘–>’ to signify that the data part is a URL of the picture.

‘colour_depth’

The colour depth of the picture in bits-per-pixel.

‘n_colours’

For indexed-colour pictures (e.g. GIF), the number of colours used, or 0 for non-indexed pictures.

‘width’

The width of the picture in pixels.

‘height’

The height of the picture in pixels.

‘raw_data_offset’

The raw picture data’s offset from the beginning of the file.

‘raw_data_length’

The length of the picture data in bytes.



129
130
131
# File 'lib/flacinfo.rb', line 129

def picture
  @picture
end

#seektableObject (readonly)

Hash of values extracted from the SEEKTABLE block. Keys are:

‘offset’

The SEEKTABLE block’s offset from the beginning of the file (not including the block header).

‘block_size’

The size of the SEEKTABLE block (not including the block header).

‘seek_points’

The number of seek points in the block.

‘points’

Another hash whose keys start at 0 and end at (‘seek_points’ - 1). Each “seektable[n]” hash contains an array whose (integer) values are:

‘0’

Sample number of first sample in the target frame, or 0xFFFFFFFFFFFFFFFF for a placeholder point.

‘1’

Offset (in bytes) from the first byte of the first frame header to the first byte of the target frame’s header.

‘2’

Number of samples in the target frame.



85
86
87
# File 'lib/flacinfo.rb', line 85

def seektable
  @seektable
end

#streaminfoObject (readonly)

Hash of values extracted from the STREAMINFO block. Keys are:

‘offset’

The STREAMINFO block’s offset from the beginning of the file (not including the block header).

‘block_size’

The size of the STREAMINFO block (not including the block header).

‘minimum_block’

The minimum block size (in samples) used in the stream.

‘maximum_block’

The maximum block size (in samples) used in the stream.

‘minimum_frame’

The minimum frame size (in bytes) used in the stream.

‘maximum_frame’

The maximum frame size (in bytes) used in the stream.

‘samplerate’

Sample rate in Hz.

‘channels’

The number of channels used in the stream.

‘bits_per_sample’

The number of bits per sample used in the stream.

‘total_samples’

The total number of samples in stream.

‘md5’

MD5 signature of the unencoded audio data.



74
75
76
# File 'lib/flacinfo.rb', line 74

def streaminfo
  @streaminfo
end

#tagsObject (readonly)

Hash of the ‘comment’ values separated into “key => value” pairs as well as the keys:

‘offset’

The VORBIS_COMMENT block’s offset from the beginning of the file (not including the block header).

‘block_size’

The size of the VORBIS_COMMENT block (not including the block header).

‘vendor_tag’

Typically, the name and version of the software that encoded the file.



95
96
97
# File 'lib/flacinfo.rb', line 95

def tags
  @tags
end

Instance Method Details

#comment_add(name) ⇒ Object

Adds a new comment to the comment array

:call-seq:

FlacInfo.comment_add(str)   -> bool

‘str’ must be in the form ‘name=value’, or ‘name=’ if you want to set an empty value for a particular tag. Returns ‘true’ if successful, false otherwise. Remember to call ‘update!’ to write changes to the file.



337
338
339
340
341
342
343
344
345
346
347
348
349
# File 'lib/flacinfo.rb', line 337

def comment_add(name)
  if name !~ /\w=/ #  We accept 'name=' in case you want to leave the value empty
    raise FlacInfoError, "comments must be in the form 'name=value'"
  end

  begin
    @comment << name
    @comments_changed = 1
  rescue StandardError
    return false
  end
  true
end

#comment_del(name) ⇒ Object

Deletes a comment from the comment array

:call-seq:

FlacInfo.comment_del(str)   -> bool

If ‘str’ is in the form ‘name=value’ only exact matches will be deleted. If ‘str’ is in the form ‘name’ any and all comments named ‘name’ will be deleted. Returns ‘true’ if a comment was deleted, false otherwise. Remember to call ‘update!’ to write changes to the file.



362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
# File 'lib/flacinfo.rb', line 362

def comment_del(name)
  bc = Array.new(@comment) #  We need a copy
  nc = if name.include? '='
         @comment.delete_if { |x| x == name }
       else
         @comment.delete_if { |x| x.split('=')[0] == name }
       end

  if nc == bc
    false
  else
    @comments_changed = 1
    true
  end
end

#hastag?(tag) ⇒ Boolean

Returns true if @tags has a value, false otherwise.

:call-seq:

FlacInfo.hastag?(tag)   -> bool

Returns:

  • (Boolean)


153
154
155
# File 'lib/flacinfo.rb', line 153

def hastag?(tag)
  @tags[tag.to_s] ? true : false
end

#inspectObject

This cleans up the output when using FlacInfo in irb


422
423
424
425
426
427
428
# File 'lib/flacinfo.rb', line 422

def inspect # :nodoc:
  s = "#<#{self.class}:0x#{(object_id * 2).to_s(16)} "
  @metadata_blocks.each do |blk|
    s += "(#{blk[0].upcase} size=#{blk[4]} offset=#{blk[3]}) "
  end
  "#{s}\b>"
end

#meta_flacObject

This method produces output similar to ‘metaflac –list’.

:call-seq:

FlacInfo.meta_flac ->   nil


208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
# File 'lib/flacinfo.rb', line 208

def meta_flac
  n = 0
  pictures_seen = 0
  @metadata_blocks.each do |block|
    puts "METADATA block ##{n}"
    puts "  type: #{block[1]} (#{block[0].upcase})"
    puts "  is last: #{block[2].zero? ? 'false' : 'true'}"
    case block[1]
    when 0
      meta_stream
    when 1
      meta_pad
    when 2
      meta_app
    when 3
      meta_seek
    when 4
      meta_vorb
    when 5
      meta_cue
    when 6
      pictures_seen += 1
      meta_pict(pictures_seen)
    else
      # This will never run ... file already parsed.
      raise FlacInfoError, 'Unknown metadata blocktype found'
    end
    n += 1
  end
  nil
end

#padding_add!(size = 4096) ⇒ Object

Adds a padding block

:call-seq:

FlacInfo.padding_add!(size)   --> Boolean

‘size’ is an optional integer argument for the size of the padding block. It defaults to 4096 bytes. Returns true if successful, else false.



387
388
389
390
391
392
# File 'lib/flacinfo.rb', line 387

def padding_add!(size = 4096)
  @metadata_blocks.each do |type|
    raise FlacInfoError, "PADDING block exists. Use 'padding_resize!'" if type[0] == 'padding'
  end
  build_padding_block(size) ? true : false
end

#padding_del!Object

Removes a padding block

:call-seq:

FlacInfo.padding_del!()   --> Boolean

Returns true if the padding block is successfully removed else false.



402
403
404
# File 'lib/flacinfo.rb', line 402

def padding_del!
  remove_padding_block ? true : false
end

#padding_resize!(size = 4096) ⇒ Object

Resizes a padding block

:call-seq:

FlacInfo.padding_resize!(size)   --> Boolean

‘size’ is an optional integer argument for the size of the new padding block. It defaults to 4096 bytes. Returns true if successful, else false.



415
416
417
418
# File 'lib/flacinfo.rb', line 415

def padding_resize!(size = 4096)
  remove_padding_block
  build_padding_block(size)
end

Pretty print the seektable.

:call-seq:

FlacInfo.print_seektable   -> nil

Raises FlacInfoError if METADATA_BLOCK_SEEKTABLE is not present.

Raises:



189
190
191
192
193
194
195
196
197
198
199
200
201
# File 'lib/flacinfo.rb', line 189

def print_seektable
  raise FlacInfoError, 'METADATA_BLOCK_SEEKTABLE not present' if @seektable == {}

  puts "  seek points: #{@seektable['seek_points']}"
  n = 0
  @seektable['seek_points'].times do
    print "    point #{n}: sample number: #{@seektable['points'][n][0]}, "
    print "stream offset: #{@seektable['points'][n][1]}, "
    print "frame samples: #{@seektable['points'][n][2]}\n"
    n += 1
  end
  nil
end

Pretty print streaminfo hash.

:call-seq:

FlacInfo.print_streaminfo   -> nil


176
177
178
179
180
# File 'lib/flacinfo.rb', line 176

def print_streaminfo
  #  No test: METADATA_BLOCK_STREAMINFO must be present in valid Flac file
  @streaminfo.each_pair { |key, val| puts "#{key}: #{val}" }
  nil
end

Pretty print tags hash.

:call-seq:

FlacInfo.print_tags   -> nil

Raises FlacInfoError if METADATA_BLOCK_VORBIS_COMMENT is not present.

Raises:



164
165
166
167
168
169
# File 'lib/flacinfo.rb', line 164

def print_tags
  raise FlacInfoError, 'METADATA_BLOCK_VORBIS_COMMENT not present' if @tags == {}

  @tags.each_pair { |key, val| puts "#{key}: #{val}" }
  nil
end

#raw_data_dump(outfile = nil) ⇒ Object

Dumps the contents of flac_file

:call-seq:

FlacInfo.raw_data_dump()          -> nil
FlacInfo.raw_data_dump(outfile)   -> nil

If passed with ‘outfile’, the data will be written to a file with that name otherwise it is written to the console (even if binary!). Raises FlacInfoError if there is no Flac File data present.

Raises:



250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
# File 'lib/flacinfo.rb', line 250

def raw_data_dump(outfile = nil)
  raise FlacInfoError, 'Flac File data not present' if @flac_file == {}

  if outfile.nil?
    puts @flac_file['raw_data']
  else
    f = if @flac_file['mime_type'] =~ /text/
          File.new(outfile, 'w')
        else
          File.new(outfile, 'wb')
        end
    f.write(@flac_file['raw_data'])
    f.close
  end
end

#update!Object

Writes Vorbis tag changes to disk

:call-seq:

FlacInfo.update!   -> bool

Returns true if write was successful, false otherwise.



324
325
326
# File 'lib/flacinfo.rb', line 324

def update!
  write_to_disk ? true : false
end

#write_picture(args = {}) ⇒ Object

Writes embedded images to a file

:call-seq:

FlacInfo.write_picture()                           -> nil
FlacInfo.write_picture(:outfile=>"str")            -> nil
FlacInfo.write_picture(:n=>int)                    -> nil
FlacInfo.write_picture(:outfile=>"str", :n=>int)   -> nil

If passed with ‘:outfile’, the image will be written to a file with that name otherwise it is written to the value of the ‘album’ tag if it exists, otherwise it is written to ‘flacimage’. All three of these will have a dot plus the relevant file extension appended. The argument to ‘:n’ is which image to write in case of multiples.

Raises:



279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
# File 'lib/flacinfo.rb', line 279

def write_picture(args = {})
  raise FlacInfoError, 'There is no METADATA_BLOCK_PICTURE' if @picture['n'].zero?

  n = if args.key?(:n)
        args[:n]
      else
        1
      end

  #  "image/jpeg" => "jpeg"
  extension = @picture[n]['mime_type'].split('/')[1]

  outfile = if !args.key?(:outfile)
              if [nil, ''].include?(@tags['album'])
                "flacimage#{n}.#{extension}"
              else
                #  Try to use contents of "album" tag for the filename
                "#{@tags['album']}#{n}.#{extension}"
              end
            else
              "#{args[:outfile]}.#{extension}"
            end

  in_p  = File.new(@filename, 'rb')
  out_p = File.new(outfile, 'wb')

  out_p.binmode #  For Windows folks...

  in_p.seek(@picture[n]['raw_data_offset'], IO::SEEK_CUR)
  raw_data = in_p.read(@picture[n]['raw_data_length'])
  out_p.write(raw_data)

  in_p.close
  out_p.close

  nil
end