format_parser 1.5.0 → 1.7.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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: f3f9d1c51523af6efea2ed6f8cf63f0f573e2b0b140c1af0435e6bb26961953c
4
- data.tar.gz: 84b73895b8924a0cffa286a4adaa3c270951e4074ec09db718f1a8d482b8e14b
3
+ metadata.gz: 8b3cef665ae16efd68e8da952fd4656e2d9403f3899bd58839da3d8026db91f4
4
+ data.tar.gz: 7b0ec88efc2ea62f526699a4041cb3f1b3062994d2a6e0b24c2cfdf247aaf532
5
5
  SHA512:
6
- metadata.gz: cfa0a69fd35d8d3c05fff79cca1b550af8b8c6876fece5443fc41f7967fed15fe20d494ce5678e16cb2a1cdd1b0918b8ce61ef4b731f3e47b18c736ca21cdf0c
7
- data.tar.gz: 0f7747775606681981367432322bb7b63e2e3c862fcd506d0b843bfc2280ae3d8dcdf334f49a9150f27a181329cf993f9cf79036d8124e75d41ca01f26edebd6
6
+ metadata.gz: 24c6379ef4fd3b5a9f061c6fc40fd8c0498ad33213684d08dd27a8b8994ba40a98bf1fa18a6d6b3b8189aa71436ec9bb394e3b8d41a8dd3ca90a5b93d0f1718a
7
+ data.tar.gz: a2d3df2c17d2559aa99f52f04624032a9243915f2a1b28a6f3626bd3b9112eb8c325b0c9a286864d25c2b4e92a44a8939448a85d1004ac5d48c2f81f747749c1
data/CHANGELOG.md CHANGED
@@ -1,3 +1,9 @@
1
+ ## 1.7.0
2
+ * Add support for `ARW` files.
3
+
4
+ ## 1.6.0
5
+ * Add support for `AAC` files.
6
+
1
7
  ## 1.5.0
2
8
  * Add support for `NEF` files.
3
9
 
data/README.md CHANGED
@@ -149,6 +149,14 @@ Unless specified otherwise in this section the fixture files are MIT licensed an
149
149
  ### MP3
150
150
  - Cassy.mp3 has been produced by WeTransfer and may be used with the library for the purposes of testing
151
151
 
