exifr 0.9
Sign up to get free protection for your applications and to get access to all the features.
- 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
|
+
|