file_data 4.0.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.
@@ -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: []