Skip to content

Commit

Permalink
feat: typecast all tags
Browse files Browse the repository at this point in the history
BREAKING CHANGE: Instead of returning String, all tags are now typecasted according to TIFF tag. Tags for time like DateTimeOriginal is nolonger typecasted to Time object.
To convert to Time object, use `Time::strptime` instea, for example:

    ```ruby
    Time.strptime(data.date_time_original, '%Y:%m:%d %H:%M:%S')
    ```
  • Loading branch information
tonytonyjan committed Sep 12, 2017
1 parent e7748ac commit 95c1185
Show file tree
Hide file tree
Showing 3 changed files with 144 additions and 83 deletions.
124 changes: 57 additions & 67 deletions ext/exif/data.c
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,9 @@

#include <libexif/exif-data.h>
#include <time.h>
#include <stdint.h>

extern VALUE rb_mExif, rb_eError, rb_eNotReadble, rb_eIFDNotFound, rb_eUnknownDataType;
extern VALUE rb_mExif, rb_eNotReadble, rb_eIFDNotFound;
extern const char* exif_entry_to_ivar(ExifEntry* entry);

VALUE rb_cData;
Expand All @@ -16,7 +17,7 @@ static VALUE new(VALUE self, VALUE file_path);
static VALUE dump(VALUE self);
static void each_content(ExifContent *ec, void *user_data);
static void each_entry(ExifEntry *, void *user_data);
static VALUE exif_entry_to_value(ExifEntry *);
static VALUE exif_entry_to_rb_value(ExifEntry *);

