format_parser 0.13.4 → 0.13.5

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: 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