Skip to content

Commit

Permalink
feat: make Exif::Data#new accept either String or IO instance
Browse files Browse the repository at this point in the history
It makes passing image data from memory possible.

BREAKING CHANGE: Passing String to Data#new is no longer treated as a file path, instead, it loads as binary data. Therefore, `Data.new(path)` has to be changed to `Data.new(File.open(path))` or `Data.new(IO.read(path))`.
  • Loading branch information
tonytonyjan committed Sep 12, 2017
1 parent 95c1185 commit d6af39a
Show file tree
Hide file tree
Showing 2 changed files with 119 additions and 83 deletions.
27 changes: 20 additions & 7 deletions ext/exif/data.c
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
#include "ruby.h"

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

Expand All @@ -13,7 +14,7 @@ VALUE rb_cData;

static const char* attrs[] = {"aperture_value", "artist", "battery_level", "bits_per_sample", "brightness_value", "cfa_pattern", "cfa_repeat_pattern_dim", "color_space", "components_configuration", "compressed_bits_per_pixel", "compression", "contrast", "copyright", "custom_rendered", "date_time", "date_time_digitized", "date_time_original", "device_setting_description", "digital_zoom_ratio", "document_name", "exif_ifd_pointer", "exif_version", "exposure_bias_value", "exposure_index", "exposure_mode", "exposure_program", "exposure_time", "file_source", "fill_order", "flash", "flash_energy", "flash_pix_version", "fnumber", "focal_length", "focal_length_in_35mm_film", "focal_plane_resolution_unit", "focal_plane_x_resolution", "focal_plane_y_resolution", "gain_control", "gamma", "gps_altitude", "gps_altitude_ref", "gps_area_information", "gps_date_stamp", "gps_dest_bearing", "gps_dest_bearing_ref", "gps_dest_distance", "gps_dest_distance_ref", "gps_dest_latitude", "gps_dest_latitude_ref", "gps_dest_longitude", "gps_dest_longitude_ref", "gps_differential", "gps_dop", "gps_img_direction", "gps_img_direction_ref", "gps_info_ifd_pointer", "gps_latitude", "gps_latitude_ref", "gps_longitude", "gps_longitude_ref", "gps_map_datum", "gps_measure_mode", "gps_processing_method", "gps_satellites", "gps_speed", "gps_speed_ref", "gps_status", "gps_time_stamp", "gps_track", "gps_track_ref", "gps_version_id", "image_description", "image_length", "image_resources", "image_unique_id", "image_width", "inter_color_profile", "interoperability_ifd_pointer", "interoperability_index", "interoperability_version", "iptc_naa", "iso_speed_ratings", "jpeg_interchange_format", "jpeg_interchange_format_length", "jpeg_proc", "light_source", "make", "maker_note", "max_aperture_value", "metering_mode", "model", "new_cfa_pattern", "new_subfile_type", "oecf", "orientation", "padding", "photometric_interpretation", "pixel_x_dimension", "pixel_y_dimension", "planar_configuration", "primary_chromaticities", "print_image_matching", "reference_black_white", "related_image_file_format", "related_image_length", "related_image_width", "related_sound_file", "resolution_unit", "rows_per_strip", "samples_per_pixel", "saturation", "scene_capture_type", "scene_type", "sensing_method", "sharpness", "shutter_speed_value", "software", "spatial_frequency_response", "spectral_sensitivity", "strip_byte_counts", "strip_offsets", "sub_ifds", "sub_sec_time", "sub_sec_time_digitized", "sub_sec_time_original", "subject_area", "subject_distance", "subject_distance_range", "subject_location", "tiff_ep_standard_id", "time_zone_offset", "transfer_function", "transfer_range", "user_comment", "white_balance", "white_point", "x_resolution", "xml_packet", "xp_author", "xp_comment", "xp_keywords", "xp_subject", "xp_title", "y_resolution", "ycbcr_coefficients", "ycbcr_positioning", "ycbcr_sub_sampling"};

static VALUE new(VALUE self, VALUE file_path);
static VALUE new(VALUE self, VALUE input);
static VALUE dump(VALUE self);
static void each_content(ExifContent *ec, void *user_data);
static void each_entry(ExifEntry *, void *user_data);
Expand All @@ -30,16 +31,28 @@ void init_data(){
rb_define_method(rb_cData, "dump", dump, 0);
}

VALUE new(VALUE self, VALUE file_path){
Check_Type(file_path, T_STRING);

VALUE new(VALUE self, VALUE input){
ExifData* ed;
VALUE rb_data;
VALUE read_data;

switch (TYPE(input)) {
case T_STRING:
read_data = input;
break;
case T_FILE:
read_data = rb_funcall(input, rb_intern("read"), 0);
break;
default:
rb_raise(rb_eTypeError, "wrong argument type %s (expected String or IO)", rb_obj_classname(input));
}

ed = exif_data_new_from_file(StringValueCStr(file_path));
ExifLoader *loader = exif_loader_new();
exif_loader_write(loader, (unsigned char*)RSTRING_PTR(read_data), RSTRING_LEN(read_data));
ed = exif_loader_get_data(loader);
exif_loader_unref (loader);
if(!ed) rb_raise(rb_eNotReadble, "File not readable or no EXIF data in file.");

rb_data = Data_Wrap_Struct(self, NULL, exif_data_free, ed);
VALUE rb_data = Data_Wrap_Struct(self, NULL, exif_data_free, ed);
exif_data_foreach_content(ed, each_content, &rb_data);
return rb_data;
}
Expand Down
175 changes: 99 additions & 76 deletions test/test_exif.rb
Original file line number Diff line number Diff line change
Expand Up @@ -4,96 +4,119 @@
require 'exif'

class TestExif < Minitest::Test
def test_byte
assert_equal 0, data.gps_altitude_ref
assert_equal [2, 2, 0, 0], data.gps_version_id
end
module Shared
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_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_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_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
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 data
raise NotImplementedError
end
end

def test_double
# TODO
class TestLoadFromString < Minitest::Test
include Shared

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

def test_float
# TODO
class TestLoadFromIO < Minitest::Test
include Shared

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

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

private

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

0 comments on commit d6af39a

Please sign in to comment.