diff options
Diffstat (limited to 'src/camera/GPhoto.vala')
-rw-r--r-- | src/camera/GPhoto.vala | 367 |
1 files changed, 367 insertions, 0 deletions
diff --git a/src/camera/GPhoto.vala b/src/camera/GPhoto.vala new file mode 100644 index 0000000..a1a46cb --- /dev/null +++ b/src/camera/GPhoto.vala @@ -0,0 +1,367 @@ +/* Copyright 2009-2014 Yorba Foundation + * + * This software is licensed under the GNU LGPL (version 2.1 or later). + * See the COPYING file in this distribution. + */ + +public errordomain GPhotoError { + LIBRARY +} + +namespace GPhoto { + // ContextWrapper assigns signals to the various GPhoto.Context callbacks, as well as spins + // the event loop at opportune times. + public class ContextWrapper { + public Context context = new Context(); + + public ContextWrapper() { + context.set_idle_func(on_idle); + context.set_error_func(on_error); + context.set_status_func(on_status); + context.set_message_func(on_message); + context.set_progress_funcs(on_progress_start, on_progress_update, on_progress_stop); + } + + public virtual void idle() { + } + +#if WITH_GPHOTO_25 + + public virtual void error(string text, void *data) { + } + + public virtual void status(string text, void *data) { + } + + public virtual void message(string text, void *data) { + } + + public virtual void progress_start(float current, string text, void *data) { + } + + public virtual void progress_update(float current, void *data) { + } + + public virtual void progress_stop() { + } + + private void on_idle(Context context) { + idle(); + } + + private void on_error(Context context, string text) { + error(text, null); + } + + private void on_status(Context context, string text) { + status(text, null); + } + + private void on_message(Context context, string text) { + message(text, null); + } + + private uint on_progress_start(Context context, float target, string text) { + progress_start(target, text, null); + + return 0; + } + + private void on_progress_update(Context context, uint id, float current) { + progress_update(current, null); + } + + private void on_progress_stop(Context context, uint id) { + progress_stop(); + } + +#else + + public virtual void error(string format, void *va_list) { + } + + public virtual void status(string format, void *va_list) { + } + + public virtual void message(string format, void *va_list) { + } + + public virtual void progress_start(float target, string format, void *va_list) { + } + + public virtual void progress_update(float current) { + } + + public virtual void progress_stop() { + } + + private void on_idle(Context context) { + idle(); + } + + private void on_error(Context context, string format, void *va_list) { + error(format, va_list); + } + + private void on_status(Context context, string format, void *va_list) { + status(format, va_list); + } + + private void on_message(Context context, string format, void *va_list) { + message(format, va_list); + } + + private uint on_progress_start(Context context, float target, string format, void *va_list) { + progress_start(target, format, va_list); + + return 0; + } + + private void on_progress_update(Context context, uint id, float current) { + progress_update(current); + } + + private void on_progress_stop(Context context, uint id) { + progress_stop(); + } + +#endif + } + + public class SpinIdleWrapper : ContextWrapper { + public SpinIdleWrapper() { + } + + public override void idle() { + base.idle(); + + spin_event_loop(); + } +#if WITH_GPHOTO_25 + public override void progress_update(float current, void *data) { + base.progress_update(current, data); + + spin_event_loop(); + } +#else + public override void progress_update(float current) { + base.progress_update(current); + + spin_event_loop(); + } +#endif + } + + // For CameraFileInfoFile, CameraFileInfoPreview, and CameraStorageInformation. See: + // http://redmine.yorba.org/issues/1851 + // https://bugzilla.redhat.com/show_bug.cgi?id=585676 + // https://sourceforge.net/tracker/?func=detail&aid=3000198&group_id=8874&atid=108874 + public const int MAX_FILENAME_LENGTH = 63; + public const int MAX_BASEDIR_LENGTH = 255; + + public bool get_info(Context context, Camera camera, string folder, string filename, + out CameraFileInfo info) throws Error { + if (folder.length > MAX_BASEDIR_LENGTH || filename.length > MAX_FILENAME_LENGTH) { + info = {}; + + return false; + } + + Result res = camera.get_file_info(folder, filename, out info, context); + if (res != Result.OK) + throw new GPhotoError.LIBRARY("[%d] Error retrieving file information for %s/%s: %s", + (int) res, folder, filename, res.as_string()); + + return true; + } + + // Libgphoto will in some instances refuse to get metadata from a camera, but the camera is accessable as a + // filesystem. In these cases shotwell can access the file directly. See: + // http://redmine.yorba.org/issues/2959 + public PhotoMetadata? get_fallback_metadata(Camera camera, Context context, string folder, string filename) { + GPhoto.CameraStorageInformation *sifs = null; + int count = 0; + camera.get_storageinfo(&sifs, out count, context); + + GPhoto.PortInfo port_info; + camera.get_port_info(out port_info); + + string path; +#if WITH_GPHOTO_25 + port_info.get_path(out path); +#else + path = port_info.path; +#endif + + string prefix = "disk:"; + if(path.has_prefix(prefix)) + path = path[prefix.length:path.length]; + else + return null; + + PhotoMetadata? metadata = new PhotoMetadata(); + try { + metadata.read_from_file(File.new_for_path(path + folder + "/" + filename)); + } catch { + metadata = null; + } + + return metadata; + } + + public Gdk.Pixbuf? load_preview(Context context, Camera camera, string folder, string filename, + out uint8[] raw, out size_t raw_length) throws Error { + raw = null; + raw_length = 0; + + try { + raw = load_file_into_buffer(context, camera, folder, filename, GPhoto.CameraFileType.PREVIEW); + } catch { + PhotoMetadata metadata = get_fallback_metadata(camera, context, folder, filename); + if(null == metadata) + return null; + if(0 == metadata.get_preview_count()) + return null; + PhotoPreview? preview = metadata.get_preview(metadata.get_preview_count() - 1); + raw = preview.flatten(); + } + + if (raw == null) { + raw_length = 0; + return null; + } + + raw_length = raw.length; + + // Try to make sure last two bytes are JPEG footer. + // This is necessary because GPhoto sometimes includes a few extra bytes. See + // Yorba bug #2905 and the following GPhoto bug: + // http://sourceforge.net/tracker/?func=detail&aid=3141521&group_id=8874&atid=108874 + if (raw_length > 32) { + for (size_t i = raw_length - 2; i > raw_length - 32; i--) { + if (raw[i] == Jpeg.MARKER_PREFIX && raw[i + 1] == Jpeg.Marker.EOI) { + debug("Adjusted length of thumbnail for: %s", filename); + raw_length = i + 2; + break; + } + } + } + + MemoryInputStream mins = new MemoryInputStream.from_data(raw, null); + return new Gdk.Pixbuf.from_stream_at_scale(mins, ImportPreview.MAX_SCALE, ImportPreview.MAX_SCALE, true, null); + } + + public Gdk.Pixbuf? load_image(Context context, Camera camera, string folder, string filename) + throws Error { + InputStream ins = load_file_into_stream(context, camera, folder, filename, GPhoto.CameraFileType.NORMAL); + if (ins == null) + return null; + + return new Gdk.Pixbuf.from_stream(ins, null); + } + + public void save_image(Context context, Camera camera, string folder, string filename, + File dest_file) throws Error { + GPhoto.CameraFile camera_file; + GPhoto.Result res = GPhoto.CameraFile.create(out camera_file); + if (res != Result.OK) + throw new GPhotoError.LIBRARY("[%d] Error allocating camera file: %s", (int) res, res.as_string()); + + res = camera.get_file(folder, filename, GPhoto.CameraFileType.NORMAL, camera_file, context); + if (res != Result.OK) + throw new GPhotoError.LIBRARY("[%d] Error retrieving file object for %s/%s: %s", + (int) res, folder, filename, res.as_string()); + + res = camera_file.save(dest_file.get_path()); + if (res != Result.OK) + throw new GPhotoError.LIBRARY("[%d] Error copying file %s/%s to %s: %s", (int) res, + folder, filename, dest_file.get_path(), res.as_string()); + } + + public PhotoMetadata? load_metadata(Context context, Camera camera, string folder, string filename) + throws Error { + uint8[] camera_raw = null; + try { + camera_raw = load_file_into_buffer(context, camera, folder, filename, GPhoto.CameraFileType.EXIF); + } catch { + return get_fallback_metadata(camera, context, folder, filename); + } + + if (camera_raw == null || camera_raw.length == 0) + return null; + + PhotoMetadata metadata = new PhotoMetadata(); + metadata.read_from_app1_segment(camera_raw); + + return metadata; + } + + // Returns an InputStream for the requested camera file. The stream should be used + // immediately rather than stored, as the backing is temporary in nature. + public InputStream load_file_into_stream(Context context, Camera camera, string folder, string filename, + GPhoto.CameraFileType filetype) throws Error { + GPhoto.CameraFile camera_file; + GPhoto.Result res = GPhoto.CameraFile.create(out camera_file); + if (res != Result.OK) + throw new GPhotoError.LIBRARY("[%d] Error allocating camera file: %s", (int) res, res.as_string()); + + res = camera.get_file(folder, filename, filetype, camera_file, context); + if (res != Result.OK) + throw new GPhotoError.LIBRARY("[%d] Error retrieving file object for %s/%s: %s", + (int) res, folder, filename, res.as_string()); + + // if entire file fits in memory, return a stream from that ... can't merely wrap + // MemoryInputStream around the camera_file buffer, as that will be destroyed when the + // function returns + unowned uint8 *data; + ulong data_len; + res = camera_file.get_data_and_size(out data, out data_len); + if (res == Result.OK) { + uint8[] buffer = new uint8[data_len]; + Memory.copy(buffer, data, buffer.length); + + return new MemoryInputStream.from_data(buffer, on_mins_destroyed); + } + + // if not stored in memory, try copying it to a temp file and then reading out of that + File temp = AppDirs.get_temp_dir().get_child("import.tmp"); + res = camera_file.save(temp.get_path()); + if (res != Result.OK) + throw new GPhotoError.LIBRARY("[%d] Error copying file %s/%s to %s: %s", (int) res, + folder, filename, temp.get_path(), res.as_string()); + + return temp.read(null); + } + + private static void on_mins_destroyed(void *data) { + free(data); + } + + // Returns a buffer with the requested file, if within reason. Use load_file for larger files. + public uint8[]? load_file_into_buffer(Context context, Camera camera, string folder, + string filename, CameraFileType filetype) throws Error { + GPhoto.CameraFile camera_file; + GPhoto.Result res = GPhoto.CameraFile.create(out camera_file); + if (res != Result.OK) + throw new GPhotoError.LIBRARY("[%d] Error allocating camera file: %s", (int) res, res.as_string()); + + res = camera.get_file(folder, filename, filetype, camera_file, context); + if (res != Result.OK) + throw new GPhotoError.LIBRARY("[%d] Error retrieving file object for %s/%s: %s", + (int) res, folder, filename, res.as_string()); + + // if buffer can be loaded into memory, return a copy of that (can't return buffer itself + // as it will be destroyed when the camera_file is unref'd) + unowned uint8 *data; + ulong data_len; + res = camera_file.get_data_and_size(out data, out data_len); + if (res != Result.OK) + return null; + + uint8[] buffer = new uint8[data_len]; + Memory.copy(buffer, data, buffer.length); + + return buffer; + } +} + |