format_parser 0.13.4 → 0.13.5

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: f23bfcabe71704fa85eca744312d6b8f48b97087c13cef3129a027b413d2b6ad
4
- data.tar.gz: df7d94ff90f305b8a65deb7734718035e86494a5eaf129f437ba603f1b2935c8
3
+ metadata.gz: 8837741d44c95c25f1b68ae19991bf6b1c1819b17f8a645d5c53a9d9ba97a8bb
4
+ data.tar.gz: 58dac9d742cf2b59bbf05ae8b810b8a6b15b144035ca625a52e4a9fbed74f93e
5
5
  SHA512:
6
- metadata.gz: 6beaa829ff6fee1f9a0c83f2ab3478fa1ea64798693d4b1025cb5d225923a0eb65a98c7355a5a15d764b34e51a51cdf8bb43718ea5297b92fc51bb4bceae842b
7
- data.tar.gz: 2bad249ebf76f964d2a565daf8ec0cfd47176b85ae12c1e9f804575bacc2af0636c12a4291220b0b1f7ae1e4ee66926d9543d77c5aa97707b98abc919231fa6b
6
+ metadata.gz: 5be5537ea121c3eff8ec4520d049b1ff0e813f22cc4ea800ad01be7ccb823e0580ea3155d9d561b6854e1f211a98ecd23e6a8de4ef4c2a735b81a6b69a7aa780
7
+ data.tar.gz: 9b056ff9cb825a4d925a03ad3fcb245e0e93545057a1cf7c3ef778f84955b16b12175c9d8f3c0ab2e04542364c8bb1c5f65e8c0ad39ff70e199774217282a087
data/.travis.yml CHANGED
@@ -7,5 +7,4 @@ rvm:
7
7
  sudo: false
8
8
  cache: bundler
9
9
  script:
10
- - bundle exec rubocop
11
- - bundle exec rspec
10
+ - bundle exec rake
data/CHANGELOG.md CHANGED
@@ -1,3 +1,6 @@
1
+ ## 0.13.5
2
+ * Use the same TIFF parsing flow for CR2 files as it seems we are not very reliable _yet._ The CR2 parser will need some work.
3
+
1
4
  ## 0.13.4
2
5
  * Make sure JSON data never contains NaN, fix the test that was supposed to verify that but didn't
3
6
  * Forcibly UTF-8 sanitize all EXIF data when building JSON
data/Rakefile CHANGED
@@ -1,6 +1,7 @@
1
1
  require 'bundler/gem_tasks'
2
2
  require 'rspec/core/rake_task'
3
3
  require 'yard'
4
+ require 'rubocop/rake_task'
4
5
 
5
6
  YARD::Rake::YardocTask.new(:doc) do |t|
6
7
  # The dash has to be between the two to "divide" the source files and
@@ -8,5 +9,6 @@ YARD::Rake::YardocTask.new(:doc) do |t|
8
9
  t.files = ['lib/**/*.rb', '-', 'LICENSE.txt', 'IMPLEMENTATION_DETAILS.md']
9
10
  end
10
11
 
12
+ RuboCop::RakeTask.new
11
13
  RSpec::Core::RakeTask.new(:spec)
12
- task default: :spec
14
+ task default: [:spec, :rubocop]
@@ -1,3 +1,3 @@
1
1
  module FormatParser
2
- VERSION = '0.13.4'
2
+ VERSION = '0.13.5'
3
3
  end
@@ -1,22 +1,12 @@
1
+ require_relative 'exif_parser'
2
+
1
3
  class FormatParser::CR2Parser
2
4
  include FormatParser::IOUtils
5
+ include FormatParser::EXIFParser
3
6
 
4
7
  TIFF_HEADER = [0x49, 0x49, 0x2a, 0x00]
5
8
  CR2_HEADER = [0x43, 0x52, 0x02, 0x00]
6
9
 
7
- PREVIEW_ORIENTATION_TAG = 0x0112
8
- PREVIEW_RESOLUTION_TAG = 0x011a
9
- PREVIEW_IMAGE_OFFSET_TAG = 0x0111
10
- PREVIEW_IMAGE_BYTE_COUNT_TAG = 0x0117
11
- EXIF_OFFSET_TAG = 0x8769
12
- MAKERNOTE_OFFSET_TAG = 0x927c
13
- AFINFO_TAG = 0x0012
14
- AFINFO2_TAG = 0x0026
15
- CAMERA_MODEL_TAG = 0x0110
16
- SHOOT_DATE_TAG = 0x0132
17
- EXPOSURE_TAG = 0x829a
18
- APERTURE_TAG = 0x829d
19
-
20
10
  def call(io)
21
11
  io = FormatParser::IOConstraint.new(io)
22
12
 
@@ -28,130 +18,27 @@ class FormatParser::CR2Parser
28
18
 
29
19
  return if !magic_bytes.eql?(CR2_HEADER) || !tiff_bytes.eql?(TIFF_HEADER)
30
20
 
