Skip to content

Commit e541719

Browse files
committed
[feature] exiftool
Signed-off-by: Anton Dukhovnikov <antond@wetafx.co.nz>
1 parent 4e7338e commit e541719

17 files changed

Lines changed: 475 additions & 35 deletions

.github/workflows/build-steps.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,6 @@ permissions: read-all
4444

4545
jobs:
4646
build_steps:
47-
4847
runs-on: ${{ inputs.runner }}
4948
container:
5049
image: ${{ inputs.container }}
@@ -95,6 +94,7 @@ jobs:
9594
conan profile detect --force
9695
conan install --requires=nlohmann_json/3.12.0 --requires=nanobind/2.9.2 --generator CMakeDeps --generator CMakeToolchain --output-folder=build_deps --build=missing
9796
python -m pip install pytest
97+
sudo yum install --setopt=tsflags=nodocs -y perl-Image-ExifTool
9898
9999
- name: Configure CMake
100100
run: >

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -218,6 +218,7 @@ A help message with a description of all command line options can be obtained by
218218
--list-illuminants Shows the list of illuminants supported in spectral mode.
219219
--use-timing Log the execution time of each step of image processing.
220220
--disable-cache Disable the colour transform cache.
221+
--disable-exiftool Disable using exiftool to fetch missing metadata.
221222
--verbose (-v) Print progress messages. Repeated -v will increase verbosity.
222223
223224
### Command line parameters changes since version v1.x:

build_scripts/install_deps_linux.bash

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ time sudo apt-get -q -f install -y \
99
nlohmann-json3-dev \
1010
libopencv-dev \
1111
openimageio-tools libopenimageio-dev \
12-
nanobind-dev
12+
nanobind-dev \
13+
exiftool
1314

1415
pip3 install pytest

build_scripts/install_deps_mac.bash

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,12 @@ set -ex
44

55
brew update
66

7-
brew install ceres-solver nlohmann-json openimageio nanobind robin-map
7+
brew install \
8+
ceres-solver \
9+
nlohmann-json \
10+
openimageio \
11+
nanobind \
12+
robin-map \
13+
exiftool
814

915
python3 -m pip install --break-system-packages pytest

build_scripts/install_deps_windows.bash

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,3 +19,7 @@ vcpkg install \
1919
VCPKG_PYTHON="C:/vcpkg/installed/x64-windows/tools/python3/python.exe"
2020
"$VCPKG_PYTHON" -m ensurepip --upgrade
2121
"$VCPKG_PYTHON" -m pip install pytest
22+
23+
curl --silent --output ./exiftool.zip https://exiftool.org/exiftool-13.45_64.zip
24+
unzip ./exiftool.zip
25+
mv ./exiftool-13.45_64/exiftool\(-k\).exe ./exiftool-13.45_64/exiftool.exe

build_scripts/install_deps_yum.bash

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,6 @@
22

33
set -ex
44

5-
sudo yum install --setopt=tsflags=nodocs -y eigen3-devel ceres-solver-devel json-devel
5+
sudo yum install --setopt=tsflags=nodocs -y eigen3-devel ceres-solver-devel json-devel perl-Image-ExifTool
66

77
sudo python -m pip install nanobind pytest

include/rawtoaces/image_converter.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -201,6 +201,9 @@ class ImageConverter
201201
/// Disable caching.
202202
bool disable_cache = false;
203203

204+
/// Disable calling exiftool to fetch missing metadata.
205+
bool disable_exiftool = false;
206+
204207
/// Verbosity level.
205208
int verbosity = 0;
206209
};

src/rawtoaces/CMakeLists.txt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,3 +20,6 @@ if( ENABLE_COVERAGE AND COVERAGE_SUPPORTED )
2020
endif()
2121

2222
install( TARGETS rawtoaces DESTINATION ${INSTALL_BIN_DIR} )
23+
24+
# Simplifies debugging with Xcode, doesn't affect anything else.
25+
set_target_properties(rawtoaces PROPERTIES XCODE_SCHEME_ARGUMENTS "${CMAKE_XCODE_SCHEME_ARGUMENTS}" )

