summaryrefslogtreecommitdiff
path: root/src/camera/GPhoto.vala
diff options
context:
space:
mode:
Diffstat (limited to 'src/camera/GPhoto.vala')
-rw-r--r--src/camera/GPhoto.vala367
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;
+ }
+}
+