![]() |
Ansel 0.0
A darktable fork - bloat + design vision
|
Corrected against
fa8e8b86faon 2026-09-29. The audit before that found 4 claim(s) in this file wrong of the tree and 7 stale. Mostly right; the option table was presented as deviations when most rows are upstream defaults, and the NLS workaround needed the function-vs-macro warning its neighbour already carries. Re-measure before acting on a claim older than the code you are changing, and re-date this line when you do.
Ansel builds its own Exiv2, from the src/external/exiv2 submodule, pinned at v0.27.7 and linked statically into lib_ansel. This is the default on every platform.
-DUSE_BUNDLED_EXIV2=OFF links whatever the system provides instead, for packagers who need it. It is supported, but it is not the tested configuration, and it comes with one hard requirement (ISOBMFF, below) that several distributions do not meet.
Exiv2 is not an ordinary dependency. Ansel does not merely link it, it inherits its behaviour:
EXIV2_ENABLE_BMFF is CR3, AVIF and HEIF. EXIV2_ENABLE_WIN_UNICODE is whether a Windows path may contain an accent.Leaving both of those to three packagers produced three different products, and that is what issue #474 turned out to be:
.exe shipped MSYS2's Exiv2 0.28.8. Every EXIF read, thumbnail extraction and XMP sidecar operation failed, silently, for any image below a folder with a non-ASCII character in its name.ubuntu-22.04's 0.27.5 — an end-of-life branch, pinned by the choice of CI runner image rather than by any decision, and with whatever options Ubuntu chose. Whether the AppImage could read CR3 metadata was, in effect, Ubuntu's call.Upstream darktable has the same problem and answers it the same way: their Windows nightly builds Exiv2 v0.27.7 from source with -DEXIV2_ENABLE_WIN_UNICODE=ON, while their Linux AppImage builds v0.28.8. Their one attempt at a code-level fix (PR #15899, "switch Windows to UTF-8
locale entirely", 34 files) was auto-closed unmerged after 300 days.
In src/external/CMakeLists.txt. Most of the table is Exiv2 0.27.7's own default and is listed to say we checked, not to say we changed it — src/external/exiv2/CMakeLists.txt:18-36 already ships XMP ON, PNG ON, NLS OFF, LENSDATA ON, VIDEO/WEBREADY/CURL/SSH OFF and UNIT_TESTS/FUZZ_TESTS/DOC OFF. The genuinely non-default rows are BUILD_SHARED_LIBS=OFF, EXIV2_ENABLE_BMFF=ON, EXIV2_ENABLE_WIN_UNICODE=ON and EXIV2_BUILD_SAMPLES/_EXIV2_COMMAND=OFF. Setting a default explicitly is still worth doing here — a submodule bump can change one — but do not read the table as a list of deviations.
| Option | Value | Why |
|---|---|---|
BUILD_SHARED_LIBS | OFF | Static: nothing else in the process can supply a second libexiv2, and there is no extra shared object to ship. |
EXIV2_ENABLE_BMFF | ON | CR3, AVIF, HEIF. Required — see below. Exiv2 0.27's default is OFF. |
EXIV2_ENABLE_XMP | ON | The whole XMP sidecar layer. Pulls in expat. |
EXIV2_ENABLE_PNG | ON | PNG metadata. Pulls in zlib. |
EXIV2_ENABLE_LENSDATA | ON | Nikon lens tables. |
EXIV2_ENABLE_WIN_UNICODE | ON on Windows | Defines EXV_UNICODE_PATH, the wide-path API. Without it, non-ASCII paths do not open. |
EXIV2_ENABLE_NLS | OFF | See Native language support below. |
EXIV2_ENABLE_VIDEO, _WEBREADY, _CURL, _SSH | OFF | Ansel reads metadata from files, never over the network. Keeps libcurl and libssh out of Exiv2's link line. |
EXIV2_BUILD_SAMPLES, _EXIV2_COMMAND, _UNIT_TESTS, _FUZZ_TESTS, _DOC | OFF | We want the library, not the distribution. |
Build dependencies this leaves us with: expat (the bundled XMP SDK) and zlib (PNG). Iconv is used when present. The packaging/install-deps-*.sh scripts install expat explicitly (install-deps-suse.sh:70,164, install-deps-macos.sh:36, packaging/nix/default.nix:13) and not zlib, which they leave to whatever already pulls it in — every one of these platforms has it through another dependency. That works, and it is worth knowing it is implicit rather than stated, because a platform that ever lacks it fails at link time with no clue pointing here.
src/CMakeLists.txt probes the EXV_ENABLE_BMFF symbol in the Exiv2 we are actually linking and fails the configure if it is absent. There is no HAVE_LIBEXIV2_WITH_ISOBMFF macro any more, and no degraded build: cr3 is unconditionally in DT_SUPPORTED_EXTENSIONS, and dt_exif_init() calls Exiv2::enableBMFF() unconditionally on 0.27.x.
The reason is what a degraded build looks like from the user's chair. Some distributions — Fedora among them — still build Exiv2 with EXIV2_ENABLE_BMFF=OFF over long-settled patent worries. Canon CR3 is among the most common file types in Ansel's telemetry. An Ansel linked against such a build starts perfectly well and then cannot open the user's raws, and nobody reading that screen concludes that their distribution disabled a flag in a metadata library: they file a bug against Ansel. A configure-time error costs a packager five minutes. The alternative costs every one of their users their raw files, and costs us the bug report.
If you hit that error with -DUSE_BUNDLED_EXIV2=OFF, the fix is either to rebuild Exiv2 with -DEXIV2_ENABLE_BMFF=ON or — far easier — to drop back to the bundled build.
Every path inside Ansel is UTF-8: that is what GLib hands us, what the database stores, and what the film-roll scanner produces. On Windows the narrow CRT reads a const char * path in the process ANSI code page instead — CP-1252, CP-850, whatever the machine is set to — so a path holding a single byte above 0x7F simply does not resolve.
Ansel's answer everywhere is to widen at the library boundary: libraw_open_wfile() in imageio/imageio_libraw.c, TIFFOpenW() in imageio/imageio_tiff.c, g_fopen() (which is _wfopen() underneath) in the XMP sidecar writer. Exiv2 is the same case, and WIDEN() in metadata/exif_internal.h is where it happens.
Three things there are deliberate:
WIDEN() lives in one header.** It used to be defined identically in both metadata/exif.cc and common/xmp_sidecar.cc. Two copies of a rule this load-bearing is how they drift.#else #define WIDEN(s) (s) fallback meant everything still compiled and Windows users lost their EXIF at runtime. That is how #474 shipped twice. There is now an #error, with ANSEL_ALLOW_NARROW_EXIV2_PATHS as the documented way to override it deliberately.Exiv2::readFile() call sites in common/xmp_sidecar.cc are now _read_xmp_packet(), which reads through dt_read_file() → g_fopen(). They are small files, Ansel already has a reader that handles Windows paths correctly, and this way they do not depend on a capability Exiv2 may not have.tools/check_it_runs.sh and the Windows job in .github/workflows/ci.yml both export from a path named Épreuve — тест and fail on Failed to open the data source. Every round of #474 — 2023, 2025, twice in 2026 — was found months later by a user with a screenshot. That check is what would have caught each one the day it landed; do not remove it.
EXIV2_ENABLE_NLS=OFF, and it is not simply a matter of flipping the flag.
Exiv2's po/CMakeLists.txt calls CMake's GETTEXT_CREATE_TRANSLATIONS(), which runs msgmerge --update against the .po files in the source tree. In a submodule that means every build leaves src/external/exiv2 dirty.
What it costs us: Exiv2::Metadatum::print() returns some tag values from translated tables ("Manual", "Auto", …), and those show in the metadata panel. Tag names are unaffected — dt_exif_set_exiv2_taglist() reads them straight from Exiv2::ExifTags::groupList() and never goes through gettext.
To get it back, shadow GETTEXT_CREATE_TRANSLATIONS() with a version that runs msgfmt and skips the merge, before add_subdirectory(exiv2) (src/external/CMakeLists.txt:318). That is about fifteen lines and it has to keep the .mo files installing into ${CMAKE_INSTALL_LOCALEDIR}, because Exiv2 resolves EXV_LOCALEDIR relative to the running binary — which, statically linked, is ansel itself.
Write that shadow as a function, never a macro, for the reason the install() shadow twenty lines above it already documents at length (:296-310): overriding a command this way is global for the rest of the configure, and in a macro ${ARGV} is textual substitution, so every ${...} in a string argument is expanded a second time and every backslash escape eaten once more. That is not hypothetical — it shipped. The macro form reduced the Windows installer's install(CODE) DLL-collection block to a script with empty variables and an unescaped regex; it copied nothing, the .exe went from 144 MB to 118 MB, and users got an application missing libcurl, libheif, libopenexr and exchndl.dll (fixed in ed17d7db7d).
Note also that dt_strlcpy_to_utf8() in metadata/exif.cc runs print()'s output through g_locale_to_utf8(), which is inherited from darktable and is wrong for anything gettext returns (gettext gives UTF-8; the locale on Windows generally is not). Turning NLS on without looking at that first would trade one mojibake for another.
Do not, until the wide-path API is back in a release. The pin is at 0.27.7 for exactly one reason: Exiv2 commit 7933ff40, shipped in 0.28.0 (May 2023), deleted the std::wstring overloads and the EXIV2_ENABLE_WIN_UNICODE option.
The state of that, as of this writing:
main in JanuaryFileIo(std::wstring), ImageFactory::open(std::wstring), createIo() and setPath() — and makes the narrow std::string constructor convert from UTF-8 and open with _wfopen(), so even code that does not widen would work.0.28.x maintenance branch, and main (the v1.0 track) has not produced a release since 0.28.0.So the condition for the bump is: **#3117, or an equivalent, present in a tagged Exiv2 release.** Check include/exiv2/basicio.hpp in the tag for FileIo(const std::wstring&).
When that day comes:
.gitmodules if the branch changes.EXIV2_ENABLE_WIN_UNICODE no longer exists. WIDEN() in metadata/exif_internal.h keys off EXV_UNICODE_PATH, which will not be defined either — give it whatever the new release uses to advertise the capability, or define ANSEL_EXIV2_WIDE_PATH from CMake once you have verified the release actually has it. Do not just let the #else branch take over; that is precisely the silent failure #474 was.EXIV2_ENABLE_INIH (on by default), which wants the inih library. Either add the dependency to packaging/install-deps-*.sh and the flatpak manifest, or set it OFF. The flatpak manifest used to carry an inih module for this; it was removed with the Exiv2 module and would need restoring.EXIV2_ENABLE_BMFF defaults to ON in 0.28, so the ISOBMFF probe should keep passing — but it is a probe, so it will tell you.metadata/exif.cc and common/xmp_sidecar.cc carry EXIV2_TEST_VERSION(0,28,0) guards for AnyError/Error, toLong/toInt64, XmpParser::initialize() and enableBMFF().ls_vendor_resolve() (src/iop/lens.c), which takes lens identity away from Exiv2 altogether.Embedding Exiv2 0.27.7 as a subdirectory needs a handful of things it does not do for itself, each commented at the site:
exv_conf.h and exiv2lib_export.h to ${CMAKE_BINARY_DIR} — which is the top of our build tree, not its own. They are copied to a named directory and that is what consumers get on their include path.include/exiv2 itself on the PUBLIC include path so its own sources can say #include "basicio.hpp". Consumers must not inherit that: it would leave Exiv2's config.h one -I away from every Ansel translation unit that writes #include "config.h" and means its own.findDependencies.cmake appends ${CMAKE_SOURCE_DIR}/cmake to CMAKE_MODULE_PATH to reach its own finders — Ansel's root cmake/ directory, when embedded. Its FindIconv.cmake is then never found, CMake's builtin one answers, and EXV_HAVE_ICONV silently ends up off.mainSetup.cmake creates an uninstall target unless one already exists. Ansel's root CMakeLists.txt therefore creates its own before add_subdirectory(src); src/external/CMakeLists.txt asserts that, so moving it back fails the configure rather than producing a duplicate-target mystery.install() is shadowed for the duration (src/external/CMakeLists.txt:311-320: the function(install) override, ANSEL_SUPPRESS_INSTALL set at :316 and released at :320) so none of that reaches Ansel's install tree — nor, downstream, the AppImage or the Windows installer. The site comment at :296-310 is the one to read before touching it: it must be a function, and the incident that proves it is recorded there.cmake_minimum_required(VERSION 3.7.2), so CMAKE_POLICY_VERSION_MINIMUM, CMAKE_POLICY_DEFAULT_CMP0069 and CMAKE_POLICY_DEFAULT_CMP0077 are set around it. The last matters most: left OLD, Exiv2's own option() calls override the variables we set, and we quietly get a shared library and Exiv2's default option set instead of ours.-Wno-register.** C++17 removed the register keyword. Clang rejects xmpsdk/src/MD5.cpp outright where GCC only warns, and neither -w nor -Wno-error reaches a hard error.CXX_EXTENSIONS ON**, unlike Ansel's own code. xmpsdk/include/XMP_Environment.h picks its platform on #if defined WIN32 — the unprefixed spelling, which GCC and Clang define only in GNU mode. Under -std=c++17 a MinGW build falls through to UNIX_ENV and XMPUtils.cpp reaches for localtime_r/gmtime_r, which MinGW does not have._LIBCPP_ENABLE_CXX17_REMOVED_AUTO_PTR**, on Exiv2's own compilation and on the interface. Image::AutoPtr is std::auto_ptr<Image> at every language standard — config.h's using auto_ptr = std::unique_ptr<T> sits in the global namespace while all 59 uses write the std:: qualification. libstdc++ keeps std::auto_ptr as deprecated in C++17; libc++ deletes it, so on macOS neither Exiv2 nor metadata/exif.cc compiles without it. The macro also restores unique_ptr's converting constructor from auto_ptr, which every std::unique_ptr<Exiv2::Image> image(Exiv2::ImageFactory::open(...)) in our tree depends on.INTERFACE_SYSTEM_INCLUDE_DIRECTORIES (-isystem), because Ansel builds with -Werror and those std::auto_ptr uses are deprecation warnings we cannot fix from here.Image::AutoPtr to std::auto_ptr or std::unique_ptr depending on __cplusplus, so the library and every consumer must agree on the C++ standard. The block sets CMAKE_CXX_STANDARD 17 explicitly, because src/CMakeLists.txt says so further down the file than add_subdirectory(external) sits.