31
- # Offset to IFD #0 where the preview image data is located
32
- # For more information about CR2 format,
33
- # see http://lclevy.free.fr/cr2/
34
- # and https://github.com/lclevy/libcraw2/blob/master/docs/cr2_poster.pdf
35
- if0_offset = parse_sequence_to_int tiff_header[4..7]
36
-
37
- parse_ifd_0(io, if0_offset)
38
- set_orientation(io, if0_offset)
39
-
40
- exif_offset = parse_ifd(io, if0_offset, EXIF_OFFSET_TAG)
21
+ # The TIFF scanner in EXIFR is plenty good enough,
22
+ # so why don't we use it? It does all the right skips
23
+ # in all the right places.
24
+ exif_data = exif_from_tiff_io(io)
25
+ return unless exif_data
41
26
 
42
- set_photo_info(io, exif_offset[0])
43
-
44
- makernote_offset = parse_ifd(io, exif_offset[0], MAKERNOTE_OFFSET_TAG)
45
-
46
- # Old Canon models have CanonAFInfo tags
47
- # Newer models have CanonAFInfo2 tags instead
48
- # See https://sno.phy.queensu.ca/~phil/exiftool/TagNames/Canon.html
49
- af_info = parse_ifd(io, makernote_offset[0], AFINFO2_TAG)
50
- unless af_info.nil?
51
- parse_dimensions(io, af_info[0], af_info[1], 8, 10)
52
- else
53
- af_info = parse_ifd(io, makernote_offset[0], AFINFO_TAG)
54
- parse_dimensions(io, af_info[0], af_info[1], 4, 6)
55
- end
27
+ w = exif_data.image_width
28
+ h = exif_data.image_length
56
29
 
57
30
  FormatParser::Image.new(
58
31
  format: :cr2,
59
- width_px: @width,
60
- height_px: @height,
61
- orientation: @orientation,
62
- image_orientation: @image_orientation,
63
- intrinsics: intrinsics
32
+ width_px: w,
33
+ height_px: h,
34
+ display_width_px: exif_data.rotated? ? h : w,
35
+ display_height_px: exif_data.rotated? ? w : h,
36
+ orientation: exif_data.orientation,
37
+ intrinsics: {exif: exif_data},
64
38
  )
65
- end
66
-
67
- private
68
-
69
- def parse_ifd(io, offset, searched_tag)
70
- io.seek(offset)
71
- entries_count = parse_sequence_to_int safe_read(io, 2)
72
- entries_count.times do
73
- ifd = ifd_entry safe_read(io, 12)
74
- return [ifd[:value], ifd[:length], ifd[:type]].map { |b| parse_sequence_to_int b } if ifd[:tag] == [searched_tag].pack('v')
75
- end
39
+ rescue EXIFR::MalformedTIFF
76
40
  nil
77
41
  end
78
42
 
79
- def ifd_entry(binary)
80
- { tag: binary[0..1], type: binary[2..3], length: binary[4..7], value: binary[8..11] }
81
- end
82
-
83
- def parse_sequence_to_int(sequence)
84
- sequence.reverse.unpack('H*').join.hex
85
- end
86
-
87
- def parse_dimensions(io, offset, length, w_offset, h_offset)
88
- io.seek(offset)
89
- items = safe_read(io, length)
90
- @width = parse_sequence_to_int items[w_offset..w_offset + 1]
91
- @height = parse_sequence_to_int items[h_offset..h_offset + 1]
92
- end
93
-
94
- def parse_ifd_0(io, offset)
95
- resolution_offset = parse_ifd(io, offset, PREVIEW_RESOLUTION_TAG)
96
- resolution_data = read_data(io, resolution_offset[0], resolution_offset[1] * 8, resolution_offset[2])
97
- @resolution = resolution_data[0] / resolution_data[1]
98
-
99
- @preview_offset = parse_ifd(io, offset, PREVIEW_IMAGE_OFFSET_TAG).first
100
- @preview_byte_count = parse_ifd(io, offset, PREVIEW_IMAGE_BYTE_COUNT_TAG).first
101
-
102
- model_offset = parse_ifd(io, offset, CAMERA_MODEL_TAG)
103
- @model = read_data(io, model_offset[0], model_offset[1], model_offset[2])
104
-
105
- shoot_date_offset = parse_ifd(io, offset, SHOOT_DATE_TAG)
106
- @shoot_date = read_data(io, shoot_date_offset[0], shoot_date_offset[1], shoot_date_offset[2])
107
- end
108
-
109
- def set_orientation(io, offset)
110
- orient = parse_ifd(io, offset, PREVIEW_ORIENTATION_TAG).first
111
- # Some old models do not have orientation info in TIFF headers
112
- return if orient > 8
113
- # EXIF orientation is an one based index
114
- # http://sylvana.net/jpegcrop/exif_orientation.html
115
- @orientation = FormatParser::EXIFParser::ORIENTATIONS[orient - 1]
116
- @image_orientation = orient
117
- end
118
-
119
- def set_photo_info(io, offset)
120
- # Type for exposure, aperture and resolution is unsigned rational
121
- # Unsigned rational = 2x unsigned long (4 bytes)
122
- exposure_offset = parse_ifd(io, offset, EXPOSURE_TAG)
123
- exposure_data = read_data(io, exposure_offset[0], exposure_offset[1] * 8, exposure_offset[2])
124
- @exposure = "#{exposure_data[0]}/#{exposure_data[1]}"
125
-
126
- aperture_offset = parse_ifd(io, offset, APERTURE_TAG)
127
- aperture_data = read_data(io, aperture_offset[0], aperture_offset[1] * 8, aperture_offset[2])
128
- @aperture = "f#{aperture_data[0] / aperture_data[1].to_f}"
129
- end
130
-
131
- def read_data(io, offset, length, type)
132
- io.seek(offset)
133
- data = io.read(length)
134
- case type
135
- when 5
136
- n = parse_sequence_to_int data[0..3]
137
- d = parse_sequence_to_int data[4..7]
138
- [n, d]
139
- else
140
- data
141
- end
142
- end
143
-
144
- def intrinsics
145
- {
146
- camera_model: @model,
147
- shoot_date: @shoot_date,
148
- exposure: @exposure,
149
- aperture: @aperture,
150
- resolution: @resolution,
151
- preview_offset: @preview_offset,
152
- preview_length: @preview_byte_count
153
- }
154
- end
155
-
156
43
  FormatParser.register_parser self, natures: :image, formats: :cr2
