Ansel 0.0
A darktable fork - bloat + design vision
Loading...
Searching...
No Matches
src/common/variables.c
Go to the documentation of this file.
1/*
2 This file is part of darktable,
3 Copyright (C) 2010 Andrea Purracchio.
4 Copyright (C) 2010-2012 Henrik Andersson.
5 Copyright (C) 2010-2012, 2014 johannes hanika.
6 Copyright (C) 2010 Pascal de Bruijn.
7 Copyright (C) 2010 Stuart Henderson.
8 Copyright (C) 2010-2012, 2014, 2016-2017 Tobias Ellinghaus.
9 Copyright (C) 2011 Daniel Andersson.
10 Copyright (C) 2012 Jérémy Rosen.
11 Copyright (C) 2012 Richard Wonka.
12 Copyright (C) 2013 Simon Spannagel.
13 Copyright (C) 2014 Kevin.
14 Copyright (C) 2014-2016 Roman Lebedev.
15 Copyright (C) 2015 Jake Probst.
16 Copyright (C) 2015 Šarūnas Burdulis.
17 Copyright (C) 2016 Kael Shipman.
18 Copyright (C) 2017 David-Tillmann Schaefer.
19 Copyright (C) 2017 luzpaz.
20 Copyright (C) 2019-2020 Aldric Renaudin.
21 Copyright (C) 2019 Denis Dyakov.
22 Copyright (C) 2019-2022 Pascal Obry.
23 Copyright (C) 2019-2022 Philippe Weyland.
24 Copyright (C) 2020 Chris Elston.
25 Copyright (C) 2020 hatsunearu.
26 Copyright (C) 2020 Heiko Bauke.
27 Copyright (C) 2020 Hubert Kowalski.
28 Copyright (C) 2020-2021 Mark-64.
29 Copyright (C) 2021 Diederik Ter Rahe.
30 Copyright (C) 2021 Marco Carrarini.
31 Copyright (C) 2021-2022 Ralf Brown.
32 Copyright (C) 2022-2026 Aurélien PIERRE.
33 Copyright (C) 2022 Martin Bařinka.
34 Copyright (C) 2023-2026 Guillaume Stutin.
35 Copyright (C) 2023 Luca Zulberti.
36
37 darktable is free software: you can redistribute it and/or modify
38 it under the terms of the GNU General Public License as published by
39 the Free Software Foundation, either version 3 of the License, or
40 (at your option) any later version.
41
42 darktable is distributed in the hope that it will be useful,
43 but WITHOUT ANY WARRANTY; without even the implied warranty of
44 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
45 GNU General Public License for more details.
46
47 You should have received a copy of the GNU General Public License
48 along with darktable. If not, see <http://www.gnu.org/licenses/>.
49*/
50#include "widgets/bauhaus.h"
51#include "common/paths.h" // DT_PATH_MAX
53#include "common/variables.h"
56#include "common/image.h"
57#include "caches/image_cache.h"
58#include "metadata/metadata.h"
59#include "common/opencl.h"
60#include "common/utility.h"
61#include "metadata/tags.h"
62#include "common/datetime.h"
63#include "common/conf.h"
64
65#include <stdio.h>
66#include <string.h>
67#include <time.h>
68
69typedef struct dt_variables_data_t
70{
72 GDateTime *time;
73 GDateTime *file_datetime;
74
76 guint sequence;
77
78 // max image size taken from export module GUI, can be zero
81
82 // total sensor size, before RAW crop
85
86 // max RAW file size, after the raw crop
89
90 // image export size after crop and export resize
95
96 char *homedir;
98 const char *file_ext;
99
100 gboolean have_exif_dt;
101 gboolean show_msec;
107 int stars;
108 GDateTime *datetime;
109
115 double longitude;
116 double latitude;
117 double elevation;
122
123 uint32_t tags_flags;
124
125 int flags;
126
128
135
136static char *_expand_source(dt_variables_params_t *params, char **source, char extra_stop);
137
138gboolean dt_get_user_pictures_dir(const gchar *homedir, gchar *picdir, size_t picdir_size)
139{
140 if(IS_NULL_PTR(homedir) || picdir_size == 0 || IS_NULL_PTR(picdir))
141 return 0;
142
143 gchar dir[DT_PATH_MAX] = { 0 };
144 const gchar *special_pictures_dir = g_get_user_special_dir(G_USER_DIRECTORY_PICTURES);
145 if(IS_NULL_PTR(special_pictures_dir))
146 g_snprintf(dir, sizeof(dir), "%s" G_DIR_SEPARATOR_S "Pictures", homedir);
147 else
148 g_strlcpy(dir, special_pictures_dir, sizeof(dir));
149
150 if(*dir == 0)
151 fprintf(stderr,"Error: Can't get user's pictures folder.\n");
152 else
153 return g_strlcpy(picdir, dir, picdir_size) >= 1;
154
155 return 0;
156}
157
158// gather some data that might be used for variable expansion
159static void _init_expansion(dt_variables_params_t *params, gboolean iterate)
160{
161 if(iterate) params->data->sequence++;
162
163 params->data->homedir = dt_loc_get_home_dir(NULL);
164
165 gchar picture_folder[DT_PATH_MAX] = { 0 };
166 dt_get_user_pictures_dir(params->data->homedir, picture_folder, sizeof(picture_folder));
167 params->data->pictures_folder = g_strdup(picture_folder);
168
169 if(params->filename)
170 {
171 params->data->file_ext = dt_util_path_get_extension(params->filename);
172 if(IS_NULL_PTR(params->data->file_ext)) params->data->file_ext = params->filename + strlen(params->filename);
173 params->data->file_datetime = dt_util_get_file_datetime(params->filename);
174 }
175 else
176 params->data->file_ext = NULL;
177
178 /* image exif time */
179 params->data->have_exif_dt = FALSE;
180 params->data->exif_iso = 100;
181 params->data->camera_maker = NULL;
182 params->data->camera_alias = NULL;
183 params->data->exif_lens = NULL;
184 params->data->version = 0;
185 params->data->stars = 0;
186 params->data->exif_exposure = 0.0f;
187 params->data->exif_exposure_bias = NAN;
188 params->data->exif_aperture = 0.0f;
189 params->data->exif_focal_length = 0.0f;
190 params->data->exif_focus_distance = 0.0f;
191 params->data->longitude = NAN;
192 params->data->latitude = NAN;
193 params->data->elevation = NAN;
194 params->data->crop_width = 0;
195 params->data->crop_height = 0;
196 params->data->import_timestamp = 0;
197 params->data->change_timestamp = 0;
198 params->data->export_timestamp = 0;
199 params->data->print_timestamp = 0;
200 params->data->show_msec = dt_conf_get_bool("lighttable/ui/milliseconds");
201
202 dt_image_t *img = NULL;
203 _image_case release = NONE;
204
205 if(params->img)
206 {
207 img = (dt_image_t *)params->img;
208 }
209 else if(params->imgid > UNKNOWN_IMAGE)
210 {
211 img = dt_image_cache_get(params->imgid, 'r');
212 release = IMGID;
213 }
214
215 if(img)
216 {
218 if(params->data->datetime)
219 params->data->have_exif_dt = TRUE;
220 params->data->exif_iso = img->exif_iso;
221 params->data->camera_maker = g_strdup(img->camera_maker);
222 params->data->camera_alias = g_strdup(img->camera_alias);
223 params->data->exif_lens = g_strdup(img->exif_lens);
224 params->data->version = img->version;
225 params->data->stars = (img->flags & 0x7);
226 if(params->data->stars == 6 || (img->flags & DT_IMAGE_REJECTED) != 0) params->data->stars = -1;
227
228 params->data->exif_exposure = img->exif_exposure;
229 params->data->exif_exposure_bias = img->exif_exposure_bias;
230 params->data->exif_aperture = img->exif_aperture;
231 params->data->exif_focal_length = img->exif_focal_length;
232 if(!isnan(img->exif_focus_distance) && fpclassify(img->exif_focus_distance) != FP_ZERO)
233 params->data->exif_focus_distance = img->exif_focus_distance;
234 params->data->longitude = img->geoloc.longitude;
235 params->data->latitude = img->geoloc.latitude;
236 params->data->elevation = img->geoloc.elevation;
237
238 params->data->flags = img->flags;
239
240 params->data->raw_height = img->p_height;
241 params->data->raw_width = img->p_width;
242 params->data->sensor_height = img->height;
243 params->data->sensor_width = img->width;
244 params->data->export_height = 0;
245 params->data->export_width = 0;
246 params->data->crop_height = img->crop_height;
247 params->data->crop_width = img->crop_width;
248 params->data->import_timestamp = img->import_timestamp;
249 params->data->change_timestamp = img->change_timestamp;
250 params->data->export_timestamp = img->export_timestamp;
251 params->data->print_timestamp = img->print_timestamp;
252 }
253
254 switch (release)
255 {
256 case NONE:
257 case FILENAME:
258 {
259 break;
260 }
261 case IMGID:
262 {
264 break;
265 }
266 }
267}
268
270{
271 if(params->data->datetime)
272 {
273 g_date_time_unref(params->data->datetime);
274 params->data->datetime = NULL;
275 }
276 dt_free(params->data->homedir);
277 dt_free(params->data->pictures_folder);
278 dt_free(params->data->camera_maker);
279 dt_free(params->data->camera_alias);
280 dt_free(params->data->exif_lens);
281}
282
283static inline gboolean _has_prefix(char **str, const char *prefix)
284{
285 gboolean res = g_str_has_prefix(*str, prefix);
286 if(res) *str += strlen(prefix);
287 return res;
288}
289
291{
292 if(isnan(params->data->longitude))
293 return g_strdup("");
294 if(dt_conf_get_bool("plugins/lighttable/metadata_view/pretty_location")
295 && g_strcmp0(params->jobcode, "infos") == 0)
296 {
297 return dt_util_longitude_str(params->data->longitude);
298 }
299 else
300 {
301 gchar NS = params->data->longitude < 0 ? 'W' : 'E';
302 return g_strdup_printf("%c%010.6f", NS, fabs(params->data->longitude));
303 }
304}
305
307{
308 if(isnan(params->data->latitude))
309 return g_strdup("");
310 if(dt_conf_get_bool("plugins/lighttable/metadata_view/pretty_location")
311 && g_strcmp0(params->jobcode, "infos") == 0)
312 {
313 return dt_util_latitude_str(params->data->latitude);
314 }
315 else
316 {
317 gchar NS = params->data->latitude < 0 ? 'S' : 'N';
318 return g_strdup_printf("%c%09.6f", NS, fabs(params->data->latitude));
319 }
320}
321
322static char *_variables_get_iso_timestamp(const GTimeSpan gts)
323{
324 if(gts <= 0) return NULL;
325
326 GDateTime *gdt = g_date_time_add(dt_datetime_origin(), gts);
327 if(IS_NULL_PTR(gdt)) return NULL;
328
329 char *result = g_date_time_format(gdt, "%Y-%m-%d %H:%M:%S");
330 g_date_time_unref(gdt);
331 return result;
332}
344static gboolean _expand_datetime_now(dt_variables_params_t *params, char **variable, char **out)
345{
346 char exif_datetime[DT_DATETIME_LENGTH];
347 char *result = NULL;
348
349 if(_has_prefix(variable, "YEAR.SHORT") || _has_prefix(variable, "SHORT_YEAR") || _has_prefix(variable, "DATE.SHORT_YEAR"))
350 result = g_date_time_format(params->data->time, "%y");
351 else if(_has_prefix(variable, "YEAR") || _has_prefix(variable, "DATE.LONG_YEAR"))
352 result = g_date_time_format(params->data->time, "%Y");
353 else if(_has_prefix(variable, "MONTH.SHORT") || _has_prefix(variable, "DATE.SHORT_MONTH"))
354 result = g_date_time_format(params->data->time, "%b");
355 else if(_has_prefix(variable, "MONTH.LONG") || _has_prefix(variable, "DATE.LONG_MONTH"))
356 result = g_date_time_format(params->data->time, "%B");
357 else if(_has_prefix(variable, "MONTH") || _has_prefix(variable, "DATE.MONTH"))
358 result = g_date_time_format(params->data->time, "%m");
359 else if(_has_prefix(variable, "DAY") || _has_prefix(variable, "DATE.DAY"))
360 result = g_date_time_format(params->data->time, "%d");
361 else if(_has_prefix(variable, "HOUR.AMPM") || _has_prefix(variable, "DATE.HOUR_AMPM"))
362 result = g_date_time_format(params->data->time, "%I %p");
363 else if(_has_prefix(variable, "HOUR") || _has_prefix(variable, "DATE.HOUR"))
364 result = g_date_time_format(params->data->time, "%H");
365 else if(_has_prefix(variable, "MINUTE") || _has_prefix(variable, "DATE.MINUTE"))
366 result = g_date_time_format(params->data->time, "%M");
367 else if(_has_prefix(variable, "SECOND") || _has_prefix(variable, "DATE.SECOND"))
368 result = g_date_time_format(params->data->time, "%S");
369 else if(_has_prefix(variable, "MSEC"))
370 {
371 result = g_date_time_format(params->data->time, "%f");
372 result[3] = '\0';
373 }
374 else if(_has_prefix(variable, "IMPORT.DATE") || _has_prefix(variable, "DATE.IMPORT"))
375 {
376 result = _variables_get_iso_timestamp(params->data->import_timestamp);
377 }
378 else if(_has_prefix(variable, "CHANGE.DATE") || _has_prefix(variable, "DATE.CHANGE"))
379 {
380 result = _variables_get_iso_timestamp(params->data->change_timestamp);
381 }
382 else if(_has_prefix(variable, "EXPORT.DATE") || _has_prefix(variable, "DATE.EXPORT"))
383 {
384 result = _variables_get_iso_timestamp(params->data->export_timestamp);
385 }
386 else if(_has_prefix(variable, "PRINT.DATE") || _has_prefix(variable, "DATE.PRINT"))
387 {
388 result = _variables_get_iso_timestamp(params->data->print_timestamp);
389 }
390 // for watermark backward compatibility.
391 // MUST stay after every DATE.* branch above: _has_prefix() is a plain prefix test, so
392 // this one also matches "DATE.IMPORT", "DATE.CHANGE", "DATE.EXPORT" and "DATE.PRINT".
393 else if(_has_prefix(variable, "DATE"))
394 {
395 dt_datetime_gdatetime_to_exif(exif_datetime, params->data->show_msec ? DT_DATETIME_LENGTH : DT_DATETIME_EXIF_LENGTH, params->data->time);
396 result = g_strdup(exif_datetime);
397 }
398
399 else return FALSE;
400
401 *out = result;
402 return TRUE;
403}
404
416static gboolean _expand_exif_datetime(dt_variables_params_t *params, char **variable, char **out)
417{
418 char exif_datetime[DT_DATETIME_LENGTH];
419 GDateTime *datetime = params->data->have_exif_dt ? params->data->datetime : params->data->time;
420 char *result = NULL;
421
422 if(_has_prefix(variable, "EXIF.YEAR.SHORT") || _has_prefix(variable, "EXIF.DATE.SHORT_YEAR"))
423 result = g_date_time_format(datetime, "%y");
424 else if(_has_prefix(variable, "EXIF.YEAR") || _has_prefix(variable, "EXIF_YEAR") || _has_prefix(variable, "EXIF.DATE.LONG_YEAR"))
425 result = g_date_time_format(datetime, "%Y");
426 else if(_has_prefix(variable, "EXIF.MONTH.SHORT") || _has_prefix(variable, "EXIF.DATE.SHORT_MONTH"))
427 result = g_date_time_format(datetime, "%b");
428 else if(_has_prefix(variable, "EXIF.MONTH.LONG") || _has_prefix(variable, "EXIF.DATE.LONG_MONTH"))
429 result = g_date_time_format(datetime, "%B");
430 else if(_has_prefix(variable, "EXIF.MONTH") || _has_prefix(variable, "EXIF_MONTH") || _has_prefix(variable, "EXIF.DATE.MONTH"))
431 result = g_date_time_format(datetime, "%m");
432 else if(_has_prefix(variable, "EXIF.DAY") || _has_prefix(variable, "EXIF_DAY") || _has_prefix(variable, "EXIF.DATE.DAY"))
433 result = g_date_time_format(datetime, "%d");
434 else if(_has_prefix(variable, "EXIF.HOUR.AMPM") || _has_prefix(variable, "EXIF.DATE.HOUR_AMPM"))
435 result = g_date_time_format(datetime, "%I %p");
436 else if(_has_prefix(variable, "EXIF.HOUR") || _has_prefix(variable, "EXIF_HOUR") || _has_prefix(variable, "EXIF.DATE.HOUR"))
437 result = g_date_time_format(datetime, "%H");
438 else if(_has_prefix(variable, "EXIF.MINUTE") || _has_prefix(variable, "EXIF_MINUTE") || _has_prefix(variable, "EXIF.DATE.MINUTE"))
439 result = g_date_time_format(datetime, "%M");
440 else if(_has_prefix(variable, "EXIF.SECOND") || _has_prefix(variable, "EXIF_SECOND") || _has_prefix(variable, "EXIF.DATE.SECOND"))
441 result = g_date_time_format(datetime, "%S");
442 else if(_has_prefix(variable, "EXIF.MSEC") || _has_prefix(variable, "EXIF_MSEC"))
443 {
444 result = g_date_time_format(datetime, "%f");
445 result[3] = '\0';
446 }
447 // for watermark backward compatibility
448 else if(_has_prefix(variable, "EXIF.DATE"))
449 {
450 dt_datetime_gdatetime_to_exif(exif_datetime, params->data->show_msec ? DT_DATETIME_LENGTH : DT_DATETIME_EXIF_LENGTH, datetime);
451 result = g_strdup(exif_datetime);
452 }
453 else return FALSE;
454
455 *out = result;
456 return TRUE;
457}
458
470static gboolean _expand_exif_and_gps(dt_variables_params_t *params, char **variable, char **out)
471{
472 char *result = NULL;
473
474 if(_has_prefix(variable, "EXIF.ISO") || _has_prefix(variable, "EXIF_ISO"))
475 result = g_strdup_printf("%d", params->data->exif_iso);
476 else if(_has_prefix(variable, "NL") && g_strcmp0(params->jobcode, "infos") == 0)
477 result = g_strdup_printf("\n");
478 else if(_has_prefix(variable, "EXIF.EXPOSURE.BIAS") || _has_prefix(variable, "EXIF_EXPOSURE_BIAS"))
479 {
480 if(!isnan(params->data->exif_exposure_bias))
481 result = g_strdup_printf("%+.2f", params->data->exif_exposure_bias);
482 }
483 else if(_has_prefix(variable, "EXIF.EXPOSURE") || _has_prefix(variable, "EXIF_EXPOSURE"))
484 {
485 result = dt_util_format_exposure(params->data->exif_exposure);
486 // for job other than info (export) we strip the slash char
487 if(g_strcmp0(params->jobcode, "infos") != 0)
488 {
489 gchar *res = dt_util_str_replace(result, "/", "_");
490 dt_free(result);
491 result = res;
492 }
493 }
494 else if(_has_prefix(variable, "EXIF.APERTURE") || _has_prefix(variable, "EXIF_APERTURE"))
495 result = g_strdup_printf("%.1f", params->data->exif_aperture);
496 else if(_has_prefix(variable, "EXIF.FOCAL.LENGTH") || _has_prefix(variable, "EXIF_FOCAL_LENGTH"))
497 result = g_strdup_printf("%d", (int)params->data->exif_focal_length);
498 else if(_has_prefix(variable, "EXIF.FOCUS.DISTANCE") || _has_prefix(variable, "EXIF_FOCUS_DISTANCE"))
499 result = g_strdup_printf("%.2f", params->data->exif_focus_distance);
500 else if(_has_prefix(variable, "LONGITUDE") || _has_prefix(variable, "GPS.LONGITUDE"))
502 else if(_has_prefix(variable, "LATITUDE") || _has_prefix(variable, "GPS.LATITUDE"))
504 else if(_has_prefix(variable, "ELEVATION") || _has_prefix(variable, "GPS.ELEVATION"))
505 result = g_strdup_printf("%.2f", params->data->elevation);
506 // for watermark backward compatibility
507 else if(_has_prefix(variable, "GPS.LOCATION"))
508 {
509 gchar *parts[4] = { 0 };
510 int i = 0;
511 if(!isnan(params->data->latitude)) parts[i++] = _variables_get_latitude(params);
512 if(!isnan(params->data->longitude)) parts[i++] = _variables_get_longitude(params);
513 if(!isnan(params->data->elevation)) parts[i++] = g_strdup_printf("%.2f", params->data->elevation);
514 result = g_strjoinv(", ", parts);
515 for(int j = 0; j < i; j++)
516 dt_free(parts[j]);
517 }
518 else if(_has_prefix(variable, "EXIF.MAKER") || _has_prefix(variable, "MAKER"))
519 result = g_strdup(params->data->camera_maker);
520 else if(_has_prefix(variable, "EXIF.MODEL") || _has_prefix(variable, "MODEL"))
521 result = g_strdup(params->data->camera_alias);
522 else if(_has_prefix(variable, "EXIF.LENS") || _has_prefix(variable, "LENS"))
523 result = g_strdup(params->data->exif_lens);
524 else return FALSE;
525
526 *out = result;
527 return TRUE;
528}
529
541static gboolean _expand_image_and_version(dt_variables_params_t *params, char **variable, char **out)
542{
543 char *result = NULL;
544
545 if(_has_prefix(variable, "ID") || _has_prefix(variable, "IMAGE.ID"))
546 result = g_strdup_printf("%d", params->imgid);
547 else if(_has_prefix(variable, "IMAGE.EXIF"))
548 {
549 gchar buffer[1024];
550 const dt_image_t *img = params->img ? (dt_image_t *)params->img
551 : dt_image_cache_get(params->imgid, 'r');
552 // an image that cannot be read expands to nothing, as its other fields do
553 if(!IS_NULL_PTR(img))
554 {
555 dt_image_print_exif(img, buffer, sizeof(buffer));
556 result = g_strdup(buffer);
557 }
559 }
560 else if(_has_prefix(variable, "VERSION.NAME") || _has_prefix(variable, "VERSION_NAME"))
561 {
562 GList *res = dt_metadata_get(params->imgid, "Xmp.darktable.version_name", NULL);
563 if(!IS_NULL_PTR(res))
564 {
565 result = g_strdup((char *)res->data);
566 }
567 g_list_free_full(res, dt_free_gpointer);
568 res = NULL;
569 }
570 else if(_has_prefix(variable, "VERSION.IF_MULTI") || _has_prefix(variable, "VERSION_IF_MULTI"))
571 {
572 // Same row set the duplicate list returns -- every image sharing this one's film roll and
573 // filename, itself included -- so this asks it rather than counting the rows twice over.
574 GList *versions = dt_image_repository_get_duplicate_ids(params->imgid);
575 const guint count = g_list_length(versions);
576 g_list_free(versions);
577
578 //only return data if more than one matching image
579 if(count > 1)
580 result = g_strdup_printf("%d", params->data->version);
581 }
582 else if(_has_prefix(variable, "VERSION"))
583 result = g_strdup_printf("%d", params->data->version);
584 else if(_has_prefix(variable, "JOBCODE"))
585 result = g_strdup(params->jobcode);
586 else if(_has_prefix(variable, "ROLL.NAME") || _has_prefix(variable, "ROLL_NAME") // deprecated as unclear
587 || _has_prefix(variable, "FOLDER.NAME"))
588 {
589 if(params->filename)
590 {
591 gchar *dirname = g_path_get_dirname(params->filename);
592 result = g_path_get_basename(dirname);
593 dt_free(dirname);
594 }
595 }
596 else return FALSE;
597
598 *out = result;
599 return TRUE;
600}
601
613static gboolean _expand_file_and_folder(dt_variables_params_t *params, char **variable, char **out)
614{
615 char *result = NULL;
616
617 if(_has_prefix(variable, "FILE.DIRECTORY") || _has_prefix(variable, "FILE_DIRECTORY"))
618 {
619 // undocumented : backward compatibility
620 if(params->filename)
621 result = g_path_get_dirname(params->filename);
622 }
623 else if(_has_prefix(variable, "FILE.FOLDER") || _has_prefix(variable, "FILE_FOLDER"))
624 {
625 if(params->filename)
626 result = g_path_get_dirname(params->filename);
627 }
628 else if(_has_prefix(variable, "FILE.YEAR"))
629 {
630 if(params->data->file_datetime)
631 result = g_date_time_format(params->data->file_datetime, "%Y");
632 }
633 else if(_has_prefix(variable, "FILE.MONTH"))
634 {
635 if(params->data->file_datetime)
636 result = g_date_time_format(params->data->file_datetime, "%m");
637 }
638 else if(_has_prefix(variable, "FILE.DAY"))
639 {
640 if(params->data->file_datetime)
641 result = g_date_time_format(params->data->file_datetime, "%d");
642 }
643 else if(_has_prefix(variable, "FILE.HOUR"))
644 {
645 if(params->data->file_datetime)
646 result = g_date_time_format(params->data->file_datetime, "%H");
647 }
648 else if(_has_prefix(variable, "FILE.MINUTE"))
649 {
650 if(params->data->file_datetime)
651 result = g_date_time_format(params->data->file_datetime, "%M");
652 }
653 else if(_has_prefix(variable, "FILE.SECOND"))
654 {
655 if(params->data->file_datetime)
656 result = g_date_time_format(params->data->file_datetime, "%S");
657 }
658 // for watermark backward compatibility
659 else if(_has_prefix(variable, "IMAGE.FILENAME"))
660 {
661 if(params->filename)
662 result = g_strdup(params->filename);
663 }
664 else if(_has_prefix(variable, "FILE.NAME") || _has_prefix(variable, "FILE_NAME") || _has_prefix(variable, "IMAGE.BASENAME"))
665 {
666 if(params->filename)
667 {
668 const char *basename = strrchr(params->filename, '/');
669 const char *windows_basename = strrchr(params->filename, '\\');
670 if(IS_NULL_PTR(basename) || (!IS_NULL_PTR(windows_basename) && windows_basename > basename))
671 basename = windows_basename;
672 basename = !IS_NULL_PTR(basename) ? basename + 1 : params->filename;
673
674 const char *extension = dt_util_path_get_extension(params->filename);
675 result = !IS_NULL_PTR(extension) ? g_strndup(basename, extension - basename - 1) : g_strdup(basename);
676 }
677 }
678 else if(_has_prefix(variable, "FILE.EXTENSION") || _has_prefix(variable, "FILE_EXTENSION"))
679 result = g_strdup(params->data->file_ext);
680 else if(_has_prefix(variable, "SEQUENCE"))
681 {
682 uint8_t nb_digit = 4;
683 if(g_ascii_isdigit(*variable[0]))
684 {
685 nb_digit = (uint8_t)*variable[0] & 0b1111;
686 (*variable) ++;
687 }
688 result = g_strdup_printf("%.*d", nb_digit, params->sequence);
689 }
690 else if(_has_prefix(variable, "USERNAME"))
691 result = g_strdup(g_get_user_name());
692 else if(_has_prefix(variable, "FOLDER.HOME") || _has_prefix(variable, "HOME_FOLDER") || _has_prefix(variable, "HOME"))
693 result = g_strdup(params->data->homedir);
694 else if(_has_prefix(variable, "FOLDER.PICTURES") || _has_prefix(variable, "PICTURES_FOLDER"))
695 result = g_strdup(params->data->pictures_folder);
696 else if(_has_prefix(variable, "FOLDER.DESKTOP") || _has_prefix(variable, "DESKTOP_FOLDER"))
697 result = g_strdup(g_get_user_special_dir(G_USER_DIRECTORY_DESKTOP)); // undocumented : backward compatibility
698 else if(_has_prefix(variable, "DESKTOP"))
699 result = g_strdup(g_get_user_special_dir(G_USER_DIRECTORY_DESKTOP));
700 else return FALSE;
701
702 *out = result;
703 return TRUE;
704}
705
718static gboolean _expand_rating_and_metadata(dt_variables_params_t *params, char **variable, char **out, gboolean *escape)
719{
720 char *result = NULL;
721
722 if(_has_prefix(variable, "STARS"))
723 result = g_strdup_printf("%d", params->data->stars);
724 else if(_has_prefix(variable, "RATING.ICONS") || _has_prefix(variable, "RATING_ICONS") || _has_prefix(variable, "Xmp.xmp.Rating"))
725 {
726 switch(params->data->stars)
727 {
728 case -1:
729 result = g_strdup("X");
730 break;
731 case 1:
732 result = g_strdup("\342\230\205");
733 break;
734 case 2:
735 result = g_strdup("\342\230\205\342\230\205");
736 break;
737 case 3:
738 result = g_strdup("\342\230\205\342\230\205\342\230\205");
739 break;
740 case 4:
741 result = g_strdup("\342\230\205\342\230\205\342\230\205\342\230\205");
742 break;
743 case 5:
744 result = g_strdup("\342\230\205\342\230\205\342\230\205\342\230\205\342\230\205");
745 break;
746 default:
747 result = g_strdup("");
748 break;
749 }
750 }
751 else if((_has_prefix(variable, "LABELS.ICONS") || _has_prefix(variable, "LABELS_ICONS") ||
752 _has_prefix(variable, "LABELS.COLORICONS") || _has_prefix(variable, "LABELS_COLORICONS"))
753 && g_strcmp0(params->jobcode, "infos") == 0)
754 {
755 *escape = FALSE;
756 GList *res = dt_metadata_get(params->imgid, "Xmp.darktable.colorlabels", NULL);
757 for(GList *res_iter = res; res_iter; res_iter = g_list_next(res_iter))
758 {
759 const int dot_index = GPOINTER_TO_INT(res_iter->data);
760 const GdkRGBA c = dt_bauhaus_get_global()->colorlabels[dot_index];
761 result = dt_util_dstrcat(result,
762 "<span foreground='#%02x%02x%02x'>\342\254\244 </span>",
763 (guint)(c.red*255), (guint)(c.green*255), (guint)(c.blue*255));
764 }
765 g_list_free(res);
766 res = NULL;
767 }
768 else if(_has_prefix(variable, "LABELS"))
769 {
770 // TODO: currently we concatenate all the color labels with a ',' as a separator. Maybe it's better to
771 // only use the first/last label?
772 GList *res = dt_metadata_get(params->imgid, "Xmp.darktable.colorlabels", NULL);
773 if(!IS_NULL_PTR(res))
774 {
775 GList *labels = NULL;
776 for(GList *res_iter = res; res_iter; res_iter = g_list_next(res_iter))
777 {
778 labels = g_list_prepend(labels, (char *)(_(dt_colorlabels_to_string(GPOINTER_TO_INT(res_iter->data)))));
779 }
780 labels = g_list_reverse(labels); // list was built in reverse order, so un-reverse it
781 result = dt_util_glist_to_str(",", labels);
782 g_list_free(labels);
783 labels = NULL;
784 }
785 g_list_free(res);
786 res = NULL;
787 }
788 else if(_has_prefix(variable, "TITLE") || _has_prefix(variable, "Xmp.dc.title"))
789 {
790 GList *res = dt_metadata_get(params->imgid, "Xmp.dc.title", NULL);
791 if(!IS_NULL_PTR(res))
792 {
793 result = g_strdup((char *)res->data);
794 }
795 g_list_free_full(res, dt_free_gpointer);
796 res = NULL;
797 }
798 else if(_has_prefix(variable, "DESCRIPTION") || _has_prefix(variable, "Xmp.dc.description"))
799 {
800 GList *res = dt_metadata_get(params->imgid, "Xmp.dc.description", NULL);
801 if(!IS_NULL_PTR(res))
802 {
803 result = g_strdup((char *)res->data);
804 }
805 g_list_free_full(res, dt_free_gpointer);
806 res = NULL;
807 }
808 else if(_has_prefix(variable, "CREATOR") || _has_prefix(variable, "Xmp.dc.creator"))
809 {
810 GList *res = dt_metadata_get(params->imgid, "Xmp.dc.creator", NULL);
811 if(!IS_NULL_PTR(res))
812 {
813 result = g_strdup((char *)res->data);
814 }
815 g_list_free_full(res, dt_free_gpointer);
816 res = NULL;
817 }
818 else if(_has_prefix(variable, "PUBLISHER") || _has_prefix(variable, "Xmp.dc.publisher"))
819 {
820 GList *res = dt_metadata_get(params->imgid, "Xmp.dc.publisher", NULL);
821 if(!IS_NULL_PTR(res))
822 {
823 result = g_strdup((char *)res->data);
824 }
825 g_list_free_full(res, dt_free_gpointer);
826 res = NULL;
827 }
828 else if(_has_prefix(variable, "RIGHTS") || _has_prefix(variable, "Xmp.dc.rights"))
829 {
830 GList *res = dt_metadata_get(params->imgid, "Xmp.dc.rights", NULL);
831 if(!IS_NULL_PTR(res))
832 {
833 result = g_strdup((char *)res->data);
834 }
835 g_list_free_full(res, dt_free_gpointer);
836 res = NULL;
837 }
838 else if(_has_prefix(variable, "OPENCL.ACTIVATED") || _has_prefix(variable, "OPENCL_ACTIVATED"))
839 {
841 result = g_strdup(_("yes"));
842 else
843 result = g_strdup(_("no"));
844 }
845 else return FALSE;
846
847 *out = result;
848 return TRUE;
849}
850
862static gboolean _expand_geometry_and_misc(dt_variables_params_t *params, char **variable, char **out)
863{
864 char *result = NULL;
865
866 if(_has_prefix(variable, "WIDTH.MAX") || _has_prefix(variable, "MAX_WIDTH"))
867 result = g_strdup_printf("%d", params->data->max_width);
868 else if(_has_prefix(variable, "WIDTH.SENSOR") || _has_prefix(variable, "SENSOR_WIDTH"))
869 result = g_strdup_printf("%d", params->data->sensor_width);
870 else if(_has_prefix(variable, "WIDTH.RAW") || _has_prefix(variable, "RAW_WIDTH"))
871 result = g_strdup_printf("%d", params->data->raw_width);
872 else if(_has_prefix(variable, "WIDTH.CROP"))
873 result = g_strdup_printf("%d", params->data->crop_width);
874 else if(_has_prefix(variable, "WIDTH.EXPORT") || _has_prefix(variable, "EXPORT_WIDTH"))
875 result = g_strdup_printf("%d", params->data->export_width);
876 else if(_has_prefix(variable, "HEIGHT.MAX") || _has_prefix(variable, "MAX_HEIGHT"))
877 result = g_strdup_printf("%d", params->data->max_height);
878 else if(_has_prefix(variable, "HEIGHT.SENSOR") || _has_prefix(variable, "SENSOR_HEIGHT"))
879 result = g_strdup_printf("%d", params->data->sensor_height);
880 else if(_has_prefix(variable, "HEIGHT.RAW") || _has_prefix(variable, "RAW_HEIGHT"))
881 result = g_strdup_printf("%d", params->data->raw_height);
882 else if(_has_prefix(variable, "HEIGHT.CROP"))
883 result = g_strdup_printf("%d", params->data->crop_height);
884 else if(_has_prefix(variable, "HEIGHT.EXPORT") || _has_prefix(variable, "EXPORT_HEIGHT"))
885 result = g_strdup_printf("%d", params->data->export_height);
886 else if (_has_prefix(variable, "CATEGORY"))
887 {
888 // CATEGORY should be followed by n [0,9] and "(category)". category can contain 0 or more '|'
889 if (g_ascii_isdigit(*variable[0]))
890 {
891 const uint8_t level = (uint8_t)*variable[0] & 0b1111;
892 (*variable) ++;
893 if (*variable[0] == '(')
894 {
895 char *category = g_strdup(*variable + 1);
896 char *end = g_strstr_len(category, -1, ")");
897 if (end)
898 {
899 end[0] = '|';
900 end[1] = '\0';
901 (*variable) += strlen(category) + 1;
902 char *tag = dt_tag_get_subtags(params->imgid, category, (int)level);
903 if (tag)
904 {
905 result = g_strdup(tag);
906 dt_free(tag);
907 }
908 }
909 dt_free(category);
910 }
911 }
912 }
913 else if(_has_prefix(variable, "TAGS") || _has_prefix(variable, "IMAGE.TAGS"))
914 {
915 GList *tags_list = dt_tag_get_list_export(params->imgid, params->data->tags_flags);
916 char *tags = dt_util_glist_to_str(", ", tags_list);
917 g_list_free_full(tags_list, dt_free_gpointer);
918 tags_list = NULL;
919 result = g_strdup(tags);
920 dt_free(tags);
921 }
922 else if(_has_prefix(variable, "SIDECAR_TXT") && g_strcmp0(params->jobcode, "infos") == 0
923 && (params->data->flags & DT_IMAGE_HAS_TXT))
924 {
925 char *path = dt_image_get_text_path(params->imgid);
926 if(path)
927 {
928 gchar *txt = NULL;
929 if(g_file_get_contents(path, &txt, NULL, NULL))
930 {
931 result = g_strdup_printf("\n%s", txt);
932 }
933 dt_free(txt);
934 dt_free(path);
935 }
936 }
937 else if(_has_prefix(variable, "DARKTABLE.VERSION") || _has_prefix(variable, "DARKTABLE_VERSION")
938 || _has_prefix(variable, "ANSEL.VERSION"))
939 result = g_strdup(darktable_package_version);
940 else if(_has_prefix(variable, "DARKTABLE.NAME") || _has_prefix(variable, "DARKTABLE_NAME")
941 || _has_prefix(variable, "ANSEL.NAME"))
942 result = g_strdup(PACKAGE_NAME);
943 else return FALSE;
944
945 *out = result;
946 return TRUE;
947}
948
966static char *_get_base_value(dt_variables_params_t *params, char **variable)
967{
968 char *result = NULL;
969 gboolean escape = TRUE;
970
971 if(!_expand_datetime_now(params, variable, &result)
972 && !_expand_exif_datetime(params, variable, &result)
973 && !_expand_exif_and_gps(params, variable, &result)
974 && !_expand_image_and_version(params, variable, &result)
975 && !_expand_file_and_folder(params, variable, &result)
976 && !_expand_rating_and_metadata(params, variable, &result, &escape)
977 && !_expand_geometry_and_misc(params, variable, &result))
978 {
979 // go past what looks like an invalid variable. we only expect to see [a-zA-Z]* in a variable name.
980 while(g_ascii_isalpha(**variable)) (*variable)++;
981 }
982 if(!result) result = g_strdup("");
983
984 if(params->escape_markup && escape)
985 {
986 gchar *e_res = g_markup_escape_text(result, -1);
987 dt_free(result);
988 return e_res;
989 }
990 return result;
991}
992// bash style variable manipulation. all patterns are just simple string comparisons!
993// See here for bash examples and documentation:
994// http://www.tldp.org/LDP/abs/html/parameter-substitution.html
995// https://www.gnu.org/software/bash/manual/html_node/Shell-Parameter-Expansion.html
996// the descriptions in the comments are referring to the bash behaviour, dt doesn't do it 100% like that!
997static char *_variable_get_value(dt_variables_params_t *params, char **variable)
998{
999 // invariant: the variable starts with "$(" which we can skip
1000 (*variable) += 2;
1001
1002 // first get the value of the variable
1003 char *base_value = _get_base_value(params, variable); // this is never going to be NULL!
1004 const size_t base_value_length = strlen(base_value);
1005
1006 // ... and now see if we have to change it
1007 const char operation = **variable;
1008 if(operation != '\0' && operation != ')') (*variable)++;
1009 switch(operation)
1010 {
1011 case '-':
1012 /*
1013 $(parameter-default)
1014 If parameter not set, use default.
1015 */
1016 {
1017 char *replacement = _expand_source(params, variable, ')');
1018 if(*base_value == '\0')
1019 {
1020 dt_free(base_value);
1021 base_value = replacement;
1022 }
1023 else
1024 dt_free(replacement);
1025 }
1026 break;
1027 case '+':
1028 /*
1029 $(parameter+alt_value)
1030 If parameter set, use alt_value, else use null string.
1031 */
1032 {
1033 char *replacement = _expand_source(params, variable, ')');
1034 if(*base_value != '\0')
1035 {
1036 dt_free(base_value);
1037 base_value = replacement;
1038 }
1039 else
1040 dt_free(replacement);
1041 }
1042 break;
1043 case ':':
1044 /*
1045 $(var:offset)
1046 Variable var expanded, starting from offset.
1047
1048 $(var:offset:length)
1049 Expansion to a max of length characters of variable var, from offset.
1050
1051 If offset evaluates to a number less than zero, the value is used as an offset in characters from the
1052 end of the value of parameter. If length evaluates to a number less than zero, it is interpreted as an
1053 offset in characters from the end of the value of parameter rather than a number of characters, and the
1054 expansion is the characters between offset and that result.
1055 */
1056 {
1057 const glong base_value_utf8_length = g_utf8_strlen(base_value, -1);
1058 const glong offset = strtol(*variable, variable, 10);
1059
1060 // find where to start
1061 char *start; // from where to copy ...
1062 if(offset >= 0)
1063 start = g_utf8_offset_to_pointer(base_value, MIN(offset, base_value_utf8_length));
1064 else
1065 start = g_utf8_offset_to_pointer(base_value + base_value_length, MAX(offset, -base_value_utf8_length));
1066
1067 // now find the end if there is a length provided
1068 char *end = base_value + base_value_length; // ... and until where
1069 if(start && **variable == ':')
1070 {
1071 (*variable)++;
1072 const size_t start_utf8_length = g_utf8_strlen(start, -1);
1073 const int length = strtol(*variable, variable, 10);
1074 if(length >= 0)
1075 end = g_utf8_offset_to_pointer(start, MIN(length, start_utf8_length));
1076 else
1077 end = g_utf8_offset_to_pointer(base_value + base_value_length, MAX(length, -start_utf8_length));
1078 }
1079
1080 char *_base_value = g_strndup(start, end - start);
1081 dt_free(base_value);
1082 base_value = _base_value;
1083 }
1084 break;
1085 case '#':
1086 /*
1087 $(var#Pattern)
1088 Remove from $var the shortest part of $Pattern that matches the front end of $var.
1089 */
1090 {
1091 char *pattern = _expand_source(params, variable, ')');
1092 const size_t pattern_length = strlen(pattern);
1093 if(!strncmp(base_value, pattern, pattern_length))
1094 {
1095 char *_base_value = g_strdup(base_value + pattern_length);
1096 dt_free(base_value);
1097 base_value = _base_value;
1098 }
1099 dt_free(pattern);
1100 }
1101 break;
1102 case '%':
1103 /*
1104 $(var%Pattern)
1105 Remove from $var the shortest part of $Pattern that matches the back end of $var.
1106 */
1107 {
1108 char *pattern = _expand_source(params, variable, ')');
1109 const size_t pattern_length = strlen(pattern);
1110 if(!strncmp(base_value + base_value_length - pattern_length, pattern, pattern_length))
1111 base_value[base_value_length - pattern_length] = '\0';
1112 dt_free(pattern);
1113 }
1114 break;
1115 case '/':
1116 /*
1117 replacement. the following cases are possible:
1118
1119 $(var/Pattern/Replacement)
1120 First match of Pattern, within var replaced with Replacement.
1121 If Replacement is omitted, then the first match of Pattern is replaced by nothing, that is, deleted.
1122
1123 $(var//Pattern/Replacement)
1124 Global replacement. All matches of Pattern, within var replaced with Replacement.
1125 As above, if Replacement is omitted, then all occurrences of Pattern are replaced by nothing, that is, deleted.
1126
1127 $(var/#Pattern/Replacement)
1128 If prefix of var matches Pattern, then substitute Replacement for Pattern.
1129
1130 $(var/%Pattern/Replacement)
1131 If suffix of var matches Pattern, then substitute Replacement for Pattern.
1132 */
1133 {
1134 const char mode = **variable;
1135
1136 if(mode == '/' || mode == '#' || mode == '%') (*variable)++;
1137 char *pattern = _expand_source(params, variable, '/');
1138 const size_t pattern_length = strlen(pattern);
1139 (*variable)++;
1140 char *replacement = _expand_source(params, variable, ')');
1141 const size_t replacement_length = strlen(replacement);
1142
1143 switch(mode)
1144 {
1145 case '/':
1146 {
1147 // TODO: write a dt_util_str_replace that can deal with pattern_length ^^
1148 char *p = g_strndup(pattern, pattern_length);
1149 char *_base_value = dt_util_str_replace(base_value, p, replacement);
1150 dt_free(p);
1151 dt_free(base_value);
1152 base_value = _base_value;
1153 break;
1154 }
1155 case '#':
1156 {
1157 if(!strncmp(base_value, pattern, pattern_length))
1158 {
1159 char *_base_value = g_malloc(base_value_length - pattern_length + replacement_length + 1);
1160 char *end = g_stpcpy(_base_value, replacement);
1161 g_stpcpy(end, base_value + pattern_length);
1162 dt_free(base_value);
1163 base_value = _base_value;
1164 }
1165 break;
1166 }
1167 case '%':
1168 {
1169 if(!strncmp(base_value + base_value_length - pattern_length, pattern, pattern_length))
1170 {
1171 char *_base_value = g_malloc(base_value_length - pattern_length + replacement_length + 1);
1172 base_value[base_value_length - pattern_length] = '\0';
1173 char *end = g_stpcpy(_base_value, base_value);
1174 g_stpcpy(end, replacement);
1175 dt_free(base_value);
1176 base_value = _base_value;
1177 }
1178 break;
1179 }
1180 default:
1181 {
1182 // TODO: is there a strstr_len that limits the length of pattern?
1183 char *p = g_strndup(pattern, pattern_length);
1184 gchar *found = g_strstr_len(base_value, -1, p);
1185 dt_free(p);
1186 if(found)
1187 {
1188 *found = '\0';
1189 char *_base_value = g_malloc(base_value_length - pattern_length + replacement_length + 1);
1190 char *end = g_stpcpy(_base_value, base_value);
1191 end = g_stpcpy(end, replacement);
1192 g_stpcpy(end, found + pattern_length);
1193 dt_free(base_value);
1194 base_value = _base_value;
1195 }
1196 break;
1197 }
1198 }
1199 dt_free(pattern);
1200 dt_free(replacement);
1201 }
1202 break;
1203 case '^':
1204 case ',':
1205 /*
1206 changing the case:
1207
1208 $(parameter^pattern)
1209 $(parameter^^pattern)
1210 $(parameter,pattern)
1211 $(parameter,,pattern)
1212 This expansion modifies the case of alphabetic characters in parameter.
1213 The ‘^’ operator converts lowercase letters to uppercase;
1214 the ‘,’ operator converts uppercase letters to lowercase.
1215 The ‘^^’ and ‘,,’ expansions convert each character in the expanded value;
1216 the ‘^’ and ‘,’ expansions convert only the first character in the expanded value.
1217 */
1218 {
1219 const char mode = **variable;
1220 char *_base_value = NULL;
1221 if(operation == '^' && mode == '^')
1222 {
1223 _base_value = g_utf8_strup (base_value, -1);
1224 (*variable)++;
1225 }
1226 else if(operation == ',' && mode == ',')
1227 {
1228 _base_value = g_utf8_strdown(base_value, -1);
1229 (*variable)++;
1230 }
1231 else
1232 {
1233 gunichar changed = g_utf8_get_char(base_value);
1234 changed = operation == '^' ? g_unichar_toupper(changed) : g_unichar_tolower(changed);
1235 int utf8_length = g_unichar_to_utf8(changed, NULL);
1236 char *next = g_utf8_next_char(base_value);
1237 _base_value = g_malloc0(base_value_length - (next - base_value) + utf8_length + 1);
1238 g_unichar_to_utf8(changed, _base_value);
1239 g_stpcpy(_base_value + utf8_length, next);
1240 }
1241 dt_free(base_value);
1242 base_value = _base_value;
1243 }
1244 break;
1245 }
1246
1247 if(**variable == ')')
1248 (*variable)++;
1249 else
1250 {
1251 // error case
1252 dt_free(base_value);
1253 }
1254
1255 return base_value;
1256}
1257
1258static void _grow_buffer(char **result, char **result_iter, size_t *result_length, size_t extra_space)
1259{
1260 const size_t used_length = *result_iter - *result;
1261 if(used_length + extra_space > *result_length)
1262 {
1263 *result_length = used_length + extra_space;
1264 *result = g_realloc(*result, *result_length + 1);
1265 *result_iter = *result + used_length;
1266 }
1267}
1268
1269static char *_expand_source(dt_variables_params_t *params, char **source, char extra_stop)
1270{
1271 char *result = g_strdup("");
1272 if(IS_NULL_PTR(*source)) return result;
1273 char *result_iter = result;
1274 size_t result_length = 0;
1275 char *source_iter = *source;
1276 const size_t source_length = strlen(*source);
1277
1278 while(*source_iter && *source_iter != extra_stop)
1279 {
1280 // find start of variable, copying over everything till then
1281 while(*source_iter && *source_iter != extra_stop)
1282 {
1283 char c = *source_iter;
1284 if(c == '$' && source_iter[1] == '(')
1285 break;
1286
1287 if(result_iter - result >= result_length)
1288 _grow_buffer(&result, &result_iter, &result_length, source_length - (source_iter - *source));
1289 *result_iter = c;
1290 result_iter++;
1291 source_iter++;
1292
1293 }
1294
1295 // it seems we have a variable here
1296 if(*source_iter == '$')
1297 {
1298 char *old_source_iter = source_iter;
1299 char *replacement = _variable_get_value(params, &source_iter);
1300 if(replacement)
1301 {
1302 const size_t replacement_length = strlen(replacement);
1303 _grow_buffer(&result, &result_iter, &result_length, replacement_length);
1304 memcpy(result_iter, replacement, replacement_length);
1305 result_iter += replacement_length;
1306 dt_free(replacement);
1307 }
1308 else
1309 {
1310 // the error case of missing closing ')' -- try to recover
1311 source_iter = old_source_iter;
1312 _grow_buffer(&result, &result_iter, &result_length, source_length - (source_iter - *source));
1313 *result_iter++ = *source_iter++;
1314 }
1315 }
1316 }
1317
1318 *result_iter = '\0';
1319 *source = source_iter;
1320
1321 return result;
1322}
1323
1324char *dt_variables_expand(dt_variables_params_t *params, gchar *source, gboolean iterate)
1325{
1326 _init_expansion(params, iterate);
1327
1328 char *result = _expand_source(params, &source, '\0');
1329
1331 return result;
1332}
1333
1335{
1336 *params = g_malloc0(sizeof(dt_variables_params_t));
1337 (*params)->data = g_malloc0(sizeof(dt_variables_data_t));
1338 (*params)->data->time = g_date_time_new_now_local();
1339 (*params)->data->file_datetime = NULL;
1340 (*params)->data->exif_time[0] = 0;
1341 (*params)->sequence = -1;
1342 (*params)->img = NULL;
1343}
1344
1346{
1347 if(params->data->time)
1348 g_date_time_unref(params->data->time);
1349
1350 if(params->data->file_datetime)
1351 g_date_time_unref(params->data->file_datetime);
1352
1353 dt_free(params->data);
1354 dt_free(params);
1355}
1356
1358{
1359 if(params->data->time) g_date_time_unref(params->data->time);
1360 params->data->time = g_date_time_ref(datetime);
1361}
1362
1364{
1365 params->data->max_width = max_width;
1366 params->data->max_height = max_height;
1367}
1368
1370{
1371 params->data->sequence = 0;
1372}
1373
1375{
1376 params->data->tags_flags = flags;
1377}
1378
1379
1380
1381// clang-format off
1382// modelines: These editor modelines have been set for all relevant files by tools/update_modelines.py
1383// vim: shiftwidth=2 expandtab tabstop=2 cindent
1384// kate: tab-indents: off; indent-width 2; replace-tabs on; indent-mode cstyle; remove-trailing-spaces modified;
1385// clang-format on
#define TRUE
Definition ashift_lsd.c:162
#define FALSE
Definition ashift_lsd.c:158
const char * extension(dt_imageio_module_data_t *data)
Definition avif.c:651
const char * dt_colorlabels_to_string(int label)
const dt_colormatrix_t dt_aligned_pixel_t out
int dt_conf_get_bool(const char *name)
void dt_image_print_exif(const dt_image_t *img, char *line, size_t line_len)
char * dt_image_get_text_path(const int32_t imgid)
#define PACKAGE_NAME
const char darktable_package_version[]
struct dt_bauhaus_t * dt_bauhaus_get_global(void)
Definition darktable.c:649
gboolean dt_datetime_gdatetime_to_exif(char *exif, const size_t exif_size, GDateTime *gdt)
Definition datetime.c:281
GTimeZone * dt_datetime_utc_tz(void)
Definition datetime.c:40
GDateTime * dt_datetime_origin(void)
Definition datetime.c:45
GDateTime * dt_datetime_img_to_gdatetime(const dt_image_t *img, const GTimeZone *tz)
Definition datetime.c:302
#define DT_DATETIME_LENGTH
Definition datetime.h:38
#define DT_DATETIME_EXIF_LENGTH
Definition datetime.h:39
const int res
Definition dtpthread.h:351
gchar * dt_loc_get_home_dir(const gchar *user)
#define UNKNOWN_IMAGE
Definition image.h:78
@ DT_IMAGE_REJECTED
Definition image.h:122
@ DT_IMAGE_HAS_TXT
Definition image.h:145
dt_image_t * dt_image_cache_get(const int32_t imgid, char mode)
void dt_image_cache_read_release(const dt_image_t *img)
GList * dt_image_repository_get_duplicate_ids(const int32_t imgid)
Every image id sharing imgid's film roll and filename, itself included, in row order – an image and i...
Reading and writing one dt_image_t to and from the library database. The SQL half of what used to be ...
static const dt_iop_drawlayer_params_t * params
Definition layers.h:80
#define IS_NULL_PTR(p)
C is way too permissive with !=, == and if(var) checks, which can mean too many things depending on w...
Definition macros.h:96
static void dt_free_gpointer(gpointer ptr)
g_free() one pointer, with the signature GDestroyNotify wants.
Definition mem_alloc.h:184
#define dt_free(ptr)
g_free() ptr and set it to NULL, skipping both if it is already NULL.
Definition mem_alloc.h:171
GList * dt_metadata_get(const int id, const char *key, uint32_t *count)
dt_mipmap_buffer_dsc_flags flags
Definition mipmap_cache.c:4
int dt_opencl_is_enabled(void)
Definition opencl.c:3335
#define DT_PATH_MAX
Buffer size for a filesystem path anywhere in Ansel.
Definition paths.h:57
static char * _expand_source(dt_variables_params_t *params, char **source, char extra_stop)
char * dt_variables_expand(dt_variables_params_t *params, gchar *source, gboolean iterate)
static gboolean _expand_rating_and_metadata(dt_variables_params_t *params, char **variable, char **out, gboolean *escape)
Expand star rating, colour labels and the Dublin Core fields.
static void _grow_buffer(char **result, char **result_iter, size_t *result_length, size_t extra_space)
static char * _variables_get_iso_timestamp(const GTimeSpan gts)
void dt_variables_params_destroy(dt_variables_params_t *params)
static void _init_expansion(dt_variables_params_t *params, gboolean iterate)
static gboolean _has_prefix(char **str, const char *prefix)
static char * _variables_get_latitude(dt_variables_params_t *params)
static gboolean _expand_datetime_now(dt_variables_params_t *params, char **variable, char **out)
Expand the current time, and the import/change/export/print timestamps.
gboolean dt_get_user_pictures_dir(const gchar *homedir, gchar *picdir, size_t picdir_size)
Gets the path to the current OS pictures directory.
static gboolean _expand_file_and_folder(dt_variables_params_t *params, char **variable, char **out)
Expand the source file's path, name and mtime, plus the user's folders.
static gboolean _expand_geometry_and_misc(dt_variables_params_t *params, char **variable, char **out)
Expand image dimensions, category, tags and build identity.
void dt_variables_set_max_width_height(dt_variables_params_t *params, int max_width, int max_height)
void dt_variables_params_init(dt_variables_params_t **params)
static char * _variable_get_value(dt_variables_params_t *params, char **variable)
void dt_variables_set_tags_flags(dt_variables_params_t *params, uint32_t flags)
static void _cleanup_expansion(dt_variables_params_t *params)
static char * _get_base_value(dt_variables_params_t *params, char **variable)
Expand one into its text.
void dt_variables_set_datetime(dt_variables_params_t *params, GDateTime *datetime)
static char * _variables_get_longitude(dt_variables_params_t *params)
static gboolean _expand_exif_datetime(dt_variables_params_t *params, char **variable, char **out)
Expand the capture time read from EXIF.
static gboolean _expand_exif_and_gps(dt_variables_params_t *params, char **variable, char **out)
Expand EXIF exposure settings, GPS position and camera identity.
void dt_variables_reset_sequence(dt_variables_params_t *params)
static gboolean _expand_image_and_version(dt_variables_params_t *params, char **variable, char **out)
Expand image id, duplicate version, job code and film roll.
GdkRGBA colorlabels[DT_COLORLABELS_LAST]
Definition bauhaus.h:284
double latitude
Definition image.h:341
double elevation
Definition image.h:341
double longitude
Definition image.h:341
float exif_exposure
Definition image.h:376
int32_t height
Definition image.h:406
GTimeSpan export_timestamp
Definition image.h:424
float exif_focus_distance
Definition image.h:381
GTimeSpan import_timestamp
Definition image.h:424
float exif_exposure_bias
Definition image.h:377
float exif_iso
Definition image.h:379
char camera_maker[64]
Definition image.h:388
float exif_aperture
Definition image.h:378
int32_t version
Definition image.h:410
int32_t crop_height
Definition image.h:407
dt_image_geoloc_t geoloc
Definition image.h:443
int32_t flags
Definition image.h:410
int32_t width
Definition image.h:406
float exif_focal_length
Definition image.h:380
GTimeSpan change_timestamp
Definition image.h:424
char camera_alias[64]
Definition image.h:390
char exif_lens[128]
Definition image.h:385
GTimeSpan print_timestamp
Definition image.h:424
int32_t p_height
Definition image.h:406
int32_t p_width
Definition image.h:406
int32_t crop_width
Definition image.h:407
char exif_time[DT_DATETIME_LENGTH]
GList * dt_tag_get_list_export(int32_t imgid, int32_t flags)
Definition tags.c:720
char * dt_tag_get_subtags(const int32_t imgid, const char *category, const int level)
Definition tags.c:1232
#define MIN(a, b)
Definition thinplate.c:32
#define MAX(a, b)
Definition thinplate.c:29
gchar * dt_util_str_replace(const gchar *string, const gchar *pattern, const gchar *substitute)
Definition utility.c:163
gchar * dt_util_longitude_str(float longitude)
Definition utility.c:595
GDateTime * dt_util_get_file_datetime(const char *const path)
Definition utility.c:815
char * dt_util_format_exposure(const float exposuretime)
Definition utility.c:891
const char * dt_util_path_get_extension(const char *path)
Locate a normal filename extension in a path basename.
Definition utility.c:1033
gchar * dt_util_latitude_str(float latitude)
Definition utility.c:577
gchar * dt_util_glist_to_str(const gchar *separator, GList *items)
Definition utility.c:193
gchar * dt_util_dstrcat(gchar *str, const gchar *format,...)
Definition utility.c:122