1
0
mirror of https://github.com/cdcseacave/TinyEXIF.git synced 2026-07-25 21:23:01 +04:00

25 Commits

Author SHA1 Message Date
cDc 39bba86ced Update version to 1.0.4 2025-11-17 19:12:20 +02:00
cDc 27a750ecf5 Robust parsing if missing offset 2025-11-17 18:47:27 +02:00
cDc 7a3167cc73 Add license 2025-11-17 18:04:59 +02:00
Azamat H. Hackimov 47dbeaca48 Update CMake project (#23) 2025-11-17 17:48:29 +02:00
Paul Gafton e5d0e39a9b Added support for DJI distortion (#21) 2025-10-09 12:45:16 +03:00
Timothy Lyanguzov 79e124e1e1 Update version to 1.0.3 (#18) 2025-01-01 12:15:34 +02:00
Marco 7d7a36dae4 Add support for big-endian CPUs (#14) 2025-01-01 12:09:30 +02:00
Sebastian Martin Dicke ccd676f1b9 include cstdint (#17) 2023-07-06 14:46:34 +03:00
simfeo 6e56015f56 Support for Google Camera motion photo metadata (#12) 2021-04-10 18:52:26 +03:00
Juha Reunanen d75f772ffa Add constructor that takes generic std::istream objects (#11) 2021-04-02 20:31:04 +03:00
Juha Reunanen a41f1c89f7 Fix MSVC++ UNICODE builds (#9)
* Problem: in MSVC++ UNICODE builds, the _tcsncmp and _tcsicmp defined via <tchar.h> need wchar_t* input, but that's not what we have available here

Solution:
1) in place of _tcsncmp, just use strncmp, which ought to be standard: http://www.cplusplus.com/reference/cstring/strncmp/
2) in place of _tcsicmp, call strcasecmp, and in MSVC++ builds create a wrapper that actually calls _stricmp (and never _wcsicmp)

* #include <string.h> and not <strings.h>
2021-03-25 15:37:51 +02:00
Juha Reunanen 915d0e353b Make XMP support optional (no need for tinyxml2) (#10) 2021-03-25 15:33:24 +02:00
shinji-yoshida 0574cbf4f2 parse GPano:PosePitchDegrees and GPano:PoseRollDegrees (#8) 2021-02-04 18:46:22 +02:00
cDc c57a5fec1a fix tinyxml2 namespace 2019-01-16 15:26:30 +02:00
cDc 5bbfe8f70c add Sentera camera support to XMP 2019-01-11 15:28:25 +02:00
cDc cb40ffa537 add test images 2019-01-11 15:24:23 +02:00
cDc 405b8a1693 parse XMP stored inside EXIF 2018-10-09 12:20:43 +03:00
cDc 22ba2704c9 parse XMP for more DJI cameras 2018-09-24 13:18:40 +03:00
cDc 8b383a2809 fix rational/s-rational EXIF parsing 2018-09-24 13:18:05 +03:00
cDc ac39905f11 add maker check for DJI too 2018-09-24 12:33:57 +03:00
cDc 98dc256e0a add PARROT camera support to XMP 2018-08-15 13:44:36 +03:00
cDc e9c19435c3 parse GPS accuracy 2018-07-18 15:41:35 +03:00
cDc 04969097a4 add MakerNote support for DJI 2018-07-10 10:00:24 +03:00
cDc 8e3316cb41 extract calibration information 2018-07-04 18:30:26 +03:00
cDc c02203795b add senseFly support 2018-07-04 18:29:53 +03:00
46 changed files with 678 additions and 309 deletions
+38
View File
@@ -0,0 +1,38 @@
name: build
on:
- push
- pull_request
jobs:
build:
env:
VCPKG_COMMIT: '74e6536215718009aae747d86d84b78376bf9e09'
name: "${{ matrix.os }} - BUILD_SHARED_LIBS=${{ matrix.shared_lib }}"
strategy:
fail-fast: false
matrix:
os:
- macos-latest
- ubuntu-latest
- windows-latest
shared_lib:
- ON
- OFF
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@v4
- uses: lukka/get-cmake@v3.28.4
- uses: lukka/run-vcpkg@v11
with:
vcpkgGitCommitId: ${{ env.VCPKG_COMMIT }}
- name: Build with -DBUILD_SHARED_LIBS=${{ matrix.shared_lib }}
uses: lukka/run-cmake@v10
with:
configurePreset: "vcpkg"
configurePresetAdditionalArgs: "[
'-DBUILD_SHARED_LIBS=${{ matrix.shared_lib }}',
'-DBUILD_DEMO=ON'
]"
buildPreset: "vcpkg"
buildPresetAdditionalArgs: "[ '--config Release' ]"
+41
View File
@@ -0,0 +1,41 @@
# Compiled Object files
*.slo
*.lo
*.o
*.obj
# Precompiled Headers
*.gch
*.pch
# Compiled Dynamic libraries
*.so
*.dylib
*.dll
# Fortran module files
*.mod
# Compiled Static libraries
*.lai
*.la
*.a
*.lib
# Executables
*.exe
*.out
*.app
# Custom
*.tmp
*.exif.txt
*.exif.txt.txt
.DS_Store
CMakeSettings.json
.vs/
.idea/
.vscode/
bin/
binaries/
make/
+54 -152
View File
@@ -1,187 +1,89 @@
cmake_minimum_required(VERSION 3.1)
project(TinyEXIF)
include(GNUInstallDirs)
find_package(tinyxml2 REQUIRED)
#CMAKE_BUILD_TOOL
cmake_minimum_required(VERSION 3.15)
################################
# set lib version here
set(GENERIC_LIB_VERSION "1.0.1")
project(TinyEXIF VERSION 1.0.4)
set(GENERIC_LIB_SOVERSION "1")
include(GNUInstallDirs)
include(CMakePackageConfigHelpers)
find_package(tinyxml2 CONFIG REQUIRED)
################################
# Add definitions
set(CMAKE_CXX_STANDARD 11)
set(CMAKE_CXX_STANDARD_REQUIRED True)
set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -DDEBUG")
################################
# Add targets
# By Default shared libray is being built
# To build static libs also - Do cmake . -DBUILD_STATIC_LIBS:BOOL=ON
# User can choose not to build shared library by using cmake -DBUILD_SHARED_LIBS:BOOL=OFF
# To build only static libs use cmake . -DBUILD_SHARED_LIBS:BOOL=OFF -DBUILD_STATIC_LIBS:BOOL=ON
# User can choose to build static library by using cmake -DBUILD_SHARED_LIBS:BOOL=OFF
# To build the demo binary, use cmake . -DBUILD_DEMO:BOOL=ON
option(BUILD_SHARED_LIBS "build as shared library" ON)
option(BUILD_STATIC_LIBS "build as static library" OFF)
option(LINK_CRT_STATIC_LIBS "link CRT static library" OFF)
option(BUILD_DEMO "build demo binary" ON)
# set MSVC runtime linkage to static or dynamic
# as in: https://stackoverflow.com/questions/10113017/setting-the-msvc-runtime-in-cmake
macro(configure_runtime CRT_RUNTIME)
if(MSVC)
# Default to statically-linked runtime.
if("${CRT_RUNTIME}" STREQUAL "")
set(CRT_RUNTIME "static")
endif()
# Set compiler options.
set(variables
CMAKE_C_FLAGS_DEBUG
CMAKE_C_FLAGS_MINSIZEREL
CMAKE_C_FLAGS_RELEASE
CMAKE_C_FLAGS_RELWITHDEBINFO
CMAKE_CXX_FLAGS_DEBUG
CMAKE_CXX_FLAGS_MINSIZEREL
CMAKE_CXX_FLAGS_RELEASE
CMAKE_CXX_FLAGS_RELWITHDEBINFO
)
if(${CRT_RUNTIME} STREQUAL "static")
message(STATUS "MSVC -> forcing use of statically-linked runtime.")
foreach(variable ${variables})
if(${variable} MATCHES "/MD")
string(REGEX REPLACE "/MD" "/MT" ${variable} "${${variable}}")
endif()
endforeach()
else()
message(STATUS "MSVC -> forcing use of dynamically-linked runtime.")
foreach(variable ${variables})
if(${variable} MATCHES "/MT")
string(REGEX REPLACE "/MT" "/MD" ${variable} "${${variable}}")
endif()
endforeach()
endif()
endif()
endmacro()
if(LINK_CRT_STATIC_LIBS)
# set MSVC runtime linkage to static
configure_runtime("static")
endif()
# to distinguish between debug and release lib
set(CMAKE_DEBUG_POSTFIX "d")
if(BUILD_SHARED_LIBS)
add_library(TinyEXIF SHARED TinyEXIF.cpp TinyEXIF.h)
# Set MSVC runtime library globally so all targets use consistent /MT or /MD.
# This avoids LNK2038 mismatches between targets compiled with different CRT variants.
if(MSVC)
set(CMAKE_MSVC_RUNTIME_LIBRARY "MultiThreaded$<$<CONFIG:Debug>:Debug>$<$<NOT:$<BOOL:${LINK_CRT_STATIC_LIBS}>>:DLL>")
if(MSVC_VERSION GREATER 1300)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /wd4251") # needs to have dll-interface
endif()
target_link_libraries(TinyEXIF tinyxml2)
set_target_properties(TinyEXIF PROPERTIES
COMPILE_DEFINITIONS "TINYEXIF_EXPORT"
VERSION "${GENERIC_LIB_VERSION}"
SOVERSION "${GENERIC_LIB_SOVERSION}")
if(DEFINED CMAKE_VERSION AND NOT "${CMAKE_VERSION}" VERSION_LESS "2.8.11")
target_include_directories(TinyEXIF PUBLIC
$<BUILD_INTERFACE:${PROJECT_SOURCE_DIR}>
$<INSTALL_INTERFACE:${CMAKE_INSTALL_PREFIX}/include>)
if(MSVC)
target_compile_definitions(TinyEXIF PUBLIC _CRT_SECURE_NO_WARNINGS)
endif()
else()
include_directories(${PROJECT_SOURCE_DIR})
if(MSVC)
add_definitions(-D_CRT_SECURE_NO_WARNINGS)
endif()
endif()
# export targets for find_package config mode
export(TARGETS TinyEXIF
FILE ${CMAKE_BINARY_DIR}/${CMAKE_PROJECT_NAME}Targets.cmake)
install(TARGETS TinyEXIF
EXPORT ${CMAKE_PROJECT_NAME}Targets
RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}
LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}
ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR})
endif()
if(BUILD_STATIC_LIBS)
add_library(TinyEXIFstatic STATIC TinyEXIF.cpp TinyEXIF.h)
target_link_libraries(TinyEXIFstatic tinyxml2)
set_target_properties(TinyEXIFstatic PROPERTIES
OUTPUT_NAME TinyEXIF
VERSION "${GENERIC_LIB_VERSION}"
SOVERSION "${GENERIC_LIB_SOVERSION}")
add_library(TinyEXIF TinyEXIF.cpp TinyEXIF.h)
add_library(TinyEXIF::TinyEXIF ALIAS TinyEXIF)
if(DEFINED CMAKE_VERSION AND NOT "${CMAKE_VERSION}" VERSION_LESS "2.8.11")
target_include_directories(TinyEXIFstatic PUBLIC
$<BUILD_INTERFACE:${PROJECT_SOURCE_DIR}>
$<INSTALL_INTERFACE:${CMAKE_INSTALL_PREFIX}/include>)
target_link_libraries(TinyEXIF tinyxml2::tinyxml2)
set_target_properties(TinyEXIF PROPERTIES
DEFINE_SYMBOL "TINYEXIF_EXPORT"
VERSION "${TinyEXIF_VERSION}"
SOVERSION "${GENERIC_LIB_SOVERSION}"
MSVC_RUNTIME_LIBRARY "${CMAKE_MSVC_RUNTIME_LIBRARY}"
)
if(MSVC)
target_compile_definitions(TinyEXIFstatic PUBLIC _CRT_SECURE_NO_WARNINGS)
endif()
else()
include_directories(${PROJECT_SOURCE_DIR})
target_include_directories(TinyEXIF PUBLIC
"$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}>"
"$<INSTALL_INTERFACE:${CMAKE_INSTALL_INCLUDEDIR}>"
)
if(MSVC)
add_definitions(-D_CRT_SECURE_NO_WARNINGS)
endif()
endif()
# export targets for find_package config mode
export(TARGETS TinyEXIFstatic
FILE ${CMAKE_BINARY_DIR}/${CMAKE_PROJECT_NAME}Targets.cmake)
install(TARGETS TinyEXIFstatic
EXPORT ${CMAKE_PROJECT_NAME}Targets
RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}
LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}
ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR})
endif()
target_compile_definitions(TinyEXIF
INTERFACE $<$<BOOL:${BUILD_SHARED_LIBS}>:TINYEXIF_IMPORT>
PRIVATE $<$<CXX_COMPILER_ID:MSVC>:_CRT_SECURE_NO_WARNINGS>
)
if(BUILD_DEMO)
add_executable(TinyEXIFdemo main.cpp)
if(BUILD_SHARED_LIBS)
add_dependencies(TinyEXIFdemo TinyEXIF)
target_link_libraries(TinyEXIFdemo TinyEXIF)
target_compile_definitions(TinyEXIFdemo PRIVATE TINYEXIF_IMPORT)
else(BUILD_STATIC_LIBS)
add_dependencies(TinyEXIFdemo TinyEXIFstatic)
target_link_libraries(TinyEXIFdemo TinyEXIFstatic tinyxml2)
endif()
set_target_properties(TinyEXIFdemo PROPERTIES MSVC_RUNTIME_LIBRARY "${CMAKE_MSVC_RUNTIME_LIBRARY}")
target_link_libraries(TinyEXIFdemo TinyEXIF)
endif()
################################
# Install targets
install(TARGETS TinyEXIF
EXPORT TinyEXIFTargets
RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}
LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}
ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR}
INCLUDES DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}
)
install(FILES TinyEXIF.h DESTINATION ${CMAKE_INSTALL_INCLUDEDIR})
foreach(p LIB INCLUDE)
set(var CMAKE_INSTALL_${p}DIR)
if(NOT IS_ABSOLUTE "${${var}}")
set(${var} "${CMAKE_INSTALL_PREFIX}/${${var}}")
endif()
endforeach()
install(EXPORT TinyEXIFTargets
FILE TinyEXIFTargets.cmake
NAMESPACE TinyEXIF::
DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/TinyEXIF
)
file(WRITE
${CMAKE_BINARY_DIR}/${CMAKE_PROJECT_NAME}Config.cmake
"include(\${CMAKE_CURRENT_LIST_DIR}/${CMAKE_PROJECT_NAME}Targets.cmake)\n")
configure_package_config_file(${CMAKE_CURRENT_SOURCE_DIR}/cmake/Config.cmake.in
"${CMAKE_CURRENT_BINARY_DIR}/TinyEXIFConfig.cmake"
INSTALL_DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/TinyEXIF
)
install(FILES
${CMAKE_BINARY_DIR}/${CMAKE_PROJECT_NAME}Config.cmake
DESTINATION lib/cmake/${CMAKE_PROJECT_NAME})
install(EXPORT ${CMAKE_PROJECT_NAME}Targets
DESTINATION lib/cmake/${CMAKE_PROJECT_NAME})
install(FILES "${CMAKE_CURRENT_BINARY_DIR}/TinyEXIFConfig.cmake"
DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/TinyEXIF
)
+28
View File
@@ -0,0 +1,28 @@
{
"version": 6,
"cmakeMinimumRequired": {
"major": 3,
"minor": 15,
"patch": 0
},
"configurePresets": [
{
"name": "vcpkg",
"binaryDir": "${sourceDir}/builds/${presetName}",
"generator": "Ninja Multi-Config",
"cacheVariables": {
"CMAKE_TOOLCHAIN_FILE": {
"type": "FILEPATH",
"value": "$env{VCPKG_ROOT}/scripts/buildsystems/vcpkg.cmake"
}
}
}
],
"buildPresets": [
{
"name": "vcpkg",
"configurePreset": "vcpkg"
}
]
}
+21
View File
@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2025 cdcseacave
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
+9 -26
View File
@@ -18,16 +18,11 @@ int main(int argc, const char** argv) {
return -1;
}
// read entire image file
std::ifstream file(argv[1], std::ifstream::in|std::ifstream::binary);
file.seekg(0,std::ios::end);
std::streampos length = file.tellg();
file.seekg(0,std::ios::beg);
std::vector<uint8_t> data(length);
file.read((char*)data.data(), length);
// open a stream to read just the necessary parts of the image file
std::ifstream istream(argv[1], std::ifstream::binary);
// parse image EXIF and XMP metadata
TinyEXIF::EXIFInfo imageEXIF(data.data(), length);
TinyEXIF::EXIFInfo imageEXIF(istream);
if (imageEXIF.Fields)
std::cout
<< "Image Description " << imageEXIF.ImageDescription << "\n"
@@ -39,24 +34,12 @@ int main(int argc, const char** argv) {
```
See `main.cpp` for more details.
## Copyright
## License
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
MIT [License](https://github.com/cdcseacave/TinyEXIF/blob/master/LICENSE)
- Redistributions of source code must retain the above copyright notice,
this list of conditions and the following disclaimer.
- Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
Copyright (c) 2025 cdcseacave
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS ``AS IS'' AND ANY EXPRESS
OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN
NO EVENT SHALL THE FREEBSD PROJECT OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
## Acknowledgments
Inspired by [easyexif](https://github.com/mayanklahiri/easyexif) library (2013 version) of Mayank Lahiri (mlahiri@gmail.com).
Binary file not shown.

After

Width:  |  Height:  |  Size: 693 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 194 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 404 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 289 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 245 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 116 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 137 KiB

BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 707 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 220 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 204 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 404 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 85 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 118 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 259 KiB

BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 230 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 53 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 48 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 355 KiB

BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 84 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 154 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 897 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 157 KiB

BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 123 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 967 KiB

BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 5.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 298 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 872 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 31 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 457 KiB

BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 27 KiB

BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

+18
View File
@@ -0,0 +1,18 @@
import os
import sys
import subprocess
source = os.path.join(os.path.dirname(sys.argv[0]), 'Samples')
TinyEXIF = os.path.join(source, 'TinyEXIF')
exiftool = 'exiftool'
ext = 'exif.txt'
if len(sys.argv) > 1:
ext = sys.argv[1]
for root, dirs, filenames in os.walk(source):
for f in filenames:
if f[-4:].lower() == '.jpg':
fullpath = os.path.join(source, f)
imgexif = fullpath[:-3] + ext
print('parse ' + fullpath + ' to ' + imgexif)
subprocess.Popen(TinyEXIF + ' ' + fullpath + ' > ' + imgexif, shell=True)
subprocess.Popen(exiftool + ' -n -s -G ' + fullpath + ' > ' + imgexif + '.txt', shell=True)
+379 -79
View File
@@ -2,53 +2,73 @@
TinyEXIF.cpp -- A simple ISO C++ library to parse basic EXIF and XMP
information from a JPEG file.
Copyright (c) 2015-2017 Seacave
Copyright (c) 2015-2025 Seacave
cdc.seacave@gmail.com
All rights reserved.
Based on the easyexif library (2013 version)
https://github.com/mayanklahiri/easyexif
of Mayank Lahiri (mlahiri@gmail.com).
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
- Redistributions of source code must retain the above copyright notice,
this list of conditions and the following disclaimer.
- Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS ``AS IS'' AND ANY EXPRESS
OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN
NO EVENT SHALL THE FREEBSD PROJECT OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
MIT License
*/
#include "TinyEXIF.h"
#include <tinyxml2.h>
#include <cstdint>
#include <cstddef>
#include <cstdio>
#include <cmath>
#include <cfloat>
#include <vector>
#include <algorithm>
#include <iostream>
#include <sstream>
#ifndef TINYEXIF_NO_XMP_SUPPORT
#include <tinyxml2.h>
#endif // TINYEXIF_NO_XMP_SUPPORT
#ifdef _MSC_VER
#include <tchar.h>
namespace {
int strcasecmp(const char* a, const char* b) {
return _stricmp(a, b);
}
}
#else
#include <strings.h>
#define _tcsncmp strncmp
#define _tcsicmp strcasecmp
#include <string.h>
#endif
namespace Tools {
// search string inside a string, case sensitive
static const char* strrnstr(const char* haystack, const char* needle, size_t len) {
const size_t needle_len(strlen(needle));
if (0 == needle_len)
return haystack;
if (len <= needle_len)
return NULL;
for (size_t i=len-needle_len; i-- > 0; ) {
if (haystack[0] == needle[0] &&
0 == strncmp(haystack, needle, needle_len))
return haystack;
haystack++;
}
return NULL;
}
// split an input string with a delimiter and fill a string vector
static void strSplit(const std::string& str, char delim, std::vector<std::string>& values) {
values.clear();
std::string::size_type start(0), end(0);
while (end != std::string::npos) {
end = str.find(delim, start);
values.emplace_back(str.substr(start, end-start));
start = end + 1;
}
}
// make sure the given degrees value is between -180 and 180
static double NormD180(double d) {
return (d = fmod(d+180.0, 360.0)) < 0 ? d+180.0 : d-180.0;
}
} // namespace Tools
namespace TinyEXIF {
enum JPEG_MARKERS {
@@ -145,6 +165,10 @@ public:
length = parse32(buf + offs + 4, alignIntel);
}
const uint8_t* GetBuffer() const { return buf; }
unsigned GetOffset() const { return offs; }
bool IsIntelAligned() const { return alignIntel; }
uint16_t GetTag() const { return tag; }
uint32_t GetLength() const { return length; }
uint32_t GetData() const { return parse32(buf + offs + 8, alignIntel); }
@@ -153,11 +177,17 @@ public:
bool IsShort() const { return format == 3; }
bool IsLong() const { return format == 4; }
bool IsRational() const { return format == 5 || format == 10; }
bool IsSRational() const { return format == 10; }
bool IsFloat() const { return format == 11; }
bool IsUndefined() const { return format == 7; }
std::string FetchString() const {
return parseString(buf, length, GetData(), tiff_header_start, len, alignIntel);
}
bool Fetch(std::string& val) const {
if (format != 2 || length == 0)
return false;
val = parseEXIFString(buf, length, GetData(), tiff_header_start, len, alignIntel);
val = FetchString();
return true;
}
bool Fetch(uint8_t& val) const {
@@ -184,16 +214,30 @@ public:
val = parse32(buf + offs + 8, alignIntel);
return true;
}
bool Fetch(float& val) const {
if (!IsFloat() || length == 0)
return false;
val = parseFloat(buf + offs + 8, alignIntel);
return true;
}
bool Fetch(double& val) const {
if (!IsRational() || length == 0)
return false;
val = parseEXIFRational(buf + GetSubIFD(), alignIntel);
val = parseRational(buf + GetSubIFD(), alignIntel, IsSRational());
return true;
}
bool Fetch(double& val, uint32_t idx) const {
if (!IsRational() || length <= idx)
return false;
val = parseEXIFRational(buf + GetSubIFD() + idx*8, alignIntel);
val = parseRational(buf + GetSubIFD() + idx*8, alignIntel, IsSRational());
return true;
}
bool FetchFloat(double& val) const {
float _val;
if (!Fetch(_val))
return false;
val = _val;
return true;
}
@@ -217,14 +261,24 @@ public:
((uint32_t)buf[2]<<8) |
buf[3];
}
static double parseEXIFRational(const uint8_t* buf, bool intel) {
static float parseFloat(const uint8_t* buf, bool intel) {
union {
uint32_t i;
float f;
} i2f;
i2f.i = parse32(buf, intel);
return i2f.f;
}
static double parseRational(const uint8_t* buf, bool intel, bool isSigned) {
const uint32_t denominator = parse32(buf+4, intel);
if (denominator == 0)
return 0.0;
const uint32_t numerator = parse32(buf, intel);
return (double)(int32_t)numerator/(double)(int32_t)denominator;
return isSigned ?
(double)(int32_t)numerator/(double)(int32_t)denominator :
(double)numerator/(double)denominator;
}
static std::string parseEXIFString(const uint8_t* buf,
static std::string parseString(const uint8_t* buf,
unsigned num_components,
unsigned data,
unsigned base,
@@ -261,6 +315,9 @@ EXIFInfo::EXIFInfo() : Fields(FIELD_NA) {
EXIFInfo::EXIFInfo(EXIFStream& stream) {
parseFrom(stream);
}
EXIFInfo::EXIFInfo(std::istream& stream) {
parseFrom(stream);
}
EXIFInfo::EXIFInfo(const uint8_t* data, unsigned length) {
parseFrom(data, length);
}
@@ -362,6 +419,16 @@ void EXIFInfo::parseIFDImage(EntryParser& parser, unsigned& exif_sub_ifd_offset,
// Parse tag as Exif IFD
void EXIFInfo::parseIFDExif(EntryParser& parser) {
switch (parser.GetTag()) {
case 0x02bc:
#ifndef TINYEXIF_NO_XMP_SUPPORT
// XMP Metadata (Adobe technote 9-14-02)
if (parser.IsUndefined()) {
const std::string strXML(parser.FetchString());
parseFromXMPSegmentXML(strXML.c_str(), (unsigned)strXML.length());
}
#endif // TINYEXIF_NO_XMP_SUPPORT
break;
case 0x829a:
// Exposure time in seconds
parser.Fetch(ExposureTime);
@@ -448,6 +515,11 @@ void EXIFInfo::parseIFDExif(EntryParser& parser) {
}
break;
case 0x927c:
// MakerNote
parseIFDMakerNote(parser);
break;
case 0x9291:
// Fractions of seconds for DateTimeOriginal
parser.Fetch(SubSecTimeOriginal);
@@ -534,6 +606,60 @@ void EXIFInfo::parseIFDExif(EntryParser& parser) {
}
}
// Parse tag as MakerNote IFD
void EXIFInfo::parseIFDMakerNote(EntryParser& parser) {
const unsigned startOff = parser.GetOffset();
const uint32_t off = parser.GetSubIFD();
if (0 != strcasecmp(Make.c_str(), "DJI"))
return;
int num_entries = EntryParser::parse16(parser.GetBuffer()+off, parser.IsIntelAligned());
if (uint32_t(2 + 12 * num_entries) > parser.GetLength())
return;
parser.Init(off+2);
parser.ParseTag();
--num_entries;
std::string maker;
if (parser.GetTag() == 1 && parser.Fetch(maker)) {
if (0 == strcasecmp(maker.c_str(), "DJI")) {
while (--num_entries >= 0) {
parser.ParseTag();
switch (parser.GetTag()) {
case 3:
// SpeedX
parser.FetchFloat(GeoLocation.SpeedX);
break;
case 4:
// SpeedY
parser.FetchFloat(GeoLocation.SpeedY);
break;
case 5:
// SpeedZ
parser.FetchFloat(GeoLocation.SpeedZ);
break;
case 9:
// Camera Pitch
parser.FetchFloat(GeoLocation.PitchDegree);
break;
case 10:
// Camera Yaw
parser.FetchFloat(GeoLocation.YawDegree);
break;
case 11:
// Camera Roll
parser.FetchFloat(GeoLocation.RollDegree);
break;
}
}
}
}
parser.Init(startOff+12);
}
// Parse tag as GPS IFD
void EXIFInfo::parseIFDGPS(EntryParser& parser) {
switch (parser.GetTag()) {
@@ -670,6 +796,7 @@ int EXIFInfo::parseFrom(EXIFStream& stream) {
return app1s(PARSE_INVALID_JPEG);
switch (int ret=parseFromEXIFSegment(buf, sectionLength)) {
case PARSE_ABSENT_DATA:
#ifndef TINYEXIF_NO_XMP_SUPPORT
switch (ret=parseFromXMPSegment(buf, sectionLength)) {
case PARSE_ABSENT_DATA:
break;
@@ -680,6 +807,7 @@ int EXIFInfo::parseFrom(EXIFStream& stream) {
default:
return app1s(ret); // some error
}
#endif // TINYEXIF_NO_XMP_SUPPORT
break;
case PARSE_SUCCESS:
if ((app1s|=FIELD_EXIF) == FIELD_ALL)
@@ -700,6 +828,36 @@ int EXIFInfo::parseFrom(EXIFStream& stream) {
return app1s();
}
int EXIFInfo::parseFrom(std::istream& stream) {
class EXIFStdStream : public EXIFStream {
public:
EXIFStdStream(std::istream& stream)
: stream(stream) {
// Would be nice to assert here that the stream was opened in binary mode, but
// apparently that's not possible: https://stackoverflow.com/a/224259/19254
}
bool IsValid() const override {
return !!stream;
}
const uint8_t* GetBuffer(unsigned desiredLength) override {
buffer.resize(desiredLength);
if (!stream.read(reinterpret_cast<char*>(buffer.data()), desiredLength))
return NULL;
return buffer.data();
}
bool SkipBuffer(unsigned desiredLength) override {
return (bool)stream.seekg(desiredLength, std::ios::cur);
}
private:
std::istream& stream;
std::vector<uint8_t> buffer;
};
EXIFStdStream streamWrapper(stream);
return parseFrom(streamWrapper);
}
int EXIFInfo::parseFrom(const uint8_t* buf, unsigned len) {
class EXIFStreamBuffer : public EXIFStream {
public:
@@ -761,12 +919,14 @@ int EXIFInfo::parseFromEXIFSegment(const uint8_t* buf, unsigned len) {
// 8 bytes
if (offs + 8 > len)
return PARSE_CORRUPT_DATA;
const uint32_t _ONE32 = 1;
const bool IS_LITTLE_ENDIAN = reinterpret_cast<uint8_t const*>(&_ONE32)[0] == 1;
bool alignIntel;
if (buf[offs] == 'I' && buf[offs+1] == 'I')
alignIntel = true; // 1: Intel byte alignment
alignIntel = IS_LITTLE_ENDIAN; // 1: Intel byte alignment
else
if (buf[offs] == 'M' && buf[offs+1] == 'M')
alignIntel = false; // 0: Motorola byte alignment
alignIntel = !IS_LITTLE_ENDIAN; // 0: Motorola byte alignment
else
return PARSE_UNKNOWN_BYTEALIGN;
EntryParser parser(buf, len, offs, alignIntel);
@@ -785,15 +945,17 @@ int EXIFInfo::parseFromEXIFSegment(const uint8_t* buf, unsigned len) {
// entries in the section. The last 4 bytes of the IFD contain an offset
// to the next IFD, which means this IFD must contain exactly 6 + 12 * num
// bytes of data.
// Note that it's possible that the next IFD offset doesn't exist,
// so here the last 4 bytes are considered optional.
if (offs + 2 > len)
return PARSE_CORRUPT_DATA;
int num_entries = EntryParser::parse16(buf + offs, alignIntel);
if (offs + 6 + 12 * num_entries > len)
unsigned num_entries = EntryParser::parse16(buf + offs, alignIntel);
if (offs + 2 + 12 * num_entries > len)
return PARSE_CORRUPT_DATA;
unsigned exif_sub_ifd_offset = len;
unsigned gps_sub_ifd_offset = len;
parser.Init(offs+2);
while (--num_entries >= 0) {
while (num_entries-- > 0) {
parser.ParseTag();
parseIFDImage(parser, exif_sub_ifd_offset, gps_sub_ifd_offset);
}
@@ -805,10 +967,10 @@ int EXIFInfo::parseFromEXIFSegment(const uint8_t* buf, unsigned len) {
if (exif_sub_ifd_offset + 4 <= len) {
offs = exif_sub_ifd_offset;
num_entries = EntryParser::parse16(buf + offs, alignIntel);
if (offs + 6 + 12 * num_entries > len)
if (offs + 2 + 12 * num_entries > len)
return PARSE_CORRUPT_DATA;
parser.Init(offs+2);
while (--num_entries >= 0) {
while (num_entries-- > 0) {
parser.ParseTag();
parseIFDExif(parser);
}
@@ -819,10 +981,10 @@ int EXIFInfo::parseFromEXIFSegment(const uint8_t* buf, unsigned len) {
if (gps_sub_ifd_offset + 4 <= len) {
offs = gps_sub_ifd_offset;
num_entries = EntryParser::parse16(buf + offs, alignIntel);
if (offs + 6 + 12 * num_entries > len)
if (offs + 2 + 12 * num_entries > len)
return PARSE_CORRUPT_DATA;
parser.Init(offs+2);
while (--num_entries >= 0) {
while (num_entries-- > 0) {
parser.ParseTag();
parseIFDGPS(parser);
}
@@ -832,6 +994,8 @@ int EXIFInfo::parseFromEXIFSegment(const uint8_t* buf, unsigned len) {
return PARSE_SUCCESS;
}
#ifndef TINYEXIF_NO_XMP_SUPPORT
//
// Main parsing function for a XMP segment.
// Do a sanity check by looking for bytes "http://ns.adobe.com/xap/1.0/\0".
@@ -842,23 +1006,6 @@ int EXIFInfo::parseFromEXIFSegment(const uint8_t* buf, unsigned len) {
// PARAM: 'len' length of buffer
//
int EXIFInfo::parseFromXMPSegment(const uint8_t* buf, unsigned len) {
struct Tools {
static const char* strrnstr(const char* haystack, const char* needle, size_t len) {
const size_t needle_len(strlen(needle));
if (0 == needle_len)
return haystack;
if (len <= needle_len)
return NULL;
for (size_t i=len-needle_len; i-- > 0; ) {
if (haystack[0] == needle[0] &&
0 == _tcsncmp(haystack, needle, needle_len))
return haystack;
haystack++;
}
return NULL;
}
};
unsigned offs = 29; // current offset into buffer
if (!buf || len < offs)
return PARSE_ABSENT_DATA;
@@ -866,17 +1013,18 @@ int EXIFInfo::parseFromXMPSegment(const uint8_t* buf, unsigned len) {
return PARSE_ABSENT_DATA;
if (offs >= len)
return PARSE_CORRUPT_DATA;
len -= offs;
return parseFromXMPSegmentXML((const char*)(buf + offs), len - offs);
}
int EXIFInfo::parseFromXMPSegmentXML(const char* szXML, unsigned len) {
// Skip xpacket end section so that tinyxml2 lib parses the section correctly.
const char* const strXMP((const char*)(buf + offs)), *strEnd;
if ((strEnd=Tools::strrnstr(strXMP, "<?xpacket end=", len)) != NULL)
len = (unsigned)(strEnd - strXMP);
const char* szEnd(Tools::strrnstr(szXML, "<?xpacket end=", len));
if (szEnd != NULL)
len = (unsigned)(szEnd - szXML);
// Try parsing the XML packet.
tinyxml2::XMLDocument doc;
const tinyxml2::XMLElement* document;
if (doc.Parse(strXMP, len) != tinyxml2::XML_SUCCESS ||
if (doc.Parse(szXML, len) != tinyxml2::XML_SUCCESS ||
((document=doc.FirstChildElement("x:xmpmeta")) == NULL && (document=doc.FirstChildElement("xmp:xmpmeta")) == NULL) ||
(document=document->FirstChildElement("rdf:RDF")) == NULL ||
(document=document->FirstChildElement("rdf:Description")) == NULL)
@@ -907,25 +1055,137 @@ int EXIFInfo::parseFromXMPSegment(const uint8_t* buf, unsigned len) {
if (element != NULL) {
const char* const szProjectionType(element->GetText());
if (szProjectionType != NULL) {
if (0 == _tcsicmp(szProjectionType, "perspective"))
if (0 == strcasecmp(szProjectionType, "perspective"))
ProjectionType = 1;
else if (0 == _tcsicmp(szProjectionType, "equirectangular") ||
0 == _tcsicmp(szProjectionType, "spherical"))
else
if (0 == strcasecmp(szProjectionType, "equirectangular") ||
0 == strcasecmp(szProjectionType, "spherical"))
ProjectionType = 2;
}
}
}
// Try parsing the XMP content for DJI info.
document->QueryDoubleAttribute("drone-dji:AbsoluteAltitude", &GeoLocation.Altitude);
document->QueryDoubleAttribute("drone-dji:RelativeAltitude", &GeoLocation.RelativeAltitude);
document->QueryDoubleAttribute("drone-dji:GimbalRollDegree", &GeoLocation.RollDegree);
document->QueryDoubleAttribute("drone-dji:GimbalPitchDegree", &GeoLocation.PitchDegree);
document->QueryDoubleAttribute("drone-dji:GimbalYawDegree", &GeoLocation.YawDegree);
// Try parsing the XMP content for supported maker's info.
struct ParseXMP {
// try yo fetch the value both from the attribute and child element
// and parse if needed rational numbers stored as string fraction
static bool Value(const tinyxml2::XMLElement* document, const char* name, double& value) {
const char* szAttribute = document->Attribute(name);
if (szAttribute == NULL) {
const tinyxml2::XMLElement* const element(document->FirstChildElement(name));
if (element == NULL || (szAttribute=element->GetText()) == NULL)
return false;
}
std::vector<std::string> values;
Tools::strSplit(szAttribute, '/', values);
switch (values.size()) {
case 1: value = strtod(values.front().c_str(), NULL); return true;
case 2: value = strtod(values.front().c_str(), NULL)/strtod(values.back().c_str(), NULL); return true;
}
return false;
}
// same as previous function but with unsigned int results
static bool Value(const tinyxml2::XMLElement* document, const char* name, uint32_t& value) {
const char* szAttribute = document->Attribute(name);
if (szAttribute == NULL) {
const tinyxml2::XMLElement* const element(document->FirstChildElement(name));
if (element == NULL || (szAttribute = element->GetText()) == NULL)
return false;
}
value = strtoul(szAttribute, NULL, 0);
return true;
}
// same as previous function but with std::string
static bool Value(const tinyxml2::XMLElement* document, const char* name, std::string& value) {
const char* szAttribute = document->Attribute(name);
if (szAttribute == NULL) {
const tinyxml2::XMLElement* const element(document->FirstChildElement(name));
if (element == NULL || (szAttribute = element->GetText()) == NULL)
return false;
}
value = std::string(szAttribute);
return true;
}
};
const char* szAbout(document->Attribute("rdf:about"));
if (0 == strcasecmp(Make.c_str(), "DJI") || (szAbout != NULL && 0 == strcasecmp(szAbout, "DJI Meta Data"))) {
ParseXMP::Value(document, "drone-dji:AbsoluteAltitude", GeoLocation.Altitude);
ParseXMP::Value(document, "drone-dji:RelativeAltitude", GeoLocation.RelativeAltitude);
ParseXMP::Value(document, "drone-dji:GimbalRollDegree", GeoLocation.RollDegree);
ParseXMP::Value(document, "drone-dji:GimbalPitchDegree", GeoLocation.PitchDegree);
ParseXMP::Value(document, "drone-dji:GimbalYawDegree", GeoLocation.YawDegree);
ParseXMP::Value(document, "drone-dji:CalibratedFocalLength", Calibration.FocalLength);
ParseXMP::Value(document, "drone-dji:CalibratedOpticalCenterX", Calibration.OpticalCenterX);
ParseXMP::Value(document, "drone-dji:CalibratedOpticalCenterY", Calibration.OpticalCenterY);
std::string dewarpData;
ParseXMP::Value(document, "drone-dji:DewarpFlag", Distortion.DewarpFlag);
ParseXMP::Value(document, "drone-dji:DewarpData", dewarpData);
std::vector<double> distortionParams;
size_t pos = dewarpData.find(';');
if (pos != std::string::npos) {
std::stringstream ss(dewarpData.substr(pos + 1));
std::string item;
while (std::getline(ss, item, ',')) {
distortionParams.push_back(std::stod(item));
}
}
// The DewarpData string has the following format:
// date;Fx,Fy,Cx,Cy,K1,K2,P1,P2,K3
// , where Fx, Fy are focal lengths in pixels, Cx, Cy are optical center offsets from the image center in pixels
if (distortionParams.size() == 9) {
Distortion.K1 = distortionParams[4];
Distortion.K2 = distortionParams[5];
Distortion.P1 = distortionParams[6];
Distortion.P2 = distortionParams[7];
Distortion.K3 = distortionParams[8];
}
} else
if (0 == strcasecmp(Make.c_str(), "senseFly") || 0 == strcasecmp(Make.c_str(), "Sentera")) {
ParseXMP::Value(document, "Camera:Roll", GeoLocation.RollDegree);
if (ParseXMP::Value(document, "Camera:Pitch", GeoLocation.PitchDegree)) {
// convert to DJI format: senseFly uses pitch 0 as NADIR, whereas DJI -90
GeoLocation.PitchDegree = Tools::NormD180(GeoLocation.PitchDegree-90.0);
}
ParseXMP::Value(document, "Camera:Yaw", GeoLocation.YawDegree);
ParseXMP::Value(document, "Camera:GPSXYAccuracy", GeoLocation.AccuracyXY);
ParseXMP::Value(document, "Camera:GPSZAccuracy", GeoLocation.AccuracyZ);
} else
if (0 == strcasecmp(Make.c_str(), "PARROT")) {
ParseXMP::Value(document, "Camera:Roll", GeoLocation.RollDegree) ||
ParseXMP::Value(document, "drone-parrot:CameraRollDegree", GeoLocation.RollDegree);
if (ParseXMP::Value(document, "Camera:Pitch", GeoLocation.PitchDegree) ||
ParseXMP::Value(document, "drone-parrot:CameraPitchDegree", GeoLocation.PitchDegree)) {
// convert to DJI format: senseFly uses pitch 0 as NADIR, whereas DJI -90
GeoLocation.PitchDegree = Tools::NormD180(GeoLocation.PitchDegree-90.0);
}
ParseXMP::Value(document, "Camera:Yaw", GeoLocation.YawDegree) ||
ParseXMP::Value(document, "drone-parrot:CameraYawDegree", GeoLocation.YawDegree);
ParseXMP::Value(document, "Camera:AboveGroundAltitude", GeoLocation.RelativeAltitude);
}
ParseXMP::Value(document, "GPano:PosePitchDegrees", GPano.PosePitchDegrees);
ParseXMP::Value(document, "GPano:PoseRollDegrees", GPano.PoseRollDegrees);
// parse GCamera:MicroVideo
if (document->Attribute("GCamera:MicroVideo")) {
ParseXMP::Value(document, "GCamera:MicroVideo", MicroVideo.HasMicroVideo);
ParseXMP::Value(document, "GCamera:MicroVideoVersion", MicroVideo.MicroVideoVersion);
ParseXMP::Value(document, "GCamera:MicroVideoOffset", MicroVideo.MicroVideoOffset);
}
return PARSE_SUCCESS;
}
#endif // TINYEXIF_NO_XMP_SUPPORT
bool EXIFInfo::Calibration_t::hasCalibration() const {
return FocalLength > 0.0 && OpticalCenterX > 0.0 && OpticalCenterY > 0.0;
}
bool EXIFInfo::Distortion_t::hasDewarpFlag() const {
return DewarpFlag != UINT32_MAX;
}
bool EXIFInfo::Distortion_t::hasDistortion() const {
return K1 != 0.0 || K2 != 0.0 || P1 != 0.0 || P2 != 0.0 || K3 != 0.0;
}
void EXIFInfo::Geolocation_t::parseCoords() {
// Convert GPS latitude
@@ -969,7 +1229,20 @@ bool EXIFInfo::Geolocation_t::hasRelativeAltitude() const {
bool EXIFInfo::Geolocation_t::hasOrientation() const {
return RollDegree != DBL_MAX && PitchDegree != DBL_MAX && YawDegree != DBL_MAX;
}
bool EXIFInfo::Geolocation_t::hasSpeed() const {
return SpeedX != DBL_MAX && SpeedY != DBL_MAX && SpeedZ != DBL_MAX;
}
bool EXIFInfo::Geolocation_t::hasAccuracy() const {
return AccuracyXY != 0 && AccuracyZ != 0;
}
bool EXIFInfo::GPano_t::hasPosePitchDegrees() const {
return PosePitchDegrees != DBL_MAX;
}
bool EXIFInfo::GPano_t::hasPoseRollDegrees() const {
return PoseRollDegrees != DBL_MAX;
}
void EXIFInfo::clear() {
Fields = FIELD_NA;
@@ -1012,6 +1285,11 @@ void EXIFInfo::clear() {
ProjectionType = 0;
SubjectArea.clear();
// Calibration
Calibration.FocalLength = 0;
Calibration.OpticalCenterX = 0;
Calibration.OpticalCenterY = 0;
// LensInfo
LensInfo.FocalLengthMax = 0;
LensInfo.FocalLengthMin = 0;
@@ -1034,6 +1312,11 @@ void EXIFInfo::clear() {
GeoLocation.RollDegree = DBL_MAX;
GeoLocation.PitchDegree = DBL_MAX;
GeoLocation.YawDegree = DBL_MAX;
GeoLocation.SpeedX = DBL_MAX;
GeoLocation.SpeedY = DBL_MAX;
GeoLocation.SpeedZ = DBL_MAX;
GeoLocation.AccuracyXY = 0;
GeoLocation.AccuracyZ = 0;
GeoLocation.GPSDOP = 0;
GeoLocation.GPSDifferential = 0;
GeoLocation.GPSMapDatum = "";
@@ -1047,6 +1330,23 @@ void EXIFInfo::clear() {
GeoLocation.LonComponents.minutes = 0;
GeoLocation.LonComponents.seconds = 0;
GeoLocation.LonComponents.direction = 0;
// Distortion
Distortion.DewarpFlag = UINT32_MAX;
Distortion.K1 = 0;
Distortion.K2 = 0;
Distortion.P1 = 0;
Distortion.P2 = 0;
Distortion.K3 = 0;
// GPano
GPano.PosePitchDegrees = DBL_MAX;
GPano.PoseRollDegrees = DBL_MAX;
// Video metadata
MicroVideo.HasMicroVideo = 0;
MicroVideo.MicroVideoVersion = 0;
MicroVideo.MicroVideoOffset = 0;
}
} // namespace TinyEXIF
+48 -27
View File
@@ -2,44 +2,21 @@
TinyEXIF.h -- A simple ISO C++ library to parse basic EXIF and XMP
information from a JPEG file.
Copyright (c) 2015-2017 Seacave
Copyright (c) 2015-2025 Seacave
cdc.seacave@gmail.com
All rights reserved.
Based on the easyexif library (2013 version)
https://github.com/mayanklahiri/easyexif
of Mayank Lahiri (mlahiri@gmail.com).
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
- Redistributions of source code must retain the above copyright notice,
this list of conditions and the following disclaimer.
- Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS ``AS IS'' AND ANY EXPRESS
OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN
NO EVENT SHALL THE FREEBSD PROJECT OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
MIT License
*/
#ifndef __TINYEXIF_H__
#define __TINYEXIF_H__
#include <cstdint>
#include <string>
#include <vector>
#define TINYEXIF_MAJOR_VERSION 1
#define TINYEXIF_MINOR_VERSION 0
#define TINYEXIF_PATCH_VERSION 1
#define TINYEXIF_PATCH_VERSION 3
#ifdef _MSC_VER
# ifdef TINYEXIF_EXPORT
@@ -100,6 +77,7 @@ class TINYEXIF_LIB EXIFInfo {
public:
EXIFInfo();
EXIFInfo(EXIFStream& stream);
EXIFInfo(std::istream& stream); // NB: the stream must have been opened in binary mode
EXIFInfo(const uint8_t* data, unsigned length);
// Parsing function for an entire JPEG image stream.
@@ -110,6 +88,7 @@ public:
// RETURN: PARSE_SUCCESS (0) on success with 'result' filled out
// error code otherwise, as defined by the PARSE_* macros
int parseFrom(EXIFStream& stream);
int parseFrom(std::istream& stream); // NB: the stream must have been opened in binary mode
int parseFrom(const uint8_t* data, unsigned length);
// Parsing function for an EXIF segment. This is used internally by parseFrom()
@@ -117,10 +96,13 @@ public:
// available (i.e., a blob starting with the bytes "Exif\0\0").
int parseFromEXIFSegment(const uint8_t* buf, unsigned len);
#ifndef TINYEXIF_NO_XMP_SUPPORT
// Parsing function for an XMP segment. This is used internally by parseFrom()
// but can be called for special cases where only the XMP section is
// available (i.e., a blob starting with the bytes "http://ns.adobe.com/xap/1.0/\0").
int parseFromXMPSegment(const uint8_t* buf, unsigned len);
int parseFromXMPSegmentXML(const char* szXML, unsigned len);
#endif // TINYEXIF_NO_XMP_SUPPORT
// Set all data members to default values.
// Should be called before parsing a new stream.
@@ -133,6 +115,8 @@ private:
void parseIFDExif(EntryParser&);
// Parse tag as GPS IFD.
void parseIFDGPS(EntryParser&);
// Parse tag as MakerNote IFD.
void parseIFDMakerNote(EntryParser&);
public:
// Data fields
@@ -239,6 +223,25 @@ public:
// 2: location of the main subject as coordinates (first value is the X coordinate and second is the Y coordinate)
// 3: area of the main subject as a circle (first value is the center X coordinate, second is the center Y coordinate, and third is the diameter)
// 4: area of the main subject as a rectangle (first value is the center X coordinate, second is the center Y coordinate, third is the width of the area, and fourth is the height of the area)
struct TINYEXIF_LIB Calibration_t { // Camera calibration information
double FocalLength; // Focal length (pixels)
double OpticalCenterX; // Principal point X (pixels)
double OpticalCenterY; // Principal point Y (pixels)
bool hasCalibration() const; // Return true if FocalLength, OpticalCenterX, OpticalCenterY are available (nonzero)
} Calibration;
struct TINYEXIF_LIB Distortion_t { // Lens distortion information
uint32_t DewarpFlag; // Dewarp flag - indicates whether undistortion has been applied to the image
// UINT32_MAX: flag missing from EXIF data
// 0: no dewarp (raw image) - the image is distorted, should also have coefficients
// 1: dewarp applied (undistorted image)
double K1;
double K2;
double P1;
double P2;
double K3;
bool hasDewarpFlag() const; // Return true if DewarpFlag is available
bool hasDistortion() const; // Return true if any of K1, K2, P1, P2, K3 are available
} Distortion;
struct TINYEXIF_LIB LensInfo_t { // Lens information
double FStopMin; // Min aperture (f-stop)
double FStopMax; // Max aperture (f-stop)
@@ -265,6 +268,11 @@ public:
double RollDegree; // Flight roll in degrees
double PitchDegree; // Flight pitch in degrees
double YawDegree; // Flight yaw in degrees
double SpeedX; // Flight speed on X in meters/second
double SpeedY; // Flight speed on Y in meters/second
double SpeedZ; // Flight speed on Z in meters/second
double AccuracyXY; // GPS accuracy on XY in meters
double AccuracyZ; // GPS accuracy on Z in meters
double GPSDOP; // GPS DOP (data degree of precision)
uint16_t GPSDifferential; // Differential correction applied to the GPS receiver (may not exist)
// 0: measurement without differential correction
@@ -283,7 +291,20 @@ public:
bool hasAltitude() const; // Return true if (alt) is available
bool hasRelativeAltitude()const;// Return true if (rel_alt) is available
bool hasOrientation() const; // Return true if (roll,yaw,pitch) is available
bool hasSpeed() const; // Return true if (speedX,speedY,speedZ) is available
bool hasAccuracy() const; // Return true if (accuracyXY,accuracyZ) is available
} GeoLocation;
struct TINYEXIF_LIB GPano_t { // Spherical metadata. https://developers.google.com/streetview/spherical-metadata
double PosePitchDegrees; // Pitch, measured in degrees above the horizon, for the center in the image. Value must be >= -90 and <= 90.
double PoseRollDegrees; // Roll, measured in degrees, of the image where level with the horizon is 0. As roll increases, the horizon rotates counterclockwise in the image. Value must be > -180 and <= 180.
bool hasPosePitchDegrees() const; // Return true if PosePitchDegrees is available
bool hasPoseRollDegrees() const; // Return true if PoseRollDegrees is available
} GPano;
struct TINYEXIF_LIB MicroVideo_t { // Google camera video file in metadata
uint32_t HasMicroVideo; // not zero if exists
uint32_t MicroVideoVersion; // just regularinfo
uint32_t MicroVideoOffset; // offset from end of file
} MicroVideo;
};
} // namespace TinyEXIF
+8
View File
@@ -0,0 +1,8 @@
@PACKAGE_INIT@
include(CMakeFindDependencyMacro)
find_dependency(tinyxml2)
include("${CMAKE_CURRENT_LIST_DIR}/TinyEXIFTargets.cmake")
check_required_components(TinyEXIF)
+27 -25
View File
@@ -9,27 +9,6 @@
#include <vector> // std::vector
#include <iomanip> // std::setprecision
class EXIFStreamFile : public TinyEXIF::EXIFStream {
public:
explicit EXIFStreamFile(const char* fileName)
: file(fileName, std::ifstream::in|std::ifstream::binary) {}
bool IsValid() const override {
return file.is_open();
}
const uint8_t* GetBuffer(unsigned desiredLength) override {
buffer.resize(desiredLength);
if (!file.read((char*)buffer.data(), desiredLength))
return NULL;
return buffer.data();
}
bool SkipBuffer(unsigned desiredLength) override {
return (bool)file.seekg(desiredLength,std::ios::cur);
}
private:
std::ifstream file;
std::vector<uint8_t> buffer;
};
int main(int argc, const char** argv)
{
if (argc != 2) {
@@ -37,9 +16,9 @@ int main(int argc, const char** argv)
return -1;
}
// read entire image file
EXIFStreamFile stream(argv[1]);
if (!stream.IsValid()) {
// open a stream to read just the necessary parts of the image file
std::ifstream stream(argv[1], std::ios::binary);
if (!stream) {
std::cout << "error: can not open '" << argv[1] << "'\n";
return -2;
}
@@ -100,6 +79,12 @@ int main(int argc, const char** argv)
std::cout << "MeteringMode " << imageEXIF.MeteringMode << "\n";
std::cout << "LightSource " << imageEXIF.LightSource << "\n";
std::cout << "ProjectionType " << imageEXIF.ProjectionType << "\n";
if (imageEXIF.Calibration.FocalLength != 0)
std::cout << "Calibration.FocalLength " << imageEXIF.Calibration.FocalLength << " pixels" << "\n";
if (imageEXIF.Calibration.OpticalCenterX != 0)
std::cout << "Calibration.OpticalCenterX " << imageEXIF.Calibration.OpticalCenterX << " pixels" << "\n";
if (imageEXIF.Calibration.OpticalCenterY != 0)
std::cout << "Calibration.OpticalCenterY " << imageEXIF.Calibration.OpticalCenterY << " pixels" << "\n";
std::cout << "LensInfo.FStopMin " << imageEXIF.LensInfo.FStopMin << "\n";
std::cout << "LensInfo.FStopMax " << imageEXIF.LensInfo.FStopMax << "\n";
std::cout << "LensInfo.FocalLengthMin " << imageEXIF.LensInfo.FocalLengthMin << " mm" << "\n";
@@ -120,12 +105,19 @@ int main(int argc, const char** argv)
std::cout << "GeoLocation.AltitudeRef " << (int)imageEXIF.GeoLocation.AltitudeRef << "\n";
}
if (imageEXIF.GeoLocation.hasRelativeAltitude())
std::cout << "GeoLocation.RelativeAltitude " << imageEXIF.GeoLocation.RelativeAltitude << "\n";
std::cout << "GeoLocation.RelativeAltitude " << imageEXIF.GeoLocation.RelativeAltitude << " m" << "\n";
if (imageEXIF.GeoLocation.hasOrientation()) {
std::cout << "GeoLocation.RollDegree " << imageEXIF.GeoLocation.RollDegree << "\n";
std::cout << "GeoLocation.PitchDegree " << imageEXIF.GeoLocation.PitchDegree << "\n";
std::cout << "GeoLocation.YawDegree " << imageEXIF.GeoLocation.YawDegree << "\n";
}
if (imageEXIF.GeoLocation.hasSpeed()) {
std::cout << "GeoLocation.SpeedX " << imageEXIF.GeoLocation.SpeedX << " m/s" << "\n";
std::cout << "GeoLocation.SpeedY " << imageEXIF.GeoLocation.SpeedY << " m/s" << "\n";
std::cout << "GeoLocation.SpeedZ " << imageEXIF.GeoLocation.SpeedZ << " m/s" << "\n";
}
if (imageEXIF.GeoLocation.AccuracyXY > 0 || imageEXIF.GeoLocation.AccuracyZ > 0)
std::cout << "GeoLocation.GPSAccuracy XY " << imageEXIF.GeoLocation.AccuracyXY << " m" << " Z " << imageEXIF.GeoLocation.AccuracyZ << " m" << "\n";
std::cout << "GeoLocation.GPSDOP " << imageEXIF.GeoLocation.GPSDOP << "\n";
std::cout << "GeoLocation.GPSDifferential " << imageEXIF.GeoLocation.GPSDifferential << "\n";
if (!imageEXIF.GeoLocation.GPSMapDatum.empty())
@@ -134,5 +126,15 @@ int main(int argc, const char** argv)
std::cout << "GeoLocation.GPSTimeStamp " << imageEXIF.GeoLocation.GPSTimeStamp << "\n";
if (!imageEXIF.GeoLocation.GPSDateStamp.empty())
std::cout << "GeoLocation.GPSDateStamp " << imageEXIF.GeoLocation.GPSDateStamp << "\n";
if (imageEXIF.GPano.hasPosePitchDegrees())
std::cout << "GPano.PosePitchDegrees " << imageEXIF.GPano.PosePitchDegrees << "\n";
if (imageEXIF.GPano.hasPoseRollDegrees())
std::cout << "GPano.PoseRollDegrees " << imageEXIF.GPano.PoseRollDegrees << "\n";
if (imageEXIF.Distortion.hasDewarpFlag())
std::cout << "Distortion.DewarpFlag " << imageEXIF.Distortion.DewarpFlag << "\n";
if (imageEXIF.Distortion.hasDistortion())
std::cout << "Distortion [K1 K2 P1 P2 K3] " << std::setprecision(6)
<< imageEXIF.Distortion.K1 << " " << imageEXIF.Distortion.K2 << " " << imageEXIF.Distortion.P1
<< " " << imageEXIF.Distortion.P2 << " " << imageEXIF.Distortion.K3 << "\n";
return EXIT_SUCCESS;
}
+7
View File
@@ -0,0 +1,7 @@
{
"name": "tinyexif",
"version-string": "1.0.4",
"dependencies": [
"tinyxml2"
]
}