RFC: preview files



Hey,

So one interesting feature of digital cameras is that they're capable of
generating thumbnails on the device side. This is actually pretty useful
when importing photos; instead of downloading hundreds images (each
5-10MB on my Canon EOS 5D DSLR) for the import dialog, one can download
the previews (typically ~10KB of size 160x120).

I've written a patch for the gphoto2 backend to do this.

First, there's a GIO patch to introduce two new properties preview::uri
and preview::uri-original that links the original file with the preview
file. See [1] for gvfs-info output.

Second there's the gphoto2 patch. It's not too complicated, we just have
to synthesize GFileInfo objects in enumerate() and then handle them in
query_file_info() and do_open_for_read(). 

(There's one catch, there doesn't seem to be any way to get the size of
the preview file (or maybe I'm just lost in the libgphoto2 API again);
not a huge problem. Most apps can open the files just fine.)

Turns out the performance is par on using the gphoto2(1) command-line
tool; in fact, we're slightly faster, see [2] for details, though that
may just be statistical noise.

If these patches get in, we should probably teach Nautilus's thumbnailer
about preview::uri and use that instead of the regular file. That will
speed up thumbnailing a lot.

Not sure what other backends can benefit from this but preview::* seems
like a pretty useful feature in general.

OK to commit?

     David


[1] :

$ gvfs-info gphoto2://[usb:001,012]/DCIM/100EOS5D/IMG_2125.JPG
display name: IMG_2125.JPG
name: IMG_2125.JPG
type: regular
size: 5221629
attributes:
  standard::name: IMG_2125.JPG
  standard::display-name: IMG_2125.JPG
  standard::type: 1
  standard::size: 5221629
  standard::content-type: image/jpeg
  standard::icon: GThemedIcon:0x1eaf520
  standard::is-hidden: FALSE
  time::modified: 1215970398
  time::modified-usec: 0
  access::can-read: TRUE
  access::can-write: TRUE
  access::can-delete: TRUE
  access::can-execute: FALSE
  access::can-trash: FALSE
  access::can-rename: TRUE
  preview::uri: gphoto2://[usb:001,012]/DCIM/100EOS5D/.gvfs-gphoto2-preview-IMG_2125.JPG
  id::filesystem: host='[usb:001,012]',type='gphoto2',mount_prefix='/'

$ gvfs-info gphoto2://[usb:001,012]/DCIM/100EOS5D/.gvfs-gphoto2-preview-IMG_2125.JPG
display name: .gvfs-gphoto2-preview-IMG_2125.JPG
name: .gvfs-gphoto2-preview-IMG_2125.JPG
type: regular
size: 0
hidden
attributes:
  standard::name: .gvfs-gphoto2-preview-IMG_2125.JPG
  standard::display-name: .gvfs-gphoto2-preview-IMG_2125.JPG
  standard::type: 1
  standard::content-type: image/jpeg
  standard::icon: GThemedIcon:0x862520
  standard::is-hidden: TRUE
  time::modified: 1215970398
  time::modified-usec: 0
  access::can-read: TRUE
  access::can-write: FALSE
  access::can-delete: FALSE
  access::can-execute: FALSE
  access::can-trash: FALSE
  access::can-rename: TRUE
  preview::uri-original: gphoto2://[usb:001,012]/DCIM/100EOS5D/IMG_2125.JPG
  id::filesystem: host='[usb:001,012]',type='gphoto2',mount_prefix='/'

[2] :

$ time for i in `gvfs-ls -h gphoto2://[usb:001,012]/DCIM/100EOS5D|grep ".gvfs-gphoto2"`; do gvfs-copy gphoto2://[usb:001,012]/DCIM/100EOS5D/$i ~/Desktop/foo; done

real	0m43.094s
user	0m2.807s
sys	0m3.178s


$ time gphoto2 -T
Detected a 'Canon:EOS 5D (normal mode)'.                                       
Downloading 'IMG_2125.JPG' from folder '/DCIM/100EOS5D'...
Downloading 'IMG_2125.JPG' from folder '/DCIM/100EOS5D'...
Saving file as thumb_IMG_2125.jpg

[...]

Downloading '_MG_2124.JPG' from folder '/DCIM/100EOS5D'...
Downloading '_MG_2124.JPG' from folder '/DCIM/100EOS5D'...
Saving file as thumb__MG_2124.jpg


real	0m43.829s
user	0m2.439s
sys	0m0.270s

Index: gfileinfo.h
===================================================================
--- gfileinfo.h	(revision 7593)
+++ gfileinfo.h	(working copy)
@@ -608,6 +608,32 @@ typedef struct _GFileInfoClass   GFileInfoClass;
  **/
 #define G_FILE_ATTRIBUTE_THUMBNAILING_FAILED "thumbnail::failed"         /* boolean */
 
+/* Preview */
+
+/**
+ * G_FILE_ATTRIBUTE_PREVIEW_URI:
+ *
+ * A key in the "preview" namespace for getting the URI to a preview
+ * file. A preview file is typically a smaller size, lower quality
+ * version of a given file; typically a lower resultion thumbnail for
+ * images. Corresponding #GFileAttributeType is
+ * %G_FILE_ATTRIBUTE_TYPE_BYTE_STRING.
+ *
+ * Since: 2.20
+ **/
+#define G_FILE_ATTRIBUTE_PREVIEW_URI "preview::uri"         /* bytestring */
+
+/**
+ * G_FILE_ATTRIBUTE_PREVIEW_URI_ORIGINAL:
+ *
+ * A key in the "preview" namespace for getting the URI to the
+ * original file that the given file is a preview for. Corresponding
+ * #GFileAttributeType is %G_FILE_ATTRIBUTE_TYPE_BYTE_STRING.
+ *
+ * Since: 2.20
+ **/
+#define G_FILE_ATTRIBUTE_PREVIEW_URI_ORIGINAL "preview::uri-original"         /* bytestring */
+
 /* File system info (for g_file_get_filesystem_info) */
 
 /**
Index: gvfsbackendgphoto2.c
===================================================================
--- gvfsbackendgphoto2.c	(revision 2049)
+++ gvfsbackendgphoto2.c	(working copy)
@@ -68,6 +68,17 @@
 #define DEBUG_NO_CACHING 1
 #endif
 
+/* the prefix used for synthesized preview files */
+#define PREVIEW_PREFIX ".gvfs-gphoto2-preview-"
+
+static gboolean file_info_has_preview (GVfsBackendGphoto2 *gphoto2_backend,
+                                       GFileInfo *file_info,
+                                       const char *directory_name);
+
+static void modify_info_for_preview (GVfsBackendGphoto2 *gphoto2_backend,
+                                     GFileInfo *file_info,
+                                     const char *directory_name);
+
 /*--------------------------------------------------------------------------------------------------------------*/
 
 /* TODO:
@@ -976,8 +987,10 @@ file_get_info (GVfsBackendGphoto2 *gphoto2_backend
   char *mime_type;
   GIcon *icon;
   unsigned int n;
+  gboolean is_preview;
 
   ret = FALSE;
+  is_preview = FALSE;
 
   full_path = g_build_filename (dir, name, NULL);
   DEBUG ("file_get_info() try_cache_only=%d dir='%s', name='%s'\n"
@@ -1033,6 +1046,12 @@ file_get_info (GVfsBackendGphoto2 *gphoto2_backend
       goto add_to_cache;
     }
 
+  if (g_str_has_prefix (name, PREVIEW_PREFIX))
+    {
+      name = name + sizeof (PREVIEW_PREFIX) - 1;
+      is_preview = TRUE;
+    }
+
   rc = gp_camera_file_get_info (gphoto2_backend->camera,
                                 dir,
                                 name,
@@ -1141,7 +1160,6 @@ file_get_info (GVfsBackendGphoto2 *gphoto2_backend
     }
   g_free (mime_type);
 
-
   if (gp_info.file.fields & GP_FILE_INFO_MTIME)
     mtime.tv_sec = gp_info.file.mtime;
   else
@@ -1167,7 +1185,17 @@ file_get_info (GVfsBackendGphoto2 *gphoto2_backend
   }
 
   ret = TRUE;
-  DEBUG ("  Generating info (file) for '%s'", full_path);
+  if (is_preview)
+    {
+      modify_info_for_preview (gphoto2_backend, info, dir);
+      DEBUG ("  Generated info (preview) for '%s'", full_path);
+    }
+  else
+    {
+      /* sets G_FILE_ATTRIBUTE_PREVIEW_URI if appropriate */
+      file_info_has_preview (gphoto2_backend, info, dir);
+      DEBUG ("  Generated info (file) for '%s'", full_path);
+    }
 
  add_to_cache:
   /* add this sucker to the cache */
