exifr 0.9
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.
- data/README +12 -0
- data/lib/exif.rb +146 -0
- data/lib/exifr.rb +5 -0
- data/lib/jpeg.rb +69 -0
- data/lib/tiff_header.rb +75 -0
- metadata +49 -0
data/README
ADDED
@@ -0,0 +1,12 @@
|
|
1
|
+
= EXIF Reader
|
2
|
+
EXIF Reader is a module to read EXIF from JPEG images.
|
3
|
+
|
4
|
+
== Examples
|
5
|
+
EXIFR::JPEG.new('IMG_3422.JPG').width # -> 2272
|
6
|
+
EXIFR::JPEG.new('IMG_3422.JPG').exif.model # -> "Canon PowerShot G3"
|
7
|
+
|
8
|
+
== Author
|
9
|
+
R.W. van 't Veer
|
10
|
+
|
11
|
+
== Copyright
|
12
|
+
Copyright (c) 2006 R.W. van 't Veer
|
data/lib/exif.rb
ADDED
@@ -0,0 +1,146 @@
|
|
1
|
+
# Copyright (c) 2006 - R.W. van 't Veer
|
2
|
+
|
3
|
+
module EXIFR
|
4
|
+
# = EXIF decoder
|
5
|
+
class EXIF < Hash
|
6
|
+
@@TAGS = {
|
7
|
+
0x0100 => :image_width,
|
8
|
+
0x0101 => :image_length,
|
9
|
+
0x0102 => :bits_per_sample,
|
10
|
+
0x0103 => :compression,
|
11
|
+
0x0106 => :photometric_interpretation,
|
12
|
+
0x010a => :fill_order,
|
13
|
+
0x010d => :document_name,
|
14
|
+
0x010e => :image_description,
|
15
|
+
0x010f => :make,
|
16
|
+
0x0110 => :model,
|
17
|
+
0x0111 => :strip_offsets,
|
18
|
+
0x0112 => :orientation,
|
19
|
+
0x0115 => :samples_per_pixel,
|
20
|
+
0x0116 => :rows_per_strip,
|
21
|
+
0x0117 => :strip_byte_counts,
|
22
|
+
0x011a => :xresolution,
|
23
|
+
0x011b => :yresolution,
|
24
|
+
0x011c => :planar_configuration,
|
25
|
+
0x0128 => :resolution_unit,
|
26
|
+
0x012d => :transfer_function,
|
27
|
+
0x0131 => :software,
|
28
|
+
0x0132 => :date_time,
|
29
|
+
0x013b => :artist,
|
30
|
+
0x013e => :white_point,
|
31
|
+
0x013f => :primary_chromaticities,
|
32
|
+
0x0156 => :transfer_range,
|
33
|
+
0x0200 => :jpegproc,
|
34
|
+
0x0201 => :jpeginterchange_format,
|
35
|
+
0x0202 => :jpeginterchange_format_length,
|
36
|
+
0x0211 => :ycb_cr_coefficients,
|
37
|
+
0x0212 => :ycb_cr_sub_sampling,
|
38
|
+
0x0213 => :ycb_cr_positioning,
|
39
|
+
0x0214 => :reference_black_white,
|
40
|
+
0x828d => :cfarepeat_pattern_dim,
|
41
|
+
0x828e => :cfapattern,
|
42
|
+
0x828f => :battery_level,
|
43
|
+
0x8298 => :copyright,
|
44
|
+
0x829a => :exposure_time,
|
45
|
+
0x829d => :fnumber,
|
46
|
+
0x83bb => :iptc_naa,
|
47
|
+
0x8769 => :exif_offset,
|
48
|
+
0x8773 => :inter_color_profile,
|
49
|
+
0x8822 => :exposure_program,
|
50
|
+
0x8824 => :spectral_sensitivity,
|
51
|
+
0x8825 => :gpsinfo,
|
52
|
+
0x8827 => :isospeed_ratings,
|
53
|
+
0x8828 => :oecf,
|
54
|
+
0x9000 => :exif_version,
|
55
|
+
0x9003 => :date_time_original,
|
56
|
+
0x9004 => :date_time_digitized,
|
57
|
+
0x9101 => :components_configuration,
|
58
|
+
0x9102 => :compressed_bits_per_pixel,
|
59
|
+
0x9201 => :shutter_speed_value,
|
60
|
+
0x9202 => :aperture_value,
|
61
|
+
0x9203 => :brightness_value,
|
62
|
+
0x9204 => :exposure_bias_value,
|
63
|
+
0x9205 => :max_aperture_value,
|
64
|
+
0x9206 => :subject_distance,
|
65
|
+
0x9207 => :metering_mode,
|
66
|
+
0x9208 => :light_source,
|
67
|
+
0x9209 => :flash,
|
68
|
+
0x920a => :focal_length,
|
69
|
+
0x9214 => :subject_area,
|
70
|
+
0x927c => :maker_note,
|
71
|
+
0x9286 => :user_comment,
|
72
|
+
0x9290 => :subsec_time,
|
73
|
+
0x9291 => :subsec_time_orginal,
|
74
|
+
0x9292 => :subsec_time_digitized,
|
75
|
+
0xa000 => :flash_pix_version,
|
76
|
+
0xa001 => :color_space,
|
77
|
+
0xa002 => :pixel_xdimension,
|
78
|
+
0xa003 => :pixel_ydimension,
|
79
|
+
0xa004 => :related_sound_file,
|
80
|
+
0xa005 => :interoperability_offset,
|
81
|
+
0xa20b => :flash_energy,
|
82
|
+
0xa20c => :spatial_frequency_response,
|
83
|
+
0xa20e => :focal_plane_xresolution,
|
84
|
+
0xa20f => :focal_plane_yresolution,
|
85
|
+
0xa210 => :focal_plane_resolution_unit,
|
86
|
+
0xa214 => :subject_location,
|
87
|
+
0xa215 => :exposure_index,
|
88
|
+
0xa217 => :sensing_method,
|
89
|
+
0xa300 => :file_source,
|
90
|
+
0xa301 => :scene_type,
|
91
|
+
0xa302 => :cfapattern,
|
92
|
+
0xa401 => :custom_rendered,
|
93
|
+
0xa402 => :exposure_mode,
|
94
|
+
0xa403 => :white_balance,
|
95
|
+
0xa404 => :digital_zoom_ratio,
|
96
|
+
0xa405 => :focal_len_in_35mm_film,
|
97
|
+
0xa406 => :scene_capture_type,
|
98
|
+
0xa407 => :gain_control,
|
99
|
+
0xa408 => :contrast,
|
100
|
+
0xa409 => :saturation,
|
101
|
+
0xa40a => :sharpness,
|
102
|
+
0xa40b => :device_setting_descr,
|
103
|
+
0xa40c => :subject_dist_range,
|
104
|
+
0xa420 => :image_unique_id
|
105
|
+
}
|
106
|
+
@@EXIF_HEADERS = [0x8769, 0x8825, 0xa005]
|
107
|
+
@@TIME_TAGS = [:date_time, :date_time_original, :date_time_digitized]
|
108
|
+
|
109
|
+
# +data+ the content of the JPEG APP1 frame without the EXIF marker
|
110
|
+
def initialize(data)
|
111
|
+
@data = data
|
112
|
+
traverse(TiffHeader.new(@data))
|
113
|
+
freeze
|
114
|
+
end
|
115
|
+
|
116
|
+
# convience
|
117
|
+
def method_missing(method, *args)
|
118
|
+
self[method]
|
119
|
+
end
|
120
|
+
|
121
|
+
private
|
122
|
+
def traverse(tiff)
|
123
|
+
tiff.fields.each do |f|
|
124
|
+
tag, value = @@TAGS[f.tag], f.value
|
125
|
+
value = (value.kind_of?(Array) && value.size == 1) ? value.first : value
|
126
|
+
if @@EXIF_HEADERS.include?(f.tag)
|
127
|
+
traverse(TiffHeader.new(@data, f.offset))
|
128
|
+
elsif tag
|
129
|
+
if @@TIME_TAGS.include? tag
|
130
|
+
self[tag] = value2time(value)
|
131
|
+
else
|
132
|
+
self[tag] = value
|
133
|
+
end
|
134
|
+
end
|
135
|
+
end
|
136
|
+
end
|
137
|
+
|
138
|
+
def value2time(value)
|
139
|
+
if value.sub(/\000.*$/, '') =~ /^(\d{4}):(\d\d):(\d\d) (\d\d):(\d\d):(\d\d)$/
|
140
|
+
Time.mktime($1, $2, $3, $4, $5, $6)
|
141
|
+
else
|
142
|
+
value
|
143
|
+
end
|
144
|
+
end
|
145
|
+
end
|
146
|
+
end
|
data/lib/exifr.rb
ADDED
data/lib/jpeg.rb
ADDED
@@ -0,0 +1,69 @@
|
|
1
|
+
# Copyright (c) 2006 - R.W. van 't Veer
|
2
|
+
|
3
|
+
require 'rational'
|
4
|
+
|
5
|
+
module EXIFR
|
6
|
+
# = JPEG decoder
|
7
|
+
#
|
8
|
+
# == Examples
|
9
|
+
# EXIFR::JPEG.new('IMG_3422.JPG').width # -> 2272
|
10
|
+
# EXIFR::JPEG.new('IMG_3422.JPG').exif.model # -> "Canon PowerShot G3"
|
11
|
+
class JPEG
|
12
|
+
# image height
|
13
|
+
attr_reader :height
|
14
|
+
# image width
|
15
|
+
attr_reader :width
|
16
|
+
# number of bits per ???
|
17
|
+
attr_reader :bits
|
18
|
+
# image comment
|
19
|
+
attr_reader :comment
|
20
|
+
# hash of exif data if available
|
21
|
+
attr_reader :exif
|
22
|
+
|
23
|
+
# +file+ is a filename or an IO object
|
24
|
+
def initialize(file)
|
25
|
+
if file.kind_of? IO
|
26
|
+
examine(file)
|
27
|
+
else
|
28
|
+
File.open(file, 'rb') { |io| examine(io) }
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
# returns +true+ when EXIF data is available
|
33
|
+
def exif?
|
34
|
+
!exif.nil?
|
35
|
+
end
|
36
|
+
|
37
|
+
private
|
38
|
+
def examine(io)
|
39
|
+
raise 'malformed JPEG' unless io.getc == 0xFF && io.getc == 0xD8 # SOI
|
40
|
+
|
41
|
+
class << io
|
42
|
+
def readint; (readchar << 8) + readchar; end
|
43
|
+
def readframe; read(readint - 2); end
|
44
|
+
def readsof; [readint, readchar, readint, readint, readchar]; end
|
45
|
+
def next
|
46
|
+
c = readchar while c != 0xFF
|
47
|
+
c = readchar while c == 0xFF
|
48
|
+
c
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
while marker = io.next
|
53
|
+
case marker
|
54
|
+
when 0xC0..0xC3, 0xC5..0xC7, 0xC9..0xCB, 0xCD..0xCF # SOF markers
|
55
|
+
length, @bits, @height, @width, components = io.readsof
|
56
|
+
raise 'malformed JPEG' unless length == 8 + components * 3
|
57
|
+
when 0xD9, 0xDA: break # EOI, SOS
|
58
|
+
when 0xFE: @comment = io.readframe # COM
|
59
|
+
when 0xE1: app1 = io.readframe # APP1, contains EXIF tag
|
60
|
+
else io.readframe # ignore frame
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
if app1 && EXIF
|
65
|
+
@exif = EXIF.new(app1[6..-1]) # rescue nil
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
data/lib/tiff_header.rb
ADDED
@@ -0,0 +1,75 @@
|
|
1
|
+
# Copyright (c) 2006 - R.W. van 't Veer
|
2
|
+
|
3
|
+
module EXIFR
|
4
|
+
class TiffHeader # :nodoc:
|
5
|
+
attr_reader :data, :fields
|
6
|
+
|
7
|
+
def initialize(data, offset = nil)
|
8
|
+
@data = data
|
9
|
+
@fields = []
|
10
|
+
|
11
|
+
class << @data
|
12
|
+
attr_accessor :short, :long
|
13
|
+
def readshort(pos); self[pos..(pos + 1)].unpack(@short)[0]; end
|
14
|
+
def readlong(pos); self[pos..(pos + 3)].unpack(@long)[0]; end
|
15
|
+
end
|
16
|
+
|
17
|
+
case @data[0..1]
|
18
|
+
when 'II'; @data.short, @data.long = 'v', 'V'
|
19
|
+
when 'MM'; @data.short, @data.long = 'n', 'N'
|
20
|
+
else; raise 'no II or MM marker found'
|
21
|
+
end
|
22
|
+
|
23
|
+
readIfds(offset || @data.readlong(4))
|
24
|
+
end
|
25
|
+
|
26
|
+
def readIfds(pos)
|
27
|
+
while pos != 0 do
|
28
|
+
num = @data.readshort(pos)
|
29
|
+
pos += 2
|
30
|
+
|
31
|
+
num.times do
|
32
|
+
fields << TiffField.new(@data, pos)
|
33
|
+
pos += 12
|
34
|
+
end
|
35
|
+
|
36
|
+
pos = @data.readlong(pos)
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
class TiffField # :nodoc:
|
42
|
+
attr_reader :tag, :offset, :value
|
43
|
+
|
44
|
+
def initialize(data, pos)
|
45
|
+
@tag, count, @offset = data.readshort(pos), data.readlong(pos + 4), data.readlong(pos + 8)
|
46
|
+
|
47
|
+
case data.readshort(pos + 2)
|
48
|
+
when 1, 6 # byte, signed byte
|
49
|
+
# TODO handle signed bytes
|
50
|
+
len, pack = count, proc { |d| d }
|
51
|
+
when 2 # ascii
|
52
|
+
len, pack = count, proc { |d| d.sub(/\000.*$/, '').strip }
|
53
|
+
when 3, 8 # short, signed short
|
54
|
+
# TODO handle signed
|
55
|
+
len, pack = count * 2, proc { |d| d.unpack(data.short + '*') }
|
56
|
+
when 4, 9 # long, signed long
|
57
|
+
# TODO handle signed
|
58
|
+
len, pack = count * 4, proc { |d| d.unpack(data.long + '*') }
|
59
|
+
when 5, 10
|
60
|
+
len, pack = count * 8, proc do |d|
|
61
|
+
r = []
|
62
|
+
d.unpack(data.long + '*').each_with_index do |v,i|
|
63
|
+
i % 2 == 0 ? r << [v] : r.last << v
|
64
|
+
end
|
65
|
+
r.map{|f| Rational.reduce(*f)}
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
if len && pack
|
70
|
+
start = len > 4 ? @offset : (pos + 8)
|
71
|
+
@value = pack[data[start..(start + len)]]
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
metadata
ADDED
@@ -0,0 +1,49 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
rubygems_version: 0.8.11
|
3
|
+
specification_version: 1
|
4
|
+
name: exifr
|
5
|
+
version: !ruby/object:Gem::Version
|
6
|
+
version: "0.9"
|
7
|
+
date: 2006-04-14 00:00:00 +02:00
|
8
|
+
summary: EXIF Reader is a module to read EXIF from JPEG images.
|
9
|
+
require_paths:
|
10
|
+
- lib
|
11
|
+
email: remco@remvee.net
|
12
|
+
homepage: http://exifr.rubyforge.org/
|
13
|
+
rubyforge_project:
|
14
|
+
description:
|
15
|
+
autorequire: exifr
|
16
|
+
default_executable:
|
17
|
+
bindir: bin
|
18
|
+
has_rdoc: true
|
19
|
+
required_ruby_version: !ruby/object:Gem::Version::Requirement
|
20
|
+
requirements:
|
21
|
+
- - ">"
|
22
|
+
- !ruby/object:Gem::Version
|
23
|
+
version: 0.0.0
|
24
|
+
version:
|
25
|
+
platform: ruby
|
26
|
+
signing_key:
|
27
|
+
cert_chain:
|
28
|
+
authors:
|
29
|
+
- R.W. van 't Veer
|
30
|
+
files:
|
31
|
+
- lib/exif.rb
|
32
|
+
- lib/exifr.rb
|
33
|
+
- lib/jpeg.rb
|
34
|
+
- lib/tiff_header.rb
|
35
|
+
- README
|
36
|
+
test_files: []
|
37
|
+
|
38
|
+
rdoc_options: []
|
39
|
+
|
40
|
+
extra_rdoc_files:
|
41
|
+
- README
|
42
|
+
executables: []
|
43
|
+
|
44
|
+
extensions: []
|
45
|
+
|
46
|
+
requirements: []
|
47
|
+
|
48
|
+
dependencies: []
|
49
|
+
|