Browse Source

more improvements

David Rose 24 years ago
parent
commit
0580d974ee

+ 130 - 46
pandaapp/src/indexify/indexImage.cxx

@@ -94,69 +94,153 @@ generate_images(const Filename &archive_dir, TextMaker *text_maker) {
   int actual_index_height = thumb_y_space + num_rows * (thumb_height + thumb_caption_height + thumb_y_space);
   int actual_index_height = thumb_y_space + num_rows * (thumb_height + thumb_caption_height + thumb_y_space);
 
 
   PNMImage index_image;
   PNMImage index_image;
-  index_image.clear(actual_index_width, actual_index_height);
-  index_image.fill(1.0, 1.0, 1.0);
 
 
   Filename reduced_dir(archive_dir, "reduced/" + _dir->get_basename());
   Filename reduced_dir(archive_dir, "reduced/" + _dir->get_basename());
 
 
+  Filename output_filename(archive_dir, _name);
+  output_filename.set_extension("jpg");
+
   Photos::const_iterator pi;
   Photos::const_iterator pi;
-  for (pi = _photos.begin(); pi != _photos.end(); ++pi) {
-    const PhotoInfo &pinfo = (*pi);
-    Photo *photo = _dir->get_photo(pinfo._photo_index);
-    Filename photo_filename(_dir->get_dir(), photo->get_basename());
-    nout << "Reading " << photo_filename << "\n";
 
 
-    PNMImage photo_image;
-    if (!photo_image.read(photo_filename)) {
-      nout << "Unable to read.\n";
-      return false;
+  // First, scan the image files to see if we can avoid regenerating
+  // the index image.
+  bool generate_index_image = true;
+  if (!force_regenerate && output_filename.exists()) {
+    // Maybe we don't need to renegerated the index.
+    generate_index_image = false;
+
+    const Filename &newest_contributing_filename = 
+      _dir->get_newest_contributing_filename();
+    if (!newest_contributing_filename.empty()) {
+      generate_index_image = 
+	(output_filename.compare_timestamps(newest_contributing_filename) < 0);
     }
     }
 
 
-    photo->_full_x_size = photo_image.get_x_size();
-    photo->_full_y_size = photo_image.get_y_size();
+    for (pi = _photos.begin(); 
+	 pi != _photos.end() && !generate_index_image; 
+	 ++pi) {
+      const PhotoInfo &pinfo = (*pi);
+      Photo *photo = _dir->get_photo(pinfo._photo_index);
+      Filename photo_filename(_dir->get_dir(), photo->get_basename());
+
+      // If any of the source photos are newer than the index image,
+      // we must regenerate it.
+      generate_index_image = 
+	(output_filename.compare_timestamps(photo_filename) < 0);
+    }
+  }
 
 
-    // Generate a reduced image for the photo.
-    PNMImage reduced_image;
-    compute_reduction(photo_image, reduced_image, reduced_width, reduced_height);
-    reduced_image.quick_filter_from(photo_image);
+  if (generate_index_image) {
+    index_image.clear(actual_index_width, actual_index_height);
+    index_image.fill(1.0, 1.0, 1.0);
+  }
+
+  // Now go back through and read whichever images are necessary, or
+  // just read the headers if we can get away with it.
+  for (pi = _photos.begin(); pi != _photos.end(); ++pi) {
+    const PhotoInfo &pinfo = (*pi);
+    Photo *photo = _dir->get_photo(pinfo._photo_index);
+    Filename photo_filename(_dir->get_dir(), photo->get_basename());
     Filename reduced_filename(reduced_dir, photo->get_basename());
     Filename reduced_filename(reduced_dir, photo->get_basename());
-    reduced_filename.make_dir();
-    nout << "Writing " << reduced_filename << "\n";
-    if (!reduced_image.write(reduced_filename)) {
-      nout << "Unable to write.\n";
-      return false;
-    }
+    PNMImage reduced_image;
 
 
-    photo->_reduced_x_size = reduced_image.get_x_size();
-    photo->_reduced_y_size = reduced_image.get_y_size();
+    if (force_regenerate || 
+	reduced_filename.compare_timestamps(photo_filename) < 0) {
+      // If the reduced filename does not exist or is older than the
+      // source filename, we must read the complete source filename to
+      // generate the reduced image.
+      nout << "Reading " << photo_filename << "\n";
+
+      PNMImage photo_image;
+      if (!photo_image.read(photo_filename)) {
+	nout << "Unable to read.\n";
+	return false;
+      }
+      
+      photo->_full_x_size = photo_image.get_x_size();
+      photo->_full_y_size = photo_image.get_y_size();
+      
+      // Generate a reduced image for the photo.
+      compute_reduction(photo_image, reduced_image, reduced_width, reduced_height);
+      reduced_image.quick_filter_from(photo_image);
+      reduced_filename.make_dir();
+      nout << "Writing " << reduced_filename << "\n";
+      if (!reduced_image.write(reduced_filename)) {
+	nout << "Unable to write.\n";
+	return false;
+      }
 
 
-    // Generate a thumbnail image for the photo.
-    PNMImage thumbnail_image;
-    compute_reduction(reduced_image, thumbnail_image, thumb_width, thumb_height);
+      photo->_reduced_x_size = reduced_image.get_x_size();
+      photo->_reduced_y_size = reduced_image.get_y_size();
 
 
-    thumbnail_image.quick_filter_from(reduced_image);
-    // Center the thumbnail image within its box.
-    int x_center = (thumb_width - thumbnail_image.get_x_size()) / 2;
-    int y_center = (thumb_height - thumbnail_image.get_y_size()) / 2;
+    } else {
+      // If the reduced image already exists and is newer than the
+      // source image, use it.
+
+      // We still read the image header to determine its size.
+      PNMImageHeader photo_image;
+      if (!photo_image.read_header(photo_filename)) {
+	nout << "Unable to read " << photo_filename << "\n";
+	return false;
+      }
+      
+      photo->_full_x_size = photo_image.get_x_size();
+      photo->_full_y_size = photo_image.get_y_size();
+
+      if (generate_index_image) {
+	// Now read the reduced image from disk, so we can put it on
+	// the index image.
+	nout << "Reading " << reduced_filename << "\n";
+	
+	if (!reduced_image.read(reduced_filename)) {
+	  nout << "Unable to read.\n";
+	  return false;
+	}
+
+	photo->_reduced_x_size = reduced_image.get_x_size();
+	photo->_reduced_y_size = reduced_image.get_y_size();
 
 
-    index_image.copy_sub_image(thumbnail_image, 
-                               pinfo._x_place + x_center, 
-                               pinfo._y_place + y_center);
+      } else {
+	// If we're not generating an index image, we don't even need
+	// the reduced image--just scan its header to get its size.
+	PNMImageHeader reduced_image;
+	if (!reduced_image.read_header(reduced_filename)) {
+	  nout << "Unable to read " << reduced_filename << "\n";
+	  return false;
+	}
+	photo->_reduced_x_size = reduced_image.get_x_size();
+	photo->_reduced_y_size = reduced_image.get_y_size();
+      }
+    }
 
 
-    if (text_maker != (TextMaker *)NULL) {
-      text_maker->generate_into(photo->get_name(), index_image, 
-                                pinfo._x_place + thumb_width / 2, 
-                                pinfo._y_place + thumb_height + thumb_caption_height);
+    if (generate_index_image) {
+      // Generate a thumbnail image for the photo.
+      PNMImage thumbnail_image;
+      compute_reduction(reduced_image, thumbnail_image, thumb_width, thumb_height);
+      
+      thumbnail_image.quick_filter_from(reduced_image);
+      // Center the thumbnail image within its box.
+      int x_center = (thumb_width - thumbnail_image.get_x_size()) / 2;
+      int y_center = (thumb_height - thumbnail_image.get_y_size()) / 2;
+      
+      index_image.copy_sub_image(thumbnail_image, 
+				 pinfo._x_place + x_center, 
+				 pinfo._y_place + y_center);
+      
+      if (text_maker != (TextMaker *)NULL) {
+	text_maker->generate_into(photo->get_name(), index_image, 
+				  pinfo._x_place + thumb_width / 2, 
+				  pinfo._y_place + thumb_height + thumb_caption_height);
+      }
     }
     }
   }
   }
 
 
-  Filename output_filename(archive_dir, _name);
-  output_filename.set_extension("jpg");
-
-  nout << "Writing " << output_filename << "\n";
-  if (!index_image.write(output_filename)) {
-    nout << "Unable to write.\n";
-    return false;
+  if (generate_index_image) {
+    nout << "Writing " << output_filename << "\n";
+    if (!index_image.write(output_filename)) {
+      nout << "Unable to write.\n";
+      return false;
+    }
   }
   }
 
 
   _index_x_size = index_image.get_x_size();
   _index_x_size = index_image.get_x_size();

+ 3 - 0
pandaapp/src/indexify/indexParameters.cxx

@@ -37,6 +37,9 @@ Filename prev_icon;
 Filename next_icon;
 Filename next_icon;
 Filename up_icon;
 Filename up_icon;
 
 
+bool force_regenerate = false;
+bool format_rose = false;
+
 // Computed parameters
 // Computed parameters
 int thumb_count_x;
 int thumb_count_x;
 int thumb_count_y;
 int thumb_count_y;

+ 3 - 0
pandaapp/src/indexify/indexParameters.h

@@ -44,6 +44,9 @@ extern Filename prev_icon;
 extern Filename next_icon;
 extern Filename next_icon;
 extern Filename up_icon;
 extern Filename up_icon;
 
 
+extern bool force_regenerate;
+extern bool format_rose;
+
 void finalize_parameters();
 void finalize_parameters();
 
 
 // The following parameters are all computed based on the above.
 // The following parameters are all computed based on the above.

+ 92 - 35
pandaapp/src/indexify/indexify.cxx

@@ -41,7 +41,33 @@ Indexify() {
      "archives (typically JPEG files), and will generate a number of "
      "archives (typically JPEG files), and will generate a number of "
      "thumbnail images and a series of HTML pages to browse them.  It is "
      "thumbnail images and a series of HTML pages to browse them.  It is "
      "especially useful in preparation for burning the photo archives to "
      "especially useful in preparation for burning the photo archives to "
-     "CD.");
+     "CD.\n\n"
+
+     "A number of directories is named on the command line; each "
+     "directory must contain a number of image files, and all directories "
+     "should be within the same parent directory.  Each directory is "
+     "considered a \"roll\", which may or may not correspond to a physical "
+     "roll of film, and the photos within each directory are grouped "
+     "correspondingly on the generated HTML pages.\n\n"
+
+     "If a file exists by the same name as an image file but with the "
+     "extension \"cm\", that file is taken to be a HTML comment about that "
+     "particular image and is inserted the HTML page for that image.  "
+     "Similarly, if there is a file within a roll directory with the same "
+     "name as the directory itself (but with the extension \"cm\"), that file "
+     "is inserted into the front page to introduce that particular roll.\n\n"
+
+     "Normally, all image files with the specified extension (normally "
+     "\"jpg\") within a roll directory are included in the index, and sorted "
+     "into alphabetical (or numeric) order.  If you wish to specify a "
+     "different order, or use only a subset of the images in a directory, "
+     "create a file in the roll directory with the same name as the "
+     "directory itself, and the extension \"ls\".  This file should "
+     "simply list the filenames (with or without extension) within the "
+     "roll directory in the order they should be listed.  If the ls "
+     "file exists but is empty, it indicates that the files should be "
+     "listed in reverse order, as from a camera that loads its film "
+     "upside-down.");
 
 
   add_option
   add_option
     ("t", "title", 0,
     ("t", "title", 0,
@@ -63,6 +89,21 @@ Indexify() {
      "directories, from the directory named by -a.",
      "directories, from the directory named by -a.",
      &Indexify::dispatch_filename, NULL, &_roll_dir_root);
      &Indexify::dispatch_filename, NULL, &_roll_dir_root);
 
 
+  add_option
+    ("f", "", 0,
+     "Forces the regeneration of all reduced and thumbnail images, even if "
+     "image files already exist that seem to be newer than the source "
+     "image files.",
+     &Indexify::dispatch_none, &force_regenerate);
+
+  add_option
+    ("r", "", 0,
+     "Specifies that roll directory names are encoded using the Rose "
+     "convention of six digits: mmyyss, where mm and yy are the month and "
+     "year, and ss is a sequence number of the roll within the month.  This "
+     "name will be reformatted to m-yy/s for output.",
+     &Indexify::dispatch_none, &format_rose);
+
   add_option
   add_option
     ("e", "extension", 0,
     ("e", "extension", 0,
      "Specifies the filename extension (without a leading dot) to identify "
      "Specifies the filename extension (without a leading dot) to identify "
@@ -170,18 +211,23 @@ handle_args(ProgramBase::Args &args) {
     Filename filename = Filename::from_os_specific(*ai);
     Filename filename = Filename::from_os_specific(*ai);
     filename.standardize();
     filename.standardize();
     if (filename.is_directory()) {
     if (filename.is_directory()) {
-      RollDirectory *roll_dir = new RollDirectory(filename);
-      if (prev_roll_dir != (RollDirectory *)NULL) {
-        roll_dir->_prev = prev_roll_dir;
-        prev_roll_dir->_next = roll_dir;
+      string basename = filename.get_basename();
+      if (basename == "icons" || basename == "html" || basename == "reduced") {
+	nout << "Ignoring " << filename << "; indexify-generated directory.\n";
+
+      } else {
+	RollDirectory *roll_dir = new RollDirectory(filename);
+	if (prev_roll_dir != (RollDirectory *)NULL) {
+	  roll_dir->_prev = prev_roll_dir;
+	  prev_roll_dir->_next = roll_dir;
+	}
+	
+	_roll_dirs.push_back(roll_dir);
+	prev_roll_dir = roll_dir;
       }
       }
 
 
-      _roll_dirs.push_back(roll_dir);
-      prev_roll_dir = roll_dir;
-
     } else if (filename.exists()) {
     } else if (filename.exists()) {
-      nout << filename << " is not a directory name.\n";
-      return false;
+      nout << "Ignoring " << filename << "; not a directory.\n";
 
 
     } else {
     } else {
       nout << filename << " does not exist.\n";
       nout << filename << " does not exist.\n";
@@ -225,9 +271,9 @@ post_command_line() {
   if (_front_title.empty()) {
   if (_front_title.empty()) {
     // Supply a default title.
     // Supply a default title.
     if (_roll_dirs.size() == 1) {
     if (_roll_dirs.size() == 1) {
-      _front_title = _roll_dirs.front()->get_basename();
+      _front_title = _roll_dirs.front()->get_name();
     } else {
     } else {
-      _front_title = _roll_dirs.front()->get_basename() + " to " + _roll_dirs.back()->get_basename();
+      _front_title = _roll_dirs.front()->get_name() + " to " + _roll_dirs.back()->get_name();
     }
     }
   }
   }
     
     
@@ -257,41 +303,51 @@ post_command_line() {
     if (prev_icon.empty()) {
     if (prev_icon.empty()) {
       prev_icon = Filename("icons", default_left_icon_filename);
       prev_icon = Filename("icons", default_left_icon_filename);
       Filename icon_filename(_archive_dir, prev_icon);
       Filename icon_filename(_archive_dir, prev_icon);
-      icon_filename.make_dir();
-      icon_filename.set_binary();
 
 
-      ofstream output;
-      if (!icon_filename.open_write(output)) {
-        nout << "Unable to write to " << icon_filename << "\n";
-        exit(1);
+      if (force_regenerate || !icon_filename.exists()) {
+	nout << "Generating " << icon_filename << "\n";
+	icon_filename.make_dir();
+	icon_filename.set_binary();
+
+	ofstream output;
+	if (!icon_filename.open_write(output)) {
+	  nout << "Unable to write to " << icon_filename << "\n";
+	  exit(1);
+	}
+	output.write(default_left_icon, default_left_icon_size);
       }
       }
-      output.write(default_left_icon, default_left_icon_size);
     }
     }
     if (next_icon.empty()) {
     if (next_icon.empty()) {
       next_icon = Filename("icons", default_right_icon_filename);
       next_icon = Filename("icons", default_right_icon_filename);
       Filename icon_filename(_archive_dir, next_icon);
       Filename icon_filename(_archive_dir, next_icon);
-      icon_filename.make_dir();
-      icon_filename.set_binary();
-
-      ofstream output;
-      if (!icon_filename.open_write(output)) {
-        nout << "Unable to write to " << icon_filename << "\n";
-        exit(1);
+      if (force_regenerate || !icon_filename.exists()) {
+	nout << "Generating " << icon_filename << "\n";
+	icon_filename.make_dir();
+	icon_filename.set_binary();
+	
+	ofstream output;
+	if (!icon_filename.open_write(output)) {
+	  nout << "Unable to write to " << icon_filename << "\n";
+	  exit(1);
+	}
+	output.write(default_right_icon, default_right_icon_size);
       }
       }
-      output.write(default_right_icon, default_right_icon_size);
     }
     }
     if (up_icon.empty()) {
     if (up_icon.empty()) {
       up_icon = Filename("icons", default_up_icon_filename);
       up_icon = Filename("icons", default_up_icon_filename);
       Filename icon_filename(_archive_dir, up_icon);
       Filename icon_filename(_archive_dir, up_icon);
-      icon_filename.make_dir();
-      icon_filename.set_binary();
-
-      ofstream output;
-      if (!icon_filename.open_write(output)) {
-        nout << "Unable to write to " << icon_filename << "\n";
-        exit(1);
+      if (force_regenerate || !icon_filename.exists()) {
+	nout << "Generating " << icon_filename << "\n";
+	icon_filename.make_dir();
+	icon_filename.set_binary();
+      
+	ofstream output;
+	if (!icon_filename.open_write(output)) {
+	  nout << "Unable to write to " << icon_filename << "\n";
+	  exit(1);
+	}
+	output.write(default_up_icon, default_up_icon_size);
       }
       }
-      output.write(default_up_icon, default_up_icon_size);
     }
     }
   }
   }
 
 
@@ -339,6 +395,7 @@ run() {
   // Then go back and generate the HTML.
   // Then go back and generate the HTML.
 
 
   Filename html_filename(_archive_dir, "index.htm");
   Filename html_filename(_archive_dir, "index.htm");
+  nout << "Generating " << html_filename << "\n";
   html_filename.set_text();
   html_filename.set_text();
   ofstream root_html;
   ofstream root_html;
   if (!html_filename.open_write(root_html)) {
   if (!html_filename.open_write(root_html)) {

+ 154 - 11
pandaapp/src/indexify/rollDirectory.cxx

@@ -22,6 +22,10 @@
 #include "indent.h"
 #include "indent.h"
 #include "notify.h"
 #include "notify.h"
 #include "string_utils.h"
 #include "string_utils.h"
+#include "indexParameters.h"
+
+#include <ctype.h>
+#include <algorithm>
 
 
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 //     Function: RollDirectory::Constructor
 //     Function: RollDirectory::Constructor
@@ -33,6 +37,11 @@ RollDirectory(const Filename &dir) :
   _dir(dir)
   _dir(dir)
 {
 {
   _basename = _dir.get_basename();
   _basename = _dir.get_basename();
+  if (format_rose) {
+    _name = format_basename(_basename);
+  } else {
+    _name = _basename;
+  }
   _prev = (RollDirectory *)NULL;
   _prev = (RollDirectory *)NULL;
   _next = (RollDirectory *)NULL;
   _next = (RollDirectory *)NULL;
 }
 }
@@ -77,6 +86,21 @@ get_basename() const {
   return _basename;
   return _basename;
 }
 }
 
 
+////////////////////////////////////////////////////////////////////
+//     Function: RollDirectory::get_name
+//       Access: Public
+//  Description: Returns the formatted name of the directory.  This is
+//               often the same as the basename, but if -r is
+//               specified on the command line it might be a somewhat
+//               different, reformatted name.  In any case, it is the
+//               name of the roll as it should be presented to the
+//               user.
+////////////////////////////////////////////////////////////////////
+const string &RollDirectory::
+get_name() const {
+  return _name;
+}
+
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 //     Function: RollDirectory::scan
 //     Function: RollDirectory::scan
 //       Access: Public
 //       Access: Public
@@ -84,17 +108,71 @@ get_basename() const {
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 bool RollDirectory::
 bool RollDirectory::
 scan(const string &extension) {
 scan(const string &extension) {
-  vector_string contents;
-  if (!_dir.scan_directory(contents)) {
-    nout << "Could not read " << _dir << "\n";
-    return false;
+  bool reverse_order = false;
+  bool explicit_list = false;
+
+  // Check for an .ls file in the roll directory, which may give an
+  // explicit ordering, or if empty, it specifies reverse ordering.
+  Filename ls_filename(_dir, _basename + ".ls");
+  if (ls_filename.exists()) {
+    add_contributing_filename(ls_filename);
+    ls_filename.set_text();
+    ifstream ls;
+    if (!ls_filename.open_read(ls)) {
+      nout << "Could not read " << ls_filename << "\n";
+    } else {
+      bool any_words = false;
+      string word;
+      ls >> word;
+      while (!ls.eof() && !ls.fail()) {
+	any_words = true;
+	Filename try_filename(_dir, word);
+	if (!try_filename.exists()) {
+	  try_filename = Filename(_dir, word + "." + extension);
+	}
+	if (!try_filename.exists()) {
+	  try_filename = Filename(_dir, _basename + word + "." + extension);
+	}
+	if (!try_filename.exists()) {
+	  try_filename = Filename(_dir, _basename + "0" + word + "." + extension);
+	}
+	if (try_filename.exists()) {
+	  _photos.push_back(new Photo(this, try_filename.get_basename()));
+	} else {
+	  nout << "Frame " << word << " not found in " << _name << "\n";
+	}
+	ls >> word;
+      }
+
+      if (!any_words) {
+	// An empty .ls file just means reverse the order.
+	reverse_order = true;
+      } else {
+	// A non-empty .ls file has listed all the files we need; no
+	// need to scan the directory.
+	explicit_list = true;
+      }
+    }
   }
   }
 
 
-  vector_string::iterator ci;
-  for (ci = contents.begin(); ci != contents.end(); ++ci) {
-    Filename basename = (*ci);
-    if (basename.get_extension() == extension) {
-      _photos.push_back(new Photo(this, basename));
+  if (!explicit_list) {
+    vector_string contents;
+    
+    if (!_dir.scan_directory(contents)) {
+      nout << "Could not read " << _dir << "\n";
+      return false;
+    }
+
+    if (reverse_order) {
+      reverse(contents.begin(), contents.end());
+    }
+
+    vector_string::iterator ci;
+    for (ci = contents.begin(); ci != contents.end(); ++ci) {
+      Filename basename = (*ci);
+      if (basename.get_extension() == extension) {
+	_photos.push_back(new Photo(this, basename));
+      }
     }
     }
   }
   }
 
 
@@ -131,6 +209,20 @@ collect_index_images() {
   }
   }
 }
 }
 
 
+////////////////////////////////////////////////////////////////////
+//     Function: RollDirectory::get_newest_contributing_filename
+//       Access: Public
+//  Description: Returns the Filename with the newest timestamp that
+//               contributes to the contents of the index images in
+//               this directory, if any (other than the images
+//               themselves).  This is used by the IndexImage code to
+//               determine if it needs to regenerate the index image.
+////////////////////////////////////////////////////////////////////
+const Filename &RollDirectory::
+get_newest_contributing_filename() const {
+  return _newest_contributing_filename;
+}
+
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 //     Function: RollDirectory::get_num_photos
 //     Function: RollDirectory::get_num_photos
 //       Access: Public
 //       Access: Public
@@ -218,9 +310,12 @@ generate_html(ostream &root_html, const Filename &archive_dir,
 
 
   } else {
   } else {
     root_html
     root_html
-      << "<h2>" << _basename << "</h2>\n";
+      << "<h2>" << _name << "</h2>\n";
   }
   }
 
 
+  nout << "Generating " << Filename(archive_dir, "html/")
+       << _basename << "/*\n";
+
   IndexImages::iterator ii;
   IndexImages::iterator ii;
   for (ii = _index_images.begin(); ii != _index_images.end(); ++ii) {
   for (ii = _index_images.begin(); ii != _index_images.end(); ++ii) {
     IndexImage *index_image = (*ii);
     IndexImage *index_image = (*ii);
@@ -239,7 +334,7 @@ generate_html(ostream &root_html, const Filename &archive_dir,
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 void RollDirectory::
 void RollDirectory::
 output(ostream &out) const {
 output(ostream &out) const {
-  out << _basename;
+  out << _name;
 }
 }
 
 
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
@@ -300,6 +395,7 @@ insert_html_comment(ostream &html, Filename cm_filename) {
   }
   }
 
 
   // We didn't find it.  Insert the whole file.
   // We didn't find it.  Insert the whole file.
+  cm.clear();
   cm.seekg(0);
   cm.seekg(0);
   int ch = cm.get();
   int ch = cm.get();
   while (ch != EOF) {
   while (ch != EOF) {
@@ -310,6 +406,20 @@ insert_html_comment(ostream &html, Filename cm_filename) {
   return true;
   return true;
 }
 }
 
 
+////////////////////////////////////////////////////////////////////
+//     Function: RollDirectory::add_contributing_filename
+//       Access: Private
+//  Description: Specifies an additional filename that contributes to
+//               the index image.
+////////////////////////////////////////////////////////////////////
+void RollDirectory::
+add_contributing_filename(const Filename &filename) {
+  if (_newest_contributing_filename.empty() ||
+      _newest_contributing_filename.compare_timestamps(filename) < 0) {
+    _newest_contributing_filename = filename;
+  }
+}
+
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 //     Function: RollDirectory::insert_html_comment_body
 //     Function: RollDirectory::insert_html_comment_body
 //       Access: Private, Static
 //       Access: Private, Static
@@ -334,3 +444,36 @@ insert_html_comment_body(ostream &html, istream &cm) {
   // Reached end of file; good enough.
   // Reached end of file; good enough.
   return true;
   return true;
 }
 }
+
+////////////////////////////////////////////////////////////////////
+//     Function: RollDirectory::format_basename
+//       Access: Private, Static
+//  Description: Reformats the roll directory into a user-friendly
+//               name based on its encoded directory name.
+////////////////////////////////////////////////////////////////////
+string RollDirectory::
+format_basename(const string &basename) {
+  if (basename.length() <= 4) {
+    return basename;
+  }
+
+  // The first four characters must be digits.
+  for (size_t i = 0; i < 4; i++) {
+    if (!isdigit(basename[i])) {
+      return basename;
+    }
+  }
+
+  string mm = basename.substr(0, 2);
+  string yy = basename.substr(2, 2);
+  string ss = basename.substr(4);
+
+  if (mm[0] == '0') {
+    mm = mm[1];
+  }
+  while (ss.length() > 1 && ss[0] == '0') {
+    ss = ss.substr(1);
+  }
+
+  return mm + "-" + yy + "/" + ss;
+}

+ 8 - 0
pandaapp/src/indexify/rollDirectory.h

@@ -40,9 +40,12 @@ public:
 
 
   const Filename &get_dir() const;
   const Filename &get_dir() const;
   const string &get_basename() const;
   const string &get_basename() const;
+  const string &get_name() const;
   bool scan(const string &extension);
   bool scan(const string &extension);
   void collect_index_images();
   void collect_index_images();
 
 
+  const Filename &get_newest_contributing_filename() const;
+
   int get_num_photos() const;
   int get_num_photos() const;
   Photo *get_photo(int n) const;
   Photo *get_photo(int n) const;
 
 
@@ -59,7 +62,9 @@ public:
   static bool insert_html_comment(ostream &html, Filename cm_filename);
   static bool insert_html_comment(ostream &html, Filename cm_filename);
 
 
 private:
 private:
+  void add_contributing_filename(const Filename &filename);
   static bool insert_html_comment_body(ostream &html, istream &cm);
   static bool insert_html_comment_body(ostream &html, istream &cm);
+  static string format_basename(const string &basename);
 
 
 public:
 public:
   RollDirectory *_prev;
   RollDirectory *_prev;
@@ -68,8 +73,11 @@ public:
 private:
 private:
   Filename _dir;
   Filename _dir;
   string _basename;
   string _basename;
+  string _name;
   typedef pvector<Photo *> Photos;
   typedef pvector<Photo *> Photos;
   Photos _photos;
   Photos _photos;
+  
+  Filename _newest_contributing_filename;
 
 
   typedef pvector<IndexImage *> IndexImages;
   typedef pvector<IndexImage *> IndexImages;
   IndexImages _index_images;
   IndexImages _index_images;