void init_data(){
int length;
Expand Down Expand Up @@ -54,31 +55,29 @@ static void each_content(ExifContent *ec, void *self_ptr){
VALUE *self;
ExifIfd ifd;

self = (VALUE*)self_ptr;
ifd = exif_content_get_ifd(ec);
if(ifd == EXIF_IFD_COUNT) rb_raise(rb_eIFDNotFound, "Con't get IFD.");

exif_content_foreach_entry(ec, each_entry, self);
exif_content_foreach_entry(ec, each_entry, self_ptr);
}

static void each_entry(ExifEntry *entry, void *self_ptr){
VALUE *self, value;
VALUE value;
const char *ivar_name;

self = (VALUE*)self_ptr;
ivar_name = exif_entry_to_ivar(entry);
value = exif_entry_to_value(entry);
value = exif_entry_to_rb_value(entry);

rb_iv_set(*self, ivar_name, value);
rb_iv_set(*(VALUE*)self_ptr, ivar_name, value);
}

static VALUE exif_entry_to_value(ExifEntry *entry){
static VALUE exif_entry_to_rb_value(ExifEntry *entry){
ExifData *data;
ExifByteOrder order;
ExifRational rational;
ExifSRational srational;
VALUE ret;
int i;
size_t len, i;
unsigned char size;
struct tm tm = {};

Expand All @@ -88,91 +87,82 @@ static VALUE exif_entry_to_value(ExifEntry *entry){
size = exif_format_get_size(entry->format);

switch(entry->format){
case EXIF_FORMAT_ASCII:
switch((int)entry->tag){
case EXIF_TAG_DATE_TIME:
case EXIF_TAG_DATE_TIME_ORIGINAL:
if(strptime((const char*)entry->data, "%Y:%m:%d %T", &tm) != NULL)
ret = rb_time_new(mktime(&tm), 0);
else rb_raise(rb_eError, "wrong date time format");
break;
case EXIF_TAG_GPS_DATE_STAMP:
if(strptime((const char*)entry->data, "%Y:%m:%d", &tm) != NULL)
ret = rb_time_new(mktime(&tm), 0);
else rb_raise(rb_eError, "wrong date time format");
break;
default:
ret = rb_str_new2((const char *)entry->data);
}
case EXIF_FORMAT_UNDEFINED:
ret = Qnil;
break;
case EXIF_FORMAT_BYTE:
if(entry->components > 1){
ret = rb_ary_new2(entry->components);
for (i = 0; i < entry->components; i++)
rb_ary_push(ret, INT2FIX((uint8_t)entry->data[i]));
}else ret = INT2FIX((uint8_t)entry->data[0]);
case EXIF_FORMAT_SBYTE:
if(entry->components > 1){
ret = rb_ary_new2(entry->components);
for (i = 0; i < entry->components; i++)
rb_ary_push(ret, INT2FIX((int8_t)entry->data[i]));
}else ret = INT2FIX((int8_t)entry->data[0]);
break;
case EXIF_FORMAT_SHORT:
if(entry->components > 1){
ret = rb_ary_new2(entry->components);
for(int i = 0; i < entry->components; i++)
for(i = 0; i < entry->components; i++)
rb_ary_push(ret, INT2FIX(exif_get_short(entry->data + i*size, order)));
}else ret = INT2FIX(exif_get_short(entry->data, order));
break;
case EXIF_FORMAT_SSHORT:
if(entry->components > 1){
ret = rb_ary_new2(entry->components);
for(int i = 0; i < entry->components; i++)
for(i = 0; i < entry->components; i++)
rb_ary_push(ret, INT2FIX(exif_get_sshort(entry->data + i*size, order)));
}else ret = INT2FIX(exif_get_sshort(entry->data, order));
break;
case EXIF_FORMAT_RATIONAL:
case EXIF_FORMAT_LONG:
if(entry->components > 1){
ret = rb_ary_new2(entry->components);
for(int i = 0; i < entry->components; i++){
srational = exif_get_srational(entry->data + i*size, order);
rb_ary_push(ret, rb_rational_new(LONG2FIX(srational.numerator), LONG2FIX(srational.denominator)));
}
}else{
srational = exif_get_srational(entry->data, order);
ret = rb_rational_new(LONG2FIX(srational.numerator), LONG2FIX(srational.denominator));
}
for(i = 0; i < entry->components; i++)
rb_ary_push(ret, ULONG2NUM(exif_get_long(entry->data + i*size, order)));
}else ret = ULONG2NUM(exif_get_long(entry->data, order));
break;
case EXIF_FORMAT_SRATIONAL:
case EXIF_FORMAT_SLONG:
if(entry->components > 1){
ret = rb_ary_new2(entry->components);
for(int i = 0; i < entry->components; i++){
rational = exif_get_rational(entry->data + i*size, order);
rb_ary_push(ret, rb_rational_new(LONG2FIX(rational.numerator), LONG2FIX(rational.denominator)));
}
}else{
rational = exif_get_rational(entry->data, order);
ret = rb_rational_new(LONG2FIX(rational.numerator), LONG2FIX(rational.denominator));
}
for(i = 0; i < entry->components; i++)
rb_ary_push(ret, LONG2NUM(exif_get_slong(entry->data + i*size, order)));
}else ret = LONG2NUM(exif_get_slong(entry->data, order));
break;
case EXIF_FORMAT_BYTE:
case EXIF_FORMAT_SBYTE:
ret = rb_str_new((const char *)entry->data, entry->size);
case EXIF_FORMAT_ASCII:
ret = rb_str_new2((const char *)entry->data);
break;
case EXIF_FORMAT_LONG:
case EXIF_FORMAT_RATIONAL:
if(entry->components > 1){
ret = rb_ary_new2(entry->components);
for(int i = 0; i < entry->components; i++)
rb_ary_push(ret, LONG2NUM(exif_get_long(entry->data + i*size, order)));
}else ret = LONG2NUM(exif_get_long(entry->data, order));
for(i = 0; i < entry->components; i++){
rational = exif_get_rational(entry->data + i * size, order);
rb_ary_push(ret, rb_rational_new(ULONG2NUM(rational.numerator), ULONG2NUM(rational.denominator)));
}
} else {
rational = exif_get_rational(entry->data, order);
ret = rb_rational_new(ULONG2NUM(rational.numerator), ULONG2NUM(rational.denominator));
}
break;
case EXIF_FORMAT_SLONG:
case EXIF_FORMAT_SRATIONAL:
if(entry->components > 1){
ret = rb_ary_new2(entry->components);
for(int i = 0; i < entry->components; i++)
rb_ary_push(ret, LONG2NUM(exif_get_slong(entry->data + i*size, order)));
}else ret = LONG2NUM(exif_get_slong(entry->data, order));
break;
case EXIF_FORMAT_FLOAT:
// TODO
for(int i = 0; i < entry->components; i++){
srational = exif_get_srational(entry->data + i * size, order);
rb_ary_push(ret, rb_rational_new(LONG2FIX(srational.numerator), LONG2FIX(srational.denominator)));
}
} else {
srational = exif_get_srational(entry->data, order);
ret = rb_rational_new(LONG2FIX(srational.numerator), LONG2FIX(srational.denominator));
}
break;
case EXIF_FORMAT_DOUBLE:
// TODO
break;
case EXIF_FORMAT_UNDEFINED:
// TODO
case EXIF_FORMAT_FLOAT:
ret = rb_float_new(*(double*)entry->data);
break;
}

// if(NIL_P(ret)) rb_raise(rb_eUnknownDataType, "Unknown data type");

return ret;
}
}
4 changes: 2 additions & 2 deletions ext/exif/exif_entry_to_ivar.c
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
#include <libexif/exif-data.h>
#include <libexif/exif-entry.h>

const char* exif_entry_to_ivar(ExifEntry* ee){
ExifIfd ifd;
Expand Down Expand Up @@ -448,4 +448,4 @@ const char* exif_entry_to_ivar(ExifEntry* ee){
break;
}
return 0;
}
}
99 changes: 85 additions & 14 deletions test/test_exif.rb
Original file line number Diff line number Diff line change
Expand Up @@ -4,25 +4,96 @@
require 'exif'

