file_data 4.0.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 008003613bb4284356911dcc023da0a9190f9787
4
+ data.tar.gz: 95cfd92bf198612c9e20796577809f507cd866ab
5
+ SHA512:
6
+ metadata.gz: fd9ae2b5d47292ee6a0318dce866f97fa4d42ad812de9e05e5e1c18e140fa0186ab93045af8a7c0c8fafc01b9ad5d9e2e0fd7b77fee2d86d9de873d2fbe29274
7
+ data.tar.gz: 759531661d30f168362e75494e2dff6c003cefc3dd6819d6875882df32280aa617e00f98d16648f003633a80ba173c9610d40ddb20ab6648bcbbc65bc2bc17ab
@@ -0,0 +1 @@
1
+ service_name: travis-ci
@@ -0,0 +1,50 @@
1
+ *.gem
2
+ *.rbc
3
+ /.config
4
+ /coverage/
5
+ /InstalledFiles
6
+ /pkg/
7
+ /spec/reports/
8
+ /spec/examples.txt
9
+ /test/tmp/
10
+ /test/version_tmp/
11
+ /tmp/
12
+
13
+ # Used by dotenv library to load environment variables.
14
+ # .env
15
+
16
+ ## Specific to RubyMotion:
17
+ .dat*
18
+ .repl_history
19
+ build/
20
+ *.bridgesupport
21
+ build-iPhoneOS/
22
+ build-iPhoneSimulator/
23
+
24
+ ## Specific to RubyMotion (use of CocoaPods):
25
+ #
26
+ # We recommend against adding the Pods directory to your .gitignore. However
27
+ # you should judge for yourself, the pros and cons are mentioned at:
28
+ # https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control
29
+ #
30
+ # vendor/Pods/
31
+
32
+ ## Documentation cache and generated files:
33
+ /.yardoc/
34
+ /_yardoc/
35
+ /doc/
36
+ /rdoc/
37
+
38
+ ## Environment normalization:
39
+ /.bundle/
40
+ /vendor/bundle
41
+ /lib/bundler/man/
42
+
43
+ # for a library or gem, you might want to ignore these files since the code is
44
+ # intended to run in multiple environments; otherwise, check them in:
45
+ # Gemfile.lock
46
+ # .ruby-version
47
+ # .ruby-gemset
48
+
49
+ # unless supporting rvm < 1.11.0 or doing something fancy, ignore this:
50
+ .rvmrc
data/.rspec ADDED
@@ -0,0 +1,2 @@
1
+ --color
2
+ --require ./spec/spec_helper.rb
@@ -0,0 +1,19 @@
1
+ Metrics/LineLength:
2
+ Max: 100
3
+ Exclude:
4
+ - 'spec/**/*_spec.rb'
5
+ Metrics/MethodLength:
6
+ Max: 15
7
+ Metrics/ClassLength:
8
+ Max: 100
9
+ EndOfLine:
10
+ Enabled: false
11
+ Style/BlockLength:
12
+ Exclude:
13
+ - 'spec/**/*_spec.rb'
14
+ Style/BlockComments:
15
+ Exclude:
16
+ - 'spec/spec_helper.rb'
17
+ AllCops:
18
+ Exclude:
19
+ - 'file_data.gemspec'
@@ -0,0 +1,5 @@
1
+ sudo: false
2
+ language: ruby
3
+ rvm:
4
+ - 2.3.3
5
+ before_install: gem install bundler -v 1.14.3
data/Gemfile ADDED
@@ -0,0 +1,2 @@
1
+ source 'https://rubygems.org'
2
+ gemspec
@@ -0,0 +1,56 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ file_data (4.0.0)
5
+
6
+ GEM
7
+ remote: https://rubygems.org/
8
+ specs:
9
+ coveralls (0.8.19)
10
+ json (>= 1.8, < 3)
11
+ simplecov (~> 0.12.0)
12
+ term-ansicolor (~> 1.3)
13
+ thor (~> 0.19.1)
14
+ tins (~> 1.6)
15
+ diff-lcs (1.3)
16
+ docile (1.1.5)
17
+ fakefs (0.10.2)
18
+ json (2.0.3)
19
+ rake (10.5.0)
20
+ rspec (3.5.0)
21
+ rspec-core (~> 3.5.0)
22
+ rspec-expectations (~> 3.5.0)
23
+ rspec-mocks (~> 3.5.0)
24
+ rspec-core (3.5.4)
25
+ rspec-support (~> 3.5.0)
26
+ rspec-expectations (3.5.0)
27
+ diff-lcs (>= 1.2.0, < 2.0)
28
+ rspec-support (~> 3.5.0)
29
+ rspec-mocks (3.5.0)
30
+ diff-lcs (>= 1.2.0, < 2.0)
31
+ rspec-support (~> 3.5.0)
32
+ rspec-support (3.5.0)
33
+ simplecov (0.12.0)
34
+ docile (~> 1.1.0)
35
+ json (>= 1.8, < 3)
36
+ simplecov-html (~> 0.10.0)
37
+ simplecov-html (0.10.0)
38
+ term-ansicolor (1.4.0)
39
+ tins (~> 1.0)
40
+ thor (0.19.4)
41
+ tins (1.13.2)
42
+
43
+ PLATFORMS
44
+ ruby
45
+ x64-mingw32
46
+
47
+ DEPENDENCIES
48
+ bundler (~> 1.14)
49
+ coveralls (~> 0.8)
50
+ fakefs (~> 0.10)
51
+ file_data!
52
+ rake (~> 10.0)
53
+ rspec (~> 3.0)
54
+
55
+ BUNDLED WITH
56
+ 1.14.3
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2017 Scott
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
@@ -0,0 +1,222 @@
1
+ file_data
2
+ =========
3
+
4
+ [![Build Status](https://travis-ci.org/ScottHaney/file_data.svg?branch=master)](https://travis-ci.org/ScottHaney/file_data)
5
+ [![Coverage Status](https://coveralls.io/repos/github/ScottHaney/file_data/badge.svg?branch=master)](https://coveralls.io/github/ScottHaney/file_data?branch=master)
6
+ [![Code Climate](https://codeclimate.com/github/ScottHaney/file_data/badges/gpa.svg)](https://codeclimate.com/github/ScottHaney/file_data)
7
+
8
+ Ruby library that reads file metadata.
9
+
10
+ Current support
11
+
12
+ * Exif: Only jpeg files are supported and FlashPix extensions are not supported
13
+
14
+ ## Exif documentation
15
+
16
+ Exif data is hierarchical and consists of tag/value pairs. The first level is whether or not the tag applies to the image or the image's thumbnail. Next a tag may be one of several sections and within the section it will have a unique numeric value. So given this terminology a unique key for an exif tag would be something like image/section1/123 for a tag that applies to the image in section 1 with a tag id of 123.
17
+
18
+ To read exif data a stream of the jpeg data should be used as input. For performance reasons all of the data that is desired should be extracted in a single method from FileData::Exif. All methods will manipulate the stream position after they are called and the user should not count on the stream position being at a specific location after a FileData::Exif method call.
19
+
20
+ Examples:
21
+
22
+ ```ruby
23
+ # Get Exif data from a file path or stream
24
+
25
+ # Complete example with file path...
26
+ tag_value = FileData::Exif.new.image_data_only('/path/to/file.jpg')
27
+
28
+ # Complete example with a file stream...
29
+ File.open('/path/to/file.jpg', 'rb') do |f|
30
+ hash = FileData::Exif.new.image_data_only(f)
31
+ end
32
+
33
+ # Examples for all commands
34
+
35
+ ## Commands that get multiple tag values
36
+
37
+ # Get only the image Exif data
38
+ hash = FileData::Exif.new.image_data_only(file_path_or_stream)
39
+
40
+ # Get only the thumbnail Exif data
41
+ hash = FileData::Exif.new.thumbnail_data_only(file_path_or_stream)
42
+
43
+ # Get all data (image and thumbnail)
44
+ # Use result.image or result.thumbnail to get value hashes
45
+ result = FileData::Exif.new.all_data(file_path_or_stream)
46
+
47
+ ## Commands that get a single tag value
48
+ # tag_key is section/tag_id from the description in the first paragraph
49
+ # tag_key values can be taken from the hash keys in FileData::ExifTags.tag_groups
50
+ # all FileData::ExifTags.tag_groups keys are given after the examples
51
+
52
+ # Image example
53
+ tag_value = FileData::Exif.new.only_image_tag(file_path_or_stream, tag_id)
54
+
55
+ # Thumbnail example
56
+ tag_value = FileData::Exif.new.only_thumbnail_tag(file_path_or_stream, tag_id)
57
+ ```
58
+
59
+ ## Known Tag Keys
60
+
61
+ Below is the contents of FileData::ExifTags.tag_groups which lists all known tag keys and their uniquely assigned names
62
+
63
+ ```ruby
64
+ # Tiff Tags (0th and 1st IFDs)
65
+ FileData::ExifTags.tag_groups[:Tiff] =
66
+ {
67
+ 256 => :Image_Structure_Width,
68
+ 257 => :Image_Structure_Length,
69
+ 258 => :Image_Structure_BitsPerSample,
70
+ 259 => :Image_Structure_Compression,
71
+ 262 => :Image_Structure_PhotometricInterpretation,
72
+ 270 => :Other_ImageDescription,
73
+ 271 => :Other_Make,
74
+ 272 => :Other_Model,
75
+ 273 => :Recording_StripOffsets,
76
+ 274 => :Image_Structure_Orientation,
77
+ 277 => :Image_Structure_SamplesPerPixel,
78
+ 278 => :Recording_RowsPerStrip,
79
+ 279 => :Recording_StripByteCounts,
80
+ 283 => :Image_Structure_YResolution,
81
+ 284 => :Image_Structure_PlanarConfiguration,
82
+ 296 => :Image_Structure_ResolutionUnit,
83
+ 301 => :Image_Data_TransferFunction,
84
+ 305 => :Other_Software,
85
+ 306 => :Other_DateTime,
86
+ 315 => :Other_Artist,
87
+ 318 => :Image_Data_WhitePoint,
88
+ 319 => :Image_Data_PrimaryChromaticities,
89
+ 513 => :Recording_JPEGInterchangeFormat,
90
+ 514 => :Recording_JPEGInterchangeFormatLength,
91
+ 529 => :Image_Data_YCbCrCoefficients,
92
+ 530 => :Image_Structure_YCbCrSubSampling,
93
+ 531 => :Image_Structure_YCbCrPositioning,
94
+ 532 => :Image_Data_ReferenceBlackWhite,
95
+ 33_432 => :Other_Copyright
96
+ }
97
+
98
+ # Exif IFD Tags
99
+ FileData::ExifTags.tag_groups[34_665] =
100
+ {
101
+ 33_434 => :Exif_PictureTakingConditions_ExposureTime,
102
+ 33_437 => :Exif_PictureTakingConditions_FNumber,
103
+ 34_850 => :Exif_PictureTakingConditions_ExposureProgram,
104
+ 34_852 => :Exif_PictureTakingConditions_SpectralSensitivity,
105
+ 34_855 => :Exif_PictureTakingConditions_PhotographicSensitivity,
106
+ 34_856 => :Exif_PictureTakingConditions_OECF,
107
+ 34_864 => :Exif_PictureTakingConditions_SensitivityType,
108
+ 34_865 => :Exif_PictureTakingConditions_StandardOutputSensitivity,
109
+ 34_866 => :Exif_PictureTakingConditions_RecommendedExposureIndex,
110
+ 34_867 => :Exif_PictureTakingConditions_ISOSpeed,
111
+ 34_868 => :Exif_PictureTakingConditions_ISOSpeedLatitudeyyy,
112
+ 34_869 => :Exif_PictureTakingConditions_ISOSpeedLatitudezzz,
113
+ 36_864 => :Exif_Version_ExifVersion,
114
+ 36_867 => :Exif_DateAndTime_DateTimeOriginal,
115
+ 36_868 => :Exif_DateAndTime_DateTimeDigitized,
116
+ 37_121 => :Exif_Configuration_ComponentsConfiguration,
117
+ 37_122 => :Exif_Configuration_CompressedBitsPerPixel,
118
+ 37_377 => :Exif_PictureTakingConditions_ShutterSpeedValue,
119
+ 37_378 => :Exif_PictureTakingConditions_ApertureValue,
120
+ 37_379 => :Exif_PictureTakingConditions_BrightnessValue,
121
+ 37_380 => :Exif_PictureTakingConditions_ExposureBiasValue,
122
+ 37_381 => :Exif_PictureTakingConditions_MaxApertureValue,
123
+ 37_382 => :Exif_PictureTakingConditions_SubjectDistance,
124
+ 37_383 => :Exif_PictureTakingConditions_MeteringMode,
125
+ 37_384 => :Exif_PictureTakingConditions_LightSource,
126
+ 37_385 => :Exif_PictureTakingConditions_Flash,
127
+ 37_396 => :Exif_PictureTakingConditions_SubjectArea,
128
+ 37_386 => :Exif_PictureTakingConditions_FocalLength,
129
+ 37_500 => :Exif_Configuration_MakerNote,
130
+ 37_510 => :Exif_Configuration_UserComment,
131
+ 37_520 => :Exif_DateAndTime_SubsecTime,
132
+ 37_521 => :Exif_DateAndTime_SubsecTimeOriginal,
133
+ 37_522 => :Exif_DateAndTime_SubsecTimeDigitized,
134
+ 37_888 => :Exif_ShootingSituation_Temperature,
135
+ 37_889 => :Exif_ShootingSituation_Humidity,
136
+ 37_890 => :Exif_ShootingSituation_Pressure,
137
+ 37_891 => :Exif_ShootingSituation_WaterDepth,
138
+ 37_892 => :Exif_ShootingSituation_Acceleration,
139
+ 37_893 => :Exif_ShootingSituation_CameraElevationAngle,
140
+ 40_960 => :Exif_Version_FlashpixVersion,
141
+ 40_961 => :Exif_ColorSpace_ColorSpace,
142
+ 40_962 => :Exif_Configuration_PixelXDimension,
143
+ 40_963 => :Exif_Configuration_PixelYDimension,
144
+ 40_964 => :Exif_RelatedFile_RelatedSoundFile,
145
+ 41_483 => :Exif_PictureTakingConditions_FlashEnergy,
146
+ 41_484 => :Exif_PictureTakingConditions_SpatialFrequencyResponse,
147
+ 41_486 => :Exif_PictureTakingConditions_FocalPlaneXResolution,
148
+ 41_487 => :Exif_PictureTakingConditions_FocalPlaneYResolution,
149
+ 41_488 => :Exif_PictureTakingConditions_FocalPlanResolutionUnit,
150
+ 41_492 => :Exif_PictureTakingConditions_SubjectLocation,
151
+ 41_493 => :Exif_PictureTakingConditions_ExposureIndex,
152
+ 41_495 => :Exif_PictureTakingConditions_SensingMode,
153
+ 41_728 => :Exif_PictureTakingConditions_FileSource,
154
+ 41_729 => :Exif_PictureTakingConditions_SceneType,
155
+ 41_730 => :Exif_PictureTakingConditions_CFAPattern,
156
+ 41_985 => :Exif_PictureTakingConditions_CustomRendered,
157
+ 41_986 => :Exif_PictureTakingConditions_ExposureMode,
158
+ 41_987 => :Exif_PictureTakingConditions_WhiteBalance,
159
+ 41_988 => :Exif_PictureTakingConditions_DigitalZoomRatio,
160
+ 41_989 => :Exif_PictureTakingConditions_FocalLengthIn35mmFilm,
161
+ 41_990 => :Exif_PictureTakingConditions_SceneCaptureType,
162
+ 41_991 => :Exif_PictureTakingConditions_GainControl,
163
+ 41_992 => :Exif_PictureTakingConditions_Contrast,
164
+ 41_993 => :Exif_PictureTakingConditions_Saturation,
165
+ 41_994 => :Exif_PictureTakingConditions_Sharpness,
166
+ 41_995 => :Exif_PictureTakingConditions_DeviceSettingDescription,
167
+ 41_996 => :Exif_PictureTakingConditions_SubjectDistanceRange,
168
+ 42_016 => :Exif_Other_ImageUniqueID,
169
+ 42_032 => :Exif_Other_CameraOwnerName,
170
+ 42_033 => :Exif_Other_BodySerialNumber,
171
+ 42_034 => :Exif_Other_LensSpecification,
172
+ 42_035 => :Exif_Other_LensMake,
173
+ 42_036 => :Exif_Other_LensModel,
174
+ 42_037 => :Exif_Other_LensSerialNumber,
175
+ 42_240 => :Exif_ColorSpace_Gamma
176
+ }
177
+
178
+ # GPS IFD Tags
179
+ FileData::ExifTags.tag_groups[34_853] =
180
+ {
181
+ 0 => :GPS_Version,
182
+ 1 => :GPS_LatitudeRef,
183
+ 2 => :GPS_Latitude,
184
+ 3 => :GPS_LongitudeRef,
185
+ 4 => :GPS_Longitude,
186
+ 5 => :GPS_AltitudeRef,
187
+ 6 => :GPS_Altitude,
188
+ 7 => :GPS_TimeStamp,
189
+ 8 => :GPS_Satellites,
190
+ 9 => :GPS_Status,
191
+ 10 => :GPS_MeasureMode,
192
+ 11 => :GPS_DOP,
193
+ 12 => :GPS_SpeedRef,
194
+ 13 => :GPS_Speed,
195
+ 14 => :GPS_TrackRef,
196
+ 15 => :GPS_Track,
197
+ 16 => :GPS_ImgDirectionRef,
198
+ 17 => :GPS_ImgDirection,
199
+ 18 => :GPS_MapDatum,
200
+ 19 => :GPS_DestLatitudeRef,
201
+ 20 => :GPS_DestLatitude,
202
+ 21 => :GPS_DestLongitudeRef,
203
+ 22 => :GPS_DestLongitude,
204
+ 23 => :GPS_DestBearingRef,
205
+ 24 => :GPS_DestBearing,
206
+ 25 => :GPS_DestDistanceRef,
207
+ 26 => :GPS_DestDistance,
208
+ 27 => :GPS_ProcessingMethod,
209
+ 28 => :GPS_AreaInformation,
210
+ 29 => :GPS_DateStamp,
211
+ 30 => :GPS_Differential,
212
+ 31 => :GPS_HPositioningError
213
+ }
214
+
215
+ # Interoperability IFD Tags
216
+ FileData::ExifTags.tag_groups[40_965] =
217
+ {
218
+ 1 => :Interoperability_Index
219
+ }
220
+ ```
221
+
222
+
@@ -0,0 +1,6 @@
1
+ require 'bundler/gem_tasks'
2
+ require 'rspec/core/rake_task'
3
+
4
+ RSpec::Core::RakeTask.new(:spec)
5
+
6
+ task default: :spec
@@ -0,0 +1,38 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'file_data/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "file_data"
8
+ spec.version = FileData::VERSION
9
+ spec.authors = ["Scott"]
10
+ spec.email = [""]
11
+
12
+ spec.summary = %q{Extracts file metadata information (currently only supports exif metadata for jpeg files)}
13
+ spec.description = %q{Extracts file metadata information (currently only supports exif metadata for jpeg files)}
14
+ spec.homepage = ""
15
+ spec.license = "MIT"
16
+
17
+ # Prevent pushing this gem to RubyGems.org. To allow pushes either set the 'allowed_push_host'
18
+ # to allow pushing to a single host or delete this section to allow pushing to any host.
19
+ if spec.respond_to?(:metadata)
20
+ spec.metadata['allowed_push_host'] = 'https://rubygems.org'
21
+ else
22
+ raise "RubyGems 2.0 or newer is required to protect against " \
23
+ "public gem pushes."
24
+ end
25
+
26
+ spec.files = `git ls-files -z`.split("\x0").reject do |f|
27
+ f.match(%r{^(test|spec|features)/})
28
+ end
29
+ spec.bindir = "exe"
30
+ spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
31
+ spec.require_paths = ["lib"]
32
+
33
+ spec.add_development_dependency "bundler", "~> 1.14"
34
+ spec.add_development_dependency "rake", "~> 10.0"
35
+ spec.add_development_dependency "rspec", "~> 3.0"
36
+ spec.add_development_dependency "coveralls", "~> 0.8"
37
+ spec.add_development_dependency "fakefs", "~> 0.10"
38
+ end
@@ -0,0 +1 @@
1
+ require 'file_data/formats/exif/exif'
@@ -0,0 +1,6 @@
1
+ # Adds the ability to concatenate enumerables with #condense
2
+ module EnumerableExtensions
3
+ def condense
4
+ Enumerator.new { |e| each { |seq| seq.each { |v| e << v } } }.lazy
5
+ end
6
+ end
@@ -0,0 +1,52 @@
1
+ module FileData
2
+ # Represents a Jpeg image stream
3
+ class Jpeg
4
+ SOI_BYTES = [255, 216].freeze
5
+ SECTION_HEADER_SIZE = 4
6
+ INVALID_HEADER_MSG = 'the given file is not a jpeg file since it does not'\
7
+ 'begin with the start of image (SOI) bytes.'.freeze
8
+
9
+ def initialize(stream)
10
+ @stream = stream
11
+ end
12
+
13
+ def each_section
14
+ read_header
15
+ Enumerator.new { |e| yield_sections(e) }.lazy
16
+ end
17
+
18
+ def read_header
19
+ soi = read_bytes(SOI_BYTES.size)
20
+ raise INVALID_HEADER_MSG unless soi == SOI_BYTES
21
+ end
22
+
23
+ def yield_sections(e)
24
+ loop do
25
+ next_section_pos = yield_section(e)
26
+ break unless section_pos?(next_section_pos)
27
+ @stream.seek(next_section_pos)
28
+ end
29
+ end
30
+
31
+ def yield_section(e)
32
+ section_start_pos = @stream.pos + 2
33
+ marker, size = read_section_header
34
+ e.yield marker, size
35
+ section_start_pos + size
36
+ end
37
+
38
+ def section_pos?(section_pos)
39
+ # Make sure that there are enough bytes for a section header.
40
+ # This also handles an ending two byte JPEG EOI sequence.
41
+ @stream.size - section_pos >= SECTION_HEADER_SIZE
42
+ end
43
+
44
+ def read_section_header
45
+ [read_bytes(2), read_bytes(2).inject { |a, v| (a << 8) + v }]
46
+ end
47
+
48
+ def read_bytes(num_bytes)
49
+ @stream.each_byte.take(num_bytes)
50
+ end
51
+ end
52
+ end
@@ -0,0 +1,31 @@
1
+ require_relative 'exif_reader'
2
+ require_relative 'exif_jpeg'
3
+
4
+ module FileData
5
+ # Convenience class for extracting exif data from a file or stream
6
+ class Exif
7
+ # Create methods that forward to ExifReader
8
+ ExifReader.public_instance_methods.each do |method_name|
9
+ define_method(method_name) do |input, *other_args|
10
+ delegate_to_exif_reader(input, method_name, other_args)
11
+ end
12
+ end
13
+
14
+ private
15
+
16
+ def delegate_to_exif_reader(input, name, other_args)
17
+ streamify(input) do |stream|
18
+ exif = ExifJpeg.new(stream).exif
19
+ ExifReader.new.send(name, exif, *other_args)
20
+ end
21
+ end
22
+
23
+ def streamify(input)
24
+ if input.is_a?(String)
25
+ File.open(input, 'rb') { |f| yield f }
26
+ else
27
+ yield input
28
+ end
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,16 @@
1
+ module FileData
2
+ # Container for Exif tag values
3
+ class ExifData
4
+ SECTIONS = { 0 => :image, 1 => :thumbnail }.freeze
5
+ SECTIONS.each { |section| define_method(section[1]) { @hash[section[0]] } }
6
+
7
+ def initialize
8
+ @hash = SECTIONS.each_with_object({}) { |pair, hash| hash[pair[0]] = {} }
9
+ end
10
+
11
+ def add_tag(index, ifd_id, tag_id, tag_value)
12
+ name = ExifTags.get_tag_name(ifd_id, tag_id)
13
+ @hash[index][name] = tag_value
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,28 @@
1
+ require 'file_data/file_types/jpeg'
2
+ require_relative 'exif_stream'
3
+
4
+ module FileData
5
+ # Retrieves an ExifStream from a jpeg stream
6
+ class ExifJpeg
7
+ APP1_BYTES = [255, 225].freeze
8
+ EXIF_ID = "Exif\0\0".bytes.to_a.freeze
9
+
10
+ def initialize(stream)
11
+ @stream = stream
12
+ end
13
+
14
+ def exif
15
+ ExifStream.new(@stream) if seek_exif
16
+ end
17
+
18
+ def seek_exif
19
+ Jpeg.new(@stream).each_section
20
+ .select { |marker, _| exif_section?(marker) }
21
+ .first
22
+ end
23
+
24
+ def exif_section?(marker)
25
+ marker == APP1_BYTES && @stream.each_byte.take(EXIF_ID.size) == EXIF_ID
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,54 @@
1
+ require_relative 'exif_jpeg'
2
+ require 'set'
3
+ require_relative 'exif_tag_reader'
4
+ require_relative 'exif_data'
5
+
6
+ module FileData
7
+ # Returns the exif data from a jpeg file
8
+ class ExifReader
9
+ def image_data_only(exif_stream)
10
+ exif_tags_internal(exif_stream, 0).image
11
+ end
12
+
13
+ def thumbnail_data_only(exif_stream)
14
+ exif_tags_internal(exif_stream, 1).thumbnail
15
+ end
16
+
17
+ def all_data(exif_stream)
18
+ exif_tags_internal(exif_stream, 0, 1)
19
+ end
20
+
21
+ def only_image_tag(exif_stream, tag_id)
22
+ exif_tag_internal(exif_stream, 0, tag_id)
23
+ end
24
+
25
+ def only_thumbnail_tag(exif_stream, tag_id)
26
+ exif_tag_internal(exif_stream, 1, tag_id)
27
+ end
28
+
29
+ def tags(exif_stream, *ifds_to_include)
30
+ return [] if exif_stream.nil?
31
+
32
+ exif_stream.read_header
33
+ ExifTagReader.new(exif_stream, *ifds_to_include).tags
34
+ end
35
+
36
+ private
37
+
38
+ def exif_tags_internal(exif_stream, *ifds_to_include)
39
+ tags(exif_stream, *ifds_to_include).each_with_object(ExifData.new) do |tag_info, data|
40
+ data.add_tag(*tag_info, exif_stream.read_tag_value)
41
+ end
42
+ end
43
+
44
+ def exif_tag_internal(exif_stream, ifd_index, tag_to_find)
45
+ exif_stream.read_tag_value if find_tag(exif_stream, ifd_index, tag_to_find)
46
+ end
47
+
48
+ def find_tag(exif_stream, ifd_index, tag_to_find)
49
+ tags(exif_stream, ifd_index).find do |_, ifd_id, tag_num|
50
+ tag_to_find == [ifd_id, tag_num]
51
+ end
52
+ end
53
+ end
54
+ end
@@ -0,0 +1,106 @@
1
+ require 'forwardable'
2
+
3
+ module FileData
4
+ # Wraps a stream with exif specific logic
5
+ class ExifStream
6
+ MOTOROLLA_BYTES = 'MM'.bytes.to_a.freeze
7
+ INTEL_BYTES = 'II'.bytes.to_a.freeze
8
+
9
+ TYPE_BYTE = 1
10
+ TYPE_ASCII = 2
11
+ TYPE_SHORT = 3
12
+ TYPE_LONG = 4
13
+ TYPE_RATIONAL = 5
14
+ TYPE_UNDEFINED = 7
15
+ TYPE_SLONG = 9
16
+ TYPE_SRATIONAL = 10
17
+
18
+ HIGH_BIT_MASK = 2**31
19
+
20
+ VALUE_OFFSET_SIZE = 4
21
+
22
+ extend Forwardable
23
+ def_delegators :@stream, :seek, :pos
24
+
25
+ def initialize(stream)
26
+ @stream = stream
27
+ @section_offset = stream.pos
28
+ end
29
+
30
+ def read_header
31
+ @is_big_endian =
32
+ case @stream.each_byte.take(2)
33
+ when INTEL_BYTES then false
34
+ when MOTOROLLA_BYTES then true
35
+ else raise 'the byte order bytes did not match any expected value'
36
+ end
37
+
38
+ raise 'the tiff constant 42 is missing' unless read_value(2) == 42
39
+ end
40
+
41
+ def seek_exif(offset)
42
+ @stream.seek(@section_offset + offset)
43
+ end
44
+
45
+ def read_tag_value
46
+ type = read_value(2)
47
+ size = read_value(4)
48
+
49
+ case type
50
+ when TYPE_RATIONAL, TYPE_SRATIONAL
51
+ read_large_val(type)
52
+ when TYPE_BYTE, TYPE_SHORT, TYPE_LONG, TYPE_SLONG
53
+ read_small_val(type)
54
+ when TYPE_ASCII
55
+ read_text(size)
56
+ when TYPE_UNDEFINED
57
+ read_undefined(size)
58
+ end
59
+ end
60
+
61
+ def read_text(size)
62
+ read_raw_val(size).pack('c*').chomp("\x00")
63
+ end
64
+
65
+ def read_undefined(size)
66
+ [read_raw_val(size), @is_big_endian]
67
+ end
68
+
69
+ def read_raw_val(size)
70
+ seek_to_large_val if size > VALUE_OFFSET_SIZE
71
+ @stream.each_byte.take([size, VALUE_OFFSET_SIZE].max)
72
+ end
73
+
74
+ def read_large_val(type)
75
+ seek_to_large_val
76
+ read_rational(type == TYPE_SRATIONAL)
77
+ end
78
+
79
+ def read_small_val(type)
80
+ value = read_value(4)
81
+ type == TYPE_SLONG ? to_slong(value) : value
82
+ end
83
+
84
+ def seek_to_large_val
85
+ seek_exif(read_value(4))
86
+ end
87
+
88
+ def read_rational(is_srational)
89
+ Array.new(2) do
90
+ piece = read_value(4)
91
+ is_srational ? to_slong(piece) : piece
92
+ end.join('/')
93
+ end
94
+
95
+ def to_slong(raw_value)
96
+ -(raw_value & HIGH_BIT_MASK) + (raw_value & ~HIGH_BIT_MASK)
97
+ end
98
+
99
+ def read_value(num_bytes)
100
+ bytes = @stream.each_byte.take(num_bytes)
101
+ bytes.reverse! unless @is_big_endian
102
+
103
+ bytes.inject { |total, val| (total << 8) + val }
104
+ end
105
+ end
106
+ end
@@ -0,0 +1,46 @@
1
+ require_relative 'exif_stream'
2
+ require_relative 'ordinal_ifd'
3
+
4
+ module FileData
5
+ # Enumerates the tags in an ExifStream
6
+ class ExifTagReader
7
+ NO_NEXT_IFD = 0
8
+
9
+ attr_accessor :stream, :ifds_to_include
10
+
11
+ def initialize(exif_stream, *ifds_to_include)
12
+ @stream = exif_stream
13
+ @ifds_to_include = ifds_to_include
14
+ end
15
+
16
+ def tags
17
+ Enumerator.new do |e|
18
+ 2.times do |index|
19
+ break if (ifd = next_ifd(index)).nil?
20
+ process_ifd(ifd, e)
21
+ end
22
+ end
23
+ end
24
+
25
+ def process_ifd(ifd, e)
26
+ # Yield the tags or just skip ahead
27
+
28
+ if ifds_to_include.include?(ifd.index)
29
+ ifd.tags.each { |t| e.yield t }
30
+ else
31
+ # Avoid skipping the last ifd as this is needless work
32
+ ifd.skip unless ifd.index == 1
33
+ end
34
+ end
35
+
36
+ def next_ifd(index)
37
+ ifd_offset = stream.read_value(4)
38
+ ifd_from_offset(ifd_offset, index) unless ifd_offset == NO_NEXT_IFD
39
+ end
40
+
41
+ def ifd_from_offset(ifd_offset, index)
42
+ stream.seek_exif(ifd_offset)
43
+ OrdinalIfd.new(stream, index)
44
+ end
45
+ end
46
+ end
@@ -0,0 +1,173 @@
1
+ module FileData
2
+ # Contains tag number to name information taken from the exif spec
3
+ class ExifTags
4
+ singleton_class.class_eval { attr_accessor :tag_groups }
5
+ @tag_groups = {}
6
+
7
+ def self.get_tag_name(ifd_id, tag_id)
8
+ get_known_name(ifd_id, tag_id) || "#{ifd_id}-#{tag_id}".to_sym
9
+ end
10
+
11
+ def self.get_known_name(ifd_id, tag_id)
12
+ ifd_group = tag_groups[ifd_id]
13
+ ifd_group[tag_id] unless ifd_group.nil?
14
+ end
15
+ end
16
+ end
17
+
18
+ # Tiff Tags (0th and 1st IFDs)
19
+ FileData::ExifTags.tag_groups[:Tiff] =
20
+ {
21
+ 256 => :Image_Structure_Width,
22
+ 257 => :Image_Structure_Length,
23
+ 258 => :Image_Structure_BitsPerSample,
24
+ 259 => :Image_Structure_Compression,
25
+ 262 => :Image_Structure_PhotometricInterpretation,
26
+ 270 => :Other_ImageDescription,
27
+ 271 => :Other_Make,
28
+ 272 => :Other_Model,
29
+ 273 => :Recording_StripOffsets,
30
+ 274 => :Image_Structure_Orientation,
31
+ 277 => :Image_Structure_SamplesPerPixel,
32
+ 278 => :Recording_RowsPerStrip,
33
+ 279 => :Recording_StripByteCounts,
34
+ 283 => :Image_Structure_YResolution,
35
+ 284 => :Image_Structure_PlanarConfiguration,
36
+ 296 => :Image_Structure_ResolutionUnit,
37
+ 301 => :Image_Data_TransferFunction,
38
+ 305 => :Other_Software,
39
+ 306 => :Other_DateTime,
40
+ 315 => :Other_Artist,
41
+ 318 => :Image_Data_WhitePoint,
42
+ 319 => :Image_Data_PrimaryChromaticities,
43
+ 513 => :Recording_JPEGInterchangeFormat,
44
+ 514 => :Recording_JPEGInterchangeFormatLength,
45
+ 529 => :Image_Data_YCbCrCoefficients,
46
+ 530 => :Image_Structure_YCbCrSubSampling,
47
+ 531 => :Image_Structure_YCbCrPositioning,
48
+ 532 => :Image_Data_ReferenceBlackWhite,
49
+ 33_432 => :Other_Copyright
50
+ }
51
+
52
+ # Exif IFD Tags
53
+ FileData::ExifTags.tag_groups[34_665] =
54
+ {
55
+ 33_434 => :Exif_PictureTakingConditions_ExposureTime,
56
+ 33_437 => :Exif_PictureTakingConditions_FNumber,
57
+ 34_850 => :Exif_PictureTakingConditions_ExposureProgram,
58
+ 34_852 => :Exif_PictureTakingConditions_SpectralSensitivity,
59
+ 34_855 => :Exif_PictureTakingConditions_PhotographicSensitivity,
60
+ 34_856 => :Exif_PictureTakingConditions_OECF,
61
+ 34_864 => :Exif_PictureTakingConditions_SensitivityType,
62
+ 34_865 => :Exif_PictureTakingConditions_StandardOutputSensitivity,
63
+ 34_866 => :Exif_PictureTakingConditions_RecommendedExposureIndex,
64
+ 34_867 => :Exif_PictureTakingConditions_ISOSpeed,
65
+ 34_868 => :Exif_PictureTakingConditions_ISOSpeedLatitudeyyy,
66
+ 34_869 => :Exif_PictureTakingConditions_ISOSpeedLatitudezzz,
67
+ 36_864 => :Exif_Version_ExifVersion,
68
+ 36_867 => :Exif_DateAndTime_DateTimeOriginal,
69
+ 36_868 => :Exif_DateAndTime_DateTimeDigitized,
70
+ 37_121 => :Exif_Configuration_ComponentsConfiguration,
71
+ 37_122 => :Exif_Configuration_CompressedBitsPerPixel,
72
+ 37_377 => :Exif_PictureTakingConditions_ShutterSpeedValue,
73
+ 37_378 => :Exif_PictureTakingConditions_ApertureValue,
74
+ 37_379 => :Exif_PictureTakingConditions_BrightnessValue,
75
+ 37_380 => :Exif_PictureTakingConditions_ExposureBiasValue,
76
+ 37_381 => :Exif_PictureTakingConditions_MaxApertureValue,
77
+ 37_382 => :Exif_PictureTakingConditions_SubjectDistance,
78
+ 37_383 => :Exif_PictureTakingConditions_MeteringMode,
79
+ 37_384 => :Exif_PictureTakingConditions_LightSource,
80
+ 37_385 => :Exif_PictureTakingConditions_Flash,
81
+ 37_396 => :Exif_PictureTakingConditions_SubjectArea,
82
+ 37_386 => :Exif_PictureTakingConditions_FocalLength,
83
+ 37_500 => :Exif_Configuration_MakerNote,
84
+ 37_510 => :Exif_Configuration_UserComment,
85
+ 37_520 => :Exif_DateAndTime_SubsecTime,
86
+ 37_521 => :Exif_DateAndTime_SubsecTimeOriginal,
87
+ 37_522 => :Exif_DateAndTime_SubsecTimeDigitized,
88
+ 37_888 => :Exif_ShootingSituation_Temperature,
89
+ 37_889 => :Exif_ShootingSituation_Humidity,
90
+ 37_890 => :Exif_ShootingSituation_Pressure,
91
+ 37_891 => :Exif_ShootingSituation_WaterDepth,
92
+ 37_892 => :Exif_ShootingSituation_Acceleration,
93
+ 37_893 => :Exif_ShootingSituation_CameraElevationAngle,
94
+ 40_960 => :Exif_Version_FlashpixVersion,
95
+ 40_961 => :Exif_ColorSpace_ColorSpace,
96
+ 40_962 => :Exif_Configuration_PixelXDimension,
97
+ 40_963 => :Exif_Configuration_PixelYDimension,
98
+ 40_964 => :Exif_RelatedFile_RelatedSoundFile,
99
+ 41_483 => :Exif_PictureTakingConditions_FlashEnergy,
100
+ 41_484 => :Exif_PictureTakingConditions_SpatialFrequencyResponse,
101
+ 41_486 => :Exif_PictureTakingConditions_FocalPlaneXResolution,
102
+ 41_487 => :Exif_PictureTakingConditions_FocalPlaneYResolution,
103
+ 41_488 => :Exif_PictureTakingConditions_FocalPlanResolutionUnit,
104
+ 41_492 => :Exif_PictureTakingConditions_SubjectLocation,
105
+ 41_493 => :Exif_PictureTakingConditions_ExposureIndex,
106
+ 41_495 => :Exif_PictureTakingConditions_SensingMode,
107
+ 41_728 => :Exif_PictureTakingConditions_FileSource,
108
+ 41_729 => :Exif_PictureTakingConditions_SceneType,
109
+ 41_730 => :Exif_PictureTakingConditions_CFAPattern,
110
+ 41_985 => :Exif_PictureTakingConditions_CustomRendered,
111
+ 41_986 => :Exif_PictureTakingConditions_ExposureMode,
112
+ 41_987 => :Exif_PictureTakingConditions_WhiteBalance,
113
+ 41_988 => :Exif_PictureTakingConditions_DigitalZoomRatio,
114
+ 41_989 => :Exif_PictureTakingConditions_FocalLengthIn35mmFilm,
115
+ 41_990 => :Exif_PictureTakingConditions_SceneCaptureType,
116
+ 41_991 => :Exif_PictureTakingConditions_GainControl,
117
+ 41_992 => :Exif_PictureTakingConditions_Contrast,
118
+ 41_993 => :Exif_PictureTakingConditions_Saturation,
119
+ 41_994 => :Exif_PictureTakingConditions_Sharpness,
120
+ 41_995 => :Exif_PictureTakingConditions_DeviceSettingDescription,
121
+ 41_996 => :Exif_PictureTakingConditions_SubjectDistanceRange,
122
+ 42_016 => :Exif_Other_ImageUniqueID,
123
+ 42_032 => :Exif_Other_CameraOwnerName,
124
+ 42_033 => :Exif_Other_BodySerialNumber,
125
+ 42_034 => :Exif_Other_LensSpecification,
126
+ 42_035 => :Exif_Other_LensMake,
127
+ 42_036 => :Exif_Other_LensModel,
128
+ 42_037 => :Exif_Other_LensSerialNumber,
129
+ 42_240 => :Exif_ColorSpace_Gamma
130
+ }
131
+
132
+ # GPS IFD Tags
133
+ FileData::ExifTags.tag_groups[34_853] =
134
+ {
135
+ 0 => :GPS_Version,
136
+ 1 => :GPS_LatitudeRef,
137
+ 2 => :GPS_Latitude,
138
+ 3 => :GPS_LongitudeRef,
139
+ 4 => :GPS_Longitude,
140
+ 5 => :GPS_AltitudeRef,
141
+ 6 => :GPS_Altitude,
142
+ 7 => :GPS_TimeStamp,
143
+ 8 => :GPS_Satellites,
144
+ 9 => :GPS_Status,
145
+ 10 => :GPS_MeasureMode,
146
+ 11 => :GPS_DOP,
147
+ 12 => :GPS_SpeedRef,
148
+ 13 => :GPS_Speed,
149
+ 14 => :GPS_TrackRef,
150
+ 15 => :GPS_Track,
151
+ 16 => :GPS_ImgDirectionRef,
152
+ 17 => :GPS_ImgDirection,
153
+ 18 => :GPS_MapDatum,
154
+ 19 => :GPS_DestLatitudeRef,
155
+ 20 => :GPS_DestLatitude,
156
+ 21 => :GPS_DestLongitudeRef,
157
+ 22 => :GPS_DestLongitude,
158
+ 23 => :GPS_DestBearingRef,
159
+ 24 => :GPS_DestBearing,
160
+ 25 => :GPS_DestDistanceRef,
161
+ 26 => :GPS_DestDistance,
162
+ 27 => :GPS_ProcessingMethod,
163
+ 28 => :GPS_AreaInformation,
164
+ 29 => :GPS_DateStamp,
165
+ 30 => :GPS_Differential,
166
+ 31 => :GPS_HPositioningError
167
+ }
168
+
169
+ # Interoperability IFD Tags
170
+ FileData::ExifTags.tag_groups[40_965] =
171
+ {
172
+ 1 => :Interoperability_Index
173
+ }
@@ -0,0 +1,39 @@
1
+ module FileData
2
+ # Contains the ability to enumerate through the exif tags in an ifd
3
+ module TagEnumerator
4
+ TAG_RECORD_SIZE = 12
5
+
6
+ def tags_enum
7
+ Enumerator.new do |e|
8
+ read_num_tags.times do
9
+ tag_start_pos = stream.pos
10
+ e.yield stream.read_value(2)
11
+ stream.seek(tag_start_pos + TAG_RECORD_SIZE)
12
+ end
13
+ end.lazy
14
+ end
15
+
16
+ def read_num_tags
17
+ stream.read_value(2)
18
+ end
19
+
20
+ def tags_size(num_tags)
21
+ num_tags * TAG_RECORD_SIZE
22
+ end
23
+ end
24
+
25
+ # Represents the tags present in any ifd (ordinal or extra)
26
+ class Ifd
27
+ include TagEnumerator
28
+
29
+ attr_accessor :stream
30
+
31
+ def initialize(exif_stream)
32
+ @stream = exif_stream
33
+ end
34
+
35
+ def tags
36
+ tags_enum
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,51 @@
1
+ require_relative 'ifd'
2
+ require_relative 'exif_tags'
3
+
4
+ module FileData
5
+ # Represents either the zeroth or first ifd
6
+ class OrdinalIfd
7
+ attr_reader :stream, :index
8
+
9
+ include TagEnumerator
10
+
11
+ def initialize(exif_stream, index)
12
+ @stream = exif_stream
13
+ @index = index
14
+ end
15
+
16
+ def tags
17
+ Enumerator.new do |e|
18
+ tags_enum.each { |tag_id| process_tag(e, tag_id) }
19
+ end
20
+ end
21
+
22
+ def process_tag(enumerator, tag_id)
23
+ if pointer_tag?(tag_id)
24
+ process_extra_ifd(enumerator, tag_id)
25
+ else
26
+ yield_tag(enumerator, :Tiff, tag_id)
27
+ end
28
+ end
29
+
30
+ def pointer_tag?(tag_id)
31
+ ExifTags.tag_groups.key?(tag_id)
32
+ end
33
+
34
+ def process_extra_ifd(enumerator, tag_id)
35
+ seek_ifd(stream.read_tag_value)
36
+ tags_enum.each { |t| yield_tag(enumerator, tag_id, t) }
37
+ end
38
+
39
+ def yield_tag(enumerator, ifd_id, tag_id)
40
+ enumerator.yield [index, ifd_id, tag_id]
41
+ end
42
+
43
+ def seek_ifd(pointer_value)
44
+ stream.seek_exif(pointer_value)
45
+ end
46
+
47
+ def skip
48
+ stream.seek(tags_size(read_num_tags), IO::SEEK_CUR)
49
+ end
50
+ end
51
+ end
@@ -0,0 +1,3 @@
1
+ module FileData
2
+ VERSION = '4.0.0'.freeze
3
+ end
metadata ADDED
@@ -0,0 +1,141 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: file_data
3
+ version: !ruby/object:Gem::Version
4
+ version: 4.0.0
5
+ platform: ruby
6
+ authors:
7
+ - Scott
8
+ autorequire:
9
+ bindir: exe
10
+ cert_chain: []
11
+ date: 2017-05-19 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: bundler
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '1.14'
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '1.14'
27
+ - !ruby/object:Gem::Dependency
28
+ name: rake
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '10.0'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '10.0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: rspec
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '3.0'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '3.0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: coveralls
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: '0.8'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: '0.8'
69
+ - !ruby/object:Gem::Dependency
70
+ name: fakefs
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - "~>"
74
+ - !ruby/object:Gem::Version
75
+ version: '0.10'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - "~>"
81
+ - !ruby/object:Gem::Version
82
+ version: '0.10'
83
+ description: Extracts file metadata information (currently only supports exif metadata
84
+ for jpeg files)
85
+ email:
86
+ - ''
87
+ executables: []
88
+ extensions: []
89
+ extra_rdoc_files: []
90
+ files:
91
+ - ".coveralls.yml"
92
+ - ".gitignore"
93
+ - ".rspec"
94
+ - ".rubocop.yml"
95
+ - ".travis.yml"
96
+ - Gemfile
97
+ - Gemfile.lock
98
+ - LICENSE.txt
99
+ - README.md
100
+ - Rakefile
101
+ - file_data.gemspec
102
+ - lib/file_data.rb
103
+ - lib/file_data/core_extensions/enumerable_extensions.rb
104
+ - lib/file_data/file_types/jpeg.rb
105
+ - lib/file_data/formats/exif/exif.rb
106
+ - lib/file_data/formats/exif/exif_data.rb
107
+ - lib/file_data/formats/exif/exif_jpeg.rb
108
+ - lib/file_data/formats/exif/exif_reader.rb
109
+ - lib/file_data/formats/exif/exif_stream.rb
110
+ - lib/file_data/formats/exif/exif_tag_reader.rb
111
+ - lib/file_data/formats/exif/exif_tags.rb
112
+ - lib/file_data/formats/exif/ifd.rb
113
+ - lib/file_data/formats/exif/ordinal_ifd.rb
114
+ - lib/file_data/version.rb
115
+ homepage: ''
116
+ licenses:
117
+ - MIT
118
+ metadata:
119
+ allowed_push_host: https://rubygems.org
120
+ post_install_message:
121
+ rdoc_options: []
122
+ require_paths:
123
+ - lib
124
+ required_ruby_version: !ruby/object:Gem::Requirement
125
+ requirements:
126
+ - - ">="
127
+ - !ruby/object:Gem::Version
128
+ version: '0'
129
+ required_rubygems_version: !ruby/object:Gem::Requirement
130
+ requirements:
131
+ - - ">="
132
+ - !ruby/object:Gem::Version
133
+ version: '0'
134
+ requirements: []
135
+ rubyforge_project:
136
+ rubygems_version: 2.5.1
137
+ signing_key:
138
+ specification_version: 4
139
+ summary: Extracts file metadata information (currently only supports exif metadata
140
+ for jpeg files)
141
+ test_files: []