Class: FormatParser::FLACParser

Inherits:
Object
  • Object
show all
Includes:
IOUtils
Defined in:
lib/parsers/flac_parser.rb

Constant Summary collapse

MAGIC_BYTES =
4
MAGIC_BYTE_STRING =
'fLaC'
BLOCK_HEADER_BYTES =
4

Instance Method Summary collapse

Methods included from IOUtils

#safe_read, #safe_skip

Instance Method Details

#bytestring_to_int(s) ⇒ Object



8
9
10
# File 'lib/parsers/flac_parser.rb', line 8

def bytestring_to_int(s)
  s.unpack('B*')[0].to_i(2)
end

#call(io) ⇒ Object

Raises:



12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
# File 'lib/parsers/flac_parser.rb', line 12

def call(io)
  magic_bytes = safe_read(io, MAGIC_BYTES)

  return unless magic_bytes == MAGIC_BYTE_STRING

  # Skip info we don't need
  safe_skip(io, BLOCK_HEADER_BYTES)

  minimum_block_size = bytestring_to_int(safe_read(io, 2))

  if minimum_block_size < 16
    raise MalformedFile, 'FLAC file minimum block size must be larger than 16'
  end

  maximum_block_size = bytestring_to_int(safe_read(io, 2))

  if maximum_block_size < minimum_block_size
    raise MalformedFile, 'FLAC file maximum block size must be equal to or larger than minimum block size'
  end

  minimum_frame_size = bytestring_to_int(safe_read(io, 3))
  maximum_frame_size = bytestring_to_int(safe_read(io, 3))

  # Audio info comes in irregularly sized (i.e. not 8-bit) chunks,
  # so read total as bitstring and parse separately
  audio_info = safe_read(io, 8).unpack('B*')[0]

  # sample rate is 20 bits
  sample_rate = audio_info.slice!(0..19).to_i(2)

  raise MalformedFile, 'FLAC file sample rate must be larger than 0' unless sample_rate > 0

  # Number of channels is 3 bits
  # Header contains number of channels minus one, so add one
  num_channels = audio_info.slice!(0..2).to_i(2) + 1

  # Bits per sample is 5 bits
  # Header contains number of bits per sample minus one, so add one
  bits_per_sample = audio_info.slice!(0..4).to_i(2) + 1

  # Total samples is 36 bits
  total_samples = audio_info.slice!(0..35).to_i(2)

  # Division is safe due to check above
  duration = total_samples.to_f / sample_rate

  FormatParser::Audio.new(
    format: :flac,
    num_audio_channels: num_channels,
    audio_sample_rate_hz: sample_rate,
    media_duration_seconds: duration,
    media_duration_frames: total_samples,
    intrinsics: {
      bits_per_sample: bits_per_sample,
      minimum_frame_size: minimum_frame_size,
      maximum_frame_size: maximum_frame_size,
      minimum_block_size: minimum_block_size,
      maximum_block_size: maximum_block_size
    }
  )
end