157
44
  end
@@ -16,40 +16,29 @@ describe FormatParser::CR2Parser do
16
16
  expect(parsed.height_px).to be_kind_of(Integer)
17
17
  expect(parsed.height_px).to be > 0
18
18
 
19
- expect(parsed.intrinsics).not_to be_nil
20
- expect(parsed.intrinsics[:camera_model]).to be_kind_of(String)
21
- expect(parsed.intrinsics[:camera_model]).to match(/Canon \w+/)
22
- expect(parsed.intrinsics[:shoot_date]).to be_kind_of(String)
23
- expect(parsed.intrinsics[:shoot_date]).to match(/\d{4}:\d{2}:\d{2} \d{2}:\d{2}:\d{2}/)
24
- expect(parsed.intrinsics[:exposure]).to be_kind_of(String)
25
- expect(parsed.intrinsics[:exposure]).to match(/1\/[0-9]+/)
26
- expect(parsed.intrinsics[:aperture]).to be_kind_of(String)
27
- expect(parsed.intrinsics[:aperture]).to match(/f[0-9]+\.[0-9]/)
28
- expect(parsed.intrinsics[:resolution]).to be_kind_of(Integer)
29
- expect(parsed.intrinsics[:resolution]).to be > 0
30
- expect(parsed.intrinsics[:preview_offset]).to be_kind_of(Integer)
31
- expect(parsed.intrinsics[:preview_offset]).to be > 0
32
- expect(parsed.intrinsics[:preview_length]).to be_kind_of(Integer)
33
- expect(parsed.intrinsics[:preview_length]).to be > 0
19
+ expect(parsed.orientation).not_to be_nil
34
20
  end
35
21
  end
36
22
  end
37
23
 
38
- describe 'is able to parse orientation info in the examples' do
39
- it 'is able to parse orientation in RAW_CANON_40D_SRAW_V103.CR2' do
40
- file = fixtures_dir + '/CR2/RAW_CANON_40D_SRAW_V103.CR2'
41
- parsed = subject.call(File.open(file, 'rb'))
42
- expect(parsed.orientation).to be_kind_of(Symbol)
43
- expect(parsed.image_orientation).to be_kind_of(Integer)
44
- expect(parsed.image_orientation).to be > 0
45
- end
24
+ it 'is able to parse orientation in RAW_CANON_40D_SRAW_V103.CR2' do
25
+ file = fixtures_dir + '/CR2/RAW_CANON_40D_SRAW_V103.CR2'
46
26
 
47
- it 'is able to return the orientation nil for the examples from old Canon models' do
48
- file = fixtures_dir + '/CR2/_MG_8591.CR2'
49
- parsed = subject.call(File.open(file, 'rb'))
50
- expect(parsed.orientation).to be_nil
51
- expect(parsed.image_orientation).to be_nil
52
- end
27
+ parsed = subject.call(File.open(file, 'rb'))
28
+
29
+ expect(parsed.width_px).to eq(1936)
30
+ expect(parsed.height_px).to eq(1288)
31
+ expect(parsed.orientation).to be_kind_of(Symbol)
32
+ end
33
+
34
+ it 'is able to return the orientation nil for the examples from old Canon models' do
35
+ file = fixtures_dir + '/CR2/_MG_8591.CR2'
36
+
37
+ parsed = subject.call(File.open(file, 'rb'))
38
+
39
+ expect(parsed.width_px).to eq(1536)
40
+ expect(parsed.height_px).to eq(1024)
41
+ expect(parsed.orientation).to eq(:top_left)
53
42
  end
54
43
 
55
44
  describe 'is able to return nil unless the examples are CR2' do
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: format_parser
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.13.4
4
+ version: 0.13.5
5
5
  platform: ruby
6
6
  authors:
7
7
  - Noah Berman