Merge pull request #6951 from sgilbertson/issue-4765-queue-template-time-and-date

Issue 4765 queue template time and date
This commit is contained in:
Lawrence37 2024-03-01 21:46:29 -08:00 committed by GitHub
commit c3402b18d5
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 212 additions and 3 deletions

View File

@ -2107,7 +2107,26 @@ QUEUE_DESTPREVIEW_TOOLTIP;Destination path for the first selected image appears
QUEUE_FORMAT_TITLE;File Format
QUEUE_LOCATION_FOLDER;Save to folder
QUEUE_LOCATION_TEMPLATE;Use template
QUEUE_LOCATION_TEMPLATE_TOOLTIP;Specify the output location based on the source photo's location, rank, trash status or position in the queue.\n\n<b>%dN</b>, <b>%d-N</b>, <b>%pN</b>, <b>%p-N</b>, <b>%PN</b> and <b>%P-N</b> (N = 1..9) will be replaced by elements of the image file's directory path (not including the file name):\n<b>%dN</b> = Nth directory from the end of the path\n<b>%d-N</b> = Nth directory from the start of the path\n<b>%pN</b> = all directories up to the Nth from the end of the path\n<b>%p-N</b> = the first N directories in the path\n<b>%PN</b> = the last N directories in the path\n<b>%P-N</b> = all directories from the Nth to the end of the path\n<b>%f</b> = base filename (no extension)\nFor Windows paths, <b>%d-1</b> is the drive letter and colon, and <b>%d-2</b> is the base directory on that drive.\n\nUsing the following pathname as an example:\n<b>/home/tom/photos/2010-10-31/photo1.raw</b>\nthe meaning of the formatting strings follows:\n<b>%d4</b> = <b>%d-1</b> = <i>home</i>\n<b>%d3</b> = <b>%d-2</b> = <i>tom</i>\n<b>%d2</b> = <b>%d-3</b> = <i>photos</i>\n<b>%d1</b> = <b>%d-4</b> = <i>2010-10-31</i>\n<b>%p1</b> = <b>%p-4</b> = <i>/home/tom/photos/2010-10-31/</i>\n<b>%p2</b> = <b>%p-3</b> = <i>/home/tom/photos/</i>\n<b>%p3</b> = <b>%p-2</b> = <i>/home/tom/</i>\n<b>%p4</b> = <b>%p-1</b> = <i>/home/</i>\n<b>%P1</b> = <b>%P-4</b> = <i>2010-10-31/</i>\n<b>%P2</b> = <b>%P-3</b> = <i>photos/2010-10-31/</i>\n<b>%P3</b> = <b>%P-2</b> = <i>tom/photos/2010-10-31/</i>\n<b>%P4</b> = <b>%P-1</b> = <i>/home/tom/photos/2010-10-31/</i>\n<b>%f</b> = <i>photo1</i>\n\n<b>%r</b> will be replaced by the photo's rank. If the photo is unranked, '<i>0</i>' is used. If the photo is in the trash, '<i>x</i>' is used.\n\n<b>%s1</b>, ..., <b>%s9</b> will be replaced by the photo's initial position in the queue at the time the queue is started. The number specifies the padding, e.g. <b>%s3</b> results in '<i>001</i>'.\n\nIf you want to save the output image alongside the source image, write:\n<b>%p1/%f</b>\n\nIf you want to save the output image in a folder named '<i>converted</i>' located in the source photo's folder, write:\n<b>%p1/converted/%f</b>\n\nIf you want to save the output image in\n'<i>/home/tom/photos/converted/2010-10-31</i>', write:\n<b>%p-3/converted/%P-4/%f</b>
QUEUE_LOCATION_TEMPLATE_TOOLTIP;Specify the output location based on characteristics such as the source photo's location, rank, trash status or position in the queue.\n\nThe output template field value can include specifiers beginning with <b>%</b>, which are replaced by those characteristics in the actual destination path.\n\nPress the <b>?</b> button for full instructions.
QUEUE_LOCATION_TEMPLATE_HELP_BUTTON_TOOLTIP;Show or hide a help panel with instructions for creating location templates
QUEUE_LOCATION_TEMPLATE_HELP_TITLE;Creating an output template
QUEUE_LOCATION_TEMPLATE_HELP_INTRO;The output template field allows you to to dynamically customize the destination folder and filename. When you include certain specifiers, which begin with <b>%</b>, they are replaced by the program when each file is being saved.\n\nThe sections below describe each type of specifier.
QUEUE_LOCATION_TEMPLATE_HELP_PATHS_TITLE;Directories and partial paths
QUEUE_LOCATION_TEMPLATE_HELP_PATHS_INTRO;The <b>%dN</b>, <b>%d-N</b>, <b>%pN</b>, <b>%p-N</b>, <b>%PN</b> and <b>%P-N</b> (N = 1..9) specifiers will be replaced by elements of the image file's directory path.\nThe format specifiers operate as follows:\n <b>%dN</b> = Nth directory from the end of the path\n <b>%d-N</b> = Nth directory from the start of the path\n <b>%pN</b> = all directories up to the Nth from the end of the path\n <b>%p-N</b> = the first N directories in the path\n <b>%PN</b> = the last N directories in the path\n <b>%P-N</b> = all directories from the Nth to the end of the path\n <b>%f</b> = base filename (no extension)
QUEUE_LOCATION_TEMPLATE_HELP_PATHS_INTRO_WINDOWS;For Windows paths, <b>%d-1</b> is the drive letter and colon, and <b>%d-2</b> is the base directory on that drive.
QUEUE_LOCATION_TEMPLATE_HELP_PATHS_BODY_1;Using this pathname as an example:
QUEUE_LOCATION_TEMPLATE_HELP_PATHS_EXAMPLE_LINUX;/home/tom/photos/2010-10-31/photo1.raw
QUEUE_LOCATION_TEMPLATE_HELP_PATHS_EXAMPLE_WINDOWS;D:\tom\photos\2010-10-31\photo1.raw
QUEUE_LOCATION_TEMPLATE_HELP_PATHS_BODY_2;The meanings of the formatting strings are:
QUEUE_LOCATION_TEMPLATE_HELP_RESULT_MISMATCH;ERROR: 2nd result is different:
QUEUE_LOCATION_TEMPLATE_HELP_RANK_TITLE;Rank
QUEUE_LOCATION_TEMPLATE_HELP_RANK_BODY;<b>%r</b> will be replaced by the photo's rank. If the photo is unranked, '<i>0</i>' is used. If the photo is in the trash, '<i>x</i>' is used.
QUEUE_LOCATION_TEMPLATE_HELP_SEQUENCE_TITLE;Position/sequence in queue
QUEUE_LOCATION_TEMPLATE_HELP_SEQUENCE_BODY;<b>%s1</b>, ..., <b>%s9</b> will be replaced by the photo's initial position in the queue at the time the queue is started. The number specifies the padding, e.g. <b>%s3</b> results in '<i>001</i>'.
QUEUE_LOCATION_TEMPLATE_HELP_TIMESTAMP_TITLE;Date and time
QUEUE_LOCATION_TEMPLATE_HELP_TIMESTAMP_BODY;Three different date/time values may be used in templates:\n <b>%tE"%Y-%m-%d"</b> = when export started\n <b>%tF"%Y-%m-%d"</b> = when file was last saved\n <b>%tP"%Y-%m-%d"</b> = when photo was taken\nThe quoted string defines the format of the resulting date and/or time. The format string <b>%tF"%Y-%m-%d"</b> is just one example. The string can use all conversion specifiers defined for the g_date_time_format function (see https://docs.gtk.org/glib/method.DateTime.format.html).\n\nExample format strings:
QUEUE_LOCATION_TEMPLATE_HELP_EXAMPLES_TITLE;Common examples
QUEUE_LOCATION_TEMPLATE_HELP_EXAMPLES_BODY;If you want to save the output image alongside the source image, write:\n<b>%p1/%f</b>\n\nIf you want to save the output image in a folder named '<i>converted</i>' located in the source photo's folder, write:\n<b>%p1/converted/%f</b>\n\nIf you want to save the output image in\n'<i>/home/tom/photos/converted/2010-10-31</i>', write:\n<b>%p-3/converted/%P-4/%f</b>
QUEUE_LOCATION_TITLE;Output Location
QUEUE_STARTSTOP_TOOLTIP;Start or stop processing the images in the queue.\n\nShortcut: <b>Ctrl</b>+<b>s</b>
SAMPLEFORMAT_0;Unknown data format

View File

@ -20,6 +20,7 @@
#include <glib/gstdio.h>
#include <cstring>
#include <functional>
#include "../rtengine/imagedata.h"
#include "../rtengine/rt_math.h"
#include "../rtengine/procparams.h"
@ -94,6 +95,21 @@ namespace // local helper functions
}
}
}
// Look in templateText at index ix for quoted string containing a time format string, and
// use that string to format dateTime. Append the formatted time to path.
void appendFormattedTime(Glib::ustring& path, unsigned int& ix, const Glib::ustring& templateText, const Glib::DateTime& dateTime)
{
constexpr gunichar quoteMark('"');
if ((ix + 1) < templateText.size() && templateText[ix] == quoteMark) {
const auto endPos = templateText.find_first_of(quoteMark, ++ix);
if (endPos != Glib::ustring::npos) {
Glib::ustring formatString(templateText, ix, endPos-ix);
path += dateTime.format(formatString);
ix = endPos;
}
}
}
}
BatchQueue::BatchQueue (FileCatalog* aFileCatalog) : processing(nullptr), fileCatalog(aFileCatalog), sequence(0), listener(nullptr)
@ -1001,6 +1017,43 @@ Glib::ustring BatchQueue::calcAutoFileNameBase (const Glib::ustring& origFileNam
seqstr << sequence;
path += seqstr.str ();
} else if (options.savePathTemplate[ix] == 't') {
// Insert formatted date/time value. Character after 't' defines time source
if (++ix < options.savePathTemplate.size()) {
Glib::DateTime dateTime;
switch(options.savePathTemplate[ix++])
{
case 'E': // (approximate) time when export started
{
dateTime = Glib::DateTime::create_now_local();
break;
}
case 'F': // time when file was last saved
{
Glib::RefPtr<Gio::File> file = Gio::File::create_for_path(origFileName);
if (file) {
Glib::RefPtr<Gio::FileInfo> info = file->query_info(G_FILE_ATTRIBUTE_TIME_MODIFIED);
if (info) {
dateTime = info->get_modification_date_time();
}
}
break;
}
case 'P': // time when picture was taken
{
const auto timestamp = FramesData(origFileName).getDateTimeAsTS();
dateTime = Glib::DateTime::create_now_local(timestamp);
break;
}
default:
{
break;
}
}
if (dateTime) {
appendFormattedTime(path, ix, options.savePathTemplate, dateTime);
}
}
}
}

