format_parser 1.5.0 → 1.7.0

Sign up to get free protection for your applications and to get access to all the features.
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: []