src/rawtoaces_util/CMakeLists.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,8 @@ add_library ( ${RAWTOACES_UTIL_LIB} ${DO_SHARED}
1919
transform_cache.h
2020
colour_transforms.cpp
2121
colour_transforms.h
22+
exiftool.cpp
23+
exiftool.h
2224

2325
# Make the headers visible in IDEs. This should not affect the builds.
2426
${UTIL_PUBLIC_HEADER}

src/rawtoaces_util/exiftool.cpp

Lines changed: 200 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,200 @@
1+
// Copyright Contributors to the rawtoaces project.
2+
// SPDX-License-Identifier: Apache-2.0
3+
// https://github.com/AcademySoftwareFoundation/rawtoaces
4+
5+
#include "exiftool.h"
6+
7+
#include <iomanip>
8+
#include <iostream>
9+
#include <filesystem>
10+
11+
#include <OpenImageIO/imagebuf.h>
12+
13+
namespace rta
14+
{
15+
namespace util
16+
{
17+
namespace exiftool
18+
{
19+
20+
std::string find_exiftool()
21+
{
22+
const char *env = getenv( "RAWTOACES_EXIFTOOL_PATH" );
23+
if ( env != nullptr )
24+
{
25+
return env;
26+
}
27+
28+
#if defined( WIN32 ) || defined( WIN64 )
29+
const std::string delimiter = ";";
30+
const std::string name = "exiftool.exe";
31+
#else
32+
const std::string delimiter = ":";
33+
const std::string name = "exiftool";
34+
#endif
35+
36+
env = getenv( "PATH" );
37+
if ( env )
38+
{
39+
std::string temp = env;
40+
41+
std::vector<std::string> paths;
42+
size_t pos = 0;
43+
std::string token;
44+
while ( ( pos = temp.find( delimiter ) ) != std::string::npos )
45+
{
46+
token = temp.substr( 0, pos );
47+
paths.push_back( token );
48+
temp.erase( 0, pos + delimiter.length() );
49+
}
50+
paths.push_back( temp );
51+
52+
for ( const auto &path: paths )
53+
{
54+
std::filesystem::path p( path );
55+
p /= name;
56+
57+
if ( std::filesystem::exists( p ) )
58+
return p.string();
59+
}
60+
}
61+
62+
return "";
63+
}
64+
65+
bool execute( const std::string &command, std::stringstream &stream )
66+
{
67+
constexpr size_t buf_size = 255;
68+
char buffer1[buf_size];
69+
// std::stringstream stream;
70+
71+
errno = 0;
72+
73+
#if defined( WIN32 ) || defined( WIN64 )
74+
FILE *file = _popen( command.c_str(), "r" );
75+
#else
76+
// clang-format off
77+
FILE *file = popen( command.c_str(), "r" );
78+
// clang-format on
79+
#endif
80+
81+
bool success = ( errno == 0 );
82+
83+
// Struggling to detect errors consistently across all platforms. In some
84+
// cases getting errno != 0 even when the command has been executed
85+
// successfully. As a workaround, I'm treating the empty output as error.
86+
bool empty = true;
87+
if ( success )
88+
{
89+
while ( fgets( buffer1, buf_size, file ) != NULL )
90+
{
91+
stream << buffer1;
92+
empty = false;
93+
}
94+
}
95+
96+
#if defined( WIN32 ) || defined( WIN64 )
97+
_pclose( file );
98+
#else
99+
pclose( file );
100+
#endif
101+
102+
stream.flush();
103+
return success && !empty;
104+
}
105+
106+
bool fetch_metadata(
107+
OIIO::ImageSpec &spec,
108+
const std::string &path,
109+
const std::vector<std::string> &keys )
110+
{
111+
112+
std::string exiftool_path = find_exiftool();
113+
114+
if ( exiftool_path.empty() )
115+
{
116+
std::cerr
117+
<< "Exiftool not found, please provide the path to "
118+
<< "exiftool binary via the RAWTOACES_EXIFTOOL_PATH environment "
119+
<< "variable." << std::endl;
120+
return false;
121+
}
122+
123+
// Mapping from OIIO attribute names to ExifTool attribute names.
124+
const std::map<std::string, std::string> oiio_to_exiftool = {
125+
{ "cameraMake", "Make" },
126+
{ "cameraModel", "Model" },
127+
{ "lensModel", "LensID" },
128+
{ "aperture", "FNumber" },
129+
{ "focalLength", "FocalLength" }
130+
};
131+
132+
// Mapping from ExifTool attribute names to OIIO attribute names,
133+
// the bool flag specifies whether the value must be converted
134+
// from string to float.
135+
const std::map<std::string, std::pair<std::string, bool>>
136+
exiftool_to_oiio = { { "Make", { "cameraMake", false } },
137+
{ "Model", { "cameraModel", false } },
138+
{ "LensID", { "lensModel", false } },
139+
{ "LensModel", { "lensModel", false } },
140+
{ "FNumber", { "aperture", true } },
141+
{ "FocalLength", { "focalLength", true } } };
142+
143+
std::string command = exiftool_path + " -S ";
144+
for ( auto key: keys )
145+
{
146+
if ( oiio_to_exiftool.count( key ) )
147+
{
148+
command += " -" + oiio_to_exiftool.at( key );
149+
}
150+
else
151+
{
152+
std::cerr << "Exiftool: unknown key " << key << std::endl;
153+
return false;
154+
}
155+
}
156+
157+
command += " " + path;
158+
159+
std::stringstream stream2;
160+
if ( !execute( command, stream2 ) )
161+
{
162+
std::cerr
163+
<< "Failed to execute exiftool. Please provide the path to "
164+
<< "exiftool binary via the RAWTOACES_EXIFTOOL_PATH environment "
165+
<< "variable." << std::endl;
166+
return false;
167+
}
168+
169+
std::map<std::string, std::string> result;
170+
std::vector<std::string> lines;
171+
std::string line;
172+
while ( std::getline( stream2, line ) )
173+
{
174+
auto pos = line.find( ": " );
175+
if ( pos != line.npos )
176+
{
177+
std::string exiftool_key = line.substr( 0, pos );
178+
std::string value = line.substr( pos + 2 );
179+
180+
auto &map = exiftool_to_oiio.at( exiftool_key );
181+
const std::string &oiio_key = std::get<0>( map );
182+
bool to_float = std::get<1>( map );
183+
184+
if ( to_float )
185+
{
186+
spec[oiio_key] = std::stof( value );
187+
}
188+
else
189+
{
190+
spec[oiio_key] = value;
191+
}
192+
}
193+
}
194+
195+
return true;
196+
}
197+
198+
} // namespace exiftool
199+
} // namespace util
200+
} // namespace rta

0 commit comments

Comments
 (0)