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 "common/debug.h"
51#include "common/darktable.h"
52#include "bauhaus/bauhaus.h"
53#include "common/variables.h"
54#include "common/colorlabels.h"
55#include "common/darktable.h"
57#include "common/image.h"
58#include "common/image_cache.h"
59#include "common/metadata.h"
60#include "common/opencl.h"
61#include "common/utility.h"
62#include "common/tags.h"
63#include "common/datetime.h"
64#include "control/conf.h"
65#include "common/exif.h"
66
67#include <stdio.h>
68#include <string.h>
69#include <time.h>
70
71typedef struct dt_variables_data_t
72{
74 GDateTime *time;
75 GDateTime *file_datetime;
76
78 guint sequence;
79
80 // max image size taken from export module GUI, can be zero
83
84 // total sensor size, before RAW crop
87
88 // max RAW file size, after the raw crop
91
92 // image export size after crop and export resize
97
98 char *homedir;
100 const char *file_ext;
101
102 gboolean have_exif_dt;
103 gboolean show_msec;
109 int stars;
110 GDateTime *datetime;
111
117 double longitude;
118 double latitude;
119 double elevation;
124
125 uint32_t tags_flags;
126
127 int flags;
128
130
137
138static char *_expand_source(dt_variables_params_t *params, char **source, char extra_stop);
139
140gboolean dt_get_user_pictures_dir(const gchar *homedir, gchar *picdir, size_t picdir_size)
141{
142 if(IS_NULL_PTR(homedir) || picdir_size == 0 || IS_NULL_PTR(picdir))
143 return 0;
144
145 gchar dir[PATH_MAX] = { 0 };
146 const gchar *special_pictures_dir = g_get_user_special_dir(G_USER_DIRECTORY_PICTURES);
147 if(IS_NULL_PTR(special_pictures_dir))
148 g_snprintf(dir, sizeof(dir), "%s" G_DIR_SEPARATOR_S "Pictures", homedir);
149 else
150 g_strlcpy(dir, special_pictures_dir, sizeof(dir));
151
152 if(*dir == 0)
153 fprintf(stderr,"Error: Can't get user's pictures folder.\n");
154 else
155 return g_strlcpy(picdir, dir, picdir_size) >= 1;
156
157 return 0;
158}
159
160// gather some data that might be used for variable expansion
161static void _init_expansion(dt_variables_params_t *params, gboolean iterate)
162{
163 if(iterate) params->data->sequence++;
164
165 params->data->homedir = dt_loc_get_home_dir(NULL);
166
167 gchar picture_folder[PATH_MAX] = { 0 };
168 dt_get_user_pictures_dir(params->data->homedir, picture_folder, sizeof(picture_folder));
169 params->data->pictures_folder = g_strdup(picture_folder);
170
171 if(params->filename)
172 {
173 params->data->file_ext = (g_strrstr(params->filename, ".") + 1);
174 if(params->data->file_ext == (gchar *)1) params->data->file_ext = params->filename + strlen(params->filename);
175 params->data->file_datetime = dt_util_get_file_datetime(params->filename);
176 }
177 else
178 params->data->file_ext = NULL;
179
180 /* image exif time */
181 params->data->have_exif_dt = FALSE;
182 params->data->exif_iso = 100;
183 params->data->camera_maker = NULL;
184 params->data->camera_alias = NULL;
185 params->data->exif_lens = NULL;
186 params->data->version = 0;
187 params->data->stars = 0;
188 params->data->exif_exposure = 0.0f;
189 params->data->exif_exposure_bias = NAN;
190 params->data->exif_aperture = 0.0f;
191 params->data->exif_focal_length = 0.0f;
192 params->data->exif_focus_distance = 0.0f;
193 params->data->longitude = NAN;
194 params->data->latitude = NAN;
195 params->data->elevation = NAN;
196 params->data->crop_width = 0;
197 params->data->crop_height = 0;
198 params->data->import_timestamp = 0;
199 params->data->change_timestamp = 0;
200 params->data->export_timestamp = 0;
201 params->data->print_timestamp = 0;
202 params->data->show_msec = dt_conf_get_bool("lighttable/ui/milliseconds");
203
204 dt_image_t *img = NULL;
205 _image_case release = NONE;
206
207 if(params->img)
208 {
209 img = (dt_image_t *)params->img;
210 }
211 else if(params->imgid > UNKNOWN_IMAGE)
212 {
213 img = dt_image_cache_get(darktable.image_cache, params->imgid, 'r');
214 release = IMGID;
215 }
216
217 if(img)
218 {
219 params->data->datetime = dt_datetime_img_to_gdatetime(img, darktable.utc_tz);
220 if(params->data->datetime)
221 params->data->have_exif_dt = TRUE;
222 params->data->exif_iso = img->exif_iso;
223 params->data->camera_maker = g_strdup(img->camera_maker);
224 params->data->camera_alias = g_strdup(img->camera_alias);
225 params->data->exif_lens = g_strdup(img->exif_lens);
226 params->data->version = img->version;
227 params->data->stars = (img->flags & 0x7);
228 if(params->data->stars == 6 || (img->flags & DT_IMAGE_REJECTED) != 0) params->data->stars = -1;
229
230 params->data->exif_exposure = img->exif_exposure;
231 params->data->exif_exposure_bias = img->exif_exposure_bias;
232 params->data->exif_aperture = img->exif_aperture;
233 params->data->exif_focal_length = img->exif_focal_length;
234 if(!isnan(img->exif_focus_distance) && fpclassify(img->exif_focus_distance) != FP_ZERO)
235 params->data->exif_focus_distance = img->exif_focus_distance;
236 params->data->longitude = img->geoloc.longitude;
237 params->data->latitude = img->geoloc.latitude;
238 params->data->elevation = img->geoloc.elevation;
239
240 params->data->flags = img->flags;
241
242 params->data->raw_height = img->p_height;
243 params->data->raw_width = img->p_width;
244 params->data->sensor_height = img->height;
245 params->data->sensor_width = img->width;
246 params->data->export_height = 0;
247 params->data->export_width = 0;
248 params->data->crop_height = img->crop_height;
249 params->data->crop_width = img->crop_width;
250 params->data->import_timestamp = img->import_timestamp;
251 params->data->change_timestamp = img->change_timestamp;
252 params->data->export_timestamp = img->export_timestamp;
253 params->data->print_timestamp = img->print_timestamp;
254 }
255
256 switch (release)
257 {
258 case NONE:
259 case FILENAME:
260 {
261 break;
262 }
263 case IMGID:
264 {
266 break;
267 }
268 }
269}
270
272{
273 if(params->data->datetime)
274 {
275 g_date_time_unref(params->data->datetime);
276 params->data->datetime = NULL;
277 }
278 dt_free(params->data->homedir);
279 dt_free(params->data->pictures_folder);
280 dt_free(params->data->camera_maker);
281 dt_free(params->data->camera_alias);
282 dt_free(params->data->exif_lens);
283}
284
285static inline gboolean _has_prefix(char **str, const char *prefix)
286{
287 gboolean res = g_str_has_prefix(*str, prefix);
288 if(res) *str += strlen(prefix);
289 return res;
290}
291
293{
294 if(isnan(params->data->longitude))
295 return g_strdup("");
296 if(dt_conf_get_bool("plugins/lighttable/metadata_view/pretty_location")
297 && g_strcmp0(params->jobcode, "infos") == 0)
298 {
299 return dt_util_longitude_str(params->data->longitude);
300 }
301 else
302 {
303 gchar NS = params->data->longitude < 0 ? 'W' : 'E';
304 return g_strdup_printf("%c%010.6f", NS, fabs(params->data->longitude));
305 }
306}
307
309{
310 if(isnan(params->data->latitude))
311 return g_strdup("");
312 if(dt_conf_get_bool("plugins/lighttable/metadata_view/pretty_location")
313 && g_strcmp0(params->jobcode, "infos") == 0)
314 {
315 return dt_util_latitude_str(params->data->latitude);
316 }
317 else
318 {
319 gchar NS = params->data->latitude < 0 ? 'S' : 'N';
320 return g_strdup_printf("%c%09.6f", NS, fabs(params->data->latitude));
321 }
322}
323
324static char *_variables_get_iso_timestamp(const GTimeSpan gts)
325{
326 if(gts <= 0) return NULL;
327
328 GDateTime *gdt = g_date_time_add(darktable.origin_gdt, gts);
329 if(IS_NULL_PTR(gdt)) return NULL;
330
331 char *result = g_date_time_format(gdt, "%Y-%m-%d %H:%M:%S");
332 g_date_time_unref(gdt);
333 return result;
334}
335
336static char *_get_base_value(dt_variables_params_t *params, char **variable)
337{
338 char *result = NULL;
339 gboolean escape = TRUE;
340
341 char exif_datetime[DT_DATETIME_LENGTH];
342 GDateTime *datetime = params->data->have_exif_dt ? params->data->datetime : params->data->time;
343
344 if(_has_prefix(variable, "YEAR.SHORT") || _has_prefix(variable, "SHORT_YEAR") || _has_prefix(variable, "DATE.SHORT_YEAR"))
345 result = g_date_time_format(params->data->time, "%y");
346 else if(_has_prefix(variable, "YEAR") || _has_prefix(variable, "DATE.LONG_YEAR"))
347 result = g_date_time_format(params->data->time, "%Y");
348 else if(_has_prefix(variable, "MONTH.SHORT") || _has_prefix(variable, "DATE.SHORT_MONTH"))
349 result = g_date_time_format(params->data->time, "%b");
350 else if(_has_prefix(variable, "MONTH.LONG") || _has_prefix(variable, "DATE.LONG_MONTH"))
351 result = g_date_time_format(params->data->time, "%B");
352 else if(_has_prefix(variable, "MONTH") || _has_prefix(variable, "DATE.MONTH"))
353 result = g_date_time_format(params->data->time, "%m");
354 else if(_has_prefix(variable, "DAY") || _has_prefix(variable, "DATE.DAY"))
355 result = g_date_time_format(params->data->time, "%d");
356 else if(_has_prefix(variable, "HOUR.AMPM") || _has_prefix(variable, "DATE.HOUR_AMPM"))
357 result = g_date_time_format(params->data->time, "%I %p");
358 else if(_has_prefix(variable, "HOUR") || _has_prefix(variable, "DATE.HOUR"))
359 result = g_date_time_format(params->data->time, "%H");
360 else if(_has_prefix(variable, "MINUTE") || _has_prefix(variable, "DATE.MINUTE"))
361 result = g_date_time_format(params->data->time, "%M");
362 else if(_has_prefix(variable, "SECOND") || _has_prefix(variable, "DATE.SECOND"))
363 result = g_date_time_format(params->data->time, "%S");
364 else if(_has_prefix(variable, "MSEC"))
365 {
366 result = g_date_time_format(params->data->time, "%f");
367 result[3] = '\0';
368 }
369 // for watermark backward compatibility
370 else if(_has_prefix(variable, "DATE"))
371 {
372 dt_datetime_gdatetime_to_exif(exif_datetime, params->data->show_msec ? DT_DATETIME_LENGTH : DT_DATETIME_EXIF_LENGTH, params->data->time);
373 result = g_strdup(exif_datetime);
374 }
375 else if(_has_prefix(variable, "IMPORT.DATE") || _has_prefix(variable, "DATE.IMPORT"))
376 {
377 result = _variables_get_iso_timestamp(params->data->import_timestamp);
378 }
379 else if(_has_prefix(variable, "CHANGE.DATE") || _has_prefix(variable, "DATE.CHANGE"))
380 {
381 result = _variables_get_iso_timestamp(params->data->change_timestamp);
382 }
383 else if(_has_prefix(variable, "EXPORT.DATE") || _has_prefix(variable, "DATE.EXPORT"))
384 {
385 result = _variables_get_iso_timestamp(params->data->export_timestamp);
386 }
387 else if(_has_prefix(variable, "PRINT.DATE") || _has_prefix(variable, "DATE.PRINT"))
388 {
389 result = _variables_get_iso_timestamp(params->data->print_timestamp);
390 }
391
392 else if(_has_prefix(variable, "EXIF.YEAR.SHORT") || _has_prefix(variable, "EXIF.DATE.SHORT_YEAR"))
393 result = g_date_time_format(datetime, "%y");
394 else if(_has_prefix(variable, "EXIF.YEAR") || _has_prefix(variable, "EXIF_YEAR") || _has_prefix(variable, "EXIF.DATE.LONG_YEAR"))
395 result = g_date_time_format(datetime, "%Y");
396 else if(_has_prefix(variable, "EXIF.MONTH.SHORT") || _has_prefix(variable, "EXIF.DATE.SHORT_MONTH"))
397 result = g_date_time_format(datetime, "%b");
398 else if(_has_prefix(variable, "EXIF.MONTH.LONG") || _has_prefix(variable, "EXIF.DATE.LONG_MONTH"))
399 result = g_date_time_format(datetime, "%B");
400 else if(_has_prefix(variable, "EXIF.MONTH") || _has_prefix(variable, "EXIF_MONTH") || _has_prefix(variable, "EXIF.DATE.MONTH"))
401 result = g_date_time_format(datetime, "%m");
402 else if(_has_prefix(variable, "EXIF.DAY") || _has_prefix(variable, "EXIF_DAY") || _has_prefix(variable, "EXIF.DATE.DAY"))
403 result = g_date_time_format(datetime, "%d");
404 else if(_has_prefix(variable, "EXIF.HOUR.AMPM") || _has_prefix(variable, "EXIF.DATE.HOUR_AMPM"))
405 result = g_date_time_format(datetime, "%I %p");
406 else if(_has_prefix(variable, "EXIF.HOUR") || _has_prefix(variable, "EXIF_HOUR") || _has_prefix(variable, "EXIF.DATE.HOUR"))
407 result = g_date_time_format(datetime, "%H");
408 else if(_has_prefix(variable, "EXIF.MINUTE") || _has_prefix(variable, "EXIF_MINUTE") || _has_prefix(variable, "EXIF.DATE.MINUTE"))
409 result = g_date_time_format(datetime, "%M");
410 else if(_has_prefix(variable, "EXIF.SECOND") || _has_prefix(variable, "EXIF_SECOND") || _has_prefix(variable, "EXIF.DATE.SECOND"))
411 result = g_date_time_format(datetime, "%S");
412 else if(_has_prefix(variable, "EXIF.MSEC") || _has_prefix(variable, "EXIF_MSEC"))
413 {
414 result = g_date_time_format(datetime, "%f");
415 result[3] = '\0';
416 }
417 // for watermark backward compatibility
418 else if(_has_prefix(variable, "EXIF.DATE"))
419 {
420 dt_datetime_gdatetime_to_exif(exif_datetime, params->data->show_msec ? DT_DATETIME_LENGTH : DT_DATETIME_EXIF_LENGTH, datetime);
421 result = g_strdup(exif_datetime);
422 }
423 else if(_has_prefix(variable, "EXIF.ISO") || _has_prefix(variable, "EXIF_ISO"))
424 result = g_strdup_printf("%d", params->data->exif_iso);
425 else if(_has_prefix(variable, "NL") && g_strcmp0(params->jobcode, "infos") == 0)
426 result = g_strdup_printf("\n");
427 else if(_has_prefix(variable, "EXIF.EXPOSURE.BIAS") || _has_prefix(variable, "EXIF_EXPOSURE_BIAS"))
428 {
429 if(!isnan(params->data->exif_exposure_bias))
430 result = g_strdup_printf("%+.2f", params->data->exif_exposure_bias);
431 }
432 else if(_has_prefix(variable, "EXIF.EXPOSURE") || _has_prefix(variable, "EXIF_EXPOSURE"))
433 {
434 result = dt_util_format_exposure(params->data->exif_exposure);
435 // for job other than info (export) we strip the slash char
436 if(g_strcmp0(params->jobcode, "infos") != 0)
437 {
438 gchar *res = dt_util_str_replace(result, "/", "_");
439 dt_free(result);
440 result = res;
441 }
442 }
443 else if(_has_prefix(variable, "EXIF.APERTURE") || _has_prefix(variable, "EXIF_APERTURE"))
444 result = g_strdup_printf("%.1f", params->data->exif_aperture);
445 else if(_has_prefix(variable, "EXIF.FOCAL.LENGTH") || _has_prefix(variable, "EXIF_FOCAL_LENGTH"))
446 result = g_strdup_printf("%d", (int)params->data->exif_focal_length);
447 else if(_has_prefix(variable, "EXIF.FOCUS.DISTANCE") || _has_prefix(variable, "EXIF_FOCUS_DISTANCE"))
448 result = g_strdup_printf("%.2f", params->data->exif_focus_distance);
449 else if(_has_prefix(variable, "LONGITUDE") || _has_prefix(variable, "GPS.LONGITUDE"))
450 result = _variables_get_longitude(params);
451 else if(_has_prefix(variable, "LATITUDE") || _has_prefix(variable, "GPS.LATITUDE"))
452 result = _variables_get_latitude(params);
453 else if(_has_prefix(variable, "ELEVATION") || _has_prefix(variable, "GPS.ELEVATION"))
454 result = g_strdup_printf("%.2f", params->data->elevation);
455 // for watermark backward compatibility
456 else if(_has_prefix(variable, "GPS.LOCATION"))
457 {
458 gchar *parts[4] = { 0 };
459 int i = 0;
460 if(!isnan(params->data->latitude)) parts[i++] = _variables_get_latitude(params);
461 if(!isnan(params->data->longitude)) parts[i++] = _variables_get_longitude(params);
462 if(!isnan(params->data->elevation)) parts[i++] = g_strdup_printf("%.2f", params->data->elevation);
463 result = g_strjoinv(", ", parts);
464 for(int j = 0; j < i; j++)
465 dt_free(parts[j]);
466 }
467 else if(_has_prefix(variable, "EXIF.MAKER") || _has_prefix(variable, "MAKER"))
468 result = g_strdup(params->data->camera_maker);
469 else if(_has_prefix(variable, "EXIF.MODEL") || _has_prefix(variable, "MODEL"))
470 result = g_strdup(params->data->camera_alias);
471 else if(_has_prefix(variable, "EXIF.LENS") || _has_prefix(variable, "LENS"))
472 result = g_strdup(params->data->exif_lens);
473 else if(_has_prefix(variable, "ID") || _has_prefix(variable, "IMAGE.ID"))
474 result = g_strdup_printf("%d", params->imgid);
475 else if(_has_prefix(variable, "IMAGE.EXIF"))
476 {
477 gchar buffer[1024];
478 const dt_image_t *img = params->img ? (dt_image_t *)params->img
479 : dt_image_cache_get(darktable.image_cache, params->imgid, 'r');
480 dt_image_print_exif(img, buffer, sizeof(buffer));
482 result = g_strdup(buffer);
483 }
484 else if(_has_prefix(variable, "VERSION.NAME") || _has_prefix(variable, "VERSION_NAME"))
485 {
486 GList *res = dt_metadata_get(params->imgid, "Xmp.darktable.version_name", NULL);
487 if(!IS_NULL_PTR(res))
488 {
489 result = g_strdup((char *)res->data);
490 }
491 g_list_free_full(res, dt_free_gpointer);
492 res = NULL;
493 }
494 else if(_has_prefix(variable, "VERSION.IF_MULTI") || _has_prefix(variable, "VERSION_IF_MULTI"))
495 {
496 sqlite3_stmt *stmt;
497
498 // count duplicates
499 // clang-format off
501 "SELECT COUNT(1)"
502 " FROM images AS i1"
503 " WHERE EXISTS (SELECT 'y' FROM images AS i2"
504 " WHERE i2.id = ?1"
505 " AND i1.film_id = i2.film_id"
506 " AND i1.filename = i2.filename)",
507 -1, &stmt, NULL);
508 // clang-format on
509 DT_DEBUG_SQLITE3_BIND_INT(stmt, 1, params->imgid);
510
511 if(sqlite3_step(stmt) == SQLITE_ROW)
512 {
513 const int count = sqlite3_column_int(stmt, 0);
514 //only return data if more than one matching image
515 if(count > 1)
516 result = g_strdup_printf("%d", params->data->version);
517 }
518 sqlite3_finalize (stmt);
519 }
520 else if(_has_prefix(variable, "VERSION"))
521 result = g_strdup_printf("%d", params->data->version);
522 else if(_has_prefix(variable, "JOBCODE"))
523 result = g_strdup(params->jobcode);
524 else if(_has_prefix(variable, "ROLL.NAME") || _has_prefix(variable, "ROLL_NAME") // deprecated as unclear
525 || _has_prefix(variable, "FOLDER.NAME"))
526 {
527 if(params->filename)
528 {
529 gchar *dirname = g_path_get_dirname(params->filename);
530 result = g_path_get_basename(dirname);
531 dt_free(dirname);
532 }
533 }
534 else if(_has_prefix(variable, "FILE.DIRECTORY") || _has_prefix(variable, "FILE_DIRECTORY"))
535 {
536 // undocumented : backward compatibility
537 if(params->filename)
538 result = g_path_get_dirname(params->filename);
539 }
540 else if(_has_prefix(variable, "FILE.FOLDER") || _has_prefix(variable, "FILE_FOLDER"))
541 {
542 if(params->filename)
543 result = g_path_get_dirname(params->filename);
544 }
545 else if(_has_prefix(variable, "FILE.YEAR"))
546 {
547 if(params->data->file_datetime)
548 result = g_date_time_format(params->data->file_datetime, "%Y");
549 }
550 else if(_has_prefix(variable, "FILE.MONTH"))
551 {
552 if(params->data->file_datetime)
553 result = g_date_time_format(params->data->file_datetime, "%m");
554 }
555 else if(_has_prefix(variable, "FILE.DAY"))
556 {
557 if(params->data->file_datetime)
558 result = g_date_time_format(params->data->file_datetime, "%d");
559 }
560 else if(_has_prefix(variable, "FILE.HOUR"))
561 {
562 if(params->data->file_datetime)
563 result = g_date_time_format(params->data->file_datetime, "%H");
564 }
565 else if(_has_prefix(variable, "FILE.MINUTE"))
566 {
567 if(params->data->file_datetime)
568 result = g_date_time_format(params->data->file_datetime, "%M");
569 }
570 else if(_has_prefix(variable, "FILE.SECOND"))
571 {
572 if(params->data->file_datetime)
573 result = g_date_time_format(params->data->file_datetime, "%S");
574 }
575 // for watermark backward compatibility
576 else if(_has_prefix(variable, "IMAGE.FILENAME"))
577 {
578 if(params->filename)
579 result = g_strdup(params->filename);
580 }
581 else if(_has_prefix(variable, "FILE.NAME") || _has_prefix(variable, "FILE_NAME") || _has_prefix(variable, "IMAGE.BASENAME"))
582 {
583 if(params->filename)
584 {
585 result = g_path_get_basename(params->filename);
586 char *dot = g_strrstr(result, ".");
587 if(dot) *dot = '\0';
588 }
589 }
590 else if(_has_prefix(variable, "FILE.EXTENSION") || _has_prefix(variable, "FILE_EXTENSION"))
591 result = g_strdup(params->data->file_ext);
592 else if(_has_prefix(variable, "SEQUENCE"))
593 {
594 uint8_t nb_digit = 4;
595 if(g_ascii_isdigit(*variable[0]))
596 {
597 nb_digit = (uint8_t)*variable[0] & 0b1111;
598 (*variable) ++;
599 }
600 result = g_strdup_printf("%.*d", nb_digit, params->sequence);
601 }
602 else if(_has_prefix(variable, "USERNAME"))
603 result = g_strdup(g_get_user_name());
604 else if(_has_prefix(variable, "FOLDER.HOME") || _has_prefix(variable, "HOME_FOLDER") || _has_prefix(variable, "HOME"))
605 result = g_strdup(params->data->homedir);
606 else if(_has_prefix(variable, "FOLDER.PICTURES") || _has_prefix(variable, "PICTURES_FOLDER"))
607 result = g_strdup(params->data->pictures_folder);
608 else if(_has_prefix(variable, "FOLDER.DESKTOP") || _has_prefix(variable, "DESKTOP_FOLDER"))
609 result = g_strdup(g_get_user_special_dir(G_USER_DIRECTORY_DESKTOP)); // undocumented : backward compatibility
610 else if(_has_prefix(variable, "DESKTOP"))
611 result = g_strdup(g_get_user_special_dir(G_USER_DIRECTORY_DESKTOP));
612 else if(_has_prefix(variable, "STARS"))
613 result = g_strdup_printf("%d", params->data->stars);
614 else if(_has_prefix(variable, "RATING.ICONS") || _has_prefix(variable, "RATING_ICONS") || _has_prefix(variable, "Xmp.xmp.Rating"))
615 {
616 switch(params->data->stars)
617 {
618 case -1:
619 result = g_strdup("X");
620 break;
621 case 1:
622 result = g_strdup("\342\230\205");
623 break;
624 case 2:
625 result = g_strdup("\342\230\205\342\230\205");
626 break;
627 case 3:
628 result = g_strdup("\342\230\205\342\230\205\342\230\205");
629 break;
630 case 4:
631 result = g_strdup("\342\230\205\342\230\205\342\230\205\342\230\205");
632 break;
633 case 5:
634 result = g_strdup("\342\230\205\342\230\205\342\230\205\342\230\205\342\230\205");
635 break;
636 default:
637 result = g_strdup("");
638 break;
639 }
640 }
641 else if((_has_prefix(variable, "LABELS.ICONS") || _has_prefix(variable, "LABELS_ICONS") ||
642 _has_prefix(variable, "LABELS.COLORICONS") || _has_prefix(variable, "LABELS_COLORICONS"))
643 && g_strcmp0(params->jobcode, "infos") == 0)
644 {
645 escape = FALSE;
646 GList *res = dt_metadata_get(params->imgid, "Xmp.darktable.colorlabels", NULL);
647 for(GList *res_iter = res; res_iter; res_iter = g_list_next(res_iter))
648 {
649 const int dot_index = GPOINTER_TO_INT(res_iter->data);
650 const GdkRGBA c = darktable.bauhaus->colorlabels[dot_index];
651 result = dt_util_dstrcat(result,
652 "<span foreground='#%02x%02x%02x'>\342\254\244 </span>",
653 (guint)(c.red*255), (guint)(c.green*255), (guint)(c.blue*255));
654 }
655 g_list_free(res);
656 res = NULL;
657 }
658 else if(_has_prefix(variable, "LABELS"))
659 {
660 // TODO: currently we concatenate all the color labels with a ',' as a separator. Maybe it's better to
661 // only use the first/last label?
662 GList *res = dt_metadata_get(params->imgid, "Xmp.darktable.colorlabels", NULL);
663 if(!IS_NULL_PTR(res))
664 {
665 GList *labels = NULL;
666 for(GList *res_iter = res; res_iter; res_iter = g_list_next(res_iter))
667 {
668 labels = g_list_prepend(labels, (char *)(_(dt_colorlabels_to_string(GPOINTER_TO_INT(res_iter->data)))));
669 }
670 labels = g_list_reverse(labels); // list was built in reverse order, so un-reverse it
671 result = dt_util_glist_to_str(",", labels);
672 g_list_free(labels);
673 labels = NULL;
674 }
675 g_list_free(res);
676 res = NULL;
677 }
678 else if(_has_prefix(variable, "TITLE") || _has_prefix(variable, "Xmp.dc.title"))
679 {
680 GList *res = dt_metadata_get(params->imgid, "Xmp.dc.title", NULL);
681 if(!IS_NULL_PTR(res))
682 {
683 result = g_strdup((char *)res->data);
684 }
685 g_list_free_full(res, dt_free_gpointer);
686 res = NULL;
687 }
688 else if(_has_prefix(variable, "DESCRIPTION") || _has_prefix(variable, "Xmp.dc.description"))
689 {
690 GList *res = dt_metadata_get(params->imgid, "Xmp.dc.description", NULL);
691 if(!IS_NULL_PTR(res))
692 {
693 result = g_strdup((char *)res->data);
694 }
695 g_list_free_full(res, dt_free_gpointer);
696 res = NULL;
697 }
698 else if(_has_prefix(variable, "CREATOR") || _has_prefix(variable, "Xmp.dc.creator"))
699 {
700 GList *res = dt_metadata_get(params->imgid, "Xmp.dc.creator", NULL);
701 if(!IS_NULL_PTR(res))
702 {
703 result = g_strdup((char *)res->data);
704 }
705 g_list_free_full(res, dt_free_gpointer);
706 res = NULL;
707 }
708 else if(_has_prefix(variable, "PUBLISHER") || _has_prefix(variable, "Xmp.dc.publisher"))
709 {
710 GList *res = dt_metadata_get(params->imgid, "Xmp.dc.publisher", NULL);
711 if(!IS_NULL_PTR(res))
712 {
713 result = g_strdup((char *)res->data);
714 }
715 g_list_free_full(res, dt_free_gpointer);
716 res = NULL;
717 }
718 else if(_has_prefix(variable, "RIGHTS") || _has_prefix(variable, "Xmp.dc.rights"))
719 {
720 GList *res = dt_metadata_get(params->imgid, "Xmp.dc.rights", NULL);
721 if(!IS_NULL_PTR(res))
722 {
723 result = g_strdup((char *)res->data);
724 }
725 g_list_free_full(res, dt_free_gpointer);
726 res = NULL;
727 }
728 else if(_has_prefix(variable, "OPENCL.ACTIVATED") || _has_prefix(variable, "OPENCL_ACTIVATED"))
729 {
731 result = g_strdup(_("yes"));
732 else
733 result = g_strdup(_("no"));
734 }
735 else if(_has_prefix(variable, "WIDTH.MAX") || _has_prefix(variable, "MAX_WIDTH"))
736 result = g_strdup_printf("%d", params->data->max_width);
737 else if(_has_prefix(variable, "WIDTH.SENSOR") || _has_prefix(variable, "SENSOR_WIDTH"))
738 result = g_strdup_printf("%d", params->data->sensor_width);
739 else if(_has_prefix(variable, "WIDTH.RAW") || _has_prefix(variable, "RAW_WIDTH"))
740 result = g_strdup_printf("%d", params->data->raw_width);
741 else if(_has_prefix(variable, "WIDTH.CROP"))
742 result = g_strdup_printf("%d", params->data->crop_width);
743 else if(_has_prefix(variable, "WIDTH.EXPORT") || _has_prefix(variable, "EXPORT_WIDTH"))
744 result = g_strdup_printf("%d", params->data->export_width);
745 else if(_has_prefix(variable, "HEIGHT.MAX") || _has_prefix(variable, "MAX_HEIGHT"))
746 result = g_strdup_printf("%d", params->data->max_height);
747 else if(_has_prefix(variable, "HEIGHT.SENSOR") || _has_prefix(variable, "SENSOR_HEIGHT"))
748 result = g_strdup_printf("%d", params->data->sensor_height);
749 else if(_has_prefix(variable, "HEIGHT.RAW") || _has_prefix(variable, "RAW_HEIGHT"))
750 result = g_strdup_printf("%d", params->data->raw_height);
751 else if(_has_prefix(variable, "HEIGHT.CROP"))
752 result = g_strdup_printf("%d", params->data->crop_height);
753 else if(_has_prefix(variable, "HEIGHT.EXPORT") || _has_prefix(variable, "EXPORT_HEIGHT"))
754 result = g_strdup_printf("%d", params->data->export_height);
755 else if (_has_prefix(variable, "CATEGORY"))
756 {
757 // CATEGORY should be followed by n [0,9] and "(category)". category can contain 0 or more '|'
758 if (g_ascii_isdigit(*variable[0]))
759 {
760 const uint8_t level = (uint8_t)*variable[0] & 0b1111;
761 (*variable) ++;
762 if (*variable[0] == '(')
763 {
764 char *category = g_strdup(*variable + 1);
765 char *end = g_strstr_len(category, -1, ")");
766 if (end)
767 {
768 end[0] = '|';
769 end[1] = '\0';
770 (*variable) += strlen(category) + 1;
771 char *tag = dt_tag_get_subtags(params->imgid, category, (int)level);
772 if (tag)
773 {
774 result = g_strdup(tag);
775 dt_free(tag);
776 }
777 }
778 dt_free(category);
779 }
780 }
781 }
782 else if(_has_prefix(variable, "TAGS") || _has_prefix(variable, "IMAGE.TAGS"))
783 {
784 GList *tags_list = dt_tag_get_list_export(params->imgid, params->data->tags_flags);
785 char *tags = dt_util_glist_to_str(", ", tags_list);
786 g_list_free_full(tags_list, dt_free_gpointer);
787 tags_list = NULL;
788 result = g_strdup(tags);
789 dt_free(tags);
790 }
791 else if(_has_prefix(variable, "SIDECAR_TXT") && g_strcmp0(params->jobcode, "infos") == 0
792 && (params->data->flags & DT_IMAGE_HAS_TXT))
793 {
794 char *path = dt_image_get_text_path(params->imgid);
795 if(path)
796 {
797 gchar *txt = NULL;
798 if(g_file_get_contents(path, &txt, NULL, NULL))
799 {
800 result = g_strdup_printf("\n%s", txt);
801 }
802 dt_free(txt);
803 dt_free(path);
804 }
805 }
806 else if(_has_prefix(variable, "DARKTABLE.VERSION") || _has_prefix(variable, "DARKTABLE_VERSION")
807 || _has_prefix(variable, "ANSEL.VERSION"))
808 result = g_strdup(darktable_package_version);
809 else if(_has_prefix(variable, "DARKTABLE.NAME") || _has_prefix(variable, "DARKTABLE_NAME")
810 || _has_prefix(variable, "ANSEL.NAME"))
811 result = g_strdup(PACKAGE_NAME);
812 else
813 {
814 // go past what looks like an invalid variable. we only expect to see [a-zA-Z]* in a variable name.
815 while(g_ascii_isalpha(**variable)) (*variable)++;
816 }
817 if(!result) result = g_strdup("");
818
819 if(params->escape_markup && escape)
820 {
821 gchar *e_res = g_markup_escape_text(result, -1);
822 dt_free(result);
823 return e_res;
824 }
825 return result;
826}
827
828// bash style variable manipulation. all patterns are just simple string comparisons!
829// See here for bash examples and documentation:
830// http://www.tldp.org/LDP/abs/html/parameter-substitution.html
831// https://www.gnu.org/software/bash/manual/html_node/Shell-Parameter-Expansion.html
832// the descriptions in the comments are referring to the bash behaviour, dt doesn't do it 100% like that!
833static char *_variable_get_value(dt_variables_params_t *params, char **variable)
834{
835 // invariant: the variable starts with "$(" which we can skip
836 (*variable) += 2;
837
838 // first get the value of the variable
839 char *base_value = _get_base_value(params, variable); // this is never going to be NULL!
840 const size_t base_value_length = strlen(base_value);
841
842 // ... and now see if we have to change it
843 const char operation = **variable;
844 if(operation != '\0' && operation != ')') (*variable)++;
845 switch(operation)
846 {
847 case '-':
848 /*
849 $(parameter-default)
850 If parameter not set, use default.
851 */
852 {
853 char *replacement = _expand_source(params, variable, ')');
854 if(*base_value == '\0')
855 {
856 dt_free(base_value);
857 base_value = replacement;
858 }
859 else
860 dt_free(replacement);
861 }
862 break;
863 case '+':
864 /*
865 $(parameter+alt_value)
866 If parameter set, use alt_value, else use null string.
867 */
868 {
869 char *replacement = _expand_source(params, variable, ')');
870 if(*base_value != '\0')
871 {
872 dt_free(base_value);
873 base_value = replacement;
874 }
875 else
876 dt_free(replacement);
877 }
878 break;
879 case ':':
880 /*
881 $(var:offset)
882 Variable var expanded, starting from offset.
883
884 $(var:offset:length)
885 Expansion to a max of length characters of variable var, from offset.
886
887 If offset evaluates to a number less than zero, the value is used as an offset in characters from the
888 end of the value of parameter. If length evaluates to a number less than zero, it is interpreted as an
889 offset in characters from the end of the value of parameter rather than a number of characters, and the
890 expansion is the characters between offset and that result.
891 */
892 {
893 const glong base_value_utf8_length = g_utf8_strlen(base_value, -1);
894 const glong offset = strtol(*variable, variable, 10);
895
896 // find where to start
897 char *start; // from where to copy ...
898 if(offset >= 0)
899 start = g_utf8_offset_to_pointer(base_value, MIN(offset, base_value_utf8_length));
900 else
901 start = g_utf8_offset_to_pointer(base_value + base_value_length, MAX(offset, -base_value_utf8_length));
902
903 // now find the end if there is a length provided
904 char *end = base_value + base_value_length; // ... and until where
905 if(start && **variable == ':')
906 {
907 (*variable)++;
908 const size_t start_utf8_length = g_utf8_strlen(start, -1);
909 const int length = strtol(*variable, variable, 10);
910 if(length >= 0)
911 end = g_utf8_offset_to_pointer(start, MIN(length, start_utf8_length));
912 else
913 end = g_utf8_offset_to_pointer(base_value + base_value_length, MAX(length, -start_utf8_length));
914 }
915
916 char *_base_value = g_strndup(start, end - start);
917 dt_free(base_value);
918 base_value = _base_value;
919 }
920 break;
921 case '#':
922 /*
923 $(var#Pattern)
924 Remove from $var the shortest part of $Pattern that matches the front end of $var.
925 */
926 {
927 char *pattern = _expand_source(params, variable, ')');
928 const size_t pattern_length = strlen(pattern);
929 if(!strncmp(base_value, pattern, pattern_length))
930 {
931 char *_base_value = g_strdup(base_value + pattern_length);
932 dt_free(base_value);
933 base_value = _base_value;
934 }
935 dt_free(pattern);
936 }
937 break;
938 case '%':
939 /*
940 $(var%Pattern)
941 Remove from $var the shortest part of $Pattern that matches the back end of $var.
942 */
943 {
944 char *pattern = _expand_source(params, variable, ')');
945 const size_t pattern_length = strlen(pattern);
946 if(!strncmp(base_value + base_value_length - pattern_length, pattern, pattern_length))
947 base_value[base_value_length - pattern_length] = '\0';
948 dt_free(pattern);
949 }
950 break;
951 case '/':
952 /*
953 replacement. the following cases are possible:
954
955 $(var/Pattern/Replacement)
956 First match of Pattern, within var replaced with Replacement.
957 If Replacement is omitted, then the first match of Pattern is replaced by nothing, that is, deleted.
958
959 $(var//Pattern/Replacement)
960 Global replacement. All matches of Pattern, within var replaced with Replacement.
961 As above, if Replacement is omitted, then all occurrences of Pattern are replaced by nothing, that is, deleted.
962
963 $(var/#Pattern/Replacement)
964 If prefix of var matches Pattern, then substitute Replacement for Pattern.
965
966 $(var/%Pattern/Replacement)
967 If suffix of var matches Pattern, then substitute Replacement for Pattern.
968 */
969 {
970 const char mode = **variable;
971
972 if(mode == '/' || mode == '#' || mode == '%') (*variable)++;
973 char *pattern = _expand_source(params, variable, '/');
974 const size_t pattern_length = strlen(pattern);
975 (*variable)++;
976 char *replacement = _expand_source(params, variable, ')');
977 const size_t replacement_length = strlen(replacement);
978
979 switch(mode)
980 {
981 case '/':
982 {
983 // TODO: write a dt_util_str_replace that can deal with pattern_length ^^
984 char *p = g_strndup(pattern, pattern_length);
985 char *_base_value = dt_util_str_replace(base_value, p, replacement);
986 dt_free(p);
987 dt_free(base_value);
988 base_value = _base_value;
989 break;
990 }
991 case '#':
992 {
993 if(!strncmp(base_value, pattern, pattern_length))
994 {
995 char *_base_value = g_malloc(base_value_length - pattern_length + replacement_length + 1);
996 char *end = g_stpcpy(_base_value, replacement);
997 g_stpcpy(end, base_value + pattern_length);
998 dt_free(base_value);
999 base_value = _base_value;
1000 }
1001 break;
1002 }
1003 case '%':
1004 {
1005 if(!strncmp(base_value + base_value_length - pattern_length, pattern, pattern_length))
1006 {
1007 char *_base_value = g_malloc(base_value_length - pattern_length + replacement_length + 1);
1008 base_value[base_value_length - pattern_length] = '\0';
1009 char *end = g_stpcpy(_base_value, base_value);
1010 g_stpcpy(end, replacement);
1011 dt_free(base_value);
1012 base_value = _base_value;
1013 }
1014 break;
1015 }
1016 default:
1017 {
1018 // TODO: is there a strstr_len that limits the length of pattern?
1019 char *p = g_strndup(pattern, pattern_length);
1020 gchar *found = g_strstr_len(base_value, -1, p);
1021 dt_free(p);
1022 if(found)
1023 {
1024 *found = '\0';
1025 char *_base_value = g_malloc(base_value_length - pattern_length + replacement_length + 1);
1026 char *end = g_stpcpy(_base_value, base_value);
1027 end = g_stpcpy(end, replacement);
1028 g_stpcpy(end, found + pattern_length);
1029 dt_free(base_value);
1030 base_value = _base_value;
1031 }
1032 break;
1033 }
1034 }
1035 dt_free(pattern);
1036 dt_free(replacement);
1037 }
1038 break;
1039 case '^':
1040 case ',':
1041 /*
1042 changing the case:
1043
1044 $(parameter^pattern)
1045 $(parameter^^pattern)
1046 $(parameter,pattern)
1047 $(parameter,,pattern)
1048 This expansion modifies the case of alphabetic characters in parameter.
1049 The ‘^’ operator converts lowercase letters to uppercase;
1050 the ‘,’ operator converts uppercase letters to lowercase.
1051 The ‘^^’ and ‘,,’ expansions convert each character in the expanded value;
1052 the ‘^’ and ‘,’ expansions convert only the first character in the expanded value.
1053 */
1054 {
1055 const char mode = **variable;
1056 char *_base_value = NULL;
1057 if(operation == '^' && mode == '^')
1058 {
1059 _base_value = g_utf8_strup (base_value, -1);
1060 (*variable)++;
1061 }
1062 else if(operation == ',' && mode == ',')
1063 {
1064 _base_value = g_utf8_strdown(base_value, -1);
1065 (*variable)++;
1066 }
1067 else
1068 {
1069 gunichar changed = g_utf8_get_char(base_value);
1070 changed = operation == '^' ? g_unichar_toupper(changed) : g_unichar_tolower(changed);
1071 int utf8_length = g_unichar_to_utf8(changed, NULL);
1072 char *next = g_utf8_next_char(base_value);
1073 _base_value = g_malloc0(base_value_length - (next - base_value) + utf8_length + 1);
1074 g_unichar_to_utf8(changed, _base_value);
1075 g_stpcpy(_base_value + utf8_length, next);
1076 }
1077 dt_free(base_value);
1078 base_value = _base_value;
1079 }
1080 break;
1081 }
1082
1083 if(**variable == ')')
1084 (*variable)++;
1085 else
1086 {
1087 // error case
1088 dt_free(base_value);
1089 }
1090
1091 return base_value;
1092}
1093
1094static void _grow_buffer(char **result, char **result_iter, size_t *result_length, size_t extra_space)
1095{
1096 const size_t used_length = *result_iter - *result;
1097 if(used_length + extra_space > *result_length)
1098 {
1099 *result_length = used_length + extra_space;
1100 *result = g_realloc(*result, *result_length + 1);
1101 *result_iter = *result + used_length;
1102 }
1103}
1104
1105static char *_expand_source(dt_variables_params_t *params, char **source, char extra_stop)
1106{
1107 char *result = g_strdup("");
1108 if(IS_NULL_PTR(*source)) return result;
1109 char *result_iter = result;
1110 size_t result_length = 0;
1111 char *source_iter = *source;
1112 const size_t source_length = strlen(*source);
1113
1114 while(*source_iter && *source_iter != extra_stop)
1115 {
1116 // find start of variable, copying over everything till then
1117 while(*source_iter && *source_iter != extra_stop)
1118 {
1119 char c = *source_iter;
1120 if(c == '$' && source_iter[1] == '(')
1121 break;
1122
1123 if(result_iter - result >= result_length)
1124 _grow_buffer(&result, &result_iter, &result_length, source_length - (source_iter - *source));
1125 *result_iter = c;
1126 result_iter++;
1127 source_iter++;
1128
1129 }
1130
1131 // it seems we have a variable here
1132 if(*source_iter == '$')
1133 {
1134 char *old_source_iter = source_iter;
1135 char *replacement = _variable_get_value(params, &source_iter);
1136 if(replacement)
1137 {
1138 const size_t replacement_length = strlen(replacement);
1139 _grow_buffer(&result, &result_iter, &result_length, replacement_length);
1140 memcpy(result_iter, replacement, replacement_length);
1141 result_iter += replacement_length;
1142 dt_free(replacement);
1143 }
1144 else
1145 {
1146 // the error case of missing closing ')' -- try to recover
1147 source_iter = old_source_iter;
1148 _grow_buffer(&result, &result_iter, &result_length, source_length - (source_iter - *source));
1149 *result_iter++ = *source_iter++;
1150 }
1151 }
1152 }
1153
1154 *result_iter = '\0';
1155 *source = source_iter;
1156
1157 return result;
1158}
1159
1160char *dt_variables_expand(dt_variables_params_t *params, gchar *source, gboolean iterate)
1161{
1162 _init_expansion(params, iterate);
1163
1164 char *result = _expand_source(params, &source, '\0');
1165
1166 _cleanup_expansion(params);
1167 return result;
1168}
1169
1171{
1172 *params = g_malloc0(sizeof(dt_variables_params_t));
1173 (*params)->data = g_malloc0(sizeof(dt_variables_data_t));
1174 (*params)->data->time = g_date_time_new_now_local();
1175 (*params)->data->file_datetime = NULL;
1176 (*params)->data->exif_time[0] = 0;
1177 (*params)->sequence = -1;
1178 (*params)->img = NULL;
1179}
1180
1182{
1183 if(params->data->time)
1184 g_date_time_unref(params->data->time);
1185
1186 if(params->data->file_datetime)
1187 g_date_time_unref(params->data->file_datetime);
1188
1189 dt_free(params->data);
1190 dt_free(params);
1191}
1192
1193void dt_variables_set_datetime(dt_variables_params_t *params, GDateTime *datetime)
1194{
1195 if(params->data->time) g_date_time_unref(params->data->time);
1196 params->data->time = g_date_time_ref(datetime);
1197}
1198
1199void dt_variables_set_max_width_height(dt_variables_params_t *params, int max_width, int max_height)
1200{
1201 params->data->max_width = max_width;
1202 params->data->max_height = max_height;
1203}
1204
1206{
1207 params->data->sequence = 0;
1208}
1209
1211{
1212 params->data->tags_flags = flags;
1213}
1214
1215
1216
1217// clang-format off
1218// modelines: These editor modelines have been set for all relevant files by tools/update_modelines.py
1219// vim: shiftwidth=2 expandtab tabstop=2 cindent
1220// kate: tab-indents: off; indent-width 2; replace-tabs on; indent-mode cstyle; remove-trailing-spaces modified;
1221// clang-format on
#define TRUE
Definition ashift_lsd.c:162
#define FALSE
Definition ashift_lsd.c:158
static const dt_aligned_pixel_simd_t const dt_adaptation_t const float p
const char * dt_colorlabels_to_string(int label)
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)
GList * dt_metadata_get(const int id, const char *key, uint32_t *count)
#define PACKAGE_NAME
const char darktable_package_version[]
int dt_conf_get_bool(const char *name)
darktable_t darktable
Definition darktable.c:181
#define UNKNOWN_IMAGE
Definition darktable.h:182
static void dt_free_gpointer(gpointer ptr)
Definition darktable.h:463
#define dt_free(ptr)
Definition darktable.h:456
#define PATH_MAX
Definition darktable.h:1062
#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 darktable.h:281
sqlite3 * dt_database_get(const dt_database_t *db)
Definition database.c:3646
gboolean dt_datetime_gdatetime_to_exif(char *exif, const size_t exif_size, GDateTime *gdt)
Definition datetime.c:260
GDateTime * dt_datetime_img_to_gdatetime(const dt_image_t *img, const GTimeZone *tz)
Definition datetime.c:281
#define DT_DATETIME_LENGTH
Definition datetime.h:37
#define DT_DATETIME_EXIF_LENGTH
Definition datetime.h:38
#define DT_DEBUG_SQLITE3_PREPARE_V2(a, b, c, d, e)
Definition debug.h:107
#define DT_DEBUG_SQLITE3_BIND_INT(a, b, c)
Definition debug.h:115
gchar * dt_loc_get_home_dir(const gchar *user)
@ DT_IMAGE_REJECTED
Definition image.h:100
@ DT_IMAGE_HAS_TXT
Definition image.h:123
void dt_image_cache_read_release(dt_image_cache_t *cache, const dt_image_t *img)
dt_image_t * dt_image_cache_get(dt_image_cache_t *cache, const int32_t imgid, char mode)
dt_mipmap_buffer_dsc_flags flags
Definition mipmap_cache.c:4
int dt_opencl_is_enabled(void)
Definition opencl.c:2737
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 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)
gboolean dt_get_user_pictures_dir(const gchar *homedir, gchar *picdir, size_t picdir_size)
Gets the path to the current OS pictures directory.
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)
void dt_variables_set_datetime(dt_variables_params_t *params, GDateTime *datetime)
static char * _variables_get_longitude(dt_variables_params_t *params)
void dt_variables_reset_sequence(dt_variables_params_t *params)
GTimeZone * utc_tz
Definition darktable.h:832
const struct dt_database_t * db
Definition darktable.h:779
struct dt_bauhaus_t * bauhaus
Definition darktable.h:778
struct dt_image_cache_t * image_cache
Definition darktable.h:777
GDateTime * origin_gdt
Definition darktable.h:833
GdkRGBA colorlabels[DT_COLORLABELS_LAST]
Definition bauhaus.h:284
double latitude
Definition image.h:275
double elevation
Definition image.h:275
double longitude
Definition image.h:275
float exif_exposure
Definition image.h:285
int32_t height
Definition image.h:315
GTimeSpan export_timestamp
Definition image.h:333
float exif_focus_distance
Definition image.h:290
GTimeSpan import_timestamp
Definition image.h:333
float exif_exposure_bias
Definition image.h:286
float exif_iso
Definition image.h:288
char camera_maker[64]
Definition image.h:297
float exif_aperture
Definition image.h:287
int32_t version
Definition image.h:319
int32_t crop_height
Definition image.h:316
dt_image_geoloc_t geoloc
Definition image.h:347
int32_t flags
Definition image.h:319
int32_t width
Definition image.h:315
float exif_focal_length
Definition image.h:289
GTimeSpan change_timestamp
Definition image.h:333
char camera_alias[64]
Definition image.h:299
char exif_lens[128]
Definition image.h:294
GTimeSpan print_timestamp
Definition image.h:333
int32_t p_height
Definition image.h:315
int32_t p_width
Definition image.h:315
int32_t crop_width
Definition image.h:316
char exif_time[DT_DATETIME_LENGTH]
GList * dt_tag_get_list_export(int32_t imgid, int32_t flags)
Definition tags.c:963
char * dt_tag_get_subtags(const int32_t imgid, const char *category, const int level)
Definition tags.c:1821
#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:136
gchar * dt_util_longitude_str(float longitude)
Definition utility.c:569
GDateTime * dt_util_get_file_datetime(const char *const path)
Definition utility.c:789
char * dt_util_format_exposure(const float exposuretime)
Definition utility.c:865
gchar * dt_util_latitude_str(float latitude)
Definition utility.c:551
gchar * dt_util_glist_to_str(const gchar *separator, GList *items)
Definition utility.c:166
gchar * dt_util_dstrcat(gchar *str, const gchar *format,...)
Definition utility.c:95