1
0
mirror of https://github.com/cdcseacave/TinyEXIF.git synced 2026-07-24 04:33:00 +04:00

13 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
13 changed files with 380 additions and 279 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' ]"
+1
View File
@@ -38,3 +38,4 @@ CMakeSettings.json
.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::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::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::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: 53 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 48 KiB

+167 -50
View File
@@ -2,50 +2,33 @@
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
@@ -60,7 +43,7 @@ namespace Tools {
return NULL;
for (size_t i=len-needle_len; i-- > 0; ) {
if (haystack[0] == needle[0] &&
0 == _tcsncmp(haystack, needle, needle_len))
0 == strncmp(haystack, needle, needle_len))
return haystack;
haystack++;
}
@@ -332,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);
}
@@ -434,11 +420,13 @@ void EXIFInfo::parseIFDImage(EntryParser& parser, unsigned& exif_sub_ifd_offset,
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:
@@ -622,7 +610,7 @@ void EXIFInfo::parseIFDExif(EntryParser& parser) {
void EXIFInfo::parseIFDMakerNote(EntryParser& parser) {
const unsigned startOff = parser.GetOffset();
const uint32_t off = parser.GetSubIFD();
if (0 != _tcsicmp(Make.c_str(), "DJI"))
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())
@@ -632,7 +620,7 @@ void EXIFInfo::parseIFDMakerNote(EntryParser& parser) {
--num_entries;
std::string maker;
if (parser.GetTag() == 1 && parser.Fetch(maker)) {
if (0 == _tcsicmp(maker.c_str(), "DJI")) {
if (0 == strcasecmp(maker.c_str(), "DJI")) {
while (--num_entries >= 0) {
parser.ParseTag();
switch (parser.GetTag()) {
@@ -808,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;
@@ -818,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)
@@ -838,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:
@@ -899,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);
@@ -923,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);
}
@@ -943,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);
}
@@ -957,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);
}
@@ -970,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".
@@ -1029,11 +1055,11 @@ int EXIFInfo::parseFromXMPSegmentXML(const char* szXML, 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"))
if (0 == strcasecmp(szProjectionType, "equirectangular") ||
0 == strcasecmp(szProjectionType, "spherical"))
ProjectionType = 2;
}
}
@@ -1058,9 +1084,31 @@ int EXIFInfo::parseFromXMPSegmentXML(const char* szXML, unsigned len) {
}
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 == _tcsicmp(Make.c_str(), "DJI") || (szAbout != NULL && 0 == _tcsicmp(szAbout, "DJI Meta Data"))) {
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);
@@ -1069,8 +1117,30 @@ int EXIFInfo::parseFromXMPSegmentXML(const char* szXML, unsigned len) {
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 == _tcsicmp(Make.c_str(), "senseFly") || 0 == _tcsicmp(Make.c_str(), "Sentera")) {
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
@@ -1080,7 +1150,7 @@ int EXIFInfo::parseFromXMPSegmentXML(const char* szXML, unsigned len) {
ParseXMP::Value(document, "Camera:GPSXYAccuracy", GeoLocation.AccuracyXY);
ParseXMP::Value(document, "Camera:GPSZAccuracy", GeoLocation.AccuracyZ);
} else
if (0 == _tcsicmp(Make.c_str(), "PARROT")) {
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) ||
@@ -1092,10 +1162,30 @@ int EXIFInfo::parseFromXMPSegmentXML(const char* szXML, unsigned len) {
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
@@ -1142,7 +1232,17 @@ bool EXIFInfo::Geolocation_t::hasOrientation() const {
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;
@@ -1230,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
+34 -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,11 +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.
@@ -246,7 +227,21 @@ public:
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)
@@ -297,7 +292,19 @@ public:
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)
+13 -24
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;
}
@@ -147,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"
]
}