class TestExif < Minitest::Test
def test_it_works
data = Exif::Data.new(File.expand_path('../sample.jpg', __FILE__))
assert_equal data.model, 'NIKON D600'
assert_equal data.image_width, 4000
assert_equal data.bits_per_sample, [8, 8, 8]
assert_equal data.gps_latitude, [Rational(24, 1), Rational(106_817, 10_000), Rational(0, 1)]
assert_equal data.gps_time_stamp, [Rational(8, 1), Rational(4, 1), Rational(25, 1)]
assert_equal data.gps_altitude, Rational(332, 1)
assert_equal data.pixel_x_dimension, 4000
assert_equal data.gps_version_id, "\x02\x02\x00\x00"
assert_equal data.gps_altitude_ref, "\x00"
assert_equal data.date_time, Time.new(2013, 12, 8, 21, 14, 11)
assert_equal data.date_time_original, Time.new(2013, 9, 10, 16, 31, 21)
assert_equal data.gps_date_stamp, Time.new(2013, 9, 10)
def test_byte
assert_equal 0, data.gps_altitude_ref
assert_equal [2, 2, 0, 0], data.gps_version_id
end

def test_short
assert_equal 4000, data.image_width
assert_equal 2670, data.image_length
assert_equal [8, 8, 8], data.bits_per_sample
assert_equal 2, data.photometric_interpretation
assert_equal 1, data.orientation
assert_equal 3, data.samples_per_pixel
assert_equal 2, data.resolution_unit
assert_equal 6, data.compression
assert_equal 3, data.exposure_program
assert_equal 250, data.iso_speed_ratings
assert_equal 2, data.metering_mode
assert_equal 9, data.light_source
assert_equal 16, data.flash
assert_equal 1, data.color_space
assert_equal 3, data.focal_plane_resolution_unit
assert_equal 2, data.sensing_method
assert_equal 0, data.custom_rendered
assert_equal 0, data.exposure_mode
assert_equal 1, data.white_balance
assert_equal 70, data.focal_length_in_35mm_film
assert_equal 0, data.scene_capture_type
assert_equal 0, data.gain_control
assert_equal 0, data.contrast
assert_equal 0, data.saturation
assert_equal 0, data.sharpness
assert_equal 0, data.subject_distance_range
end

def test_long
assert_equal 4000, data.pixel_x_dimension
assert_equal 2670, data.pixel_y_dimension
end

def test_ascii
assert_equal 'NIKON CORPORATION', data.make
assert_equal 'NIKON D600', data.model
assert_equal 'Adobe Photoshop CS6 (Macintosh)', data.software
assert_equal '2013:12:08 21:14:11', data.date_time
assert_equal '2013:09:10 16:31:21', data.date_time_original
assert_equal '2013:09:10 16:31:21', data.date_time_digitized
assert_equal '90', data.sub_sec_time_original
assert_equal '90', data.sub_sec_time_digitized
assert_equal 'N', data.gps_latitude_ref
assert_equal 'E', data.gps_longitude_ref
assert_equal 'WGS-84', data.gps_map_datum
assert_equal '2013:09:10', data.gps_date_stamp
end

def test_rational
# assert_equal Rational(72, 1), data.x_resolution
# assert_equal Rational(72, 1), data.y_resolution
assert_equal Rational(1, 125), data.exposure_time
assert_equal Rational(8, 1), data.fnumber
assert_equal Rational(870723, 125000), data.shutter_speed_value
assert_equal Rational(6, 1), data.aperture_value
assert_equal Rational(-1, 3), data.exposure_bias_value
assert_equal Rational(3, 1), data.max_aperture_value
assert_equal Rational(70, 1), data.focal_length
assert_equal Rational(54886891, 32768), data.focal_plane_x_resolution
assert_equal Rational(54886891, 32768), data.focal_plane_y_resolution
assert_equal Rational(1, 1), data.digital_zoom_ratio
assert_equal [Rational(24, 1), Rational(106817, 10000), Rational(0, 1)], data.gps_latitude
assert_equal [Rational(121, 1), Rational(76869, 2500), Rational(0, 1)], data.gps_longitude
assert_equal Rational(332, 1), data.gps_altitude
assert_equal [Rational(8, 1), Rational(4, 1), Rational(25, 1)], data.gps_time_stamp
end

def test_double
# TODO
end

def test_float
# TODO
end

def test_not_readble
assert_raises Exif::NotReadble do
Exif::Data.new(File.expand_path('../not_readable.jpg', __FILE__))
end
end

private

def data
@data ||= Exif::Data.new(File.expand_path('../sample.jpg', __FILE__))
end
end

0 comments on commit 95c1185

Please sign in to comment.