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-2025 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 = (g_strrstr(params->filename, ".") + 1);
172 if(params->data->file_ext == (gchar *)1) 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 {
217 params->data->datetime = dt_datetime_img_to_gdatetime(img, dt_datetime_utc_tz());
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"))
501 result = _variables_get_longitude(params);
502 else if(_has_prefix(variable, "LATITUDE") || _has_prefix(variable, "GPS.LATITUDE"))
503 result = _variables_get_latitude(params);
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 dt_image_print_exif(img, buffer, sizeof(buffer));
553 if(IS_NULL_PTR(params->img)) dt_image_cache_read_release(img);
554 result = g_strdup(buffer);
555 }
556 else if(_has_prefix(variable, "VERSION.NAME") || _has_prefix(variable, "VERSION_NAME"))
557 {
558 GList *res = dt_metadata_get(params->imgid, "Xmp.darktable.version_name", NULL);
559 if(!IS_NULL_PTR(res))
560 {
561 result = g_strdup((char *)res->data);
562 }
563 g_list_free_full(res, dt_free_gpointer);
564 res = NULL;
565 }
566 else if(_has_prefix(variable, "VERSION.IF_MULTI") || _has_prefix(variable, "VERSION_IF_MULTI"))
567 {
568 // Same row set the duplicate list returns -- every image sharing this one's film roll and
569 // filename, itself included -- so this asks it rather than counting the rows twice over.
570 GList *versions = dt_image_repository_get_duplicate_ids(params->imgid);
571 const guint count = g_list_length(versions);
572 g_list_free(versions);
573
574 //only return data if more than one matching image
575 if(count > 1)
576 result = g_strdup_printf("%d", params->data->version);
577 }
578 else if(_has_prefix(variable, "VERSION"))
579 result = g_strdup_printf("%d", params->data->version);
580 else if(_has_prefix(variable, "JOBCODE"))
581 result = g_strdup(params->jobcode);
582 else if(_has_prefix(variable, "ROLL.NAME") || _has_prefix(variable, "ROLL_NAME") // deprecated as unclear
583 || _has_prefix(variable, "FOLDER.NAME"))
584 {
585 if(params->filename)
586 {
587 gchar *dirname = g_path_get_dirname(params->filename);
588 result = g_path_get_basename(dirname);
589 dt_free(dirname);
590 }
591 }
592 else return FALSE;
593
594 *out = result;
595 return TRUE;
596}
597
609static gboolean _expand_file_and_folder(dt_variables_params_t *params, char **variable, char **out)
610{
611 char *result = NULL;
612
613 if(_has_prefix(variable, "FILE.DIRECTORY") || _has_prefix(variable, "FILE_DIRECTORY"))
614 {
615 // undocumented : backward compatibility
616 if(params->filename)
617 result = g_path_get_dirname(params->filename);
618 }
619 else if(_has_prefix(variable, "FILE.FOLDER") || _has_prefix(variable, "FILE_FOLDER"))
620 {
621 if(params->filename)
622 result = g_path_get_dirname(params->filename);
623 }
624 else if(_has_prefix(variable, "FILE.YEAR"))
625 {
626 if(params->data->file_datetime)
627 result = g_date_time_format(params->data->file_datetime, "%Y");
628 }
629 else if(_has_prefix(variable, "FILE.MONTH"))
630 {
631 if(params->data->file_datetime)
632 result = g_date_time_format(params->data->file_datetime, "%m");
633 }
634 else if(_has_prefix(variable, "FILE.DAY"))
635 {
636 if(params->data->file_datetime)
637 result = g_date_time_format(params->data->file_datetime, "%d");
638 }
639 else if(_has_prefix(variable, "FILE.HOUR"))
640 {
641 if(params->data->file_datetime)
642 result = g_date_time_format(params->data->file_datetime, "%H");
643 }
644 else if(_has_prefix(variable, "FILE.MINUTE"))
645 {
646 if(params->data->file_datetime)
647 result = g_date_time_format(params->data->file_datetime, "%M");
648 }
649 else if(_has_prefix(variable, "FILE.SECOND"))
650 {
651 if(params->data->file_datetime)
652 result = g_date_time_format(params->data->file_datetime, "%S");
653 }
654 // for watermark backward compatibility
655 else if(_has_prefix(variable, "IMAGE.FILENAME"))
656 {
657 if(params->filename)
658 result = g_strdup(params->filename);
659 }
660 else if(_has_prefix(variable, "FILE.NAME") || _has_prefix(variable, "FILE_NAME") || _has_prefix(variable, "IMAGE.BASENAME"))
661 {
662 if(params->filename)
663 {
664 result = g_path_get_basename(params->filename);
665 char *dot = g_strrstr(result, ".");
666 if(dot) *dot = '\0';
667 }
668 }
669 else if(_has_prefix(variable, "FILE.EXTENSION") || _has_prefix(variable, "FILE_EXTENSION"))
670 result = g_strdup(params->data->file_ext);
671 else if(_has_prefix(variable, "SEQUENCE"))
672 {
673 uint8_t nb_digit = 4;
674 if(g_ascii_isdigit(*variable[0]))
675 {
676 nb_digit = (uint8_t)*variable[0] & 0b1111;
677 (*variable) ++;
678 }
679 result = g_strdup_printf("%.*d", nb_digit, params->sequence);
680 }
681 else if(_has_prefix(variable, "USERNAME"))
682 result = g_strdup(g_get_user_name());
683 else if(_has_prefix(variable, "FOLDER.HOME") || _has_prefix(variable, "HOME_FOLDER") || _has_prefix(variable, "HOME"))
684 result = g_strdup(params->data->homedir);
685 else if(_has_prefix(variable, "FOLDER.PICTURES") || _has_prefix(variable, "PICTURES_FOLDER"))
686 result = g_strdup(params->data->pictures_folder);
687 else if(_has_prefix(variable, "FOLDER.DESKTOP") || _has_prefix(variable, "DESKTOP_FOLDER"))
688 result = g_strdup(g_get_user_special_dir(G_USER_DIRECTORY_DESKTOP)); // undocumented : backward compatibility
689 else if(_has_prefix(variable, "DESKTOP"))
690 result = g_strdup(g_get_user_special_dir(G_USER_DIRECTORY_DESKTOP));
691 else return FALSE;
692
693 *out = result;
694 return TRUE;
695}
696
709static gboolean _expand_rating_and_metadata(dt_variables_params_t *params, char **variable, char **out, gboolean *escape)
710{
711 char *result = NULL;
712
713 if(_has_prefix(variable, "STARS"))
714 result = g_strdup_printf("%d", params->data->stars);
715 else if(_has_prefix(variable, "RATING.ICONS") || _has_prefix(variable, "RATING_ICONS") || _has_prefix(variable, "Xmp.xmp.Rating"))
716 {
717 switch(params->data->stars)
718 {
719 case -1:
720 result = g_strdup("X");
721 break;
722 case 1:
723 result = g_strdup("\342\230\205");
724 break;
725 case 2:
726 result = g_strdup("\342\230\205\342\230\205");
727 break;
728 case 3:
729 result = g_strdup("\342\230\205\342\230\205\342\230\205");
730 break;
731 case 4:
732 result = g_strdup("\342\230\205\342\230\205\342\230\205\342\230\205");
733 break;
734 case 5:
735 result = g_strdup("\342\230\205\342\230\205\342\230\205\342\230\205\342\230\205");
736 break;
737 default:
738 result = g_strdup("");
739 break;
740 }
741 }
742 else if((_has_prefix(variable, "LABELS.ICONS") || _has_prefix(variable, "LABELS_ICONS") ||
743 _has_prefix(variable, "LABELS.COLORICONS") || _has_prefix(variable, "LABELS_COLORICONS"))
744 && g_strcmp0(params->jobcode, "infos") == 0)
745 {
746 *escape = FALSE;
747 GList *res = dt_metadata_get(params->imgid, "Xmp.darktable.colorlabels", NULL);
748 for(GList *res_iter = res; res_iter; res_iter = g_list_next(res_iter))
749 {
750 const int dot_index = GPOINTER_TO_INT(res_iter->data);
751 const GdkRGBA c = dt_bauhaus_get_global()->colorlabels[dot_index];
752 result = dt_util_dstrcat(result,
753 "<span foreground='#%02x%02x%02x'>\342\254\244 </span>",
754 (guint)(c.red*255), (guint)(c.green*255), (guint)(c.blue*255));
755 }
756 g_list_free(res);
757 res = NULL;
758 }
759 else if(_has_prefix(variable, "LABELS"))
760 {
761 // TODO: currently we concatenate all the color labels with a ',' as a separator. Maybe it's better to
762 // only use the first/last label?
763 GList *res = dt_metadata_get(params->imgid, "Xmp.darktable.colorlabels", NULL);
764 if(!IS_NULL_PTR(res))
765 {
766 GList *labels = NULL;
767 for(GList *res_iter = res; res_iter; res_iter = g_list_next(res_iter))
768 {
769 labels = g_list_prepend(labels, (char *)(_(dt_colorlabels_to_string(GPOINTER_TO_INT(res_iter->data)))));
770 }
771 labels = g_list_reverse(labels); // list was built in reverse order, so un-reverse it
772 result = dt_util_glist_to_str(",", labels);
773 g_list_free(labels);
774 labels = NULL;
775 }
776 g_list_free(res);
777 res = NULL;
778 }
779 else if(_has_prefix(variable, "TITLE") || _has_prefix(variable, "Xmp.dc.title"))
780 {
781 GList *res = dt_metadata_get(params->imgid, "Xmp.dc.title", NULL);
782 if(!IS_NULL_PTR(res))
783 {
784 result = g_strdup((char *)res->data);
785 }
786 g_list_free_full(res, dt_free_gpointer);
787 res = NULL;
788 }
789 else if(_has_prefix(variable, "DESCRIPTION") || _has_prefix(variable, "Xmp.dc.description"))
790 {
791 GList *res = dt_metadata_get(params->imgid, "Xmp.dc.description", NULL);
792 if(!IS_NULL_PTR(res))
793 {
794 result = g_strdup((char *)res->data);
795 }
796 g_list_free_full(res, dt_free_gpointer);
797 res = NULL;
798 }
799 else if(_has_prefix(variable, "CREATOR") || _has_prefix(variable, "Xmp.dc.creator"))
800 {
801 GList *res = dt_metadata_get(params->imgid, "Xmp.dc.creator", NULL);
802 if(!IS_NULL_PTR(res))
803 {
804 result = g_strdup((char *)res->data);
805 }
806 g_list_free_full(res, dt_free_gpointer);
807 res = NULL;
808 }
809 else if(_has_prefix(variable, "PUBLISHER") || _has_prefix(variable, "Xmp.dc.publisher"))
810 {
811 GList *res = dt_metadata_get(params->imgid, "Xmp.dc.publisher", NULL);
812 if(!IS_NULL_PTR(res))
813 {
814 result = g_strdup((char *)res->data);
815 }
816 g_list_free_full(res, dt_free_gpointer);
817 res = NULL;
818 }
819 else if(_has_prefix(variable, "RIGHTS") || _has_prefix(variable, "Xmp.dc.rights"))
820 {
821 GList *res = dt_metadata_get(params->imgid, "Xmp.dc.rights", NULL);
822 if(!IS_NULL_PTR(res))
823 {
824 result = g_strdup((char *)res->data);
825 }
826 g_list_free_full(res, dt_free_gpointer);
827 res = NULL;
828 }
829 else if(_has_prefix(variable, "OPENCL.ACTIVATED") || _has_prefix(variable, "OPENCL_ACTIVATED"))
830 {
832 result = g_strdup(_("yes"));
833 else
834 result = g_strdup(_("no"));
835 }
836 else return FALSE;
837
838 *out = result;
839 return TRUE;
840}
841
853static gboolean _expand_geometry_and_misc(dt_variables_params_t *params, char **variable, char **out)
854{
855 char *result = NULL;
856
857 if(_has_prefix(variable, "WIDTH.MAX") || _has_prefix(variable, "MAX_WIDTH"))
858 result = g_strdup_printf("%d", params->data->max_width);
859 else if(_has_prefix(variable, "WIDTH.SENSOR") || _has_prefix(variable, "SENSOR_WIDTH"))
860 result = g_strdup_printf("%d", params->data->sensor_width);
861 else if(_has_prefix(variable, "WIDTH.RAW") || _has_prefix(variable, "RAW_WIDTH"))
862 result = g_strdup_printf("%d", params->data->raw_width);
863 else if(_has_prefix(variable, "WIDTH.CROP"))
864 result = g_strdup_printf("%d", params->data->crop_width);
865 else if(_has_prefix(variable, "WIDTH.EXPORT") || _has_prefix(variable, "EXPORT_WIDTH"))
866 result = g_strdup_printf("%d", params->data->export_width);
867 else if(_has_prefix(variable, "HEIGHT.MAX") || _has_prefix(variable, "MAX_HEIGHT"))
868 result = g_strdup_printf("%d", params->data->max_height);
869 else if(_has_prefix(variable, "HEIGHT.SENSOR") || _has_prefix(variable, "SENSOR_HEIGHT"))
870 result = g_strdup_printf("%d", params->data->sensor_height);
871 else if(_has_prefix(variable, "HEIGHT.RAW") || _has_prefix(variable, "RAW_HEIGHT"))
872 result = g_strdup_printf("%d", params->data->raw_height);
873 else if(_has_prefix(variable, "HEIGHT.CROP"))
874 result = g_strdup_printf("%d", params->data->crop_height);
875 else if(_has_prefix(variable, "HEIGHT.EXPORT") || _has_prefix(variable, "EXPORT_HEIGHT"))
876 result = g_strdup_printf("%d", params->data->export_height);
877 else if (_has_prefix(variable, "CATEGORY"))
878 {
879 // CATEGORY should be followed by n [0,9] and "(category)". category can contain 0 or more '|'
880 if (g_ascii_isdigit(*variable[0]))
881 {
882 const uint8_t level = (uint8_t)*variable[0] & 0b1111;
883 (*variable) ++;
884 if (*variable[0] == '(')
885 {
886 char *category = g_strdup(*variable + 1);
887 char *end = g_strstr_len(category, -1, ")");
888 if (end)
889 {
890 end[0] = '|';
891 end[1] = '\0';
892 (*variable) += strlen(category) + 1;
893 char *tag = dt_tag_get_subtags(params->imgid, category, (int)level);
894 if (tag)
895 {
896 result = g_strdup(tag);
897 dt_free(tag);
898 }
899 }
900 dt_free(category);
901 }
902 }
903 }
904 else if(_has_prefix(variable, "TAGS") || _has_prefix(variable, "IMAGE.TAGS"))
905 {
906 GList *tags_list = dt_tag_get_list_export(params->imgid, params->data->tags_flags);
907 char *tags = dt_util_glist_to_str(", ", tags_list);
908 g_list_free_full(tags_list, dt_free_gpointer);
909 tags_list = NULL;
910 result = g_strdup(tags);
911 dt_free(tags);
912 }
913 else if(_has_prefix(variable, "SIDECAR_TXT") && g_strcmp0(params->jobcode, "infos") == 0
914 && (params->data->flags & DT_IMAGE_HAS_TXT))
915 {
916 char *path = dt_image_get_text_path(params->imgid);
917 if(path)
918 {
919 gchar *txt = NULL;
920 if(g_file_get_contents(path, &txt, NULL, NULL))
921 {
922 result = g_strdup_printf("\n%s", txt);
923 }
924 dt_free(txt);
925 dt_free(path);
926 }
927 }
928 else if(_has_prefix(variable, "DARKTABLE.VERSION") || _has_prefix(variable, "DARKTABLE_VERSION")
929 || _has_prefix(variable, "ANSEL.VERSION"))
930 result = g_strdup(darktable_package_version);
931 else if(_has_prefix(variable, "DARKTABLE.NAME") || _has_prefix(variable, "DARKTABLE_NAME")
932 || _has_prefix(variable, "ANSEL.NAME"))
933 result = g_strdup(PACKAGE_NAME);
934 else return FALSE;
935
936 *out = result;
937 return TRUE;
938}
939
957static char *_get_base_value(dt_variables_params_t *params, char **variable)
958{
959 char *result = NULL;
960 gboolean escape = TRUE;
961
962 if(!_expand_datetime_now(params, variable, &result)
963 && !_expand_exif_datetime(params, variable, &result)
964 && !_expand_exif_and_gps(params, variable, &result)
965 && !_expand_image_and_version(params, variable, &result)
966 && !_expand_file_and_folder(params, variable, &result)
967 && !_expand_rating_and_metadata(params, variable, &result, &escape)
968 && !_expand_geometry_and_misc(params, variable, &result))
969 {
970 // go past what looks like an invalid variable. we only expect to see [a-zA-Z]* in a variable name.
971 while(g_ascii_isalpha(**variable)) (*variable)++;
972 }
973 if(!result) result = g_strdup("");
974
975 if(params->escape_markup && escape)
976 {
977 gchar *e_res = g_markup_escape_text(result, -1);
978 dt_free(result);
979 return e_res;
980 }
981 return result;
982}
983// bash style variable manipulation. all patterns are just simple string comparisons!
984// See here for bash examples and documentation:
985// http://www.tldp.org/LDP/abs/html/parameter-substitution.html
986// https://www.gnu.org/software/bash/manual/html_node/Shell-Parameter-Expansion.html
987// the descriptions in the comments are referring to the bash behaviour, dt doesn't do it 100% like that!
988static char *_variable_get_value(dt_variables_params_t *params, char **variable)
989{
990 // invariant: the variable starts with "$(" which we can skip
991 (*variable) += 2;
992
993 // first get the value of the variable
994 char *base_value = _get_base_value(params, variable); // this is never going to be NULL!
995 const size_t base_value_length = strlen(base_value);
996
997 // ... and now see if we have to change it
998 const char operation = **variable;
999 if(operation != '\0' && operation != ')') (*variable)++;
1000 switch(operation)
1001 {
1002 case '-':
1003 /*
1004 $(parameter-default)
1005 If parameter not set, use default.
1006 */
1007 {
1008 char *replacement = _expand_source(params, variable, ')');
1009 if(*base_value == '\0')
1010 {
1011 dt_free(base_value);
1012 base_value = replacement;
1013 }
1014 else
1015 dt_free(replacement);
1016 }
1017 break;
1018 case '+':
1019 /*
1020 $(parameter+alt_value)
1021 If parameter set, use alt_value, else use null string.
1022 */
1023 {
1024 char *replacement = _expand_source(params, variable, ')');
1025 if(*base_value != '\0')
1026 {
1027 dt_free(base_value);
1028 base_value = replacement;
1029 }
1030 else
1031 dt_free(replacement);
1032 }
1033 break;
1034 case ':':
1035 /*
1036 $(var:offset)
1037 Variable var expanded, starting from offset.
1038
1039 $(var:offset:length)
1040 Expansion to a max of length characters of variable var, from offset.
1041
1042 If offset evaluates to a number less than zero, the value is used as an offset in characters from the
1043 end of the value of parameter. If length evaluates to a number less than zero, it is interpreted as an
1044 offset in characters from the end of the value of parameter rather than a number of characters, and the
1045 expansion is the characters between offset and that result.
1046 */
1047 {
1048 const glong base_value_utf8_length = g_utf8_strlen(base_value, -1);
1049 const glong offset = strtol(*variable, variable, 10);
1050
1051 // find where to start
1052 char *start; // from where to copy ...
1053 if(offset >= 0)
1054 start = g_utf8_offset_to_pointer(base_value, MIN(offset, base_value_utf8_length));
1055 else
1056 start = g_utf8_offset_to_pointer(base_value + base_value_length, MAX(offset, -base_value_utf8_length));
1057
1058 // now find the end if there is a length provided
1059 char *end = base_value + base_value_length; // ... and until where
1060 if(start && **variable == ':')
1061 {
1062 (*variable)++;
1063 const size_t start_utf8_length = g_utf8_strlen(start, -1);
1064 const int length = strtol(*variable, variable, 10);
1065 if(length >= 0)
1066 end = g_utf8_offset_to_pointer(start, MIN(length, start_utf8_length));
1067 else
1068 end = g_utf8_offset_to_pointer(base_value + base_value_length, MAX(length, -start_utf8_length));
1069 }
1070
1071 char *_base_value = g_strndup(start, end - start);
1072 dt_free(base_value);
1073 base_value = _base_value;
1074 }
1075 break;
1076 case '#':
1077 /*
1078 $(var#Pattern)
1079 Remove from $var the shortest part of $Pattern that matches the front end of $var.
1080 */
1081 {
1082 char *pattern = _expand_source(params, variable, ')');
1083 const size_t pattern_length = strlen(pattern);
1084 if(!strncmp(base_value, pattern, pattern_length))
1085 {
1086 char *_base_value = g_strdup(base_value + pattern_length);
1087 dt_free(base_value);
1088 base_value = _base_value;
1089 }
1090 dt_free(pattern);
1091 }
1092 break;
1093 case '%':
1094 /*
1095 $(var%Pattern)
1096 Remove from $var the shortest part of $Pattern that matches the back end of $var.
1097 */
1098 {
1099 char *pattern = _expand_source(params, variable, ')');
1100 const size_t pattern_length = strlen(pattern);
1101 if(!strncmp(base_value + base_value_length - pattern_length, pattern, pattern_length))
1102 base_value[base_value_length - pattern_length] = '\0';
1103 dt_free(pattern);
1104 }
1105 break;
1106 case '/':
1107 /*
1108 replacement. the following cases are possible:
1109
1110 $(var/Pattern/Replacement)
1111 First match of Pattern, within var replaced with Replacement.
1112 If Replacement is omitted, then the first match of Pattern is replaced by nothing, that is, deleted.
1113
1114 $(var//Pattern/Replacement)
1115 Global replacement. All matches of Pattern, within var replaced with Replacement.
1116 As above, if Replacement is omitted, then all occurrences of Pattern are replaced by nothing, that is, deleted.
1117
1118 $(var/#Pattern/Replacement)
1119 If prefix of var matches Pattern, then substitute Replacement for Pattern.
1120
1121 $(var/%Pattern/Replacement)
1122 If suffix of var matches Pattern, then substitute Replacement for Pattern.
1123 */
1124 {
1125 const char mode = **variable;
1126
1127 if(mode == '/' || mode == '#' || mode == '%') (*variable)++;
1128 char *pattern = _expand_source(params, variable, '/');
1129 const size_t pattern_length = strlen(pattern);
1130 (*variable)++;
1131 char *replacement = _expand_source(params, variable, ')');
1132 const size_t replacement_length = strlen(replacement);
1133
1134 switch(mode)
1135 {
1136 case '/':
1137 {
1138 // TODO: write a dt_util_str_replace that can deal with pattern_length ^^
1139 char *p = g_strndup(pattern, pattern_length);
1140 char *_base_value = dt_util_str_replace(base_value, p, replacement);
1141 dt_free(p);
1142 dt_free(base_value);
1143 base_value = _base_value;
1144 break;
1145 }
1146 case '#':
1147 {
1148 if(!strncmp(base_value, pattern, pattern_length))
1149 {
1150 char *_base_value = g_malloc(base_value_length - pattern_length + replacement_length + 1);
1151 char *end = g_stpcpy(_base_value, replacement);
1152 g_stpcpy(end, base_value + pattern_length);
1153 dt_free(base_value);
1154 base_value = _base_value;
1155 }
1156 break;
1157 }
1158 case '%':
1159 {
1160 if(!strncmp(base_value + base_value_length - pattern_length, pattern, pattern_length))
1161 {
1162 char *_base_value = g_malloc(base_value_length - pattern_length + replacement_length + 1);
1163 base_value[base_value_length - pattern_length] = '\0';
1164 char *end = g_stpcpy(_base_value, base_value);
1165 g_stpcpy(end, replacement);
1166 dt_free(base_value);
1167 base_value = _base_value;
1168 }
1169 break;
1170 }
1171 default:
1172 {
1173 // TODO: is there a strstr_len that limits the length of pattern?
1174 char *p = g_strndup(pattern, pattern_length);
1175 gchar *found = g_strstr_len(base_value, -1, p);
1176 dt_free(p);
1177 if(found)
1178 {
1179 *found = '\0';
1180 char *_base_value = g_malloc(base_value_length - pattern_length + replacement_length + 1);
1181 char *end = g_stpcpy(_base_value, base_value);
1182 end = g_stpcpy(end, replacement);
1183 g_stpcpy(end, found + pattern_length);
1184 dt_free(base_value);
1185 base_value = _base_value;
1186 }
1187 break;
1188 }
1189 }
1190 dt_free(pattern);
1191 dt_free(replacement);
1192 }
1193 break;
1194 case '^':
1195 case ',':
1196 /*
1197 changing the case:
1198
1199 $(parameter^pattern)
1200 $(parameter^^pattern)
1201 $(parameter,pattern)
1202 $(parameter,,pattern)
1203 This expansion modifies the case of alphabetic characters in parameter.
1204 The ‘^’ operator converts lowercase letters to uppercase;
1205 the ‘,’ operator converts uppercase letters to lowercase.
1206 The ‘^^’ and ‘,,’ expansions convert each character in the expanded value;
1207 the ‘^’ and ‘,’ expansions convert only the first character in the expanded value.
1208 */
1209 {
1210 const char mode = **variable;
1211 char *_base_value = NULL;
1212 if(operation == '^' && mode == '^')
1213 {
1214 _base_value = g_utf8_strup (base_value, -1);
1215 (*variable)++;
1216 }
1217 else if(operation == ',' && mode == ',')
1218 {
1219 _base_value = g_utf8_strdown(base_value, -1);
1220 (*variable)++;
1221 }
1222 else
1223 {
1224 gunichar changed = g_utf8_get_char(base_value);
1225 changed = operation == '^' ? g_unichar_toupper(changed) : g_unichar_tolower(changed);
1226 int utf8_length = g_unichar_to_utf8(changed, NULL);
1227 char *next = g_utf8_next_char(base_value);
1228 _base_value = g_malloc0(base_value_length - (next - base_value) + utf8_length + 1);
1229 g_unichar_to_utf8(changed, _base_value);
1230 g_stpcpy(_base_value + utf8_length, next);
1231 }
1232 dt_free(base_value);
1233 base_value = _base_value;
1234 }
1235 break;
1236 }
1237
1238 if(**variable == ')')
1239 (*variable)++;
1240 else
1241 {
1242 // error case
1243 dt_free(base_value);
1244 }
1245
1246 return base_value;
1247}
1248
1249static void _grow_buffer(char **result, char **result_iter, size_t *result_length, size_t extra_space)
1250{
1251 const size_t used_length = *result_iter - *result;
1252 if(used_length + extra_space > *result_length)
1253 {
1254 *result_length = used_length + extra_space;
1255 *result = g_realloc(*result, *result_length + 1);
1256 *result_iter = *result + used_length;
1257 }
1258}
1259
1260static char *_expand_source(dt_variables_params_t *params, char **source, char extra_stop)
1261{
1262 char *result = g_strdup("");
1263 if(IS_NULL_PTR(*source)) return result;
1264 char *result_iter = result;
1265 size_t result_length = 0;
1266 char *source_iter = *source;
1267 const size_t source_length = strlen(*source);
1268
1269 while(*source_iter && *source_iter != extra_stop)
1270 {
1271 // find start of variable, copying over everything till then
1272 while(*source_iter && *source_iter != extra_stop)
1273 {
1274 char c = *source_iter;
1275 if(c == '$' && source_iter[1] == '(')
1276 break;
1277
1278 if(result_iter - result >= result_length)
1279 _grow_buffer(&result, &result_iter, &result_length, source_length - (source_iter - *source));
1280 *result_iter = c;
1281 result_iter++;
1282 source_iter++;
1283
1284 }
1285
1286 // it seems we have a variable here
1287 if(*source_iter == '$')
1288 {
1289 char *old_source_iter = source_iter;
1290 char *replacement = _variable_get_value(params, &source_iter);
1291 if(replacement)
1292 {
1293 const size_t replacement_length = strlen(replacement);
1294 _grow_buffer(&result, &result_iter, &result_length, replacement_length);
1295 memcpy(result_iter, replacement, replacement_length);
1296 result_iter += replacement_length;
1297 dt_free(replacement);
1298 }
1299 else
1300 {
1301 // the error case of missing closing ')' -- try to recover
1302 source_iter = old_source_iter;
1303 _grow_buffer(&result, &result_iter, &result_length, source_length - (source_iter - *source));
1304 *result_iter++ = *source_iter++;
1305 }
1306 }
1307 }
1308
1309 *result_iter = '\0';
1310 *source = source_iter;
1311
1312 return result;
1313}
1314
1315char *dt_variables_expand(dt_variables_params_t *params, gchar *source, gboolean iterate)
1316{
1317 _init_expansion(params, iterate);
1318
1319 char *result = _expand_source(params, &source, '\0');
1320
1321 _cleanup_expansion(params);
1322 return result;
1323}
1324
1326{
1327 *params = g_malloc0(sizeof(dt_variables_params_t));
1328 (*params)->data = g_malloc0(sizeof(dt_variables_data_t));
1329 (*params)->data->time = g_date_time_new_now_local();
1330 (*params)->data->file_datetime = NULL;
1331 (*params)->data->exif_time[0] = 0;
1332 (*params)->sequence = -1;
1333 (*params)->img = NULL;
1334}
1335
1337{
1338 if(params->data->time)
1339 g_date_time_unref(params->data->time);
1340
1341 if(params->data->file_datetime)
1342 g_date_time_unref(params->data->file_datetime);
1343
1344 dt_free(params->data);
1345 dt_free(params);
1346}
1347
1348void dt_variables_set_datetime(dt_variables_params_t *params, GDateTime *datetime)
1349{
1350 if(params->data->time) g_date_time_unref(params->data->time);
1351 params->data->time = g_date_time_ref(datetime);
1352}
1353
1354void dt_variables_set_max_width_height(dt_variables_params_t *params, int max_width, int max_height)
1355{
1356 params->data->max_width = max_width;
1357 params->data->max_height = max_height;
1358}
1359
1361{
1362 params->data->sequence = 0;
1363}
1364
1366{
1367 params->data->tags_flags = flags;
1368}
1369
1370
1371
1372// clang-format off
1373// modelines: These editor modelines have been set for all relevant files by tools/update_modelines.py
1374// vim: shiftwidth=2 expandtab tabstop=2 cindent
1375// kate: tab-indents: off; indent-width 2; replace-tabs on; indent-mode cstyle; remove-trailing-spaces modified;
1376// clang-format on
#define TRUE
Definition ashift_lsd.c:162
#define FALSE
Definition ashift_lsd.c:158
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:646
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:113
@ DT_IMAGE_HAS_TXT
Definition image.h:136
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 ...
#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:3262
#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:332
double elevation
Definition image.h:332
double longitude
Definition image.h:332
float exif_exposure
Definition image.h:367
int32_t height
Definition image.h:397
GTimeSpan export_timestamp
Definition image.h:415
float exif_focus_distance
Definition image.h:372
GTimeSpan import_timestamp
Definition image.h:415
float exif_exposure_bias
Definition image.h:368
float exif_iso
Definition image.h:370
char camera_maker[64]
Definition image.h:379
float exif_aperture
Definition image.h:369
int32_t version
Definition image.h:401
int32_t crop_height
Definition image.h:398
dt_image_geoloc_t geoloc
Definition image.h:434
int32_t flags
Definition image.h:401
int32_t width
Definition image.h:397
float exif_focal_length
Definition image.h:371
GTimeSpan change_timestamp
Definition image.h:415
char camera_alias[64]
Definition image.h:381
char exif_lens[128]
Definition image.h:376
GTimeSpan print_timestamp
Definition image.h:415
int32_t p_height
Definition image.h:397
int32_t p_width
Definition image.h:397
int32_t crop_width
Definition image.h:398
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:140
gchar * dt_util_longitude_str(float longitude)
Definition utility.c:572
GDateTime * dt_util_get_file_datetime(const char *const path)
Definition utility.c:792
char * dt_util_format_exposure(const float exposuretime)
Definition utility.c:868
gchar * dt_util_latitude_str(float latitude)
Definition utility.c:554
gchar * dt_util_glist_to_str(const gchar *separator, GList *items)
Definition utility.c:170
gchar * dt_util_dstrcat(gchar *str, const gchar *format,...)
Definition utility.c:99