Read metadata in the idle loop
authorColin Clark <colin.clark@cclark.uk>
Mon, 18 Jun 2018 17:53:46 +0000 (18:53 +0100)
committerColin Clark <colin.clark@cclark.uk>
Mon, 18 Jun 2018 17:53:46 +0000 (18:53 +0100)
Selecting sort-by DateOriginal, DateDigitized or Rating caused Geeqie to
freeze while the metadata was read for the whole folder contents.

This commit reads those data in the idle loop and thus prevents the
freeze.
There is an option in Preferences/Metadata to autmatically read the
metadata in the idle loop whenever a new folder is selected.

22 files changed:
doc/docbook/GuideMainWindowStatusBar.xml
doc/docbook/GuideOptionsMetadata.xml
src/filedata.c
src/filedata.h
src/layout.c
src/layout_image.c
src/main.h
src/options.c
src/options.h
src/preferences.c
src/rcfile.c
src/typedefs.h
src/view_file.h
src/view_file/view_file.c
src/view_file/view_file_icon.c
src/view_file/view_file_icon.h
src/view_file/view_file_list.c
src/view_file/view_file_list.h
web/help/GuideMainWindowStatusBar.html
web/help/GuideOptionsMetadata.html
web/help/GuideReferenceXmpExif.html
web/help/GuideSidebarsInfo.html

index dbbb36f..343caae 100644 (file)
@@ -6,8 +6,14 @@
   <para />\r
   <section id="ProgressBar">\r
     <title>Progress Bar</title>\r
-    <para>The Progress bar updates to display the current state of thumbnail generation. When this section contains no text, thumbnail generation is idle. When “Loading thumbs...” is displayed, thumbnails are currently being generated when Geeqie is idle; the progress bar will update to display the percentage of thumbnails that are completed.</para>\r
-    <para />\r
+    <para>\r
+      The Progress bar updates to display the current state of thumbnail generation, or the reading of metadata in the current folder.\r
+      <para />\r
+      When “Loading thumbs...” is displayed, thumbnails are currently being generated when Geeqie is idle; the progress bar will update to display the percentage of thumbnails that are completed.\r
+      <para />\r
+      When “Loading meta...” is displayed, certain metadata is being loaded when Geeqie is idle; the progress bar will update to display the percentage of files that have been read. Refer to\r
+      <link linkend="PreLoadMetadata">Preferences metadata.</link>\r
+    </para>\r
   </section>\r
   <section id="Sortmethod">\r
     <title>Sort method</title>\r
index 5844eaa..b48ffdc 100644 (file)
     </itemizedlist>\r
     <para />\r
   </section>\r
+  <section id="PreLoadMetadata">\r
+    <title>Pre-load metadata</title>\r
+    <itemizedlist>\r
+      <listitem>\r
+        <para>\r
+          <guilabel>Read metadata in background</guilabel>\r
+          <para />\r
+          Using the folder sorting options:\r
+          <para />\r
+          <guilabel>Exif date original</guilabel>\r
+          <para />\r
+          <guilabel>Exif date digitized</guilabel>\r
+          <para />\r
+          <guilabel>Rating</guilabel>\r
+          <para />\r
+          requires metadata to be read from all files in a folder before the sort action can be made. If a folder contains a large number of file, this can take a noticeable period of time.\r
+          <para />\r
+          If this option is checked, Geeqie will automatically read the required metatada in the background as soon as a folder is opened. This will reduce the amount of time you have to wait until the sort is completed.\r
+          <para />\r
+          If you do not use these sort otions, leave this option unchecked.\r
+        </para>\r
+      </listitem>\r
+    </itemizedlist>\r
+    <para />\r
+  </section>\r
 </section>\r
index 356b7c5..876cb80 100644 (file)
@@ -429,7 +429,7 @@ static FileData *file_data_new(const gchar *path_utf8, struct stat *st, gboolean
        fd->ref = 1;
        fd->magick = FD_MAGICK;
        fd->exifdate = 0;
-       fd->rating = 0;
+       fd->rating = STAR_RATING_NOT_READ;
        fd->format_class = filter_file_get_class(path_utf8);
 
        if (disable_sidecars) fd->disable_grouping = TRUE;
@@ -477,7 +477,10 @@ void read_exif_time_data(FileData *file)
                return;
                }
 
-       file->exif = exif_read_fd(file);
+       if (!file->exif)
+               {
+               exif_read_fd(file);
+               }
 
        if (file->exif)
                {
@@ -512,7 +515,10 @@ void read_exif_time_digitized_data(FileData *file)
                return;
                }
 