View File

@ -73,7 +73,10 @@ BatchQueuePanel::BatchQueuePanel (FileCatalog* aFileCatalog) : parent(nullptr)
hb2->pack_start (*useTemplate, Gtk::PACK_SHRINK, 4);
outdirTemplate = Gtk::manage (new Gtk::Entry ());
hb2->pack_start (*outdirTemplate);
odvb->pack_start (*hb2, Gtk::PACK_SHRINK, 4);
templateHelpButton = Gtk::manage (new Gtk::ToggleButton("?"));
templateHelpButton->set_tooltip_markup (M ("QUEUE_LOCATION_TEMPLATE_HELP_BUTTON_TOOLTIP"));
hb2->pack_start (*templateHelpButton, Gtk::PACK_SHRINK, 0);
odvb->pack_start (*hb2, Gtk::PACK_SHRINK, 0);
outdirTemplate->set_tooltip_markup (M("QUEUE_LOCATION_TEMPLATE_TOOLTIP"));
useTemplate->set_tooltip_markup (M("QUEUE_LOCATION_TEMPLATE_TOOLTIP"));
Gtk::Box* hb3 = Gtk::manage (new Gtk::Box ());
@ -136,6 +139,7 @@ BatchQueuePanel::BatchQueuePanel (FileCatalog* aFileCatalog) : parent(nullptr)
outdirTemplate->signal_changed().connect (sigc::mem_fun(*this, &BatchQueuePanel::saveOptions));
useTemplate->signal_toggled().connect (sigc::mem_fun(*this, &BatchQueuePanel::saveOptions));
useFolder->signal_toggled().connect (sigc::mem_fun(*this, &BatchQueuePanel::saveOptions));
templateHelpButton->signal_toggled().connect (sigc::mem_fun(*this, &BatchQueuePanel::templateHelpButtonToggled));
saveFormatPanel->setListener (this);
// setup button bar
@ -147,8 +151,19 @@ BatchQueuePanel::BatchQueuePanel (FileCatalog* aFileCatalog) : parent(nullptr)
topBox->pack_start (*fdir, Gtk::PACK_EXPAND_WIDGET, 4);
topBox->pack_start (*fformat, Gtk::PACK_EXPAND_WIDGET, 4);
middleSplitPane = Gtk::manage (new Gtk::Paned(Gtk::ORIENTATION_HORIZONTAL));
templateHelpTextView = Gtk::manage (new Gtk::TextView());
templateHelpTextView->set_editable(false);
templateHelpTextView->set_wrap_mode(Gtk::WRAP_WORD);
scrolledTemplateHelpWindow = Gtk::manage(new Gtk::ScrolledWindow());
scrolledTemplateHelpWindow->add(*templateHelpTextView);
middleSplitPane->pack1 (*scrolledTemplateHelpWindow);
middleSplitPane->pack2 (*batchQueue);
scrolledTemplateHelpWindow->set_visible(false); // initially hidden, templateHelpButton shows it
scrolledTemplateHelpWindow->set_no_show_all(true);
// add middle browser area
pack_start (*batchQueue);
pack_start (*middleSplitPane);
// lower box with thumbnail zoom
bottomBox = Gtk::manage (new Gtk::Box ());
@ -322,6 +337,122 @@ void BatchQueuePanel::setGuiFromBatchState(bool queueRunning, int qsize)
updateTab(qsize);
}
void BatchQueuePanel::templateHelpButtonToggled()
{
bool visible = templateHelpButton->get_active();
auto buffer = templateHelpTextView->get_buffer();
if (buffer->get_text().empty()) {
// Populate the help text the first time it's shown
populateTemplateHelpBuffer(buffer);
const auto fullWidth = middleSplitPane->get_width();
middleSplitPane->set_position(fullWidth / 2);
}
scrolledTemplateHelpWindow->set_visible(visible);
templateHelpTextView->set_visible(visible);
}
void BatchQueuePanel::populateTemplateHelpBuffer(Glib::RefPtr<Gtk::TextBuffer> buffer)
{
auto pos = buffer->begin();
const auto insertTopicHeading = [&pos, buffer](const Glib::ustring& text) {
pos = buffer->insert_markup(pos, Glib::ustring::format("\n\n<u><b>", text, "</b></u>\n"));
};
const auto insertTopicBody = [&pos, buffer](const Glib::ustring& text) {
pos = buffer->insert_markup(pos, Glib::ustring::format("\n", text, "\n"));
};
const auto mainTitle = M("QUEUE_LOCATION_TEMPLATE_HELP_TITLE");
pos = buffer->insert_markup(pos, Glib::ustring::format("<big><b>", mainTitle, "</b></big>\n"));
pos = buffer->insert_markup(pos, M("QUEUE_LOCATION_TEMPLATE_HELP_INTRO"));
insertTopicHeading(M("QUEUE_LOCATION_TEMPLATE_HELP_EXAMPLES_TITLE"));
pos = buffer->insert_markup(pos, M("QUEUE_LOCATION_TEMPLATE_HELP_EXAMPLES_BODY"));
insertTopicHeading(M("QUEUE_LOCATION_TEMPLATE_HELP_PATHS_TITLE"));
pos = buffer->insert_markup(pos, M("QUEUE_LOCATION_TEMPLATE_HELP_PATHS_INTRO"));
pos = buffer->insert(pos, "\n");
#ifdef _WIN32
pos = buffer->insert_markup(pos, M("QUEUE_LOCATION_TEMPLATE_HELP_PATHS_INTRO_WINDOWS"));
pos = buffer->insert(pos, "\n");
#endif
pos = buffer->insert(pos, "\n");
pos = buffer->insert_markup(pos, M("QUEUE_LOCATION_TEMPLATE_HELP_PATHS_BODY_1"));
#ifdef _WIN32
const auto exampleFilePath = M("QUEUE_LOCATION_TEMPLATE_HELP_PATHS_EXAMPLE_WINDOWS");
#else
const auto exampleFilePath = M("QUEUE_LOCATION_TEMPLATE_HELP_PATHS_EXAMPLE_LINUX");
#endif
pos = buffer->insert_markup(pos, Glib::ustring::format("\n ", exampleFilePath, "\n"));
pos = buffer->insert_markup(pos, M("QUEUE_LOCATION_TEMPLATE_HELP_PATHS_BODY_2"));
// Examples are generated from exampleFilePath using the actual template processing function
const Options savedOptions = options; // to be restored after generating example results
options.saveUsePathTemplate = true;
// Since this code only ever runs once (the first time the help text is presented), no attempt is
// made to be efficient. Use a brute-force method to discover the number of elements in exampleFilePath.
int pathElementCount = 0;
for (int n=9; n>=0; n--) {
options.savePathTemplate = Glib::ustring::format("%d", n);
const auto result = BatchQueue::calcAutoFileNameBase(exampleFilePath);
if (!result.empty()) {
// The 'd' specifier returns an empty string if N exceeds the number of path elements, so
// the largest N that does not return an empty string is the number of elements in exampleFilePath.
pathElementCount = n;
break;
}
}
// Function inserts examples for a particular specifier, with every valid N value for the
// number of elements in the path.
const auto insertPathExamples = [&buffer, &pos, pathElementCount, exampleFilePath](char letter, int offset1, int mult1, int offset2, int mult2)
{
for (int n=0; n<pathElementCount; n++) {
auto path1 = Glib::ustring::format("%", letter, offset1+n*mult1);
auto path2 = Glib::ustring::format("%", letter, offset2+n*mult2);
options.savePathTemplate = path1;
auto result1 = BatchQueue::calcAutoFileNameBase(exampleFilePath);
options.savePathTemplate = path2;
auto result2 = BatchQueue::calcAutoFileNameBase(exampleFilePath);
pos = buffer->insert_markup(pos, Glib::ustring::format("\n <tt><b>", path1, "</b> = <b>", path2, "</b> = <i>", result1, "</i></tt>"));
if (result1 != result2) {
// If this error appears, it indicates a coding error in either BatchQueue::calcAutoFileNameBase
// or BatchQueuePanel::populateTemplateHelpBuffer.
pos = buffer->insert_markup(pos, Glib::ustring::format(" ", M("QUEUE_LOCATION_TEMPLATE_HELP_RESULT_MISMATCH"), " ", result2));
}
}
};
// Example outputs in comments below are for a 4-element path.
insertPathExamples('d', pathElementCount, -1, -1, -1); // <b>%d4</b> = <b>%d-1</b> = <i>home</i>
insertPathExamples('p', 1, 1, -pathElementCount, 1); // <b>%p1</b> = <b>%p-4</b> = <i>/home/tom/photos/2010-10-31/</i>
insertPathExamples('P', 1, 1, -pathElementCount, 1); // <b>%P1</b> = <b>%P-4</b> = <i>2010-10-31/</i>
{
const Glib::ustring fspecifier("%f");
options.savePathTemplate = fspecifier;
const auto result = BatchQueue::calcAutoFileNameBase(exampleFilePath);
pos = buffer->insert_markup(pos, Glib::ustring::format("\n <tt><b>", fspecifier, "</b> = <i>", result, "</i></tt>"));
}
insertTopicHeading(M("QUEUE_LOCATION_TEMPLATE_HELP_RANK_TITLE"));
pos = buffer->insert_markup(pos, M("QUEUE_LOCATION_TEMPLATE_HELP_RANK_BODY"));
insertTopicHeading(M("QUEUE_LOCATION_TEMPLATE_HELP_SEQUENCE_TITLE"));
pos = buffer->insert_markup(pos, M("QUEUE_LOCATION_TEMPLATE_HELP_SEQUENCE_BODY"));
insertTopicHeading(M("QUEUE_LOCATION_TEMPLATE_HELP_TIMESTAMP_TITLE"));
pos = buffer->insert_markup(pos, M("QUEUE_LOCATION_TEMPLATE_HELP_TIMESTAMP_BODY"));
const Glib::ustring dateTimeFormatExamples[] = {
"%Y-%m-%d",
"%Y%m%d_%H%M%S",
"%y/%b/%-d/"
};
const auto timezone = Glib::DateTime::create_now_local().get_timezone();
const auto timeForExamples = Glib::DateTime::create_from_iso8601("2001-02-03T04:05:06.123456", timezone);
for (auto && fmt : dateTimeFormatExamples) {
const auto result = timeForExamples.format(fmt);
pos = buffer->insert_markup(pos, Glib::ustring::format("\n <tt><b>%tE\"", fmt, "\"</b> = <i>", result, "</i></tt>"));
}
pos = buffer->insert(pos, "\n");
options = savedOptions; // Do not add any lines in this function below here
}
void BatchQueuePanel::addBatchQueueJobs(const std::vector<BatchQueueEntry*>& entries, bool head)
{
batchQueue->addEntries(entries, head);

View File

@ -52,8 +52,12 @@ class BatchQueuePanel : public Gtk::Box,
RTWindow* parent;
BatchQueue* batchQueue;
Gtk::TextView* templateHelpTextView;
Gtk::ScrolledWindow* scrolledTemplateHelpWindow;
Gtk::ToggleButton* templateHelpButton;
Gtk::Box* bottomBox;
Gtk::Box* topBox;
Gtk::Paned* middleSplitPane;
std::atomic<bool> queueShouldRun;
@ -80,6 +84,8 @@ private:
void stopBatchProc ();
void startOrStopBatchProc();
void setGuiFromBatchState(bool queueRunning, int qsize);
void templateHelpButtonToggled();
void populateTemplateHelpBuffer(Glib::RefPtr<Gtk::TextBuffer> buffer);
void pathFolderChanged ();
void pathFolderButtonPressed ();