Skip to content

Commit

Permalink
More improvements on WAV parser (#246)
Browse files Browse the repository at this point in the history
* More improvements on WAV parser

* Calculate duration based on byte rate

* Adjust test

* Use sample_rate for determining duration

* Adjust test

* Bump version
  • Loading branch information
linkyndy authored Jul 12, 2024
1 parent b71b6f6 commit 3409b7e
Show file tree
Hide file tree
Showing 4 changed files with 16 additions and 15 deletions.
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
## 2.10.0
* Improve WAV parser by focusing on performance rather than on attempting a best-effort when extracting metadata from files that do not strictly follow the format spec.

## 2.9.0
* Improve WAV parser by performing a best-effort when extracting metadata from files that do not strictly follow the format spec.

Expand Down
2 changes: 1 addition & 1 deletion lib/format_parser/version.rb
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
module FormatParser
VERSION = '2.9.0'
VERSION = '2.10.0'
end
21 changes: 9 additions & 12 deletions lib/parsers/wav_parser.rb
Original file line number Diff line number Diff line change
Expand Up @@ -21,30 +21,27 @@ def call(io)
# The specification does not require the Format chunk to be the first chunk
# after the RIFF header.
# https://www.mmsp.ece.mcgill.ca/Documents/AudioFormats/WAVE/WAVE.html
fmt_processed = false
data_processed = false
fmt_data = {}
data_size = 0
total_sample_frames = nil
loop do
chunk_type, chunk_size = safe_read(io, 8).unpack('a4l')
case chunk_type
when 'fmt ' # watch out: the chunk ID of the format chunk ends with a space
fmt_data = unpack_fmt_chunk(io, chunk_size)
fmt_processed = true
when 'data'
data_size = chunk_size
when 'fact'
total_sample_frames = safe_read(io, 4).unpack('l').first
safe_skip(io, chunk_size - 4)
data_processed = true
else
# Skip this chunk until a known chunk is encountered
safe_skip(io, chunk_size)
end
rescue FormatParser::IOUtils::InvalidRead
# We've reached EOF, so it's time to make the most out of the metadata we
# managed to parse
break
break if fmt_processed && data_processed
end

file_info(fmt_data, data_size, total_sample_frames)
file_info(fmt_data, data_size)
end

def unpack_fmt_chunk(io, chunk_size)
Expand All @@ -70,10 +67,10 @@ def unpack_fmt_chunk(io, chunk_size)
}
end

def file_info(fmt_data, data_size, sample_frames)
def file_info(fmt_data, data_size)
# NOTE: Each sample includes information for each channel
sample_frames ||= data_size / (fmt_data[:channels] * fmt_data[:bits_per_sample] / 8) if fmt_data[:channels] > 0 && fmt_data[:bits_per_sample] > 0
duration_in_seconds = sample_frames / fmt_data[:sample_rate].to_f if fmt_data[:sample_rate] > 0
sample_frames = data_size / (fmt_data[:channels] * fmt_data[:bits_per_sample] / 8) if fmt_data[:channels] > 0 && fmt_data[:bits_per_sample] > 0
duration_in_seconds = sample_frames / fmt_data[:sample_rate].to_f if sample_frames && fmt_data[:byte_rate] > 0
FormatParser::Audio.new(
format: :wav,
num_audio_channels: fmt_data[:channels],
Expand Down
5 changes: 3 additions & 2 deletions spec/parsers/wav_parser_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,9 @@
expect(parse_result.format).to eq(:wav)
expect(parse_result.num_audio_channels).to eq(1)
expect(parse_result.audio_sample_rate_hz).to eq(8000)
expect(parse_result.media_duration_frames).to eq(110488)
expect(parse_result.media_duration_seconds).to be_within(0.01).of(13.81)
# Fixture does not define bits_per_sample in the fmt chunk
expect(parse_result.media_duration_frames).to be_nil
expect(parse_result.media_duration_seconds).to be_nil
end

it 'returns correct info about pcm files with more channels' do
Expand Down

0 comments on commit 3409b7e

Please sign in to comment.