-       file->exif = exif_read_fd(file);
+       if (!file->exif)
+               {
+               exif_read_fd(file);
+               }
 
        if (file->exif)
                {
@@ -539,6 +545,18 @@ void read_exif_time_digitized_data(FileData *file)
                }
 }
 
+void read_rating_data(FileData *file)
+{
+       gchar *rating_str;
+
+       rating_str = metadata_read_string(file, RATING_KEY, METADATA_PLAIN);
+       if (rating_str)
+               {
+               file->rating = atoi(rating_str);
+               g_free(rating_str);
+               }
+}
+
 void set_exif_time_data(GList *files)
 {
        DEBUG_1("%s set_exif_time_data: ...", get_exec_time());
@@ -1161,18 +1179,6 @@ GList *filelist_insert_sort_full(GList *list, gpointer data, SortType method, gb
 
 GList *filelist_sort(GList *list, SortType method, gboolean ascend)
 {
-       if (method == SORT_EXIFTIME)
-               {
-               set_exif_time_data(list);
-               }
-       if (method == SORT_EXIFTIMEDIGITIZED)
-               {
-               set_exif_time_digitized_data(list);
-               }
-       if (method == SORT_RATING)
-               {
-               set_rating_data(list);
-               }
        return filelist_sort_full(list, method, ascend, (GCompareFunc) filelist_sort_file_cb);
 }
 
index 6aa159f..1b4e097 100644 (file)
@@ -167,5 +167,6 @@ void read_exif_time_digitized_data(FileData *file);
 gboolean marks_list_save(gchar *path, gboolean clear);
 gboolean marks_list_load(const gchar *path);
 void marks_clear_all();
+void read_rating_data(FileData *file);
 #endif
 /* vim: set shiftwidth=8 softtabstop=0 cindent cinoptions={1s: */
index dab1e02..4c14cdf 100644 (file)
@@ -363,6 +363,10 @@ static void layout_sort_menu_cb(GtkWidget *widget, gpointer data)
 
        type = (SortType)GPOINTER_TO_INT(data);
 
+       if (type == SORT_EXIFTIME || type == SORT_EXIFTIMEDIGITIZED || type == SORT_RATING)
+               {
+               vf_read_metadata_in_idle(lw->vf);
+               }
        layout_sort_set(lw, type, lw->sort_ascend);
 }
 
@@ -547,11 +551,30 @@ static GtkWidget *layout_zoom_button(LayoutWindow *lw, GtkWidget *box, gint size
 
 void layout_status_update_progress(LayoutWindow *lw, gdouble val, const gchar *text)
 {
+       static gdouble thumb = 0;
+       static gdouble meta = 0;
+
        if (!layout_valid(&lw)) return;
        if (!lw->info_progress_bar) return;
 
+       /* Give priority to the loading meta data message
+        */
+       if(!g_strcmp0(text, "Loading thumbs..."))
+               {
+               thumb = val;
+               if (meta)
+                       {
+                       return;
+                       }
+               }
+       else
+               {
+               meta = val;
+               }
+
        gtk_progress_bar_set_fraction(GTK_PROGRESS_BAR(lw->info_progress_bar), val);
-       gtk_progress_bar_set_text(GTK_PROGRESS_BAR(lw->info_progress_bar), (text) ? text : " ");
+       gtk_progress_bar_set_text(GTK_PROGRESS_BAR(lw->info_progress_bar),
+                                                                       val ? ((text) ? text : " ") : " ");
 }
 
 void layout_status_update_info(LayoutWindow *lw, const gchar *text)
@@ -1096,6 +1119,11 @@ gboolean layout_set_fd(LayoutWindow *lw, FileData *fd)
        if (options->metadata.confirm_on_dir_change && dir_changed)
                metadata_write_queue_confirm(FALSE, NULL, NULL);
 
+       if (lw->vf && (options->read_metadata_in_idle || (lw->sort_method == SORT_EXIFTIME || lw->sort_method == SORT_EXIFTIMEDIGITIZED || lw->sort_method == SORT_RATING)))
+               {
+               vf_read_metadata_in_idle(lw->vf);
+               }
+
        return TRUE;
 }
 
index 39a5800..250df6b 100644 (file)
@@ -1125,6 +1125,7 @@ void layout_image_alter_orientation(LayoutWindow *lw, AlterType type)
 static void image_alter_rating(FileData *fd_n, const gchar *rating)
 {
        metadata_write_string(fd_n, RATING_KEY, rating);
+       read_rating_data(fd_n);
 }
 
 void layout_image_rating(LayoutWindow *lw, const gchar *rating)
index 2c63afe..6bf9f99 100644 (file)
 #define TIMEZONE_DATABASE "timezone21.bin"
 
 #define HELP_SEARCH_ENGINE "https://duckduckgo.com/?q=site:geeqie.org/help "
+
+#define STAR_RATING_NOT_READ -12345
 /*
  *----------------------------------------------------------------------------
  * main.c
index a3bcdb8..a4e6319 100644 (file)
@@ -187,6 +187,7 @@ ConfOptions *init_options(ConfOptions *options)
        options->log_window.paused = FALSE;
        options->log_window.timer_data = FALSE;
 
+       options->read_metadata_in_idle = FALSE;
        return options;
 }
 
index 403e596..a351ff5 100644 (file)
@@ -288,6 +288,8 @@ struct _ConfOptions
                gboolean line_wrap;
                gboolean timer_data;
        } log_window;
+
+       gboolean read_metadata_in_idle;
 };
 
 ConfOptions *options;
index 86915d4..4eb6ad6 100644 (file)
@@ -412,6 +412,7 @@ static void config_window_apply(void)
        options->with_rename = c_options->with_rename;
        config_entry_to_option(help_search_engine_entry, &options->help_search_engine, NULL);
 
+       options->read_metadata_in_idle = c_options->read_metadata_in_idle;
 #ifdef DEBUG
        set_debug_level(debug_c);
 #endif
@@ -2220,6 +2221,12 @@ static void config_tab_metadata(GtkWidget *notebook)
 
        pref_checkbox_new_int(group, _("Write metadata on directory change"),
                              options->metadata.confirm_on_dir_change, &c_options->metadata.confirm_on_dir_change);
+
+       group = pref_group_new(vbox, FALSE, _("Pre-load metadata"), GTK_ORIENTATION_VERTICAL);
+
+       ct_button = pref_checkbox_new_int(group, _("Read metadata in background"),
+                                         options->read_metadata_in_idle, &c_options->read_metadata_in_idle);
+       gtk_widget_set_tooltip_text(ct_button,"On folder change, read DateTimeOriginal, DateTimeDigitized and Star Rating in the idle loop.\nIf this is not selected, initial loading of the folder will be faster but sorting on these items will be slower");
 }
 
 /* metadata tab */
index d02c764..f293117 100644 (file)
@@ -479,6 +479,8 @@ static void write_global_attributes(GString *outstr, gint indent)
        WRITE_NL(); WRITE_INT(*options, stereo.fixed_x2);
        WRITE_NL(); WRITE_INT(*options, stereo.fixed_y2);
 
+       WRITE_NL(); WRITE_BOOL(*options, read_metadata_in_idle);
+
        /* copy move rename */
        WRITE_NL(); WRITE_INT(*options, cp_mv_rn.auto_start);
        WRITE_NL(); WRITE_INT(*options, cp_mv_rn.auto_padding);
@@ -802,6 +804,8 @@ static gboolean load_global_params(const gchar **attribute_names, const gchar **
                if (READ_INT(*options, stereo.fixed_x2)) continue;
                if (READ_INT(*options, stereo.fixed_y2)) continue;
 
+               if (READ_BOOL(*options, read_metadata_in_idle)) continue;
+
                /* copy move rename */
                if (READ_INT(*options, cp_mv_rn.auto_start))  continue;
                if (READ_INT(*options, cp_mv_rn.auto_padding)) continue;
index 3368641..86c2805 100644 (file)
@@ -595,6 +595,7 @@ struct _FileData {
        GHashTable *modified_xmp; // hash table which contains unwritten xmp metadata in format: key->list of string values
        GList *cached_metadata;
        gint rating;
+       gboolean metadata_in_idle_loaded;
 
        SelectionType selected;  // Used by view_file_icon.
 };
@@ -887,6 +888,8 @@ struct _ViewFile
 
        /* file list for edit menu */
        GList *editmenu_fd_list;
+
+       guint read_metadata_in_idle_id;
 };
 
 struct _ViewFileInfoList
index 454f70b..d02d76c 100644 (file)
@@ -72,6 +72,6 @@ void vf_notify_cb(FileData *fd, NotifyType type, gpointer data);
 void vf_thumb_update(ViewFile *vf);
 void vf_thumb_cleanup(ViewFile *vf);
 void vf_thumb_stop(ViewFile *vf);
-
+void vf_read_metadata_in_idle(ViewFile *vf);
 #endif /* VIEW_FILE_H */
 /* vim: set shiftwidth=8 softtabstop=0 cindent cinoptions={1s: */
index fb80dac..0bac1ff 100644 (file)
@@ -404,6 +404,11 @@ static void vf_pop_menu_sort_cb(GtkWidget *widget, gpointer data)
 
        type = (SortType)GPOINTER_TO_INT(data);
 
+       if (type == SORT_EXIFTIME || type == SORT_EXIFTIMEDIGITIZED || type == SORT_RATING)
+               {
+               vf_read_metadata_in_idle(vf);
+               }
+
        if (vf->layout)
                {
                layout_sort_set(vf->layout, type, vf->sort_ascend);
@@ -697,6 +702,10 @@ static void vf_destroy_cb(GtkWidget *widget, gpointer data)
                gtk_widget_destroy(vf->popup);
                }
 
+       if (vf->read_metadata_in_idle_id)
+               {
+               g_idle_remove_by_data(vf);
+               }
        file_data_unref(vf->dir_fd);
        g_free(vf->info);
        g_free(vf);
@@ -848,6 +857,7 @@ ViewFile *vf_new(FileViewType type, FileData *dir_fd)
        vf->type = type;
        vf->sort_method = SORT_NAME;
        vf->sort_ascend = TRUE;
+       vf->read_metadata_in_idle_id = 0;
 
        vf->scrolled = gtk_scrolled_window_new(NULL, NULL);
        gtk_scrolled_window_set_shadow_type(GTK_SCROLLED_WINDOW(vf->scrolled), GTK_SHADOW_IN);
@@ -926,6 +936,20 @@ static gdouble vf_thumb_progress(ViewFile *vf)
        return (gdouble)done / count;
 }
 