@@ -1687,6 +1715,7 @@ do_open_for_read (GVfsBackend *backend,
   GVfsBackendGphoto2 *gphoto2_backend = G_VFS_BACKEND_GPHOTO2 (backend);
   char *dir;
   char *name;
+  gboolean is_preview;
 
   DEBUG ("open_for_read (%s)", filename);
 
@@ -1721,10 +1750,20 @@ do_open_for_read (GVfsBackend *backend,
       goto out;
     }
 
+  is_preview = FALSE;
+  if (g_str_has_prefix (name, PREVIEW_PREFIX))
+    {
+      char *s;
+      s = name;
+      name = g_strdup (name + sizeof (PREVIEW_PREFIX) - 1);
+      g_free (s);
+      is_preview = TRUE;
+    }
+
   rc = gp_camera_file_get (gphoto2_backend->camera,
                            dir,
                            name,
-                           GP_FILE_TYPE_NORMAL,
+                           is_preview ? GP_FILE_TYPE_PREVIEW : GP_FILE_TYPE_NORMAL,
                            read_handle->file,
                            gphoto2_backend->context);
   if (rc != 0)
@@ -1940,7 +1979,97 @@ try_query_info (GVfsBackend *backend,
 
 /* ------------------------------------------------------------------------------------------------- */
 
+static gboolean
+file_info_has_preview (GVfsBackendGphoto2 *gphoto2_backend,
+                       GFileInfo *file_info,
+                       const char *directory_name)
+{
+  gboolean ret;
+  char *preview_uri;
+  const char *name;
+  const char *content_type;
+
+  ret = FALSE;
+  preview_uri = NULL;
+
+  name = g_file_info_get_name (file_info);
+  if (name == NULL)
+    goto out;
+
+  content_type = g_file_info_get_content_type (file_info);
+  if (content_type == NULL)
+    goto out;
+
+  /* for now we assume only that all JPG files has a preview file */
+  if (strcmp (content_type, "image/jpg") != 0 &&
+      strcmp (content_type, "image/jpeg") != 0)
+    goto out;
+
+  ret = TRUE;
+
+  preview_uri = g_strdup_printf ("gphoto2://[%s]/%s/%s%s",
+                                 gphoto2_backend->gphoto2_port,
+                                 directory_name + strlen (gphoto2_backend->ignore_prefix),
+                                 PREVIEW_PREFIX,
+                                 name);
+  g_file_info_set_attribute_string (file_info, G_FILE_ATTRIBUTE_PREVIEW_URI, preview_uri);
+
+ out:
+  g_free (preview_uri);
+  return ret;
+}
+
 static void
+modify_info_for_preview (GVfsBackendGphoto2 *gphoto2_backend,
+                         GFileInfo *file_info,
+                         const char *directory_name)
+{
+  char *name;
+  char *preview_name;
+  char *preview_original_uri;
+
+  name = g_strdup (g_file_info_get_name (file_info));
+  preview_name = g_strdup_printf (PREVIEW_PREFIX "%s", name);
+
+  g_file_info_set_display_name (file_info, preview_name);
+  g_file_info_set_name (file_info, preview_name);
+  g_file_info_set_is_hidden (file_info, TRUE);
+  g_file_info_set_attribute_boolean (file_info, G_FILE_ATTRIBUTE_ACCESS_CAN_WRITE, FALSE);
+  g_file_info_set_attribute_boolean (file_info, G_FILE_ATTRIBUTE_ACCESS_CAN_DELETE, FALSE);
+
+  /* TODO: how do we get the size? */
+  g_file_info_remove_attribute (file_info, G_FILE_ATTRIBUTE_STANDARD_SIZE);
+
+  preview_original_uri = g_strdup_printf ("gphoto2://[%s]/%s/%s",
+                                          gphoto2_backend->gphoto2_port,
+                                          directory_name + strlen (gphoto2_backend->ignore_prefix),
+                                          name);
+  g_file_info_set_attribute_string (file_info, G_FILE_ATTRIBUTE_PREVIEW_URI_ORIGINAL, preview_original_uri);
+
+  g_free (preview_original_uri);
+  g_free (preview_name);
+  g_free (name);
+}
+
+static void
+maybe_add_preview_file (GVfsBackendGphoto2 *gphoto2_backend,
+                        GList **file_info_list,
+                        GFileInfo *file_info,
+                        const char *directory_name)
+{
+  if (file_info_has_preview (gphoto2_backend,
+                             file_info,
+                             directory_name))
+    {
+      GFileInfo *preview_info;
+
+      preview_info = g_file_info_dup (file_info);
+      modify_info_for_preview (gphoto2_backend, preview_info, directory_name);
+      *file_info_list = g_list_append (*file_info_list, preview_info);
+    }
+}
+
+static void
 do_enumerate (GVfsBackend *backend,
               GVfsJobEnumerate *job,
               const char *given_filename,
@@ -2106,6 +2235,8 @@ do_enumerate (GVfsBackend *backend,
           return;
         }
       l = g_list_append (l, info);
+
+      maybe_add_preview_file (gphoto2_backend, &l, info, as_dir);
     }
   if (!using_cached_file_list)
     {
@@ -2208,6 +2339,8 @@ try_enumerate (GVfsBackend *backend,
           goto error_not_cached;
         }
       l = g_list_append (l, info);
+
+      maybe_add_preview_file (gphoto2_backend, &l, info, filename);
     }
   g_mutex_lock (gphoto2_backend->lock);
   gp_list_unref (list);


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]