152
+ ### AAC
153
+ - Originals music files: “Furious Freak” and “Galway”, Kevin MacLeod (incompetech.com), Licensed under Creative Commons: By Attribution 3.0, http://creativecommons.org/licenses/by/3.0/
154
+ - The AAC samples were converted from 'wav' format and made available [here](https://espressif-docs.readthedocs-hosted.com/projects/esp-adf/en/latest/design-guide/audio-samples.html) by Espressif Systems, as part of their audio development framework (under the ESPRESSIF MIT License).
155
+ - Files:
156
+ - ff-16b-2c-44100hz.aac
157
+ - ff-16b-1c-44100hz.aac
158
+ - gs-16b-2c-44100hz.aac
159
+ - gs-16b-1c-44100hz.aac
152
160
  ### FDX
153
161
  - fixture.fdx was created by one of the project maintainers and is MIT licensed
154
162
 
@@ -186,7 +194,7 @@ Unless specified otherwise in this section the fixture files are MIT licensed an
186
194
  - `IMG_9266_*.tif` and all it's variations were created by the project maintainers
187
195
 
188
196
  ### ARW
189
- - ARW example is downloaded from http://www.rawsamples.ch/ and is Creative Common Licensed.
197
+ - ARW examples are downloaded from http://www.rawsamples.ch/ and are Creative Common Licensed.
190
198
 
191
199
  ### ZIP
192
200
  - The .zip fixture files have been created by the project maintainers
@@ -1,3 +1,3 @@
1
1
  module FormatParser
2
- VERSION = '1.5.0'
2
+ VERSION = '1.7.0'
3
3
  end
@@ -0,0 +1,138 @@
1
+ # This is a representation of the relevant information found in an Audio Data Transport Stream (ADTS) file header.
2
+ class FormatParser::AdtsHeaderInfo
3
+ attr_accessor :mpeg_version, :layer, :protection_absence, :profile, :mpeg4_sampling_frequency_index,
4
+ :mpeg4_channel_config, :originality, :home_usage, :frame_length, :buffer_fullness,
5
+ :aac_frames_per_adts_frame
6
+
7
+ # An ADTS header has the following format, when represented in bits:
8
+ # AAAAAAAA AAAABCCD EEFFFFGH HHIJKLMM MMMMMMMM MMMOOOOO OOOOOOPP (QQQQQQQQ QQQQQQQQ)
9
+ # The chunks represented by these letters have specific meanings, as described here:
10
+ # https://wiki.multimedia.cx/index.php/ADTS
11
+
12
+ AAC_ADTS_HEADER_BITS_CHUNK_SIZES = [
13
+ ['A', 12], ['B', 1], ['C', 2], ['D', 1],
14
+ ['E', 2], ['F', 4], ['G', 1], ['H', 3],
15
+ ['I', 1], ['J', 1], ['K', 1], ['L', 1],
16
+ ['M', 13], ['O', 11], ['P', 2], ['Q', 16]
17
+ ]
18
+ MPEG4_AUDIO_OBJECT_TYPE_RANGE = 0..45
19
+ MPEG4_AUDIO_SAMPLING_FREQUENCY_RANGE = 0..14
20
+ MPEG4_AUDIO_SAMPLING_FREQUENCY_HASH = {
21
+ 0 => 96000, 1 => 88200, 2 => 64000,
22
+ 3 => 48000, 4 => 44100, 5 => 32000,
23
+ 6 => 24000, 7 => 22050, 8 => 16000,
24
+ 9 => 12000, 10 => 11025, 11 => 8000,
25
+ 12 => 7350, 13 => 'Reserved', 14 => 'Reserved'
26
+ }
27
+ AAC_PROFILE_DESCRIPTION_HASH = {
28
+ 0 => 'AAC_MAIN',
29
+ 1 => 'AAC_LC (Low Complexity)',
30
+ 2 => 'AAC_SSR (Scaleable Sampling Rate)',
31
+ 3 => 'AAC_LTP (Long Term Prediction)'
32
+ }
33
+ MPEG_VERSION_HASH = { 0 => 'MPEG-4', 1 => 'MPEG-2'}
34
+
35
+ def mpeg4_sampling_frequency
36
+ if !@mpeg4_sampling_frequency_index.nil? && MPEG4_AUDIO_SAMPLING_FREQUENCY_HASH.key?(@mpeg4_sampling_frequency_index)
37
+ return MPEG4_AUDIO_SAMPLING_FREQUENCY_HASH[@mpeg4_sampling_frequency_index]
38
+ end
39
+ nil
40
+ end
41
+
42
+ def profile_description
43
+ if !@profile.nil? && AAC_PROFILE_DESCRIPTION_HASH.key?(@profile)
44
+ return AAC_PROFILE_DESCRIPTION_HASH[@profile]
45
+ end
46
+ nil
47
+ end
48
+
49
+ def mpeg_version_description
50
+ if !@mpeg_version.nil? && MPEG_VERSION_HASH.key?(@mpeg_version)
51
+ return MPEG_VERSION_HASH[@mpeg_version]
52
+ end
53
+ nil
54
+ end
55
+
56
+ def number_of_audio_channels
57
+ case @mpeg4_channel_config
58
+ when 1..6
59
+ @mpeg4_channel_config
60
+ when 7
61
+ 8
62
+ else
63
+ nil
64
+ end
65
+ end
66
+
67
+ def fixed_bitrate?
68
+ # A buffer fullness value of 0x7FF (decimal: 2047) denotes a variable bitrate, for which buffer fullness isn't applicable
69
+ @buffer_fullness != 2047
70
+ end
71
+
72
+ # The frame rate - i.e. frames per second
73
+ def frame_rate
74
+ # An AAC sample uncompresses to 1024 PCM samples
75
+ mpeg4_sampling_frequency.to_f / 1024
76
+ end
77
+
78
+ # If the given bit array is a valid ADTS header, this method will parse it and return an instance of AdtsHeaderInfo.
79
+ # Will return nil if the header does not match the ADTS specifications.
80
+ def self.parse_adts_header(header_bits)
81
+ result = FormatParser::AdtsHeaderInfo.new
82
+
83
+ AAC_ADTS_HEADER_BITS_CHUNK_SIZES.each do |letter_size|
84
+ letter = letter_size[0]
85
+ chunk_size = letter_size[1]
86
+ chunk = header_bits.shift(chunk_size)
87
+ decimal_number = chunk.join.to_i(2)
88
+
89
+ # Skipping data represented by the letters G, K, L, Q, as we are not interested in those values.
90
+ case letter
91
+ when 'A'
92
+ # Syncword, all bits must be set to 1
93
+ return nil unless chunk.all? { |bit| bit == '1' }
94
+ when 'B'
95
+ # MPEG Version, set to 0 for MPEG-4 and 1 for MPEG-2
96
+ result.mpeg_version = decimal_number
97
+ when 'C'
98
+ # Layer, always set to 0
99
+ return nil unless decimal_number == 0
100
+ when 'D'
101
+ # Protection absence, set to 1 if there is no CRC and 0 if there is CRC
102
+ result.protection_absence = decimal_number == 1
103
+ when 'E'
104
+ # AAC Profile
105
+ return nil unless MPEG4_AUDIO_OBJECT_TYPE_RANGE.include?(decimal_number + 1)
106
+ result.profile = decimal_number
107
+ when 'F'
108
+ # MPEG-4 Sampling Frequency Index (15 is forbidden)
109
+ return nil unless MPEG4_AUDIO_SAMPLING_FREQUENCY_RANGE.include?(decimal_number)
110
+ result.mpeg4_sampling_frequency_index = decimal_number
111
+ when 'H'
112
+ # MPEG-4 Channel Configuration (in the case of 0, the channel configuration is sent via an in-band PCE (Program Config Element))
113
+ result.mpeg4_channel_config = decimal_number
114
+ when 'I'
115
+ # Originality, set to 1 to signal originality of the audio and 0 otherwise
116
+ result.originality = decimal_number == 1
117
+ when 'J'
118
+ # Home, set to 1 to signal home usage of the audio and 0 otherwise
119
+ result.home_usage = decimal_number == 1
120
+ when 'M'
121
+ # Frame length, length of the ADTS frame including headers and CRC check (protectionabsent == 1? 7: 9)
122
+ # We expect this to be higher than the header length, but we won't impose any other restrictions
123
+ header_length = result.protection_absence ? 7 : 9
124
+ return nil unless decimal_number > header_length
125
+ result.frame_length = decimal_number
126
+ when 'O'
127
+ # Buffer fullness, states the bit-reservoir per frame.
128
+ # It is merely an informative field with no clear use case defined in the specification.
129
+ result.buffer_fullness = decimal_number
130
+ when 'P'
131
+ # Number of AAC frames (RDBs (Raw Data Blocks)) in ADTS frame minus 1. For maximum compatibility always use one AAC frame per ADTS frame.
132
+ result.aac_frames_per_adts_frame = decimal_number + 1
133
+ end
134
+ end
135
+
136
+ result
137
+ end
138
+ end
@@ -0,0 +1,35 @@
1
+ require_relative 'aac_parser/adts_header_info'
2
+
3
+ class FormatParser::AACParser
4
+ include FormatParser::IOUtils
5
+
6
+ AAC_MIME_TYPE = 'audio/aac'
7
+
8
+ def likely_match?(filename)
9
+ filename =~ /\.aac$/i
10
+ end
11
+
12
+ def call(raw_io)
13
+ io = FormatParser::IOConstraint.new(raw_io)
14
+ header = safe_read(io, 9)
15
+ header_bits = header.unpack('B*').first.split('')
16
+
17
+ header_info = FormatParser::AdtsHeaderInfo.parse_adts_header(header_bits)
18
+ return if header_info.nil?
19
+
20
+ FormatParser::Audio.new(
21
+ title: nil,
22
+ album: nil,
23
+ artist: nil,
24
+ format: :aac,
25
+ num_audio_channels: header_info.number_of_audio_channels,
26
+ audio_sample_rate_hz: header_info.mpeg4_sampling_frequency,
27
+ media_duration_seconds: nil,
28
+ media_duration_frames: nil,
29
+ intrinsics: nil,
30
+ content_type: AAC_MIME_TYPE
31
+ )
32
+ end
33
+
34
+ FormatParser.register_parser new, natures: :audio, formats: :aac
35
+ end
@@ -0,0 +1,50 @@
1
+ require_relative 'exif_parser'
2
+
3
+ class FormatParser::ARWParser
4
+ include FormatParser::IOUtils
5
+ include FormatParser::EXIFParser
6
+
7
+ # Standard TIFF headers
8
+ MAGIC_LE = [0x49, 0x49, 0x2A, 0x0].pack('C4')
9
+ MAGIC_BE = [0x4D, 0x4D, 0x0, 0x2A].pack('C4')
10
+ HEADER_BYTES = [MAGIC_LE, MAGIC_BE]
11
+ ARW_MIME_TYPE = 'image/x-sony-arw'
12
+
13
+ def likely_match?(filename)
14
+ filename =~ /\.arw$/i
15
+ end
16
+
17
+ def call(io)
18
+ io = FormatParser::IOConstraint.new(io)
19
+
20
+ return unless HEADER_BYTES.include?(safe_read(io, 4))
21
+ exif_data = exif_from_tiff_io(io)
22
+
23
+ return unless valid?(exif_data)
24
+
25
+ w = exif_data.width || exif_data.pixel_x_dimension
26
+ h = exif_data.height || exif_data.pixel_y_dimension
27
+
28
+ FormatParser::Image.new(
29
+ format: :arw,
30
+ width_px: w,
31
+ height_px: h,
32
+ display_width_px: exif_data.rotated? ? h : w,
33
+ display_height_px: exif_data.rotated? ? w : h,
34
+ orientation: exif_data.orientation_sym,
35
+ intrinsics: { exif: exif_data },
36
+ content_type: ARW_MIME_TYPE,
37
+ )
38
+ rescue EXIFR::MalformedTIFF
39
+ nil
40
+ end
41
+
42
+ def valid?(exif_data)
43
+ # taken directly from tiff_parser.rb
44
+ # Similar to how exiftool determines the image type as ARW, we are implementing a check here
45
+ # https://github.com/exiftool/exiftool/blob/e969456372fbaf4b980fea8bb094d71033ac8bf7/lib/Image/ExifTool/Exif.pm#L929
46
+ exif_data.compression == 6 && exif_data.new_subfile_type == 1 && exif_data.make&.start_with?('SONY')
47
+ end
48
+
49
+ FormatParser.register_parser new, natures: :image, formats: :arw
50
+ end
@@ -6,7 +6,6 @@ class FormatParser::TIFFParser
6
6
  MAGIC_BE = [0x4D, 0x4D, 0x0, 0x2A].pack('C4')
7
7
  HEADER_BYTES = [MAGIC_LE, MAGIC_BE]
8
8
  TIFF_MIME_TYPE = 'image/tiff'
9
- ARW_MIME_TYPE = 'image/x-sony-arw'
10
9
 
11
10
  def likely_match?(filename)
12
11
  filename =~ /\.tiff?$/i
@@ -28,20 +27,20 @@ class FormatParser::TIFFParser
28
27
  exif_data = exif_from_tiff_io(io)
29
28
  return unless exif_data
30
29
 
30
+ return if arw?(exif_data)
31
+
31
32
  w = exif_data.width || exif_data.pixel_x_dimension
32
33
  h = exif_data.height || exif_data.pixel_y_dimension
33
34
 
34
- format = arw?(exif_data) ? :arw : :tif
35
- mime_type = arw?(exif_data) ? ARW_MIME_TYPE : TIFF_MIME_TYPE
36
35
  FormatParser::Image.new(
37
- format: format,
36
+ format: :tif,
38
37
  width_px: w,
39
38
  height_px: h,
40
39
  display_width_px: exif_data.rotated? ? h : w,
41
40
  display_height_px: exif_data.rotated? ? w : h,
42
41
  orientation: exif_data.orientation_sym,
43
42
  intrinsics: {exif: exif_data},
44
- content_type: mime_type,
43
+ content_type: TIFF_MIME_TYPE,
45
44
  )
46
45
  rescue EXIFR::MalformedTIFF
47
46
  nil
@@ -55,7 +54,7 @@ class FormatParser::TIFFParser
55
54
  # Similar to how exiftool determines the image type as ARW, we are implementing a check here
56
55
  # https://github.com/exiftool/exiftool/blob/e969456372fbaf4b980fea8bb094d71033ac8bf7/lib/Image/ExifTool/Exif.pm#L929
57
56
  def arw?(exif_data)
58
- exif_data.compression == 6 && exif_data.new_subfile_type == 1 && exif_data.make == 'SONY'
57
+ exif_data.compression == 6 && exif_data.new_subfile_type == 1 && exif_data.make&.start_with?('SONY')
59
58
  end
60
59
 
61
60
  FormatParser.register_parser new, natures: :image, formats: :tif
@@ -0,0 +1,87 @@
1
+ require 'spec_helper'
2
+
3
+ describe FormatParser::AACParser do
4
+ it 'should match filenames with valid AAC extensions' do
5
+ filenames = ['audiofile', 'audio_file', 'audio-file', 'audio file', 'audio.file']
6
+ extensions = ['.aac', '.AAC', '.Aac', '.AAc', '.aAc', '.aAC', '.aaC']
7
+ filenames.each do |filename|
8
+ extensions.each do |extension|
9
+ expect(subject.likely_match?(filename + extension)).to be_truthy
10
+ end
11
+ end
12
+ end
13
+
14
+ it 'should not match filenames with invalid AAC extensions' do
15
+ extensions = ['.aa', '.ac', '.acc', '.mp3', '.ogg', '.wav', '.flac', '.m4a', '.m4b', '.m4p', '.m4r', '.3gp']
16
+ extensions.each do |extension|
17
+ expect(subject.likely_match?('audiofile' + extension)).to be_falsey
18
+ end
19
+ end
20
+
21
+ it 'should parse a short sample, single channel audio, 16 kb/s, 44100 HZ' do
22
+ file_path = fixtures_dir + '/AAC/gs-16b-1c-44100hz.aac'
23
+ parsed = subject.call(File.open(file_path, 'rb'))
24
+
25
+ expect(parsed).not_to be_nil
26
+
27
+ expect(parsed.nature).to eq(:audio)
28
+ expect(parsed.format).to eq(:aac)
29
+ expect(parsed.num_audio_channels).to eq(1)
30
+ expect(parsed.audio_sample_rate_hz).to eq(44100)
31
+ expect(parsed.content_type).to eq('audio/aac')
32
+ end
33
+
34
+ it 'should parse a short sample, two channel audio, 16 kb/s, 44100 HZ' do
35
+ file_path = fixtures_dir + '/AAC/gs-16b-2c-44100hz.aac'
36
+ parsed = subject.call(File.open(file_path, 'rb'))
37
+
38
+ expect(parsed).not_to be_nil
39
+
40
+ expect(parsed.nature).to eq(:audio)
41
+ expect(parsed.format).to eq(:aac)
42
+ expect(parsed.num_audio_channels).to eq(2)
43
+ expect(parsed.audio_sample_rate_hz).to eq(44100)
44
+ expect(parsed.content_type).to eq('audio/aac')
45
+ end
46
+
47
+ it 'should parse a long sample, single channel audio, 16 kb/s, 44100 HZ' do
48
+ file_path = fixtures_dir + '/AAC/ff-16b-1c-44100hz.aac'
49
+ parsed = subject.call(File.open(file_path, 'rb'))
50
+
51
+ expect(parsed).not_to be_nil
52
+
53
+ expect(parsed.nature).to eq(:audio)
54
+ expect(parsed.format).to eq(:aac)
55
+ expect(parsed.num_audio_channels).to eq(1)
56
+ expect(parsed.audio_sample_rate_hz).to eq(44100)
57
+ expect(parsed.content_type).to eq('audio/aac')
58
+ end
59
+
60
+ it 'should parse a long sample, two channel audio, 16 kb/s, 44100 HZ' do
61
+ file_path = fixtures_dir + '/AAC/ff-16b-2c-44100hz.aac'
62
+ parsed = subject.call(File.open(file_path, 'rb'))
63
+
64
+ expect(parsed).not_to be_nil
65
+
66
+ expect(parsed.nature).to eq(:audio)
67
+ expect(parsed.format).to eq(:aac)
68
+ expect(parsed.num_audio_channels).to eq(2)
69
+ expect(parsed.audio_sample_rate_hz).to eq(44100)
70
+ expect(parsed.content_type).to eq('audio/aac')
71
+ end
72
+
73
+ shared_examples 'invalid filetype' do |filetype, fixture_path|
74
+ it "should fail to parse #{filetype}" do
75
+ file_path = fixtures_dir + fixture_path
76
+ parsed = subject.call(File.open(file_path, 'rb'))
77
+ expect(parsed).to be_nil
78
+ end
79
+ end
80
+
81
+ include_examples 'invalid filetype', 'AIFF', '/AIFF/fixture.aiff'
82
+ include_examples 'invalid filetype', 'FLAC', '/FLAC/atc_fixture_vbr.flac'
83
+ include_examples 'invalid filetype', 'MP3', '/MP3/Cassy.mp3'
84
+ include_examples 'invalid filetype', 'MPG', '/MPG/video1.mpg'
85
+ include_examples 'invalid filetype', 'OGG', '/Ogg/hi.ogg'
86
+ include_examples 'invalid filetype', 'WAV', '/WAV/c_8kmp316.wav'
87
+ end
@@ -0,0 +1,38 @@
1
+ require 'spec_helper'
2
+
3
+ describe FormatParser::AdtsHeaderInfo do
4
+ shared_examples 'parsed header' do |header_bits, expected_mpeg_version_description, expected_protection_absence, expected_profile_description, expected_mpeg4_sampling_frequency, expected_mpeg4_channel_config, expected_number_of_audio_channels, expected_originality, expected_home_usage, expected_frame_length, expected_aac_frames_per_adts_frame, expected_has_fixed_bitrate|
5
+ it "extracts correct values for header #{header_bits}" do
6
+ result = FormatParser::AdtsHeaderInfo.parse_adts_header(header_bits.split(''))
7
+ expect(result).not_to be_nil
8
+ expect(result.mpeg_version_description).to eq(expected_mpeg_version_description)
9
+ expect(result.protection_absence).to eq(expected_protection_absence)
10
+ expect(result.profile_description).to eq(expected_profile_description)
11
+ expect(result.mpeg4_sampling_frequency).to eq(expected_mpeg4_sampling_frequency)
12
+ expect(result.mpeg4_channel_config).to eq(expected_mpeg4_channel_config)
13
+ expect(result.number_of_audio_channels).to eq(expected_number_of_audio_channels)
14
+ expect(result.originality).to eq(expected_originality)
15
+ expect(result.home_usage).to eq(expected_home_usage)
16
+ expect(result.frame_length).to eq(expected_frame_length)
17
+ expect(result.aac_frames_per_adts_frame).to eq(expected_aac_frames_per_adts_frame)
18
+ expect(result.fixed_bitrate?).to eq(expected_has_fixed_bitrate)
19
+ end
20
+ end
21
+
22
+ shared_examples 'invalid header' do |failure_reason, header_bits|
23
+ it "fails on #{failure_reason} for header #{header_bits}" do
24
+ result = FormatParser::AdtsHeaderInfo.parse_adts_header(header_bits.split(''))
25
+ expect(result).to be_nil
26
+ end
27
+ end
28
+
29
+ # These headers have been validated here: https://www.p23.nl/projects/aac-header/
30
+ include_examples 'parsed header', '1111111111110001010111001000000000101110011111111111110000100001', 'MPEG-4', true, 'AAC_LC (Low Complexity)', 22050, 2, 2, false, false, 371, 1, false
31
+ include_examples 'parsed header', '111111111111000101010000010000000000011110011111111111001101111000000010', 'MPEG-4', true, 'AAC_LC (Low Complexity)', 44100, 1, 1, false, false, 60, 1, false
32
+
33
+ include_examples 'invalid header', 'invalid syncword', '1111110111110001010111001000000000101110011111111111110000100001'
34
+ include_examples 'invalid header', 'invalid layer value', '1111111111110011010111001000000000101110011111111111110000100001'
35
+ include_examples 'invalid header', 'invalid sampling frequency index 15', '1111111111110001011111001000000000101110011111111111110000100001'
36
+ include_examples 'invalid header', 'zero frame length', '1111111111110001010111001000000000000000011111111111110000100001'
37
+ include_examples 'invalid header', 'random header', '101000101011010101010101111010101010101011001010101010101111000000011101'
38
+ end
@@ -0,0 +1,119 @@
1
+ require 'spec_helper'
2
+
3
+ describe FormatParser::ARWParser do
4
+ shared_examples 'likely_match for file' do |filename_with_extension|
5
+ it "matches '#{filename_with_extension}'" do
6
+ expect(subject.likely_match?(filename_with_extension)).to be_truthy
7
+ end
8
+ end
9
+
10
+ shared_examples 'no likely_match for file' do |filename_with_extension|
11
+ it "does not match '#{filename_with_extension}'" do
12
+ expect(subject.likely_match?(filename_with_extension)).to be_falsey
13
+ end
14
+ end
15
+
16
+ describe 'likely_match' do
17
+ filenames = ['raw_file', 'another raw file', 'and.another', 'one-more']
18
+ valid_extensions = ['.arw', '.Arw', '.aRw', '.arW', '.ARw', '.ArW', '.aRW', '.ARW']
19
+ invalid_extensions = ['.tiff', '.cr2', '.new', '.jpeg']
20
+ filenames.each do |filename|
21
+ valid_extensions.each do |extension|
22
+ include_examples 'likely_match for file', filename + extension
23
+ end
24
+ invalid_extensions.each do |extension|
25
+ include_examples 'no likely_match for file', filename + extension
26
+ end
27
+ end
28
+ end
29
+
30
+ describe 'parses Sony ARW fixtures as arw format file' do
31
+ expected_parsed_dimensions = {
32
+ 'RAW_SONY_A100.ARW' => {
33
+ width_px: 3872,
34
+ height_px: 2592,
35
+ display_width_px: 3872,
36
+ display_height_px: 2592,
37
+ orientation: :top_left
38
+ },
39
+ 'RAW_SONY_A700.ARW' => {
40
+ width_px: 4288,
41
+ height_px: 2856,
42
+ display_width_px: 4288,
43
+ display_height_px: 2856,
44
+ orientation: :top_left
45
+ },
46
+ 'RAW_SONY_A900.ARW' => {
47
+ width_px: 6080,
48
+ height_px: 4048,
49
+ display_width_px: 6080,
50
+ display_height_px: 4048,
51
+ orientation: :top_left
52
+ },
53
+ # rotated 90 degree image
54
+ 'RAW_SONY_DSC-RX100M2.ARW' => {
55
+ width_px: 5472,
56
+ height_px: 3648,
57
+ display_width_px: 3648,
58
+ display_height_px: 5472,
59
+ orientation: :right_top,
60
+ },
61
+ 'RAW_SONY_ILCE-7RM2.ARW' => {
62
+ width_px: 7952,
63
+ height_px: 5304,
64
+ display_width_px: 7952,
65
+ display_height_px: 5304,
66
+ orientation: :top_left,
67
+ },
68
+ 'RAW_SONY_NEX7.ARW' => {
69
+ width_px: 6000,
70
+ height_px: 4000,
71
+ display_width_px: 6000,
72
+ display_height_px: 4000,
73
+ orientation: :top_left,
74
+ },
75
+ 'RAW_SONY_SLTA55V.ARW' => {
76
+ width_px: 4928,
77
+ height_px: 3280,
78
+ display_width_px: 4928,
79
+ display_height_px: 3280,
80
+ orientation: :top_left,
81
+ },
82
+ }
83
+
84
+ Dir.glob(fixtures_dir + '/ARW/*.ARW').each do |arw_path|
85
+ it "is able to parse #{File.basename(arw_path)}" do
86
+ expected_dimension = expected_parsed_dimensions[File.basename(arw_path)]
87
+ # error if a new .arw test file is added without specifying the expected dimensions
88
+ expect(expected_dimension).not_to be_nil
89
+
90
+ parsed = subject.call(File.open(arw_path, 'rb'))
91
+ expect(parsed).not_to be_nil
92
+ expect(parsed.nature).to eq(:image)
93
+ expect(parsed.format).to eq(:arw)
94
+ expect(parsed.intrinsics[:exif]).not_to be_nil
95
+ expect(parsed.content_type).to eq('image/x-sony-arw')
96
+
97
+ expect(parsed.width_px).to eq(expected_dimension[:width_px])
98
+ expect(parsed.height_px).to eq(expected_dimension[:height_px])
99
+ expect(parsed.display_width_px).to eq(expected_dimension[:display_width_px])
100
+ expect(parsed.display_height_px).to eq(expected_dimension[:display_height_px])
101
+ expect(parsed.orientation).to eq(expected_dimension[:orientation])
102
+ end
103
+ end
104
+
105
+ shared_examples 'invalid filetype' do |filetype, fixture_path|
106
+ it "should fail to parse #{filetype}" do
107
+ file_path = fixtures_dir + fixture_path
108
+ parsed = subject.call(File.open(file_path, 'rb'))
109
+ expect(parsed).to be_nil
110
+ end
111
+ end
112
+
113
+ include_examples 'invalid filetype', 'NEF', '/NEF/RAW_NIKON_1S2.NEF'
114
+ include_examples 'invalid filetype', 'TIFF', '/TIFF/Shinbutsureijoushuincho.tiff'
115
+ include_examples 'invalid filetype', 'JPG', '/JPEG/orient_6.jpg'
116
+ include_examples 'invalid filetype', 'PNG', '/PNG/cat.png'
117
+ include_examples 'invalid filetype', 'CR2', '/CR2/RAW_CANON_1DM2.CR2'
118
+ end
119
+ end
@@ -47,21 +47,6 @@ describe FormatParser::TIFFParser do
47
47
  expect(parsed.intrinsics[:exif]).not_to be_nil
48
48
  end
49
49
 
50
- it 'parses Sony ARW fixture as arw format file' do
51
- arw_path = fixtures_dir + '/ARW/RAW_SONY_ILCE-7RM2.ARW'
52
-
53
- parsed = subject.call(File.open(arw_path, 'rb'))
54
-
55
- expect(parsed).not_to be_nil
56
- expect(parsed.nature).to eq(:image)
57
- expect(parsed.format).to eq(:arw)
58
-
59
- expect(parsed.width_px).to eq(7952)
60
- expect(parsed.height_px).to eq(5304)
61
- expect(parsed.intrinsics[:exif]).not_to be_nil
62
- expect(parsed.content_type).to eq('image/x-sony-arw')
63
- end
64
-
65
50
  describe 'correctly extracts dimensions from various TIFF flavors of the same file' do
66
51
  Dir.glob(fixtures_dir + '/TIFF/IMG_9266*.tif').each do |tiff_path|
67
52
  it "is able to parse #{File.basename(tiff_path)}" do
@@ -100,4 +85,13 @@ describe FormatParser::TIFFParser do
100
85
  end
101
86
  end
102
87
  end
88
+
89
+ describe 'bails out on ARW files, such as' do
90
+ Dir.glob(fixtures_dir + '/ARW/*.ARW').each do |arw_path|
91
+ it "skips #{File.basename(arw_path)}" do
92
+ parsed = subject.call(File.open(arw_path, 'rb'))
93
+ expect(parsed).to be_nil
94
+ end
95
+ end
96
+ end
103
97
  end
metadata CHANGED
@@ -1,15 +1,15 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: format_parser
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.5.0
4
+ version: 1.7.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Noah Berman
8
8
  - Julik Tarkhanov
9
- autorequire:
9
+ autorequire:
10
10
  bindir: exe
11
11
  cert_chain: []
12
- date: 2022-08-23 00:00:00.000000000 Z
12
+ date: 2022-10-05 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: ks
@@ -229,7 +229,10 @@ files:
229
229
  - lib/image.rb
230
230
  - lib/io_constraint.rb
231
231
  - lib/io_utils.rb
232
+ - lib/parsers/aac_parser.rb
233
+ - lib/parsers/aac_parser/adts_header_info.rb
232
234
  - lib/parsers/aiff_parser.rb
235
+ - lib/parsers/arw_parser.rb
233
236
  - lib/parsers/bmp_parser.rb
234
237
  - lib/parsers/cr2_parser.rb
235
238
  - lib/parsers/dpx_parser.rb
@@ -273,7 +276,10 @@ files:
273
276
  - spec/hash_utils_spec.rb
274
277
  - spec/integration/active_storage/rails_app.rb
275
278
  - spec/io_utils_spec.rb
279
+ - spec/parsers/aac_parser_spec.rb
280
+ - spec/parsers/adts_header_info_spec.rb
276
281
  - spec/parsers/aiff_parser_spec.rb
282
+ - spec/parsers/arw_parser_spec.rb
277
283
  - spec/parsers/bmp_parser_spec.rb
278
284
  - spec/parsers/cr2_parser_spec.rb
279
285
  - spec/parsers/dpx_parser_spec.rb
@@ -306,7 +312,7 @@ licenses:
306
312
  - MIT (Hippocratic)
307
313
  metadata:
308
314
  allowed_push_host: https://rubygems.org
309
- post_install_message:
315
+ post_install_message:
310
316
  rdoc_options: []
311
317
  require_paths:
312
318
  - lib
@@ -321,8 +327,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
321
327
  - !ruby/object:Gem::Version
322
328
  version: '0'
323
329
  requirements: []
324
- rubygems_version: 3.1.6
325
- signing_key:
330
+ rubygems_version: 3.3.7
331
+ signing_key:
326
332
  specification_version: 4
327
333
  summary: A library for efficient parsing of file metadata
328
334
  test_files: []