+static gdouble vf_read_metadata_in_idle_progress(ViewFile *vf)
+{
+       gint count = 0;
+       gint done = 0;
+
+       switch (vf->type)
+               {
+               case FILEVIEW_LIST: vflist_read_metadata_progress_count(vf->list, &count, &done); break;
+               case FILEVIEW_ICON: vficon_read_metadata_progress_count(vf->list, &count, &done); break;
+               }
+
+       return (gdouble)done / count;
+}
+
 static void vf_set_thumb_fd(ViewFile *vf, FileData *fd)
 {
        switch (vf->type)
@@ -1197,4 +1221,74 @@ void vf_notify_cb(FileData *fd, NotifyType type, gpointer data)
                }
 }
 
+static gboolean vf_read_metadata_in_idle_cb(gpointer data)
+{
+       FileData *fd;
+       ViewFile *vf = data;
+       GList *list_entry;
+       GList *work;
+
+       vf_thumb_status(vf, vf_read_metadata_in_idle_progress(vf), _("Loading meta..."));
+
+       work = vf->list;
+
+       while (work)
+               {
+               fd = work->data;
+
+               if (fd && !fd->metadata_in_idle_loaded)
+                       {
+                       if (!fd->exifdate)
+                               {
+                               read_exif_time_data(fd);
+                               }
+                       if (!fd->exifdate_digitized)
+                               {
+                               read_exif_time_digitized_data(fd);
+                               }
+                       if (fd->rating == STAR_RATING_NOT_READ)
+                               {
+                               read_rating_data(fd);
+                               }
+                       fd->metadata_in_idle_loaded = TRUE;
+                       return TRUE;
+                       }
+               work = work->next;
+               }
+
+       vf_thumb_status(vf, 0.0, NULL);
+       vf->read_metadata_in_idle_id = 0;
+       vf_refresh(vf);
+       return FALSE;
+}
+
+static void vf_read_metadata_in_idle_finished_cb(gpointer data)
+{
+       ViewFile *vf = data;
+
+       vf_thumb_status(vf, 0.0, "Loading meta...");
+       vf->read_metadata_in_idle_id = 0;
+}
+
+void vf_read_metadata_in_idle(ViewFile *vf)
+{
+       GList *work;
+       FileData *fd;
+
+       if (!vf) return;
+
+       if (vf->read_metadata_in_idle_id)
+               {
+               g_idle_remove_by_data(vf);
+               }
+       vf->read_metadata_in_idle_id = 0;
+
+       if (vf->list)
+               {
+               vf->read_metadata_in_idle_id = g_idle_add_full(G_PRIORITY_LOW, vf_read_metadata_in_idle_cb, vf, vf_read_metadata_in_idle_finished_cb);
+               }
+
+       return;
+}
+
 /* vim: set shiftwidth=8 softtabstop=0 cindent cinoptions={1s: */
