format_parser 0.1.0

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.
@@ -0,0 +1,25 @@
1
+ require 'spec_helper'
2
+
3
+ describe FormatParser::AIFFParser do
4
+ it 'parses an AIFF sample file' do
5
+ parse_result = subject.information_from_io(File.open(__dir__ + '/fixtures/AIFF/fixture.aiff', 'rb'))
6
+
7
+ expect(parse_result.file_nature).to eq(:audio)
8
+ expect(parse_result.file_type).to eq(:aiff)
9
+ expect(parse_result.media_duration_frames).to eq(46433)
10
+ expect(parse_result.num_audio_channels).to eq(2)
11
+ expect(parse_result.audio_sample_rate_hz).to be_within(0.01).of(44100)
12
+ expect(parse_result.media_duration_seconds).to be_within(0.01).of(1.05)
13
+ end
14
+
15
+ it 'parses a Logic Pro created AIFF sample file having a COMT chunk before a COMM chunk' do
16
+ parse_result = subject.information_from_io(File.open(__dir__ + '/fixtures/AIFF/fixture-logic-aiff.aif', 'rb'))
17
+
18
+ expect(parse_result.file_nature).to eq(:audio)
19
+ expect(parse_result.file_type).to eq(:aiff)
20
+ expect(parse_result.media_duration_frames).to eq(302400)
21
+ expect(parse_result.num_audio_channels).to eq(2)
22
+ expect(parse_result.audio_sample_rate_hz).to be_within(0.01).of(44100)
23
+ expect(parse_result.media_duration_seconds).to be_within(0.01).of(6.85)
24
+ end
25
+ end
data/spec/care_spec.rb ADDED
@@ -0,0 +1,77 @@
1
+ require 'spec_helper'
2
+
3
+ describe Care do
4
+ describe Care::Cache do
5
+ let(:source) { StringIO.new("Hello there, this is our little caching reader") }
6
+
7
+ it 'performs correct reads at various offsets' do
8
+ cache = Care::Cache.new(3)
9
+ expect(cache.byteslice(source, 0, 3)).to eq("Hel")
10
+ expect(cache.byteslice(source, 0, 7)).to eq("Hello t")
11
+ expect(cache.byteslice(source, 1, 7)).to eq("ello th")
12
+ expect(cache.byteslice(source, 11, 8)).to eq(", this i")
13
+ expect(cache.byteslice(source, 12, 12)).to eq(" this is our")
14
+ expect(cache.byteslice(source, 120, 12)).to be_nil
15
+ end
16
+
17
+ it 'can be cleared' do
18
+ cache = Care::Cache.new(3)
19
+ expect(cache.byteslice(source, 0, 3)).to eq("Hel")
20
+ expect(cache.instance_variable_get("@pages")).not_to be_empty
21
+ cache.clear
22
+ expect(cache.instance_variable_get("@pages")).to be_empty
23
+ end
24
+
25
+ it 'fits all the reads into one if the input fits into one page' do
26
+ expect(source).to receive(:read).at_most(2).times.and_call_original
27
+
28
+ cache = Care::Cache.new(source.size)
29
+
30
+ cache.byteslice(source, 0, 3)
31
+ cache.byteslice(source, 11, 8)
32
+ cache.byteslice(source, 190, 12)
33
+ cache.byteslice(source, 290, 1)
34
+ cache.byteslice(source, 890, 1)
35
+ end
36
+
37
+ it 'permits oversized reads' do
38
+ cache = Care::Cache.new(4)
39
+ expect(cache.byteslice(source, 0, 999)).to eq(source.string)
40
+ end
41
+
42
+ it 'returns nil with an empty input' do
43
+ cache = Care::Cache.new(4)
44
+ expect(cache.byteslice(StringIO.new(''), 0, 1)).to be_nil
45
+ end
46
+ end
47
+
48
+ describe Care::IOWrapper do
49
+ it 'forwards calls to read() to the Care and adjusts internal offsets' do
50
+ fake_cache_class = Class.new do
51
+ attr_reader :recorded_calls
52
+ def byteslice(io, at, n_bytes)
53
+ @recorded_calls ||= []
54
+ @recorded_calls << [io, at, n_bytes]
55
+ # Pretend reads always succeed and return the requisite number of bytes
56
+ "x" * n_bytes
57
+ end
58
+ end
59
+
60
+ cache_double = fake_cache_class.new
61
+ io_double = double('IO')
62
+
63
+ subject = Care::IOWrapper.new(io_double, cache_double)
64
+
65
+ subject.read(2)
66
+ subject.read(3)
67
+ subject.seek(11)
68
+ subject.read(5)
69
+
70
+ expect(cache_double.recorded_calls).to be_kind_of(Array)
71
+ first, second, third = *cache_double.recorded_calls
72
+ expect(first).to eq([io_double, 0, 2])
73
+ expect(second).to eq([io_double, 2, 3])
74
+ expect(third).to eq([io_double, 11, 5])
75
+ end
76
+ end
77
+ end
@@ -0,0 +1,16 @@
1
+ require 'spec_helper'
2
+
3
+ describe FormatParser::FileInformation do
4
+
5
+ context "File data checks" do
6
+ it 'succeeds with relevant attributes' do
7
+ result = described_class.new(file_nature: :image, file_type: :jpg, width_px: 42, height_px: 10, image_orientation: 1)
8
+ expect(result.file_nature).to eq(:image)
9
+ expect(result.file_type).to eq(:jpg)
10
+ expect(result.width_px).to eq(42)
11
+ expect(result.height_px).to eq(10)
12
+ expect(result.image_orientation).to eq(1)
13
+ end
14
+ end
15
+
16
+ end
@@ -0,0 +1,23 @@
1
+ require 'spec_helper'
2
+
3
+ describe FormatParser do
4
+ it 'returns nil when trying to parse an empty IO' do
5
+ d = StringIO.new('')
6
+ expect(FormatParser.parse(d)).to be_nil
7
+ end
8
+
9
+ it 'returns nil when parsing an IO no parser can make sense of' do
10
+ d = StringIO.new(Random.new.bytes(1))
11
+ expect(FormatParser.parse(d)).to be_nil
12
+ end
13
+
14
+ describe 'with fuzzing' do
15
+ it "returns either a valid result or a nil for all fuzzed inputs at seed #{RSpec.configuration.seed}" do
16
+ r = Random.new(RSpec.configuration.seed)
17
+ 1024.times do
18
+ random_blob = StringIO.new(r.bytes(512 * 1024))
19
+ FormatParser.parse(random_blob) # If there is an error in one of the parsers the example will raise too
20
+ end
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,42 @@
1
+ require 'spec_helper'
2
+
3
+ describe "IOUtils" do
4
+ let(:io) {File.open(fixtures_dir + '/test.jpg', 'rb')}
5
+ include FormatParser::IOUtils
6
+
7
+ describe '#safe_read' do
8
+ it 'raises if the requested bytes are past the EOF' do
9
+ io.seek(268118) # Seek to the actual end of the file
10
+ expect {
11
+ safe_read(io, 10)
12
+ }.to raise_error(FormatParser::IOUtils::InvalidRead)
13
+ end
14
+
15
+ it 'raises if we ask for more bytes than are available' do
16
+ expect {
17
+ safe_read(io, 1_000_000)
18
+ }.to raise_error(FormatParser::IOUtils::InvalidRead)
19
+ end
20
+ end
21
+
22
+ describe '#safe_skip' do
23
+ it 'raises on a negative skip byte amount' do
24
+ fake_io = double()
25
+ expect {
26
+ safe_skip(fake_io, -5)
27
+ }.to raise_error(FormatParser::IOUtils::InvalidRead)
28
+ end
29
+
30
+ it 'uses #pos if available on the object' do
31
+ fake_io = double(pos: 11)
32
+ expect(fake_io).to receive(:seek).with(11+5)
33
+ safe_skip(fake_io, 5)
34
+ end
35
+
36
+ it 'uses #read if no #pos is available on the object' do
37
+ fake_io = double()
38
+ expect(fake_io).to receive(:read).with(5).and_return('x' * 5)
39
+ safe_skip(fake_io, 5)
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,29 @@
1
+ require 'spec_helper'
2
+
3
+ describe FormatParser::DPXParser do
4
+ describe 'with Depix example files' do
5
+ Dir.glob(fixtures_dir + '/dpx/*.*').each do |dpx_path|
6
+ it "is able to parse #{File.basename(dpx_path)}" do
7
+ parsed = subject.information_from_io(File.open(dpx_path, 'rb'))
8
+
9
+ expect(parsed).not_to be_nil
10
+ expect(parsed.file_nature).to eq(:image)
11
+ expect(parsed.file_type).to eq(:dpx)
12
+
13
+ # If we have an error in the struct offsets these values are likely to become
14
+ # the maximum value of a 4-byte uint, which is way higher
15
+ expect(parsed.width_px).to be_kind_of(Integer)
16
+ expect(parsed.width_px).to be_between(0, 2048)
17
+ expect(parsed.height_px).to be_kind_of(Integer)
18
+ expect(parsed.height_px).to be_between(0, 4000)
19
+ end
20
+ end
21
+
22
+ it 'correctly reads pixel dimensions' do
23
+ fi = File.open(fixtures_dir + '/dpx/026_FROM_HERO_TAPE_5-3-1_MOV.0029.dpx', 'rb')
24
+ parsed = subject.information_from_io(fi)
25
+ expect(parsed.width_px).to eq(1920)
26
+ expect(parsed.height_px).to eq(1080)
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,45 @@
1
+ require 'spec_helper'
2
+
3
+ describe FormatParser::EXIFParser do
4
+
5
+ # ORIENTATIONS = [
6
+ # :top_left,
7
+ # :top_right,
8
+ # :bottom_right,
9
+ # :bottom_left,
10
+ # :left_top,
11
+ # :right_top,
12
+ # :right_bottom,
13
+ # :left_bottom
14
+ # ]
15
+
16
+ describe 'is able to correctly parse orientation for all the JPEG EXIF examples from FastImage' do
17
+ Dir.glob(fixtures_dir + '/exif-orientation-testimages/jpg/*.jpg').each do |jpeg_path|
18
+ filename = File.basename(jpeg_path)
19
+ it "is able to parse #{filename}" do
20
+ parser = FormatParser::EXIFParser.new(:jpeg, File.open(jpeg_path, 'rb'))
21
+ parser.scan_image_exif
22
+ expect(parser).not_to be_nil
23
+
24
+ expect(parser.orientation).to be_kind_of(Symbol)
25
+ # Filenames in this dir correspond with the orientation of the file
26
+ expect(filename.include?(parser.orientation.to_s)).to be true
27
+ end
28
+ end
29
+ end
30
+
31
+ describe 'is able to correctly parse orientation for all the TIFF EXIF examples from FastImage' do
32
+ Dir.glob(fixtures_dir + '/exif-orientation-testimages/tiff-*/*.tif').each do |tiff_path|
33
+ filename = File.basename(tiff_path)
34
+ it "is able to parse #{filename}" do
35
+ parser = FormatParser::EXIFParser.new(:tiff, File.open(tiff_path, 'rb'))
36
+ parser.scan_image_exif
37
+ expect(parser).not_to be_nil
38
+
39
+ expect(parser.orientation).to be_kind_of(Symbol)
40
+ # Filenames in this dir correspond with the orientation of the file
41
+ expect(filename.include?(parser.orientation.to_s)).to be true
42
+ end
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,35 @@
1
+ require 'spec_helper'
2
+
3
+ describe FormatParser::GIFParser do
4
+ describe 'is able to parse all the examples from FastImage' do
5
+ Dir.glob(fixtures_dir + '/*.gif').each do |gif_path|
6
+ it "is able to parse #{File.basename(gif_path)}" do
7
+ parsed = subject.information_from_io(File.open(gif_path, 'rb'))
8
+
9
+ expect(parsed).not_to be_nil
10
+
11
+ expect(parsed.file_nature).to eq(:image)
12
+ expect(parsed.file_type).to eq(:gif)
13
+ expect(parsed.color_mode).to eq(:indexed)
14
+
15
+ expect(parsed.width_px).to be_kind_of(Integer)
16
+ expect(parsed.width_px).to be > 0
17
+
18
+ expect(parsed.height_px).to be_kind_of(Integer)
19
+ expect(parsed.height_px).to be > 0
20
+ end
21
+ end
22
+ end
23
+
24
+ describe 'is able to correctly parse our own examples' do
25
+ it 'is able to parse the animated GIF' do
26
+ gif_path = fixtures_dir + "GIF/anim.gif"
27
+
28
+ parsed = subject.information_from_io(File.open(gif_path, 'rb'))
29
+ expect(parsed).not_to be_nil
30
+
31
+ expect(parsed.width_px).to eq(320)
32
+ expect(parsed.height_px).to eq(180)
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,36 @@
1
+ require 'spec_helper'
2
+
3
+ describe FormatParser::JPEGParser do
4
+ describe 'is able to parse all the examples from FastImage' do
5
+ Dir.glob(fixtures_dir + '/*.jpg').each do |jpeg_path|
6
+ it "is able to parse #{File.basename(jpeg_path)}" do
7
+ parsed = subject.information_from_io(File.open(jpeg_path, 'rb'))
8
+ expect(parsed).not_to be_nil
9
+ expect(parsed.file_nature).to eq(:image)
10
+ expect(parsed.file_type).to eq(:jpg)
11
+
12
+ expect(parsed.width_px).to be_kind_of(Integer)
13
+ expect(parsed.width_px).to be > 0
14
+
15
+ expect(parsed.height_px).to be_kind_of(Integer)
16
+ expect(parsed.height_px).to be > 0
17
+ end
18
+ end
19
+ end
20
+
21
+ describe 'is able to parse all the JPEG exif examples from FastImage' do
22
+ Dir.glob(fixtures_dir + '/exif-orientation-testimages/jpg/*.jpg').each do |jpeg_path|
23
+ it "is able to parse #{File.basename(jpeg_path)}" do
24
+ parsed = subject.information_from_io(File.open(jpeg_path, 'rb'))
25
+ expect(parsed).not_to be_nil
26
+
27
+ expect(parsed.orientation).to be_kind_of(Symbol)
28
+ expect(parsed.width_px).to be > 0
29
+
30
+ expect(parsed.height_px).to be_kind_of(Integer)
31
+ expect(parsed.height_px).to be > 0
32
+ end
33
+ end
34
+ end
35
+
36
+ end
@@ -0,0 +1,33 @@
1
+ require 'spec_helper'
2
+
3
+ describe FormatParser::PNGParser do
4
+ describe 'is able to parse all the examples from FastImage' do
5
+ Dir.glob(fixtures_dir + '/*.png').each do |png_path|
6
+ it "is able to parse #{File.basename(png_path)}" do
7
+ parsed = subject.information_from_io(File.open(png_path, 'rb'))
8
+ expect(parsed).not_to be_nil
9
+ expect(parsed.file_nature).to eq(:image)
10
+ expect(parsed.file_type).to eq(:png)
11
+ expect(parsed.color_mode).to eq(:indexed)
12
+
13
+ expect(parsed.width_px).to be_kind_of(Integer)
14
+ expect(parsed.width_px).to be > 0
15
+
16
+ expect(parsed.height_px).to be_kind_of(Integer)
17
+ expect(parsed.height_px).to be > 0
18
+ end
19
+ end
20
+ end
21
+
22
+ it 'is able to parse an animated PNG' do
23
+ gif_path = fixtures_dir + "PNG/anim.png"
24
+
25
+ parsed = subject.information_from_io(File.open(gif_path, 'rb'))
26
+ expect(parsed).not_to be_nil
27
+
28
+ expect(parsed.width_px).to eq(320)
29
+ expect(parsed.height_px).to eq(180)
30
+ expect(parsed.has_multiple_frames).to eq(true)
31
+ expect(parsed.num_animation_or_video_frames).to eq(17)
32
+ end
33
+ end
@@ -0,0 +1,21 @@
1
+ require 'spec_helper'
2
+
3
+ describe FormatParser::PSDParser do
4
+ describe 'is able to parse all the examples from FastImage' do
5
+ Dir.glob(fixtures_dir + '/*.psd').each do |psd_path|
6
+ it "is able to parse #{File.basename(psd_path)}" do
7
+ parsed = subject.information_from_io(File.open(psd_path, 'rb'))
8
+
9
+ expect(parsed).not_to be_nil
10
+ expect(parsed.file_nature).to eq(:image)
11
+ expect(parsed.file_type).to eq(:psd)
12
+
13
+ expect(parsed.width_px).to be_kind_of(Integer)
14
+ expect(parsed.width_px).to be > 0
15
+
16
+ expect(parsed.height_px).to be_kind_of(Integer)
17
+ expect(parsed.height_px).to be > 0
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,37 @@
1
+ require 'spec_helper'
2
+
3
+ describe FormatParser::TIFFParser do
4
+ describe 'is able to parse all the examples from FastImage' do
5
+ Dir.glob(fixtures_dir + '/TIFF/*.tif').each do |tiff_path|
6
+ it "is able to parse #{File.basename(tiff_path)}" do
7
+ parsed = subject.information_from_io(File.open(tiff_path, 'rb'))
8
+
9
+ expect(parsed).not_to be_nil
10
+ expect(parsed.file_nature).to eq(:image)
11
+ expect(parsed.file_type).to eq(:tif)
12
+
13
+ expect(parsed.width_px).to be_kind_of(Integer)
14
+ expect(parsed.width_px).to be > 0
15
+
16
+ expect(parsed.height_px).to be_kind_of(Integer)
17
+ expect(parsed.height_px).to be > 0
18
+ end
19
+ end
20
+ end
21
+
22
+ describe 'is able to parse all the TIFF exif examples from FastImage' do
23
+ Dir.glob(fixtures_dir + '/exif-orientation-testimages/tiff-*/*.tif').each do |tiff_path|
24
+ it "is able to parse #{File.basename(tiff_path)}" do
25
+ parsed = subject.information_from_io(File.open(tiff_path, 'rb'))
26
+ expect(parsed).not_to be_nil
27
+
28
+ expect(parsed.orientation).to be_kind_of(Symbol)
29
+ expect(parsed.width_px).to be > 0
30
+
31
+ expect(parsed.height_px).to be_kind_of(Integer)
32
+ expect(parsed.height_px).to be > 0
33
+ end
34
+ end
35
+ end
36
+
37
+ end
@@ -0,0 +1,35 @@
1
+ require 'spec_helper'
2
+
3
+ describe "ReadLimiter" do
4
+ let(:io) { StringIO.new(Random.new.bytes(1024)) }
5
+
6
+ it 'does not enforce any limits with default arguments' do
7
+ reader = FormatParser::ReadLimiter.new(io)
8
+ 2048.times { reader.seek(1) }
9
+ 2048.times { reader.read(4) }
10
+ end
11
+
12
+ it 'enforces the number of seeks' do
13
+ reader = FormatParser::ReadLimiter.new(io, max_seeks: 4)
14
+ 4.times { reader.seek(1) }
15
+ expect {
16
+ reader.seek(1)
17
+ }.to raise_error(/Seek budget exceeded/)
18
+ end
19
+
20
+ it 'enforces the number of reads' do
21
+ reader = FormatParser::ReadLimiter.new(io, max_reads: 4)
22
+ 4.times { reader.read(1) }
23
+ expect {
24
+ reader.read(1)
25
+ }.to raise_error(/calls exceeded \(4 max\)/)
26
+ end
27
+
28
+ it 'enforces the number of bytes read' do
29
+ reader = FormatParser::ReadLimiter.new(io, max_bytes: 512)
30
+ reader.read(512)
31
+ expect {
32
+ reader.read(1)
33
+ }.to raise_error(/bytes budget \(512\) exceeded/)
34
+ end
35
+ end