index eea06b2..75085e4 100644 (file)
@@ -1690,6 +1690,19 @@ void vficon_thumb_progress_count(GList *list, gint *count, gint *done)
                }
 }
 
+void vficon_read_metadata_progress_count(GList *list, gint *count, gint *done)
+{
+       GList *work = list;
+       while (work)
+               {
+               FileData *fd = work->data;
+               work = work->next;
+
+               if (fd->metadata_in_idle_loaded) (*done)++;
+               (*count)++;
+               }
+}
+
 void vficon_set_thumb_fd(ViewFile *vf, FileData *fd)
 {
        GtkTreeModel *store;
index 0ca75b5..9e9bd0b 100644 (file)
@@ -68,6 +68,7 @@ void vficon_selection_to_mark(ViewFile *vf, gint mark, SelectionToMarkMode mode)
 
 
 void vficon_thumb_progress_count(GList *list, gint *count, gint *done);
+void vficon_read_metadata_progress_count(GList *list, gint *count, gint *done);
 void vficon_set_thumb_fd(ViewFile *vf, FileData *fd);
 FileData *vficon_thumb_next_fd(ViewFile *vf);
 void vficon_thumb_reset_all(ViewFile *vf);
index c89e6cb..cde019c 100644 (file)
@@ -1079,6 +1079,24 @@ void vflist_thumb_progress_count(GList *list, gint *count, gint *done)
                }
 }
 
+void vflist_read_metadata_progress_count(GList *list, gint *count, gint *done)
+{
+       GList *work = list;
+       while (work)
+               {
+               FileData *fd = work->data;
+               work = work->next;
+
+               if (fd->metadata_in_idle_loaded) (*done)++;
+
+               if (fd->sidecar_files)
+                       {
+                       vflist_read_metadata_progress_count(fd->sidecar_files, count, done);
+                       }
+               (*count)++;
+               }
+}
+
 void vflist_set_thumb_fd(ViewFile *vf, FileData *fd)
 {
        GtkTreeStore *store;
index 38bc829..958e0d3 100644 (file)
@@ -69,6 +69,7 @@ void vflist_selection_to_mark(ViewFile *vf, gint mark, SelectionToMarkMode mode)
 void vflist_color_set(ViewFile *vf, FileData *fd, gboolean color_set);
 
 void vflist_thumb_progress_count(GList *list, gint *count, gint *done);
+void vflist_read_metadata_progress_count(GList *list, gint *count, gint *done);
 void vflist_set_thumb_fd(ViewFile *vf, FileData *fd);
 FileData *vflist_thumb_next_fd(ViewFile *vf);
 void vflist_thumb_reset_all(ViewFile *vf);
index 77ebd84..5670855 100644 (file)
@@ -475,8 +475,14 @@ dd.answer div.label { float: left; }
 </ul></div>
 <div class="division section">
 <a name="ProgressBar"></a><div class="header"><h2 class="section title"><span class="title"><span class="label">2.6.1. </span>Progress Bar</span></h2></div>
-<p class="para block block-first">The Progress bar updates to display the current state of thumbnail generation. When this section contains no text, thumbnail generation is idle. When “Loading thumbs...” is displayed, thumbnails are currently being generated when Geeqie is idle; the progress bar will update to display the percentage of thumbnails that are completed.</p>
-<p class="para block"></p>
+<p class="para block block-first">
+      The Progress bar updates to display the current state of thumbnail generation, or the reading of metadata in the current folder.
+      <p class="para block block-first"></p>
+      When “Loading thumbs...” is displayed, thumbnails are currently being generated when Geeqie is idle; the progress bar will update to display the percentage of thumbnails that are completed.
+      <p class="para block"></p>
+      When “Loading meta...” is displayed, certain metadata is being loaded when Geeqie is idle; the progress bar will update to display the percentage of files that have been read. Refer to
+      <a class="link" href="GuideOptionsMetadata.html#PreLoadMetadata" title="Pre-load metadata">Preferences metadata.</a>
+    </p>
 </div>
 <div class="division section">
 <a name="Sortmethod"></a><div class="header"><h2 class="section title"><span class="title"><span class="label">2.6.2. </span>Sort method</span></h2></div>
index f758c56..66c83a1 100644 (file)
@@ -484,6 +484,9 @@ dd.answer div.label { float: left; }
 <li>
 <span class="label">11.6.5. </span><a class="xref" href="GuideOptionsMetadata.html#AutoSaveOptions" title="Auto-save options">Auto-save options</a>
 </li>
+<li>
+<span class="label">11.6.6. </span><a class="xref" href="GuideOptionsMetadata.html#PreLoadMetadata" title="Pre-load metadata">Pre-load metadata</a>
+</li>
 </ul></div>
 <div class="division section">
 <a name="MetadataWritingProcess"></a><div class="header"><h2 class="section title"><span class="title"><a name="titleMetadataWritingProcess"></a><span class="label">11.6.1. </span>Metadata writing process</span></h2></div>
@@ -628,6 +631,29 @@ dd.answer div.label { float: left; }
 </ul></div>
 <p class="para block"></p>
 </div>
+<div class="division section">
+<a name="PreLoadMetadata"></a><div class="header"><h2 class="section title"><span class="title"><span class="label">11.6.6. </span>Pre-load metadata</span></h2></div>
+<div class="block list itemizedlist"><ul class="itemizedlist"><li class="li-first">
+        <span class="para">
+          <span class="guilabel">Read metadata in background</span>
+          <p class="para block"></p>
+          Using the folder sorting options:
+          <p class="para block"></p>
+          <span class="guilabel">Exif date original</span>
+          <p class="para block"></p>
+          <span class="guilabel">Exif date digitized</span>
+          <p class="para block"></p>
+          <span class="guilabel">Rating</span>
+          <p class="para block"></p>
+          requires metadata to be read from all files in a folder before the sort action can be made. If a folder contains a large number of file, this can take a noticeable period of time.
+          <p class="para block"></p>
+          If this option is checked, Geeqie will automatically read the required metatada in the background as soon as a folder is opened. This will reduce the amount of time you have to wait until the sort is completed.
+          <p class="para block"></p>
+          If you do not use these sort otions, leave this option unchecked.
+        </span>
+      </li></ul></div>
+<p class="para block"></p>
+</div>
 </div></div>
 <div class="navbar navbar-bottom"><table class="navbar"><tr>
 <td class="navbar-prev"><a class="navbar-prev" href="GuideOptionsFiltering.html" title="Files Options">Files Options</a></td>
index 95d67ca..f342103 100644 (file)
@@ -733,10 +733,10 @@ dd.answer div.label { float: left; }
             </td>
 </tr>
 <tr class="tr-shade">
-<td class="td-colsep">
+<td class="td-colsep td-rowsep">
               <span class="para">formatted.timezone</span>
             </td>
-<td class="td-colsep">
+<td class="td-colsep td-rowsep">
               <span class="para">
                 Exif.GPSInfo.GPSLatitude
                 <p class="para block block-first"></p>
@@ -747,13 +747,24 @@ dd.answer div.label { float: left; }
                 Exif.GPSInfo.GPSLongitudeRef
               </span>
             </td>
-<td>
+<td class="td-rowsep">
               <span class="para">
                 Timezone indicated by lat/long
                 <a name="-noteref-ref1"></a><sup><a class="footnote" href="#ref1">2</a></sup>
               </span>
             </td>
 </tr>
+<tr>
+<td class="td-colsep">
+              <span class="para">formatted.star_rating</span>
+            </td>
+<td class="td-colsep">
+              <span class="para">Xmp.xmp.Rating</span>
+            </td>
+<td>
+              <span class="para">Rating shown as a set of 🟊  characters</span>
+            </td>
+</tr>
 </tbody>
 </table>
 </div>
index cd78e8c..b8d709e 100644 (file)
@@ -730,8 +730,12 @@ dd.answer div.label { float: left; }
 <td class="td-rowsep">file date and time in human readable form</td>
 </tr>
 <tr class="tr-shade">
-<td class="td-colsep">file.mode</td>
-<td>file mode flags</td>
+<td class="td-colsep td-rowsep">file.mode</td>
+<td class="td-rowsep">file mode flags</td>
+</tr>
+<tr>
+<td class="td-colsep">file.ctime</td>
+<td>refer to operating system documentation for the meaning of ctime</td>
 </tr>
 </tbody></table>
 </div>