diff --git a/core/libs/CMakeLists.txt b/core/libs/CMakeLists.txt index 9dcc7ce4c3..aca29a17b3 100644 --- a/core/libs/CMakeLists.txt +++ b/core/libs/CMakeLists.txt @@ -1,89 +1,92 @@ # # Copyright (c) 2010-2020 by Gilles Caulier, # # Redistribution and use is allowed according to the terms of the BSD license. # For details see the accompanying COPYING-CMAKE-SCRIPTS file. #--------------------------------- # Common DNG SDK definitions for DNGWriter and RAWEngine. # # Set platteforms flags. # NOTE: see bug #195735: do not enable Mac flags provided by Adobe. # Sounds like all compile fine like under Linux. +add_definitions(-DEnablePluginManager=0) +add_definitions(-DXMP_StaticBuild=1) + if(MSVC) add_definitions( # XMP SDK -DWIN_ENV=1 # DNG SDK -DqWinOS=1 -DqMacOS=0 -DqDNGUseStdInt=0 ) else() add_definitions( # XMP SDK -DUNIX_ENV=1 # DNG SDK -DqWinOS=0 -DqMacOS=0 -DqDNGUseStdInt=1 ) endif() # Check processor endianness include(TestBigEndian) TEST_BIG_ENDIAN(IS_BIG_ENDIAN) if(NOT IS_BIG_ENDIAN) add_definitions(-DqDNGLittleEndian) endif() # Thread safe support under Mac and Linux using pthread library if(NOT MSVC) add_definitions(-DqDNGThreadSafe) endif() # Mode definition for console output. add_definitions(-DqDNGValidateTarget) #--------------------------------- add_subdirectory(dimg) add_subdirectory(metadataengine) add_subdirectory(database) add_subdirectory(dngwriter) add_subdirectory(dtrash) add_subdirectory(facesengine) add_subdirectory(iojobs) add_subdirectory(jpegutils) add_subdirectory(pgfutils) add_subdirectory(threadimageio) add_subdirectory(widgets) add_subdirectory(properties) add_subdirectory(progressmanager) add_subdirectory(threads) add_subdirectory(versionmanager) add_subdirectory(notificationmanager) add_subdirectory(models) add_subdirectory(template) add_subdirectory(dialogs) add_subdirectory(dplugins) add_subdirectory(kmemoryinfo) add_subdirectory(fileactionmanager) add_subdirectory(filters) add_subdirectory(settings) add_subdirectory(rawengine) add_subdirectory(album) add_subdirectory(tags) add_subdirectory(transitionmngr) add_subdirectory(timeadjust) if(ENABLE_MEDIAPLAYER) add_subdirectory(video) endif() diff --git a/core/libs/dngwriter/CMakeLists.txt b/core/libs/dngwriter/CMakeLists.txt index af46581b27..d1dced7d90 100644 --- a/core/libs/dngwriter/CMakeLists.txt +++ b/core/libs/dngwriter/CMakeLists.txt @@ -1,186 +1,310 @@ # # Copyright (c) 2010-2020, Gilles Caulier, # # Redistribution and use is allowed according to the terms of the BSD license. # For details see the accompanying COPYING-CMAKE-SCRIPTS file. # DNG SDK and XMP SDK use C++ exceptions kde_enable_exceptions() include_directories( $ $ ${EXPAT_INCLUDE_DIR} ${CMAKE_CURRENT_SOURCE_DIR}/extra/md5 ${CMAKE_CURRENT_SOURCE_DIR}/extra/dng_sdk ${CMAKE_CURRENT_SOURCE_DIR}/extra/xmp_sdk/XMPCore ${CMAKE_CURRENT_SOURCE_DIR}/extra/xmp_sdk/XMPCore/source + ${CMAKE_CURRENT_SOURCE_DIR}/extra/xmp_sdk/XMPFilesPlugins/api/source ${CMAKE_CURRENT_SOURCE_DIR}/extra/xmp_sdk/source ${CMAKE_CURRENT_SOURCE_DIR}/extra/xmp_sdk/build ${CMAKE_CURRENT_SOURCE_DIR}/extra/xmp_sdk/ ${CMAKE_CURRENT_SOURCE_DIR}/extra/xmp_sdk/public/include ${CMAKE_CURRENT_SOURCE_DIR}/extra/xmp_sdk/public/include/client-glue ${CMAKE_CURRENT_SOURCE_DIR}/extra/xmp_sdk/public/include/XMPCore ${CMAKE_CURRENT_SOURCE_DIR}/extra/xmp_sdk/public/include/XMPCore/Interfaces ${CMAKE_CURRENT_SOURCE_DIR}/extra/xmp_sdk/public/include/XMPCore/sources ${CMAKE_CURRENT_SOURCE_DIR}/extra/xmp_sdk/public/include/XMPCommon ${CMAKE_CURRENT_SOURCE_DIR}/extra/xmp_sdk/public/include/XMPCommon/Interfaces ${CMAKE_CURRENT_SOURCE_DIR}/extra/xmp_sdk/public/include/XMPCommon/Interfaces/BaseInterfaces ${CMAKE_CURRENT_SOURCE_DIR}/extra/xmp_sdk/public/include/XMPCommon/Utilities ${CMAKE_CURRENT_SOURCE_DIR}/extra/xmp_sdk/public/include/XMPCommon/sources ${CMAKE_CURRENT_SOURCE_DIR}/ ) #------------------------------------------------------------------------------------ set(libmd5_SRCS ${CMAKE_CURRENT_SOURCE_DIR}/extra/md5/XMP_MD5.cpp) # Adjust flag for static lib and 64 bits computers using -fPIC for GCC compiler (bug: #269903) foreach(_currentfile ${libmd5_SRCS}) if(NOT MSVC) set_source_files_properties(${_currentfile} PROPERTIES COMPILE_FLAGS "-fPIC") endif() endforeach() add_library(libmd5_src OBJECT ${libmd5_SRCS}) #------------------------------------------------------------------------------------ set(libxmp_SRCS - ${CMAKE_CURRENT_SOURCE_DIR}/extra/xmp_sdk/source/Host_IO-POSIX.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/extra/xmp_sdk/XMPFiles/source/WXMPFiles.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/extra/xmp_sdk/XMPFiles/source/FormatSupport/MOOV_Support.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/extra/xmp_sdk/XMPFiles/source/FormatSupport/PSIR_FileWriter.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/extra/xmp_sdk/XMPFiles/source/FormatSupport/ReconcileLegacy.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/extra/xmp_sdk/XMPFiles/source/FormatSupport/TimeConversionUtils.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/extra/xmp_sdk/XMPFiles/source/FormatSupport/AIFF/AIFFMetadata.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/extra/xmp_sdk/XMPFiles/source/FormatSupport/AIFF/AIFFReconcile.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/extra/xmp_sdk/XMPFiles/source/FormatSupport/AIFF/AIFFBehavior.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/extra/xmp_sdk/XMPFiles/source/FormatSupport/ReconcileIPTC.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/extra/xmp_sdk/XMPFiles/source/FormatSupport/ASF_Support.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/extra/xmp_sdk/XMPFiles/source/FormatSupport/XDCAM_Support.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/extra/xmp_sdk/XMPFiles/source/FormatSupport/RIFF.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/extra/xmp_sdk/XMPFiles/source/FormatSupport/ISOBaseMedia_Support.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/extra/xmp_sdk/XMPFiles/source/FormatSupport/TIFF_FileWriter.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/extra/xmp_sdk/XMPFiles/source/FormatSupport/TIFF_Support.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/extra/xmp_sdk/XMPFiles/source/FormatSupport/SWF_Support.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/extra/xmp_sdk/XMPFiles/source/FormatSupport/PSIR_MemoryReader.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/extra/xmp_sdk/XMPFiles/source/FormatSupport/Reconcile_Impl.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/extra/xmp_sdk/XMPFiles/source/FormatSupport/PackageFormat_Support.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/extra/xmp_sdk/XMPFiles/source/FormatSupport/IPTC_Support.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/extra/xmp_sdk/XMPFiles/source/FormatSupport/RIFF_Support.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/extra/xmp_sdk/XMPFiles/source/FormatSupport/PostScript_Support.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/extra/xmp_sdk/XMPFiles/source/FormatSupport/ID3_Support.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/extra/xmp_sdk/XMPFiles/source/FormatSupport/WAVE/iXMLMetadata.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/extra/xmp_sdk/XMPFiles/source/FormatSupport/WAVE/CartMetadata.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/extra/xmp_sdk/XMPFiles/source/FormatSupport/WAVE/Cr8rMetadata.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/extra/xmp_sdk/XMPFiles/source/FormatSupport/WAVE/BEXTMetadata.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/extra/xmp_sdk/XMPFiles/source/FormatSupport/WAVE/DISPMetadata.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/extra/xmp_sdk/XMPFiles/source/FormatSupport/WAVE/INFOMetadata.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/extra/xmp_sdk/XMPFiles/source/FormatSupport/WAVE/PrmLMetadata.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/extra/xmp_sdk/XMPFiles/source/FormatSupport/WAVE/WAVEReconcile.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/extra/xmp_sdk/XMPFiles/source/FormatSupport/WAVE/WAVEBehavior.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/extra/xmp_sdk/XMPFiles/source/FormatSupport/ReconcileTIFF.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/extra/xmp_sdk/XMPFiles/source/FormatSupport/QuickTime_Support.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/extra/xmp_sdk/XMPFiles/source/FormatSupport/PNG_Support.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/extra/xmp_sdk/XMPFiles/source/FormatSupport/P2_Support.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/extra/xmp_sdk/XMPFiles/source/FormatSupport/IFF/ChunkController.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/extra/xmp_sdk/XMPFiles/source/FormatSupport/IFF/Chunk.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/extra/xmp_sdk/XMPFiles/source/FormatSupport/IFF/IChunkBehavior.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/extra/xmp_sdk/XMPFiles/source/FormatSupport/IFF/ChunkPath.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/extra/xmp_sdk/XMPFiles/source/FormatSupport/TIFF_MemoryReader.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/extra/xmp_sdk/XMPFiles/source/FormatSupport/XMPScanner.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/extra/xmp_sdk/XMPFiles/source/FormatSupport/SVG_Adapter.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/extra/xmp_sdk/XMPFiles/source/NativeMetadataSupport/MetadataSet.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/extra/xmp_sdk/XMPFiles/source/NativeMetadataSupport/IReconcile.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/extra/xmp_sdk/XMPFiles/source/NativeMetadataSupport/IMetadata.cpp +# ${CMAKE_CURRENT_SOURCE_DIR}/extra/xmp_sdk/XMPFiles/source/PluginHandler/PluginManager.cpp +# ${CMAKE_CURRENT_SOURCE_DIR}/extra/xmp_sdk/XMPFiles/source/PluginHandler/FileHandlerInstance.cpp +# ${CMAKE_CURRENT_SOURCE_DIR}/extra/xmp_sdk/XMPFiles/source/PluginHandler/HostAPIImpl.cpp +# ${CMAKE_CURRENT_SOURCE_DIR}/extra/xmp_sdk/XMPFiles/source/PluginHandler/XMPAtoms.cpp +# ${CMAKE_CURRENT_SOURCE_DIR}/extra/xmp_sdk/XMPFiles/source/PluginHandler/OS_Utils_Mac.cpp +# ${CMAKE_CURRENT_SOURCE_DIR}/extra/xmp_sdk/XMPFiles/source/PluginHandler/OS_Utils_WIN.cpp +# ${CMAKE_CURRENT_SOURCE_DIR}/extra/xmp_sdk/XMPFiles/source/PluginHandler/Module.cpp +# ${CMAKE_CURRENT_SOURCE_DIR}/extra/xmp_sdk/XMPFiles/source/PluginHandler/OS_Utils_Linux.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/extra/xmp_sdk/XMPFiles/source/XMPFiles_Impl.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/extra/xmp_sdk/XMPFiles/source/HandlerRegistry.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/extra/xmp_sdk/XMPFiles/source/FileHandlers/MPEG2_Handler.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/extra/xmp_sdk/XMPFiles/source/FileHandlers/XDCAMEX_Handler.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/extra/xmp_sdk/XMPFiles/source/FileHandlers/InDesign_Handler.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/extra/xmp_sdk/XMPFiles/source/FileHandlers/SonyHDV_Handler.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/extra/xmp_sdk/XMPFiles/source/FileHandlers/SVG_Handler.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/extra/xmp_sdk/XMPFiles/source/FileHandlers/ASF_Handler.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/extra/xmp_sdk/XMPFiles/source/FileHandlers/PostScript_Handler.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/extra/xmp_sdk/XMPFiles/source/FileHandlers/AVCHD_Handler.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/extra/xmp_sdk/XMPFiles/source/FileHandlers/Trivial_Handler.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/extra/xmp_sdk/XMPFiles/source/FileHandlers/RIFF_Handler.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/extra/xmp_sdk/XMPFiles/source/FileHandlers/GIF_Handler.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/extra/xmp_sdk/XMPFiles/source/FileHandlers/UCF_Handler.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/extra/xmp_sdk/XMPFiles/source/FileHandlers/PNG_Handler.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/extra/xmp_sdk/XMPFiles/source/FileHandlers/P2_Handler.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/extra/xmp_sdk/XMPFiles/source/FileHandlers/FLV_Handler.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/extra/xmp_sdk/XMPFiles/source/FileHandlers/PSD_Handler.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/extra/xmp_sdk/XMPFiles/source/FileHandlers/Basic_Handler.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/extra/xmp_sdk/XMPFiles/source/FileHandlers/TIFF_Handler.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/extra/xmp_sdk/XMPFiles/source/FileHandlers/SWF_Handler.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/extra/xmp_sdk/XMPFiles/source/FileHandlers/MP3_Handler.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/extra/xmp_sdk/XMPFiles/source/FileHandlers/XDCAMSAM_Handler.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/extra/xmp_sdk/XMPFiles/source/FileHandlers/MPEG4_Handler.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/extra/xmp_sdk/XMPFiles/source/FileHandlers/XDCAM_Handler.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/extra/xmp_sdk/XMPFiles/source/FileHandlers/AIFF_Handler.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/extra/xmp_sdk/XMPFiles/source/FileHandlers/WAVE_Handler.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/extra/xmp_sdk/XMPFiles/source/FileHandlers/XDCAMFAM_Handler.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/extra/xmp_sdk/XMPFiles/source/FileHandlers/Scanner_Handler.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/extra/xmp_sdk/XMPFiles/source/FileHandlers/JPEG_Handler.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/extra/xmp_sdk/XMPFiles/source/XMPFiles.cpp +# ${CMAKE_CURRENT_SOURCE_DIR}/extra/xmp_sdk/XMPFilesPlugins/api/source/HostAPIAccess.cpp +# ${CMAKE_CURRENT_SOURCE_DIR}/extra/xmp_sdk/XMPFilesPlugins/api/source/PluginAPIImpl.cpp +# ${CMAKE_CURRENT_SOURCE_DIR}/extra/xmp_sdk/XMPFilesPlugins/api/source/PluginBase.cpp +# ${CMAKE_CURRENT_SOURCE_DIR}/extra/xmp_sdk/XMPFilesPlugins/api/source/PluginRegistry.cpp +# ${CMAKE_CURRENT_SOURCE_DIR}/extra/xmp_sdk/XMPFilesPlugins/api/source/PluginUtils.cpp +# ${CMAKE_CURRENT_SOURCE_DIR}/extra/xmp_sdk/public/include/client-glue/TXMPUtils.incl_cpp +# ${CMAKE_CURRENT_SOURCE_DIR}/extra/xmp_sdk/public/include/client-glue/TXMPFiles.incl_cpp +# ${CMAKE_CURRENT_SOURCE_DIR}/extra/xmp_sdk/public/include/client-glue/TXMPMeta.incl_cpp +# ${CMAKE_CURRENT_SOURCE_DIR}/extra/xmp_sdk/public/include/client-glue/TXMPIterator.incl_cpp +# ${CMAKE_CURRENT_SOURCE_DIR}/extra/xmp_sdk/public/include/XMPCommon/source/IConfigurationManager.cpp +# ${CMAKE_CURRENT_SOURCE_DIR}/extra/xmp_sdk/public/include/XMPCommon/source/IError.cpp +# ${CMAKE_CURRENT_SOURCE_DIR}/extra/xmp_sdk/public/include/XMPCommon/source/IConfigurable.cpp +# ${CMAKE_CURRENT_SOURCE_DIR}/extra/xmp_sdk/public/include/XMPCommon/source/IErrorNotifier.cpp +# ${CMAKE_CURRENT_SOURCE_DIR}/extra/xmp_sdk/public/include/XMPCommon/source/IUTF8String.cpp +# ${CMAKE_CURRENT_SOURCE_DIR}/extra/xmp_sdk/public/include/XMP.incl_cpp +# ${CMAKE_CURRENT_SOURCE_DIR}/extra/xmp_sdk/public/include/XMPCore/source/IClientDOMParser.cpp +# ${CMAKE_CURRENT_SOURCE_DIR}/extra/xmp_sdk/public/include/XMPCore/source/IDOMSerializer.cpp +# ${CMAKE_CURRENT_SOURCE_DIR}/extra/xmp_sdk/public/include/XMPCore/source/IDOMParser.cpp +# ${CMAKE_CURRENT_SOURCE_DIR}/extra/xmp_sdk/public/include/XMPCore/source/IClientDOMSerializer.cpp +# ${CMAKE_CURRENT_SOURCE_DIR}/extra/xmp_sdk/public/include/XMPCore/source/IArrayNode.cpp +# ${CMAKE_CURRENT_SOURCE_DIR}/extra/xmp_sdk/public/include/XMPCore/source/IDOMImplementationRegistry.cpp +# ${CMAKE_CURRENT_SOURCE_DIR}/extra/xmp_sdk/public/include/XMPCore/source/INodeIterator.cpp +# ${CMAKE_CURRENT_SOURCE_DIR}/extra/xmp_sdk/public/include/XMPCore/source/INode.cpp +# ${CMAKE_CURRENT_SOURCE_DIR}/extra/xmp_sdk/public/include/XMPCore/source/ICoreConfigurationManager.cpp +# ${CMAKE_CURRENT_SOURCE_DIR}/extra/xmp_sdk/public/include/XMPCore/source/INameSpacePrefixMap.cpp +# ${CMAKE_CURRENT_SOURCE_DIR}/extra/xmp_sdk/public/include/XMPCore/source/IMetadata.cpp +# ${CMAKE_CURRENT_SOURCE_DIR}/extra/xmp_sdk/public/include/XMPCore/source/ICompositeNode.cpp +# ${CMAKE_CURRENT_SOURCE_DIR}/extra/xmp_sdk/public/include/XMPCore/source/ICoreObjectFactory.cpp +# ${CMAKE_CURRENT_SOURCE_DIR}/extra/xmp_sdk/public/include/XMPCore/source/IPathSegment.cpp +# ${CMAKE_CURRENT_SOURCE_DIR}/extra/xmp_sdk/public/include/XMPCore/source/ISimpleNode.cpp +# ${CMAKE_CURRENT_SOURCE_DIR}/extra/xmp_sdk/public/include/XMPCore/source/IStructureNode.cpp +# ${CMAKE_CURRENT_SOURCE_DIR}/extra/xmp_sdk/public/include/XMPCore/source/IPath.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/extra/xmp_sdk/source/XMPFiles_IO.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/extra/xmp_sdk/source/XMP_ProgressTracker.cpp + # ${CMAKE_CURRENT_SOURCE_DIR}/extra/xmp_sdk/source/Host_IO-Win.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/extra/xmp_sdk/source/IOUtils.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/extra/xmp_sdk/source/PerfUtils.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/extra/xmp_sdk/source/SafeStringAPIs.cpp ${CMAKE_CURRENT_SOURCE_DIR}/extra/xmp_sdk/source/UnicodeConversions.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/extra/xmp_sdk/source/XIO.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/extra/xmp_sdk/source/XML_Node.cpp ${CMAKE_CURRENT_SOURCE_DIR}/extra/xmp_sdk/source/XMP_LibUtils.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/extra/xmp_sdk/source/XMP_ProgressTracker.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/extra/xmp_sdk/source/XMPFiles_IO.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/extra/xmp_sdk/XMPCore/source/ExpatAdapter.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/extra/xmp_sdk/XMPCore/source/ParseRDF.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/extra/xmp_sdk/XMPCore/source/WXMPIterator.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/extra/xmp_sdk/XMPCore/source/WXMPMeta.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/extra/xmp_sdk/XMPCore/source/WXMPUtils.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/extra/xmp_sdk/source/XML_Node.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/extra/xmp_sdk/source/XIO.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/extra/xmp_sdk/source/UnicodeInlines.incl_cpp + ${CMAKE_CURRENT_SOURCE_DIR}/extra/xmp_sdk/source/IOUtils.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/extra/xmp_sdk/source/PerfUtils.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/extra/xmp_sdk/source/Host_IO-POSIX.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/extra/xmp_sdk/XMPCore/source/XMPMeta-Parse.cpp ${CMAKE_CURRENT_SOURCE_DIR}/extra/xmp_sdk/XMPCore/source/XMPCore_Impl.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/extra/xmp_sdk/XMPCore/source/XMPIterator.cpp ${CMAKE_CURRENT_SOURCE_DIR}/extra/xmp_sdk/XMPCore/source/XMPMeta-GetSet.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/extra/xmp_sdk/XMPCore/source/XMPMeta-Parse.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/extra/xmp_sdk/XMPCore/source/XMPMeta-Serialize.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/extra/xmp_sdk/XMPCore/source/ExpatAdapter.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/extra/xmp_sdk/XMPCore/source/WXMPUtils.cpp ${CMAKE_CURRENT_SOURCE_DIR}/extra/xmp_sdk/XMPCore/source/XMPMeta.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/extra/xmp_sdk/XMPCore/source/XMPUtils-FileInfo.cpp ${CMAKE_CURRENT_SOURCE_DIR}/extra/xmp_sdk/XMPCore/source/XMPUtils.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/extra/xmp_sdk/XMPCore/source/WXMPMeta.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/extra/xmp_sdk/XMPCore/source/WXMPIterator.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/extra/xmp_sdk/XMPCore/source/XMPMeta-Serialize.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/extra/xmp_sdk/XMPCore/source/XMPIterator.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/extra/xmp_sdk/XMPCore/source/XMPIterator2.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/extra/xmp_sdk/XMPCore/source/ParseRDF.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/extra/xmp_sdk/XMPCore/source/XMPUtils2.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/extra/xmp_sdk/XMPCore/source/XMPMeta2-GetSet.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/extra/xmp_sdk/XMPCore/source/XMPUtils-FileInfo.cpp ) # Disable warnings: we will never touch this code. # Adjust flag for static lib and 64 bits computers using -fPIC for GCC compiler (bug: #269903) foreach(_currentfile ${libxmp_SRCS}) if(MSVC) set_source_files_properties(${_currentfile} PROPERTIES COMPILE_FLAGS "-w") else() set_source_files_properties(${_currentfile} PROPERTIES COMPILE_FLAGS "-w -fPIC") endif() endforeach() add_library(libxmp_src OBJECT ${libxmp_SRCS}) #------------------------------------------------------------------------------------ set(libdng_SRCS ${CMAKE_CURRENT_SOURCE_DIR}/extra/dng_sdk/dng_1d_function.cpp ${CMAKE_CURRENT_SOURCE_DIR}/extra/dng_sdk/dng_1d_table.cpp ${CMAKE_CURRENT_SOURCE_DIR}/extra/dng_sdk/dng_abort_sniffer.cpp ${CMAKE_CURRENT_SOURCE_DIR}/extra/dng_sdk/dng_area_task.cpp ${CMAKE_CURRENT_SOURCE_DIR}/extra/dng_sdk/dng_bad_pixels.cpp ${CMAKE_CURRENT_SOURCE_DIR}/extra/dng_sdk/dng_big_table.cpp ${CMAKE_CURRENT_SOURCE_DIR}/extra/dng_sdk/dng_bottlenecks.cpp ${CMAKE_CURRENT_SOURCE_DIR}/extra/dng_sdk/dng_camera_profile.cpp ${CMAKE_CURRENT_SOURCE_DIR}/extra/dng_sdk/dng_color_space.cpp ${CMAKE_CURRENT_SOURCE_DIR}/extra/dng_sdk/dng_color_spec.cpp ${CMAKE_CURRENT_SOURCE_DIR}/extra/dng_sdk/dng_date_time.cpp ${CMAKE_CURRENT_SOURCE_DIR}/extra/dng_sdk/dng_exceptions.cpp ${CMAKE_CURRENT_SOURCE_DIR}/extra/dng_sdk/dng_exif.cpp ${CMAKE_CURRENT_SOURCE_DIR}/extra/dng_sdk/dng_file_stream.cpp ${CMAKE_CURRENT_SOURCE_DIR}/extra/dng_sdk/dng_filter_task.cpp ${CMAKE_CURRENT_SOURCE_DIR}/extra/dng_sdk/dng_fingerprint.cpp ${CMAKE_CURRENT_SOURCE_DIR}/extra/dng_sdk/dng_gain_map.cpp ${CMAKE_CURRENT_SOURCE_DIR}/extra/dng_sdk/dng_globals.cpp ${CMAKE_CURRENT_SOURCE_DIR}/extra/dng_sdk/dng_host.cpp ${CMAKE_CURRENT_SOURCE_DIR}/extra/dng_sdk/dng_hue_sat_map.cpp ${CMAKE_CURRENT_SOURCE_DIR}/extra/dng_sdk/dng_ifd.cpp ${CMAKE_CURRENT_SOURCE_DIR}/extra/dng_sdk/dng_image.cpp ${CMAKE_CURRENT_SOURCE_DIR}/extra/dng_sdk/dng_image_writer.cpp ${CMAKE_CURRENT_SOURCE_DIR}/extra/dng_sdk/dng_info.cpp ${CMAKE_CURRENT_SOURCE_DIR}/extra/dng_sdk/dng_iptc.cpp ${CMAKE_CURRENT_SOURCE_DIR}/extra/dng_sdk/dng_jpeg_image.cpp ${CMAKE_CURRENT_SOURCE_DIR}/extra/dng_sdk/dng_lens_correction.cpp ${CMAKE_CURRENT_SOURCE_DIR}/extra/dng_sdk/dng_linearization_info.cpp ${CMAKE_CURRENT_SOURCE_DIR}/extra/dng_sdk/dng_local_string.cpp ${CMAKE_CURRENT_SOURCE_DIR}/extra/dng_sdk/dng_lossless_jpeg.cpp ${CMAKE_CURRENT_SOURCE_DIR}/extra/dng_sdk/dng_matrix.cpp ${CMAKE_CURRENT_SOURCE_DIR}/extra/dng_sdk/dng_memory.cpp ${CMAKE_CURRENT_SOURCE_DIR}/extra/dng_sdk/dng_memory_stream.cpp ${CMAKE_CURRENT_SOURCE_DIR}/extra/dng_sdk/dng_misc_opcodes.cpp ${CMAKE_CURRENT_SOURCE_DIR}/extra/dng_sdk/dng_mosaic_info.cpp ${CMAKE_CURRENT_SOURCE_DIR}/extra/dng_sdk/dng_mutex.cpp ${CMAKE_CURRENT_SOURCE_DIR}/extra/dng_sdk/dng_negative.cpp ${CMAKE_CURRENT_SOURCE_DIR}/extra/dng_sdk/dng_opcode_list.cpp ${CMAKE_CURRENT_SOURCE_DIR}/extra/dng_sdk/dng_opcodes.cpp ${CMAKE_CURRENT_SOURCE_DIR}/extra/dng_sdk/dng_orientation.cpp ${CMAKE_CURRENT_SOURCE_DIR}/extra/dng_sdk/dng_parse_utils.cpp ${CMAKE_CURRENT_SOURCE_DIR}/extra/dng_sdk/dng_pixel_buffer.cpp ${CMAKE_CURRENT_SOURCE_DIR}/extra/dng_sdk/dng_point.cpp ${CMAKE_CURRENT_SOURCE_DIR}/extra/dng_sdk/dng_preview.cpp ${CMAKE_CURRENT_SOURCE_DIR}/extra/dng_sdk/dng_pthread.cpp ${CMAKE_CURRENT_SOURCE_DIR}/extra/dng_sdk/dng_rational.cpp ${CMAKE_CURRENT_SOURCE_DIR}/extra/dng_sdk/dng_read_image.cpp ${CMAKE_CURRENT_SOURCE_DIR}/extra/dng_sdk/dng_rect.cpp ${CMAKE_CURRENT_SOURCE_DIR}/extra/dng_sdk/dng_ref_counted_block.cpp ${CMAKE_CURRENT_SOURCE_DIR}/extra/dng_sdk/dng_reference.cpp ${CMAKE_CURRENT_SOURCE_DIR}/extra/dng_sdk/dng_render.cpp ${CMAKE_CURRENT_SOURCE_DIR}/extra/dng_sdk/dng_resample.cpp ${CMAKE_CURRENT_SOURCE_DIR}/extra/dng_sdk/dng_safe_arithmetic.cpp ${CMAKE_CURRENT_SOURCE_DIR}/extra/dng_sdk/dng_shared.cpp ${CMAKE_CURRENT_SOURCE_DIR}/extra/dng_sdk/dng_simple_image.cpp ${CMAKE_CURRENT_SOURCE_DIR}/extra/dng_sdk/dng_spline.cpp ${CMAKE_CURRENT_SOURCE_DIR}/extra/dng_sdk/dng_stream.cpp ${CMAKE_CURRENT_SOURCE_DIR}/extra/dng_sdk/dng_string.cpp ${CMAKE_CURRENT_SOURCE_DIR}/extra/dng_sdk/dng_string_list.cpp ${CMAKE_CURRENT_SOURCE_DIR}/extra/dng_sdk/dng_tag_types.cpp ${CMAKE_CURRENT_SOURCE_DIR}/extra/dng_sdk/dng_temperature.cpp ${CMAKE_CURRENT_SOURCE_DIR}/extra/dng_sdk/dng_tile_iterator.cpp ${CMAKE_CURRENT_SOURCE_DIR}/extra/dng_sdk/dng_tone_curve.cpp ${CMAKE_CURRENT_SOURCE_DIR}/extra/dng_sdk/dng_utils.cpp ${CMAKE_CURRENT_SOURCE_DIR}/extra/dng_sdk/dng_validate.cpp ${CMAKE_CURRENT_SOURCE_DIR}/extra/dng_sdk/dng_xmp.cpp ${CMAKE_CURRENT_SOURCE_DIR}/extra/dng_sdk/dng_xmp_sdk.cpp ${CMAKE_CURRENT_SOURCE_DIR}/extra/dng_sdk/dng_xy_coord.cpp ) # Disable warnings: we will never touch this code. # Adjust flag for static lib and 64 bits computers using -fPIC for GCC compiler (bug: #269903) foreach(_currentfile ${libdng_SRCS}) if(MSVC) set_source_files_properties(${_currentfile} PROPERTIES COMPILE_FLAGS "-w") else() set_source_files_properties(${_currentfile} PROPERTIES COMPILE_FLAGS "-w -fPIC") endif() endforeach() add_library(libdng_src OBJECT ${libdng_SRCS}) #------------------------------------------------------------------------------------ set(libdngwriter_SRCS ${CMAKE_CURRENT_SOURCE_DIR}/dngwriter.cpp ${CMAKE_CURRENT_SOURCE_DIR}/dngwriter_p.cpp ${CMAKE_CURRENT_SOURCE_DIR}/dngwriter_convert.cpp ${CMAKE_CURRENT_SOURCE_DIR}/dngwriterhost.cpp ${CMAKE_CURRENT_SOURCE_DIR}/dngsettings.cpp ) add_library(dngwriter_src OBJECT ${libdngwriter_SRCS}) diff --git a/core/libs/dngwriter/extra/xmp_sdk/XMPCore/source/XMPMeta-GetSet.cpp b/core/libs/dngwriter/extra/xmp_sdk/XMPCore/source/XMPMeta-GetSet.cpp index 18c024c8cf..2de14ac5bd 100644 --- a/core/libs/dngwriter/extra/xmp_sdk/XMPCore/source/XMPMeta-GetSet.cpp +++ b/core/libs/dngwriter/extra/xmp_sdk/XMPCore/source/XMPMeta-GetSet.cpp @@ -1,1358 +1,1358 @@ // ================================================================================================= // Copyright 2003 Adobe Systems Incorporated // All Rights Reserved. // // NOTICE: Adobe permits you to use, modify, and distribute this file in accordance with the terms // of the Adobe license agreement accompanying it. // // Adobe patent application tracking #P435, entitled 'Unique markers to simplify embedding data of // one format in a file with a different format', inventors: Sean Parent, Greg Gilley. // ================================================================================================= #include "public/include/XMP_Environment.h" // ! This must be the first include! #include "XMPCore/source/XMPCore_Impl.hpp" #include "XMPCore/source/XMPMeta.hpp" #include "XMPCore/source/XMPIterator.hpp" #include "XMPCore/source/XMPUtils.hpp" #include "public/include/XMP_Version.h" #include "source/UnicodeInlines.incl_cpp" #include "source/UnicodeConversions.hpp" #include "source/ExpatAdapter.hpp" #include "XMPCore/XMPCoreDefines.h" #if ENABLE_CPP_DOM_MODEL -#include "third-party/zuid/interfaces/MD5.h" +#include "XMP_MD5.h" #include "XMPCore/Interfaces/IMetadata_I.h" #include "XMPCore/Interfaces/IPathSegment_I.h" #include "XMPCore/Interfaces/IPath_I.h" #include "XMPCore/Interfaces/INameSpacePrefixMap_I.h" #include "XMPCore/Interfaces/IDOMImplementationRegistry_I.h" #include "XMPCore/XMPCoreFwdDeclarations_I.h" #include "XMPCore/XMPCoreFwdDeclarations.h" #include "XMPCore/Interfaces/IStructureNode_I.h" #include "XMPCore/Interfaces/ISimpleNode_I.h" #include "XMPCore/Interfaces/IArrayNode_I.h" #include "XMPCore/Interfaces/INodeIterator.h" #include "XMPCore/Interfaces/ICoreObjectFactory.h" #include "XMPCommon/Interfaces/IUTF8String_I.h" #include "XMPCore/Interfaces/INode_I.h" #endif #if XMP_DebugBuild #include #endif using namespace std; #if XMP_WinBuild #pragma warning ( disable : 4533 ) // initialization of '...' is skipped by 'goto ...' #pragma warning ( disable : 4702 ) // unreachable code #pragma warning ( disable : 4800 ) // forcing value to bool 'true' or 'false' (performance warning) #endif // *** Use the XMP_PropIsXyz (Schema, Simple, Struct, Array, ...) macros // *** Add debug codegen checks, e.g. that typical masking operations really work // *** Change all uses of strcmp and strncmp to XMP_LitMatch and XMP_LitNMatch // ================================================================================================= // Local Types and Constants // ========================= typedef unsigned char XMP_CLTMatch; #if XMP_MARKER_EXTENSIBILITY_BACKWARD_COMPATIBILITY extern "C" { void ReleaseXMP_Node(void * node) { if (node) { XMP_Node * ptr = (XMP_Node *)node; delete ptr; ptr = NULL; } } } #if ENABLE_CPP_DOM_MODEL extern "C" { void ReleaseIStructureNode(void * node) { if (node) { AdobeXMPCore::pIStructureNode_base ptr = ( AdobeXMPCore::pIStructureNode_base )node; ptr->Release(); node = NULL; } } } #endif #endif enum { // Values for XMP_CLTMatch. kXMP_CLT_NoValues, kXMP_CLT_SpecificMatch, kXMP_CLT_SingleGeneric, kXMP_CLT_MultipleGeneric, kXMP_CLT_XDefault, kXMP_CLT_FirstItem }; // ================================================================================================= // Static Variables // ================ // ================================================================================================= // Local Utilities // =============== // ------------------------------------------------------------------------------------------------- // SetNodeValue // ------------ static inline void SetNodeValue ( XMP_Node * node, XMP_StringPtr value ) { node->SetValue( value ); } //SetNodeValue void XMP_Node::SetValue( XMP_StringPtr value ) { #if XMP_DebugBuild // ! Hack to force an assert. if ( (this->name == "xmp:TestAssertNotify") && XMP_LitMatch ( value, "DoIt!" ) ) { XMP_Assert ( this->name != "xmp:TestAssertNotify" ); } #endif std::string newValue = value; // Need a local copy to tweak and not change node.value for errors. XMP_Uns8* chPtr = (XMP_Uns8*) newValue.c_str(); // Check for valid UTF-8, replace ASCII controls with a space. while ( *chPtr != 0 ) { while ( (*chPtr != 0) && (*chPtr < 0x80) ) { if ( *chPtr < 0x20 ) { if ( (*chPtr != kTab) && (*chPtr != kLF) && (*chPtr != kCR) ) *chPtr = 0x20; } else if (*chPtr == 0x7F ) { *chPtr = 0x20; } ++chPtr; } XMP_Assert ( (*chPtr == 0) || (*chPtr >= 0x80) ); if ( *chPtr != 0 ) { XMP_Uns32 cp = GetCodePoint ( (const XMP_Uns8 **) &chPtr ); // Throws for bad UTF-8. if ( (cp == 0xFFFE) || (cp == 0xFFFF) ) { XMP_Throw ( "U+FFFE and U+FFFF are not allowed in XML", kXMPErr_BadUnicode ); } } } if ( XMP_PropIsQualifier(this->options) && (this->name == "xml:lang") ) NormalizeLangValue ( &newValue ); this->value.swap ( newValue ); #if 0 // *** XMP_DebugBuild this->_valuePtr = this->value.c_str(); #endif } // XMP_Node::SetValue // ------------------------------------------------------------------------------------------------- // SetNode // ------- // // The internals for SetProperty and related calls, used after the node is found or created. static void SetNode ( XMP_Node * node, XMP_StringPtr value, XMP_OptionBits options ) { if ( options & kXMP_DeleteExisting ) { XMP_ClearOption ( options, kXMP_DeleteExisting ); node->options = options; node->value.erase(); node->RemoveChildren(); node->RemoveQualifiers(); } node->options |= options; // Keep options set by FindNode when creating a new node. if ( value != 0 ) { // This is setting the value of a leaf node. if ( node->options & kXMP_PropCompositeMask ) XMP_Throw ( "Composite nodes can't have values", kXMPErr_BadXPath ); XMP_Assert ( node->children.empty() ); SetNodeValue ( node, value ); } else { // This is setting up an array or struct. if ( ! node->value.empty() ) XMP_Throw ( "Composite nodes can't have values", kXMPErr_BadXPath ); if ( node->options & kXMP_PropCompositeMask ) { // Can't change an array to a struct, or vice versa. if ( (options & kXMP_PropCompositeMask) != (node->options & kXMP_PropCompositeMask) ) { XMP_Throw ( "Requested and existing composite form mismatch", kXMPErr_BadXPath ); } } node->RemoveChildren(); } } // SetNode // ------------------------------------------------------------------------------------------------- // DoSetArrayItem // -------------- static void DoSetArrayItem ( XMP_Node * arrayNode, XMP_Index itemIndex, XMP_StringPtr itemValue, XMP_OptionBits options ) { XMP_OptionBits itemLoc = options & kXMP_PropArrayLocationMask; XMP_Index arraySize = static_cast( arrayNode->children.size() ); options &= ~kXMP_PropArrayLocationMask; options = VerifySetOptions ( options, itemValue ); // Now locate or create the item node and set the value. Note the index parameter is one-based! // The index can be in the range [0..size+1] or "last", normalize it and check the insert flags. // The order of the normalization checks is important. If the array is empty we end up with an // index and location to set item size+1. XMP_Node * itemNode = 0; if ( itemIndex == kXMP_ArrayLastItem ) itemIndex = arraySize; if ( (itemIndex == 0) && (itemLoc == kXMP_InsertAfterItem) ) { itemIndex = 1; itemLoc = kXMP_InsertBeforeItem; } if ( (itemIndex == arraySize) && (itemLoc == kXMP_InsertAfterItem) ) { itemIndex += 1; itemLoc = 0; } if ( (itemIndex == arraySize+1) && (itemLoc == kXMP_InsertBeforeItem) ) itemLoc = 0; if ( itemIndex == arraySize+1 ) { if ( itemLoc != 0 ) XMP_Throw ( "Can't insert before or after implicit new item", kXMPErr_BadIndex ); itemNode = new XMP_Node ( arrayNode, kXMP_ArrayItemName, 0 ); arrayNode->children.push_back ( itemNode ); } else { if ( (itemIndex < 1) || (itemIndex > arraySize) ) XMP_Throw ( "Array index out of bounds", kXMPErr_BadIndex ); --itemIndex; // ! Convert the index to a C zero-based value! if ( itemLoc == 0 ) { itemNode = arrayNode->children[itemIndex]; } else { XMP_NodePtrPos itemPos = arrayNode->children.begin() + itemIndex; if ( itemLoc == kXMP_InsertAfterItem ) ++itemPos; itemNode = new XMP_Node ( arrayNode, kXMP_ArrayItemName, 0 ); itemPos = arrayNode->children.insert ( itemPos, itemNode ); } } SetNode ( itemNode, itemValue, options ); } // DoSetArrayItem // ------------------------------------------------------------------------------------------------- // ChooseLocalizedText // ------------------- // // 1. Look for an exact match with the specific language. // 2. If a generic language is given, look for partial matches. // 3. Look for an "x-default" item. // 4. Choose the first item. static XMP_CLTMatch ChooseLocalizedText ( const XMP_Node * arrayNode, XMP_StringPtr genericLang, XMP_StringPtr specificLang, const XMP_Node * * itemNode ) { const XMP_Node * currItem = 0; const size_t itemLim = arrayNode->children.size(); size_t itemNum; // See if the array has the right form. Allow empty alt arrays, that is what parsing returns. // *** Should check alt-text bit when that is reliably maintained. if ( ! ( XMP_ArrayIsAltText(arrayNode->options) || (arrayNode->children.empty() && XMP_ArrayIsAlternate(arrayNode->options)) ) ) { XMP_Throw ( "Localized text array is not alt-text", kXMPErr_BadXPath ); } if ( arrayNode->children.empty() ) { *itemNode = 0; return kXMP_CLT_NoValues; } for ( itemNum = 0; itemNum < itemLim; ++itemNum ) { currItem = arrayNode->children[itemNum]; if ( currItem->options & kXMP_PropCompositeMask ) { XMP_Throw ( "Alt-text array item is not simple", kXMPErr_BadXPath ); } if ( currItem->qualifiers.empty() || (currItem->qualifiers[0]->name != "xml:lang") ) { XMP_Throw ( "Alt-text array item has no language qualifier", kXMPErr_BadXPath ); } } // Look for an exact match with the specific language. for ( itemNum = 0; itemNum < itemLim; ++itemNum ) { currItem = arrayNode->children[itemNum]; if ( currItem->qualifiers[0]->value == specificLang ) { *itemNode = currItem; return kXMP_CLT_SpecificMatch; } } if ( *genericLang != 0 ) { // Look for the first partial match with the generic language. const size_t genericLen = strlen ( genericLang ); for ( itemNum = 0; itemNum < itemLim; ++itemNum ) { currItem = arrayNode->children[itemNum]; XMP_StringPtr currLang = currItem->qualifiers[0]->value.c_str(); const size_t currLangSize = currItem->qualifiers[0]->value.size(); if ( (currLangSize >= genericLen) && XMP_LitNMatch ( currLang, genericLang, genericLen ) && ((currLangSize == genericLen) || (currLang[genericLen] == '-')) ) { *itemNode = currItem; break; // ! Don't return, need to look for other matches. } } if ( itemNum < itemLim ) { // Look for a second partial match with the generic language. for ( ++itemNum; itemNum < itemLim; ++itemNum ) { currItem = arrayNode->children[itemNum]; XMP_StringPtr currLang = currItem->qualifiers[0]->value.c_str(); const size_t currLangSize = currItem->qualifiers[0]->value.size(); if ( (currLangSize >= genericLen) && XMP_LitNMatch ( currLang, genericLang, genericLen ) && ((currLangSize == genericLen) || (currLang[genericLen] == '-')) ) { return kXMP_CLT_MultipleGeneric; // ! Leave itemNode with the first partial match. } } return kXMP_CLT_SingleGeneric; // No second partial match was found. } } // Look for an 'x-default' item. for ( itemNum = 0; itemNum < itemLim; ++itemNum ) { currItem = arrayNode->children[itemNum]; if ( currItem->qualifiers[0]->value == "x-default" ) { *itemNode = currItem; return kXMP_CLT_XDefault; } } // Everything failed, choose the first item. *itemNode = arrayNode->children[0]; return kXMP_CLT_FirstItem; } // ChooseLocalizedText // ------------------------------------------------------------------------------------------------- // AppendLangItem // -------------- static void AppendLangItem ( XMP_Node * arrayNode, XMP_StringPtr itemLang, XMP_StringPtr itemValue ) { XMP_Node * newItem = new XMP_Node ( arrayNode, kXMP_ArrayItemName, (kXMP_PropHasQualifiers | kXMP_PropHasLang) ); XMP_Node * langQual = new XMP_Node ( newItem, "xml:lang", kXMP_PropIsQualifier ); try { // ! Use SetNodeValue, not constructors above, to get the character checks. SetNodeValue ( newItem, itemValue ); SetNodeValue ( langQual, itemLang ); } catch (...) { delete newItem; delete langQual; throw; } newItem->qualifiers.push_back ( langQual ); if ( (arrayNode->children.empty()) || (langQual->value != "x-default") ) { arrayNode->children.push_back ( newItem ); } else { arrayNode->children.insert ( arrayNode->children.begin(), newItem ); } } // AppendLangItem // ================================================================================================= // Class Methods // ============= // // // ================================================================================================= // ------------------------------------------------------------------------------------------------- // GetProperty // ----------- bool XMPMeta::GetProperty ( XMP_StringPtr schemaNS, XMP_StringPtr propName, XMP_StringPtr * propValue, XMP_StringLen * valueSize, XMP_OptionBits * options ) const { XMP_Assert ( (schemaNS != 0) && (propName != 0) ); // Enforced by wrapper. XMP_Assert ( (propValue != 0) && (valueSize != 0) && (options != 0) ); // Enforced by wrapper. XMP_ExpandedXPath expPath; ExpandXPath ( schemaNS, propName, &expPath ); XMP_Node * propNode = FindConstNode ( &tree, expPath ); if ( propNode == 0 ) return false; *propValue = propNode->value.c_str(); *valueSize = static_cast( propNode->value.size() ); *options = propNode->options; return true; } // GetProperty // ------------------------------------------------------------------------------------------------- // GetArrayItem // ------------ bool XMPMeta::GetArrayItem ( XMP_StringPtr schemaNS, XMP_StringPtr arrayName, XMP_Index itemIndex, XMP_StringPtr * itemValue, XMP_StringLen * valueSize, XMP_OptionBits * options ) const { XMP_Assert ( (schemaNS != 0) && (arrayName != 0) ); // Enforced by wrapper. XMP_Assert ( (itemValue != 0) && (options != 0) ); // Enforced by wrapper. // ! Special case check to make errors consistent if the array does not exist. The other array // ! functions and existing array here (empty or not) already throw. if ( (itemIndex <= 0) && (itemIndex != kXMP_ArrayLastItem) ) XMP_Throw ( "Array index must be larger than zero", kXMPErr_BadXPath ); XMP_VarString itemPath; XMPUtils::ComposeArrayItemPath ( schemaNS, arrayName, itemIndex, &itemPath ); return GetProperty ( schemaNS, itemPath.c_str(), itemValue, valueSize, options ); } // GetArrayItem // ------------------------------------------------------------------------------------------------- // GetStructField // -------------- bool XMPMeta::GetStructField ( XMP_StringPtr schemaNS, XMP_StringPtr structName, XMP_StringPtr fieldNS, XMP_StringPtr fieldName, XMP_StringPtr * fieldValue, XMP_StringLen * valueSize, XMP_OptionBits * options ) const { XMP_Assert ( (schemaNS != 0) && (structName != 0) && (fieldNS != 0) && (fieldName != 0) ); // Enforced by wrapper. XMP_Assert ( (fieldValue != 0) && (options != 0) ); // Enforced by wrapper. XMP_VarString fieldPath; XMPUtils::ComposeStructFieldPath ( schemaNS, structName, fieldNS, fieldName, &fieldPath ); return GetProperty ( schemaNS, fieldPath.c_str(), fieldValue, valueSize, options ); } // GetStructField // ------------------------------------------------------------------------------------------------- // GetQualifier // ------------ bool XMPMeta::GetQualifier ( XMP_StringPtr schemaNS, XMP_StringPtr propName, XMP_StringPtr qualNS, XMP_StringPtr qualName, XMP_StringPtr * qualValue, XMP_StringLen * valueSize, XMP_OptionBits * options ) const { XMP_Assert ( (schemaNS != 0) && (propName != 0) && (qualNS != 0) && (qualName != 0) ); // Enforced by wrapper. XMP_Assert ( (qualValue != 0) && (options != 0) ); // Enforced by wrapper. XMP_VarString qualPath; XMPUtils::ComposeQualifierPath ( schemaNS, propName, qualNS, qualName, &qualPath ); return GetProperty ( schemaNS, qualPath.c_str(), qualValue, valueSize, options ); } // GetQualifier // ------------------------------------------------------------------------------------------------- // SetProperty // ----------- // *** Should handle array items specially, calling SetArrayItem. void XMPMeta::SetProperty ( XMP_StringPtr schemaNS, XMP_StringPtr propName, XMP_StringPtr propValue, XMP_OptionBits options ) { XMP_Assert ( (schemaNS != 0) && (propName != 0) ); // Enforced by wrapper. options = VerifySetOptions ( options, propValue ); XMP_ExpandedXPath expPath; ExpandXPath ( schemaNS, propName, &expPath ); XMP_Node * propNode = FindNode ( &tree, expPath, kXMP_CreateNodes, options ); if ( propNode == 0 ) XMP_Throw ( "Specified property does not exist", kXMPErr_BadXPath ); SetNode ( propNode, propValue, options ); } // SetProperty // ------------------------------------------------------------------------------------------------- // SetArrayItem // ------------ void XMPMeta::SetArrayItem ( XMP_StringPtr schemaNS, XMP_StringPtr arrayName, XMP_Index itemIndex, XMP_StringPtr itemValue, XMP_OptionBits options ) { XMP_Assert ( (schemaNS != 0) && (arrayName != 0) ); // Enforced by wrapper. XMP_ExpandedXPath arrayPath; ExpandXPath ( schemaNS, arrayName, &arrayPath ); XMP_Node * arrayNode = FindNode ( &tree, arrayPath, kXMP_ExistingOnly ); // Just lookup, don't try to create. if ( arrayNode == 0 ) XMP_Throw ( "Specified array does not exist", kXMPErr_BadXPath ); DoSetArrayItem ( arrayNode, itemIndex, itemValue, options ); } // SetArrayItem // ------------------------------------------------------------------------------------------------- // AppendArrayItem // --------------- void XMPMeta::AppendArrayItem ( XMP_StringPtr schemaNS, XMP_StringPtr arrayName, XMP_OptionBits arrayOptions, XMP_StringPtr itemValue, XMP_OptionBits options ) { XMP_Assert ( (schemaNS != 0) && (arrayName != 0) ); // Enforced by wrapper. arrayOptions = VerifySetOptions ( arrayOptions, 0 ); if ( (arrayOptions & ~kXMP_PropArrayFormMask) != 0 ) { XMP_Throw ( "Only array form flags allowed for arrayOptions", kXMPErr_BadOptions ); } // Locate or create the array. If it already exists, make sure the array form from the options // parameter is compatible with the current state. XMP_ExpandedXPath arrayPath; ExpandXPath ( schemaNS, arrayName, &arrayPath ); XMP_Node * arrayNode = FindNode ( &tree, arrayPath, kXMP_ExistingOnly ); // Just lookup, don't try to create. if ( arrayNode != 0 ) { // The array exists, make sure the form is compatible. Zero arrayForm means take what exists. if ( ! (arrayNode->options & kXMP_PropValueIsArray) ) { XMP_Throw ( "The named property is not an array", kXMPErr_BadXPath ); } #if 0 // *** Disable for now. Need to do some general rethinking of semantic checks. if ( (arrayOptions != 0) && (arrayOptions != (arrayNode->options & kXMP_PropArrayFormMask)) ) { XMP_Throw ( "Mismatch of existing and specified array form", kXMPErr_BadOptions ); } #endif } else { // The array does not exist, try to create it. if ( arrayOptions == 0 ) XMP_Throw ( "Explicit arrayOptions required to create new array", kXMPErr_BadOptions ); arrayNode = FindNode ( &tree, arrayPath, kXMP_CreateNodes, arrayOptions ); if ( arrayNode == 0 ) XMP_Throw ( "Failure creating array node", kXMPErr_BadXPath ); } DoSetArrayItem ( arrayNode, kXMP_ArrayLastItem, itemValue, (options | kXMP_InsertAfterItem) ); } // AppendArrayItem // ------------------------------------------------------------------------------------------------- // SetStructField // -------------- void XMPMeta::SetStructField ( XMP_StringPtr schemaNS, XMP_StringPtr structName, XMP_StringPtr fieldNS, XMP_StringPtr fieldName, XMP_StringPtr fieldValue, XMP_OptionBits options ) { XMP_Assert ( (schemaNS != 0) && (structName != 0) && (fieldNS != 0) && (fieldName != 0) ); // Enforced by wrapper. XMP_VarString fieldPath; XMPUtils::ComposeStructFieldPath ( schemaNS, structName, fieldNS, fieldName, &fieldPath ); SetProperty ( schemaNS, fieldPath.c_str(), fieldValue, options ); } // SetStructField // ------------------------------------------------------------------------------------------------- // SetQualifier // ------------ void XMPMeta::SetQualifier ( XMP_StringPtr schemaNS, XMP_StringPtr propName, XMP_StringPtr qualNS, XMP_StringPtr qualName, XMP_StringPtr qualValue, XMP_OptionBits options ) { XMP_Assert ( (schemaNS != 0) && (propName != 0) && (qualNS != 0) && (qualName != 0) ); // Enforced by wrapper. XMP_ExpandedXPath expPath; ExpandXPath ( schemaNS, propName, &expPath ); XMP_Node * propNode = FindNode ( &tree, expPath, kXMP_ExistingOnly ); if ( propNode == 0 ) XMP_Throw ( "Specified property does not exist", kXMPErr_BadXPath ); XMP_VarString qualPath; XMPUtils::ComposeQualifierPath ( schemaNS, propName, qualNS, qualName, &qualPath ); SetProperty ( schemaNS, qualPath.c_str(), qualValue, options ); } // SetQualifier // ------------------------------------------------------------------------------------------------- // DeleteProperty // -------------- void XMPMeta::DeleteProperty ( XMP_StringPtr schemaNS, XMP_StringPtr propName ) { XMP_Assert ( (schemaNS != 0) && (propName != 0) ); // Enforced by wrapper. XMP_ExpandedXPath expPath; ExpandXPath ( schemaNS, propName, &expPath ); XMP_NodePtrPos ptrPos; XMP_Node * propNode = FindNode ( &tree, expPath, kXMP_ExistingOnly, kXMP_NoOptions, &ptrPos ); if ( propNode == 0 ) return; XMP_Node * parentNode = propNode->parent; // Erase the pointer from the parent's vector, then delete the node and all below it. if ( ! (propNode->options & kXMP_PropIsQualifier) ) { parentNode->children.erase ( ptrPos ); DeleteEmptySchema ( parentNode ); } else { if ( propNode->name == "xml:lang" ) { XMP_Assert ( parentNode->options & kXMP_PropHasLang ); // *** &= ~flag would be safer parentNode->options ^= kXMP_PropHasLang; } else if ( propNode->name == "rdf:type" ) { XMP_Assert ( parentNode->options & kXMP_PropHasType ); parentNode->options ^= kXMP_PropHasType; } parentNode->qualifiers.erase ( ptrPos ); XMP_Assert ( parentNode->options & kXMP_PropHasQualifiers ); if ( parentNode->qualifiers.empty() ) parentNode->options ^= kXMP_PropHasQualifiers; } delete propNode; // ! The destructor takes care of the whole subtree. } // DeleteProperty // ------------------------------------------------------------------------------------------------- // DeleteArrayItem // --------------- void XMPMeta::DeleteArrayItem ( XMP_StringPtr schemaNS, XMP_StringPtr arrayName, XMP_Index itemIndex ) { XMP_Assert ( (schemaNS != 0) && (arrayName != 0) ); // Enforced by wrapper. XMP_VarString itemPath; XMPUtils::ComposeArrayItemPath ( schemaNS, arrayName, itemIndex, &itemPath ); DeleteProperty ( schemaNS, itemPath.c_str() ); } // DeleteArrayItem // ------------------------------------------------------------------------------------------------- // DeleteStructField // ----------------- void XMPMeta::DeleteStructField ( XMP_StringPtr schemaNS, XMP_StringPtr structName, XMP_StringPtr fieldNS, XMP_StringPtr fieldName ) { XMP_Assert ( (schemaNS != 0) && (structName != 0) && (fieldNS != 0) && (fieldName != 0) ); // Enforced by wrapper. XMP_VarString fieldPath; XMPUtils::ComposeStructFieldPath ( schemaNS, structName, fieldNS, fieldName, &fieldPath ); DeleteProperty ( schemaNS, fieldPath.c_str() ); } // DeleteStructField // ------------------------------------------------------------------------------------------------- // DeleteQualifier // --------------- void XMPMeta::DeleteQualifier ( XMP_StringPtr schemaNS, XMP_StringPtr propName, XMP_StringPtr qualNS, XMP_StringPtr qualName ) { XMP_Assert ( (schemaNS != 0) && (propName != 0) && (qualNS != 0) && (qualName != 0) ); // Enforced by wrapper. XMP_VarString qualPath; XMPUtils::ComposeQualifierPath ( schemaNS, propName, qualNS, qualName, &qualPath ); DeleteProperty ( schemaNS, qualPath.c_str() ); } // DeleteQualifier // ------------------------------------------------------------------------------------------------- // DoesPropertyExist // ----------------- bool XMPMeta::DoesPropertyExist ( XMP_StringPtr schemaNS, XMP_StringPtr propName ) const { XMP_Assert ( (schemaNS != 0) && (propName != 0) ); // Enforced by wrapper. XMP_ExpandedXPath expPath; ExpandXPath ( schemaNS, propName, &expPath ); XMP_Node * propNode = FindConstNode ( &tree, expPath ); return (propNode != 0); } // DoesPropertyExist // ------------------------------------------------------------------------------------------------- // DoesArrayItemExist // ------------------ bool XMPMeta::DoesArrayItemExist ( XMP_StringPtr schemaNS, XMP_StringPtr arrayName, XMP_Index itemIndex ) const { XMP_Assert ( (schemaNS != 0) && (arrayName != 0) ); // Enforced by wrapper. XMP_VarString itemPath; XMPUtils::ComposeArrayItemPath ( schemaNS, arrayName, itemIndex, &itemPath ); return DoesPropertyExist ( schemaNS, itemPath.c_str() ); } // DoesArrayItemExist // ------------------------------------------------------------------------------------------------- // DoesStructFieldExist // -------------------- bool XMPMeta::DoesStructFieldExist ( XMP_StringPtr schemaNS, XMP_StringPtr structName, XMP_StringPtr fieldNS, XMP_StringPtr fieldName ) const { XMP_Assert ( (schemaNS != 0) && (structName != 0) && (fieldNS != 0) && (fieldName != 0) ); // Enforced by wrapper. XMP_VarString fieldPath; XMPUtils::ComposeStructFieldPath ( schemaNS, structName, fieldNS, fieldName, &fieldPath ); return DoesPropertyExist ( schemaNS, fieldPath.c_str() ); } // DoesStructFieldExist // ------------------------------------------------------------------------------------------------- // DoesQualifierExist // ------------------ bool XMPMeta::DoesQualifierExist ( XMP_StringPtr schemaNS, XMP_StringPtr propName, XMP_StringPtr qualNS, XMP_StringPtr qualName ) const { XMP_Assert ( (schemaNS != 0) && (propName != 0) && (qualNS != 0) && (qualName != 0) ); // Enforced by wrapper. XMP_VarString qualPath; XMPUtils::ComposeQualifierPath ( schemaNS, propName, qualNS, qualName, &qualPath ); return DoesPropertyExist ( schemaNS, qualPath.c_str() ); } // DoesQualifierExist // ------------------------------------------------------------------------------------------------- // GetLocalizedText // ---------------- bool XMPMeta::GetLocalizedText ( XMP_StringPtr schemaNS, XMP_StringPtr arrayName, XMP_StringPtr _genericLang, XMP_StringPtr _specificLang, XMP_StringPtr * actualLang, XMP_StringLen * langSize, XMP_StringPtr * itemValue, XMP_StringLen * valueSize, XMP_OptionBits * options ) const { XMP_Assert ( (schemaNS != 0) && (arrayName != 0) && (_genericLang != 0) && (_specificLang != 0) ); // Enforced by wrapper. XMP_Assert ( (actualLang != 0) && (langSize != 0) ); // Enforced by wrapper. XMP_Assert ( (itemValue != 0) && (valueSize != 0) && (options != 0) ); // Enforced by wrapper. XMP_VarString zGenericLang ( _genericLang ); XMP_VarString zSpecificLang ( _specificLang ); NormalizeLangValue ( &zGenericLang ); NormalizeLangValue ( &zSpecificLang ); XMP_StringPtr genericLang = zGenericLang.c_str(); XMP_StringPtr specificLang = zSpecificLang.c_str(); XMP_ExpandedXPath arrayPath; ExpandXPath ( schemaNS, arrayName, &arrayPath ); const XMP_Node * arrayNode = FindConstNode ( &tree, arrayPath ); // *** This expand/find idiom is used in 3 Getters. if ( arrayNode == 0 ) return false; // *** Should extract it into a local utility. XMP_CLTMatch match; const XMP_Node * itemNode; match = ChooseLocalizedText ( arrayNode, genericLang, specificLang, &itemNode ); if ( match == kXMP_CLT_NoValues ) return false; *actualLang = itemNode->qualifiers[0]->value.c_str(); *langSize = static_cast( itemNode->qualifiers[0]->value.size() ); *itemValue = itemNode->value.c_str(); *valueSize = static_cast( itemNode->value.size() ); *options = itemNode->options; return true; } // GetLocalizedText // ------------------------------------------------------------------------------------------------- // SetLocalizedText // ---------------- void XMPMeta::SetLocalizedText ( XMP_StringPtr schemaNS, XMP_StringPtr arrayName, XMP_StringPtr _genericLang, XMP_StringPtr _specificLang, XMP_StringPtr itemValue, XMP_OptionBits options ) { IgnoreParam(options); XMP_Assert ( (schemaNS != 0) && (arrayName != 0) && (_genericLang != 0) && (_specificLang != 0) ); // Enforced by wrapper. XMP_VarString zGenericLang ( _genericLang ); XMP_VarString zSpecificLang ( _specificLang ); NormalizeLangValue ( &zGenericLang ); NormalizeLangValue ( &zSpecificLang ); XMP_StringPtr genericLang = zGenericLang.c_str(); XMP_StringPtr specificLang = zSpecificLang.c_str(); XMP_ExpandedXPath arrayPath; ExpandXPath ( schemaNS, arrayName, &arrayPath ); // Find the array node and set the options if it was just created. XMP_Node * arrayNode = FindNode ( &tree, arrayPath, kXMP_CreateNodes, (kXMP_PropValueIsArray | kXMP_PropArrayIsOrdered | kXMP_PropArrayIsAlternate) ); if ( arrayNode == 0 ) XMP_Throw ( "Failed to find or create array node", kXMPErr_BadXPath ); if ( ! XMP_ArrayIsAltText(arrayNode->options) ) { if ( arrayNode->children.empty() && XMP_ArrayIsAlternate(arrayNode->options) ) { arrayNode->options |= kXMP_PropArrayIsAltText; } else { XMP_Throw ( "Localized text array is not alt-text", kXMPErr_BadXPath ); } } // Make sure the x-default item, if any, is first. size_t itemNum, itemLim; XMP_Node * xdItem = 0; bool haveXDefault = false; for ( itemNum = 0, itemLim = arrayNode->children.size(); itemNum < itemLim; ++itemNum ) { XMP_Node * currItem = arrayNode->children[itemNum]; XMP_Assert ( XMP_PropHasLang(currItem->options) ); if ( currItem->qualifiers.empty() || (currItem->qualifiers[0]->name != "xml:lang") ) { XMP_Throw ( "Language qualifier must be first", kXMPErr_BadXPath ); } if ( currItem->qualifiers[0]->value == "x-default" ) { xdItem = currItem; haveXDefault = true; break; } } if ( haveXDefault && (itemNum != 0) ) { XMP_Assert ( arrayNode->children[itemNum]->qualifiers[0]->value == "x-default" ); XMP_Node * temp = arrayNode->children[0]; arrayNode->children[0] = arrayNode->children[itemNum]; arrayNode->children[itemNum] = temp; } // Find the appropriate item. ChooseLocalizedText will make sure the array is a language alternative. const XMP_Node * cItemNode; // ! ChooseLocalizedText returns a pointer to a const node. XMP_CLTMatch match = ChooseLocalizedText ( arrayNode, genericLang, specificLang, &cItemNode ); XMP_Node * itemNode = const_cast ( cItemNode ); const bool specificXDefault = XMP_LitMatch ( specificLang, "x-default" ); switch ( match ) { case kXMP_CLT_NoValues : // Create the array items for the specificLang and x-default, with x-default first. AppendLangItem ( arrayNode, "x-default", itemValue ); haveXDefault = true; if ( ! specificXDefault ) AppendLangItem ( arrayNode, specificLang, itemValue ); break; case kXMP_CLT_SpecificMatch : if ( ! specificXDefault ) { // Update the specific item, update x-default if it matches the old value. if ( xdItem != NULL && haveXDefault && (xdItem != itemNode) && (xdItem->value == itemNode->value) ) { SetNodeValue ( xdItem, itemValue ); } SetNodeValue ( itemNode, itemValue ); // ! Do this after the x-default check! } else { // Update all items whose values match the old x-default value. XMP_Assert ( xdItem != NULL && haveXDefault && (xdItem == itemNode) ); for ( itemNum = 0, itemLim = arrayNode->children.size(); itemNum < itemLim; ++itemNum ) { XMP_Node * currItem = arrayNode->children[itemNum]; if ( (currItem == xdItem) || (currItem->value != xdItem->value) ) continue; SetNodeValue ( currItem, itemValue ); } SetNodeValue ( xdItem, itemValue ); // And finally do the x-default item. } break; case kXMP_CLT_SingleGeneric : // Update the generic item, update x-default if it matches the old value. if ( xdItem != NULL && haveXDefault && (xdItem != itemNode) && (xdItem->value == itemNode->value) ) { SetNodeValue ( xdItem, itemValue ); } SetNodeValue ( itemNode, itemValue ); // ! Do this after the x-default check! break; case kXMP_CLT_MultipleGeneric : // Create the specific language, ignore x-default. AppendLangItem ( arrayNode, specificLang, itemValue ); if ( specificXDefault ) haveXDefault = true; break; case kXMP_CLT_XDefault : // Create the specific language, update x-default if it was the only item. if ( arrayNode->children.size() == 1 ) SetNodeValue ( xdItem, itemValue ); AppendLangItem ( arrayNode, specificLang, itemValue ); break; case kXMP_CLT_FirstItem : // Create the specific language, don't add an x-default item. AppendLangItem ( arrayNode, specificLang, itemValue ); if ( specificXDefault ) haveXDefault = true; break; default : XMP_Throw ( "Unexpected result from ChooseLocalizedText", kXMPErr_InternalFailure ); } // Add an x-default at the front if needed. if ( (! haveXDefault) && (arrayNode->children.size() == 1) ) { AppendLangItem ( arrayNode, "x-default", itemValue ); } } // SetLocalizedText // ------------------------------------------------------------------------------------------------- // DeleteLocalizedText // ------------------- void XMPMeta::DeleteLocalizedText ( XMP_StringPtr schemaNS, XMP_StringPtr arrayName, XMP_StringPtr _genericLang, XMP_StringPtr _specificLang ) { XMP_Assert ( (schemaNS != 0) && (arrayName != 0) && (_genericLang != 0) && (_specificLang != 0) ); // Enforced by wrapper. XMP_VarString zGenericLang ( _genericLang ); XMP_VarString zSpecificLang ( _specificLang ); NormalizeLangValue ( &zGenericLang ); NormalizeLangValue ( &zSpecificLang ); XMP_StringPtr genericLang = zGenericLang.c_str(); XMP_StringPtr specificLang = zSpecificLang.c_str(); XMP_ExpandedXPath arrayPath; ExpandXPath ( schemaNS, arrayName, &arrayPath ); // Find the LangAlt array and the selected array item. XMP_Node * arrayNode = FindNode ( &tree, arrayPath, kXMP_ExistingOnly ); if ( arrayNode == 0 ) return; size_t arraySize = arrayNode->children.size(); XMP_CLTMatch match; XMP_Node * itemNode; match = ChooseLocalizedText ( arrayNode, genericLang, specificLang, (const XMP_Node **) &itemNode ); if ( match != kXMP_CLT_SpecificMatch ) return; size_t itemIndex = 0; for ( ; itemIndex < arraySize; ++itemIndex ) { if ( arrayNode->children[itemIndex] == itemNode ) break; } XMP_Enforce ( itemIndex < arraySize ); // Decide if the selected item is x-default or not, find relevant matching item. bool itemIsXDefault = false; if ( ! itemNode->qualifiers.empty() ) { XMP_Node * qualNode = itemNode->qualifiers[0]; if ( (qualNode->name == "xml:lang") && (qualNode->value == "x-default") ) itemIsXDefault = true; } if ( itemIsXDefault && (itemIndex != 0) ) { // Enforce the x-default is first policy. XMP_Node * temp = arrayNode->children[0]; arrayNode->children[0] = arrayNode->children[itemIndex]; arrayNode->children[itemIndex] = temp; itemIndex = 0; } XMP_Node * assocNode = 0; size_t assocIndex; size_t assocIsXDefault = false; if ( itemIsXDefault ) { for ( assocIndex = 1; assocIndex < arraySize; ++assocIndex ) { if ( arrayNode->children[assocIndex]->value == itemNode->value ) { assocNode = arrayNode->children[assocIndex]; break; } } } else if ( itemIndex > 0 ) { XMP_Node * itemZero = arrayNode->children[0]; if ( itemZero->value == itemNode->value ) { XMP_Node * qualNode = itemZero->qualifiers[0]; if ( (qualNode->name == "xml:lang") && (qualNode->value == "x-default") ) { assocNode = arrayNode->children[0]; assocIndex = 0; assocIsXDefault = true; } } } // Delete the appropriate nodes. XMP_NodePtrPos arrayBegin = arrayNode->children.begin(); if ( assocNode == 0 ) { arrayNode->children.erase ( arrayBegin + itemIndex ); } else if ( itemIndex < assocIndex ) { arrayNode->children.erase ( arrayBegin + assocIndex ); arrayNode->children.erase ( arrayBegin + itemIndex ); } else { arrayNode->children.erase ( arrayBegin + itemIndex ); arrayNode->children.erase ( arrayBegin + assocIndex ); } delete itemNode; if ( assocNode != 0 ) delete assocNode; } // DeleteLocalizedText // ------------------------------------------------------------------------------------------------- // GetProperty_Bool // ---------------- bool XMPMeta::GetProperty_Bool ( XMP_StringPtr schemaNS, XMP_StringPtr propName, bool * propValue, XMP_OptionBits * options ) const { XMP_Assert ( (schemaNS != 0) && (propName != 0) ); // Enforced by wrapper. XMP_Assert ( (propValue != 0) && (options != 0) ); // Enforced by wrapper. XMP_StringPtr valueStr; XMP_StringLen valueLen; bool found = GetProperty ( schemaNS, propName, &valueStr, &valueLen, options ); if ( found ) { if ( ! XMP_PropIsSimple ( *options ) ) XMP_Throw ( "Property must be simple", kXMPErr_BadXPath ); *propValue = XMPUtils::ConvertToBool ( valueStr ); } return found; } // GetProperty_Bool // ------------------------------------------------------------------------------------------------- // GetProperty_Int // --------------- bool XMPMeta::GetProperty_Int ( XMP_StringPtr schemaNS, XMP_StringPtr propName, XMP_Int32 * propValue, XMP_OptionBits * options ) const { XMP_Int64 tempValue64 = 0; if ( GetProperty_Int64( schemaNS, propName, &tempValue64, options ) ) { if ( tempValue64 < (XMP_Int64) Min_XMP_Int32 || tempValue64 > (XMP_Int64) Max_XMP_Int32 ) { // overflow condition XMP_Throw ( "Overflow condition", kXMPErr_BadValue ); } else { *propValue = (XMP_Int32) tempValue64; return true; } } return false; } // GetProperty_Int // ------------------------------------------------------------------------------------------------- // GetProperty_Int64 // ----------------- bool XMPMeta::GetProperty_Int64 ( XMP_StringPtr schemaNS, XMP_StringPtr propName, XMP_Int64 * propValue, XMP_OptionBits * options ) const { XMP_Assert ( (schemaNS != 0) && (propName != 0) ); // Enforced by wrapper. XMP_Assert ( (propValue != 0) && (options != 0) ); // Enforced by wrapper. XMP_StringPtr valueStr; XMP_StringLen valueLen; bool found = GetProperty ( schemaNS, propName, &valueStr, &valueLen, options ); if ( found ) { if ( ! XMP_PropIsSimple ( *options ) ) XMP_Throw ( "Property must be simple", kXMPErr_BadXPath ); std::string propValueStr; propValueStr.append( valueStr, valueLen ); XMPUtils::Trim( propValueStr ); *propValue = XMPUtils::ConvertToInt64 ( propValueStr.c_str() ); } return found; } // GetProperty_Int64 // ------------------------------------------------------------------------------------------------- // GetProperty_Float // ----------------- bool XMPMeta::GetProperty_Float ( XMP_StringPtr schemaNS, XMP_StringPtr propName, double * propValue, XMP_OptionBits * options ) const { XMP_Assert ( (schemaNS != 0) && (propName != 0) ); // Enforced by wrapper. XMP_Assert ( (propValue != 0) && (options != 0) ); // Enforced by wrapper. XMP_StringPtr valueStr; XMP_StringLen valueLen; bool found = GetProperty ( schemaNS, propName, &valueStr, &valueLen, options ); if ( found ) { if ( ! XMP_PropIsSimple ( *options ) ) XMP_Throw ( "Property must be simple", kXMPErr_BadXPath ); std::string propValueStr; propValueStr.append( valueStr, valueLen ); XMPUtils::Trim( propValueStr ); *propValue = XMPUtils::ConvertToFloat ( propValueStr.c_str() ); } return found; } // GetProperty_Float // ------------------------------------------------------------------------------------------------- // GetProperty_Date // ---------------- bool XMPMeta::GetProperty_Date ( XMP_StringPtr schemaNS, XMP_StringPtr propName, XMP_DateTime * propValue, XMP_OptionBits * options ) const { XMP_Assert ( (schemaNS != 0) && (propName != 0) ); // Enforced by wrapper. XMP_Assert ( (propValue != 0) && (options != 0) ); // Enforced by wrapper. XMP_StringPtr valueStr; XMP_StringLen valueLen; bool found = GetProperty ( schemaNS, propName, &valueStr, &valueLen, options ); if ( found ) { if ( ! XMP_PropIsSimple ( *options ) ) XMP_Throw ( "Property must be simple", kXMPErr_BadXPath ); XMPUtils::ConvertToDate ( valueStr, propValue ); } return found; } // GetProperty_Date // ------------------------------------------------------------------------------------------------- // SetProperty_Bool // ---------------- void XMPMeta::SetProperty_Bool ( XMP_StringPtr schemaNS, XMP_StringPtr propName, bool propValue, XMP_OptionBits options ) { XMP_Assert ( (schemaNS != 0) && (propName != 0) ); // Enforced by wrapper. XMP_VarString valueStr; XMPUtils::ConvertFromBool ( propValue, &valueStr ); SetProperty ( schemaNS, propName, valueStr.c_str(), options ); } // SetProperty_Bool // ------------------------------------------------------------------------------------------------- // SetProperty_Int // --------------- void XMPMeta::SetProperty_Int ( XMP_StringPtr schemaNS, XMP_StringPtr propName, XMP_Int32 propValue, XMP_OptionBits options ) { XMP_Assert ( (schemaNS != 0) && (propName != 0) ); // Enforced by wrapper. XMP_VarString valueStr; XMPUtils::ConvertFromInt ( propValue, "", &valueStr ); SetProperty ( schemaNS, propName, valueStr.c_str(), options ); } // SetProperty_Int // ------------------------------------------------------------------------------------------------- // SetProperty_Int64 // ----------------- void XMPMeta::SetProperty_Int64 ( XMP_StringPtr schemaNS, XMP_StringPtr propName, XMP_Int64 propValue, XMP_OptionBits options ) { XMP_Assert ( (schemaNS != 0) && (propName != 0) ); // Enforced by wrapper. XMP_VarString valueStr; XMPUtils::ConvertFromInt64 ( propValue, "", &valueStr ); SetProperty ( schemaNS, propName, valueStr.c_str(), options ); } // SetProperty_Int64 // ------------------------------------------------------------------------------------------------- // SetProperty_Float // ----------------- void XMPMeta::SetProperty_Float ( XMP_StringPtr schemaNS, XMP_StringPtr propName, double propValue, XMP_OptionBits options ) { XMP_Assert ( (schemaNS != 0) && (propName != 0) ); // Enforced by wrapper. XMP_VarString valueStr; XMPUtils::ConvertFromFloat ( propValue, "", &valueStr ); SetProperty ( schemaNS, propName, valueStr.c_str(), options ); } // SetProperty_Float // ------------------------------------------------------------------------------------------------- // SetProperty_Date // ---------------- void XMPMeta::SetProperty_Date ( XMP_StringPtr schemaNS, XMP_StringPtr propName, const XMP_DateTime & propValue, XMP_OptionBits options ) { XMP_Assert ( (schemaNS != 0) && (propName != 0) ); // Enforced by wrapper. XMP_VarString valueStr; XMPUtils::ConvertFromDate ( propValue, &valueStr ); SetProperty ( schemaNS, propName, valueStr.c_str(), options ); } // SetProperty_Date // ================================================================================================= diff --git a/core/libs/dngwriter/extra/xmp_sdk/XMPCore/source/XMPMeta2-GetSet.cpp b/core/libs/dngwriter/extra/xmp_sdk/XMPCore/source/XMPMeta2-GetSet.cpp index f7f1d9bd50..d16dacb0df 100644 --- a/core/libs/dngwriter/extra/xmp_sdk/XMPCore/source/XMPMeta2-GetSet.cpp +++ b/core/libs/dngwriter/extra/xmp_sdk/XMPCore/source/XMPMeta2-GetSet.cpp @@ -1,1356 +1,1356 @@ // ================================================================================================= // Copyright 2003 Adobe Systems Incorporated // All Rights Reserved. // // NOTICE: Adobe permits you to use, modify, and distribute this file in accordance with the terms // of the Adobe license agreement accompanying it. // // Adobe patent application tracking #P435, entitled 'Unique markers to simplify embedding data of // one format in a file with a different format', inventors: Sean Parent, Greg Gilley. // ================================================================================================= // ================================================================================================= #include "XMPCore/XMPCoreDefines.h" #if ENABLE_CPP_DOM_MODEL #include "public/include/XMP_Environment.h" // ! This must be the first include! #include "XMPCore/source/XMPCore_Impl.hpp" #include "XMPCore/source/XMPMeta2.hpp" #include "XMPCore/source/XMPIterator.hpp" #include "XMPCore/source/XMPUtils.hpp" #include "public/include/XMP_Version.h" #include "source/UnicodeInlines.incl_cpp" #include "source/UnicodeConversions.hpp" #include "source/ExpatAdapter.hpp" -#include "third-party/zuid/interfaces/MD5.h" +#include "XMP_MD5.h" #include "XMPCore/Interfaces/IMetadata_I.h" #include "XMPCore/Interfaces/IArrayNode_I.h" #include "XMPCore/Interfaces/ISimpleNode_I.h" #include "XMPCommon/Interfaces/IUTF8String_I.h" #include "XMPCore/Interfaces/IPathSegment_I.h" #include "XMPCore/Interfaces/IPath_I.h" #include "XMPCore/Interfaces/INameSpacePrefixMap_I.h" #include "XMPCore/Interfaces/IDOMImplementationRegistry_I.h" #include "XMPCore/Interfaces/IDOMParser.h" #include "XMPCore/Interfaces/IDOMSerializer.h" #include "XMPCore/Interfaces/INodeIterator.h" #include "XMPCore/Interfaces/IDOMParser_I.h" #include "XMPCore/Interfaces/IDOMSerializer_I.h" #include "XMPCore/Interfaces/ICoreConfigurationManager.h" #if XMP_DebugBuild #include #endif using namespace std; #if XMP_WinBuild #pragma warning ( disable : 4533 ) // initialization of '...' is skipped by 'goto ...' #pragma warning ( disable : 4702 ) // unreachable code #pragma warning ( disable : 4800 ) // forcing value to bool 'true' or 'false' (performance warning) #endif // *** Use the XMP_PropIsXyz (Schema, Simple, Struct, Array, ...) macros // *** Add debug codegen checks, e.g. that typical masking operations really work // *** Change all uses of strcmp and strncmp to XMP_LitMatch and XMP_LitNMatch // ================================================================================================= // Local Types and Constants // ========================= typedef unsigned char XMP_CLTMatch; enum { // Values for XMP_CLTMatch. kXMP_CLT_NoValues, kXMP_CLT_SpecificMatch, kXMP_CLT_SingleGeneric, kXMP_CLT_MultipleGeneric, kXMP_CLT_XDefault, kXMP_CLT_FirstItem }; const XMP_VarString xmlNameSpace = "http://www.w3.org/XML/1998/namespace"; // ================================================================================================= // Static Variables // ================ // ================================================================================================= // Local Utilities // =============== extern void SplitNameAndValue ( const XMP_VarString & selStep, XMP_VarString * nameStr, XMP_VarString * valueStr ); extern void DumpNodeOptions ( XMP_OptionBits options,XMP_TextOutputProc outProc,void *refCon ); using namespace AdobeXMPCore_Int; using namespace AdobeXMPCommon_Int; static void AppendIXMPLangItem ( const spIArrayNode & arrayNode, XMP_StringPtr itemLang, XMP_StringPtr itemValue ) { spISimpleNode newItem = ISimpleNode::CreateSimpleNode( arrayNode->GetNameSpace()->c_str(), arrayNode->GetNameSpace()->size(), arrayNode->GetName()->c_str(), arrayNode->GetName()->size(), "", AdobeXMPCommon::npos ); spISimpleNode langQual = ISimpleNode::CreateSimpleNode( xmlNameSpace.c_str(), xmlNameSpace.size(), "lang", AdobeXMPCommon::npos, "", AdobeXMPCommon::npos ); try { XMPUtils::SetNode(newItem,itemValue,(kXMP_PropHasQualifiers | kXMP_PropHasLang)); XMPUtils::SetNode(langQual, itemLang, kXMP_PropIsQualifier); } catch (...) { newItem->Clear(); langQual->Clear(); throw; } newItem->InsertQualifier(langQual); if ( (!arrayNode->ChildCount() || !XMP_LitMatch(langQual->GetValue()->c_str(),"x-default") )) { size_t arraySize = arrayNode->ChildCount(); arrayNode->InsertNodeAtIndex(newItem, arraySize + 1); } else { arrayNode->InsertNodeAtIndex(newItem, 1); } } // AppendLangItem // ------------------------------------------------------------------------------------------------- // GetProperty // ----------- XMPMeta2::XMPMeta2() { mDOM = IMetadata::CreateMetadata(); mDOM->EnableFeature("alias", 5); spRegistry = IDOMImplementationRegistry::GetDOMImplementationRegistry(); spParser = spRegistry->GetParser( "rdf" ); } XMPMeta2::~XMPMeta2() { } bool XMPMeta2::GetProperty ( XMP_StringPtr schemaNS, XMP_StringPtr propName, XMP_StringPtr * propValue, XMP_StringLen * valueSize, XMP_OptionBits * options ) const { XMP_Assert ( (schemaNS != 0) && (propName != 0) ); // Enforced by wrapper. XMP_Assert ( (propValue != 0) && (valueSize != 0) && (options != 0) ); // Enforced by wrapper. XMP_ExpandedXPath expPath; ExpandXPath ( schemaNS, propName, &expPath ); auto defaultMap = INameSpacePrefixMap::GetDefaultNameSpacePrefixMap(); spINode destNode = mDOM; bool qualifierFlag = false; size_t pathStartIdx = 1; if (expPath[kRootPropStep].options & kXMP_StepIsAlias) { if (!XMPUtils::HandleConstAliasStep(mDOM, destNode, expPath, 0)) return false; pathStartIdx = 2; } for ( size_t i = pathStartIdx, endIndex = expPath.size(); i < endIndex; i++ ) { if(!destNode) return false; XMP_VarString stepStr = expPath[i].step; XMP_VarString prevStep = ( i == 0 ) ? "" : expPath[i - 1].step; spcIUTF8String nameSpace ; switch( expPath[i].options ) { case kXMP_StructFieldStep: { size_t colonPos = stepStr.find(':'); XMP_VarString prefix = stepStr.substr( 0, colonPos ); // get the namespace from the prefix nameSpace = defaultMap->GetNameSpace( prefix.c_str(), prefix.size() ); if(destNode->GetNodeType() == INode::kNTStructure) { spIStructureNode tempNode = destNode->ConvertToStructureNode(); destNode = tempNode->GetNode(nameSpace->c_str(), AdobeXMPCommon::npos, stepStr.c_str() + colonPos + 1, AdobeXMPCommon::npos ); } else { return false; } } break; case kXMP_ArrayIndexStep: { if(destNode->GetNodeType() != INode::kNTArray) { return false; } spIArrayNode tempNode = destNode->ConvertToArrayNode(); XMP_Index index = 0; XMP_Assert ( (stepStr.length() >= 2) && (*( stepStr.begin()) == '[') && (stepStr[stepStr.length()-1] == ']') ); for ( size_t chNum = 1,chEnd = stepStr.length() -1 ; chNum != chEnd; ++chNum ) { XMP_Assert ( ('0' <= stepStr[chNum]) && (stepStr[chNum] <= '9') ); index = (index * 10) + (stepStr[chNum] - '0'); } if ( index < 1) XMP_Throw ( "Array index must be larger than one", kXMPErr_BadXPath ); size_t colonPos = prevStep.find(':'); XMP_VarString prefix = prevStep.substr( 0, colonPos ); nameSpace = defaultMap->GetNameSpace( prefix.c_str(), prefix.size() ); destNode = tempNode->GetNodeAtIndex( index ); } break; case kXMP_ArrayLastStep: { if(destNode->GetNodeType() != INode::kNTArray) { return false; } spIArrayNode tempNode = destNode->ConvertToArrayNode(); size_t colonPos = prevStep.find(':'); XMP_VarString prefix = prevStep.substr( 0, colonPos ); nameSpace = defaultMap->GetNameSpace( prefix.c_str(), prefix.size() ); spINode parentNode = destNode; if(parentNode && parentNode->GetNodeType()== INode::kNTArray) { size_t childCount = parentNode->ConvertToArrayNode()->ChildCount(); if(!childCount) { XMP_Throw ( "Array index overflow", kXMPErr_BadXPath ); } destNode = tempNode->GetNodeAtIndex(childCount); } } break; case kXMP_QualifierStep: { XMP_Assert(stepStr[0]=='?'); stepStr = stepStr.substr(1); size_t colonPos = stepStr.find(':'); XMP_VarString prefix = stepStr.substr( 0, colonPos); nameSpace = defaultMap->GetNameSpace( prefix.c_str(), prefix.size() ); destNode = destNode->GetQualifier(nameSpace->c_str(), nameSpace->size(), stepStr.c_str() + colonPos + 1, AdobeXMPCommon::npos ); qualifierFlag = true; } break; case kXMP_QualSelectorStep: { if(destNode->GetNodeType() != INode::kNTArray) { return false; } spIArrayNode tempNode = destNode->ConvertToArrayNode(); XMP_VarString qualName, qualValue, qualNameSpace; SplitNameAndValue (stepStr, &qualName, &qualValue ); spINode parentNode = destNode; size_t colonPos = qualName.find(':'); XMP_VarString prefix = qualName.substr( 0, colonPos); qualNameSpace = defaultMap->GetNameSpace( prefix.c_str(), prefix.size() )->c_str(); bool indexFound = false; if(parentNode && parentNode->GetNodeType() == INode::kNTArray) { spIArrayNode parentArrayNode = parentNode->ConvertToArrayNode(); size_t arrayChildCount = parentArrayNode->ChildCount(); for(size_t arrayIdx = 1; arrayIdx <= arrayChildCount; arrayIdx++) { spINode currentArrayItem = parentArrayNode->GetNodeAtIndex(arrayIdx); spINode qualNode = currentArrayItem->GetQualifier(qualNameSpace.c_str(), qualNameSpace.size(), qualName.c_str() + colonPos + 1, AdobeXMPCommon::npos ); if(!qualNode) continue; XMP_VarString currentQualValue = qualNode->ConvertToSimpleNode()->GetValue()->c_str(); if( currentQualValue == qualValue) { indexFound = true; destNode = parentArrayNode->GetNodeAtIndex( arrayIdx); break; } } } if(!indexFound) { return false; } } break; case kXMP_FieldSelectorStep : { XMP_VarString fieldName, fieldValue, fieldNameSpace; SplitNameAndValue (stepStr, &fieldName, &fieldValue ); spINode parentNode = destNode; size_t colonPos = fieldName.find(':'); XMP_VarString prefix = fieldName.substr( 0, colonPos); fieldNameSpace = defaultMap->GetNameSpace( prefix.c_str(), prefix.size() )->c_str(); bool indexFound = false; if(parentNode && parentNode->GetNodeType() == INode::kNTArray) { spIArrayNode parentArrayNode = parentNode->ConvertToArrayNode(); size_t arrayChildCount = parentArrayNode->ChildCount(); for(size_t arrayIdx = 1; arrayIdx <= arrayChildCount; arrayIdx++) { spINode currentItem = parentArrayNode->GetNodeAtIndex(arrayIdx); if(currentItem->GetNodeType() != INode::kNTStructure) { return false; } spINode fieldNode = currentItem->ConvertToStructureNode()->GetNode(fieldNameSpace.c_str(), fieldNameSpace.size(), fieldName.c_str() + colonPos + 1, AdobeXMPCommon::npos ); if(!fieldNode || fieldNode->GetNodeType() != INode::kNTSimple) continue; XMP_VarString currentFieldValue = fieldNode->ConvertToSimpleNode()->GetValue()->c_str(); if( currentFieldValue == fieldValue) { indexFound = true; destNode = parentArrayNode->GetNodeAtIndex( arrayIdx); break; } } } if(!indexFound) { return false; } } break; default: break; } } if (!destNode) { return false; } if(options)*options = XMPUtils::GetIXMPOptions(destNode); if ( destNode->GetNodeType() == INode::kNTSimple ) { spcIUTF8String value = destNode->ConvertToSimpleNode()->GetValue(); *propValue = value->c_str(); *valueSize = static_cast( value->size() ); } return true; } // GetProperty // ------------------------------------------------------------------------------------------------- // CountArrayItems // --------------- XMP_Index XMPMeta2::CountArrayItems ( XMP_StringPtr schemaNS, XMP_StringPtr arrayName ) const { XMP_Assert ( (schemaNS != 0) && (arrayName != 0) ); // Enforced by wrapper. XMP_ExpandedXPath expPath; ExpandXPath ( schemaNS, arrayName, &expPath ); spINode arrayNode ; XMP_OptionBits arrayOptions = 0; if(!XMPUtils::FindCnstNode(this->mDOM, expPath, arrayNode, &arrayOptions)) return false; if ( ! (arrayOptions & kXMP_PropValueIsArray) ) XMP_Throw ( "The named property is not an array", kXMPErr_BadXPath ); return static_cast( XMPUtils::GetNodeChildCount(arrayNode) ); } // CountArrayItems void XMPMeta2::ParseFromBuffer ( XMP_StringPtr buffer, XMP_StringLen bufferSize, XMP_OptionBits options ) { bool lastClientCall = (options & kXMP_ParseMoreBuffers) ? false : true; if (!mBuffer) { mBuffer = IUTF8String_I::CreateUTF8String("", 0); } sizet bufferSizeIn64Bits = static_cast(bufferSize); if (bufferSize == kXMP_UseNullTermination) { bufferSizeIn64Bits = std::string::npos; } mBuffer->append(buffer, bufferSizeIn64Bits); if (!lastClientCall) { return; } spParser->GetIDOMParser_I()->SetErrorCallback(&errorCallback); mDOM = spParser->Parse( mBuffer->c_str(), mBuffer->size() ); mBuffer->clear(); } void XMPMeta2::SerializeToBuffer ( XMP_VarString * rdfString, XMP_OptionBits options, XMP_StringLen padding, XMP_StringPtr newline, XMP_StringPtr indent, XMP_Index baseIndent ) const { auto spRegistry = IDOMImplementationRegistry::GetDOMImplementationRegistry(); auto rdfSerializer = spRegistry->GetSerializer( "rdf" ); auto str = rdfSerializer->GetIDOMSerializer_I()->SerializeInternal( mDOM, options, padding, newline, indent, baseIndent); rdfString->clear(); if (str) rdfString->append( str->c_str() ); } void XMPMeta2::Sort() { // need internal implementation of sort here return; } // Sort void XMPMeta2::Erase() { if ( this->xmlParser != 0 ) { delete ( this->xmlParser ); this->xmlParser = 0; } mDOM->Clear(); } // DoesPropertyExist // ----------------- bool XMPMeta2::DoesPropertyExist ( XMP_StringPtr schemaNS, XMP_StringPtr propName ) const { XMP_Assert ( (schemaNS != 0) && (propName != 0) ); // Enforced by wrapper. XMP_ExpandedXPath expPath; ExpandXPath ( schemaNS, propName, &expPath ); spINode destNode; XMP_OptionBits options; return XMPUtils::FindCnstNode ( this->mDOM, expPath, destNode, &options ); } // DoesPropertyExist // SetProperty // ----------- // *** Should handle array items specially, calling SetArrayItem. void XMPMeta2::SetProperty ( XMP_StringPtr schemaNS, XMP_StringPtr propName, XMP_StringPtr propValue, XMP_OptionBits options ) { XMP_Assert ( (schemaNS != 0) && (propName != 0) ); // Enforced by wrapper. options = VerifySetOptions ( options, propValue ); XMP_ExpandedXPath expPath; ExpandXPath ( schemaNS, propName, &expPath ); spINode node ; bool propertyFound = XMPUtils::FindNode ( mDOM, expPath, kXMP_CreateNodes, options, node, 0 ); if (!propertyFound) XMP_Throw ( "Specified property does not exist", kXMPErr_BadXPath ); XMPUtils::SetNode ( node, propValue, options ); } // SetProperty // -------------------------------------------------------------------------------------------------' // ------------------------------------------------------------------------------------------------- // SetArrayItem // ------------ void XMPMeta2::SetArrayItem ( XMP_StringPtr schemaNS, XMP_StringPtr arrayName, XMP_Index itemIndex, XMP_StringPtr itemValue, XMP_OptionBits options ) { XMP_Assert ( (schemaNS != 0) && (arrayName != 0) ); // Enforced by wrapper. XMP_ExpandedXPath arrayPath; ExpandXPath ( schemaNS, arrayName, &arrayPath ); spINode destNode; if(!XMPUtils::FindNode ( mDOM, arrayPath, false,options, destNode ) ) { XMP_Throw ( "Specified array does not exist", kXMPErr_BadXPath ); } int x = destNode->GetNodeType(); if(destNode->GetNodeType() != INode::kNTArray) { XMP_Throw ( "Specified array does not exist", kXMPErr_BadXPath ); } spIArrayNode arrayNode = destNode->ConvertToArrayNode(); XMPUtils::DoSetArrayItem ( arrayNode, itemIndex, itemValue, options ); } // SetArrayItem // ------------------------------------------------------------------------------------------------- // AppendArrayItem // --------------- void XMPMeta2::AppendArrayItem ( XMP_StringPtr schemaNS, XMP_StringPtr arrayName, XMP_OptionBits arrayOptions, XMP_StringPtr itemValue, XMP_OptionBits options ) { // TO DO check in case array node doesn't already exist, and the parent of the array to be created is also an array -currently appending the array at the end of the existing array XMP_Assert ( (schemaNS != 0) && (arrayName != 0) ); // Enforced by wrapper. arrayOptions = VerifySetOptions ( arrayOptions, 0 ); if ( (arrayOptions & ~kXMP_PropArrayFormMask) != 0 ) { XMP_Throw ( "Only array form flags allowed for arrayOptions", kXMPErr_BadOptions ); } XMP_ExpandedXPath arrayPath; ExpandXPath ( schemaNS, arrayName, &arrayPath ); spINode destNode; spIArrayNode arrayNode; XMP_OptionBits dummyOptions; XMP_Index insertIndex = 0; // either destNode will be the array node or it will be the parent node of the array if(XMPUtils::FindCnstNode (mDOM, arrayPath, destNode, &dummyOptions )) { if ( destNode->GetNodeType() != INode::kNTArray) { XMP_Throw ( "The named property is not an array", kXMPErr_BadXPath ); } } else { if ( arrayOptions == 0 ) XMP_Throw ( "Explicit arrayOptions required to create new array", kXMPErr_BadOptions ); XPathStepInfo lastPathSegment( arrayPath.back()); XMP_VarString arrayStep = lastPathSegment.step; //arrayPath.pop_back(); if(!XMPUtils::FindNode(this->mDOM, arrayPath, kXMP_CreateNodes, arrayOptions, destNode, &insertIndex)) { XMP_Throw ( "Failure creating array node", kXMPErr_BadXPath ); } } arrayNode = destNode->ConvertToArrayNode(); XMPUtils::DoSetArrayItem ( arrayNode, kXMP_ArrayLastItem, itemValue, (options | kXMP_InsertAfterItem) ); } // AppendArrayItem // ------------------------------------------------------------------------------------------------- // SetQualifier // ------------ void XMPMeta2::SetQualifier ( XMP_StringPtr schemaNS, XMP_StringPtr propName, XMP_StringPtr qualNS, XMP_StringPtr qualName, XMP_StringPtr qualValue, XMP_OptionBits options ) { XMP_Assert ( (schemaNS != 0) && (propName != 0) && (qualNS != 0) && (qualName != 0) ); // Enforced by wrapper. XMP_ExpandedXPath expPath; ExpandXPath ( schemaNS, propName, &expPath ); spINode destNode ; if(!XMPUtils::FindCnstNode ( mDOM, expPath, destNode) ) XMP_Throw ( "Specified property does not exist", kXMPErr_BadXPath ); XMP_VarString qualPath; XMPUtils::ComposeQualifierPath ( schemaNS, propName, qualNS, qualName, &qualPath ); SetProperty ( schemaNS, qualPath.c_str(), qualValue, options ); } // SetQualifier // Clone // ----- void XMPMeta2::Clone ( XMPMeta * clone, XMP_OptionBits options ) const { XMPMeta2 * xmpMeta2Ptr = dynamic_cast(clone); // Possible to do a safer/better cast? if (xmpMeta2Ptr== 0 ) XMP_Throw ( "Null clone pointer", kXMPErr_BadParam ); if ( options != 0 ) XMP_Throw ( "No options are defined yet", kXMPErr_BadOptions ); xmpMeta2Ptr->mDOM->Clear(); xmpMeta2Ptr->mDOM = mDOM->Clone()->ConvertToMetadata(); } // Clone // ------------------------------------------------------------------------------------------------- // DeleteProperty // -------------- void XMPMeta2::DeleteProperty ( XMP_StringPtr schemaNS, XMP_StringPtr propName ) { XMP_Assert ( (schemaNS != 0) && (propName != 0) ); // Enforced by wrapper. XMP_ExpandedXPath expPath; ExpandXPath ( schemaNS, propName, &expPath ); XMP_NodePtrPos ptrPos; spINode propNode ; XMP_OptionBits options = 0; XMP_Index arrayIndex = 0; if(!XMPUtils::FindCnstNode ( mDOM, expPath, propNode, &options, &arrayIndex ) || !propNode ) { return; } if (!propNode) return; spINode parentNode = propNode->GetParent(); // Erase the pointer from the parent's vector, then delete the node and all below it. if ( (options & kXMP_PropIsQualifier) ) { parentNode->RemoveQualifier( propNode->GetNameSpace()->c_str(), propNode->GetNameSpace()->size(), propNode->GetName()->c_str(), propNode->GetName()->size() ); } else if(parentNode->GetNodeType() == INode::kNTArray) { spIArrayNode parentArrayNode = parentNode->ConvertToArrayNode(); parentArrayNode->RemoveNodeAtIndex(arrayIndex); } else if(parentNode->GetNodeType() == INode::kNTStructure) { spIStructureNode parentStructureNode = parentNode->ConvertToStructureNode(); parentStructureNode->RemoveNode( propNode->GetNameSpace()->c_str(), propNode->GetNameSpace()->size(), propNode->GetName()->c_str(), propNode->GetName()->size() ); } // delete subtree - needed ? //propNode->Clear(); } // DeleteProperty void XMPMeta2::GetObjectName ( XMP_StringPtr * namePtr, XMP_StringLen * nameLen ) const { *namePtr = this->mDOM->GetAboutURI()->c_str(); *nameLen = static_cast ( this->mDOM->GetAboutURI()->size() ); } // GetObjectName // ------------------------------------------------------------------------------------------------- // SetObjectName // ------------- void XMPMeta2::SetObjectName ( XMP_StringPtr name ) { VerifyUTF8 (name); // Throws if the string is not legit UTF-8. this->mDOM->SetAboutURI(name, AdobeXMPCommon::npos ); } // SetObjectName // ------------------------------------------------------------------------------------------------- // ChooseLocalizedText // ------------------- // // 1. Look for an exact match with the specific language. // 2. If a generic language is given, look for partial matches. // 3. Look for an "x-default" item. // 4. Choose the first item. static XMP_CLTMatch ChooseIXMPLocalizedText ( const spIArrayNode &arrayNode, XMP_OptionBits &options, XMP_StringPtr genericLang, XMP_StringPtr specificLang, spINode &itemNode ) { spINode currItem ; const size_t itemLim = arrayNode->ChildCount(); size_t itemNum; const XMP_VarString xmlLangQualifierName = "lang"; // See if the array has the right form. Allow empty alt arrays, that is what parsing returns. // *** Should check alt-text bit when that is reliably maintained. if ( ! ( XMP_ArrayIsAltText(options) || (!itemLim && XMP_ArrayIsAlternate(options)) ) ) { XMP_Throw ( "Localized text array is not alt-text", kXMPErr_BadXPath ); } if ( !itemLim ) { return kXMP_CLT_NoValues; } for ( itemNum = 1; itemNum <= itemLim; ++itemNum ) { currItem = arrayNode->GetNodeAtIndex(itemNum); if ( currItem->GetNodeType()!= INode::kNTSimple ) { XMP_Throw ( "Alt-text array item is not simple", kXMPErr_BadXPath ); } if ( !currItem->HasQualifiers() || !currItem->GetQualifier(xmlNameSpace.c_str(), xmlNameSpace.size(), xmlLangQualifierName.c_str(), xmlLangQualifierName.size() ) ) { XMP_Throw ( "Alt-text array item has no language qualifier", kXMPErr_BadXPath ); } } // Look for an exact match with the specific language. spISimpleNode xmlLangQualifierNode, currItemValue; for ( itemNum = 1; itemNum <= itemLim; ++itemNum ) { currItem = arrayNode->GetNodeAtIndex(itemNum); xmlLangQualifierNode = currItem->QualifiersIterator()->GetNode()->ConvertToSimpleNode(); currItemValue = currItem->ConvertToSimpleNode(); if ( !strcmp(xmlLangQualifierNode->GetValue()->c_str(), specificLang ) ) { itemNode = currItem; return kXMP_CLT_SpecificMatch; } } if ( *genericLang != 0 ) { // Look for the first partial match with the generic language. const size_t genericLen = strlen ( genericLang ); for ( itemNum = 1; itemNum <= itemLim; ++itemNum ) { currItem = arrayNode->GetNodeAtIndex(itemNum); xmlLangQualifierNode = currItem->GetQualifier( xmlNameSpace.c_str(), xmlNameSpace.size(), xmlLangQualifierName.c_str(), xmlLangQualifierName.size() )->ConvertToSimpleNode(); XMP_StringPtr currLang = xmlLangQualifierNode->GetValue()->c_str(); const size_t currLangSize = xmlLangQualifierNode->GetValue()->size(); if ( (currLangSize >= genericLen) && XMP_LitNMatch ( currLang, genericLang, genericLen ) && ((currLangSize == genericLen) || (currLang[genericLen] == '-')) ) { itemNode = currItem; break; // ! Don't return, need to look for other matches. } } if ( itemNum <= itemLim ) { // Look for a second partial match with the generic language. for ( ++itemNum; itemNum <= itemLim; ++itemNum ) { currItem = arrayNode->GetNodeAtIndex(itemNum); xmlLangQualifierNode = currItem->GetQualifier( xmlNameSpace.c_str(), xmlNameSpace.size(), xmlLangQualifierName.c_str(), xmlLangQualifierName.size() )->ConvertToSimpleNode(); XMP_StringPtr currLang = xmlLangQualifierNode->GetValue()->c_str(); const size_t currLangSize = xmlLangQualifierNode->GetValue()->size(); if ( (currLangSize >= genericLen) && XMP_LitNMatch ( currLang, genericLang, genericLen ) && ((currLangSize == genericLen) || (currLang[genericLen] == '-')) ) { return kXMP_CLT_MultipleGeneric; // ! Leave itemNode with the first partial match. } } return kXMP_CLT_SingleGeneric; // No second partial match was found. } } // Look for an 'x-default' item. for ( itemNum = 1; itemNum <= itemLim; ++itemNum ) { currItem = arrayNode->GetNodeAtIndex(itemNum); xmlLangQualifierNode = currItem->GetQualifier( xmlNameSpace.c_str(), xmlNameSpace.size(), xmlLangQualifierName.c_str(), xmlLangQualifierName.size() )->ConvertToSimpleNode(); if ( !XMP_LitMatch(xmlLangQualifierNode->GetValue()->c_str(), "x-default" ) ) { itemNode = currItem; return kXMP_CLT_XDefault; } } // Everything failed, choose the first item. itemNode = arrayNode->GetNodeAtIndex(1); return kXMP_CLT_FirstItem; } // ChooseLocalizedText // ------------------------------------------------------------------------------------------------- // GetLocalizedText // ---------------- bool XMPMeta2::GetLocalizedText ( XMP_StringPtr schemaNS, XMP_StringPtr arrayName, XMP_StringPtr _genericLang, XMP_StringPtr _specificLang, XMP_StringPtr * actualLang, XMP_StringLen * langSize, XMP_StringPtr * itemValue, XMP_StringLen * valueSize, XMP_OptionBits * options ) const { // TO DO : options XMP_Assert ( (schemaNS != 0) && (arrayName != 0) && (_genericLang != 0) && (_specificLang != 0) ); // Enforced by wrapper. XMP_Assert ( (actualLang != 0) && (langSize != 0) ); // Enforced by wrapper. XMP_Assert ( (itemValue != 0) && (valueSize != 0) && (options != 0) ); // Enforced by wrapper. XMP_VarString zGenericLang ( _genericLang ); XMP_VarString zSpecificLang ( _specificLang ); NormalizeLangValue ( &zGenericLang ); NormalizeLangValue ( &zSpecificLang ); XMP_StringPtr genericLang = zGenericLang.c_str(); XMP_StringPtr specificLang = zSpecificLang.c_str(); XMP_ExpandedXPath arrayPath; ExpandXPath ( schemaNS, arrayName, &arrayPath ); spINode arrayNode, itemNode; XMP_OptionBits arrayOptions; if(!XMPUtils::FindCnstNode( this->mDOM, arrayPath, arrayNode, &arrayOptions)) return false; XMP_CLTMatch match = ChooseIXMPLocalizedText( arrayNode->ConvertToArrayNode(), arrayOptions, genericLang, specificLang, itemNode ); if ( match == kXMP_CLT_NoValues ) return false; spISimpleNode qualifierNode = itemNode->GetQualifier( xmlNameSpace.c_str(), xmlNameSpace.size(), "lang", AdobeXMPCommon::npos )->ConvertToSimpleNode(); *actualLang = qualifierNode->GetValue()->c_str(); *langSize = static_cast( qualifierNode->GetValue()->size() ); spcIUTF8String itemNodeValue = itemNode->ConvertToSimpleNode()->GetValue(); *itemValue = itemNodeValue->c_str(); *valueSize = static_cast( itemNodeValue->size() ); *options = XMPUtils::GetIXMPOptions(itemNode); return true; } // GetLocalizedText // ------------------------------------------------------------------------------------------------- // DeleteLocalizedText // ------------------- void XMPMeta2::DeleteLocalizedText ( XMP_StringPtr schemaNS, XMP_StringPtr arrayName, XMP_StringPtr _genericLang, XMP_StringPtr _specificLang ) { XMP_Assert ( (schemaNS != 0) && (arrayName != 0) && (_genericLang != 0) && (_specificLang != 0) ); // Enforced by wrapper. XMP_VarString zGenericLang ( _genericLang ); XMP_VarString zSpecificLang ( _specificLang ); NormalizeLangValue ( &zGenericLang ); NormalizeLangValue ( &zSpecificLang ); XMP_StringPtr genericLang = zGenericLang.c_str(); XMP_StringPtr specificLang = zSpecificLang.c_str(); XMP_ExpandedXPath arrayPath; ExpandXPath ( schemaNS, arrayName, &arrayPath ); // Find the LangAlt array and the selected array item. spINode destNode, itemNode; spIArrayNode arrayNode; XMP_OptionBits arrayOptions; if(!XMPUtils::FindCnstNode( this->mDOM, arrayPath, destNode, &arrayOptions)) return; arrayNode = destNode->ConvertToArrayNode(); size_t arraySize = arrayNode->ChildCount(); XMP_CLTMatch match = ChooseIXMPLocalizedText( arrayNode->ConvertToArrayNode(), arrayOptions, genericLang, specificLang, itemNode ); spcIUTF8String itemValue = itemNode->ConvertToSimpleNode()->GetValue(); if ( match != kXMP_CLT_SpecificMatch ) return; size_t itemIndex = 1; for ( ; itemIndex <= arraySize; ++itemIndex ) { if ( arrayNode->GetNodeAtIndex(itemIndex) == itemNode ) break; } XMP_Enforce ( itemIndex <= arraySize ); // Decide if the selected item is x-default or not, find relevant matching item. spISimpleNode qualNode ; bool itemIsXDefault = false; if ( itemNode->HasQualifiers() ) { qualNode = itemNode->GetQualifier( xmlNameSpace.c_str(), xmlNameSpace.size(), "lang", AdobeXMPCommon::npos )->ConvertToSimpleNode(); if (XMP_LitMatch(qualNode->GetValue()->c_str(), "x-default")) itemIsXDefault = true; } if ( itemIsXDefault && (itemIndex != 1) ) { // Enforce the x-default is first policy. auto sp = arrayNode->GetNodeAtIndex( itemIndex ); arrayNode->GetNodeAtIndex(1).swap( sp ); itemIndex = 1; } spINode assocNode; size_t assocIndex = 0; size_t assocIsXDefault = false; if ( itemIsXDefault ) { for ( assocIndex = 2; assocIndex <= arraySize; ++assocIndex ) { spISimpleNode indexNode = arrayNode->GetNodeAtIndex( assocIndex )->ConvertToSimpleNode(); if ( !strcmp(indexNode->GetValue()->c_str(), itemValue->c_str()) ) { assocNode = arrayNode->GetNodeAtIndex(assocIndex); break; } } } else if ( itemIndex > 1 ) { spcIUTF8String itemOneValue = arrayNode->GetNodeAtIndex( 1 )->ConvertToSimpleNode()->GetValue(); if ( !strcmp(itemOneValue->c_str(), itemValue->c_str()) ) { qualNode = arrayNode->GetNodeAtIndex( 1 )->GetQualifier( xmlNameSpace.c_str(), xmlNameSpace.size(), "lang", AdobeXMPCommon::npos )->ConvertToSimpleNode(); if ( XMP_LitMatch(qualNode->GetValue()->c_str(),"x-default") ) { assocNode = arrayNode->GetNodeAtIndex(1); assocIndex = 1; assocIsXDefault = true; } } } if ( !assocIndex) { arrayNode->RemoveNodeAtIndex(itemIndex); } else if ( itemIndex < assocIndex ) { arrayNode->RemoveNodeAtIndex(assocIndex); arrayNode->RemoveNodeAtIndex(itemIndex); } else { arrayNode->RemoveNodeAtIndex(itemIndex); arrayNode->RemoveNodeAtIndex(assocIndex); } } // DeleteLocalizedText // ------------------------------------------------------------------------------------------------- // SetLocalizedText // ---------------- void XMPMeta2::SetLocalizedText ( XMP_StringPtr schemaNS, XMP_StringPtr arrayName, XMP_StringPtr _genericLang, XMP_StringPtr _specificLang, XMP_StringPtr itemValue, XMP_OptionBits options ) { // is new DOM enforcing that first qualifier should be a lang alt IgnoreParam(options); XMP_Assert ( (schemaNS != 0) && (arrayName != 0) && (_genericLang != 0) && (_specificLang != 0) ); // Enforced by wrapper. XMP_VarString zGenericLang ( _genericLang ); XMP_VarString zSpecificLang ( _specificLang ); NormalizeLangValue ( &zGenericLang ); NormalizeLangValue ( &zSpecificLang ); XMP_StringPtr genericLang = zGenericLang.c_str(); XMP_StringPtr specificLang = zSpecificLang.c_str(); XMP_ExpandedXPath arrayPath; ExpandXPath ( schemaNS, arrayName, &arrayPath ); // Find the array node and set the options if it was just created. spINode destNode; spIArrayNode arrayNode; XMP_OptionBits arrayOptions; if( !XMPUtils::FindCnstNode(this->mDOM, arrayPath, destNode) ) { XPathStepInfo lastPathSegment( arrayPath.back()); XMP_VarString arrayStep = lastPathSegment.step; XMP_Index insertIndex = 0; if (!XMPUtils::FindNode(this->mDOM, arrayPath, kXMP_CreateNodes, kXMP_PropArrayIsAlternate | kXMP_PropValueIsArray, destNode, &insertIndex)) { XMP_Throw ( "Failure creating array node", kXMPErr_BadXPath ); } } arrayNode = destNode->ConvertToArrayNode(); arrayOptions = XMPUtils::GetIXMPOptions(arrayNode); size_t arrayChildCount = arrayNode->ChildCount(); if ( !arrayNode ) XMP_Throw ( "Failed to find or create array node", kXMPErr_BadXPath ); if ( ! XMP_ArrayIsAltText(arrayOptions) ) { if ( !arrayChildCount && XMP_ArrayIsAlternate(arrayOptions) ) { arrayOptions |= kXMP_PropArrayIsAltText; } else { XMP_Throw ( "Localized text array is not alt-text", kXMPErr_BadXPath ); } } // Make sure the x-default item, if any, is first. size_t itemNum, itemLim; spcISimpleNode firstQualifier; spINode xdItem; bool haveXDefault = false; for ( itemNum = 1, itemLim = arrayNode->ChildCount(); itemNum <= itemLim; ++itemNum ) { spINode currItem = arrayNode->GetNodeAtIndex(itemNum); XMP_Assert (XMP_PropHasLang(XMPUtils::GetIXMPOptions(currItem))); if(!currItem->HasQualifiers()) { XMP_Throw ( "Language qualifier must be first", kXMPErr_BadXPath ); } firstQualifier = currItem->QualifiersIterator()->GetNode()->ConvertToSimpleNode(); if (!XMP_LitMatch(firstQualifier->GetName()->c_str(),"lang")) { XMP_Throw ( "Language qualifier must be first", kXMPErr_BadXPath ); } if (XMP_LitMatch(firstQualifier->GetValue()->c_str(),"x-default" )) { xdItem = currItem; haveXDefault = true; break; } } if ( haveXDefault && (itemNum != 1) ) { //TODO or not to do XMP_Assert ( XMP_LitMatch(firstQualifier->GetValue()->c_str(), "x-default") ); spcISimpleNode tempNode = arrayNode->GetNodeAtIndex( itemNum )->GetQualifier( xmlNameSpace.c_str(), xmlNameSpace.size(), "lang", AdobeXMPCommon::npos )->ConvertToSimpleNode(); firstQualifier.swap(tempNode); } spINode itemNode; spcIUTF8String xdValue, itemNodeValue; if(xdItem && xdItem->GetNodeType() == INode::kNTSimple) { xdValue = xdItem->ConvertToSimpleNode()->GetValue(); } XMP_CLTMatch match = ChooseIXMPLocalizedText ( arrayNode->ConvertToArrayNode(), arrayOptions, genericLang, specificLang, itemNode); if(itemNode && itemNode->GetNodeType() == INode::kNTSimple) { itemNodeValue = itemNode->ConvertToSimpleNode()->GetValue(); } const bool specificXDefault = XMP_LitMatch ( specificLang, "x-default" ); switch ( match ) { case kXMP_CLT_NoValues : // Create the array items for the specificLang and x-default, with x-default first. AppendIXMPLangItem ( arrayNode, "x-default", itemValue ); haveXDefault = true; if ( ! specificXDefault ) AppendIXMPLangItem ( arrayNode, specificLang, itemValue ); break; case kXMP_CLT_SpecificMatch : if ( ! specificXDefault ) { // Update the specific item, update x-default if it matches the old value. if ( xdItem && haveXDefault && (xdItem != itemNode) && (XMP_LitMatch(xdValue->c_str(), itemNodeValue->c_str())) ) { XMPUtils::SetNode ( xdItem, itemValue, XMPUtils::GetIXMPOptions( xdItem)); } XMPUtils::SetNode(itemNode, itemValue,XMPUtils:: GetIXMPOptions(itemNode)); } else { // Update all items whose values match the old x-default value. XMP_Assert ( xdItem && haveXDefault && (xdItem.get() == itemNode.get()) ); for ( itemNum = 1, itemLim = arrayNode->ChildCount(); itemNum <= itemLim; ++itemNum ) { spISimpleNode currItem = arrayNode->GetNodeAtIndex( itemNum )->ConvertToSimpleNode(); if ( (currItem.get() == xdItem.get() ) || (strcmp(currItem->GetValue()->c_str(), xdValue->c_str()) )) continue; XMPUtils::SetNode ( currItem, itemValue, XMPUtils::GetIXMPOptions(currItem) ); } XMPUtils::SetNode( xdItem, itemValue,XMPUtils:: GetIXMPOptions(xdItem)); } break; case kXMP_CLT_SingleGeneric : // Update the generic item, update x-default if it matches the old value. if ( xdItem && haveXDefault && (xdItem != itemNode) && (XMP_LitMatch(xdValue->c_str(),itemNodeValue->c_str()) ) ) { XMPUtils::SetNode ( xdItem, itemValue, XMPUtils::GetIXMPOptions(xdItem) ); } XMPUtils::SetNode( itemNode, itemValue,XMPUtils:: GetIXMPOptions(itemNode) ); // ! Do this after the x-default check! break; case kXMP_CLT_MultipleGeneric : // Create the specific language, ignore x-default. AppendIXMPLangItem ( arrayNode, specificLang, itemValue ); if ( specificXDefault ) haveXDefault = true; break; case kXMP_CLT_XDefault : // Create the specific language, update x-default if it was the only item. if ( arrayNode->ChildCount()== 1 ) XMPUtils::SetNode ( xdItem, itemValue, XMPUtils::GetIXMPOptions(xdItem) ); AppendIXMPLangItem ( arrayNode, specificLang, itemValue ); break; case kXMP_CLT_FirstItem : // Create the specific language, don't add an x-default item. AppendIXMPLangItem ( arrayNode, specificLang, itemValue ); if ( specificXDefault ) haveXDefault = true; break; default : XMP_Throw ( "Unexpected result from ChooseLocalizedText", kXMPErr_InternalFailure ); } // Add an x-default at the front if needed. if ( (! haveXDefault) && (arrayNode->ChildCount() == 1) ) { AppendIXMPLangItem ( arrayNode, "x-default", itemValue ); } } // SetLocalizedText // ------------------------------------------------------------------------------------------------- // DumpPropertyTree // ---------------- // *** Extract the validation code into a separate routine to call on exit in debug builds. static void DumpIXMPPropertyTree ( const spcINode & currNode, int indent, size_t itemIndex, XMP_TextOutputProc outProc, void * refCon ) { if(!currNode) return; char buffer [32]; // Decimal of a 64 bit int is at most about 20 digits. XMP_OptionBits options = XMPUtils::GetIXMPOptions(currNode); auto defaultMap = INameSpacePrefixMap::GetDefaultNameSpacePrefixMap(); XMP_VarString currNameSpace = defaultMap->GetPrefix(currNode->GetNameSpace()->c_str(), currNode->GetNameSpace()->size() )->c_str(); XMP_VarString nodeFullName = currNameSpace + ":" + currNode->GetName()->c_str(); OutProcIndent ( (size_t)indent ); size_t childCount = 0; if ( itemIndex == 0 ) { if ( options & kXMP_PropIsQualifier ) OutProcNChars ( "? ", 2 ); DumpClearString ( nodeFullName, outProc, refCon ); } else { OutProcNChars ( "[", 1 ); OutProcDecInt ( itemIndex ); OutProcNChars ( "]", 1 ); } if ( ! (options & kXMP_PropCompositeMask) ) { OutProcNChars ( " = \"", 4 ); DumpClearString ( currNode->ConvertToSimpleNode()->GetValue()->c_str(), outProc, refCon ); OutProcNChars ( "\"", 1 ); } if ( options != 0 ) { OutProcNChars ( " ", 2 ); DumpNodeOptions ( options, outProc, refCon ); } if ( options & kXMP_PropHasLang ) { spcISimpleNode firstQualifier = currNode->QualifiersIterator()->GetNode()->ConvertToSimpleNode(); if ( !currNode->HasQualifiers() || !(XMP_LitMatch(firstQualifier->GetName()->c_str(), "lang") ) ) { OutProcLiteral ( " ** bad lang flag **" ); } } // *** Check rdf:type also. if ( ! (options & kXMP_PropCompositeMask) ) { if(currNode->GetNodeType() == INode::kNTArray) { childCount = currNode->ConvertToArrayNode()->ChildCount(); } if(currNode->GetNodeType() == INode::kNTStructure) { childCount = currNode->ConvertToStructureNode()->ChildCount(); } if ( childCount ) OutProcLiteral ( " ** bad children **" ); } else if ( options & kXMP_PropValueIsArray ) { if ( options & kXMP_PropValueIsStruct ) OutProcLiteral ( " ** bad comp flags **" ); } else if ( (options & kXMP_PropCompositeMask) != kXMP_PropValueIsStruct ) { OutProcLiteral ( " ** bad comp flags **" ); } OutProcNewline(); if( currNode->HasQualifiers() ) { auto qualIter = currNode->QualifiersIterator(); for (size_t qualNum = 0 ; qualIter; qualIter = qualIter->Next(), qualNum++ ) { spcINode currQual = qualIter->GetNode(); XMP_OptionBits currQualOptions = XMPUtils::GetIXMPOptions(currQual); if ( currQual->GetParent() && currQual->GetParent()->GetParent()!= currNode ) OutProcLiteral ( "** bad parent link => " ); if ( XMP_LitMatch(currQual->GetName()->c_str(), kXMP_ArrayItemName ) ) OutProcLiteral ( "** bad qual name => " ); if ( ! (currQualOptions & kXMP_PropIsQualifier) ) OutProcLiteral ( "** bad qual flag => " ); if ( XMP_LitMatch(currQual->GetName()->c_str(), "lang" )) { if ( (qualNum != 0) || (! (options & kXMP_PropHasLang)) ) OutProcLiteral ( "** bad lang qual => " ); } DumpIXMPPropertyTree ( currQual, indent + 2, 0, outProc, refCon ); } } spcINodeIterator childIter; if(currNode->GetNodeType() == INode::kNTArray) { childIter = currNode->ConvertToArrayNode()->Iterator(); } if(currNode->GetNodeType() == INode::kNTStructure) { childIter = currNode->ConvertToStructureNode()->Iterator(); } for (size_t childNum = 0; childIter; childIter = childIter->Next(), childNum++) { spcINode currentChild = childIter->GetNode(); XMP_OptionBits currentChildOptions = XMPUtils::GetIXMPOptions( currentChild); if( !currentChild) continue; if ( currentChild->GetParent() != currNode ) OutProcLiteral ( "** bad parent link => " ); if ( currentChildOptions & kXMP_PropIsQualifier ) OutProcLiteral ( "** bad qual flag => " ); if ( options & kXMP_PropValueIsArray ) { itemIndex = childNum + 1; if (XMP_LitMatch(currentChild->GetName()->c_str(), kXMP_ArrayItemName) ) OutProcLiteral ( "** bad item name => " ); } else { itemIndex = 0; if ( XMP_LitMatch(currentChild->GetName()->c_str(), kXMP_ArrayItemName ) ) OutProcLiteral ( "** bad field name => " ); } DumpIXMPPropertyTree ( currentChild, indent + 1, itemIndex, outProc, refCon ); } } // DumpPropertyTree // ------------------------------------------------------------------------------------------------- // DumpObject // ---------- void XMPMeta2::DumpObject ( XMP_TextOutputProc outProc, void * refCon ) const { // TODO // value of mDOM ? XMP_Assert ( outProc != 0 ); // ! Enforced by wrapper. auto defaultMap = INameSpacePrefixMap::GetDefaultNameSpacePrefixMap(); OutProcLiteral ( "Dumping XMPMeta object \"" ); DumpClearString (mDOM->GetAboutURI()->c_str(), outProc, refCon ); OutProcNChars ( "\" ", 3 ); DumpNodeOptions ( XMPUtils::GetIXMPOptions(mDOM), outProc, refCon ); OutProcNewline(); // One can't possibly allocate mDOM a value ?! /* if ( ! tree.value.empty() ) { OutProcLiteral ( "** bad root value ** \"" ); DumpClearString ( tree.value, outProc, refCon ); OutProcNChars ( "\"", 1 ); OutProcNewline(); } */ if ( mDOM->HasQualifiers() ) { OutProcLiteral ( "** bad root qualifiers **" ); OutProcNewline(); spINodeIterator qualIter = mDOM->QualifiersIterator(); for ( ; qualIter; qualIter = qualIter->Next() ) { DumpIXMPPropertyTree ( qualIter->GetNode(), 3, 0, outProc, refCon ); } } map schemaUsed; if ( mDOM->ChildCount() ) { spINodeIterator childIter = mDOM->Iterator(); for ( ;childIter; childIter = childIter->Next() ) { spINode currSchema = childIter->GetNode(); XMP_OptionBits currSchemaOptions = kXMP_SchemaNode; if(!schemaUsed.count(currSchema->GetNameSpace()->c_str())) { OutProcNewline(); OutProcIndent ( 1 ); XMP_VarString prefix = defaultMap->GetPrefix(currSchema->GetNameSpace()->c_str(), currSchema->GetNameSpace()->size() )->c_str(); prefix += ":"; DumpClearString ( prefix.c_str(), outProc, refCon ); OutProcNChars ( " ", 2 ); DumpClearString ( currSchema->GetNameSpace()->c_str(), outProc, refCon ); OutProcNChars ( " ", 2 ); DumpNodeOptions ( currSchemaOptions, outProc, refCon ); OutProcNewline(); if ( ! (currSchemaOptions & kXMP_SchemaNode) ) { OutProcLiteral ( "** bad schema options **" ); OutProcNewline(); } schemaUsed[currSchema->GetNameSpace()->c_str()] = true; } DumpIXMPPropertyTree ( currSchema, 2, 0, outProc, refCon ); } } } // DumpObject // ------------------------------------------------------------------------------------------------- // SetErrorCallback // ---------------- void XMPMeta2::SetErrorCallback( XMPMeta_ErrorCallbackWrapper wrapperProc, XMPMeta_ErrorCallbackProc clientProc, void * context, XMP_Uns32 limit) { XMP_Assert(wrapperProc != 0); // Must always be set by the glue; this->errorCallback.Clear(); this->errorCallback.wrapperProc = wrapperProc; this->errorCallback.clientProc = clientProc; this->errorCallback.context = context; this->errorCallback.limit = limit; spParser->GetIDOMParser_I()->SetErrorCallback(&errorCallback); } // SetErrorCallback // ------------------------------------------------------------------------------------------------- // ResetErrorCallbackLimit // ----------------------- void XMPMeta2::ResetErrorCallbackLimit(XMP_Uns32 limit) { this->errorCallback.limit = limit; this->errorCallback.notifications = 0; this->errorCallback.topSeverity = kXMPErrSev_Recoverable; spParser->GetIDOMParser_I()->SetErrorCallback(&errorCallback); } // ResetErrorCallbackLimit #endif diff --git a/core/libs/dngwriter/extra/xmp_sdk/XMPCore/source/XMPUtils-FileInfo.cpp b/core/libs/dngwriter/extra/xmp_sdk/XMPCore/source/XMPUtils-FileInfo.cpp index 6c7073f964..56699eea55 100644 --- a/core/libs/dngwriter/extra/xmp_sdk/XMPCore/source/XMPUtils-FileInfo.cpp +++ b/core/libs/dngwriter/extra/xmp_sdk/XMPCore/source/XMPUtils-FileInfo.cpp @@ -1,1927 +1,1927 @@ // ================================================================================================= // Copyright 2003 Adobe Systems Incorporated // All Rights Reserved. // // NOTICE: Adobe permits you to use, modify, and distribute this file in accordance with the terms // of the Adobe license agreement accompanying it. // ================================================================================================= #include "public/include/XMP_Environment.h" // ! This must be the first include! #include "XMPCore/source/XMPCore_Impl.hpp" #include "XMPCore/XMPCoreDefines.h" #include "XMPCore/source/XMPUtils.hpp" #if ENABLE_CPP_DOM_MODEL #include "source/UnicodeInlines.incl_cpp" #include "source/UnicodeConversions.hpp" #include "source/ExpatAdapter.hpp" - #include "third-party/zuid/interfaces/MD5.h" + #include "XMP_MD5.h" #include "XMPCore/Interfaces/IMetadata_I.h" #include "XMPCore/Interfaces/IArrayNode_I.h" #include "XMPCore/Interfaces/ISimpleNode_I.h" #include "XMPCore/Interfaces/INodeIterator_I.h" #include "XMPCore/Interfaces/IPathSegment_I.h" #include "XMPCore/Interfaces/IPath_I.h" #include "XMPCore/Interfaces/INameSpacePrefixMap_I.h" #include "XMPCore/Interfaces/IDOMImplementationRegistry_I.h" #include "XMPCommon/Interfaces/IUTF8String_I.h" #endif #include // For binary_search. #include #include #include #include #include #include // For snprintf. #if XMP_WinBuild #pragma warning ( disable : 4800 ) // forcing value to bool 'true' or 'false' (performance warning) #endif // ================================================================================================= // Local Types and Constants // ========================= typedef unsigned long UniCodePoint; enum UniCharKind { UCK_normal, UCK_space, UCK_comma, UCK_semicolon, UCK_quote, UCK_control }; typedef enum UniCharKind UniCharKind; #define UnsByte(c) ((unsigned char)(c)) #define UCP(u) ((UniCodePoint)(u)) // ! Needed on Windows (& PC Linux?) for inequalities with literals ito avoid sign extension. #ifndef TraceMultiFile #define TraceMultiFile 0 #endif // ================================================================================================= // Static Variables // ================ // ================================================================================================= // Local Utilities // =============== // ------------------------------------------------------------------------------------------------- // ClassifyCharacter // ----------------- static void ClassifyCharacter ( XMP_StringPtr fullString, size_t offset, UniCharKind * charKind, size_t * charSize, UniCodePoint * uniChar ) { *charKind = UCK_normal; // Assume typical case. unsigned char currByte = UnsByte ( fullString[offset] ); if ( currByte < UnsByte(0x80) ) { // ---------------------------------------- // We've got a single byte ASCII character. *charSize = 1; *uniChar = currByte; if ( currByte > UnsByte(0x22) ) { if ( currByte == UnsByte(0x2C) ) { *charKind = UCK_comma; } else if ( currByte == UnsByte(0x3B) ) { *charKind = UCK_semicolon; } // [2674672] Discontinue to interpret square brackets // as Asian quotes in XMPUtils::SeparateArrayItems(..)) // *** else if ( (currByte == UnsByte(0x5B)) || (currByte == UnsByte(0x5D)) ) { // *** *charKind = UCK_quote; // ! ASCII '[' and ']' are used as quotes in Chinese and Korean. // *** } } else { // currByte <= 0x22 if ( currByte == UnsByte(0x22) ) { *charKind = UCK_quote; } else if ( currByte == UnsByte(0x21) ) { *charKind = UCK_normal; } else if ( currByte == UnsByte(0x20) ) { *charKind = UCK_space; } else { *charKind = UCK_control; } } } else { // currByte >= 0x80 // --------------------------------------------------------------------------------------- // We've got a multibyte Unicode character. The first byte has the number of bytes and the // highest order bits. The other bytes each add 6 more bits. Compose the UTF-32 form so we // can classify directly with the Unicode code points. Order the upperBits tests to be // fastest for Japan, probably the most common non-ASCII usage. *charSize = 0; *uniChar = currByte; while ( (*uniChar & 0x80) != 0 ) { // Count the leading 1 bits in the byte. ++(*charSize); *uniChar = *uniChar << 1; } XMP_Assert ( (offset + *charSize) <= strlen(fullString) ); *uniChar = *uniChar & 0x7F; // Put the character bits in the bottom of uniChar. *uniChar = *uniChar >> *charSize; for ( size_t i = (offset + 1); i < (offset + *charSize); ++i ) { *uniChar = (*uniChar << 6) | (UnsByte(fullString[i]) & 0x3F); } XMP_Uns32 upperBits = static_cast(*uniChar >> 8); // First filter on just the high order 24 bits. if ( upperBits == 0xFF ) { // U+FFxx if ( *uniChar == UCP(0xFF0C) ) { *charKind = UCK_comma; // U+FF0C, full width comma. } else if ( *uniChar == UCP(0xFF1B) ) { *charKind = UCK_semicolon; // U+FF1B, full width semicolon. } else if ( *uniChar == UCP(0xFF64) ) { *charKind = UCK_comma; // U+FF64, half width ideographic comma. } } else if ( upperBits == 0xFE ) { // U+FE-- if ( *uniChar == UCP(0xFE50) ) { *charKind = UCK_comma; // U+FE50, small comma. } else if ( *uniChar == UCP(0xFE51) ) { *charKind = UCK_comma; // U+FE51, small ideographic comma. } else if ( *uniChar == UCP(0xFE54) ) { *charKind = UCK_semicolon; // U+FE54, small semicolon. } } else if ( upperBits == 0x30 ) { // U+30-- if ( *uniChar == UCP(0x3000) ) { *charKind = UCK_space; // U+3000, ideographic space. } else if ( *uniChar == UCP(0x3001) ) { *charKind = UCK_comma; // U+3001, ideographic comma. } else if ( (UCP(0x3008) <= *uniChar) && (*uniChar <= UCP(0x300F)) ) { *charKind = UCK_quote; // U+3008..U+300F, various quotes. } else if ( *uniChar == UCP(0x303F) ) { *charKind = UCK_space; // U+303F, ideographic half fill space. } else if ( (UCP(0x301D) <= *uniChar) && (*uniChar <= UCP(0x301F)) ) { *charKind = UCK_quote; // U+301D..U+301F, double prime quotes. } } else if ( upperBits == 0x20 ) { // U+20-- if ( (UCP(0x2000) <= *uniChar) && (*uniChar <= UCP(0x200B)) ) { *charKind = UCK_space; // U+2000..U+200B, en quad through zero width space. } else if ( *uniChar == UCP(0x2015) ) { *charKind = UCK_quote; // U+2015, dash quote. } else if ( (UCP(0x2018) <= *uniChar) && (*uniChar <= UCP(0x201F)) ) { *charKind = UCK_quote; // U+2018..U+201F, various quotes. } else if ( *uniChar == UCP(0x2028) ) { *charKind = UCK_control; // U+2028, line separator. } else if ( *uniChar == UCP(0x2029) ) { *charKind = UCK_control; // U+2029, paragraph separator. } else if ( (*uniChar == UCP(0x2039)) || (*uniChar == UCP(0x203A)) ) { *charKind = UCK_quote; // U+2039 and U+203A, guillemet quotes. } } else if ( upperBits == 0x06 ) { // U+06-- if ( *uniChar == UCP(0x060C) ) { *charKind = UCK_comma; // U+060C, Arabic comma. } else if ( *uniChar == UCP(0x061B) ) { *charKind = UCK_semicolon; // U+061B, Arabic semicolon. } } else if ( upperBits == 0x05 ) { // U+05-- if ( *uniChar == UCP(0x055D) ) { *charKind = UCK_comma; // U+055D, Armenian comma. } } else if ( upperBits == 0x03 ) { // U+03-- if ( *uniChar == UCP(0x037E) ) { *charKind = UCK_semicolon; // U+037E, Greek "semicolon" (really a question mark). } } else if ( upperBits == 0x00 ) { // U+00-- if ( (*uniChar == UCP(0x00AB)) || (*uniChar == UCP(0x00BB)) ) { *charKind = UCK_quote; // U+00AB and U+00BB, guillemet quotes. } } } } // ClassifyCharacter // ------------------------------------------------------------------------------------------------- // IsClosingingQuote // ----------------- static inline bool IsClosingingQuote ( UniCodePoint uniChar, UniCodePoint openQuote, UniCodePoint closeQuote ) { if ( (uniChar == closeQuote) || ( (openQuote == UCP(0x301D)) && ((uniChar == UCP(0x301E)) || (uniChar == UCP(0x301F))) ) ) { return true; } else { return false; } } // IsClosingingQuote // ------------------------------------------------------------------------------------------------- // IsSurroundingQuote // ------------------ static inline bool IsSurroundingQuote ( UniCodePoint uniChar, UniCodePoint openQuote, UniCodePoint closeQuote ) { if ( (uniChar == openQuote) || IsClosingingQuote ( uniChar, openQuote, closeQuote ) ) { return true; } else { return false; } } // IsSurroundingQuote // ------------------------------------------------------------------------------------------------- // GetClosingQuote // --------------- static UniCodePoint GetClosingQuote ( UniCodePoint openQuote ) { UniCodePoint closeQuote; switch ( openQuote ) { case UCP(0x0022) : closeQuote = UCP(0x0022); // ! U+0022 is both opening and closing. break; // *** [2674672] Discontinue to interpret square brackets // *** as Asian quotes in XMPUtils::SeparateArrayItems(..)) // *** case UCP(0x005B) : closeQuote = UCP(0x005D); // *** break; case UCP(0x00AB) : closeQuote = UCP(0x00BB); // ! U+00AB and U+00BB are reversible. break; case UCP(0x00BB) : closeQuote = UCP(0x00AB); break; case UCP(0x2015) : closeQuote = UCP(0x2015); // ! U+2015 is both opening and closing. break; case UCP(0x2018) : closeQuote = UCP(0x2019); break; case UCP(0x201A) : closeQuote = UCP(0x201B); break; case UCP(0x201C) : closeQuote = UCP(0x201D); break; case UCP(0x201E) : closeQuote = UCP(0x201F); break; case UCP(0x2039) : closeQuote = UCP(0x203A); // ! U+2039 and U+203A are reversible. break; case UCP(0x203A) : closeQuote = UCP(0x2039); break; case UCP(0x3008) : closeQuote = UCP(0x3009); break; case UCP(0x300A) : closeQuote = UCP(0x300B); break; case UCP(0x300C) : closeQuote = UCP(0x300D); break; case UCP(0x300E) : closeQuote = UCP(0x300F); break; case UCP(0x301D) : closeQuote = UCP(0x301F); // ! U+301E also closes U+301D. break; default : closeQuote = 0; break; } return closeQuote; } // GetClosingQuote // ------------------------------------------------------------------------------------------------- // CodePointToUTF8 // --------------- static void CodePointToUTF8 ( UniCodePoint uniChar, XMP_VarString & utf8Str ) { size_t i, byteCount; XMP_Uns8 buffer [8]; UniCodePoint cpTemp; if ( uniChar <= 0x7F ) { i = 7; byteCount = 1; buffer[7] = char(uniChar); } else { // --------------------------------------------------------------------------------------- // Copy the data bits from the low order end to the high order end, include the 0x80 mask. i = 8; cpTemp = uniChar; while ( cpTemp != 0 ) { -- i; // Exit with i pointing to the last byte stored. buffer[i] = UnsByte(0x80) | (UnsByte(cpTemp) & 0x3F); cpTemp = cpTemp >> 6; } byteCount = 8 - i; // The total number of bytes needed. XMP_Assert ( (2 <= byteCount) && (byteCount <= 6) ); // ------------------------------------------------------------------------------------- // Make sure the high order byte can hold the byte count mask, compute and set the mask. size_t bitCount = 0; // The number of data bits in the first byte. for ( cpTemp = (buffer[i] & UnsByte(0x3F)); cpTemp != 0; cpTemp = cpTemp >> 1 ) bitCount += 1; if ( bitCount > (8 - (byteCount + 1)) ) byteCount += 1; i = 8 - byteCount; // First byte index and mask shift count. XMP_Assert ( (0 <= i) && (i <= 6) ); buffer[i] |= (UnsByte(0xFF) << i) & UnsByte(0xFF); // AUDIT: Safe, i is between 0 and 6. } utf8Str.assign ( (char*)(&buffer[i]), byteCount ); } // CodePointToUTF8 // ------------------------------------------------------------------------------------------------- // ApplyQuotes // ----------- static void ApplyQuotes ( XMP_VarString * item, UniCodePoint openQuote, UniCodePoint closeQuote, bool allowCommas ) { bool prevSpace = false; size_t charOffset, charLen; UniCharKind charKind; UniCodePoint uniChar; // ----------------------------------------------------------------------------------------- // See if there are any separators in the value. Stop at the first occurrance. This is a bit // tricky in order to make typical typing work conveniently. The purpose of applying quotes // is to preserve the values when splitting them back apart. That is CatenateContainerItems // and SeparateContainerItems must round trip properly. For the most part we only look for // separators here. Internal quotes, as in -- Irving "Bud" Jones -- won't cause problems in // the separation. An initial quote will though, it will make the value look quoted. charOffset = 0; ClassifyCharacter ( item->c_str(), charOffset, &charKind, &charLen, &uniChar ); if ( charKind != UCK_quote ) { for ( charOffset = 0; size_t(charOffset) < item->size(); charOffset += charLen ) { ClassifyCharacter ( item->c_str(), charOffset, &charKind, &charLen, &uniChar ); if ( charKind == UCK_space ) { if ( prevSpace ) break; // Multiple spaces are a separator. prevSpace = true; } else { prevSpace = false; if ( (charKind == UCK_semicolon) || (charKind == UCK_control) ) break; if ( (charKind == UCK_comma) && (! allowCommas) ) break; } } } if ( size_t(charOffset) < item->size() ) { // -------------------------------------------------------------------------------------- // Create a quoted copy, doubling any internal quotes that match the outer ones. Internal // quotes did not stop the "needs quoting" search, but they do need doubling. So we have // to rescan the front of the string for quotes. Handle the special case of U+301D being // closed by either U+301E or U+301F. XMP_VarString newItem; size_t splitPoint; for ( splitPoint = 0; splitPoint <= charOffset; ++splitPoint ) { ClassifyCharacter ( item->c_str(), splitPoint, &charKind, &charLen, &uniChar ); if ( charKind == UCK_quote ) break; } CodePointToUTF8 ( openQuote, newItem ); newItem.append ( *item, 0, splitPoint ); // Copy the leading "normal" portion. for ( charOffset = splitPoint; size_t(charOffset) < item->size(); charOffset += charLen ) { ClassifyCharacter ( item->c_str(), charOffset, &charKind, &charLen, &uniChar ); newItem.append ( *item, charOffset, charLen ); if ( (charKind == UCK_quote) && IsSurroundingQuote ( uniChar, openQuote, closeQuote ) ) { newItem.append ( *item, charOffset, charLen ); } } XMP_VarString closeStr; CodePointToUTF8 ( closeQuote, closeStr ); newItem.append ( closeStr ); *item = newItem; } } // ApplyQuotes // ------------------------------------------------------------------------------------------------- // IsInternalProperty // ------------------ // *** Need static checks of the schema prefixes! static const char * kExternalxmpDM[] = { "xmpDM:album", "xmpDM:altTapeName", "xmpDM:altTimecode", "xmpDM:artist", "xmpDM:cameraAngle", "xmpDM:cameraLabel", "xmpDM:cameraModel", "xmpDM:cameraMove", "xmpDM:client", "xmpDM:comment", "xmpDM:composer", "xmpDM:director", "xmpDM:directorPhotography", "xmpDM:engineer", "xmpDM:genre", "xmpDM:good", "xmpDM:instrument", "xmpDM:logComment", "xmpDM:projectName", "xmpDM:releaseDate", "xmpDM:scene", "xmpDM:shotDate", "xmpDM:shotDay", "xmpDM:shotLocation", "xmpDM:shotName", "xmpDM:shotNumber", "xmpDM:shotSize", "xmpDM:speakerPlacement", "xmpDM:takeNumber", "xmpDM:tapeName", "xmpDM:trackNumber", "xmpDM:videoAlphaMode", "xmpDM:videoAlphaPremultipleColor", 0 }; // ! Must have zero sentinel! typedef const char ** CharStarIterator; // Used for binary search of kExternalxmpDM; static const char ** kLastExternalxmpDM = 0; // Set on first use. static int CharStarLess (const char * left, const char * right ) { return (strcmp ( left, right ) < 0); } #define IsExternalProperty(s,p) (! IsInternalProperty ( s, p )) bool IsInternalProperty ( const XMP_VarString & schema, const XMP_VarString & prop ) { bool isInternal = false; if ( schema == kXMP_NS_DC ) { if ( (prop == "dc:format") || (prop == "dc:language") ) { isInternal = true; } } else if ( schema == kXMP_NS_XMP ) { if ( (prop == "xmp:BaseURL") || (prop == "xmp:CreatorTool") || (prop == "xmp:Format") || (prop == "xmp:Locale") || (prop == "xmp:MetadataDate") || (prop == "xmp:ModifyDate") ) { isInternal = true; } } else if ( schema == kXMP_NS_PDF ) { if ( (prop == "pdf:BaseURL") || (prop == "pdf:Creator") || (prop == "pdf:ModDate") || (prop == "pdf:PDFVersion") || (prop == "pdf:Producer") ) { isInternal = true; } } else if ( schema == kXMP_NS_TIFF ) { isInternal = true; // ! The TIFF properties are internal by default. if ( (prop == "tiff:ImageDescription") || // ! ImageDescription, Artist, and Copyright are aliased. (prop == "tiff:Artist") || (prop == "tiff:Copyright") ) { isInternal = false; } } else if ( schema == kXMP_NS_EXIF ) { isInternal = true; // ! The EXIF properties are internal by default. if ( prop == "exif:UserComment" ) isInternal = false; } else if ( schema == kXMP_NS_EXIF_Aux ) { isInternal = true; // ! The EXIF Aux properties are internal by default. } else if ( schema == kXMP_NS_Photoshop ) { if ( (prop == "photoshop:ICCProfile") || (prop == "photoshop:TextLayers") ) isInternal = true; } else if ( schema == kXMP_NS_CameraRaw ) { isInternal = true; // All of crs: is internal, they are processing settings. } else if ( schema == kXMP_NS_DM ) { // ! Most of the xmpDM schema is internal, and unknown properties default to internal. if ( kLastExternalxmpDM == 0 ) { for ( kLastExternalxmpDM = &kExternalxmpDM[0]; *kLastExternalxmpDM != 0; ++kLastExternalxmpDM ) {} } isInternal = (! std::binary_search ( &kExternalxmpDM[0], kLastExternalxmpDM, prop.c_str(), CharStarLess )); } else if ( schema == kXMP_NS_Script ) { isInternal = true; // ! Most of the xmpScript schema is internal, and unknown properties default to internal. if ( (prop == "xmpScript:action") || (prop == "xmpScript:character") || (prop == "xmpScript:dialog") || (prop == "xmpScript:sceneSetting") || (prop == "xmpScript:sceneTimeOfDay") ) { isInternal = false; } } else if ( schema == kXMP_NS_BWF ) { if ( prop == "bext:version" ) isInternal = true; } else if ( schema == kXMP_NS_AdobeStockPhoto ) { isInternal = true; // ! The bmsp schema has only internal properties. } else if ( schema == kXMP_NS_XMP_MM ) { isInternal = true; // ! The xmpMM schema has only internal properties. } else if ( schema == kXMP_NS_XMP_Text ) { isInternal = true; // ! The xmpT schema has only internal properties. } else if ( schema == kXMP_NS_XMP_PagedFile ) { isInternal = true; // ! The xmpTPg schema has only internal properties. } else if ( schema == kXMP_NS_XMP_Graphics ) { isInternal = true; // ! The xmpG schema has only internal properties. } else if ( schema == kXMP_NS_XMP_Image ) { isInternal = true; // ! The xmpGImg schema has only internal properties. } else if ( schema == kXMP_NS_XMP_Font ) { isInternal = true; // ! The stFNT schema has only internal properties. } return isInternal; } // IsInternalProperty // ------------------------------------------------------------------------------------------------- // RemoveSchemaChildren // -------------------- static void RemoveSchemaChildren ( XMP_NodePtrPos schemaPos, bool doAll ) { XMP_Node * schemaNode = *schemaPos; XMP_Assert ( XMP_NodeIsSchema ( schemaNode->options ) ); // ! Iterate backwards to reduce shuffling as children are erased and to simplify the logic for // ! denoting the current child. (Erasing child n makes the old n+1 now be n.) size_t propCount = schemaNode->children.size(); XMP_NodePtrPos beginPos = schemaNode->children.begin(); for ( size_t propNum = propCount-1, propLim = (size_t)(-1); propNum != propLim; --propNum ) { XMP_NodePtrPos currProp = beginPos + propNum; if ( doAll || IsExternalProperty ( schemaNode->name, (*currProp)->name ) ) { delete *currProp; // ! Both delete the node and erase the pointer from the parent. schemaNode->children.erase ( currProp ); } } if ( schemaNode->children.empty() ) { XMP_Node * tree = schemaNode->parent; tree->children.erase ( schemaPos ); delete schemaNode; } } // RemoveSchemaChildren // ------------------------------------------------------------------------------------------------- // ItemValuesMatch // --------------- // // Does the value comparisons for array merging as part of XMPUtils::AppendProperties. static bool ItemValuesMatch ( const XMP_Node * leftNode, const XMP_Node * rightNode ) { const XMP_OptionBits leftForm = leftNode->options & kXMP_PropCompositeMask; const XMP_OptionBits rightForm = leftNode->options & kXMP_PropCompositeMask; if ( leftForm != rightForm ) return false; if ( leftForm == 0 ) { // Simple nodes, check the values and xml:lang qualifiers. if ( leftNode->value != rightNode->value ) return false; if ( (leftNode->options & kXMP_PropHasLang) != (rightNode->options & kXMP_PropHasLang) ) return false; if ( leftNode->options & kXMP_PropHasLang ) { if ( leftNode->qualifiers[0]->value != rightNode->qualifiers[0]->value ) return false; } } else if ( leftForm == kXMP_PropValueIsStruct ) { // Struct nodes, see if all fields match, ignoring order. if ( leftNode->children.size() != rightNode->children.size() ) return false; for ( size_t leftNum = 0, leftLim = leftNode->children.size(); leftNum != leftLim; ++leftNum ) { const XMP_Node * leftField = leftNode->children[leftNum]; const XMP_Node * rightField = FindConstChild ( rightNode, leftField->name.c_str() ); if ( (rightField == 0) || (! ItemValuesMatch ( leftField, rightField )) ) return false; } } else { // Array nodes, see if the "leftNode" values are present in the "rightNode", ignoring order, duplicates, // and extra values in the rightNode-> The rightNode is the destination for AppendProperties. XMP_Assert ( leftForm & kXMP_PropValueIsArray ); for ( size_t leftNum = 0, leftLim = leftNode->children.size(); leftNum != leftLim; ++leftNum ) { const XMP_Node * leftItem = leftNode->children[leftNum]; size_t rightNum, rightLim; for ( rightNum = 0, rightLim = rightNode->children.size(); rightNum != rightLim; ++rightNum ) { const XMP_Node * rightItem = rightNode->children[rightNum]; if ( ItemValuesMatch ( leftItem, rightItem ) ) break; } if ( rightNum == rightLim ) return false; } } return true; // All of the checks passed. } // ItemValuesMatch // ------------------------------------------------------------------------------------------------- // AppendSubtree // ------------- // // The main implementation of XMPUtils::AppendProperties. See the description in TXMPMeta.hpp. static void AppendSubtree ( const XMP_Node * sourceNode, XMP_Node * destParent, const bool mergeCompound, const bool replaceOld, const bool deleteEmpty ) { XMP_NodePtrPos destPos; XMP_Node * destNode = FindChildNode ( destParent, sourceNode->name.c_str(), kXMP_ExistingOnly, &destPos ); bool valueIsEmpty = false; if ( XMP_PropIsSimple ( sourceNode->options ) ) { valueIsEmpty = sourceNode->value.empty(); } else { valueIsEmpty = sourceNode->children.empty(); } if ( valueIsEmpty ) { if ( deleteEmpty && (destNode != 0) ) { delete ( destNode ); destParent->children.erase ( destPos ); } return; // ! Done, empty values are either ignored or cause deletions. } if ( destNode == 0 ) { // The one easy case, the destination does not exist. destNode = CloneSubtree ( sourceNode, destParent, true /* skipEmpty */ ); XMP_Assert ( (destNode == 0) || (! destNode->value.empty()) || (! destNode->children.empty()) ); return; } // If we get here we're going to modify an existing property, either replacing or merging. XMP_Assert ( (! valueIsEmpty) && (destNode != 0) ); XMP_OptionBits sourceForm = sourceNode->options & kXMP_PropCompositeMask; XMP_OptionBits destForm = destNode->options & kXMP_PropCompositeMask; bool replaceThis = replaceOld; // ! Don't modify replaceOld, it gets passed to inner calls. if ( mergeCompound && (! XMP_PropIsSimple ( sourceForm )) ) replaceThis = false; if ( replaceThis ) { destNode->value = sourceNode->value; // *** Should use SetNode. destNode->options = sourceNode->options; destNode->RemoveChildren(); destNode->RemoveQualifiers(); CloneOffspring ( sourceNode, destNode, true /* skipEmpty */ ); if ( (! XMP_PropIsSimple ( destNode->options )) && destNode->children.empty() ) { // Don't keep an empty array or struct. The source might be implicitly empty due to // all children being empty. In this case CloneOffspring should skip them. DeleteSubtree ( destPos ); } return; } // From here on are cases for merging arrays or structs. if ( XMP_PropIsSimple ( sourceForm ) || (sourceForm != destForm) ) return; if ( sourceForm == kXMP_PropValueIsStruct ) { // To merge a struct process the fields recursively. E.g. add simple missing fields. The // recursive call to AppendSubtree will handle deletion for fields with empty values. for ( size_t sourceNum = 0, sourceLim = sourceNode->children.size(); sourceNum != sourceLim && destNode!= NULL; ++sourceNum ) { const XMP_Node * sourceField = sourceNode->children[sourceNum]; AppendSubtree ( sourceField, destNode, mergeCompound, replaceOld, deleteEmpty ); if ( deleteEmpty && destNode->children.empty() ) { delete ( destNode ); destParent->children.erase ( destPos ); } } } else if ( sourceForm & kXMP_PropArrayIsAltText ) { // Merge AltText arrays by the xml:lang qualifiers. Make sure x-default is first. Make a // special check for deletion of empty values. Meaningful in AltText arrays because the // xml:lang qualifier provides unambiguous source/dest correspondence. XMP_Assert ( mergeCompound ); for ( size_t sourceNum = 0, sourceLim = sourceNode->children.size(); sourceNum != sourceLim && destNode!= NULL; ++sourceNum ) { const XMP_Node * sourceItem = sourceNode->children[sourceNum]; if ( sourceItem->qualifiers.empty() || (sourceItem->qualifiers[0]->name != "xml:lang") ) continue; XMP_Index destIndex = LookupLangItem ( destNode, sourceItem->qualifiers[0]->value ); if ( sourceItem->value.empty() ) { if ( deleteEmpty && (destIndex != -1) ) { delete ( destNode->children[destIndex] ); destNode->children.erase ( destNode->children.begin() + destIndex ); if ( destNode->children.empty() ) { delete ( destNode ); destParent->children.erase ( destPos ); } } } else { if ( destIndex != -1 ) { // The source and dest arrays both have this language item. if ( replaceOld ) { // ! Yes, check replaceOld not replaceThis! destNode->children[destIndex]->value = sourceItem->value; } } else { // The dest array does not have this language item, add it. if ( (sourceItem->qualifiers[0]->value != "x-default") || destNode->children.empty() ) { // Typical case, empty dest array or not "x-default". Non-empty should always have "x-default". CloneSubtree ( sourceItem, destNode, true /* skipEmpty */ ); } else { // Edge case, non-empty dest array had no "x-default", insert that at the beginning. XMP_Node * destItem = new XMP_Node ( destNode, sourceItem->name, sourceItem->value, sourceItem->options ); CloneOffspring ( sourceItem, destItem, true /* skipEmpty */ ); destNode->children.insert ( destNode->children.begin(), destItem ); } } } } } else if ( sourceForm & kXMP_PropValueIsArray ) { // Merge other arrays by item values. Don't worry about order or duplicates. Source // items with empty values do not cause deletion, that conflicts horribly with merging. for ( size_t sourceNum = 0, sourceLim = sourceNode->children.size(); sourceNum != sourceLim; ++sourceNum ) { const XMP_Node * sourceItem = sourceNode->children[sourceNum]; size_t destNum, destLim; for ( destNum = 0, destLim = destNode->children.size(); destNum != destLim; ++destNum ) { const XMP_Node * destItem = destNode->children[destNum]; if ( ItemValuesMatch ( sourceItem, destItem ) ) break; } if ( destNum == destLim ) CloneSubtree ( sourceItem, destNode, true /* skipEmpty */ ); } } } // AppendSubtree // ================================================================================================= // Class Static Functions // ====================== // ------------------------------------------------------------------------------------------------- // CatenateArrayItems // ------------------ #if ENABLE_CPP_DOM_MODEL // ------------------------------------------------------------------------------------------------- // CatenateArrayItems_v2 // ------------------ /* class static */ void XMPUtils::CatenateArrayItems_v2(const XMPMeta & ptr, XMP_StringPtr schemaNS, XMP_StringPtr arrayName, XMP_StringPtr separator, XMP_StringPtr quotes, XMP_OptionBits options, XMP_VarString * catedStr) { using namespace AdobeXMPCore; using namespace AdobeXMPCommon; if(sUseNewCoreAPIs) { const XMPMeta2 & xmpObj = dynamic_cast(ptr); XMP_Assert((schemaNS != 0) && (arrayName != 0)); // ! Enforced by wrapper. XMP_Assert((separator != 0) && (quotes != 0) && (catedStr != 0)); // ! Enforced by wrapper. size_t strLen, strPos, charLen; UniCharKind charKind; UniCodePoint currUCP, openQuote, closeQuote; const bool allowCommas = ((options & kXMPUtil_AllowCommas) != 0); spINode arrayNode; // ! Move up to avoid gcc complaints. XMP_OptionBits arrayForm = 0, arrayOptions = 0; spcINode currItem; // Make sure the separator is OK. It must be one semicolon surrounded by zero or more spaces. // Any of the recognized semicolons or spaces are allowed. strPos = 0; strLen = strlen(separator); bool haveSemicolon = false; while (strPos < strLen) { ClassifyCharacter(separator, strPos, &charKind, &charLen, &currUCP); strPos += charLen; if (charKind == UCK_semicolon) { if (haveSemicolon) XMP_Throw("Separator can have only one semicolon", kXMPErr_BadParam); haveSemicolon = true; } else if (charKind != UCK_space) { XMP_Throw("Separator can have only spaces and one semicolon", kXMPErr_BadParam); } }; if (!haveSemicolon) XMP_Throw("Separator must have one semicolon", kXMPErr_BadParam); // Make sure the open and close quotes are a legitimate pair. strLen = strlen(quotes); ClassifyCharacter(quotes, 0, &charKind, &charLen, &openQuote); if (charKind != UCK_quote) XMP_Throw("Invalid quoting character", kXMPErr_BadParam); if (charLen == strLen) { closeQuote = openQuote; } else { strPos = charLen; ClassifyCharacter(quotes, strPos, &charKind, &charLen, &closeQuote); if (charKind != UCK_quote) XMP_Throw("Invalid quoting character", kXMPErr_BadParam); if ((strPos + charLen) != strLen) XMP_Throw("Quoting string too long", kXMPErr_BadParam); } if (closeQuote != GetClosingQuote(openQuote)) XMP_Throw("Mismatched quote pair", kXMPErr_BadParam); // Return an empty result if the array does not exist, hurl if it isn't the right form. catedStr->erase(); XMP_ExpandedXPath arrayPath; ExpandXPath(schemaNS, arrayName, &arrayPath); XMPUtils::FindCnstNode((xmpObj.mDOM), arrayPath, arrayNode, &arrayOptions); if (!arrayNode) return; arrayForm = arrayOptions & kXMP_PropCompositeMask; if ((!(arrayForm & kXMP_PropValueIsArray)) || (arrayForm & kXMP_PropArrayIsAlternate)) { XMP_Throw("Named property must be non-alternate array", kXMPErr_BadParam); } size_t arrayChildCount = XMPUtils::GetNodeChildCount(arrayNode); if (!arrayChildCount) return; // Build the result, quoting the array items, adding separators. Hurl if any item isn't simple. // Start the result with the first value, then add the rest with a preceeding separator. spcINodeIterator arrayIter = XMPUtils::GetNodeChildIterator(arrayNode); if ((XMPUtils::GetIXMPOptions(currItem) & kXMP_PropCompositeMask) != 0) XMP_Throw("Array items must be simple", kXMPErr_BadParam); *catedStr = arrayIter->GetNode()->ConvertToSimpleNode()->GetValue()->c_str(); ApplyQuotes(catedStr, openQuote, closeQuote, allowCommas); //ArrayNodes in the new DOM are homogeneous so need to check types of other items in the arary if the first one is Simple for (arrayIter = arrayIter->Next(); arrayIter; arrayIter = arrayIter->Next()) { XMP_VarString tempStr( arrayIter->GetNode()->ConvertToSimpleNode()->GetValue()->c_str()); ApplyQuotes(&tempStr, openQuote, closeQuote, allowCommas); *catedStr += separator; *catedStr += tempStr; } } else { return; } } // CatenateArrayItems_v2 #endif // ------------------------------------------------------------------------------------------------- /* class static */ void XMPUtils::CatenateArrayItems ( const XMPMeta & xmpObj, XMP_StringPtr schemaNS, XMP_StringPtr arrayName, XMP_StringPtr separator, XMP_StringPtr quotes, XMP_OptionBits options, XMP_VarString * catedStr ) { #if ENABLE_CPP_DOM_MODEL if(sUseNewCoreAPIs) { dynamic_cast(xmpObj); CatenateArrayItems_v2(xmpObj, schemaNS, arrayName, separator, quotes, options, catedStr); return; } #endif XMP_Assert ( (schemaNS != 0) && (arrayName != 0) ); // ! Enforced by wrapper. XMP_Assert ( (separator != 0) && (quotes != 0) && (catedStr != 0) ); // ! Enforced by wrapper. size_t strLen, strPos, charLen; UniCharKind charKind; UniCodePoint currUCP, openQuote, closeQuote; const bool allowCommas = ((options & kXMPUtil_AllowCommas) != 0); const XMP_Node * arrayNode = 0; // ! Move up to avoid gcc complaints. XMP_OptionBits arrayForm = 0; const XMP_Node * currItem = 0; // Make sure the separator is OK. It must be one semicolon surrounded by zero or more spaces. // Any of the recognized semicolons or spaces are allowed. strPos = 0; strLen = strlen ( separator ); bool haveSemicolon = false; while ( strPos < strLen ) { ClassifyCharacter ( separator, strPos, &charKind, &charLen, &currUCP ); strPos += charLen; if ( charKind == UCK_semicolon ) { if ( haveSemicolon ) XMP_Throw ( "Separator can have only one semicolon", kXMPErr_BadParam ); haveSemicolon = true; } else if ( charKind != UCK_space ) { XMP_Throw ( "Separator can have only spaces and one semicolon", kXMPErr_BadParam ); } }; if ( ! haveSemicolon ) XMP_Throw ( "Separator must have one semicolon", kXMPErr_BadParam ); // Make sure the open and close quotes are a legitimate pair. strLen = strlen ( quotes ); ClassifyCharacter ( quotes, 0, &charKind, &charLen, &openQuote ); if ( charKind != UCK_quote ) XMP_Throw ( "Invalid quoting character", kXMPErr_BadParam ); if ( charLen == strLen ) { closeQuote = openQuote; } else { strPos = charLen; ClassifyCharacter ( quotes, strPos, &charKind, &charLen, &closeQuote ); if ( charKind != UCK_quote ) XMP_Throw ( "Invalid quoting character", kXMPErr_BadParam ); if ( (strPos + charLen) != strLen ) XMP_Throw ( "Quoting string too long", kXMPErr_BadParam ); } if ( closeQuote != GetClosingQuote ( openQuote ) ) XMP_Throw ( "Mismatched quote pair", kXMPErr_BadParam ); // Return an empty result if the array does not exist, hurl if it isn't the right form. catedStr->erase(); XMP_ExpandedXPath arrayPath; ExpandXPath ( schemaNS, arrayName, &arrayPath ); arrayNode = FindConstNode ( &xmpObj.tree, arrayPath ); if ( arrayNode == 0 ) return; arrayForm = arrayNode->options & kXMP_PropCompositeMask; if ( (! (arrayForm & kXMP_PropValueIsArray)) || (arrayForm & kXMP_PropArrayIsAlternate) ) { XMP_Throw ( "Named property must be non-alternate array", kXMPErr_BadParam ); } if ( arrayNode->children.empty() ) return; // Build the result, quoting the array items, adding separators. Hurl if any item isn't simple. // Start the result with the first value, then add the rest with a preceeding separator. currItem = arrayNode->children[0]; if ( (currItem->options & kXMP_PropCompositeMask) != 0 ) XMP_Throw ( "Array items must be simple", kXMPErr_BadParam ); *catedStr = currItem->value; ApplyQuotes ( catedStr, openQuote, closeQuote, allowCommas ); for ( size_t itemNum = 1, itemLim = arrayNode->children.size(); itemNum != itemLim; ++itemNum ) { const XMP_Node * item = arrayNode->children[itemNum]; if ( (item->options & kXMP_PropCompositeMask) != 0 ) XMP_Throw ( "Array items must be simple", kXMPErr_BadParam ); XMP_VarString tempStr ( item->value ); ApplyQuotes ( &tempStr, openQuote, closeQuote, allowCommas ); *catedStr += separator; *catedStr += tempStr; } } // CatenateArrayItems // ------------------------------------------------------------------------------------------------- // SeparateArrayItems_v2 // ------------------ #if ENABLE_CPP_DOM_MODEL /* class static */ void XMPUtils::SeparateArrayItems_v2(XMPMeta * xmpObj2, XMP_StringPtr schemaNS, XMP_StringPtr arrayName, XMP_OptionBits options, XMP_StringPtr catedStr) { #if ENABLE_CPP_DOM_MODEL using namespace AdobeXMPCore; using namespace AdobeXMPCommon; XMPMeta2 * xmpObj = NULL; if(sUseNewCoreAPIs) { xmpObj = dynamic_cast (xmpObj2); } #endif XMP_Assert((schemaNS != 0) && (arrayName != 0) && (catedStr != 0)); // ! Enforced by wrapper. // TODO - check if the array item name should be arrayname one or karrayitem // TODO - check the find array in case array doesn't already exist XMP_VarString itemValue; size_t itemStart, itemEnd; size_t nextSize, charSize = 0; UniCharKind nextKind, charKind = UCK_normal; UniCodePoint nextChar, uniChar = 0; XMP_OptionBits arrayOptions = 0; bool preserveCommas = false; if (options & kXMPUtil_AllowCommas) { preserveCommas = true; options ^= kXMPUtil_AllowCommas; } options = VerifySetOptions(options, 0); if (options & ~kXMP_PropArrayFormMask) XMP_Throw("Options can only provide array form", kXMPErr_BadOptions); // Find the array node, make sure it is OK. Move the current children aside, to be readded later if kept. XMP_ExpandedXPath arrayPath; ExpandXPath(schemaNS, arrayName, &arrayPath); spINode arrayNode; if (XMPUtils::FindCnstNode(xmpObj->mDOM, arrayPath, arrayNode, &arrayOptions)){ XMP_OptionBits arrayForm = arrayOptions & kXMP_PropArrayFormMask; if ((arrayForm == 0) || (arrayForm & kXMP_PropArrayIsAlternate)) { XMP_Throw("Named property must be non-alternate array", kXMPErr_BadXPath); } if ((options != 0) && (options != arrayForm)) XMP_Throw("Mismatch of specified and existing array form", kXMPErr_BadXPath); // *** Right error? } else { // The array does not exist, try to create it. XPathStepInfo lastPathSegment(arrayPath.back()); XMP_VarString arrayStep = lastPathSegment.step; //arrayPath.pop_back(); spINode destNode; XMP_Index insertIndex = 0; if (!XMPUtils::FindNode(xmpObj->mDOM, arrayPath, kXMP_CreateNodes, options, destNode, &insertIndex, true)) { XMP_Throw("Failure creating array node", kXMPErr_BadXPath); } std::string arrayNameSpace, arrayName; auto defaultMap = INameSpacePrefixMap::GetDefaultNameSpacePrefixMap(); arrayOptions = options; XMPUtils::GetNameSpaceAndNameFromStepValue(lastPathSegment.step, defaultMap, arrayNameSpace, arrayName); // Need to check Alternate first if (arrayOptions & kXMP_PropArrayIsAlternate) { arrayNode = IArrayNode::CreateAlternativeArrayNode( arrayNameSpace.c_str(), arrayNameSpace.size(), arrayName.c_str(), arrayName.size()); } else if (arrayOptions & kXMP_PropArrayIsOrdered) { arrayNode = IArrayNode::CreateOrderedArrayNode( arrayNameSpace.c_str(), arrayNameSpace.size(), arrayName.c_str(), arrayName.size() ); } else if (arrayOptions & kXMP_PropArrayIsUnordered) { arrayNode = IArrayNode::CreateUnorderedArrayNode( arrayNameSpace.c_str(), arrayNameSpace.size(), arrayName.c_str(), arrayName.size() ); } else { XMP_Throw("Failure creating array node", kXMPErr_BadXPath); } if (destNode->GetNodeType() == INode::kNTStructure) { destNode->ConvertToStructureNode()->InsertNode(arrayNode); } else if (destNode->GetNodeType() == INode::kNTArray) { destNode->ConvertToArrayNode()->AppendNode(arrayNode); } else { XMP_Throw("Failure creating array node", kXMPErr_BadXPath); } if (!arrayNode) XMP_Throw("Failed to create named array", kXMPErr_BadXPath); } size_t oldChildCount = XMPUtils::GetNodeChildCount(arrayNode); std::vector oldArrayNodes; std::vector qualifiers; // used to handle duplicates std::vector oldArrayNodeSeen(oldChildCount, false); spcINodeIterator oldArrayChildIter = XMPUtils::GetNodeChildIterator(arrayNode); for (; oldArrayChildIter; oldArrayChildIter = oldArrayChildIter->Next()) { oldArrayNodes.push_back( oldArrayChildIter->GetNode()->ConvertToSimpleNode()->GetValue()->c_str()); if (oldArrayChildIter->GetNode()->HasQualifiers()) { qualifiers.push_back(oldArrayChildIter->GetNode()->Clone()); /*for ( auto it = oldArrayChildIter->GetNode()->QualifiersIterator(); it; it = it->Next() ) { qualifiers.push_back( it->GetNode()->Clone() ); }*/ } else { qualifiers.push_back(spINode()); } } arrayNode->Clear(true, false); // used to avoid typecasting repeatedly! spIArrayNode tempArrayNode = arrayNode->ConvertToArrayNode(); size_t endPos = strlen(catedStr); itemEnd = 0; while (itemEnd < endPos) { for (itemStart = itemEnd; itemStart < endPos; itemStart += charSize) { ClassifyCharacter(catedStr, itemStart, &charKind, &charSize, &uniChar); if ((charKind == UCK_normal) || (charKind == UCK_quote)) break; } if (itemStart >= endPos) break; if (charKind != UCK_quote) { for (itemEnd = itemStart; itemEnd < endPos; itemEnd += charSize) { ClassifyCharacter(catedStr, itemEnd, &charKind, &charSize, &uniChar); if ((charKind == UCK_normal) || (charKind == UCK_quote)) continue; if ((charKind == UCK_comma) && preserveCommas) continue; if (charKind != UCK_space) break; if ((itemEnd + charSize) >= endPos) break; // Anything left? ClassifyCharacter(catedStr, (itemEnd + charSize), &nextKind, &nextSize, &nextChar); if ((nextKind == UCK_normal) || (nextKind == UCK_quote)) continue; if ((nextKind == UCK_comma) && preserveCommas) continue; break; // Have multiple spaces, or a space followed by a separator. } itemValue.assign(catedStr, itemStart, (itemEnd - itemStart)); } else { // Accumulate quoted values into a local string, undoubling internal quotes that // match the surrounding quotes. Do not undouble "unmatching" quotes. UniCodePoint openQuote = uniChar; UniCodePoint closeQuote = GetClosingQuote(openQuote); itemStart += charSize; // Skip the opening quote; itemValue.erase(); for (itemEnd = itemStart; itemEnd < endPos; itemEnd += charSize) { ClassifyCharacter(catedStr, itemEnd, &charKind, &charSize, &uniChar); if ((charKind != UCK_quote) || (!IsSurroundingQuote(uniChar, openQuote, closeQuote))) { // This is not a matching quote, just append it to the item value. itemValue.append(catedStr, itemEnd, charSize); } else { // This is a "matching" quote. Is it doubled, or the final closing quote? Tolerate // various edge cases like undoubled opening (non-closing) quotes, or end of input. if ((itemEnd + charSize) < endPos) { ClassifyCharacter(catedStr, itemEnd + charSize, &nextKind, &nextSize, &nextChar); } else { nextKind = UCK_semicolon; nextSize = 0; nextChar = 0x3B; } if (uniChar == nextChar) { // This is doubled, copy it and skip the double. itemValue.append(catedStr, itemEnd, charSize); itemEnd += nextSize; // Loop will add in charSize. } else if (!IsClosingingQuote(uniChar, openQuote, closeQuote)) { // This is an undoubled, non-closing quote, copy it. itemValue.append(catedStr, itemEnd, charSize); } else { // This is an undoubled closing quote, skip it and exit the loop. itemEnd += charSize; break; } } } // Loop to accumulate the quoted value. } // Add the separated item to the array. Keep a matching old value in case it had separators. size_t oldChild; spISimpleNode newItem; for (oldChild = 1; oldChild <= oldChildCount; ++oldChild) { if (!oldArrayNodeSeen[oldChild - 1] && itemValue == oldArrayNodes[oldChild - 1]) break; } if (oldChild == oldChildCount + 1) { // newItem = new XMP_Node ( arrayNode, kXMP_ArrayItemName, itemValue.c_str(), 0 ); newItem = ISimpleNode::CreateSimpleNode(arrayNode->GetNameSpace()->c_str(), arrayNode->GetNameSpace()->size(), kXMP_ArrayItemName, AdobeXMPCommon::npos, itemValue.c_str()); } else { newItem = ISimpleNode::CreateSimpleNode(arrayNode->GetNameSpace()->c_str(), arrayNode->GetNameSpace()->size(), kXMP_ArrayItemName, AdobeXMPCommon::npos, oldArrayNodes[oldChild - 1].c_str()); if (qualifiers[ oldChild - 1] && qualifiers[ oldChild - 1] ->HasQualifiers() ) { for (auto it = qualifiers[oldChild - 1] ->QualifiersIterator(); it; it = it->Next()) { newItem->InsertQualifier(it->GetNode()->Clone()); } } oldArrayNodeSeen[oldChild - 1] = true; // ! Don't match again, let duplicates be seen. } tempArrayNode->AppendNode(newItem); } // Loop through all of the returned items. // Delete any of the old children that were not kept. } // SeparateArrayItems_v2 #endif // ------------------------------------------------------------------------------------------------- // SeparateArrayItems // ------------------ /* class static */ void XMPUtils::SeparateArrayItems ( XMPMeta * xmpObj, XMP_StringPtr schemaNS, XMP_StringPtr arrayName, XMP_OptionBits options, XMP_StringPtr catedStr ) { #if ENABLE_CPP_DOM_MODEL if (sUseNewCoreAPIs) { SeparateArrayItems_v2(xmpObj, schemaNS, arrayName, options, catedStr); return; } #endif XMP_Assert ( (schemaNS != 0) && (arrayName != 0) && (catedStr != 0) ); // ! Enforced by wrapper. XMP_VarString itemValue; size_t itemStart, itemEnd; size_t nextSize, charSize = 0; // Avoid VS uninit var warnings. UniCharKind nextKind, charKind = UCK_normal; UniCodePoint nextChar, uniChar = 0; // Extract "special" option bits, verify and normalize the others. bool preserveCommas = false; if ( options & kXMPUtil_AllowCommas ) { preserveCommas = true; options ^= kXMPUtil_AllowCommas; } options = VerifySetOptions ( options, 0 ); // Keep a zero value, has special meaning below. if ( options & ~kXMP_PropArrayFormMask ) XMP_Throw ( "Options can only provide array form", kXMPErr_BadOptions ); // Find the array node, make sure it is OK. Move the current children aside, to be readded later if kept. XMP_ExpandedXPath arrayPath; ExpandXPath ( schemaNS, arrayName, &arrayPath ); XMP_Node * arrayNode = ::FindNode( &xmpObj->tree, arrayPath, kXMP_ExistingOnly ); if ( arrayNode != 0 ) { // The array exists, make sure the form is compatible. Zero arrayForm means take what exists. XMP_OptionBits arrayForm = arrayNode->options & kXMP_PropArrayFormMask; if ( (arrayForm == 0) || (arrayForm & kXMP_PropArrayIsAlternate) ) { XMP_Throw ( "Named property must be non-alternate array", kXMPErr_BadXPath ); } if ( (options != 0) && (options != arrayForm) ) XMP_Throw ( "Mismatch of specified and existing array form", kXMPErr_BadXPath ); // *** Right error? } else { // The array does not exist, try to create it. arrayNode = ::FindNode( &xmpObj->tree, arrayPath, kXMP_CreateNodes, (options | kXMP_PropValueIsArray) ); if ( arrayNode == 0 ) XMP_Throw ( "Failed to create named array", kXMPErr_BadXPath ); } XMP_NodeOffspring oldChildren ( arrayNode->children ); size_t oldChildCount = oldChildren.size(); arrayNode->children.clear(); // Extract the item values one at a time, until the whole input string is done. Be very careful // in the extraction about the string positions. They are essentially byte pointers, while the // contents are UTF-8. Adding or subtracting 1 does not necessarily move 1 Unicode character! size_t endPos = strlen ( catedStr ); itemEnd = 0; while ( itemEnd < endPos ) { // Skip any leading spaces and separation characters. Always skip commas here. They can be // kept when within a value, but not when alone between values. for ( itemStart = itemEnd; itemStart < endPos; itemStart += charSize ) { ClassifyCharacter ( catedStr, itemStart, &charKind, &charSize, &uniChar ); if ( (charKind == UCK_normal) || (charKind == UCK_quote) ) break; } if ( itemStart >= endPos ) break; if ( charKind != UCK_quote ) { // This is not a quoted value. Scan for the end, create an array item from the substring. for ( itemEnd = itemStart; itemEnd < endPos; itemEnd += charSize ) { ClassifyCharacter ( catedStr, itemEnd, &charKind, &charSize, &uniChar ); if ( (charKind == UCK_normal) || (charKind == UCK_quote) ) continue; if ( (charKind == UCK_comma) && preserveCommas ) continue; if ( charKind != UCK_space ) break; if ( (itemEnd + charSize) >= endPos ) break; // Anything left? ClassifyCharacter ( catedStr, (itemEnd+charSize), &nextKind, &nextSize, &nextChar ); if ( (nextKind == UCK_normal) || (nextKind == UCK_quote) ) continue; if ( (nextKind == UCK_comma) && preserveCommas ) continue; break; // Have multiple spaces, or a space followed by a separator. } itemValue.assign ( catedStr, itemStart, (itemEnd - itemStart) ); } else { // Accumulate quoted values into a local string, undoubling internal quotes that // match the surrounding quotes. Do not undouble "unmatching" quotes. UniCodePoint openQuote = uniChar; UniCodePoint closeQuote = GetClosingQuote ( openQuote ); itemStart += charSize; // Skip the opening quote; itemValue.erase(); for ( itemEnd = itemStart; itemEnd < endPos; itemEnd += charSize ) { ClassifyCharacter ( catedStr, itemEnd, &charKind, &charSize, &uniChar ); if ( (charKind != UCK_quote) || (! IsSurroundingQuote ( uniChar, openQuote, closeQuote)) ) { // This is not a matching quote, just append it to the item value. itemValue.append ( catedStr, itemEnd, charSize ); } else { // This is a "matching" quote. Is it doubled, or the final closing quote? Tolerate // various edge cases like undoubled opening (non-closing) quotes, or end of input. if ( (itemEnd + charSize) < endPos ) { ClassifyCharacter ( catedStr, itemEnd+charSize, &nextKind, &nextSize, &nextChar ); } else { nextKind = UCK_semicolon; nextSize = 0; nextChar = 0x3B; } if ( uniChar == nextChar ) { // This is doubled, copy it and skip the double. itemValue.append ( catedStr, itemEnd, charSize ); itemEnd += nextSize; // Loop will add in charSize. } else if ( ! IsClosingingQuote ( uniChar, openQuote, closeQuote ) ) { // This is an undoubled, non-closing quote, copy it. itemValue.append ( catedStr, itemEnd, charSize ); } else { // This is an undoubled closing quote, skip it and exit the loop. itemEnd += charSize; break; } } } // Loop to accumulate the quoted value. } // Add the separated item to the array. Keep a matching old value in case it had separators. size_t oldChild; for ( oldChild = 0; oldChild < oldChildCount; ++oldChild ) { if ( (oldChildren[oldChild] != 0) && (itemValue == oldChildren[oldChild]->value) ) break; } XMP_Node * newItem = 0; if ( oldChild == oldChildCount ) { newItem = new XMP_Node ( arrayNode, kXMP_ArrayItemName, itemValue.c_str(), 0 ); } else { newItem = oldChildren[oldChild]; oldChildren[oldChild] = 0; // ! Don't match again, let duplicates be seen. } arrayNode->children.push_back ( newItem ); } // Loop through all of the returned items. // Delete any of the old children that were not kept. for ( size_t i = 0; i < oldChildCount; ++i ) { if ( oldChildren[i] != 0 ) delete oldChildren[i]; } } // SeparateArrayItems // ------------------------------------------------------------------------------------------------- // ApplyTemplate // ------------- /* class static */ void XMPUtils::ApplyTemplate ( XMPMeta * workingXMP, const XMPMeta & templateXMP, XMP_OptionBits actions ) { #if ENABLE_CPP_DOM_MODEL if (sUseNewCoreAPIs) { ApplyTemplate_v2(workingXMP, templateXMP, actions); return; } #endif bool doClear = XMP_OptionIsSet ( actions, kXMPTemplate_ClearUnnamedProperties ); bool doAdd = XMP_OptionIsSet ( actions, kXMPTemplate_AddNewProperties ); bool doReplace = XMP_OptionIsSet ( actions, kXMPTemplate_ReplaceExistingProperties ); bool deleteEmpty = XMP_OptionIsSet ( actions, kXMPTemplate_ReplaceWithDeleteEmpty ); doReplace |= deleteEmpty; // Delete-empty implies Replace. deleteEmpty &= (! doClear); // Clear implies not delete-empty, but keep the implicit Replace. bool doAll = XMP_OptionIsSet ( actions, kXMPTemplate_IncludeInternalProperties ); // ! In several places we do loops backwards so that deletions do not perturb the remaining indices. // ! These loops use ordinals (size .. 1), we must use a zero based index inside the loop. if ( doClear ) { // Visit the top level working properties, delete if not in the template. for ( size_t schemaOrdinal = workingXMP->tree.children.size(); schemaOrdinal > 0; --schemaOrdinal ) { size_t schemaNum = schemaOrdinal-1; // ! Convert ordinal to index! XMP_Node * workingSchema = workingXMP->tree.children[schemaNum]; const XMP_Node * templateSchema = FindConstSchema ( &templateXMP.tree, workingSchema->name.c_str() ); if ( templateSchema == 0 ) { // The schema is not in the template, delete all properties or just all external ones. if ( doAll ) { workingSchema->RemoveChildren(); // Remove the properties here, delete the schema below. } else { for ( size_t propOrdinal = workingSchema->children.size(); propOrdinal > 0; --propOrdinal ) { size_t propNum = propOrdinal-1; // ! Convert ordinal to index! XMP_Node * workingProp = workingSchema->children[propNum]; if ( IsExternalProperty ( workingSchema->name, workingProp->name ) ) { delete ( workingProp ); workingSchema->children.erase ( workingSchema->children.begin() + propNum ); } } } } else { // Check each of the working XMP's properties to see if it is in the template. for ( size_t propOrdinal = workingSchema->children.size(); propOrdinal > 0; --propOrdinal ) { size_t propNum = propOrdinal-1; // ! Convert ordinal to index! XMP_Node * workingProp = workingSchema->children[propNum]; if ( (doAll || IsExternalProperty ( workingSchema->name, workingProp->name )) && (FindConstChild ( templateSchema, workingProp->name.c_str() ) == 0) ) { delete ( workingProp ); workingSchema->children.erase ( workingSchema->children.begin() + propNum ); } } } if ( workingSchema->children.empty() ) { delete ( workingSchema ); workingXMP->tree.children.erase ( workingXMP->tree.children.begin() + schemaNum ); } } } if ( doAdd | doReplace ) { for ( size_t schemaNum = 0, schemaLim = templateXMP.tree.children.size(); schemaNum < schemaLim; ++schemaNum ) { const XMP_Node * templateSchema = templateXMP.tree.children[schemaNum]; // Make sure we have an output schema node, then process the top level template properties. XMP_NodePtrPos workingSchemaPos; XMP_Node * workingSchema = FindSchemaNode ( &workingXMP->tree, templateSchema->name.c_str(), kXMP_ExistingOnly, &workingSchemaPos ); if ( workingSchema == 0 ) { workingSchema = new XMP_Node ( &workingXMP->tree, templateSchema->name, templateSchema->value, kXMP_SchemaNode ); workingXMP->tree.children.push_back ( workingSchema ); workingSchemaPos = workingXMP->tree.children.end() - 1; } for ( size_t propNum = 0, propLim = templateSchema->children.size(); propNum < propLim; ++propNum ) { const XMP_Node * templateProp = templateSchema->children[propNum]; if ( doAll || IsExternalProperty ( templateSchema->name, templateProp->name ) ) { AppendSubtree ( templateProp, workingSchema, doAdd, doReplace, deleteEmpty ); } } if ( workingSchema->children.empty() ) { delete ( workingSchema ); workingXMP->tree.children.erase ( workingSchemaPos ); } } } } // ApplyTemplate // ------------------------------------------------------------------------------------------------- // RemoveProperties // ---------------- /* class static */ void XMPUtils::RemoveProperties ( XMPMeta * xmpObj, XMP_StringPtr schemaNS, XMP_StringPtr propName, XMP_OptionBits options ) { #if ENABLE_CPP_DOM_MODEL if (sUseNewCoreAPIs) { RemoveProperties_v2(xmpObj, schemaNS, propName, options); return; } #endif XMP_Assert ( (schemaNS != 0) && (propName != 0) ); // ! Enforced by wrapper. const bool doAll = XMP_TestOption (options, kXMPUtil_DoAllProperties ); const bool includeAliases = XMP_TestOption ( options, kXMPUtil_IncludeAliases ); if ( *propName != 0 ) { // Remove just the one indicated property. This might be an alias, the named schema might // not actually exist. So don't lookup the schema node. if ( *schemaNS == 0 ) XMP_Throw ( "Property name requires schema namespace", kXMPErr_BadParam ); XMP_ExpandedXPath expPath; ExpandXPath ( schemaNS, propName, &expPath ); XMP_NodePtrPos propPos; XMP_Node * propNode = ::FindNode( &(xmpObj->tree), expPath, kXMP_ExistingOnly, kXMP_NoOptions, &propPos ); if ( propNode != 0 ) { if ( doAll || IsExternalProperty ( expPath[kSchemaStep].step, expPath[kRootPropStep].step ) ) { XMP_Node * parent = propNode->parent; // *** Should have XMP_Node::RemoveChild(pos). delete propNode; // ! Both delete the node and erase the pointer from the parent. parent->children.erase ( propPos ); DeleteEmptySchema ( parent ); } } } else if ( *schemaNS != 0 ) { // Remove all properties from the named schema. Optionally include aliases, in which case // there might not be an actual schema node. XMP_NodePtrPos schemaPos; XMP_Node * schemaNode = FindSchemaNode ( &xmpObj->tree, schemaNS, kXMP_ExistingOnly, &schemaPos ); if ( schemaNode != 0 ) RemoveSchemaChildren ( schemaPos, doAll ); if ( includeAliases ) { // We're removing the aliases also. Look them up by their namespace prefix. Yes, the // alias map is sorted so we could process just that portion. But that takes more code // and the extra speed isn't worth it. (Plus this way we avoid a dependence on the map // implementation.) Lookup the XMP node from the alias, to make sure the actual exists. XMP_StringPtr nsPrefix; XMP_StringLen nsLen; (void) XMPMeta::GetNamespacePrefix ( schemaNS, &nsPrefix, &nsLen ); XMP_AliasMapPos currAlias = sRegisteredAliasMap->begin(); XMP_AliasMapPos endAlias = sRegisteredAliasMap->end(); for ( ; currAlias != endAlias; ++currAlias ) { if ( strncmp ( currAlias->first.c_str(), nsPrefix, nsLen ) == 0 ) { XMP_NodePtrPos actualPos; XMP_Node * actualProp = ::FindNode( &xmpObj->tree, currAlias->second, kXMP_ExistingOnly, kXMP_NoOptions, &actualPos ); if ( actualProp != 0 ) { XMP_Node * rootProp = actualProp; while ( ! XMP_NodeIsSchema ( rootProp->parent->options ) ) rootProp = rootProp->parent; if ( doAll || IsExternalProperty ( rootProp->parent->name, rootProp->name ) ) { XMP_Node * parent = actualProp->parent; delete actualProp; // ! Both delete the node and erase the pointer from the parent. parent->children.erase ( actualPos ); DeleteEmptySchema ( parent ); } } } } } } else { // Remove all appropriate properties from all schema. In this case we don't have to be // concerned with aliases, they are handled implicitly from the actual properties. // ! Iterate backwards to reduce shuffling if schema are erased and to simplify the logic // ! for denoting the current schema. (Erasing schema n makes the old n+1 now be n.) size_t schemaCount = xmpObj->tree.children.size(); XMP_NodePtrPos beginPos = xmpObj->tree.children.begin(); for ( size_t schemaNum = schemaCount-1, schemaLim = (size_t)(-1); schemaNum != schemaLim; --schemaNum ) { XMP_NodePtrPos currSchema = beginPos + schemaNum; RemoveSchemaChildren ( currSchema, doAll ); } } } // RemoveProperties // ------------------------------------------------------------------------------------------------- // DuplicateSubtree // ---------------- /* class static */ void XMPUtils::DuplicateSubtree ( const XMPMeta & source, XMPMeta * dest, XMP_StringPtr sourceNS, XMP_StringPtr sourceRoot, XMP_StringPtr destNS, XMP_StringPtr destRoot, XMP_OptionBits options ) { #if ENABLE_CPP_DOM_MODEL if(sUseNewCoreAPIs) { (void)dynamic_cast(source); return XMPUtils::DuplicateSubtree_v2(source, dest, sourceNS, sourceRoot, destNS, destRoot, options); } #endif IgnoreParam(options); bool fullSourceTree = false; bool fullDestTree = false; XMP_ExpandedXPath sourcePath, destPath; const XMP_Node * sourceNode = 0; XMP_Node * destNode = 0; XMP_Assert ( (sourceNS != 0) && (*sourceNS != 0) ); XMP_Assert ( (sourceRoot != 0) && (*sourceRoot != 0) ); XMP_Assert ( (dest != 0) && (destNS != 0) && (destRoot != 0) ); if ( *destNS == 0 ) destNS = sourceNS; if ( *destRoot == 0 ) destRoot = sourceRoot; if ( XMP_LitMatch ( sourceNS, "*" ) ) fullSourceTree = true; if ( XMP_LitMatch ( destNS, "*" ) ) fullDestTree = true; if ( (&source == dest) && (fullSourceTree | fullDestTree) ) { XMP_Throw ( "Can't duplicate tree onto itself", kXMPErr_BadParam ); } if ( fullSourceTree & fullDestTree ) XMP_Throw ( "Use Clone for full tree to full tree", kXMPErr_BadParam ); if ( fullSourceTree ) { // The destination must be an existing empty struct, copy all of the source top level as fields. ExpandXPath ( destNS, destRoot, &destPath ); destNode = ::FindNode( &dest->tree, destPath, kXMP_ExistingOnly ); if ( (destNode == 0) || (! XMP_PropIsStruct ( destNode->options )) ) { XMP_Throw ( "Destination must be an existing struct", kXMPErr_BadXPath ); } if ( ! destNode->children.empty() ) { if ( options & kXMP_DeleteExisting ) { destNode->RemoveChildren(); } else { XMP_Throw ( "Destination must be an empty struct", kXMPErr_BadXPath ); } } for ( size_t schemaNum = 0, schemaLim = source.tree.children.size(); schemaNum < schemaLim; ++schemaNum ) { const XMP_Node * currSchema = source.tree.children[schemaNum]; for ( size_t propNum = 0, propLim = currSchema->children.size(); propNum < propLim; ++propNum ) { sourceNode = currSchema->children[propNum]; XMP_Node * copyNode = new XMP_Node ( destNode, sourceNode->name, sourceNode->value, sourceNode->options ); destNode->children.push_back ( copyNode ); CloneOffspring ( sourceNode, copyNode ); } } } else if ( fullDestTree ) { // The source node must be an existing struct, copy all of the fields to the dest top level. XMP_ExpandedXPath srcPath; ExpandXPath ( sourceNS, sourceRoot, &srcPath ); sourceNode = FindConstNode ( &source.tree, srcPath ); if ( (sourceNode == 0) || (! XMP_PropIsStruct ( sourceNode->options )) ) { XMP_Throw ( "Source must be an existing struct", kXMPErr_BadXPath ); } destNode = &dest->tree; if ( ! destNode->children.empty() ) { if ( options & kXMP_DeleteExisting ) { destNode->RemoveChildren(); } else { XMP_Throw ( "Destination tree must be empty", kXMPErr_BadXPath ); } } std::string nsPrefix; XMP_StringPtr nsURI; XMP_StringLen nsLen; for ( size_t fieldNum = 0, fieldLim = sourceNode->children.size(); fieldNum < fieldLim; ++fieldNum ) { const XMP_Node * currField = sourceNode->children[fieldNum]; size_t colonPos = currField->name.find ( ':' ); if ( colonPos == std::string::npos ) continue; nsPrefix.assign ( currField->name.c_str(), colonPos ); bool nsOK = XMPMeta::GetNamespaceURI ( nsPrefix.c_str(), &nsURI, &nsLen ); if ( ! nsOK ) XMP_Throw ( "Source field namespace is not global", kXMPErr_BadSchema ); XMP_Node * destSchema = FindSchemaNode ( &dest->tree, nsURI, kXMP_CreateNodes ); if ( destSchema == 0 ) XMP_Throw ( "Failed to find destination schema", kXMPErr_BadSchema ); XMP_Node * copyNode = new XMP_Node ( destSchema, currField->name, currField->value, currField->options ); destSchema->children.push_back ( copyNode ); CloneOffspring ( currField, copyNode ); } } else { // Find the root nodes for the source and destination subtrees. ExpandXPath ( sourceNS, sourceRoot, &sourcePath ); ExpandXPath ( destNS, destRoot, &destPath ); sourceNode = FindConstNode ( &source.tree, sourcePath ); if ( sourceNode == 0 ) XMP_Throw ( "Can't find source subtree", kXMPErr_BadXPath ); destNode = ::FindNode ( &dest->tree, destPath, kXMP_ExistingOnly ); // Dest must not yet exist. if ( destNode != 0 ) XMP_Throw ( "Destination subtree must not exist", kXMPErr_BadXPath ); destNode = ::FindNode ( &dest->tree, destPath, kXMP_CreateNodes ); // Now create the dest. if ( destNode == 0 ) XMP_Throw ( "Can't create destination root node", kXMPErr_BadXPath ); // Make sure the destination is not within the source! The source can't be inside the destination // because the source already existed and the destination was just created. if ( &source == dest ) { for ( XMP_Node * testNode = destNode; testNode != 0; testNode = testNode->parent ) { if ( testNode == sourceNode ) { // *** delete the just-created dest root node XMP_Throw ( "Destination subtree is within the source subtree", kXMPErr_BadXPath ); } } } // *** Could use a CloneTree util here and maybe elsewhere. destNode->value = sourceNode->value; // *** Should use SetNode. destNode->options = sourceNode->options; CloneOffspring ( sourceNode, destNode ); } } // DuplicateSubtree // ================================================================================================= diff --git a/core/libs/dngwriter/extra/xmp_sdk/XMPCore/source/XMPUtils2.cpp b/core/libs/dngwriter/extra/xmp_sdk/XMPCore/source/XMPUtils2.cpp index 9d88cd3fdf..885a1e3367 100644 --- a/core/libs/dngwriter/extra/xmp_sdk/XMPCore/source/XMPUtils2.cpp +++ b/core/libs/dngwriter/extra/xmp_sdk/XMPCore/source/XMPUtils2.cpp @@ -1,921 +1,921 @@ // ================================================================================================= // ADOBE SYSTEMS INCORPORATED // Copyright 2014 Adobe Systems Incorporated // All Rights Reserved // // NOTICE: Adobe permits you to use, modify, and distribute this file in accordance with the terms // of the Adobe license agreement accompanying it. // ================================================================================================= #include "public/include/XMP_Environment.h" // ! This must be the first include! #include "XMPCore/source/XMPCore_Impl.hpp" #include "XMPCore/XMPCoreDefines.h" #if ENABLE_CPP_DOM_MODEL #include "XMPCore/source/XMPUtils.hpp" #include "source/UnicodeInlines.incl_cpp" #include "source/UnicodeConversions.hpp" #include "source/ExpatAdapter.hpp" -#include "third-party/zuid/interfaces/MD5.h" +#include "XMP_MD5.h" #include "XMPCore/Interfaces/IMetadata_I.h" #include "XMPCore/Interfaces/IArrayNode_I.h" #include "XMPCore/Interfaces/ISimpleNode_I.h" #include "XMPCore/Interfaces/INodeIterator.h" #include "XMPCore/Interfaces/IPathSegment_I.h" #include "XMPCore/Interfaces/IPath_I.h" #include "XMPCore/Interfaces/INameSpacePrefixMap_I.h" #include "XMPCore/Interfaces/IDOMImplementationRegistry_I.h" #include "XMPCommon/Interfaces/IUTF8String_I.h" const XMP_VarString xmlNameSpace = "http://www.w3.org/XML/1998/namespace"; extern bool IsInternalProperty(const XMP_VarString & schema, const XMP_VarString & prop); extern const char * sListProps[]; extern const char * sDateProps[]; using namespace AdobeXMPCommon; using namespace AdobeXMPCore; // ================================================================================================= // CloneSubtree // ============ void CloneIXMPSubtree(const spcINode & origRoot, const spINode & cloneParent, bool skipEmpty /* = false */) { spINode clonedRoot = origRoot->Clone(skipEmpty, true); if (!clonedRoot) return; if (cloneParent->GetNodeType() == INode::kNTArray) { cloneParent->ConvertToArrayNode()->AppendNode( clonedRoot ); } else if (cloneParent->GetNodeType() == INode::kNTStructure) { cloneParent->ConvertToStructureNode()->InsertNode( cloneParent ); } } // CloneSubtree // ================================================================================================= // LookupLangItem // ============== // // ! Assumes that the language value is already normalized. XMP_Index LookupIXMPLangItem(const spcIArrayNode & arrayNode, XMP_VarString & lang) { XMP_OptionBits arrayOptions = XMPUtils::GetIXMPOptions(arrayNode); if (!(arrayOptions & kXMP_PropValueIsArray)) { // *** Check for alt-text? XMP_Throw("Language item must be used on array", kXMPErr_BadXPath); } XMP_Index index = 1; XMP_Index itemLim = arrayNode->ChildCount(); for (; index <= itemLim; ++index) { spcINode currItem = arrayNode->GetNodeAtIndex(index); if (!currItem->HasQualifiers()) continue; spcINode langQual = currItem->GetQualifier(xmlNameSpace.c_str(), xmlNameSpace.size(), "lang", AdobeXMPCommon::npos ); if (!langQual) continue; if (langQual->GetNodeType() != INode::kNTSimple) continue; XMP_VarString langQualValue = langQual->ConvertToSimpleNode()->GetValue()->c_str(); if (langQualValue == lang) break; } if (index == itemLim + 1) index = -1; return index; } // LookupLangItem // ================================================================================================= // CompareSubtrees // =============== // // Compare 2 subtrees for semantic equality. The comparison includes value, qualifiers, and form. // Schemas, top level properties, struct fields, and qualifiers are allowed to have differing order, // the appropriate right node is found from the left node's name. Alt-text arrays are allowed to be // in differing language order, other arrays are compared in order. // *** Might someday consider sorting unordered arrays. // *** Should expose this through XMPUtils. bool CompareSubtrees(spcINode leftNode, spcINode rightNode) { // Don't compare the names here, we want to allow the outermost roots to have different names. XMP_OptionBits leftNodeOptions = XMPUtils::GetIXMPOptions(leftNode), rightNodeOptions = XMPUtils::GetIXMPOptions(rightNode); if (leftNode->GetNodeType() != rightNode->GetNodeType()) return false; if (leftNode->GetNodeType() == INode::kNTSimple) { if ( strcmp( leftNode->ConvertToSimpleNode()->GetValue()->c_str(), rightNode->ConvertToSimpleNode()->GetValue()->c_str() ) ) { return false; } } if (XMPUtils::GetNodeChildCount(leftNode) != XMPUtils::GetNodeChildCount(rightNode)) return false; if (leftNode->HasQualifiers() != rightNode->HasQualifiers() ) return false; if (leftNode->HasQualifiers()) { for (auto leftQualIter = leftNode->QualifiersIterator(); leftQualIter; leftQualIter = leftQualIter->Next()) { spcINode leftQualNode = leftQualIter->GetNode(); spcINode righQualNode = rightNode->GetINode_I()->GetQualifier( leftQualNode->GetNameSpace(), leftQualNode->GetName() ); if (!righQualNode || !CompareSubtrees(leftQualNode, righQualNode)){ return false; } } } if (leftNode->GetNodeType() == INode::kNTStructure ) { for ( auto leftChildIter = XMPUtils::GetNodeChildIterator(leftNode); leftChildIter; leftChildIter = leftChildIter->Next()) { spcINode leftChildNode = leftChildIter->GetNode(); spcINode rightChildNode = XMPUtils::FindChildNode( AdobeXMPCore_Int::const_pointer_cast(rightNode), leftChildNode->GetName()->c_str(), leftChildNode->GetNameSpace()->c_str(), false, 0); if (!rightChildNode || !CompareSubtrees(leftChildNode, rightChildNode)) { return false; } } } else if (leftNodeOptions & kXMP_PropArrayIsAltText) { // The parent node is an alt-text array. auto leftNodeAsArray = leftNode->ConvertToArrayNode(); auto rightNodeAsArray = rightNode->ConvertToArrayNode(); size_t leftNodeChildCount = XMPUtils::GetNodeChildCount(leftNode); for (size_t idx = 1; idx <= leftNodeChildCount; ++idx) { spcINode leftChild = leftNodeAsArray->GetNodeAtIndex(idx); spcINode leftChildFirstQualifier = leftChild->GetQualifier(xmlNameSpace.c_str(), xmlNameSpace.size(), "lang", AdobeXMPCommon::npos ); if (leftChildFirstQualifier) { XMP_VarString leftChildFirstQualifierValue = leftChildFirstQualifier->ConvertToSimpleNode()->GetValue()->c_str(); size_t rightIdx = LookupIXMPLangItem( rightNodeAsArray, leftChildFirstQualifierValue ); if (rightIdx == -1) { return false; } spcINode rightChild = rightNodeAsArray->GetNodeAtIndex( rightIdx ); if (!CompareSubtrees(leftChild, rightChild)) { return false; } } } } else { // The parent must be simple or some other (not alt-text) kind of array. XMP_Assert((!(leftNodeOptions & kXMP_PropCompositeMask)) || (leftNodeOptions & kXMP_PropValueIsArray)); auto leftNodeAsArray = leftNode->ConvertToArrayNode(); auto rightNodeAsArray = rightNode->ConvertToArrayNode(); size_t leftNodeChildCount = XMPUtils::GetNodeChildCount(leftNode); for (size_t idx = 1; idx <= leftNodeChildCount; ++idx) { spcINode leftChild = leftNodeAsArray->GetNodeAtIndex(idx); spcINode rightChild = rightNodeAsArray->GetNodeAtIndex(idx); if (!CompareSubtrees(leftChild, rightChild)) { return false; } } } return true; }// CompareSubtrees // ------------------------------------------------------------------------------------------------- // MergeArrayItems // --------------- static void MergeArrayItems(spINode newArray, spINode mergedArray) { XMP_Assert(newArray->GetNodeType() == INode::kNTArray); XMP_Assert(mergedArray->GetNodeType() == INode::kNTArray); auto newArrayAsArrayNode = newArray->ConvertToArrayNode(); auto mergedArrayAsArrayNode = mergedArray->ConvertToArrayNode(); for (size_t newNum = 1, newLim = XMPUtils::GetNodeChildCount(newArray); newNum <= newLim; ++newNum) { spcINode newItem = newArrayAsArrayNode->GetNodeAtIndex(newNum); size_t mergedNum, mergedLim; for (mergedNum = 1, mergedLim = XMPUtils::GetNodeChildCount(mergedArray) + 1; mergedNum < mergedLim; ++mergedNum) { spcINode mergedItem = mergedArrayAsArrayNode->GetNodeAtIndex(mergedNum); if (CompareSubtrees(newItem, mergedItem)) break; } if (mergedNum == mergedLim) CloneIXMPSubtree(newItem, mergedArray, false); } } // MergeArrayItems static bool ItemValuesMatch(spcINode leftNode, spcINode rightNode) { if (!leftNode && !rightNode) return true; if (!leftNode || !rightNode) return false; const XMP_OptionBits leftNodeOptions = XMPUtils::GetIXMPOptions(leftNode); const XMP_OptionBits rightNodeOptions = XMPUtils::GetIXMPOptions(rightNode); const XMP_OptionBits leftForm = leftNodeOptions & kXMP_PropCompositeMask; const XMP_OptionBits rightForm = rightNodeOptions & kXMP_PropCompositeMask; if (leftForm != rightForm) return false; if (leftForm == 0) { // Simple nodes, check the values and xml:lang qualifiers. XMP_VarString leftValue = leftNode->ConvertToSimpleNode()->GetValue()->c_str(); XMP_VarString rightValue = rightNode->ConvertToSimpleNode()->GetValue()->c_str(); if (leftValue != rightValue) return false; if ((leftNodeOptions & kXMP_PropHasLang) != (rightNodeOptions & kXMP_PropHasLang)) return false; if (leftNodeOptions & kXMP_PropHasLang) { spcINode leftDefaultQualifier = leftNode->GetQualifier( xmlNameSpace.c_str(), xmlNameSpace.size(), "lang", AdobeXMPCommon::npos ); spcINode rightDefaultQualifier = rightNode->GetQualifier( xmlNameSpace.c_str(), xmlNameSpace.size(), "lang", AdobeXMPCommon::npos ); if (!leftDefaultQualifier && !rightDefaultQualifier) return true; if (!leftDefaultQualifier || !rightDefaultQualifier) return false; XMP_VarString leftFirstQualValue = leftDefaultQualifier->ConvertToSimpleNode()->GetValue()->c_str(); XMP_VarString rightFirstQualValue = rightDefaultQualifier->ConvertToSimpleNode()->GetValue()->c_str(); if (leftFirstQualValue != rightFirstQualValue) { return false; } } } else if (leftForm == kXMP_PropValueIsStruct) { // Struct nodes, see if all fields match, ignoring order. size_t leftNodeChildCount = XMPUtils::GetNodeChildCount(leftNode); size_t rightNodeChildCount = XMPUtils::GetNodeChildCount(rightNode); if (leftNodeChildCount != rightNodeChildCount) return false; auto leftNodeChildIter = XMPUtils::GetNodeChildIterator(leftNode); auto rightNodeChildIter = XMPUtils::GetNodeChildIterator(rightNode); for (; leftNodeChildIter; leftNodeChildIter = leftNodeChildIter->Next(), rightNodeChildIter = rightNodeChildIter->Next()) { spcINode leftField = leftNodeChildIter->GetNode(); spINode rightField = XMPUtils::FindChildNode( AdobeXMPCore_Int::const_pointer_cast(rightNode), leftField->GetNameSpace()->c_str(), leftField->GetName()->c_str(), kXMP_ExistingOnly, 0); if (!rightField || !ItemValuesMatch(leftField, rightField)) { return false; } } } else { // Array nodes, see if the "leftNode" values are present in the "rightNode", ignoring order, duplicates, // and extra values in the rightNode-> The rightNode is the destination for AppendProperties. XMP_Assert(leftForm & kXMP_PropValueIsArray); size_t leftNodeChildCount = XMPUtils::GetNodeChildCount(leftNode); size_t rightNodeChildCount = XMPUtils::GetNodeChildCount(rightNode); auto leftNodeAsArrayNode = leftNode->ConvertToArrayNode(); auto rightNodeAsArrayNode = rightNode->ConvertToArrayNode(); for (size_t leftNum = 1; leftNum <= leftNodeChildCount; ++leftNum) { spcINode leftItem = leftNodeAsArrayNode->GetNodeAtIndex(leftNum); bool leftItemFound = false; for (size_t rightNum = 1; rightNum <= rightNodeChildCount; ++rightNum) { spcINode rightItem = rightNodeAsArrayNode->GetNodeAtIndex(rightNum); if (ItemValuesMatch(leftItem, rightItem)) { leftItemFound = true; break; } } if (!leftItemFound) return false; } } return true; // All of the checks passed. } // ItemValuesMatch static void AppendSubtree(spcINode sourceNode, spINode &destParent, const bool mergeCompound, const bool replaceOld, const bool deleteEmpty) { XMP_VarString sourceName = sourceNode->GetName()->c_str(); XMP_VarString sourceNamespace = sourceNode->GetNameSpace()->c_str(); XMP_VarString destName = destParent->GetName()->c_str(); XMP_VarString destNamespace = destParent->GetNameSpace()->c_str(); if (sourceName.find("UserComment") != XMP_VarString::npos) { int y = 1; y++; } // Need clone non empty only // to do lang alt append // to do lang alt size_t destPos = 0; spINode destNode = XMPUtils::FindChildNode(destParent, sourceNode->GetName()->c_str(), sourceNode->GetNameSpace()->c_str(), kXMP_ExistingOnly, &destPos); bool valueIsEmpty = false; XMP_OptionBits sourceNodeOptions = XMPUtils::GetIXMPOptions(sourceNode); if (sourceNode->GetNodeType() == INode::kNTSimple) { valueIsEmpty = sourceNode->ConvertToSimpleNode()->GetValue()->empty(); } else { valueIsEmpty = XMPUtils::GetNodeChildCount(sourceNode) == 0; } if (valueIsEmpty) { if (sourceNode && deleteEmpty) { destParent->ConvertToStructureNode()->GetIStructureNode_I()->RemoveNode( sourceNode->GetNameSpace(), sourceNode->GetName() ); } return; // ! Done, empty values are either ignored or cause deletions. } if (!destNode) { // The one easy case, the destination does not exist. destNode = sourceNode->Clone(true, true); // TO DO need to skip empty nodes if (!destNode) { if (destParent->GetNodeType() == INode::kNTStructure) { destParent->ConvertToStructureNode()->GetIStructureNode_I()->RemoveNode( sourceNode->GetNameSpace(), sourceNode->GetName() ); } else if (destParent->GetNodeType() == INode::kNTArray) { destParent->ConvertToArrayNode()->RemoveNodeAtIndex( destPos ); } return; } if (destParent->GetNodeType() == INode::kNTStructure) { destParent->ConvertToStructureNode()->InsertNode( destNode ); } else if (destParent->GetNodeType() == INode::kNTArray) { destParent->ConvertToArrayNode()->InsertNodeAtIndex( destNode, destPos ); } // XMP_Assert ( (!destNode) || (! destNode->value.empty()) || (! destNode->children.empty()) ); return; } // If we get here we're going to modify an existing property, either replacing or merging. XMP_Assert((!valueIsEmpty) && (destNode)); XMP_OptionBits sourceForm = XMPUtils::GetIXMPOptions(sourceNode) & kXMP_PropCompositeMask; XMP_OptionBits destForm = XMPUtils::GetIXMPOptions(destNode) & kXMP_PropCompositeMask; bool replaceThis = replaceOld; // ! Don't modify replaceOld, it gets passed to inner calls. if (mergeCompound && (!XMP_PropIsSimple(sourceForm))) replaceThis = false; if (replaceThis) { if (destNode) { destParent->ConvertToStructureNode()->GetIStructureNode_I()->RemoveNode( destNode->GetNameSpace(), destNode->GetName() ); } destNode = sourceNode->Clone(true, true); if (!destNode) return; if ( destNode->GetParent() != destParent && destParent->GetNodeType() == INode::kNTStructure ) destParent->ConvertToStructureNode()->AppendNode( destNode ); else if (destNode->GetParent() != destParent && destParent->GetNodeType() == INode::kNTArray) { destParent->ConvertToArrayNode()->InsertNodeAtIndex( destNode, destPos ); } if (!XMP_PropIsSimple(XMPUtils::GetIXMPOptions(destNode)) && !XMPUtils::GetNodeChildCount(destNode)) { // Don't keep an empty array or struct. The source might be implicitly empty due to // all children being empty. In this case CloneOffspring should skip them. if (destParent->GetNodeType() == INode::kNTStructure) { destParent->ConvertToStructureNode()->GetIStructureNode_I()->RemoveNode( destNode->GetNameSpace(), destNode->GetName() ); } else if (destParent->GetNodeType() == INode::kNTArray) { destParent->ConvertToArrayNode()->RemoveNodeAtIndex( destPos ); } } return; } // From here on are cases for merging arrays or structs. if (XMP_PropIsSimple(sourceForm) || (sourceForm != destForm)) return; if (sourceForm == kXMP_PropValueIsStruct) { auto sourceChildIter = XMPUtils::GetNodeChildIterator(sourceNode); for (; sourceChildIter; sourceChildIter = sourceChildIter->Next()) { spcINode sourceField = sourceChildIter->GetNode(); AppendSubtree(sourceField, destNode, mergeCompound, replaceOld, deleteEmpty); if (deleteEmpty && !XMPUtils::GetNodeChildCount(destNode)) { destParent->ConvertToStructureNode()->GetIStructureNode_I()->RemoveNode( destNode->GetNameSpace(), destNode->GetName() ); } } } else if (sourceForm & kXMP_PropArrayIsAltText) { XMP_Assert(mergeCompound); spcIArrayNode sourceArrayNode = sourceNode->ConvertToArrayNode(); for (size_t sourceNum = 1, sourceLim = XMPUtils::GetNodeChildCount(sourceNode); sourceNum <= sourceLim && destNode; ++sourceNum) { spcINode sourceItem = sourceArrayNode->GetNodeAtIndex(sourceNum); spcIUTF8String sourceItemValue = sourceItem->ConvertToSimpleNode()->GetValue(); if (!sourceItem->HasQualifiers()) continue; spcINode langQualNode = sourceItem->GetQualifier(xmlNameSpace.c_str(), xmlNameSpace.size(), "lang", AdobeXMPCommon::npos ); if (!langQualNode || langQualNode->GetNodeType() != INode::kNTSimple) continue; XMP_VarString langValue = langQualNode->ConvertToSimpleNode()->GetValue()->c_str(); size_t destIndex = LookupIXMPLangItem( destNode->ConvertToArrayNode(), langValue ); if (sourceItemValue->empty()) { if (deleteEmpty && (destIndex != -1)) { //delete ( destNode->children[destIndex] ); //destNode->children.erase ( destNode->children.begin() + destIndex ); destNode->ConvertToArrayNode()->RemoveNodeAtIndex(destIndex); if (!XMPUtils::GetNodeChildCount(destNode)) { if (destParent->GetNodeType() == INode::kNTStructure) { destParent->ConvertToStructureNode()->GetIStructureNode_I()->RemoveNode( destNode->GetNameSpace(), destNode->GetName() ); } else if (destParent->GetNodeType() == INode::kNTArray) { destParent->ConvertToArrayNode()->RemoveNodeAtIndex( destPos ); } } } } else { if (destIndex != -1) { // The source and dest arrays both have this language item. if (replaceOld) { auto temp = destNode->ConvertToArrayNode()->GetNodeAtIndex( destIndex ); temp->ConvertToSimpleNode()->SetValue(sourceItemValue->c_str(), sourceItemValue->size() ); } } else { spcISimpleNode firstQualifier = sourceItem->GetSimpleQualifier( xmlNameSpace.c_str(), xmlNameSpace.size(), "lang", AdobeXMPCommon::npos ); if ((!XMP_LitMatch(firstQualifier->GetValue()->c_str(), "x-default")) || !XMPUtils::GetNodeChildCount(destNode)) { CloneIXMPSubtree(sourceItem, destNode, true); } else { spINode destItem = AdobeXMPCore_Int::ISimpleNode_I::CreateSimpleNode( sourceItem->GetNameSpace(), sourceItem->GetName(), sourceItemValue ); destNode->ConvertToArrayNode()->InsertNodeAtIndex(destItem, 1); } } } } } else if (sourceForm & kXMP_PropValueIsArray) { auto sourceNodeChildIter = XMPUtils::GetNodeChildIterator(sourceNode); for (; sourceNodeChildIter; sourceNodeChildIter = sourceNodeChildIter->Next()) { spcINode sourceItem = sourceNodeChildIter->GetNode(); spIArrayNode arrayNode = destNode->ConvertToArrayNode(); size_t arrayChildCount = arrayNode->ChildCount(); size_t foundIndex = arrayChildCount + 1; for (size_t arrayIdx = 1; arrayIdx <= arrayChildCount; arrayIdx++) { spINode destItem = arrayNode->GetNodeAtIndex(arrayIdx); //XMP_VarString destValue = INode_I::AdaptConstNodeTo_I(destItem)->GetValue_I()->c_str(); if (ItemValuesMatch(destItem, sourceItem)) { foundIndex = arrayIdx; break; } } if (foundIndex == arrayChildCount + 1) { CloneIXMPSubtree(sourceItem, destNode, true); } } } } // AppendSubtree_v2 // ------------------------------------------------------------------------------------------------- // ApplyTemplate // ------------- /* class static */ void XMPUtils::ApplyTemplate_v2( XMPMeta * workingXMPBasePtr, const XMPMeta & templateXMPBasePtr, XMP_OptionBits actions) { XMPMeta2 * workingXMP = dynamic_cast(workingXMPBasePtr); if (!workingXMPBasePtr) return; const XMPMeta2 & templateXMP = dynamic_cast (templateXMPBasePtr); bool doClear = XMP_OptionIsSet(actions, kXMPTemplate_ClearUnnamedProperties); bool doAdd = XMP_OptionIsSet(actions, kXMPTemplate_AddNewProperties); bool doReplace = XMP_OptionIsSet(actions, kXMPTemplate_ReplaceExistingProperties); bool deleteEmpty = XMP_OptionIsSet(actions, kXMPTemplate_ReplaceWithDeleteEmpty); doReplace |= deleteEmpty; // Delete-empty implies Replace. deleteEmpty &= (!doClear); // Clear implies not delete-empty, but keep the implicit Replace. bool doAll = XMP_OptionIsSet(actions, kXMPTemplate_IncludeInternalProperties); auto defaultMap = INameSpacePrefixMap::GetDefaultNameSpacePrefixMap()->GetINameSpacePrefixMap_I(); if (doClear) { // Visit the top level working properties, delete if not in the template. auto topLevelPropIter = XMPUtils::GetNodeChildIterator(workingXMP->mDOM); std::vector propsToBeDeleted; // needed to avoid problems related with deletion and invalid const iterator for (; topLevelPropIter; topLevelPropIter = topLevelPropIter->Next()) { spcINode topLevelProp = topLevelPropIter->GetNode(); XMP_VarString currNameSpace = defaultMap->GetPrefix(topLevelProp->GetNameSpace())->c_str(); XMP_VarString nodeFullName = currNameSpace + ":" + topLevelProp->GetName()->c_str(); if (doAll || !IsInternalProperty(topLevelProp->GetNameSpace()->c_str(), nodeFullName)) { if (!templateXMP.mDOM->GetIStructureNode_I()->GetNode(topLevelProp->GetNameSpace(), topLevelProp->GetName())) { propsToBeDeleted.push_back(topLevelProp); } } } for (size_t idx = 0; idx < propsToBeDeleted.size(); idx++) { workingXMP->mDOM->GetIStructureNode_I()->RemoveNode(propsToBeDeleted[idx]->GetNameSpace(), propsToBeDeleted[idx]->GetName()); } } if (doAdd | doReplace) { auto templateTopPropIter = XMPUtils::GetNodeChildIterator(templateXMP.mDOM); for (; templateTopPropIter; templateTopPropIter = templateTopPropIter->Next()) { spcINode currentTemplateTopProp = templateTopPropIter->GetNode(); XMP_VarString currNameSpace = defaultMap->GetPrefix(currentTemplateTopProp->GetNameSpace())->c_str(); XMP_VarString nodeFullName = currNameSpace + ":" + currentTemplateTopProp->GetName()->c_str(); XMP_ExpandedXPath expPath; ExpandXPath(currentTemplateTopProp->GetNameSpace()->c_str(), nodeFullName.c_str(), &expPath); spINode destNode; spINode templateXMPRoot = workingXMP->mDOM; if (!currentTemplateTopProp) continue; if (doAll || !IsInternalProperty(currentTemplateTopProp->GetNameSpace()->c_str(), nodeFullName)){ AppendSubtree ( currentTemplateTopProp, templateXMPRoot, doAdd, doReplace, deleteEmpty ); } } } } // ApplyTemplate_v2 // ------------------------------------------------------------------------------------------------- // DuplicateSubtree // ---------------- void CloneContents(spINode sourceNode, spINode &destNode) { if (sourceNode->GetNodeType() == INode::kNTSimple) { spISimpleNode sourceSimpleNode = sourceNode->ConvertToSimpleNode(); spISimpleNode destSimpleNode = AdobeXMPCore_Int::ISimpleNode_I::CreateSimpleNode(destNode->GetNameSpace(), destNode->GetName(), sourceSimpleNode->GetValue() ); destNode = destSimpleNode; } else if (sourceNode->GetNodeType() == INode::kNTArray) { spIArrayNode arraySourceNode = sourceNode->ConvertToArrayNode(); spIArrayNode destArrayNode = AdobeXMPCore_Int::IArrayNode_I::CreateArrayNode(destNode->GetNameSpace(), destNode->GetName(), arraySourceNode->GetArrayForm()); destNode = destArrayNode; for (auto childIter = arraySourceNode->Iterator(); childIter; childIter = childIter->Next()) { spINode childCloned = childIter->GetNode()->Clone(); destArrayNode->AppendNode(childCloned); } destNode = destArrayNode; } else { spIStructureNode arraySourceNode = sourceNode->ConvertToStructureNode(); spIStructureNode destArrayNode = destNode->ConvertToStructureNode(); for (auto childIter = arraySourceNode->Iterator(); childIter; childIter = childIter->Next()) { spINode childCloned = childIter->GetNode()->Clone(); destArrayNode->AppendNode(childCloned); } destNode = destArrayNode; } if (sourceNode->HasQualifiers()) { for (auto qualIter = sourceNode->QualifiersIterator(); qualIter; qualIter = qualIter->Next()) { spINode clonedQual = qualIter->GetNode()->Clone(); destNode->InsertQualifier(clonedQual); } } } /* class static */ void XMPUtils::DuplicateSubtree_v2(const XMPMeta & sourceBasePtr, XMPMeta * destBasePtr, XMP_StringPtr sourceNS, XMP_StringPtr sourceRoot, XMP_StringPtr destNS, XMP_StringPtr destRoot, XMP_OptionBits options) { XMPMeta2 * dest = dynamic_cast(destBasePtr); if (!dest) return; const XMPMeta2 & source = dynamic_cast (sourceBasePtr); IgnoreParam(options); // TODO : Use of testnode == soucenode seems slightly dodgy, verify if it works in null case, simple case, subtree case bool fullSourceTree = false; bool fullDestTree = false; XMP_ExpandedXPath sourcePath, destPath; spINode sourceNode; spINode destNode; XMP_Assert((sourceNS != 0) && (*sourceNS != 0)); XMP_Assert((sourceRoot != 0) && (*sourceRoot != 0)); XMP_Assert((dest != 0) && (destNS != 0) && (destRoot != 0)); if (*destNS == 0) destNS = sourceNS; if (*destRoot == 0) destRoot = sourceRoot; if (XMP_LitMatch(sourceNS, "*")) fullSourceTree = true; if (XMP_LitMatch(destNS, "*")) fullDestTree = true; if ((&source == dest) && (fullSourceTree | fullDestTree)) { XMP_Throw("Can't duplicate tree onto itself", kXMPErr_BadParam); } if (fullSourceTree & fullDestTree) XMP_Throw("Use Clone for full tree to full tree", kXMPErr_BadParam); if (fullSourceTree) { // The destination must be an existing empty struct, copy all of the source top level as fields. ExpandXPath(destNS, destRoot, &destPath); XMP_OptionBits destOptions = 0; if (!XMPUtils::FindCnstNode(dest->mDOM, destPath, destNode, &destOptions)) { XMP_Throw("Destination must be an existing struct", kXMPErr_BadXPath); } if (!XMP_PropIsStruct(destOptions)) { XMP_Throw("Destination must be an existing struct", kXMPErr_BadXPath); } if (XMPUtils::GetNodeChildCount(destNode)) { if (options & kXMP_DeleteExisting) { destNode->Clear(); } else { XMP_Throw("Destination must be an empty struct", kXMPErr_BadXPath); } } sourceNode = source.mDOM; CloneContents(sourceNode, destNode); } else if (fullDestTree) { // The source node must be an existing struct, copy all of the fields to the dest top level. XMP_ExpandedXPath srcPath; ExpandXPath(sourceNS, sourceRoot, &srcPath); spINode sourceNode; XMP_OptionBits sourceNodeOptions = 0; XMPUtils::FindCnstNode(source.mDOM, srcPath, sourceNode, &sourceNodeOptions); if ((!sourceNode) || (!XMP_PropIsStruct(sourceNodeOptions))) { XMP_Throw("Source must be an existing struct", kXMPErr_BadXPath); } destNode = dest->mDOM; if (XMPUtils::GetNodeChildCount(destNode)) { if (options & kXMP_DeleteExisting) { destNode->Clear(); } else { XMP_Throw("Destination tree must be empty", kXMPErr_BadXPath); } } for (auto sourceChildIter = XMPUtils::GetNodeChildIterator(sourceNode); sourceChildIter; sourceChildIter = sourceChildIter->Next()) { spINode copyNode = sourceChildIter->GetNode()->Clone(); if (destNode->GetNodeType() == INode::kNTStructure){ destNode->ConvertToStructureNode()->AppendNode(copyNode); } } } else { // Find the root nodes for the source and destination subtrees. ExpandXPath(sourceNS, sourceRoot, &sourcePath); ExpandXPath(destNS, destRoot, &destPath); spINode destNodeCopy; if (!XMPUtils::FindCnstNode(source.mDOM, sourcePath, sourceNode)) { XMP_Throw("Can't find source subtree", kXMPErr_BadXPath); } if (XMPUtils::FindCnstNode(dest->mDOM, destPath, destNode)) { XMP_Throw("Destination subtree must not exist", kXMPErr_BadXPath); } if (!XMPUtils::FindNode(dest->mDOM, destPath, kXMP_CreateNodes, 0, destNode)) { // Now create the dest. XMP_Throw("Can't create destination root node", kXMPErr_BadXPath); } // Make sure the destination is not within the source! The source can't be inside the destination // because the source already existed and the destination was just created. if (&source == dest) { for (spINode testNode = destNode; testNode; testNode = testNode->GetParent()){ if (testNode.get() == sourceNode.get()) { XMP_Throw("Destination subtree is within the source subtree", kXMPErr_BadXPath); } } } // *** Could use a CloneTree util here and maybe elsewhere. //destNode = sourceNode->Clone(); if (sourceNode->GetNodeType() == INode::kNTSimple) { spISimpleNode sourceSimpleNode = sourceNode->ConvertToSimpleNode(); spISimpleNode destSimpleNode = AdobeXMPCore_Int::ISimpleNode_I::CreateSimpleNode(destNode->GetNameSpace(), destNode->GetName(), sourceSimpleNode->GetValue() ); destNodeCopy = destSimpleNode; } else if (sourceNode->GetNodeType() == INode::kNTArray) { spIArrayNode arraySourceNode = sourceNode->ConvertToArrayNode(); spIArrayNode destArrayNode = AdobeXMPCore_Int::IArrayNode_I::CreateArrayNode(destNode->GetNameSpace(), destNode->GetName(), arraySourceNode->GetArrayForm()); destNodeCopy = destArrayNode; for (auto childIter = arraySourceNode->Iterator(); childIter; childIter = childIter->Next()) { spINode childCloned = childIter->GetNode()->Clone(); destArrayNode->AppendNode(childCloned); } destNodeCopy = destArrayNode; } else { spIStructureNode arraySourceNode = sourceNode->ConvertToStructureNode(); spIStructureNode destArrayNode = AdobeXMPCore_Int::IStructureNode_I::CreateStructureNode(destNode->GetNameSpace(), destNode->GetName()); destNodeCopy = destArrayNode; for (auto childIter = arraySourceNode->Iterator(); childIter; childIter = childIter->Next()) { spINode childCloned = childIter->GetNode()->Clone(); destArrayNode->AppendNode(childCloned); } destNodeCopy = destArrayNode; } if (sourceNode->HasQualifiers()) { for (auto qualIter = sourceNode->QualifiersIterator(); qualIter; qualIter = qualIter->Next()) { spINode clonedQual = qualIter->GetNode()->Clone(); destNodeCopy->InsertQualifier(clonedQual); } } dest->mDOM->ReplaceNode(destNodeCopy); } } // DuplicateSubtree_v2 // ------------------------------------------------------------------------------------------------- // RemoveProperties // ---------------- /* class static */ void XMPUtils::RemoveProperties_v2(XMPMeta * xmpMetaPtr, XMP_StringPtr schemaNS, XMP_StringPtr propName, XMP_OptionBits options) { using namespace AdobeXMPCommon; using namespace AdobeXMPCore; XMPMeta2 * xmpObj = dynamic_cast (xmpMetaPtr); if (!xmpObj) { return; } // Handle aliases in remove properties XMP_Assert((schemaNS != 0) && (propName != 0)); // ! Enforced by wrapper. const bool doAll = XMP_TestOption(options, kXMPUtil_DoAllProperties); const bool includeAliases = XMP_TestOption(options, kXMPUtil_IncludeAliases); if (*propName != 0) { // Remove just the one indicated property. This might be an alias, the named schema might // not actually exist. So don't lookup the schema node. if (*schemaNS == 0) XMP_Throw("Property name requires schema namespace", kXMPErr_BadParam); XMP_ExpandedXPath expPath; ExpandXPath(schemaNS, propName, &expPath); XMP_Index propIndex = 0; spINode propNode; if (XMPUtils::FindNode(xmpObj->mDOM, expPath, kXMP_ExistingOnly, kXMP_NoOptions, propNode, &propIndex)) { if (doAll || !IsInternalProperty(expPath[kSchemaStep].step, expPath[kRootPropStep].step)) { spINode parentNode = propNode->GetParent(); // *** Should have XMP_Node::RemoveChild(pos). if (parentNode->GetNodeType() == INode::kNTStructure) { parentNode->ConvertToStructureNode()->GetIStructureNode_I()->RemoveNode( propNode->GetNameSpace(), propNode->GetName() ); } else if (parentNode->GetNodeType() == INode::kNTArray) { parentNode->ConvertToArrayNode()->RemoveNodeAtIndex(propIndex); } } } } else if (*schemaNS != 0) { std::vector topLevelNodesToBeDeleted; for (auto topLevelIter = xmpObj->mDOM->Iterator(); topLevelIter; topLevelIter = topLevelIter->Next()) { spINode topLevelProp = topLevelIter->GetNode(); if (XMP_LitMatch(topLevelProp->GetNameSpace()->c_str(), schemaNS)) { topLevelNodesToBeDeleted.push_back(topLevelProp); } } for (size_t propIdx = 0; propIdx < topLevelNodesToBeDeleted.size(); ++propIdx) { xmpObj->mDOM->GetIStructureNode_I()->RemoveNode(topLevelNodesToBeDeleted[propIdx]->GetNameSpace(), topLevelNodesToBeDeleted[propIdx]->GetName()); } if (includeAliases) { // Removing Aliases XMP_StringPtr nsPrefix; XMP_StringLen nsLen; (void)XMPMeta::GetNamespacePrefix(schemaNS, &nsPrefix, &nsLen); XMP_AliasMapPos currAlias = sRegisteredAliasMap->begin(); XMP_AliasMapPos endAlias = sRegisteredAliasMap->end(); for (; currAlias != endAlias; ++currAlias) { if (strncmp(currAlias->first.c_str(), nsPrefix, nsLen) == 0) { spINode destNode; XMP_Index actualPos = 0; size_t colonPos = currAlias->first.find_first_of(":"); xmpObj->mDOM->RemoveNode( schemaNS, AdobeXMPCommon::npos, currAlias->first.substr( colonPos + 1 ).c_str(), AdobeXMPCommon::npos ); /*if (!XMPUtils::FindCnstNode(xmpObj->mDOM, currAlias->second, destNode, 0, &actualPos)) continue; if (!destNode) continue; spINode rootProp = destNode; while (rootProp && rootProp->GetParent() != xmpObj->mDOM) { rootProp = rootProp->GetParent(); } if (doAll || !IsInternalProperty(rootProp->GetNameSpace()->c_str(), rootProp->GetName()->c_str())) { spINode parentNode = destNode->GetParent(); if (destNode->IsArrayItem()){ INode_I::AdaptNodeTo_I(parentNode)->RemoveNode_I(actualPos); } else { INode_I::AdaptNodeTo_I(parentNode)->RemoveNode_I(destNode->GetNameSpace()->c_str(), destNode->GetName()->c_str()); } }*/ } } } } else { xmpObj->mDOM->Clear(); } } // RemoveProperties_v2 #endif diff --git a/core/libs/dngwriter/extra/xmp_sdk/XMPFiles/source/FileHandlers/AIFF_Handler.cpp b/core/libs/dngwriter/extra/xmp_sdk/XMPFiles/source/FileHandlers/AIFF_Handler.cpp new file mode 100644 index 0000000000..8c9d604191 --- /dev/null +++ b/core/libs/dngwriter/extra/xmp_sdk/XMPFiles/source/FileHandlers/AIFF_Handler.cpp @@ -0,0 +1,441 @@ +// ================================================================================================= +// ADOBE SYSTEMS INCORPORATED +// Copyright 2010 Adobe Systems Incorporated +// All Rights Reserved +// +// NOTICE: Adobe permits you to use, modify, and distribute this file in accordance with the terms +// of the Adobe license agreement accompanying it. +// ================================================================================================= + +#include "public/include/XMP_Environment.h" // ! XMP_Environment.h must be the first included header. +#include "public/include/XMP_Const.h" + +#include "XMPFiles/source/FileHandlers/AIFF_Handler.hpp" +#include "XMPFiles/source/FormatSupport/AIFF/AIFFBehavior.h" +#include "XMPFiles/source/FormatSupport/AIFF/AIFFReconcile.h" +#include "XMPFiles/source/NativeMetadataSupport/MetadataSet.h" +#include "source/XIO.hpp" + +using namespace IFF_RIFF; + +// ================================================================================================= +/// \file AIFF_Handler.cpp +/// \brief File format handler for AIFF. +// ================================================================================================= + + +// ================================================================================================= +// AIFF_MetaHandlerCTor +// ==================== + +XMPFileHandler * AIFF_MetaHandlerCTor ( XMPFiles * parent ) +{ + return new AIFF_MetaHandler ( parent ); +} + +// ================================================================================================= +// AIFF_CheckFormat +// =============== +// +// Checks if the given file is a valid AIFF or AIFC file. +// The first 12 bytes are checked. The first 4 must be "FORM" +// Bytes 8 to 12 must be "AIFF" or "AIFC" + +bool AIFF_CheckFormat ( XMP_FileFormat format, + XMP_StringPtr filePath, + XMP_IO* file, + XMPFiles* parent ) +{ + // Reset file pointer position + file ->Rewind(); + + XMP_Uns8 chunkID[12]; + XMP_Int32 got = file->Read ( chunkID, 12 ); + + // Reset file pointer position + file ->Rewind(); + + // Need to have at least ID, size and Type of first chunk + if ( got < 12 ) + { + return false; + } + + const BigEndian& endian = BigEndian::getInstance(); + if ( endian.getUns32(chunkID) != kChunk_FORM ) + { + return false; + } + + XMP_Uns32 type = AIFF_MetaHandler::whatAIFFFormat( &chunkID[8] ); + if ( type == kType_AIFF || type == kType_AIFC ) + { + return true; + } + + return false; +} // AIFF_CheckFormat + + +// ================================================================================================= +// AIFF_MetaHandler::whatAIFFFormat +// =============== + +XMP_Uns32 AIFF_MetaHandler::whatAIFFFormat( XMP_Uns8* buffer ) +{ + XMP_Uns32 type = 0; + + const BigEndian& endian = BigEndian::getInstance(); + + if( buffer != 0 ) + { + if( endian.getUns32( buffer ) == kType_AIFF ) + { + type = kType_AIFF; + } + else if( endian.getUns32( buffer ) == kType_AIFC ) + { + type = kType_AIFC; + } + } + + return type; +} // whatAIFFFormat + + +// Static inits + +// ChunkIdentifier +// FORM:AIFF/APPL:XMP +const ChunkIdentifier AIFF_MetaHandler::kAIFFXMP[2] = { { kChunk_FORM, kType_AIFF }, { kChunk_APPL, kType_XMP } }; +// FORM:AIFC/APPL:XMP +const ChunkIdentifier AIFF_MetaHandler::kAIFCXMP[2] = { { kChunk_FORM, kType_AIFC }, { kChunk_APPL, kType_XMP } }; +// FORM:AIFF/NAME +const ChunkIdentifier AIFF_MetaHandler::kAIFFName[2] = { { kChunk_FORM, kType_AIFF }, { kChunk_NAME, kType_NONE } }; +// FORM:AIFC/NAME +const ChunkIdentifier AIFF_MetaHandler::kAIFCName[2] = { { kChunk_FORM, kType_AIFC }, { kChunk_NAME, kType_NONE } }; +// FORM:AIFF/AUTH +const ChunkIdentifier AIFF_MetaHandler::kAIFFAuth[2] = { { kChunk_FORM, kType_AIFF }, { kChunk_AUTH, kType_NONE } }; +// FORM:AIFC/AUTH +const ChunkIdentifier AIFF_MetaHandler::kAIFCAuth[2] = { { kChunk_FORM, kType_AIFC }, { kChunk_AUTH, kType_NONE } }; +// FORM:AIFF/(c) +const ChunkIdentifier AIFF_MetaHandler::kAIFFCpr[2] = { { kChunk_FORM, kType_AIFF }, { kChunk_CPR, kType_NONE } }; +// FORM:AIFC/(c) +const ChunkIdentifier AIFF_MetaHandler::kAIFCCpr[2] = { { kChunk_FORM, kType_AIFC }, { kChunk_CPR, kType_NONE } }; +// FORM:AIFF/ANNO +const ChunkIdentifier AIFF_MetaHandler::kAIFFAnno[2] = { { kChunk_FORM, kType_AIFF }, { kChunk_ANNO, kType_NONE } }; +// FORM:AIFC/ANNO +const ChunkIdentifier AIFF_MetaHandler::kAIFCAnno[2] = { { kChunk_FORM, kType_AIFC }, { kChunk_ANNO, kType_NONE } }; + +// ================================================================================================= +// AIFF_MetaHandler::AIFF_MetaHandler +// ================================ + +AIFF_MetaHandler::AIFF_MetaHandler ( XMPFiles * _parent ) + : mChunkBehavior(NULL), mChunkController(NULL), + mAiffMeta(), mXMPChunk(NULL), + mNameChunk(NULL), mAuthChunk(NULL), + mCprChunk(NULL), mAnnoChunk(NULL) +{ + this->parent = _parent; + this->handlerFlags = kAIFF_HandlerFlags; + this->stdCharForm = kXMP_Char8Bit; + + this->mChunkBehavior = new AIFFBehavior(); + this->mChunkController = new ChunkController( mChunkBehavior, true ); + +} // AIFF_MetaHandler::AIFF_MetaHandler + +// ================================================================================================= +// AIFF_MetaHandler::~AIFF_MetaHandler +// ================================= + +AIFF_MetaHandler::~AIFF_MetaHandler() +{ + if( mChunkController != NULL ) + { + delete mChunkController; + } + + if( mChunkBehavior != NULL ) + { + delete mChunkBehavior; + } +} // AIFF_MetaHandler::~AIFF_MetaHandler + + +// ================================================================================================= +// AIFF_MetaHandler::CacheFileData +// ============================== + +void AIFF_MetaHandler::CacheFileData() +{ + // Need to determine the file type, need the first 12 bytes of the file + + // Reset file pointer position + this->parent->ioRef ->Rewind(); + + XMP_Uns8 buffer[12]; + XMP_Int32 got = this->parent->ioRef->Read ( buffer, 12 ); + XMP_Assert( got == 12 ); + + XMP_Uns32 type = AIFF_MetaHandler::whatAIFFFormat( &buffer[8] ); + XMP_Assert( type == kType_AIFF || type == kType_AIFC ); + + // Reset file pointer position + this->parent->ioRef ->Rewind(); + + // Add the relevant chunk paths for the determined AIFF format + if( type == kType_AIFF ) + { + mAIFFXMPChunkPath.append( kAIFFXMP, SizeOfCIArray(kAIFFXMP) ); + mAIFFNameChunkPath.append( kAIFFName, SizeOfCIArray(kAIFFName) ); + mAIFFAuthChunkPath.append( kAIFFAuth, SizeOfCIArray(kAIFFAuth) ); + mAIFFCprChunkPath.append( kAIFFCpr, SizeOfCIArray(kAIFFCpr) ); + mAIFFAnnoChunkPath.append( kAIFFAnno, SizeOfCIArray(kAIFFAnno) ); + } + else // kType_AIFC + { + mAIFFXMPChunkPath.append( kAIFCXMP, SizeOfCIArray(kAIFCXMP) ); + mAIFFNameChunkPath.append( kAIFCName, SizeOfCIArray(kAIFCName) ); + mAIFFAuthChunkPath.append( kAIFCAuth, SizeOfCIArray(kAIFCAuth) ); + mAIFFCprChunkPath.append( kAIFCCpr, SizeOfCIArray(kAIFCCpr) ); + mAIFFAnnoChunkPath.append( kAIFCAnno, SizeOfCIArray(kAIFCAnno) ); + } + + mChunkController->addChunkPath( mAIFFXMPChunkPath ); + mChunkController->addChunkPath( mAIFFNameChunkPath ); + mChunkController->addChunkPath( mAIFFAuthChunkPath ); + mChunkController->addChunkPath( mAIFFCprChunkPath ); + mChunkController->addChunkPath( mAIFFAnnoChunkPath ); + + + // Parse the given file + // Throws exception if the file cannot be parsed + mChunkController->parseFile( this->parent->ioRef, &this->parent->openFlags ); + + // Check if the file contains XMP (last one if there are multiple chunks) + mXMPChunk = mChunkController->getChunk( mAIFFXMPChunkPath, true ); + + // Retrieve XMP packet info + if( mXMPChunk != NULL ) + { + // subtract the type size that is contained in the XMP data chunk + this->packetInfo.length = static_cast(mXMPChunk->getSize() - 4); + this->packetInfo.charForm = kXMP_Char8Bit; + this->packetInfo.writeable = true; + + // Get actual the XMP packet without the 4byte type + this->xmpPacket.assign ( mXMPChunk->getString( this->packetInfo.length, 4 ) ); + + // set state + this->containsXMP = true; + } +} // AIFF_MetaHandler::CacheFileData + + +// ================================================================================================= +// AIFF_MetaHandler::ProcessXMP +// ============================ + +void AIFF_MetaHandler::ProcessXMP() +{ + // Must be done only once + if ( this->processedXMP ) + { + return; + } + // Set the status at start, in case something goes wrong in this method + this->processedXMP = true; + + // Parse the XMP + if ( ! this->xmpPacket.empty() ) { + + XMP_Assert ( this->containsXMP ); + + FillPacketInfo ( this->xmpPacket, &this->packetInfo ); + + this->xmpObj.ParseFromBuffer ( this->xmpPacket.c_str(), (XMP_StringLen)this->xmpPacket.size() ); + + this->containsXMP = true; + } + + // Then import native properties + MetadataSet metaSet; + AIFFReconcile recon; + + // Fill the AIFF metadata object with values + + // Get NAME (title) legacy chunk + mNameChunk = mChunkController->getChunk( mAIFFNameChunkPath, true ); + if( mNameChunk != NULL ) + { + mAiffMeta.setValue( AIFFMetadata::kName, mNameChunk->getString() ); + } + // Get AUTH (author) legacy chunk + mAuthChunk = mChunkController->getChunk( mAIFFAuthChunkPath, true ); + if( mAuthChunk != NULL ) + { + mAiffMeta.setValue( AIFFMetadata::kAuthor, mAuthChunk->getString() ); + } + // Get CPR (Copyright) legacy chunk + mCprChunk = mChunkController->getChunk( mAIFFCprChunkPath, true ); + if( mCprChunk != NULL ) + { + mAiffMeta.setValue( AIFFMetadata::kCopyright, mCprChunk->getString() ); + } + // Get ANNO (annotation) legacy chunk(s) + // Get the list of Annotation chunks and pick the last one not being empty + const std::vector &annoChunks = mChunkController->getChunks( mAIFFAnnoChunkPath ); + + mAnnoChunk = selectLastNonEmptyAnnoChunk( annoChunks ); + if( mAnnoChunk != NULL ) + { + mAiffMeta.setValue( AIFFMetadata::kAnnotation, mAnnoChunk->getString() ); + } + + // Only interested in AIFF metadata + metaSet.append( &mAiffMeta ); + // Do the import + if( recon.importToXMP( this->xmpObj, metaSet ) ) + { + // Remember if anything has changed + this->containsXMP = true; + } + +} // AIFF_MetaHandler::ProcessXMP + + +IChunkData* AIFF_MetaHandler::selectLastNonEmptyAnnoChunk( const std::vector &annoChunks ) +{ + IChunkData* annoChunk = NULL; + for ( std::vector::const_reverse_iterator iter = annoChunks.rbegin(); iter != annoChunks.rend(); iter++ ) + { + if( ! (*iter)->getString().empty() && (*iter)->getString()[0] != '\0' ) + { + annoChunk = *iter; + break; + } + } + return annoChunk; +} // selectFirstNonEmptyAnnoChunk + + +// ================================================================================================= +// AIFF_MetaHandler::UpdateFile +// =========================== + +void AIFF_MetaHandler::UpdateFile ( bool doSafeUpdate ) +{ + if ( ! this->needsUpdate ) { // If needsUpdate is set then at least the XMP changed. + return; + } + + if ( doSafeUpdate ) + { + XMP_Throw ( "AIFF_MetaHandler::UpdateFile: Safe update not supported", kXMPErr_Unavailable ); + } + + //update/create XMP chunk + if( this->containsXMP ) + { + this->xmpObj.SerializeToBuffer ( &(this->xmpPacket) ); + + if( mXMPChunk != NULL ) + { + mXMPChunk->setData( reinterpret_cast(this->xmpPacket.c_str()), this->xmpPacket.length(), true ); + } + else // create XMP chunk + { + mXMPChunk = mChunkController->createChunk( kChunk_APPL, kType_XMP ); + mXMPChunk->setData( reinterpret_cast(this->xmpPacket.c_str()), this->xmpPacket.length(), true ); + mChunkController->insertChunk( mXMPChunk ); + } + } + // XMP Packet is never completely removed from the file. + + // Export XMP to legacy chunks. Create/delete them if necessary + MetadataSet metaSet; + AIFFReconcile recon; + + metaSet.append( &mAiffMeta ); + + // If anything changes, update/create/delete the legacy chunks + if( recon.exportFromXMP( metaSet, this->xmpObj ) ) + { + updateLegacyChunk( &mNameChunk, kChunk_NAME, AIFFMetadata::kName ); + updateLegacyChunk( &mAuthChunk, kChunk_AUTH, AIFFMetadata::kAuthor ); + updateLegacyChunk( &mCprChunk, kChunk_CPR, AIFFMetadata::kCopyright ); + updateLegacyChunk( &mAnnoChunk, kChunk_ANNO, AIFFMetadata::kAnnotation ); + } + + XMP_ProgressTracker* progressTracker=this->parent->progressTracker; + // local progess tracking required because for Handlers incapable of + // kXMPFiles_CanRewrite XMPFiles call this Update method after making + // a copy of the orignal file + bool localProgressTracking=false; + if ( progressTracker != 0 ) + { + if ( ! progressTracker->WorkInProgress() ) + { + localProgressTracking = true; + progressTracker->BeginWork (); + } + } + //write tree back to file + mChunkController->writeFile( this->parent->ioRef ,progressTracker); + if ( localProgressTracking && progressTracker != 0 ) progressTracker->WorkComplete(); + + this->needsUpdate = false; // Make sure this is only called once. +} // AIFF_MetaHandler::UpdateFile + + +void AIFF_MetaHandler::updateLegacyChunk( IChunkData **chunk, XMP_Uns32 chunkID, XMP_Uns32 legacyId ) +{ + // If there is a legacy value, update/create the appropriate chunk + if( mAiffMeta.valueExists( legacyId ) ) + { + std::string chunkValue; + std::string legacyValue = mAiffMeta.getValue( legacyId ); + + // If the length is < 4 we need to fill up the value with \0 to a size of 4 + // This ensures that the overall size of text chunks is 12 bytes so that they can be + // converted to free chunks if necessary + if( legacyValue.length() < 4 ) + { + char buffer[4]; + memset( buffer, 0, 4 ); + memcpy( buffer, legacyValue.c_str(), legacyValue.length() ); + chunkValue.assign( buffer, 4 ); + } + else // take the value as is + { + chunkValue = legacyValue; + } + + if( *chunk != NULL ) + { + (*chunk)->setData( reinterpret_cast(chunkValue.c_str()), chunkValue.length() ); + } + else + { + *chunk = mChunkController->createChunk( chunkID, kType_NONE ); + (*chunk)->setData( reinterpret_cast(chunkValue.c_str()), chunkValue.length() ); + mChunkController->insertChunk( *chunk ); + } + } + else //delete chunk if existing + { + mChunkController->removeChunk ( *chunk ); + } +} // updateLegacyChunk + + +// ================================================================================================= +// AIFF_MetaHandler::WriteTempFile +// =============================== + +void AIFF_MetaHandler::WriteTempFile ( XMP_IO* tempRef ) +{ + XMP_Throw ( "AIFF_MetaHandler::WriteTempFile is not Implemented!", kXMPErr_Unimplemented ); +} // AIFF_MetaHandler::WriteTempFile diff --git a/core/libs/dngwriter/extra/xmp_sdk/XMPFiles/source/FileHandlers/AIFF_Handler.hpp b/core/libs/dngwriter/extra/xmp_sdk/XMPFiles/source/FileHandlers/AIFF_Handler.hpp new file mode 100644 index 0000000000..1ccb6a9e0f --- /dev/null +++ b/core/libs/dngwriter/extra/xmp_sdk/XMPFiles/source/FileHandlers/AIFF_Handler.hpp @@ -0,0 +1,159 @@ +// ================================================================================================= +// ADOBE SYSTEMS INCORPORATED +// Copyright 2010 Adobe Systems Incorporated +// All Rights Reserved +// +// NOTICE: Adobe permits you to use, modify, and distribute this file in accordance with the terms +// of the Adobe license agreement accompanying it. +// ================================================================================================= + +#ifndef __AIFF_Handler_hpp__ +#define __AIFF_Handler_hpp__ 1 + +#include "public/include/XMP_Environment.h" // ! XMP_Environment.h must be the first included header. +#include "public/include/XMP_Const.h" + +#include "XMPFiles/source/FormatSupport/IFF/ChunkController.h" +#include "XMPFiles/source/FormatSupport/IFF/IChunkBehavior.h" +#include "XMPFiles/source/FormatSupport/IFF/IChunkData.h" +#include "source/Endian.h" +#include "XMPFiles/source/FormatSupport/IFF/ChunkPath.h" +#include "XMPFiles/source/FormatSupport/AIFF/AIFFMetadata.h" +#include "source/XIO.hpp" + +#include "XMPFiles/source/XMPFiles_Impl.hpp" + +using namespace IFF_RIFF; + +// ================================================================================================= +/// \file AIFF_Handler.hpp +/// \brief File format handler for AIFF. +// ================================================================================================= + +/** + * Contructor for the handler. + */ +extern XMPFileHandler * AIFF_MetaHandlerCTor ( XMPFiles * parent ); + +/** + * Checks the format of the file, see common code. + */ +extern bool AIFF_CheckFormat ( XMP_FileFormat format, + XMP_StringPtr filePath, + XMP_IO* fileRef, + XMPFiles * parent ); + +/** AIFF does not need kXMPFiles_CanRewrite as we can always use UpdateFile to either do + * in-place update or append to the file. */ +static const XMP_OptionBits kAIFF_HandlerFlags = (kXMPFiles_CanInjectXMP | + kXMPFiles_CanExpand | + kXMPFiles_PrefersInPlace | + kXMPFiles_CanReconcile | + kXMPFiles_AllowsSafeUpdate | + kXMPFiles_CanNotifyProgress + ); + +/** + * Main class for the the AIFF file handler. + */ +class AIFF_MetaHandler : public XMPFileHandler +{ +public: + AIFF_MetaHandler ( XMPFiles* parent ); + ~AIFF_MetaHandler(); + + void CacheFileData(); + void ProcessXMP(); + + void UpdateFile ( bool doSafeUpdate ); + void WriteTempFile ( XMP_IO* tempRef ); + + /** + * Checks if the first 4 bytes of the given buffer are either type AIFF or AIFC + * @param buffer a byte buffer that must contain at least 4 bytes and point to the correct byte + * @return Either kType_AIFF, kType_AIFC 0 if no type could be determined + */ + static XMP_Uns32 whatAIFFFormat( XMP_Uns8* buffer ); + +private: + /** + * Updates/creates/deletes a given legacy chunk depending on the given new legacy value + * If the Chunk exists and the value is not empty, it is updated. If the value is empty the + * Chunk is removed from the tree. If the Chunk does not exist but a value is given, it is created + * and initialized with that value + * + * @param chunk OUT pointer to the legacy chunk + * @param chunkID Id of the Chunk if it needs to be created + * @param legacyId ID of the legacy value + */ + void updateLegacyChunk( IChunkData **chunk, XMP_Uns32 chunkID, XMP_Uns32 legacyId ); + + /** + * Finds the last non-empty annotation chunk in the given list + * @param annoChunks list of annotation chunks + * @return pointer to the first non-empty chunk or NULL + */ + IChunkData* selectLastNonEmptyAnnoChunk( const std::vector &annoChunks ); + + /** private standard Ctor, not to be used */ + AIFF_MetaHandler (): mChunkController(NULL), mChunkBehavior(NULL), + mAiffMeta(), mXMPChunk(NULL), + mNameChunk(NULL), mAuthChunk(NULL), + mCprChunk(NULL), mAnnoChunk(NULL) {}; + + + // ----- MEMBERS ----- // + + /** Controls the parsing and writing of the passed stream. */ + ChunkController *mChunkController; + /** Represents the rules how chunks are added, removed or rearranged */ + IChunkBehavior *mChunkBehavior; + /** container for Legacy metadata */ + AIFFMetadata mAiffMeta; + + /** pointer to the XMP chunk */ + IChunkData *mXMPChunk; + /** pointer to legacy chunks */ + IChunkData *mNameChunk; + IChunkData *mAuthChunk; + IChunkData *mCprChunk; + IChunkData *mAnnoChunk; + + /** Type of the file, either AIFF or AIFC */ + //XMP_Uns32 mFileType; + + + // ----- CONSTANTS ----- // + + /** Chunk path identifier of interest in AIFF/AIFC */ + static const ChunkIdentifier kAIFFXMP[2]; + static const ChunkIdentifier kAIFCXMP[2]; + static const ChunkIdentifier kAIFFName[2]; + static const ChunkIdentifier kAIFCName[2]; + static const ChunkIdentifier kAIFFAuth[2]; + static const ChunkIdentifier kAIFCAuth[2]; + static const ChunkIdentifier kAIFFCpr[2]; + static const ChunkIdentifier kAIFCCpr[2]; + static const ChunkIdentifier kAIFFAnno[2]; + static const ChunkIdentifier kAIFCAnno[2]; + + /** Path to XMP chunk */ + ChunkPath mAIFFXMPChunkPath; + + /** Path to NAME chunk */ + ChunkPath mAIFFNameChunkPath; + + /** Path to AUTH chunk */ + ChunkPath mAIFFAuthChunkPath; + + /** Path to COPYRIGHT chunk */ + ChunkPath mAIFFCprChunkPath; + + /** Path to ANNOTATION chunk */ + ChunkPath mAIFFAnnoChunkPath; + +}; // AIFF_MetaHandler + +// ================================================================================================= + +#endif /* __AIFF_Handler_hpp__ */ diff --git a/core/libs/dngwriter/extra/xmp_sdk/XMPFiles/source/FileHandlers/ASF_Handler.cpp b/core/libs/dngwriter/extra/xmp_sdk/XMPFiles/source/FileHandlers/ASF_Handler.cpp new file mode 100644 index 0000000000..14bf22541f --- /dev/null +++ b/core/libs/dngwriter/extra/xmp_sdk/XMPFiles/source/FileHandlers/ASF_Handler.cpp @@ -0,0 +1,355 @@ +// ================================================================================================= +// ADOBE SYSTEMS INCORPORATED +// Copyright 2006 Adobe Systems Incorporated +// All Rights Reserved +// +// NOTICE: Adobe permits you to use, modify, and distribute this file in accordance with the terms +// of the Adobe license agreement accompanying it. +// ================================================================================================= + +#include "public/include/XMP_Environment.h" // ! XMP_Environment.h must be the first included header. + +#include "public/include/XMP_Const.h" +#include "public/include/XMP_IO.hpp" + +#include "XMPFiles/source/XMPFiles_Impl.hpp" +#include "source/XMPFiles_IO.hpp" +#include "source/XIO.hpp" + +#include "XMPFiles/source/FileHandlers/ASF_Handler.hpp" + +// ================================================================================================= +/// \file ASF_Handler.hpp +/// \brief File format handler for ASF. +/// +/// This handler ... +/// +// ================================================================================================= + +// ================================================================================================= +// ASF_MetaHandlerCTor +// ==================== + +XMPFileHandler * ASF_MetaHandlerCTor ( XMPFiles * parent ) +{ + return new ASF_MetaHandler ( parent ); + +} // ASF_MetaHandlerCTor + +// ================================================================================================= +// ASF_CheckFormat +// =============== + +bool ASF_CheckFormat ( XMP_FileFormat format, + XMP_StringPtr filePath, + XMP_IO* fileRef, + XMPFiles * parent ) +{ + + IgnoreParam(format); IgnoreParam(fileRef); IgnoreParam(parent); + XMP_Assert ( format == kXMP_WMAVFile ); + + if ( fileRef->Length() < guidLen ) return false; + GUID guid; + + fileRef->Rewind(); + fileRef->Read ( &guid, guidLen ); + if ( ! IsEqualGUID ( ASF_Header_Object, guid ) ) return false; + + return true; + +} // ASF_CheckFormat + +// ================================================================================================= +// ASF_MetaHandler::ASF_MetaHandler +// ================================== + +ASF_MetaHandler::ASF_MetaHandler ( XMPFiles * _parent ) +{ + this->parent = _parent; + this->handlerFlags = kASF_HandlerFlags; + this->stdCharForm = kXMP_Char8Bit; + +} + +// ================================================================================================= +// ASF_MetaHandler::~ASF_MetaHandler +// =================================== + +ASF_MetaHandler::~ASF_MetaHandler() +{ + // Nothing extra to do. +} + +// ================================================================================================= +// ASF_MetaHandler::CacheFileData +// =============================== + +void ASF_MetaHandler::CacheFileData() +{ + + this->containsXMP = false; + + XMP_IO* fileRef ( this->parent->ioRef ); + if ( fileRef == 0 ) return; + + ASF_Support support ( &this->legacyManager,0 ); + ASF_Support::ObjectState objectState; + long numTags = support.OpenASF ( fileRef, objectState ); + if ( numTags == 0 ) return; + + if ( objectState.xmpLen != 0 ) { + + // XMP present + + XMP_Int32 len = XMP_Int32 ( objectState.xmpLen ); + + this->xmpPacket.reserve( len ); + this->xmpPacket.assign ( len, ' ' ); + + bool found = ASF_Support::ReadBuffer ( fileRef, objectState.xmpPos, objectState.xmpLen, + const_cast(this->xmpPacket.data()) ); + if ( found ) { + this->packetInfo.offset = objectState.xmpPos; + this->packetInfo.length = len; + this->containsXMP = true; + } + + } + +} // ASF_MetaHandler::CacheFileData + +// ================================================================================================= +// ASF_MetaHandler::ProcessXMP +// ============================ +// +// Process the raw XMP and legacy metadata that was previously cached. + +void ASF_MetaHandler::ProcessXMP() +{ + + this->processedXMP = true; // Make sure we only come through here once. + + // Process the XMP packet. + + if ( this->xmpPacket.empty() ) { + + // import legacy in any case, when no XMP present + legacyManager.ImportLegacy ( &this->xmpObj ); + this->legacyManager.SetDigest ( &this->xmpObj ); + + } else { + + XMP_Assert ( this->containsXMP ); + XMP_StringPtr packetStr = this->xmpPacket.c_str(); + XMP_StringLen packetLen = (XMP_StringLen)this->xmpPacket.size(); + + this->xmpObj.ParseFromBuffer ( packetStr, packetLen ); + + if ( ! legacyManager.CheckDigest ( this->xmpObj ) ) { + legacyManager.ImportLegacy ( &this->xmpObj ); + } + + } + + // Assume we now have something in the XMP. + this->containsXMP = true; + +} // ASF_MetaHandler::ProcessXMP + +// ================================================================================================= +// ASF_MetaHandler::UpdateFile +// ============================ + +void ASF_MetaHandler::UpdateFile ( bool doSafeUpdate ) +{ + + bool updated = false; + + if ( ! this->needsUpdate ) return; + + XMP_IO* fileRef ( this->parent->ioRef ); + if ( fileRef == 0 ) return; + + ASF_Support support(0,this->parent->progressTracker); + ASF_Support::ObjectState objectState; + long numTags = support.OpenASF ( fileRef, objectState ); + if ( numTags == 0 ) return; + + XMP_StringLen packetLen = (XMP_StringLen)xmpPacket.size(); + + this->legacyManager.ExportLegacy ( this->xmpObj ); + if ( this->legacyManager.hasLegacyChanged() ) { + + this->legacyManager.SetDigest ( &this->xmpObj ); + + // serialize with updated digest + if ( objectState.xmpLen == 0 ) { + + // XMP does not exist, use standard padding + this->xmpObj.SerializeToBuffer ( &this->xmpPacket, kXMP_UseCompactFormat ); + + } else { + + // re-use padding with static XMP size + try { + XMP_OptionBits compactExact = (kXMP_UseCompactFormat | kXMP_ExactPacketLength); + this->xmpObj.SerializeToBuffer ( &this->xmpPacket, compactExact, XMP_StringLen(objectState.xmpLen) ); + } catch ( ... ) { + // re-use padding with exact packet length failed (legacy-digest needed too much space): try again using standard padding + this->xmpObj.SerializeToBuffer ( &this->xmpPacket, kXMP_UseCompactFormat ); + } + + } + + } + + XMP_StringPtr packetStr = xmpPacket.c_str(); + packetLen = (XMP_StringLen)xmpPacket.size(); + if ( packetLen == 0 ) return; + + // value, when guessing for sufficient legacy padding (line-ending conversion etc.) + const int paddingTolerance = 50; + + bool xmpGrows = ( objectState.xmpLen && (packetLen > objectState.xmpLen) && ( ! objectState.xmpIsLastObject) ); + + bool legacyGrows = ( this->legacyManager.hasLegacyChanged() && + (this->legacyManager.getLegacyDiff() > (this->legacyManager.GetPadding() - paddingTolerance)) ); + + if ( doSafeUpdate || legacyGrows || xmpGrows ) { + + // do a safe update in any case + updated = SafeWriteFile(); + + } else { + + // possibly we can do an in-place update + + if ( objectState.xmpLen < packetLen ) { + + updated = SafeWriteFile(); + + } else { + + XMP_ProgressTracker* progressTracker = this->parent->progressTracker; + if ( progressTracker != 0 ) progressTracker->BeginWork ( (float)packetLen ); + + // current XMP chunk size is sufficient -> write (in place update) + updated = ASF_Support::WriteBuffer(fileRef, objectState.xmpPos, packetLen, packetStr ); + + // legacy update + if ( updated && this->legacyManager.hasLegacyChanged() ) { + + ASF_Support::ObjectIterator curPos = objectState.objects.begin(); + ASF_Support::ObjectIterator endPos = objectState.objects.end(); + + for ( ; curPos != endPos; ++curPos ) { + + ASF_Support::ObjectData object = *curPos; + + // find header-object + if ( IsEqualGUID ( ASF_Header_Object, object.guid ) ) { + // update header object + updated = support.UpdateHeaderObject ( fileRef, object, legacyManager ); + } + + } + + } + + if ( progressTracker != 0 ) progressTracker->WorkComplete(); + } + + } + + if ( ! updated ) return; // If there's an error writing the chunk, bail. + + this->needsUpdate = false; + +} // ASF_MetaHandler::UpdateFile + +// ================================================================================================= +// ASF_MetaHandler::WriteTempFile +// ============================== + +void ASF_MetaHandler::WriteTempFile ( XMP_IO* tempRef ) +{ + bool ok; + XMP_IO* originalRef = this->parent->ioRef; + + ASF_Support support(0,this->parent->progressTracker); + ASF_Support::ObjectState objectState; + long numTags = support.OpenASF ( originalRef, objectState ); + if ( numTags == 0 ) return; + + tempRef->Truncate ( 0 ); + + ASF_Support::ObjectIterator curPos = objectState.objects.begin(); + ASF_Support::ObjectIterator endPos = objectState.objects.end(); + XMP_ProgressTracker* progressTracker = this->parent->progressTracker; + if ( progressTracker != 0 ) { + float nonheadersize = (float)(xmpPacket.size()+kASF_ObjectBaseLen+8); + bool legacyChange=this->legacyManager.hasLegacyChanged( ); + for ( ; curPos != endPos; ++curPos ) { + if (curPos->xmp) continue; + //header objects are taken care of in ASF_Support::WriteHeaderObject + if ( ! ( IsEqualGUID ( ASF_Header_Object, curPos->guid) && legacyChange ) ) { + nonheadersize+=(curPos->len); + } + } + curPos = objectState.objects.begin(); + endPos = objectState.objects.end(); + progressTracker->BeginWork ( nonheadersize ); + } + for ( ; curPos != endPos; ++curPos ) { + + ASF_Support::ObjectData object = *curPos; + + // discard existing XMP object + if ( object.xmp ) continue; + + // update header-object, when legacy needs update + if ( IsEqualGUID ( ASF_Header_Object, object.guid) && this->legacyManager.hasLegacyChanged( ) ) { + // rewrite header object + ok = support.WriteHeaderObject ( originalRef, tempRef, object, this->legacyManager, false ); + if ( ! ok ) XMP_Throw ( "Failure writing ASF header object", kXMPErr_InternalFailure ); + } else { + // copy any other object + ok = ASF_Support::CopyObject ( originalRef, tempRef, object ); + if ( ! ok ) XMP_Throw ( "Failure copyinh ASF object", kXMPErr_InternalFailure ); + } + + // write XMP object immediately after the (one and only) top-level DataObject + if ( IsEqualGUID ( ASF_Data_Object, object.guid ) ) { + XMP_StringPtr packetStr = xmpPacket.c_str(); + XMP_StringLen packetLen = (XMP_StringLen)xmpPacket.size(); + ok = ASF_Support::WriteXMPObject ( tempRef, packetLen, packetStr ); + if ( ! ok ) XMP_Throw ( "Failure writing ASF XMP object", kXMPErr_InternalFailure ); + } + + } + + ok = support.UpdateFileSize ( tempRef ); + if ( ! ok ) XMP_Throw ( "Failure updating ASF file size", kXMPErr_InternalFailure ); + if ( progressTracker != 0 ) progressTracker->WorkComplete(); + +} // ASF_MetaHandler::WriteTempFile + +// ================================================================================================= +// ASF_MetaHandler::SafeWriteFile +// ============================== + +bool ASF_MetaHandler::SafeWriteFile() +{ + XMP_IO* originalFile = this->parent->ioRef; + XMP_IO* tempFile = originalFile->DeriveTemp(); + if ( tempFile == 0 ) XMP_Throw ( "Failure creating ASF temp file", kXMPErr_InternalFailure ); + + this->WriteTempFile ( tempFile ); + originalFile->AbsorbTemp(); + + return true; + +} // ASF_MetaHandler::SafeWriteFile + +// ================================================================================================= diff --git a/core/libs/dngwriter/extra/xmp_sdk/XMPFiles/source/FileHandlers/ASF_Handler.hpp b/core/libs/dngwriter/extra/xmp_sdk/XMPFiles/source/FileHandlers/ASF_Handler.hpp new file mode 100644 index 0000000000..f8b883eb8f --- /dev/null +++ b/core/libs/dngwriter/extra/xmp_sdk/XMPFiles/source/FileHandlers/ASF_Handler.hpp @@ -0,0 +1,65 @@ +#ifndef __ASF_Handler_hpp__ +#define __ASF_Handler_hpp__ 1 + +// ================================================================================================= +// ADOBE SYSTEMS INCORPORATED +// Copyright 2006 Adobe Systems Incorporated +// All Rights Reserved +// +// NOTICE: Adobe permits you to use, modify, and distribute this file in accordance with the terms +// of the Adobe license agreement accompanying it. +// ================================================================================================= + +#include "XMPFiles/source/XMPFiles_Impl.hpp" +#include "XMPFiles/source/FormatSupport/ASF_Support.hpp" + +// ================================================================================================= +/// \file ASF_Handler.hpp +/// \brief File format handler for ASF. +/// +/// This header ... +/// +// ================================================================================================= + +// *** Could derive from Basic_Handler - buffer file tail in a temp file. + +extern XMPFileHandler* ASF_MetaHandlerCTor ( XMPFiles* parent ); + +extern bool ASF_CheckFormat ( XMP_FileFormat format, + XMP_StringPtr filePath, + XMP_IO* fileRef, + XMPFiles * parent ); + +static const XMP_OptionBits kASF_HandlerFlags = ( kXMPFiles_CanInjectXMP | + kXMPFiles_CanExpand | + kXMPFiles_PrefersInPlace | + kXMPFiles_CanReconcile | + kXMPFiles_AllowsOnlyXMP | + kXMPFiles_ReturnsRawPacket | + kXMPFiles_NeedsReadOnlyPacket | + kXMPFiles_CanNotifyProgress ); + +class ASF_MetaHandler : public XMPFileHandler +{ +public: + + void CacheFileData(); + void ProcessXMP(); + + void UpdateFile ( bool doSafeUpdate ); + void WriteTempFile ( XMP_IO* tempRef ); + + bool SafeWriteFile (); + + ASF_MetaHandler ( XMPFiles* parent ); + virtual ~ASF_MetaHandler(); + +private: + + ASF_LegacyManager legacyManager; + +}; // ASF_MetaHandler + +// ================================================================================================= + +#endif /* __ASF_Handler_hpp__ */ diff --git a/core/libs/dngwriter/extra/xmp_sdk/XMPFiles/source/FileHandlers/AVCHD_Handler.cpp b/core/libs/dngwriter/extra/xmp_sdk/XMPFiles/source/FileHandlers/AVCHD_Handler.cpp new file mode 100644 index 0000000000..8c37196e7c --- /dev/null +++ b/core/libs/dngwriter/extra/xmp_sdk/XMPFiles/source/FileHandlers/AVCHD_Handler.cpp @@ -0,0 +1,2424 @@ +// ================================================================================================= +// ADOBE SYSTEMS INCORPORATED +// Copyright 2008 Adobe Systems Incorporated +// All Rights Reserved +// +// NOTICE: Adobe permits you to use, modify, and distribute this file in accordance with the terms +// of the Adobe license agreement accompanying it. +// ================================================================================================= + +#include "public/include/XMP_Environment.h" // ! XMP_Environment.h must be the first included header. + +#include "public/include/XMP_Const.h" +#include "public/include/XMP_IO.hpp" + +#include "XMPFiles/source/XMPFiles_Impl.hpp" +#include "source/XMPFiles_IO.hpp" +#include "source/XIO.hpp" +#include "source/IOUtils.hpp" + +#include "XMPFiles/source/FileHandlers/AVCHD_Handler.hpp" +#include "XMPFiles/source/FormatSupport/PackageFormat_Support.hpp" + +#include "source/UnicodeConversions.hpp" +#include "XMP_MD5.h" + +using namespace std; + +// AVCHD maker ID values. Panasonic has confirmed their Maker ID with us, the others come from examining +// sample data files. +#define kMakerIDPanasonic 0x103 +#define kMakerIDSony 0x108 +#define kMakerIDCanon 0x1011 + +// ================================================================================================= +/// \file AVCHD_Handler.cpp +/// \brief Folder format handler for AVCHD. +/// +/// This handler is for the AVCHD video format. +/// +/// A typical AVCHD layout looks like: +/// +/// BDMV/ +/// index.bdmv +/// MovieObject.bdmv +/// PLAYLIST/ +/// 00000.mpls +/// 00001.mpls +/// STREAM/ +/// 00000.m2ts +/// 00001.m2ts +/// CLIPINF/ +/// 00000.clpi +/// 00001.clpi +/// BACKUP/ +/// +// ================================================================================================= + +// ================================================================================================= + +// AVCHD Format. Book 1: Playback System Basic Specifications V 1.01. p. 76 + +struct AVCHD_blkProgramInfo +{ + XMP_Uns32 mLength; + XMP_Uns8 mReserved1[2]; + XMP_Uns32 mSPNProgramSequenceStart; + XMP_Uns16 mProgramMapPID; + XMP_Uns8 mNumberOfStreamsInPS; + XMP_Uns8 mReserved2; + + // Video stream. + struct + { + XMP_Uns8 mPresent; + XMP_Uns8 mVideoFormat; + XMP_Uns8 mFrameRate; + XMP_Uns8 mAspectRatio; + XMP_Uns8 mCCFlag; + } mVideoStream; + + // Audio stream. + struct + { + XMP_Uns8 mPresent; + XMP_Uns8 mAudioPresentationType; + XMP_Uns8 mSamplingFrequency; + XMP_Uns8 mAudioLanguageCode[4]; + } mAudioStream; + + // Pverlay bitmap stream. + struct + { + XMP_Uns8 mPresent; + XMP_Uns8 mOBLanguageCode[4]; + } mOverlayBitmapStream; + + // Menu bitmap stream. + struct + { + XMP_Uns8 mPresent; + XMP_Uns8 mBMLanguageCode[4]; + } mMenuBitmapStream; + +}; + +// AVCHD Format, Panasonic proprietary PRO_PlayListMark block + +struct AVCCAM_blkProPlayListMark +{ + XMP_Uns8 mPresent; + XMP_Uns8 mProTagID; + XMP_Uns8 mFillItem1; + XMP_Uns16 mLength; + XMP_Uns8 mMarkType; + + // Entry mark + struct + { + XMP_Uns8 mGlobalClipID[32]; + XMP_Uns8 mStartTimeCode[4]; + XMP_Uns8 mStreamTimecodeInfo; + XMP_Uns8 mStartBinaryGroup[4]; + XMP_Uns8 mLastUpdateTimeZone; + XMP_Uns8 mLastUpdateDate[7]; + XMP_Uns16 mFillItem; + } mEntryMark; + + // Shot Mark + struct + { + XMP_Uns8 mPresent; + XMP_Uns8 mShotMark; + XMP_Uns8 mFillItem[3]; + } mShotMark; + + // Access + struct + { + XMP_Uns8 mPresent; + XMP_Uns8 mCreatorCharacterSet; + XMP_Uns8 mCreatorLength; + XMP_Uns8 mCreator[32]; + XMP_Uns8 mLastUpdatePersonCharacterSet; + XMP_Uns8 mLastUpdatePersonLength; + XMP_Uns8 mLastUpdatePerson[32]; + } mAccess; + + // Device + struct + { + XMP_Uns8 mPresent; + XMP_Uns16 mMakerID; + XMP_Uns16 mMakerModelCode; + XMP_Uns8 mSerialNoCharacterCode; + XMP_Uns8 mSerialNoLength; + XMP_Uns8 mSerialNo[24]; + XMP_Uns16 mFillItem; + } mDevice; + + // Shoot + struct + { + XMP_Uns8 mPresent; + XMP_Uns8 mShooterCharacterSet; + XMP_Uns8 mShooterLength; + XMP_Uns8 mShooter[32]; + XMP_Uns8 mStartDateTimeZone; + XMP_Uns8 mStartDate[7]; + XMP_Uns8 mEndDateTimeZone; + XMP_Uns8 mEndDate[7]; + XMP_Uns16 mFillItem; + } mShoot; + + // Location + struct + { + XMP_Uns8 mPresent; + XMP_Uns8 mSource; + XMP_Uns32 mGPSLatitudeRef; + XMP_Uns32 mGPSLatitude1; + XMP_Uns32 mGPSLatitude2; + XMP_Uns32 mGPSLatitude3; + XMP_Uns32 mGPSLongitudeRef; + XMP_Uns32 mGPSLongitude1; + XMP_Uns32 mGPSLongitude2; + XMP_Uns32 mGPSLongitude3; + XMP_Uns32 mGPSAltitudeRef; + XMP_Uns32 mGPSAltitude; + XMP_Uns8 mPlaceNameCharacterSet; + XMP_Uns8 mPlaceNameLength; + XMP_Uns8 mPlaceName[64]; + XMP_Uns8 mFillItem; + } mLocation; +}; + +// AVCHD Format, Panasonic proprietary extension data (AVCCAM) + +struct AVCCAM_Pro_PlayListInfo +{ + XMP_Uns8 mPresent; + XMP_Uns8 mTagID; + XMP_Uns8 mTagVersion; + XMP_Uns16 mFillItem1; + XMP_Uns32 mLength; + XMP_Uns16 mNumberOfPlayListMarks; + XMP_Uns16 mFillItem2; + + // Although a playlist may contain multiple marks, we only store the one that corresponds to + // the clip/shot of interest. + AVCCAM_blkProPlayListMark mPlayListMark; +}; + +// AVCHD Format, Panasonic proprietary extension data (AVCCAM) + +struct AVCHD_blkPanasonicPrivateData +{ + XMP_Uns8 mPresent; + XMP_Uns16 mNumberOfData; + XMP_Uns16 mReserved; + + struct + { + XMP_Uns8 mPresent; + XMP_Uns8 mTagID; + XMP_Uns8 mTagVersion; + XMP_Uns16 mTagLength; + XMP_Uns8 mProfessionalMetaID[16]; + } mProMetaIDBlock; + + struct + { + XMP_Uns8 mPresent; + XMP_Uns8 mTagID; + XMP_Uns8 mTagVersion; + XMP_Uns16 mTagLength; + XMP_Uns8 mGlobalClipID[32]; + XMP_Uns8 mStartTimecode[4]; + XMP_Uns32 mStartBinaryGroup; + } mProClipIDBlock; + + AVCCAM_Pro_PlayListInfo mProPlaylistInfoBlock; +}; + +// AVCHD Format. Book 2: Recording Extension Specifications, section 4.2.4.2. plus Panasonic extensions + +struct AVCHD_blkMakersPrivateData +{ + XMP_Uns8 mPresent; + XMP_Uns32 mLength; + XMP_Uns32 mDataBlockStartAddress; + XMP_Uns8 mReserved[3]; + XMP_Uns8 mNumberOfMakerEntries; + XMP_Uns16 mMakerID; + XMP_Uns16 mMakerModelCode; + AVCHD_blkPanasonicPrivateData mPanasonicPrivateData; +}; + +// AVCHD Format. Book 2: Recording Extension Specifications, section 4.4.2.1 + +struct AVCHD_blkClipInfoExt +{ + XMP_Uns32 mLength; + XMP_Uns16 mMakerID; + XMP_Uns16 mMakerModelCode; +}; + +// AVCHD Format. Book 2: Recording Extension Specifications, section 4.4.1.2 + +struct AVCHD_blkClipExtensionData +{ + XMP_Uns8 mPresent; + XMP_Uns8 mTypeIndicator[4]; + XMP_Uns8 mReserved1[4]; + XMP_Uns32 mProgramInfoExtStartAddress; + XMP_Uns32 mMakersPrivateDataStartAddress; + + AVCHD_blkClipInfoExt mClipInfoExt; + AVCHD_blkMakersPrivateData mMakersPrivateData; +}; + +// AVCHD Format. Book 2: Recording Extension Specifications, section 4.3.3.1 -- although each playlist +// may contain a list of these, we only record the one that matches our target shot/clip. + +struct AVCHD_blkPlayListMarkExt +{ + XMP_Uns32 mLength; + XMP_Uns16 mNumberOfPlaylistMarks; + bool mPresent; + XMP_Uns16 mMakerID; + XMP_Uns16 mMakerModelCode; + XMP_Uns8 mReserved1[3]; + XMP_Uns8 mFlags; // bit 0: MarkWriteProtectFlag, bits 1-2: pulldown + XMP_Uns16 mRefToMarkThumbnailIndex; + XMP_Uns8 mBlkTimezone; + XMP_Uns8 mRecordDataAndTime[7]; + XMP_Uns8 mMarkCharacterSet; + XMP_Uns8 mMarkNameLength; + XMP_Uns8 mMarkName[24]; + XMP_Uns8 mMakersInformation[16]; + XMP_Uns8 mBlkTimecode[4]; + XMP_Uns16 mReserved2; +}; + +// AVCHD Format. Book 2: Recording Extension Specifications, section 4.3.2.1 + +struct AVCHD_blkPlaylistMeta +{ + XMP_Uns32 mLength; + XMP_Uns16 mMakerID; + XMP_Uns16 mMakerModelCode; + XMP_Uns32 mReserved1; + XMP_Uns16 mRefToMenuThumbnailIndex; + XMP_Uns8 mBlkTimezone; + XMP_Uns8 mRecordDataAndTime[7]; + XMP_Uns8 mReserved2; + XMP_Uns8 mPlaylistCharacterSet; + XMP_Uns8 mPlaylistNameLength; + XMP_Uns8 mPlaylistName[255]; +}; + +// AVCHD Format. Book 2: Recording Extension Specifications, section 4.3.1.2 + +struct AVCHD_blkPlayListExtensionData +{ + XMP_Uns8 mPresent; + char mTypeIndicator[4]; + XMP_Uns8 mReserved[4]; + XMP_Uns32 mPlayListMarkExtStartAddress; + XMP_Uns32 mMakersPrivateDataStartAddress; + + AVCHD_blkPlaylistMeta mPlaylistMeta; + AVCHD_blkPlayListMarkExt mPlaylistMarkExt; + AVCHD_blkMakersPrivateData mMakersPrivateData; +}; + +// AVCHD Format. Book 1: Playback System Basic Specifications V 1.01. p. 38 +struct AVCHD_blkExtensionData +{ + XMP_Uns32 mLength; + XMP_Uns32 mDataBlockStartAddress; + XMP_Uns8 mReserved[3]; + XMP_Uns8 mNumberOfDataEntries; + + struct AVCHD_blkExtDataEntry + { + XMP_Uns16 mExtDataType; + XMP_Uns16 mExtDataVersion; + XMP_Uns32 mExtDataStartAddress; + XMP_Uns32 mExtDataLength; + } mExtDataEntry; +}; + +// Simple container for the various AVCHD legacy metadata structures we care about for an AVCHD clip + +struct AVCHD_LegacyMetadata +{ + AVCHD_blkProgramInfo mProgramInfo; + AVCHD_blkClipExtensionData mClipExtensionData; + AVCHD_blkPlayListExtensionData mPlaylistExtensionData; +}; + +// ================================================================================================= +// MakeLeafPath +// ============ + +static bool MakeLeafPath ( std::string * path, XMP_StringPtr root, XMP_StringPtr group, + XMP_StringPtr clip, XMP_StringPtr suffix, bool checkFile = false ) +{ + size_t partialLen; + + *path = root; + *path += kDirChar; + *path += "BDMV"; + *path += kDirChar; + *path += group; + *path += kDirChar; + *path += clip; + partialLen = path->size(); + *path += suffix; + + if ( ! checkFile ) return true; + if ( Host_IO::GetFileMode ( path->c_str() ) == Host_IO::kFMode_IsFile ) return true; + + // Convert the suffix to uppercase and try again. Even on Mac/Win, in case a remote file system is sensitive. + for ( char* chPtr = ((char*)path->c_str() + partialLen); *chPtr != 0; ++chPtr ) { + if ( (0x61 <= *chPtr) && (*chPtr <= 0x7A) ) *chPtr -= 0x20; + } + if ( Host_IO::GetFileMode ( path->c_str() ) == Host_IO::kFMode_IsFile ) return true; + + if ( XMP_LitMatch ( suffix, ".clpi" ) ) { // Special case of ".cpi" for the clip file. + + path->erase ( partialLen ); + *path += ".cpi"; + if ( Host_IO::GetFileMode ( path->c_str() ) == Host_IO::kFMode_IsFile ) return true; + + path->erase ( partialLen ); + *path += ".CPI"; + if ( Host_IO::GetFileMode ( path->c_str() ) == Host_IO::kFMode_IsFile ) return true; + + } else if ( XMP_LitMatch ( suffix, ".mpls" ) ) { // Special case of ".mpl" for the playlist file. + + path->erase ( partialLen ); + *path += ".mpl"; + if ( Host_IO::GetFileMode ( path->c_str() ) == Host_IO::kFMode_IsFile ) return true; + + path->erase ( partialLen ); + *path += ".MPL"; + if ( Host_IO::GetFileMode ( path->c_str() ) == Host_IO::kFMode_IsFile ) return true; + + } else if ( XMP_LitMatch ( suffix, ".m2ts" ) ) { // Special case of ".mts" for the stream file. + + path->erase ( partialLen ); + *path += ".mts"; + if ( Host_IO::GetFileMode ( path->c_str() ) == Host_IO::kFMode_IsFile ) return true; + + path->erase ( partialLen ); + *path += ".MTS"; + if ( Host_IO::GetFileMode ( path->c_str() ) == Host_IO::kFMode_IsFile ) return true; + + } + + // Still not found, revert to the original suffix. + path->erase ( partialLen ); + *path += suffix; + return false; + +} // MakeLeafPath + +// ================================================================================================= +// AVCHD_CheckFormat +// ================= +// +// This version checks for the presence of a top level BPAV directory, and the required files and +// directories immediately within it. The CLIPINF, PLAYLIST, and STREAM subfolders are required, as +// are the index.bdmv and MovieObject.bdmv files. +// +// The state of the string parameters depends on the form of the path passed by the client. If the +// client passed a logical clip path, like ".../MyMovie/00001", the parameters are: +// rootPath - ".../MyMovie" +// gpName - empty +// parentName - empty +// leafName - "00001" +// If the client passed a full file path, like ".../MyMovie/BDMV/CLIPINF/00001.clpi", they are: +// rootPath - ".../MyMovie" +// gpName - "BDMV" +// parentName - "CLIPINF" or "PALYLIST" or "STREAM" +// leafName - "00001" + +// ! The common code has shifted the gpName, parentName, and leafName strings to upper case. It has +// ! also made sure that for a logical clip path the rootPath is an existing folder, and that the +// ! file exists for a full file path. + +// ! Using explicit '/' as a separator when creating paths, it works on Windows. + +// ! Sample files show that the ".bdmv" extension can sometimes be ".bdm". Allow either. + +bool AVCHD_CheckFormat ( XMP_FileFormat format, + const std::string & rootPath, + const std::string & gpName, + const std::string & parentName, + const std::string & leafName, + XMPFiles * parent ) +{ + if ( gpName.empty() != parentName.empty() ) return false; // Must be both empty or both non-empty. + + if ( ! gpName.empty() ) { + if ( gpName != "BDMV" ) return false; + if ( (parentName != "CLIPINF") && (parentName != "PLAYLIST") && (parentName != "STREAM") ) return false; + } + + // Check the rest of the required general structure. Look for both ".bdmv" and ".bmd" extensions. + + std::string bdmvPath ( rootPath ); + bdmvPath += kDirChar; + bdmvPath += "BDMV"; + + if ( Host_IO::GetChildMode ( bdmvPath.c_str(), "CLIPINF" ) != Host_IO::kFMode_IsFolder ) return false; + if ( Host_IO::GetChildMode ( bdmvPath.c_str(), "PLAYLIST" ) != Host_IO::kFMode_IsFolder ) return false; + if ( Host_IO::GetChildMode ( bdmvPath.c_str(), "STREAM" ) != Host_IO::kFMode_IsFolder ) return false; + + if ( (Host_IO::GetChildMode ( bdmvPath.c_str(), "index.bdmv" ) != Host_IO::kFMode_IsFile) && + (Host_IO::GetChildMode ( bdmvPath.c_str(), "index.bdm" ) != Host_IO::kFMode_IsFile) && + (Host_IO::GetChildMode ( bdmvPath.c_str(), "INDEX.BDMV" ) != Host_IO::kFMode_IsFile) && // Some usage is all caps. + (Host_IO::GetChildMode ( bdmvPath.c_str(), "INDEX.BDM" ) != Host_IO::kFMode_IsFile) ) return false; + + if ( (Host_IO::GetChildMode ( bdmvPath.c_str(), "MovieObject.bdmv" ) != Host_IO::kFMode_IsFile) && + (Host_IO::GetChildMode ( bdmvPath.c_str(), "MovieObj.bdm" ) != Host_IO::kFMode_IsFile) && + (Host_IO::GetChildMode ( bdmvPath.c_str(), "MOVIEOBJECT.BDMV" ) != Host_IO::kFMode_IsFile) && // Some usage is all caps. + (Host_IO::GetChildMode ( bdmvPath.c_str(), "MOVIEOBJ.BDM" ) != Host_IO::kFMode_IsFile) ) return false; + + + // Make sure the .clpi file exists. + std::string tempPath; + bool foundClpi = MakeLeafPath ( &tempPath, rootPath.c_str(), "CLIPINF", leafName.c_str(), ".clpi", true /* checkFile */ ); + if ( ! foundClpi ) return false; + + // And now save the pseudo path for the handler object. + tempPath = rootPath; + tempPath += kDirChar; + tempPath += leafName; + size_t pathLen = tempPath.size() + 1; // Include a terminating nul. + parent->tempPtr = malloc ( pathLen ); + if ( parent->tempPtr == 0 ) XMP_Throw ( "No memory for AVCHD clip info", kXMPErr_NoMemory ); + memcpy ( parent->tempPtr, tempPath.c_str(), pathLen ); + + return true; + +} // AVCHD_CheckFormat + +// ================================================================================================= + +static void* CreatePseudoClipPath ( const std::string & clientPath ) { + + // Used to create the clip pseudo path when the CheckFormat function is skipped. + + std::string pseudoPath = clientPath; + + size_t pathLen; + void* tempPtr = 0; + + if ( Host_IO::Exists ( pseudoPath.c_str() ) ) { + + // The client passed a physical path. The logical clip name is the leaf name, with the + // extension removed. There are no extra suffixes on AVCHD files. The movie root path ends + // two levels up. + + std::string clipName, ignored; + + XIO::SplitLeafName ( &pseudoPath, &clipName ); // Extract the logical clip name. + XIO::SplitFileExtension ( &clipName, &ignored ); + + XIO::SplitLeafName ( &pseudoPath, &ignored ); // Remove the 2 intermediate folder levels. + XIO::SplitLeafName ( &pseudoPath, &ignored ); + + pseudoPath += kDirChar; + pseudoPath += clipName; + + } + + pathLen = pseudoPath.size() + 1; // Include a terminating nul. + tempPtr = malloc ( pathLen ); + if ( tempPtr == 0 ) XMP_Throw ( "No memory for AVCHD clip info", kXMPErr_NoMemory ); + memcpy ( tempPtr, pseudoPath.c_str(), pathLen ); + + return tempPtr; + +} // CreatePseudoClipPath + +// ================================================================================================= +// ReadAVCHDProgramInfo +// ==================== + +static bool ReadAVCHDProgramInfo ( XMPFiles_IO & cpiFile, AVCHD_blkProgramInfo& avchdProgramInfo ) +{ + avchdProgramInfo.mLength = XIO::ReadUns32_BE ( &cpiFile ); + cpiFile.ReadAll ( avchdProgramInfo.mReserved1, 2 ); + avchdProgramInfo.mSPNProgramSequenceStart = XIO::ReadUns32_BE ( &cpiFile ); + avchdProgramInfo.mProgramMapPID = XIO::ReadUns16_BE ( &cpiFile ); + cpiFile.ReadAll ( &avchdProgramInfo.mNumberOfStreamsInPS, 1 ); + cpiFile.ReadAll ( &avchdProgramInfo.mReserved2, 1 ); + + XMP_Uns16 streamPID = 0; + for ( int i=0; i> 4; // hi 4 bits + avchdProgramInfo.mVideoStream.mFrameRate = videoFormatAndFrameRate & 0x0f; // lo 4 bits + + XMP_Uns8 aspectRatioAndReserved = 0; + cpiFile.ReadAll ( &aspectRatioAndReserved, 1 ); + avchdProgramInfo.mVideoStream.mAspectRatio = aspectRatioAndReserved >> 4; // hi 4 bits + + XMP_Uns8 ccFlag = 0; + cpiFile.ReadAll ( &ccFlag, 1 ); + avchdProgramInfo.mVideoStream.mCCFlag = ccFlag; + + avchdProgramInfo.mVideoStream.mPresent = 1; + } + break; + + case 0x80 : // Fall through. + case 0x81 : // Audio stream case. + { + XMP_Uns8 audioPresentationTypeAndFrequency = 0; + cpiFile.ReadAll ( &audioPresentationTypeAndFrequency, 1 ); + + avchdProgramInfo.mAudioStream.mAudioPresentationType = audioPresentationTypeAndFrequency >> 4; // hi 4 bits + avchdProgramInfo.mAudioStream.mSamplingFrequency = audioPresentationTypeAndFrequency & 0x0f; // lo 4 bits + + cpiFile.ReadAll ( avchdProgramInfo.mAudioStream.mAudioLanguageCode, 3 ); + avchdProgramInfo.mAudioStream.mAudioLanguageCode[3] = 0; + + avchdProgramInfo.mAudioStream.mPresent = 1; + } + break; + + case 0x90 : // Overlay bitmap stream case. + cpiFile.ReadAll ( &avchdProgramInfo.mOverlayBitmapStream.mOBLanguageCode, 3 ); + avchdProgramInfo.mOverlayBitmapStream.mOBLanguageCode[3] = 0; + avchdProgramInfo.mOverlayBitmapStream.mPresent = 1; + break; + + case 0x91 : // Menu bitmap stream. + cpiFile.ReadAll ( &avchdProgramInfo.mMenuBitmapStream.mBMLanguageCode, 3 ); + avchdProgramInfo.mMenuBitmapStream.mBMLanguageCode[3] = 0; + avchdProgramInfo.mMenuBitmapStream.mPresent = 1; + break; + + default : + break; + + } + + cpiFile.Seek ( pos + length, kXMP_SeekFromStart ); + + } + + return true; +} + +// ================================================================================================= +// ReadAVCHDExtensionData +// ====================== + +static bool ReadAVCHDExtensionData ( XMPFiles_IO & cpiFile, AVCHD_blkExtensionData& extensionDataHeader ) +{ + extensionDataHeader.mLength = XIO::ReadUns32_BE ( &cpiFile ); + + if ( extensionDataHeader.mLength == 0 ) { + // Nothing to read + return true; + } + + extensionDataHeader.mDataBlockStartAddress = XIO::ReadUns32_BE ( &cpiFile ); + cpiFile.ReadAll ( extensionDataHeader.mReserved, 3 ); + cpiFile.ReadAll ( &extensionDataHeader.mNumberOfDataEntries, 1 ); + + if ( extensionDataHeader.mNumberOfDataEntries != 1 ) { + // According to AVCHD Format. Book1. v. 1.01. p 38, "This field shall be set to 1 in this format." + return false; + } + + extensionDataHeader.mExtDataEntry.mExtDataType = XIO::ReadUns16_BE ( &cpiFile ); + extensionDataHeader.mExtDataEntry.mExtDataVersion = XIO::ReadUns16_BE ( &cpiFile ); + extensionDataHeader.mExtDataEntry.mExtDataStartAddress = XIO::ReadUns32_BE ( &cpiFile ); + extensionDataHeader.mExtDataEntry.mExtDataLength = XIO::ReadUns32_BE ( &cpiFile ); + + if ( extensionDataHeader.mExtDataEntry.mExtDataType != 0x1000 ) { + // According to AVCHD Format. Book1. v. 1.01. p 38, "If the metadata is for an AVCHD application, + // this value shall be set to 'Ox1OOO'." + return false; + } + + return true; +} + +// ================================================================================================= +// ReadAVCCAMProMetaID +// =================== +// +// Read Panasonic's proprietary PRO_MetaID block + +static bool ReadAVCCAMProMetaID ( XMPFiles_IO & cpiFile, XMP_Uns8 tagID, AVCHD_blkPanasonicPrivateData& extensionDataHeader ) +{ + extensionDataHeader.mPresent = 1; + extensionDataHeader.mProMetaIDBlock.mPresent = 1; + extensionDataHeader.mProMetaIDBlock.mTagID = tagID; + cpiFile.ReadAll ( &extensionDataHeader.mProMetaIDBlock.mTagVersion, 1); + extensionDataHeader.mProMetaIDBlock.mTagLength = XIO::ReadUns16_BE ( &cpiFile ); + cpiFile.ReadAll ( &extensionDataHeader.mProMetaIDBlock.mProfessionalMetaID, 16); + + return true; +} + +// ================================================================================================= +// ReadAVCCAMProClipInfo +// ===================== +// +// Read Panasonic's proprietary PRO_ClipInfo block. + +static bool ReadAVCCAMProClipInfo ( XMPFiles_IO & cpiFile, XMP_Uns8 tagID, AVCHD_blkPanasonicPrivateData& extensionDataHeader ) +{ + extensionDataHeader.mPresent = 1; + extensionDataHeader.mProClipIDBlock.mPresent = 1; + extensionDataHeader.mProClipIDBlock.mTagID = tagID; + cpiFile.ReadAll ( &extensionDataHeader.mProClipIDBlock.mTagVersion, 1); + extensionDataHeader.mProClipIDBlock.mTagLength = XIO::ReadUns16_BE ( &cpiFile ); + cpiFile.ReadAll ( &extensionDataHeader.mProClipIDBlock.mGlobalClipID, 32); + cpiFile.ReadAll ( &extensionDataHeader.mProClipIDBlock.mStartTimecode, 4 ); + extensionDataHeader.mProClipIDBlock.mStartBinaryGroup = XIO::ReadUns32_BE ( &cpiFile ); + + return true; +} + +// ================================================================================================= +// ReadAVCCAM_blkPRO_ShotMark +// ========================== +// +// Read Panasonic's proprietary PRO_ShotMark block. + +static bool ReadAVCCAM_blkPRO_ShotMark ( XMPFiles_IO & mplFile, AVCCAM_blkProPlayListMark& proMark ) +{ + proMark.mShotMark.mPresent = 1; + mplFile.ReadAll ( &proMark.mShotMark.mShotMark, 1); + mplFile.ReadAll ( &proMark.mShotMark.mFillItem, 3); + + return true; +} + +// ================================================================================================= +// ReadAVCCAM_blkPRO_Access +// ======================== +// +// Read Panasonic's proprietary PRO_Access block. + +static bool ReadAVCCAM_blkPRO_Access ( XMPFiles_IO & mplFile, AVCCAM_blkProPlayListMark& proMark ) +{ + proMark.mAccess.mPresent = 1; + mplFile.ReadAll ( &proMark.mAccess.mCreatorCharacterSet, 1 ); + mplFile.ReadAll ( &proMark.mAccess.mCreatorLength, 1 ); + mplFile.ReadAll ( &proMark.mAccess.mCreator, 32 ); + mplFile.ReadAll ( &proMark.mAccess.mLastUpdatePersonCharacterSet, 1 ); + mplFile.ReadAll ( &proMark.mAccess.mLastUpdatePersonLength, 1 ); + mplFile.ReadAll ( &proMark.mAccess.mLastUpdatePerson, 32 ); + + return true; +} + +// ================================================================================================= +// ReadAVCCAM_blkPRO_Device +// ======================== +// +// Read Panasonic's proprietary PRO_Device block. + +static bool ReadAVCCAM_blkPRO_Device ( XMPFiles_IO & mplFile, AVCCAM_blkProPlayListMark& proMark ) +{ + proMark.mDevice.mPresent = 1; + proMark.mDevice.mMakerID = XIO::ReadUns16_BE ( &mplFile ); + proMark.mDevice.mMakerModelCode = XIO::ReadUns16_BE ( &mplFile ); + mplFile.ReadAll ( &proMark.mDevice.mSerialNoCharacterCode, 1 ); + mplFile.ReadAll ( &proMark.mDevice.mSerialNoLength, 1 ); + mplFile.ReadAll ( &proMark.mDevice.mSerialNo, 24 ); + mplFile.ReadAll ( &proMark.mDevice.mFillItem, 2 ); + + return true; +} + +// ================================================================================================= +// ReadAVCCAM_blkPRO_Shoot +// ======================= +// +// Read Panasonic's proprietary PRO_Shoot block. + +static bool ReadAVCCAM_blkPRO_Shoot ( XMPFiles_IO & mplFile, AVCCAM_blkProPlayListMark& proMark ) +{ + proMark.mShoot.mPresent = 1; + mplFile.ReadAll ( &proMark.mShoot.mShooterCharacterSet, 1 ); + mplFile.ReadAll ( &proMark.mShoot.mShooterLength, 1 ); + mplFile.ReadAll ( &proMark.mShoot.mShooter, 32 ); + mplFile.ReadAll ( &proMark.mShoot.mStartDateTimeZone, 1 ); + mplFile.ReadAll ( &proMark.mShoot.mStartDate, 7 ); + mplFile.ReadAll ( &proMark.mShoot.mEndDateTimeZone, 1 ); + mplFile.ReadAll ( &proMark.mShoot.mEndDate, 7 ); + mplFile.ReadAll ( &proMark.mShoot.mFillItem, 2 ); + + return true; +} + +// ================================================================================================= +// ReadAVCCAM_blkPRO_Location +// ========================== +// +// Read Panasonic's proprietary PRO_Location block. + +static bool ReadAVCCAM_blkPRO_Location ( XMPFiles_IO & mplFile, AVCCAM_blkProPlayListMark& proMark ) +{ + proMark.mLocation.mPresent = 1; + mplFile.ReadAll ( &proMark.mLocation.mSource, 1 ); + proMark.mLocation.mGPSLatitudeRef = XIO::ReadUns32_BE ( &mplFile ); + proMark.mLocation.mGPSLatitude1 = XIO::ReadUns32_BE ( &mplFile ); + proMark.mLocation.mGPSLatitude2 = XIO::ReadUns32_BE ( &mplFile ); + proMark.mLocation.mGPSLatitude3 = XIO::ReadUns32_BE ( &mplFile ); + proMark.mLocation.mGPSLongitudeRef = XIO::ReadUns32_BE ( &mplFile ); + proMark.mLocation.mGPSLongitude1 = XIO::ReadUns32_BE ( &mplFile ); + proMark.mLocation.mGPSLongitude2 = XIO::ReadUns32_BE ( &mplFile ); + proMark.mLocation.mGPSLongitude3 = XIO::ReadUns32_BE ( &mplFile ); + proMark.mLocation.mGPSAltitudeRef = XIO::ReadUns32_BE ( &mplFile ); + proMark.mLocation.mGPSAltitude = XIO::ReadUns32_BE ( &mplFile ); + mplFile.ReadAll ( &proMark.mLocation.mPlaceNameCharacterSet, 1 ); + mplFile.ReadAll ( &proMark.mLocation.mPlaceNameLength, 1 ); + mplFile.ReadAll ( &proMark.mLocation.mPlaceName, 64 ); + mplFile.ReadAll ( &proMark.mLocation.mFillItem, 1 ); + + return true; +} + +// ================================================================================================= +// ReadAVCCAMProPlaylistInfo +// ========================= +// +// Read Panasonic's proprietary PRO_PlayListInfo block. + +static bool ReadAVCCAMProPlaylistInfo ( XMPFiles_IO & mplFile, + XMP_Uns8 tagID, + XMP_Uns16 playlistMarkID, + AVCHD_blkPanasonicPrivateData& extensionDataHeader ) +{ + AVCCAM_Pro_PlayListInfo& playlistBlock = extensionDataHeader.mProPlaylistInfoBlock; + + playlistBlock.mTagID = tagID; + mplFile.ReadAll ( &playlistBlock.mTagVersion, 1); + mplFile.ReadAll ( &playlistBlock.mFillItem1, 2); + playlistBlock.mLength = XIO::ReadUns32_BE ( &mplFile ); + playlistBlock.mNumberOfPlayListMarks = XIO::ReadUns16_BE ( &mplFile ); + mplFile.ReadAll ( &playlistBlock.mFillItem2, 2); + + if ( playlistBlock.mNumberOfPlayListMarks == 0 ) return true; + + extensionDataHeader.mPresent = 1; + + XMP_Uns64 blockStart = 0; + + for ( int i = 0; i < playlistBlock.mNumberOfPlayListMarks; ++i ) { + AVCCAM_blkProPlayListMark& currMark = playlistBlock.mPlayListMark; + + mplFile.ReadAll ( &currMark.mProTagID, 1); + mplFile.ReadAll ( &currMark.mFillItem1, 1); + currMark.mLength = XIO::ReadUns16_BE ( &mplFile ); + blockStart = mplFile.Offset(); + mplFile.ReadAll ( &currMark.mMarkType, 1 ); + + if ( ( currMark.mProTagID == 0x40 ) && ( currMark.mMarkType == 0x01 ) ) { + mplFile.ReadAll ( &currMark.mEntryMark.mGlobalClipID, 32); + + // skip marks for different clips + if ( i == playlistMarkID ) { + playlistBlock.mPresent = 1; + currMark.mPresent = 1; + mplFile.ReadAll ( &currMark.mEntryMark.mStartTimeCode, 4); + mplFile.ReadAll ( &currMark.mEntryMark.mStreamTimecodeInfo, 1); + mplFile.ReadAll ( &currMark.mEntryMark.mStartBinaryGroup, 4); + mplFile.ReadAll ( &currMark.mEntryMark.mLastUpdateTimeZone, 1); + mplFile.ReadAll ( &currMark.mEntryMark.mLastUpdateDate, 7); + mplFile.ReadAll ( &currMark.mEntryMark.mFillItem, 2); + + XMP_Uns64 currPos = mplFile.Offset(); + XMP_Uns8 blockTag = 0; + XMP_Uns8 blockFill; + XMP_Uns16 blockLength = 0; + + while ( currPos < ( blockStart + currMark.mLength ) ) { + mplFile.ReadAll ( &blockTag, 1); + mplFile.ReadAll ( &blockFill, 1); + blockLength = XIO::ReadUns16_BE ( &mplFile ); + currPos += 4; + + switch ( blockTag ) { + case 0x20: + if ( ! ReadAVCCAM_blkPRO_ShotMark ( mplFile, currMark ) ) return false; + break; + + + case 0x21: + if ( ! ReadAVCCAM_blkPRO_Access ( mplFile, currMark ) ) return false; + break; + + case 0x22: + if ( ! ReadAVCCAM_blkPRO_Device ( mplFile, currMark ) ) return false; + break; + + case 0x23: + if ( ! ReadAVCCAM_blkPRO_Shoot ( mplFile, currMark ) ) return false; + break; + + case 0x24: + if (! ReadAVCCAM_blkPRO_Location ( mplFile, currMark ) ) return false; + break; + + default : break; + } + + currPos += blockLength; + mplFile.Seek ( currPos, kXMP_SeekFromStart ); + } + } + } + + mplFile.Seek ( blockStart + currMark.mLength, kXMP_SeekFromStart ); + } + + return true; +} + +// ================================================================================================= +// ReadAVCCAMMakersPrivateData +// =========================== +// +// Read Panasonic's implementation of an AVCCAM "Maker's Private Data" block. Panasonic calls their +// extensions "AVCCAM." + +static bool ReadAVCCAMMakersPrivateData ( XMPFiles_IO & fileRef, + XMP_Uns16 playlistMarkID, + AVCHD_blkPanasonicPrivateData& avccamPrivateData ) +{ + const XMP_Uns64 blockStart = fileRef.Offset(); + + avccamPrivateData.mNumberOfData = XIO::ReadUns16_BE ( &fileRef ); + fileRef.ReadAll ( &avccamPrivateData.mReserved, 2 ); + + for (int i = 0; i < avccamPrivateData.mNumberOfData; ++i) { + const XMP_Uns8 tagID = XIO::ReadUns8 ( &fileRef ); + + switch ( tagID ) { + case 0xe0: ReadAVCCAMProMetaID ( fileRef, tagID, avccamPrivateData ); + break; + case 0xe2: ReadAVCCAMProClipInfo( fileRef, tagID, avccamPrivateData ); + break; + case 0xf0: ReadAVCCAMProPlaylistInfo( fileRef, tagID, playlistMarkID, avccamPrivateData ); + break; + + default: + // Ignore any blocks we don't now or care about + break; + } + } + + return true; +} + +// ================================================================================================= +// ReadAVCHDMakersPrivateData +// ========================== +// +// AVCHD Format. Book 2: Recording Extension Specifications, section 4.2.4.2. + +static bool ReadAVCHDMakersPrivateData ( XMPFiles_IO & mplFile, + XMP_Uns16 playlistMarkID, + AVCHD_blkMakersPrivateData& avchdLegacyData ) +{ + const XMP_Uns64 blockStart = mplFile.Offset(); + + avchdLegacyData.mLength = XIO::ReadUns32_BE ( &mplFile ); + + if ( avchdLegacyData.mLength == 0 ) return false; + + avchdLegacyData.mPresent = 1; + avchdLegacyData.mDataBlockStartAddress = XIO::ReadUns32_BE ( &mplFile ); + mplFile.ReadAll ( &avchdLegacyData.mReserved, 3 ); + mplFile.ReadAll ( &avchdLegacyData.mNumberOfMakerEntries, 1 ); + + if ( avchdLegacyData.mNumberOfMakerEntries == 0 ) return true; + + XMP_Uns16 makerID; + XMP_Uns16 makerModelCode; + XMP_Uns32 mpdStartAddress; + XMP_Uns32 mpdLength; + + for ( int i = 0; i < avchdLegacyData.mNumberOfMakerEntries; ++i ) { + makerID = XIO::ReadUns16_BE ( &mplFile ); + makerModelCode = XIO::ReadUns16_BE ( &mplFile ); + mpdStartAddress = XIO::ReadUns32_BE ( &mplFile ); + mpdLength = XIO::ReadUns32_BE ( &mplFile ); + + // We only have documentation for Panasonic's Maker's Private Data blocks, so we'll ignore everyone else's + if ( makerID == kMakerIDPanasonic ) { + avchdLegacyData.mMakerID = makerID; + avchdLegacyData.mMakerModelCode = makerModelCode; + mplFile.Seek ( blockStart + mpdStartAddress, kXMP_SeekFromStart ); + + if (! ReadAVCCAMMakersPrivateData ( mplFile, playlistMarkID, avchdLegacyData.mPanasonicPrivateData ) ) return false; + } + } + + return true; +} + +// ================================================================================================= +// ReadAVCHDClipExtensionData +// ========================== + +static bool ReadAVCHDClipExtensionData ( XMPFiles_IO & cpiFile, AVCHD_blkClipExtensionData& avchdExtensionData ) +{ + const XMP_Int64 extensionBlockStart = cpiFile.Offset(); + AVCHD_blkExtensionData extensionDataHeader; + + if ( ! ReadAVCHDExtensionData ( cpiFile, extensionDataHeader ) ) { + return false; + } + + if ( extensionDataHeader.mLength == 0 ) { + return true; + } + + const XMP_Int64 dataBlockStart = extensionBlockStart + extensionDataHeader.mDataBlockStartAddress; + + cpiFile.Seek ( dataBlockStart, kXMP_SeekFromStart ); + cpiFile.ReadAll ( avchdExtensionData.mTypeIndicator, 4 ); + + if ( strncmp ( reinterpret_cast( avchdExtensionData.mTypeIndicator ), "CLEX", 4 ) != 0 ) return false; + + avchdExtensionData.mPresent = 1; + cpiFile.ReadAll ( avchdExtensionData.mReserved1, 4 ); + avchdExtensionData.mProgramInfoExtStartAddress = XIO::ReadUns32_BE ( &cpiFile ); + avchdExtensionData.mMakersPrivateDataStartAddress = XIO::ReadUns32_BE ( &cpiFile ); + + // read Clip info extension + cpiFile.Seek ( dataBlockStart + 40, kXMP_SeekFromStart ); + avchdExtensionData.mClipInfoExt.mLength = XIO::ReadUns32_BE ( &cpiFile ); + avchdExtensionData.mClipInfoExt.mMakerID = XIO::ReadUns16_BE ( &cpiFile ); + avchdExtensionData.mClipInfoExt.mMakerModelCode = XIO::ReadUns16_BE ( &cpiFile ); + + if ( avchdExtensionData.mMakersPrivateDataStartAddress == 0 ) return true; + + if ( avchdExtensionData.mClipInfoExt.mMakerID == kMakerIDPanasonic ) { + // Read Maker's Private Data block -- we only have Panasonic's definition for their AVCCAM models + // at this point, so we'll ignore the block if its from a different manufacturer. + cpiFile.Seek ( dataBlockStart + avchdExtensionData.mMakersPrivateDataStartAddress, kXMP_SeekFromStart ); + + if ( ! ReadAVCHDMakersPrivateData ( cpiFile, 0, avchdExtensionData.mMakersPrivateData ) ) { + return false; + } + } + + return true; +} + +// ================================================================================================= +// AVCHD_PlaylistContainsClip +// ========================== +// +// Returns true of the specified AVCHD playlist block references the specified clip, or false if not. + +static bool AVCHD_PlaylistContainsClip ( XMPFiles_IO & mplFile, XMP_Uns16& playItemID, const std::string& strClipName ) +{ + // Read clip header. ( AVCHD Format. Book1. v. 1.01. p 45 ) + struct AVCHD_blkPlayList + { + XMP_Uns32 mLength; + XMP_Uns16 mReserved; + XMP_Uns16 mNumberOfPlayItems; + XMP_Uns16 mNumberOfSubPaths; + }; + + AVCHD_blkPlayList blkPlayList; + blkPlayList.mLength = XIO::ReadUns32_BE ( &mplFile ); + mplFile.ReadAll ( &blkPlayList.mReserved, 2 ); + blkPlayList.mNumberOfPlayItems = XIO::ReadUns16_BE ( &mplFile ); + blkPlayList.mNumberOfSubPaths = XIO::ReadUns16_BE ( &mplFile ); + + // Search the play items. ( AVCHD Format. Book1. v. 1.01. p 47 ) + struct AVCHD_blkPlayItem + { + XMP_Uns16 mLength; + char mClipInformationFileName[5]; + // Note: remaining fields omitted because we don't care about them + }; + + AVCHD_blkPlayItem currPlayItem; + XMP_Uns64 blockStart = 0; + for ( playItemID = 0; playItemID < blkPlayList.mNumberOfPlayItems; ++playItemID ) { + currPlayItem.mLength = XIO::ReadUns16_BE ( &mplFile ); + + // mLength is measured from the end of mLength, not the start of the block ( AVCHD Format. Book1. v. 1.01. p 47 ) + blockStart = mplFile.Offset(); + mplFile.ReadAll ( currPlayItem.mClipInformationFileName, 5 ); + + if ( strncmp ( strClipName.c_str(), currPlayItem.mClipInformationFileName, 5 ) == 0 ) return true; + + mplFile.Seek ( blockStart + currPlayItem.mLength, kXMP_SeekFromStart ); + } + + return false; +} + +// ================================================================================================= +// ReadAVCHDPlaylistMetadataBlock +// ============================== + +static bool ReadAVCHDPlaylistMetadataBlock ( XMPFiles_IO & mplFile, + AVCHD_blkPlaylistMeta& avchdLegacyData ) +{ + avchdLegacyData.mLength = XIO::ReadUns32_BE ( &mplFile ); + + if ( avchdLegacyData.mLength < sizeof ( AVCHD_blkPlaylistMeta ) ) return false; + + avchdLegacyData.mMakerID = XIO::ReadUns16_BE ( &mplFile ); + avchdLegacyData.mMakerModelCode = XIO::ReadUns16_BE ( &mplFile ); + mplFile.ReadAll ( &avchdLegacyData.mReserved1, 4 ); + avchdLegacyData.mRefToMenuThumbnailIndex = XIO::ReadUns16_BE ( &mplFile ); + mplFile.ReadAll ( &avchdLegacyData.mBlkTimezone, 1 ); + mplFile.ReadAll ( &avchdLegacyData.mRecordDataAndTime, 7 ); + mplFile.ReadAll ( &avchdLegacyData.mReserved2, 1 ); + mplFile.ReadAll ( &avchdLegacyData.mPlaylistCharacterSet, 1 ); + mplFile.ReadAll ( &avchdLegacyData.mPlaylistNameLength, 1 ); + mplFile.ReadAll ( &avchdLegacyData.mPlaylistName, avchdLegacyData.mPlaylistNameLength ); + + return true; +} + +// ================================================================================================= +// ReadAVCHDPlaylistMarkExtension +// ============================== + +static bool ReadAVCHDPlaylistMarkExtension ( XMPFiles_IO & mplFile, + XMP_Uns16 playlistMarkID, + AVCHD_blkPlayListMarkExt& avchdLegacyData ) +{ + avchdLegacyData.mLength = XIO::ReadUns32_BE ( &mplFile ); + + if ( avchdLegacyData.mLength == 0 ) return false; + + avchdLegacyData.mNumberOfPlaylistMarks = XIO::ReadUns16_BE ( &mplFile ); + + if ( avchdLegacyData.mNumberOfPlaylistMarks <= playlistMarkID ) return true; + + // Number of bytes in blkMarkExtension, AVCHD Book 2, section 4.3.3.1 + const XMP_Uns64 markExtensionSize = 66; + + // Entries in the mark extension block correspond one-to-one with entries in + // blkPlaylistMark, so we'll only read the one that corresponds to the + // chosen clip. + const XMP_Uns64 markOffset = markExtensionSize * playlistMarkID; + + avchdLegacyData.mPresent = 1; + mplFile.Seek ( markOffset, kXMP_SeekFromCurrent ); + avchdLegacyData.mMakerID = XIO::ReadUns16_BE ( &mplFile ); + avchdLegacyData.mMakerModelCode = XIO::ReadUns16_BE ( &mplFile ); + mplFile.ReadAll ( &avchdLegacyData.mReserved1, 3 ); + mplFile.ReadAll ( &avchdLegacyData.mFlags, 1 ); + avchdLegacyData.mRefToMarkThumbnailIndex = XIO::ReadUns16_BE ( &mplFile ); + mplFile.ReadAll ( &avchdLegacyData.mBlkTimezone, 1 ); + mplFile.ReadAll ( &avchdLegacyData.mRecordDataAndTime, 7 ); + mplFile.ReadAll ( &avchdLegacyData.mMarkCharacterSet, 1 ); + mplFile.ReadAll ( &avchdLegacyData.mMarkNameLength, 1 ); + mplFile.ReadAll ( &avchdLegacyData.mMarkName, 24 ); + mplFile.ReadAll ( &avchdLegacyData.mMakersInformation, 16 ); + mplFile.ReadAll ( &avchdLegacyData.mBlkTimecode, 4 ); + mplFile.ReadAll ( &avchdLegacyData.mReserved2, 2 ); + + return true; +} + +// ================================================================================================= +// ReadAVCHDPlaylistMarkID +// ======================= +// +// Read the playlist mark block to find the ID of the playlist mark that matches the specified +// playlist item. + +static bool ReadAVCHDPlaylistMarkID ( XMPFiles_IO & mplFile, + XMP_Uns16 playItemID, + XMP_Uns16& markID ) +{ + XMP_Uns32 length = XIO::ReadUns32_BE ( &mplFile ); + XMP_Uns16 numberOfPlayListMarks = XIO::ReadUns16_BE ( &mplFile ); + + if ( length == 0 ) return false; + + XMP_Uns8 reserved; + XMP_Uns8 markType; + XMP_Uns16 refToPlayItemID; + + for ( int i = 0; i < numberOfPlayListMarks; ++i ) { + mplFile.ReadAll ( &reserved, 1 ); + mplFile.ReadAll ( &markType, 1 ); + refToPlayItemID = XIO::ReadUns16_BE ( &mplFile ); + + if ( ( markType == 0x01 ) && ( refToPlayItemID == playItemID ) ) { + markID = i; + return true; + } + + mplFile.Seek ( 10, kXMP_SeekFromCurrent ); + } + + return false; +} + +// ================================================================================================= +// ReadAVCHDPlaylistExtensionData +// ============================== + +static bool ReadAVCHDPlaylistExtensionData ( XMPFiles_IO & mplFile, + AVCHD_LegacyMetadata& avchdLegacyData, + XMP_Uns16 playlistMarkID ) +{ + const XMP_Int64 extensionBlockStart = mplFile.Offset(); + AVCHD_blkExtensionData extensionDataHeader; + + if ( ! ReadAVCHDExtensionData ( mplFile, extensionDataHeader ) ) { + return false; + } + + if ( extensionDataHeader.mLength == 0 ) { + return true; + } + + const XMP_Int64 dataBlockStart = extensionBlockStart + extensionDataHeader.mDataBlockStartAddress; + AVCHD_blkPlayListExtensionData& extensionData = avchdLegacyData.mPlaylistExtensionData; + const int reserved2Len = 24; + + mplFile.Seek ( dataBlockStart, kXMP_SeekFromStart ); + mplFile.ReadAll ( extensionData.mTypeIndicator, 4 ); + + if ( strncmp ( extensionData.mTypeIndicator, "PLEX", 4 ) != 0 ) return false; + + extensionData.mPresent = true; + mplFile.ReadAll ( extensionData.mReserved, 4 ); + extensionData.mPlayListMarkExtStartAddress = XIO::ReadUns32_BE ( &mplFile ); + extensionData.mMakersPrivateDataStartAddress = XIO::ReadUns32_BE ( &mplFile ); + mplFile.Seek ( reserved2Len, kXMP_SeekFromCurrent ); + + if ( ! ReadAVCHDPlaylistMetadataBlock ( mplFile, extensionData.mPlaylistMeta ) ) return false; + + mplFile.Seek ( dataBlockStart + extensionData.mPlayListMarkExtStartAddress, kXMP_SeekFromStart ); + + if ( ! ReadAVCHDPlaylistMarkExtension ( mplFile, playlistMarkID, extensionData.mPlaylistMarkExt ) ) return false; + + if ( extensionData.mMakersPrivateDataStartAddress > 0 ) { + + // return true here because all the data is already read successfully except the maker's private data and more + // specifically of panasonic. So if the relevant panasonic data is not present we just skip it. + // Assumption here is that if its not present in ClipExtension then it will not be in Playlist extension + if ( ! avchdLegacyData.mClipExtensionData.mMakersPrivateData.mPanasonicPrivateData.mPresent ) return true; + + mplFile.Seek ( dataBlockStart + extensionData.mMakersPrivateDataStartAddress, kXMP_SeekFromStart ); + + // Here private data was found.If the data was panasonic private data and we were unable to read it , + // Return false + if ( ! ReadAVCHDMakersPrivateData ( mplFile, playlistMarkID, extensionData.mMakersPrivateData ) ) return false; + + } + + return true; +} + +// ================================================================================================= +// ReadAVCHDLegacyClipFile +// ======================= +// +// Read the legacy metadata stored in an AVCHD .CPI file. + +static bool ReadAVCHDLegacyClipFile ( const std::string& strPath, AVCHD_LegacyMetadata& avchdLegacyData ) +{ + bool success = false; + + try { + + Host_IO::FileRef hostRef = Host_IO::Open ( strPath.c_str(), Host_IO::openReadOnly ); + if ( hostRef == Host_IO::noFileRef ) return false; // The open failed. + XMPFiles_IO cpiFile ( hostRef, strPath.c_str(), Host_IO::openReadOnly ); + + memset ( &avchdLegacyData, 0, sizeof(AVCHD_LegacyMetadata) ); + + // Read clip header. ( AVCHD Format. Book1. v. 1.01. p 64 ) + struct AVCHD_ClipInfoHeader + { + char mTypeIndicator[4]; + char mTypeIndicator2[4]; + XMP_Uns32 mSequenceInfoStartAddress; + XMP_Uns32 mProgramInfoStartAddress; + XMP_Uns32 mCPIStartAddress; + XMP_Uns32 mClipMarkStartAddress; + XMP_Uns32 mExtensionDataStartAddress; + XMP_Uns8 mReserved[12]; + }; + + // Read the AVCHD header. + AVCHD_ClipInfoHeader avchdHeader; + cpiFile.ReadAll ( avchdHeader.mTypeIndicator, 4 ); + cpiFile.ReadAll ( avchdHeader.mTypeIndicator2, 4 ); + + if ( strncmp ( avchdHeader.mTypeIndicator, "HDMV", 4 ) != 0 ) return false; + if ( strncmp ( avchdHeader.mTypeIndicator2, "0100", 4 ) != 0 ) return false; + + avchdHeader.mSequenceInfoStartAddress = XIO::ReadUns32_BE ( &cpiFile ); + avchdHeader.mProgramInfoStartAddress = XIO::ReadUns32_BE ( &cpiFile ); + avchdHeader.mCPIStartAddress = XIO::ReadUns32_BE ( &cpiFile ); + avchdHeader.mClipMarkStartAddress = XIO::ReadUns32_BE ( &cpiFile ); + avchdHeader.mExtensionDataStartAddress = XIO::ReadUns32_BE ( &cpiFile ); + cpiFile.ReadAll ( avchdHeader.mReserved, 12 ); + + // Seek to the program header. (AVCHD Format. Book1. v. 1.01. p 77 ) + cpiFile.Seek ( avchdHeader.mProgramInfoStartAddress, kXMP_SeekFromStart ); + + // Read the program info block + success = ReadAVCHDProgramInfo ( cpiFile, avchdLegacyData.mProgramInfo ); + + if ( success && ( avchdHeader.mExtensionDataStartAddress != 0 ) ) { + // Seek to the program header. (AVCHD Format. Book1. v. 1.01. p 77 ) + cpiFile.Seek ( avchdHeader.mExtensionDataStartAddress, kXMP_SeekFromStart ); + success = ReadAVCHDClipExtensionData ( cpiFile, avchdLegacyData.mClipExtensionData ); + } + + } catch ( ... ) { + + return false; + + } + + return success; + +} // ReadAVCHDLegacyClipFile + +// ================================================================================================= +// ReadAVCHDLegacyPlaylistFile +// =========================== +// +// Read the legacy metadata stored in an AVCHD .MPL file. + +static bool ReadAVCHDLegacyPlaylistFile ( const std::string& mplPath, + const std::string& strClipName, + AVCHD_LegacyMetadata& avchdLegacyData ) +{ + +#if 1 + bool success = false; + + try { + + Host_IO::FileRef hostRef = Host_IO::Open ( mplPath.c_str(), Host_IO::openReadOnly ); + if ( hostRef == Host_IO::noFileRef ) return false; // The open failed. + XMPFiles_IO mplFile ( hostRef, mplPath.c_str(), Host_IO::openReadOnly ); + + // Read playlist header. ( AVCHD Format. Book1. v. 1.01. p 43 ) + struct AVCHD_PlaylistFileHeader { + char mTypeIndicator[4]; + char mTypeIndicator2[4]; + XMP_Uns32 mPlaylistStartAddress; + XMP_Uns32 mPlaylistMarkStartAddress; + XMP_Uns32 mExtensionDataStartAddress; + }; + + // Read the AVCHD playlist file header. + AVCHD_PlaylistFileHeader avchdHeader; + mplFile.ReadAll ( avchdHeader.mTypeIndicator, 4 ); + mplFile.ReadAll ( avchdHeader.mTypeIndicator2, 4 ); + + if ( strncmp ( avchdHeader.mTypeIndicator, "MPLS", 4 ) != 0 ) return false; + if ( strncmp ( avchdHeader.mTypeIndicator2, "0100", 4 ) != 0 ) return false; + + avchdHeader.mPlaylistStartAddress = XIO::ReadUns32_BE ( &mplFile ); + avchdHeader.mPlaylistMarkStartAddress = XIO::ReadUns32_BE ( &mplFile ); + avchdHeader.mExtensionDataStartAddress = XIO::ReadUns32_BE ( &mplFile ); + + if ( avchdHeader.mExtensionDataStartAddress == 0 ) return false; + + // Seek to the start of the Playlist block. (AVCHD Format. Book1. v. 1.01. p 45 ) + mplFile.Seek ( avchdHeader.mPlaylistStartAddress, kXMP_SeekFromStart ); + + XMP_Uns16 playItemID = 0xFFFF; + XMP_Uns16 playlistMarkID = 0xFFFF; + + if ( AVCHD_PlaylistContainsClip ( mplFile, playItemID, strClipName ) ) { + mplFile.Seek ( avchdHeader.mPlaylistMarkStartAddress, kXMP_SeekFromStart ); + if ( ! ReadAVCHDPlaylistMarkID ( mplFile, playItemID, playlistMarkID ) ) return false; + mplFile.Seek ( avchdHeader.mExtensionDataStartAddress, kXMP_SeekFromStart ); + success = ReadAVCHDPlaylistExtensionData ( mplFile, avchdLegacyData, playlistMarkID ); + } + + } catch ( ... ) { + + success = false; + + } + + return success; + +#else + + bool success = false; + std::string mplPath; + char playlistName [10]; + const int rootPlaylistNum = atoi(strClipName.c_str()); + + // Find the corresponding .MPL file -- because of clip spanning the .MPL name may not match the .CPI name for + // a given clip -- we need to open .MPL files and look for one that contains a reference to the clip name. To speed + // up the search we'll start with the playlist with the same number/name as the clip and search backwards. Assuming + // this directory was generated by a camera, the clip numbers will increase sequentially across the playlist files, + // though one playlist file may reference more than one clip. + for ( int i = rootPlaylistNum; i >= 0; --i ) { + + sprintf ( playlistName, "%05d", i ); + + if ( MakeLeafPath ( &mplPath, strRootPath.c_str(), "PLAYLIST", playlistName, ".mpl", true /* checkFile */ ) ) { + + try { + + Host_IO::FileRef hostRef = Host_IO::Open ( mplPath.c_str(), Host_IO::openReadOnly ); + if ( hostRef == Host_IO::noFileRef ) return false; // The open failed. + XMPFiles_IO mplFile ( hostRef, mplPath.c_str(), Host_IO::openReadOnly ); + + // Read playlist header. ( AVCHD Format. Book1. v. 1.01. p 43 ) + struct AVCHD_PlaylistFileHeader + { + char mTypeIndicator[4]; + char mTypeIndicator2[4]; + XMP_Uns32 mPlaylistStartAddress; + XMP_Uns32 mPlaylistMarkStartAddress; + XMP_Uns32 mExtensionDataStartAddress; + }; + + // Read the AVCHD playlist file header. + AVCHD_PlaylistFileHeader avchdHeader; + mplFile.ReadAll ( avchdHeader.mTypeIndicator, 4 ); + mplFile.ReadAll ( avchdHeader.mTypeIndicator2, 4 ); + + if ( strncmp ( avchdHeader.mTypeIndicator, "MPLS", 4 ) != 0 ) return false; + if ( strncmp ( avchdHeader.mTypeIndicator2, "0100", 4 ) != 0 ) return false; + + avchdHeader.mPlaylistStartAddress = XIO::ReadUns32_BE ( &mplFile ); + avchdHeader.mPlaylistMarkStartAddress = XIO::ReadUns32_BE ( &mplFile ); + avchdHeader.mExtensionDataStartAddress = XIO::ReadUns32_BE ( &mplFile ); + + if ( avchdHeader.mExtensionDataStartAddress == 0 ) return false; + + // Seek to the start of the Playlist block. (AVCHD Format. Book1. v. 1.01. p 45 ) + mplFile.Seek ( avchdHeader.mPlaylistStartAddress, kXMP_SeekFromStart ); + + XMP_Uns16 playItemID = 0xFFFF; + XMP_Uns16 playlistMarkID = 0xFFFF; + + if ( AVCHD_PlaylistContainsClip ( mplFile, playItemID, strClipName ) ) { + mplFile.Seek ( avchdHeader.mPlaylistMarkStartAddress, kXMP_SeekFromStart ); + + if ( ! ReadAVCHDPlaylistMarkID ( mplFile, playItemID, playlistMarkID ) ) return false; + + mplFile.Seek ( avchdHeader.mExtensionDataStartAddress, kXMP_SeekFromStart ); + success = ReadAVCHDPlaylistExtensionData ( mplFile, avchdLegacyData, playlistMarkID ); + } + + } catch ( ... ) { + + return false; + + } + } + + } + + return success; + +#endif + +} // ReadAVCHDLegacyPlaylistFile + +// ================================================================================================= +// FindAVCHDLegacyPlaylistFile +// =========================== +// +// Find and read the legacy metadata stored in an AVCHD .MPL file. + +static bool FindAVCHDLegacyPlaylistFile ( const std::string& strRootPath, + const std::string& strClipName, + AVCHD_LegacyMetadata& avchdLegacyData, + std::string &mplPath ) +{ + bool success = false; + + // Find the corresponding .MPL file -- because of clip spanning the .MPL name may not match the + // .CPI name for a given clip -- we need to open .MPL files and look for one that contains a + // reference to the clip name. To speed up the search we'll start with the playlist with the + // same number/name as the clip, and if that fails look into other playlist files in the + // directory. One playlist file may reference more than one clip. + + if ( MakeLeafPath ( &mplPath, strRootPath.c_str(), "PLAYLIST", strClipName.c_str(), ".mpl", true /* checkFile */ ) ) { + success = ReadAVCHDLegacyPlaylistFile ( mplPath, strClipName, avchdLegacyData ); + } + + if ( ! success ) { + + std::string playlistPath = strRootPath; + playlistPath += kDirChar; + playlistPath += "BDMV"; + playlistPath += kDirChar; + playlistPath += "PLAYLIST"; + playlistPath += kDirChar; + + std::string childName; + + if ( Host_IO::GetFileMode ( playlistPath.c_str() ) == Host_IO::kFMode_IsFolder ) { + + Host_IO::AutoFolder af; + af.folder = Host_IO::OpenFolder ( playlistPath.c_str() ); + if ( af.folder == Host_IO::noFolderRef ) return false; + + while ( (! success) && Host_IO::GetNextChild ( af.folder, &childName ) && + (childName.find(".mpl") || childName.find(".MPL")) ) { + mplPath = playlistPath + childName; + if ( Host_IO::GetFileMode ( mplPath.c_str() ) == Host_IO::kFMode_IsFile ) { + success = ReadAVCHDLegacyPlaylistFile ( mplPath, strClipName, avchdLegacyData ); + } + + } + + af.Close(); + + } + + } + + return success; + +} // FindAVCHDLegacyPlaylistFile + +// ================================================================================================= +// ReadAVCHDLegacyMetadata +// ======================= +// +// Read the legacy metadata stored in an AVCHD .CPI file. + +static bool ReadAVCHDLegacyMetadata ( const std::string& strPath, + const std::string& strRootPath, + const std::string& strClipName, + AVCHD_LegacyMetadata& avchdLegacyData, + std::string& mplFile) +{ + bool success = ReadAVCHDLegacyClipFile ( strPath, avchdLegacyData ); + + if ( success && avchdLegacyData.mClipExtensionData.mPresent ) { + success = FindAVCHDLegacyPlaylistFile ( strRootPath, strClipName, avchdLegacyData, mplFile ); + } + + return success; + +} // ReadAVCHDLegacyMetadata + +// ================================================================================================= +// AVCCAM_SetXMPStartTimecode +// ========================== + +static void AVCCAM_SetXMPStartTimecode ( SXMPMeta& xmpObj, const XMP_Uns8* avccamTimecode, XMP_Uns8 avchdFrameRate ) +{ + // Timecode in SMPTE 12M format, according to Panasonic's documentation + if ( *reinterpret_cast( avccamTimecode ) == 0xFFFFFFFF ) { + // 0xFFFFFFFF means timecode not specified + return; + } + + const XMP_Uns8 isColor = ( avccamTimecode[0] >> 7 ) & 0x01; + const XMP_Uns8 isDropFrame = ( avccamTimecode[0] >> 6 ) & 0x01; + const XMP_Uns8 frameTens = ( avccamTimecode[0] >> 4 ) & 0x03; + const XMP_Uns8 frameUnits = avccamTimecode[0] & 0x0f; + const XMP_Uns8 secondTens = ( avccamTimecode[1] >> 4 ) & 0x07; + const XMP_Uns8 secondUnits = avccamTimecode[1] & 0x0f; + const XMP_Uns8 minuteTens = ( avccamTimecode[2] >> 4 ) & 0x07; + const XMP_Uns8 minuteUnits = avccamTimecode[2] & 0x0f; + const XMP_Uns8 hourTens = ( avccamTimecode[3] >> 4 ) & 0x03; + const XMP_Uns8 hourUnits = avccamTimecode[3] & 0x0f; + char tcSeparator = ':'; + const char* dmTimeFormat = NULL; + const char* dmTimeScale = NULL; + const char* dmTimeSampleSize = NULL; + + switch ( avchdFrameRate ) { + case 1 : + // 23.976i + dmTimeFormat = "23976Timecode"; + dmTimeScale = "24000"; + dmTimeSampleSize = "1001"; + break; + + case 2 : + // 24p + dmTimeFormat = "24Timecode"; + dmTimeScale = "24"; + dmTimeSampleSize = "1"; + break; + + case 3 : + case 6 : + // 50i or 25p + dmTimeFormat = "25Timecode"; + dmTimeScale = "25"; + dmTimeSampleSize = "1"; + break; + + case 4 : + case 7 : + // 29.97p or 59.94i + if ( isDropFrame ) { + dmTimeFormat = "2997DropTimecode"; + tcSeparator = ';'; + } else { + dmTimeFormat = "2997NonDropTimecode"; + } + + dmTimeScale = "30000"; + dmTimeSampleSize = "1001"; + + break; + } + + if ( dmTimeFormat != NULL ) { + char timecodeBuff [12]; + + sprintf ( timecodeBuff, "%d%d%c%d%d%c%d%d%c%d%d", hourTens, hourUnits, tcSeparator, + minuteTens, minuteUnits, tcSeparator, secondTens, secondUnits, tcSeparator, frameTens, frameUnits); + + xmpObj.SetProperty( kXMP_NS_DM, "startTimeScale", dmTimeScale, kXMP_DeleteExisting ); + xmpObj.SetProperty( kXMP_NS_DM, "startTimeSampleSize", dmTimeSampleSize, kXMP_DeleteExisting ); + xmpObj.SetStructField ( kXMP_NS_DM, "startTimecode", kXMP_NS_DM, "timeValue", timecodeBuff, 0 ); + xmpObj.SetStructField ( kXMP_NS_DM, "startTimecode", kXMP_NS_DM, "timeFormat", dmTimeFormat, 0 ); + } +} + +// ================================================================================================= +// AVCHD_SetXMPMakeAndModel +// ======================== + +static bool AVCHD_SetXMPMakeAndModel ( SXMPMeta& xmpObj, const AVCHD_blkClipExtensionData& clipExtData ) +{ + if ( ! clipExtData.mPresent ) return false; + + XMP_StringPtr xmpValue = 0; + + // Set the Make. Use a hex string for unknown makes. + { + char hexMakeNumber [7]; + + switch ( clipExtData.mClipInfoExt.mMakerID ) { + case kMakerIDCanon : xmpValue = "Canon"; break; + case kMakerIDPanasonic : xmpValue = "Panasonic"; break; + case kMakerIDSony : xmpValue = "Sony"; break; + default : + std::sprintf ( hexMakeNumber, "0x%04x", clipExtData.mClipInfoExt.mMakerID ); + xmpValue = hexMakeNumber; + + break; + } + + xmpObj.SetProperty ( kXMP_NS_TIFF, "Make", xmpValue, kXMP_DeleteExisting ); + } + + // Set the Model number. Use a hex string for unknown model numbers so they can still be distinguished. + { + char hexModelNumber [7]; + + xmpValue = 0; + + switch ( clipExtData.mClipInfoExt.mMakerID ) { + case kMakerIDCanon : + switch ( clipExtData.mClipInfoExt.mMakerModelCode ) { + case 0x1000 : xmpValue = "HR10"; break; + case 0x2000 : xmpValue = "HG10"; break; + case 0x2001 : xmpValue = "HG21"; break; + case 0x3000 : xmpValue = "HF100"; break; + case 0x3003 : xmpValue = "HF S10"; break; + default : break; + } + break; + + case kMakerIDPanasonic : + switch ( clipExtData.mClipInfoExt.mMakerModelCode ) { + case 0x0202 : xmpValue = "HD-writer"; break; + case 0x0400 : xmpValue = "AG-HSC1U"; break; + case 0x0401 : xmpValue = "AG-HMC70"; break; + case 0x0410 : xmpValue = "AG-HMC150"; break; + case 0x0411 : xmpValue = "AG-HMC40"; break; + case 0x0412 : xmpValue = "AG-HMC80"; break; + case 0x0413 : xmpValue = "AG-3DA1"; break; + case 0x0414 : xmpValue = "AG-AF100"; break; + case 0x0450 : xmpValue = "AG-HMR10"; break; + case 0x0451 : xmpValue = "AJ-YCX250"; break; + case 0x0452 : xmpValue = "AG-MDR15"; break; + case 0x0490 : xmpValue = "AVCCAM Restorer"; break; + case 0x0491 : xmpValue = "AVCCAM Viewer"; break; + case 0x0492 : xmpValue = "AVCCAM Viewer for Mac"; break; + default : break; + } + + break; + + default : break; + } + + if ( ( xmpValue == 0 ) && ( clipExtData.mClipInfoExt.mMakerID != kMakerIDSony ) ) { + // Panasonic has said that if we don't have a string for the model number, they'd like to see the code + // anyway. We'll do the same for every manufacturer except Sony, who have said that they use + // the same model number for multiple cameras. + std::sprintf ( hexModelNumber, "0x%04x", clipExtData.mClipInfoExt.mMakerModelCode ); + xmpValue = hexModelNumber; + } + + if ( xmpValue != 0 ) xmpObj.SetProperty ( kXMP_NS_TIFF, "Model", xmpValue, kXMP_DeleteExisting ); + } + + return true; +} + +// ================================================================================================= +// AVCHD_StringFieldToXMP +// ====================== + +static std::string AVCHD_StringFieldToXMP ( XMP_Uns8 avchdLength, + XMP_Uns8 avchdCharacterSet, + const XMP_Uns8* avchdField, + XMP_Uns8 avchdFieldSize ) +{ + std::string xmpString; + + if ( avchdCharacterSet == 0x02 ) { + // UTF-16, Big Endian + UTF8Unit utf8Name [512]; + const XMP_Uns8 avchdMaxChars = ( avchdFieldSize / 2); + size_t utf16Read; + size_t utf8Written; + + // The spec doesn't say whether AVCHD length fields count bytes or characters, so we'll + // clamp to the max number of UTF-16 characters just in case. + const int stringLength = ( avchdLength > avchdMaxChars ) ? avchdMaxChars : avchdLength; + + UTF16BE_to_UTF8 ( reinterpret_cast ( avchdField ), stringLength, + utf8Name, 512, &utf16Read, &utf8Written ); + xmpString.assign ( reinterpret_cast ( utf8Name ), utf8Written ); + } else { + // AVCHD supports many character encodings, but UTF-8 (0x01) and ASCII (0x90) are the only ones I've + // seen in the wild at this point. We'll treat the other character sets as UTF-8 on the assumption that + // at least a few characters will come across, and something is better than nothing. + const int stringLength = ( avchdLength > avchdFieldSize ) ? avchdFieldSize : avchdLength; + + xmpString.assign ( reinterpret_cast ( avchdField ), stringLength ); + } + + return xmpString; +} + +// ================================================================================================= +// AVCHD_SetXMPShotName +// ==================== + +static void AVCHD_SetXMPShotName ( SXMPMeta& xmpObj, const AVCHD_blkPlayListMarkExt& markExt, const std::string& strClipName ) +{ + if ( markExt.mPresent ) { + const std::string shotName = AVCHD_StringFieldToXMP ( markExt.mMarkNameLength, markExt.mMarkCharacterSet, markExt.mMarkName, 24 ); + + if ( ! shotName.empty() ) xmpObj.SetProperty ( kXMP_NS_DC, "shotName", shotName.c_str(), kXMP_DeleteExisting ); + } +} + +// ================================================================================================= +// BytesToHex +// ========== + +#define kHexDigits "0123456789ABCDEF" + +static std::string BytesToHex ( const XMP_Uns8* inClipIDBytes, int inNumBytes ) +{ + const int numChars = ( inNumBytes * 2 ); + std::string hexStr; + + hexStr.reserve(numChars); + + for ( int i = 0; i < inNumBytes; ++i ) { + const XMP_Uns8 byte = inClipIDBytes[i]; + hexStr.push_back ( kHexDigits [byte >> 4] ); + hexStr.push_back ( kHexDigits [byte & 0xF] ); + } + + return hexStr; +} + +// ================================================================================================= +// AVCHD_DateFieldToXMP +// ==================== +// +// AVCHD Format Book 2, section 4.2.2.2. + +static std::string AVCHD_DateFieldToXMP ( XMP_Uns8 avchdTimeZone, const XMP_Uns8* avchdDateTime ) +{ + const XMP_Uns8 daylightSavingsTime = ( avchdTimeZone >> 6 ) & 0x01; + const XMP_Uns8 timezoneSign = ( avchdTimeZone >> 5 ) & 0x01; + const XMP_Uns8 timezoneValue = ( avchdTimeZone >> 1 ) & 0x0F; + const XMP_Uns8 halfHourFlag = avchdTimeZone & 0x01; + int utcOffsetHours = 0; + unsigned int utcOffsetMinutes = 0; + + // It's not entirely clear how to interpret the daylightSavingsTime flag from the documentation -- my best + // guess is that it should only be used if trying to display local time, not the UTC-relative time that + // XMP specifies. + if ( timezoneValue != 0xF ) { + utcOffsetHours = timezoneSign ? -timezoneValue : timezoneValue; + utcOffsetMinutes = 30 * halfHourFlag; + } + + char dateBuff [26]; + + sprintf ( dateBuff, + "%01d%01d%01d%01d-%01d%01d-%01d%01dT%01d%01d:%01d%01d:%01d%01d%+02d:%02d", + (avchdDateTime[0] >> 4), (avchdDateTime[0] & 0x0F), + (avchdDateTime[1] >> 4), (avchdDateTime[1] & 0x0F), + (avchdDateTime[2] >> 4), (avchdDateTime[2] & 0x0F), + (avchdDateTime[3] >> 4), (avchdDateTime[3] & 0x0F), + (avchdDateTime[4] >> 4), (avchdDateTime[4] & 0x0F), + (avchdDateTime[5] >> 4), (avchdDateTime[5] & 0x0F), + (avchdDateTime[6] >> 4), (avchdDateTime[6] & 0x0F), + utcOffsetHours, utcOffsetMinutes ); + + return std::string(dateBuff); +} + +// ================================================================================================= +// AVCHD_MetaHandlerCTor +// ===================== + +XMPFileHandler * AVCHD_MetaHandlerCTor ( XMPFiles * parent ) +{ + return new AVCHD_MetaHandler ( parent ); + +} // AVCHD_MetaHandlerCTor + +// ================================================================================================= +// AVCHD_MetaHandler::AVCHD_MetaHandler +// ==================================== + +AVCHD_MetaHandler::AVCHD_MetaHandler ( XMPFiles * _parent ) +{ + this->parent = _parent; // Inherited, can't set in the prefix. + this->handlerFlags = kAVCHD_HandlerFlags; + this->stdCharForm = kXMP_Char8Bit; + + // Extract the root path and clip name. + + if ( this->parent->tempPtr == 0 ) { + // The CheckFormat call might have been skipped. + this->parent->tempPtr = CreatePseudoClipPath ( this->parent->GetFilePath() ); + } + + this->rootPath.assign ( (char*) this->parent->tempPtr ); + free ( this->parent->tempPtr ); + this->parent->tempPtr = 0; + + XIO::SplitLeafName ( &this->rootPath, &this->clipName ); + +} // AVCHD_MetaHandler::AVCHD_MetaHandler + +// ================================================================================================= +// AVCHD_MetaHandler::~AVCHD_MetaHandler +// ===================================== + +AVCHD_MetaHandler::~AVCHD_MetaHandler() +{ + + if ( this->parent->tempPtr != 0 ) { + free ( this->parent->tempPtr ); + this->parent->tempPtr = 0; + } + +} // AVCHD_MetaHandler::~AVCHD_MetaHandler + +// ================================================================================================= +// AVCHD_MetaHandler::MakeClipInfoPath +// =================================== + +bool AVCHD_MetaHandler::MakeClipInfoPath ( std::string * path, XMP_StringPtr suffix, bool checkFile /* = false */ ) const +{ + return MakeLeafPath ( path, this->rootPath.c_str(), "CLIPINF", this->clipName.c_str(), suffix, checkFile ); +} // AVCHD_MetaHandler::MakeClipInfoPath + +// ================================================================================================= +// AVCHD_MetaHandler::MakeClipStreamPath +// ===================================== + +bool AVCHD_MetaHandler::MakeClipStreamPath ( std::string * path, XMP_StringPtr suffix, bool checkFile /* = false */ ) const +{ + return MakeLeafPath ( path, this->rootPath.c_str(), "STREAM", this->clipName.c_str(), suffix, checkFile ); +} // AVCHD_MetaHandler::MakeClipStreamPath + +// ================================================================================================= +// AVCHD_MetaHandler::MakePlaylistPath +// ===================================== + +bool AVCHD_MetaHandler::MakePlaylistPath ( std::string * path, XMP_StringPtr suffix, bool checkFile /* = false */ ) const +{ + return MakeLeafPath ( path, this->rootPath.c_str(), "PLAYLIST", this->clipName.c_str(), suffix, checkFile ); +} // AVCHD_MetaHandler::MakePlaylistPath + +// ================================================================================================= +// AVCHD_MetaHandler::MakeLegacyDigest +// =================================== + +void AVCHD_MetaHandler::MakeLegacyDigest ( std::string * digestStr ) +{ + std::string strClipPath; + std::string strPlaylistPath; + std::vector legacyBuff; + + bool ok = this->MakeClipInfoPath ( &strClipPath, ".clpi", true /* checkFile */ ); + if ( ! ok ) return; + + ok = this->MakePlaylistPath ( &strPlaylistPath, ".mpls", true /* checkFile */ ); + if ( ! ok ) return; + + try { + { + Host_IO::FileRef hostRef = Host_IO::Open ( strClipPath.c_str(), Host_IO::openReadOnly ); + if ( hostRef == Host_IO::noFileRef ) return; // The open failed. + XMPFiles_IO cpiFile ( hostRef, strClipPath.c_str(), Host_IO::openReadOnly ); + + // Read at most the first 2k of data from the cpi file to use in the digest + // (every CPI file I've seen is less than 1k). + const XMP_Int64 cpiLen = cpiFile.Length(); + const XMP_Int64 buffLen = (cpiLen <= 2048) ? cpiLen : 2048; + + legacyBuff.resize ( (unsigned int) buffLen ); + cpiFile.ReadAll ( &(legacyBuff[0]), static_cast ( buffLen ) ); + } + + { + Host_IO::FileRef hostRef = Host_IO::Open ( strPlaylistPath.c_str(), Host_IO::openReadOnly ); + if ( hostRef == Host_IO::noFileRef ) return; // The open failed. + XMPFiles_IO mplFile ( hostRef, strPlaylistPath.c_str(), Host_IO::openReadOnly ); + + // Read at most the first 2k of data from the cpi file to use in the digest + // (every playlist file I've seen is less than 1k). + const XMP_Int64 mplLen = mplFile.Length(); + const XMP_Int64 buffLen = (mplLen <= 2048) ? mplLen : 2048; + const XMP_Int64 clipBuffLen = legacyBuff.size(); + + legacyBuff.resize ( (unsigned int) (clipBuffLen + buffLen) ); + mplFile.ReadAll ( &( legacyBuff [(unsigned int)clipBuffLen] ), (XMP_Int32)buffLen ); + } + + } catch (...) { + return; + } + + MD5_CTX context; + unsigned char digestBin [16]; + + MD5Init ( &context ); + MD5Update ( &context, (XMP_Uns8*)&(legacyBuff[0]), (unsigned int) legacyBuff.size() ); + MD5Final ( digestBin, &context ); + + *digestStr = BytesToHex ( digestBin, 16 ); +} // AVCHD_MetaHandler::MakeLegacyDigest + +// ================================================================================================= +// AVCHD_MetaHandler::GetFileModDate +// ================================= + +static inline bool operator< ( const XMP_DateTime & left, const XMP_DateTime & right ) { + int compare = SXMPUtils::CompareDateTime ( left, right ); + return (compare < 0); +} + +bool AVCHD_MetaHandler::GetFileModDate ( XMP_DateTime * modDate ) +{ + + // The AVCHD locations of metadata: + // BDMV/ + // CLIPINF/ + // 00001.clpi + // PLAYLIST/ + // 00001.mpls + // STREAM/ + // 00001.xmp + + bool ok, haveDate = false; + std::string fullPath; + XMP_DateTime oneDate, junkDate; + if ( modDate == 0 ) modDate = &junkDate; + + ok = this->MakeClipInfoPath ( &fullPath, ".clpi", true /* checkFile */ ); + if ( ok ) ok = Host_IO::GetModifyDate ( fullPath.c_str(), &oneDate ); + if ( ok ) { + if ( *modDate < oneDate ) *modDate = oneDate; + haveDate = true; + } + + ok = this->MakePlaylistPath ( &fullPath, ".mpls", true /* checkFile */ ); + if ( ok ) ok = Host_IO::GetModifyDate ( fullPath.c_str(), &oneDate ); + if ( ok ) { + if ( (! haveDate) || (*modDate < oneDate) ) *modDate = oneDate; + haveDate = true; + } + + ok = this->MakeClipStreamPath ( &fullPath, ".xmp", true /* checkFile */ ); + if ( ok ) ok = Host_IO::GetModifyDate ( fullPath.c_str(), &oneDate ); + if ( ok ) { + if ( (! haveDate) || (*modDate < oneDate) ) *modDate = oneDate; + haveDate = true; + } + + return haveDate; + +} // AVCHD_MetaHandler::GetFileModDate + +// ================================================================================================= +// AVCHD_MetaHandler::FillMetadataFiles +// ================================ +void AVCHD_MetaHandler::FillMetadataFiles ( std::vector * metadataFiles) +{ + std::string noExtPath, filePath, altPath; + + noExtPath = rootPath + kDirChar + "BDMV" + kDirChar + "STREAM" + kDirChar + clipName; + filePath = noExtPath + ".xmp"; + if ( ! Host_IO::Exists ( filePath.c_str() ) ) { + altPath = noExtPath + ".XMP"; + if ( Host_IO::Exists ( altPath.c_str() ) ) filePath = altPath; + } + metadataFiles->push_back ( filePath ); + + noExtPath = rootPath + kDirChar + "BDMV" + kDirChar + "CLIPINF" + kDirChar + clipName; + filePath = noExtPath + ".clpi"; + if ( ! Host_IO::Exists ( filePath.c_str() ) ) { + altPath = noExtPath + ".CLPI"; + if ( ! Host_IO::Exists ( altPath.c_str() ) ) altPath = noExtPath + ".cpi"; + if ( ! Host_IO::Exists ( altPath.c_str() ) ) altPath = noExtPath + ".CPI"; + if ( Host_IO::Exists ( altPath.c_str() ) ) filePath = altPath; + } + metadataFiles->push_back ( filePath ); + +} // FillMetadataFiles_AVCHD + +// ================================================================================================= +// AVCHD_MetaHandler::IsMetadataWritable +// ======================================= + +bool AVCHD_MetaHandler::IsMetadataWritable ( ) +{ + std::vector metadataFiles; + FillMetadataFiles(&metadataFiles); + std::vector::iterator itr = metadataFiles.begin(); + // Check whether sidecar is writable, if not then check if it can be created. + return Host_IO::Writable( itr->c_str(), true ); +}// AVCHD_MetaHandler::IsMetadataWritable + +// ================================================================================================= +// AVCHD_MetaHandler::FillAssociatedResources +// ====================================== +void AVCHD_MetaHandler::FillAssociatedResources ( std::vector * resourceList ) +{ + /// The possible associated resources: + /// BDMV/ + /// index.bdmv + /// MovieObject.bdmv + /// PLAYLIST/ + /// xxxxx.mpls + /// STREAM/ + /// zzzzz.m2ts + /// zzzzz.xmp + /// CLIPINF/ + /// zzzzz.clpi + /// BACKUP/ + // xxxxx is a five digit playlist name + // zzzzz is a five digit clip name + // + std::string bdmvPath = rootPath + kDirChar + "BDMV" + kDirChar; + std::string filePath, clipInfoPath; + //Add RootPath + filePath = rootPath + kDirChar; + PackageFormat_Support::AddResourceIfExists( resourceList, filePath ); + // Add existing files under the folder "BDMV" + filePath = bdmvPath + "index.bdmv"; + if ( ! PackageFormat_Support::AddResourceIfExists ( resourceList, filePath ) ) { + filePath = bdmvPath + "INDEX.BDMV"; + if ( ! PackageFormat_Support::AddResourceIfExists ( resourceList, filePath ) ) { + filePath = bdmvPath + "index.bdm"; + if ( ! PackageFormat_Support::AddResourceIfExists ( resourceList, filePath ) ) { + filePath = bdmvPath + "INDEX.BDM"; + PackageFormat_Support::AddResourceIfExists ( resourceList, filePath ); + } + } + } + filePath = bdmvPath + "MovieObject.bdmv"; + if ( ! PackageFormat_Support::AddResourceIfExists ( resourceList, filePath ) ) { + filePath = bdmvPath + "MOVIEOBJECT.BDMV"; + if ( ! PackageFormat_Support::AddResourceIfExists ( resourceList, filePath ) ) { + filePath = bdmvPath + "MovieObj.bdm"; + if ( ! PackageFormat_Support::AddResourceIfExists ( resourceList, filePath ) ) { + filePath = bdmvPath + "MOVIEOBJ.BDM"; + PackageFormat_Support::AddResourceIfExists ( resourceList, filePath ); + } + } + } + + + if ( MakeClipInfoPath ( &filePath, ".clpi", true /* checkFile */ ) ) { + PackageFormat_Support::AddResourceIfExists ( resourceList, filePath ); + clipInfoPath = filePath; + } + else { + filePath = bdmvPath + "CLIPINF" + kDirChar ; + PackageFormat_Support::AddResourceIfExists ( resourceList, filePath ); + } + + bool addedStreamDir=false; + if ( MakeClipStreamPath ( &filePath, ".xmp", true /* checkFile */ ) ) { + PackageFormat_Support::AddResourceIfExists ( resourceList, filePath ); + addedStreamDir = true; + } + + + if ( MakeClipStreamPath ( &filePath, ".m2ts", true /* checkFile */ ) ) { + PackageFormat_Support::AddResourceIfExists ( resourceList, filePath ); + } + else if ( ! addedStreamDir ) { + filePath = bdmvPath + "STREAM" + kDirChar ; + PackageFormat_Support::AddResourceIfExists ( resourceList, filePath ); + } + + AVCHD_LegacyMetadata avchdLegacyData; + if ( ReadAVCHDLegacyMetadata ( clipInfoPath, this->rootPath, this->clipName, avchdLegacyData, filePath ) ) { + PackageFormat_Support::AddResourceIfExists ( resourceList, filePath ); + } + else { + filePath = bdmvPath + "PLAYLIST" + kDirChar ; + PackageFormat_Support::AddResourceIfExists ( resourceList, filePath ); + } +} + +// ================================================================================================= +// AVCHD_MetaHandler::CacheFileData +// ================================ + +void AVCHD_MetaHandler::CacheFileData() +{ + XMP_Assert ( ! this->containsXMP ); + + if ( this->parent->UsesClientIO() ) { + XMP_Throw ( "AVCHD cannot be used with client-managed I/O", kXMPErr_InternalFailure ); + } + + // See if the clip's .XMP file exists. + + std::string xmpPath; + bool found = this->MakeClipStreamPath ( &xmpPath, ".xmp", true /* checkFile */ ); + if ( ! found ) return; // No XMP. + XMP_Assert ( Host_IO::Exists ( xmpPath.c_str() ) ); // MakeClipStreamPath should ensure this. + + // Read the entire .XMP file. We know the XMP exists, New_XMPFiles_IO is supposed to return 0 + // only if the file does not exist. + + bool readOnly = XMP_OptionIsClear ( this->parent->openFlags, kXMPFiles_OpenForUpdate ); + + XMP_Assert ( this->parent->ioRef == 0 ); + XMPFiles_IO* xmpFile = XMPFiles_IO::New_XMPFiles_IO ( xmpPath.c_str(), readOnly ); + if ( xmpFile == 0 ) XMP_Throw ( "AVCHD XMP file open failure", kXMPErr_InternalFailure ); + this->parent->ioRef = xmpFile; + + XMP_Int64 xmpLen = xmpFile->Length(); + if ( xmpLen > 100*1024*1024 ) { + XMP_Throw ( "AVCHD XMP is outrageously large", kXMPErr_InternalFailure ); // Sanity check. + } + + this->xmpPacket.erase(); + this->xmpPacket.append ( (size_t ) xmpLen, ' ' ); + + XMP_Int32 ioCount = xmpFile->ReadAll ( (void*)this->xmpPacket.data(), (XMP_Int32)xmpLen ); + + this->packetInfo.offset = 0; + this->packetInfo.length = (XMP_Int32)xmpLen; + FillPacketInfo ( this->xmpPacket, &this->packetInfo ); + + this->containsXMP = true; + +} // AVCHD_MetaHandler::CacheFileData + +// ================================================================================================= +// AVCHD_MetaHandler::ProcessXMP +// ============================= + +void AVCHD_MetaHandler::ProcessXMP() +{ + if ( this->processedXMP ) return; + this->processedXMP = true; // Make sure only called once. + + if ( this->containsXMP ) { + this->xmpObj.ParseFromBuffer ( this->xmpPacket.c_str(), (XMP_StringLen)this->xmpPacket.size() ); + } + + // read clip info + AVCHD_LegacyMetadata avchdLegacyData; + std::string strPath,mplfile; + + bool ok = this->MakeClipInfoPath ( &strPath, ".clpi", true /* checkFile */ ); + if ( ok ) ReadAVCHDLegacyMetadata ( strPath, this->rootPath, this->clipName, avchdLegacyData , mplfile); + if ( ! ok ) return; + + const AVCHD_blkPlayListMarkExt& markExt = avchdLegacyData.mPlaylistExtensionData.mPlaylistMarkExt; + XMP_Uns8 pulldownFlag = 0; + + if ( markExt.mPresent ) { + const std::string dateString = AVCHD_DateFieldToXMP ( markExt.mBlkTimezone, markExt.mRecordDataAndTime ); + + if ( ! dateString.empty() ) this->xmpObj.SetProperty ( kXMP_NS_DM, "shotDate", dateString.c_str(), kXMP_DeleteExisting ); + AVCHD_SetXMPShotName ( this->xmpObj, markExt, this->clipName ); + AVCCAM_SetXMPStartTimecode ( this->xmpObj, markExt.mBlkTimecode, avchdLegacyData.mProgramInfo.mVideoStream.mFrameRate ); + pulldownFlag = (markExt.mFlags >> 1) & 0x03; // bits 1 and 2 + } + + // Video Stream. AVCHD Format v. 1.01 p. 78 + + const bool has2_2pulldown = (pulldownFlag == 0x01); + const bool has3_2pulldown = (pulldownFlag == 0x10); + XMP_StringPtr xmpValue = 0; + + if ( avchdLegacyData.mProgramInfo.mVideoStream.mPresent ) { + + // XMP videoFrameSize. + xmpValue = 0; + int frameIndex = -1; + bool isProgressiveHD = false; + const char* frameWidth[4] = { "720", "720", "1280", "1920" }; + const char* frameHeight[4] = { "480", "576", "720", "1080" }; + + switch ( avchdLegacyData.mProgramInfo.mVideoStream.mVideoFormat ) { + case 1 : frameIndex = 0; break; // 480i + case 2 : frameIndex = 1; break; // 576i + case 3 : frameIndex = 0; break; // 480p + case 4 : frameIndex = 3; break; // 1080i + case 5 : frameIndex = 2; isProgressiveHD = true; break; // 720p + case 6 : frameIndex = 3; isProgressiveHD = true; break; // 1080p + default: break; + } + + if ( frameIndex != -1 ) { + xmpValue = frameWidth[frameIndex]; + this->xmpObj.SetStructField ( kXMP_NS_DM, "videoFrameSize", kXMP_NS_XMP_Dimensions, "w", xmpValue, 0 ); + xmpValue = frameHeight[frameIndex]; + this->xmpObj.SetStructField ( kXMP_NS_DM, "videoFrameSize", kXMP_NS_XMP_Dimensions, "h", xmpValue, 0 ); + xmpValue = "pixels"; + this->xmpObj.SetStructField ( kXMP_NS_DM, "videoFrameSize", kXMP_NS_XMP_Dimensions, "unit", xmpValue, 0 ); + } + + // XMP videoFrameRate. The logic below seems pretty tortured, but matches "Table 4-4 pulldown" on page 31 of Book 2 of the AVCHD + // spec, if you interepret "frame_mbs_only_flag" as "isProgressiveHD", "frame-rate [Hz]" as the frame rate encoded in + // mVideoStream.mFrameRate, and "Video Scan Type" as the desired xmp output value. The algorithm produces correct results for + // all the AVCHD media I've tested. + xmpValue = 0; + if ( isProgressiveHD ) { + + switch ( avchdLegacyData.mProgramInfo.mVideoStream.mFrameRate ) { + case 1 : xmpValue = "23.98p"; break; // "23.976" + case 2 : xmpValue = "24p"; break; // "24" + case 3 : xmpValue = "25p"; break; // "25" + case 4 : xmpValue = has2_2pulldown ? "29.97p" : "59.94p"; break; // "29.97" + case 6 : xmpValue = has2_2pulldown ? "25p" : "50p"; break; // "50" + case 7 : // "59.94" + if ( has2_2pulldown ) + xmpValue = "29.97p"; + else + xmpValue = has3_2pulldown ? "23.98p" : "59.94p"; + + break; + default: break; + } + + } else { + + switch ( avchdLegacyData.mProgramInfo.mVideoStream.mFrameRate ) { + case 3 : xmpValue = has2_2pulldown ? "25p" : "50i"; break; // "25" (but 1080p25 is reported as 1080i25 with 2:2 pulldown...) + case 4 : // "29.97" + if ( has2_2pulldown ) + xmpValue = "29.97p"; + else + xmpValue = "59.94i"; + + break; + default: break; + } + + } + + if ( xmpValue != 0 ) { + this->xmpObj.SetProperty ( kXMP_NS_DM, "videoFrameRate", xmpValue, kXMP_DeleteExisting ); + } + + this->containsXMP = true; + + } + + // Audio Stream. + if ( avchdLegacyData.mProgramInfo.mAudioStream.mPresent ) { + + xmpValue = 0; + switch ( avchdLegacyData.mProgramInfo.mAudioStream.mAudioPresentationType ) { + case 1 : xmpValue = "Mono"; break; + case 3 : xmpValue = "Stereo"; break; + default : break; + } + if ( xmpValue != 0 ) { + this->xmpObj.SetProperty ( kXMP_NS_DM, "audioChannelType", xmpValue, kXMP_DeleteExisting ); + } + + xmpValue = 0; + switch ( avchdLegacyData.mProgramInfo.mAudioStream.mSamplingFrequency ) { + case 1 : xmpValue = "48000"; break; + case 4 : xmpValue = "96000"; break; + case 5 : xmpValue = "192000"; break; + default : break; + } + if ( xmpValue != 0 ) { + this->xmpObj.SetProperty ( kXMP_NS_DM, "audioSampleRate", xmpValue, kXMP_DeleteExisting ); + } + + this->containsXMP = true; + } + + // Proprietary vendor extensions + if ( AVCHD_SetXMPMakeAndModel ( this->xmpObj, avchdLegacyData.mClipExtensionData ) ) this->containsXMP = true; + + this->xmpObj.SetProperty ( kXMP_NS_DM, "title", this->clipName.c_str(), kXMP_DeleteExisting ); + this->containsXMP = true; + + if ( avchdLegacyData.mClipExtensionData.mMakersPrivateData.mPresent && + ( avchdLegacyData.mClipExtensionData.mClipInfoExt.mMakerID == kMakerIDPanasonic ) ) { + + const AVCHD_blkPanasonicPrivateData& panasonicClipData = avchdLegacyData.mClipExtensionData.mMakersPrivateData.mPanasonicPrivateData; + + if ( panasonicClipData.mProClipIDBlock.mPresent ) { + const std::string globalClipIDString = BytesToHex ( panasonicClipData.mProClipIDBlock.mGlobalClipID, 32 ); + + this->xmpObj.SetProperty ( kXMP_NS_DC, "identifier", globalClipIDString.c_str(), kXMP_DeleteExisting ); + } + + const AVCHD_blkPanasonicPrivateData& panasonicPlaylistData = + avchdLegacyData.mPlaylistExtensionData.mMakersPrivateData.mPanasonicPrivateData; + + if ( panasonicPlaylistData.mProPlaylistInfoBlock.mPlayListMark.mPresent ) { + const AVCCAM_blkProPlayListMark& playlistMark = panasonicPlaylistData.mProPlaylistInfoBlock.mPlayListMark; + + if ( playlistMark.mShotMark.mPresent ) { + // Unlike P2, where "shotmark" is a boolean, Panasonic treats their AVCCAM shotmark as a bit field with + // 8 user-definable bits. For now we're going to treat any bit being set as xmpDM::good == true, and all + // bits being clear as xmpDM::good == false. + const bool isGood = ( playlistMark.mShotMark.mShotMark != 0 ); + + xmpObj.SetProperty_Bool ( kXMP_NS_DM, "good", isGood, kXMP_DeleteExisting ); + } + + if ( playlistMark.mAccess.mPresent && ( playlistMark.mAccess.mCreatorLength > 0 ) ) { + const std::string creatorString = AVCHD_StringFieldToXMP ( + playlistMark.mAccess.mCreatorLength, playlistMark.mAccess.mCreatorCharacterSet, playlistMark.mAccess.mCreator, 32 ) ; + + if ( ! creatorString.empty() ) { + xmpObj.DeleteProperty ( kXMP_NS_DC, "creator" ); + xmpObj.AppendArrayItem ( kXMP_NS_DC, "creator", kXMP_PropArrayIsOrdered, creatorString.c_str() ); + } + } + + if ( playlistMark.mDevice.mPresent && ( playlistMark.mDevice.mSerialNoLength > 0 ) ) { + const std::string serialNoString = AVCHD_StringFieldToXMP ( + playlistMark.mDevice.mSerialNoLength, playlistMark.mDevice.mSerialNoCharacterCode, playlistMark.mDevice.mSerialNo, 24 ) ; + + if ( ! serialNoString.empty() ) xmpObj.SetProperty ( kXMP_NS_EXIF_Aux, "SerialNumber", serialNoString.c_str(), kXMP_DeleteExisting ); + } + + if ( playlistMark.mLocation.mPresent && ( playlistMark.mLocation.mPlaceNameLength > 0 ) ) { + const std::string placeNameString = AVCHD_StringFieldToXMP ( + playlistMark.mLocation.mPlaceNameLength, playlistMark.mLocation.mPlaceNameCharacterSet, playlistMark.mLocation.mPlaceName, 64 ) ; + + if ( ! placeNameString.empty() ) xmpObj.SetProperty ( kXMP_NS_DM, "shotLocation", placeNameString.c_str(), kXMP_DeleteExisting ); + } + } + } + +} // AVCHD_MetaHandler::ProcessXMP + +// ================================================================================================= +// AVCHD_MetaHandler::UpdateFile +// ============================= +// +// Note that UpdateFile is only called from XMPFiles::CloseFile, so it is OK to close the file here. + +void AVCHD_MetaHandler::UpdateFile ( bool doSafeUpdate ) +{ + if ( ! this->needsUpdate ) return; + this->needsUpdate = false; // Make sure only called once. + + XMP_Assert ( this->parent->UsesLocalIO() ); + + std::string newDigest; + this->MakeLegacyDigest ( &newDigest ); + this->xmpObj.SetStructField ( kXMP_NS_XMP, "NativeDigests", kXMP_NS_XMP, "AVCHD", newDigest.c_str(), kXMP_DeleteExisting ); + + this->xmpObj.SerializeToBuffer ( &this->xmpPacket, this->GetSerializeOptions() ); + + std::string xmpPath; + this->MakeClipStreamPath ( &xmpPath, ".xmp" ); + + bool haveXMP = Host_IO::Exists ( xmpPath.c_str() ); + if ( ! haveXMP ) { + XMP_Assert ( this->parent->ioRef == 0 ); + Host_IO::Create ( xmpPath.c_str() ); + this->parent->ioRef = XMPFiles_IO::New_XMPFiles_IO ( xmpPath.c_str(), Host_IO::openReadWrite ); + if ( this->parent->ioRef == 0 ) XMP_Throw ( "Failure opening AVCHD XMP file", kXMPErr_ExternalFailure ); + } + + XMP_IO* xmpFile = this->parent->ioRef; + XMP_Assert ( xmpFile != 0 ); + XIO::ReplaceTextFile ( xmpFile, this->xmpPacket, (haveXMP & doSafeUpdate) ); + +} // AVCHD_MetaHandler::UpdateFile + +// ================================================================================================= +// AVCHD_MetaHandler::WriteTempFile +// ================================ + +void AVCHD_MetaHandler::WriteTempFile ( XMP_IO* tempRef ) +{ + + // ! WriteTempFile is not supposed to be called for handlers that own the file. + XMP_Throw ( "AVCHD_MetaHandler::WriteTempFile should not be called", kXMPErr_InternalFailure ); + +} // AVCHD_MetaHandler::WriteTempFile + +// ================================================================================================= diff --git a/core/libs/dngwriter/extra/xmp_sdk/XMPFiles/source/FileHandlers/AVCHD_Handler.hpp b/core/libs/dngwriter/extra/xmp_sdk/XMPFiles/source/FileHandlers/AVCHD_Handler.hpp new file mode 100644 index 0000000000..0860d5b765 --- /dev/null +++ b/core/libs/dngwriter/extra/xmp_sdk/XMPFiles/source/FileHandlers/AVCHD_Handler.hpp @@ -0,0 +1,84 @@ +#ifndef __AVCHD_Handler_hpp__ +#define __AVCHD_Handler_hpp__ 1 + +// ================================================================================================= +// ADOBE SYSTEMS INCORPORATED +// Copyright 2008 Adobe Systems Incorporated +// All Rights Reserved +// +// NOTICE: Adobe permits you to use, modify, and distribute this file in accordance with the terms +// of the Adobe license agreement accompanying it. +// ================================================================================================= + +#include "public/include/XMP_Environment.h" // ! This must be the first include. + +#include "XMPFiles/source/XMPFiles_Impl.hpp" + +#include "source/ExpatAdapter.hpp" + +// ================================================================================================= +/// \file AVCHD_Handler.hpp +/// \brief Folder format handler for AVCHD. +/// +/// This header ... +/// +// ================================================================================================= + +extern XMPFileHandler * AVCHD_MetaHandlerCTor ( XMPFiles * parent ); + +extern bool AVCHD_CheckFormat ( XMP_FileFormat format, + const std::string & rootPath, + const std::string & gpName, + const std::string & parentName, + const std::string & leafName, + XMPFiles * parent ); + +static const XMP_OptionBits kAVCHD_HandlerFlags = (kXMPFiles_CanInjectXMP | + kXMPFiles_CanExpand | + kXMPFiles_CanRewrite | + kXMPFiles_PrefersInPlace | + kXMPFiles_CanReconcile | + kXMPFiles_AllowsOnlyXMP | + kXMPFiles_ReturnsRawPacket | + kXMPFiles_HandlerOwnsFile | + kXMPFiles_AllowsSafeUpdate | + kXMPFiles_FolderBasedFormat); + +class AVCHD_MetaHandler : public XMPFileHandler +{ +public: + + bool GetFileModDate ( XMP_DateTime * modDate ); + void FillMetadataFiles ( std::vector * metadataFiles ); + void FillAssociatedResources ( std::vector * resourceList ); + bool IsMetadataWritable ( ); + + void CacheFileData(); + void ProcessXMP(); + + void UpdateFile ( bool doSafeUpdate ); + void WriteTempFile ( XMP_IO* tempRef ); + + XMP_OptionBits GetSerializeOptions() // *** These should be standard for standalone XMP files. + { return (kXMP_UseCompactFormat | kXMP_OmitPacketWrapper); }; + + AVCHD_MetaHandler ( XMPFiles * _parent ); + virtual ~AVCHD_MetaHandler(); + +private: + + AVCHD_MetaHandler() {}; // Hidden on purpose. + + bool MakeClipInfoPath ( std::string * path, XMP_StringPtr suffix, bool checkFile = false ) const; + bool MakeClipStreamPath ( std::string * path, XMP_StringPtr suffix, bool checkFile = false ) const; + bool MakePlaylistPath ( std::string * path, XMP_StringPtr suffix, bool checkFile = false ) const; + + void MakeLegacyDigest ( std::string * digestStr ); + + std::string rootPath, clipName; + +}; // AVCHD_MetaHandler + +// ================================================================================================= + +#endif /* __AVCHD_Handler_hpp__ */ diff --git a/core/libs/dngwriter/extra/xmp_sdk/XMPFiles/source/FileHandlers/Basic_Handler.cpp b/core/libs/dngwriter/extra/xmp_sdk/XMPFiles/source/FileHandlers/Basic_Handler.cpp new file mode 100644 index 0000000000..795dbd7e25 --- /dev/null +++ b/core/libs/dngwriter/extra/xmp_sdk/XMPFiles/source/FileHandlers/Basic_Handler.cpp @@ -0,0 +1,243 @@ +// ================================================================================================= +// ADOBE SYSTEMS INCORPORATED +// Copyright 2004 Adobe Systems Incorporated +// All Rights Reserved +// +// NOTICE: Adobe permits you to use, modify, and distribute this file in accordance with the terms +// of the Adobe license agreement accompanying it. +// ================================================================================================= + +#include "public/include/XMP_Environment.h" // ! XMP_Environment.h must be the first included header. + +#include "public/include/XMP_Const.h" +#include "public/include/XMP_IO.hpp" + +#include "XMPFiles/source/XMPFiles_Impl.hpp" +#include "source/XIO.hpp" + +#include "XMPFiles/source/FileHandlers/Basic_Handler.hpp" + +using namespace std; + +// ================================================================================================= +/// \file Basic_Handler.cpp +/// \brief Base class for basic handlers that only process in-place XMP. +/// +/// This header ... +/// +// ================================================================================================= + +// ================================================================================================= +// Basic_MetaHandler::~Basic_MetaHandler +// ===================================== + +Basic_MetaHandler::~Basic_MetaHandler() +{ + // ! Inherit the base cleanup. + +} // Basic_MetaHandler::~Basic_MetaHandler + +// ================================================================================================= +// Basic_MetaHandler::UpdateFile +// ============================= + +// ! This must be called from the destructor for all derived classes. It can't be called from the +// ! Basic_MetaHandler destructor, by then calls to the virtual functions would not go to the +// ! actual implementations for the derived class. + +void Basic_MetaHandler::UpdateFile ( bool doSafeUpdate ) +{ + IgnoreParam ( doSafeUpdate ); + XMP_Assert ( ! doSafeUpdate ); // Not supported at this level. + if ( ! this->needsUpdate ) return; + + XMP_IO* fileRef = this->parent->ioRef; + XMP_PacketInfo & packetInfo = this->packetInfo; + std::string & xmpPacket = this->xmpPacket; + + XMP_AbortProc abortProc = this->parent->abortProc; + void * abortArg = this->parent->abortArg; + const bool checkAbort = (abortProc != 0); + + this->CaptureFileEnding ( fileRef ); // ! Do this first, before any location info changes. + if ( checkAbort && abortProc(abortArg) ) { + XMP_Throw ( "Basic_MetaHandler::UpdateFile - User abort", kXMPErr_UserAbort ); + } + + this->NoteXMPRemoval ( fileRef ); + this->ShuffleTrailingContent ( fileRef ); + if ( checkAbort && abortProc(abortArg) ) { + XMP_Throw ( "Basic_MetaHandler::UpdateFile - User abort", kXMPErr_UserAbort ); + } + + XMP_Int64 tempLength = this->xmpFileOffset - this->xmpPrefixSize + this->trailingContentSize; + fileRef->Truncate ( tempLength ); + + packetInfo.offset = tempLength + this->xmpPrefixSize; + this->NoteXMPInsertion ( fileRef ); + + fileRef->ToEOF(); + this->WriteXMPPrefix ( fileRef ); + fileRef->Write ( xmpPacket.c_str(), (XMP_StringLen)xmpPacket.size() ); + this->WriteXMPSuffix ( fileRef ); + if ( checkAbort && abortProc(abortArg) ) { + XMP_Throw ( "Basic_MetaHandler::UpdateFile - User abort", kXMPErr_UserAbort ); + } + + this->RestoreFileEnding ( fileRef ); + + this->xmpFileOffset = packetInfo.offset; + this->xmpFileSize = packetInfo.length; + this->needsUpdate = false; + +} // Basic_MetaHandler::UpdateFile + +// ================================================================================================= +// Basic_MetaHandler::WriteTempFile +// ================================ + +// *** What about computing the new file length and pre-allocating the file? + +void Basic_MetaHandler::WriteTempFile ( XMP_IO* tempRef ) +{ + XMP_AbortProc abortProc = this->parent->abortProc; + void * abortArg = this->parent->abortArg; + const bool checkAbort = (abortProc != 0); + + XMP_IO* originalRef = this->parent->ioRef; + + // Capture the "back" of the original file. + + this->CaptureFileEnding ( originalRef ); + if ( checkAbort && abortProc(abortArg) ) { + XMP_Throw ( "Basic_MetaHandler::UpdateFile - User abort", kXMPErr_UserAbort ); + } + + // Seek to the beginning of the original and temp files, truncate the temp. + + originalRef->Rewind(); + tempRef->Rewind(); + tempRef->Truncate ( 0 ); + + // Copy the front of the original file to the temp file. Note the XMP (pseudo) removal and + // insertion. This mainly updates info about the new XMP length. + + XMP_Int64 xmpSectionOffset = this->xmpFileOffset - this->xmpPrefixSize; + XMP_Int32 oldSectionLength = this->xmpPrefixSize + this->xmpFileSize + this->xmpSuffixSize; + + XIO::Copy ( originalRef, tempRef, xmpSectionOffset, abortProc, abortArg ); + this->NoteXMPRemoval ( originalRef ); + packetInfo.offset = this->xmpFileOffset; // ! The packet offset does not change. + this->NoteXMPInsertion ( tempRef ); + tempRef->ToEOF(); + if ( checkAbort && abortProc(abortArg) ) { + XMP_Throw ( "Basic_MetaHandler::WriteFile - User abort", kXMPErr_UserAbort ); + } + + // Write the new XMP section to the temp file. + + this->WriteXMPPrefix ( tempRef ); + tempRef->Write ( this->xmpPacket.c_str(), (XMP_StringLen)this->xmpPacket.size() ); + this->WriteXMPSuffix ( tempRef ); + if ( checkAbort && abortProc(abortArg) ) { + XMP_Throw ( "Basic_MetaHandler::WriteFile - User abort", kXMPErr_UserAbort ); + } + + // Copy the trailing file content from the original and write the "back" of the file. + + XMP_Int64 remainderOffset = xmpSectionOffset + oldSectionLength; + + originalRef->Seek ( remainderOffset, kXMP_SeekFromStart ); + XIO::Copy ( originalRef, tempRef, this->trailingContentSize, abortProc, abortArg ); + this->RestoreFileEnding ( tempRef ); + + // Done. + + this->xmpFileOffset = packetInfo.offset; + this->xmpFileSize = packetInfo.length; + this->needsUpdate = false; + +} // Basic_MetaHandler::WriteTempFile + +// ================================================================================================= +// ShuffleTrailingContent +// ====================== +// +// Shuffle the trailing content portion of a file forward. This does not include the final "back" +// portion of the file, just the arbitrary length content between the XMP section and the back. +// Don't use XIO::Copy, that assumes separate files and hence separate I/O positions. + +// ! The XMP packet location and prefix/suffix sizes must still reflect the XMP section that is in +// ! the process of being removed. + +void Basic_MetaHandler::ShuffleTrailingContent ( XMP_IO* fileRef ) +{ + XMP_Int64 readOffset = this->packetInfo.offset + xmpSuffixSize; + XMP_Int64 writeOffset = this->packetInfo.offset - xmpPrefixSize; + + XMP_Int64 remainingLength = this->trailingContentSize; + + enum { kBufferSize = 64*1024 }; + char buffer [kBufferSize]; + + XMP_AbortProc abortProc = this->parent->abortProc; + void * abortArg = this->parent->abortArg; + const bool checkAbort = (abortProc != 0); + + while ( remainingLength > 0 ) { + + XMP_Int32 ioCount = kBufferSize; + if ( remainingLength < kBufferSize ) ioCount = (XMP_Int32)remainingLength; + + fileRef->Seek ( readOffset, kXMP_SeekFromStart ); + fileRef->ReadAll ( buffer, ioCount ); + fileRef->Seek ( writeOffset, kXMP_SeekFromStart ); + fileRef->Write ( buffer, ioCount ); + + readOffset += ioCount; + writeOffset += ioCount; + remainingLength -= ioCount; + + if ( checkAbort && abortProc(abortArg) ) { + XMP_Throw ( "Basic_MetaHandler::ShuffleTrailingContent - User abort", kXMPErr_UserAbort ); + } + + } + +} // ShuffleTrailingContent + +// ================================================================================================= +// Dummies needed for VS.Net +// ========================= + +void Basic_MetaHandler::WriteXMPPrefix ( XMP_IO* fileRef ) +{ + XMP_Throw ( "Basic_MetaHandler::WriteXMPPrefix - Needs specific override", kXMPErr_InternalFailure ); +} + +void Basic_MetaHandler::WriteXMPSuffix ( XMP_IO* fileRef ) +{ + XMP_Throw ( "Basic_MetaHandler::WriteXMPSuffix - Needs specific override", kXMPErr_InternalFailure ); +} + +void Basic_MetaHandler::NoteXMPRemoval ( XMP_IO* fileRef ) +{ + XMP_Throw ( "Basic_MetaHandler::NoteXMPRemoval - Needs specific override", kXMPErr_InternalFailure ); +} + +void Basic_MetaHandler::NoteXMPInsertion ( XMP_IO* fileRef ) +{ + XMP_Throw ( "Basic_MetaHandler::NoteXMPInsertion - Needs specific override", kXMPErr_InternalFailure ); +} + +void Basic_MetaHandler::CaptureFileEnding ( XMP_IO* fileRef ) +{ + XMP_Throw ( "Basic_MetaHandler::CaptureFileEnding - Needs specific override", kXMPErr_InternalFailure ); +} + +void Basic_MetaHandler::RestoreFileEnding ( XMP_IO* fileRef ) +{ + XMP_Throw ( "Basic_MetaHandler::RestoreFileEnding - Needs specific override", kXMPErr_InternalFailure ); +} + +// ================================================================================================= diff --git a/core/libs/dngwriter/extra/xmp_sdk/XMPFiles/source/FileHandlers/Basic_Handler.hpp b/core/libs/dngwriter/extra/xmp_sdk/XMPFiles/source/FileHandlers/Basic_Handler.hpp new file mode 100644 index 0000000000..d5fca0f262 --- /dev/null +++ b/core/libs/dngwriter/extra/xmp_sdk/XMPFiles/source/FileHandlers/Basic_Handler.hpp @@ -0,0 +1,117 @@ +#ifndef __Basic_Handler_hpp__ +#define __Basic_Handler_hpp__ 1 + +// ================================================================================================= +// ADOBE SYSTEMS INCORPORATED +// Copyright 2004 Adobe Systems Incorporated +// All Rights Reserved +// +// NOTICE: Adobe permits you to use, modify, and distribute this file in accordance with the terms +// of the Adobe license agreement accompanying it. +// ================================================================================================= + +#include "public/include/XMP_Environment.h" // ! XMP_Environment.h must be the first included header. + +#include "public/include/XMP_Const.h" +#include "public/include/XMP_IO.hpp" + +#include "XMPFiles/source/XMPFiles_Impl.hpp" + +// ================================================================================================= +/// \file Basic_Handler.hpp +/// +/// \brief Base class for handlers that support a simple file model allowing insertion and expansion +/// of XMP, but probably not reconciliation with other forms of metadata. Reconciliation would have +/// to be done within the I/O model presented here. +/// +/// \note Any specific derived handler might not be able to do insertion, but all must support +/// expansion. If a handler can't do either it should be derived from Trivial_Handler. Common code +/// must check the actual canInject flag where appropriate. +/// +/// The model for a basic handler divides the file into 6 portions: +/// +/// \li The front of the file. This portion can be arbitrarily large. Files over 4GB are supported. +/// Adding or expanding the XMP must not require expanding this portion of the file. The XMP offset +/// or length might be written into reserved space in this section though. +/// +/// \li A prefix for the XMP section. The prefix and suffix for the XMP "section" are the format +/// specific portions that surround the raw XMP packet. They must be generated on the fly, even when +/// updating existing XMP with or without expansion. Their length must not depend on the XMP packet. +/// +/// \li The XMP packet, as created by SXMPMeta::SerializeToBuffer. The size must be less than 2GB. +/// +/// \li A suffix for the XMP section. +/// +/// \li Trailing file content. This portion can be arbitarily large. It must be possible to remove +/// the XMP, move this portion of the file forward, then reinsert the XMP after this portion. This +/// is actually how the XMP is expanded. There must not be any embedded file offsets in this part, +/// this content must not change if the XMP changes size. +/// +/// \li The back of the file. This portion must have modest size, and/or be generated on the fly. +/// When inserting XMP, part of this may be buffered in RAM (hence the modest size requirement), the +/// XMP section is written, then this portion is rewritten. There must not be any embedded file +/// offsets in this part, this content must not change if the XMP changes size. +/// +/// \note There is no general promise here about crash-safe I/O. An update to an existing file might +/// have invalid partial state, for example while moving the trailing content portion forward if the +/// XMP increases in size or even rewriting existing XMP in-place. Crash-safe updates are managed at +/// a higher level of XMPFiles, using a temporary file and final swap of file content. +/// +// ================================================================================================= + +static const XMP_OptionBits kBasic_HandlerFlags = (kXMPFiles_CanInjectXMP | + kXMPFiles_CanExpand | + kXMPFiles_CanRewrite | + kXMPFiles_PrefersInPlace | + kXMPFiles_AllowsOnlyXMP | + kXMPFiles_ReturnsRawPacket | + kXMPFiles_AllowsSafeUpdate); + +class Basic_MetaHandler : public XMPFileHandler +{ +public: + + Basic_MetaHandler() : + xmpFileOffset(0), xmpFileSize(0), xmpPrefixSize(0), xmpSuffixSize(0), trailingContentSize(0) {}; + ~Basic_MetaHandler(); + + virtual void CacheFileData() = 0; // Sets offset for insertion if no XMP yet. + + void UpdateFile ( bool doSafeUpdate ); + void WriteTempFile ( XMP_IO* tempRef ); + +protected: + + // Write a cached or fixed prefix or suffix for the XMP. The file is passed because it could be + // either the original file or a safe-update temp file. + virtual void WriteXMPPrefix ( XMP_IO* fileRef ) = 0; // ! Must have override in actual handlers! + virtual void WriteXMPSuffix ( XMP_IO* fileRef ) = 0; // ! Must have override in actual handlers! + + // Note that the XMP is being removed or inserted. The file is passed because it could be either + // the original file or a safe-update temp file. + virtual void NoteXMPRemoval ( XMP_IO* fileRef ) = 0; // ! Must have override in actual handlers! + virtual void NoteXMPInsertion ( XMP_IO* fileRef ) = 0; // ! Must have override in actual handlers! + + // Capture or restore the tail portion of the file. The file is passed because it could be either + // the original file or a safe-update temp file. + virtual void CaptureFileEnding ( XMP_IO* fileRef ) = 0; // ! Must have override in actual handlers! + virtual void RestoreFileEnding ( XMP_IO* fileRef ) = 0; // ! Must have override in actual handlers! + + // Move the trailing content portion forward. Excludes "back" of the file. The file is passed + // because it could be either the original file or a safe-update temp file. + void ShuffleTrailingContent ( XMP_IO* fileRef ); // Has a common implementation. + + XMP_Uns64 xmpFileOffset; // The offset of the XMP in the file. + XMP_Uns32 xmpFileSize; // The size of the XMP in the file. + // ! The packetInfo offset and length are updated by PutXMP, before the file is updated! + + XMP_Uns32 xmpPrefixSize; // The size of the existing header for the XMP section. + XMP_Uns32 xmpSuffixSize; // The size of the existing trailer for the XMP section. + + XMP_Uns64 trailingContentSize; // The size of the existing trailing content. Excludes "back" of the file. + +}; // Basic_MetaHandler + +// ================================================================================================= + +#endif /* __Basic_Handler_hpp__ */ diff --git a/core/libs/dngwriter/extra/xmp_sdk/XMPFiles/source/FileHandlers/FLV_Handler.cpp b/core/libs/dngwriter/extra/xmp_sdk/XMPFiles/source/FileHandlers/FLV_Handler.cpp new file mode 100644 index 0000000000..2863f7b6c5 --- /dev/null +++ b/core/libs/dngwriter/extra/xmp_sdk/XMPFiles/source/FileHandlers/FLV_Handler.cpp @@ -0,0 +1,762 @@ +// ================================================================================================= +// ADOBE SYSTEMS INCORPORATED +// Copyright 2007 Adobe Systems Incorporated +// All Rights Reserved +// +// NOTICE: Adobe permits you to use, modify, and distribute this file in accordance with the terms +// of the Adobe license agreement accompanying it. +// ================================================================================================= + +#include "public/include/XMP_Environment.h" // ! XMP_Environment.h must be the first included header. +#include "public/include/XMP_Const.h" + +#include "XMPFiles/source/FileHandlers/FLV_Handler.hpp" + +#include "source/XIO.hpp" +#include "XMP_MD5.h" + +using namespace std; + +// ================================================================================================= +/// \file FLV_Handler.cpp +/// \brief File format handler for FLV. +/// +/// FLV is a fairly simple format, with a strong orientation to streaming use. It consists of a +/// small file header then a sequence of tags that can contain audio data, video data, or +/// ActionScript data. All integers in FLV are big endian. +/// +/// For FLV version 1, the file header contains: +/// +/// UI24 signature - the characters "FLV" +/// UI8 version - 1 +/// UI8 flags - 0x01 = has video tags, 0x04 = has audio tags +/// UI32 length in bytes of file header +/// +/// For FLV version 1, each tag begins with an 11 byte header: +/// +/// UI8 tag type - 8 = audio tag, 9 = video tag, 18 = script data tag +/// UI24 content length in bytes +/// UI24 time - low order 3 bytes +/// UI8 time - high order byte +/// UI24 stream ID +/// +/// This is followed by the tag's content, then a UI32 "back pointer" which is the header size plus +/// the content size. A UI32 zero is placed between the file header and the first tag as a +/// terminator for backward scans. The time in a tag header is the start of playback for that tag. +/// The tags must be in ascending time order. For a given time it is preferred that script data tags +/// precede audio and video tags. +/// +/// For metadata purposes only the script data tags are of interest. Script data information becomes +/// accessible to ActionScript at the playback moment of the script data tag through a call to a +/// registered data handler. The content of a script data tag contains a string and an ActionScript +/// data value. The string is the name of the handler to be invoked, the data value is passed as an +/// ActionScript Object parameter to the handler. +/// +/// The XMP is placed in a script data tag with the name "onXMPData". A variety of legacy metadata +/// is contained in a script data tag with the name "onMetaData". This contains only "internal" +/// information (like duration or width/height), nothing that is user or author editiable (like +/// title or description). Some of these legacy items are imported into the XMP, none are updated +/// from the XMP. +/// +/// A script data tag's content is: +/// +/// UI8 0x02 +/// UI16 name length - includes nul terminator if present +/// UI8n object name - UTF-8, possibly with nul terminator +/// ... object value - serialized ActionScript value (SCRIPTDATAVALUE in FLV spec) +/// +/// The onXMPData and onMetaData values are both ECMA arrays. These have more in common with XMP +/// structs than arrays, the items have arbitrary string names. The serialized form is: +/// +/// UI8 0x08 +/// UI32 array length - need not be exact, an optimization hint +/// array items +/// UI16 name length - includes nul terminator if present +/// UI8n item name - UTF-8, possibly with nul terminator +/// ... object value - serialized ActionScript value (SCRIPTDATAVALUE in FLV spec) +/// UI24 0x000009 - array terminator +/// +/// The object names and array item names in sample files do not have a nul terminator. The policy +/// here is to treat them as optional when reading, and to omit them when writing. +/// +/// The onXMPData array typically has one item named "liveXML". The value of this is a short or long +/// string as necessary: +/// +/// UI8 type - 2 for a short string, 12 for a long string +/// UIx value length - UI16 for a short string, UI32 for a long string, includes nul terminator +/// UI8n value - UTF-8 with nul terminator +/// +// ================================================================================================= + +static inline XMP_Uns32 GetUns24BE ( const void * addr ) +{ + return (GetUns32BE(addr) >> 8); +} + +static inline void PutUns24BE ( XMP_Uns32 value, void * addr ) +{ + XMP_Uns8 * bytes = (XMP_Uns8*)addr; + bytes[0] = (XMP_Uns8)(value >> 16); + bytes[1] = (XMP_Uns8)(value >> 8); + bytes[2] = (XMP_Uns8)(value); +} + +// ================================================================================================= +// FLV_CheckFormat +// =============== +// +// Check for "FLV" and 1 in the first 4 bytes, that the header length is at least 9, that the file +// size is at least as big as the header, and that the leading 0 back pointer is present if the file +// is bigger than the header. + +#define kFLV1 0x464C5601UL + +bool FLV_CheckFormat ( XMP_FileFormat format, + XMP_StringPtr filePath, + XMP_IO* fileRef, + XMPFiles * parent ) +{ + XMP_Uns8 buffer [9]; + + fileRef->Rewind(); + XMP_Uns32 ioCount = fileRef->Read ( buffer, 9 ); + if ( ioCount != 9 ) return false; + + XMP_Uns32 fileSignature = GetUns32BE ( &buffer[0] ); + if ( fileSignature != kFLV1 ) return false; + + XMP_Uns32 headerSize = GetUns32BE ( &buffer[5] ); + XMP_Uns64 fileSize = fileRef->Length(); + if ( (fileSize < (headerSize + 4)) && (fileSize != headerSize) ) return false; + + if ( fileSize >= (headerSize + 4) ) { + XMP_Uns32 bpZero; + fileRef->Seek ( headerSize, kXMP_SeekFromStart ); + ioCount = fileRef->Read ( &bpZero, 4 ); + if ( (ioCount != 4) || (bpZero != 0) ) return false; + } + + return true; + +} // FLV_CheckFormat + +// ================================================================================================= +// FLV_MetaHandlerCTor +// =================== + +XMPFileHandler * FLV_MetaHandlerCTor ( XMPFiles * parent ) +{ + + return new FLV_MetaHandler ( parent ); + +} // FLV_MetaHandlerCTor + +// ================================================================================================= +// FLV_MetaHandler::FLV_MetaHandler +// ================================ + +FLV_MetaHandler::FLV_MetaHandler ( XMPFiles * _parent ) + : flvHeaderLen(0), longXMP(false), xmpTagPos(0), omdTagPos(0), xmpTagLen(0), omdTagLen(0) +{ + + this->parent = _parent; // Inherited, can't set in the prefix. + this->handlerFlags = kFLV_HandlerFlags; + this->stdCharForm = kXMP_Char8Bit; + +} // FLV_MetaHandler::FLV_MetaHandler + +// ================================================================================================= +// FLV_MetaHandler::~FLV_MetaHandler +// ================================= + +FLV_MetaHandler::~FLV_MetaHandler() +{ + + // Nothing to do yet. + +} // FLV_MetaHandler::~FLV_MetaHandler + +// ================================================================================================= +// GetTagInfo +// ========== +// +// Seek to the start of a tag and extract the type, data size, and timestamp. Leave the file +// positioned at the first byte of data. + +struct TagInfo { + XMP_Uns8 type; + XMP_Uns32 time; + XMP_Uns32 dataSize; +}; + +static void GetTagInfo ( XMP_IO* fileRef, XMP_Uns64 tagPos, TagInfo * info ) +{ + XMP_Uns8 buffer [11]; + + fileRef->Seek ( tagPos, kXMP_SeekFromStart ); + fileRef->ReadAll ( buffer, 11 ); + + info->type = buffer[0]; + info->time = GetUns24BE ( &buffer[4] ) || (buffer[7] << 24); + info->dataSize = GetUns24BE ( &buffer[1] ); + +} // GetTagInfo + +// ================================================================================================= +// GetASValueLen +// ============= +// +// Return the full length of a serialized ActionScript value, including the type byte, zero if unknown. + +static XMP_Uns32 GetASValueLen ( const XMP_Uns8 * asValue, const XMP_Uns8 * asLimit ) +{ + XMP_Uns32 valueLen = 0; + const XMP_Uns8 * itemPtr; + XMP_Uns32 arrayCount; + + switch ( asValue[0] ) { + + case 0 : // IEEE double + valueLen = 1 + 8; + break; + + case 1 : // UI8 Boolean + valueLen = 1 + 1; + break; + + case 2 : // Short string + valueLen = 1 + 2 + GetUns16BE ( &asValue[1] ); + break; + + case 3 : // ActionScript object, a name and value. + itemPtr = &asValue[1]; + itemPtr += 2 + GetUns16BE ( itemPtr ); // Move past the name portion. + itemPtr += GetASValueLen ( itemPtr, asLimit ); // And past the data portion. + valueLen = (XMP_Uns32) (itemPtr - asValue); + break; + + case 4 : // Short string (movie clip path) + valueLen = 1 + 2 + GetUns16BE ( &asValue[1] ); + break; + + case 5 : // Null + valueLen = 1; + break; + + case 6 : // Undefined + valueLen = 1; + break; + + case 7 : // UI16 reference ID + valueLen = 1 + 2; + break; + + case 8 : // ECMA array, ignore the count, look for the 0x000009 terminator. + itemPtr = &asValue[5]; + while ( itemPtr < asLimit ) { + XMP_Uns16 nameLen = GetUns16BE ( itemPtr ); + itemPtr += 2 + nameLen; // Move past the name portion. + if ( (nameLen == 0) && (*itemPtr == 9) ) { + itemPtr += 1; + break; // Done, found the 0x000009 terminator. + } + itemPtr += GetASValueLen ( itemPtr, asLimit ); // And past the data portion. + } + valueLen = (XMP_Uns32) (itemPtr - asValue); + break; + + case 10 : // Strict array, has an exact count. + arrayCount = GetUns32BE ( &asValue[1] ); + itemPtr = &asValue[5]; + for ( ; (arrayCount > 0) && (itemPtr < asLimit); --arrayCount ) { + itemPtr += 2 + GetUns16BE ( itemPtr ); // Move past the name portion. + itemPtr += GetASValueLen ( itemPtr, asLimit ); // And past the data portion. + } + valueLen = (XMP_Uns32) (itemPtr - asValue); + break; + + case 11 : // Date + valueLen = 1 + 8 + 2; + break; + + case 12: // Long string + valueLen = 1 + 4 + GetUns32BE ( &asValue[1] ); + break; + + } + + return valueLen; + +} // GetASValueLen + +// ================================================================================================= +// CheckName +// ========= +// +// Check for the name portion of a script data tag or array item, with optional nul terminator. The +// wantedLen must not count the terminator. + +static inline bool CheckName ( XMP_StringPtr inputName, XMP_Uns16 inputLen, + XMP_StringPtr wantedName, XMP_Uns16 wantedLen ) +{ + + if ( inputLen == wantedLen+1 ) { + if ( inputName[wantedLen] != 0 ) return false; // Extra byte must be terminating nul. + --inputLen; + } + + if ( (inputLen == wantedLen) && XMP_LitNMatch ( inputName, wantedName, wantedLen ) ) return true; + return false; + +} // CheckName + +// ================================================================================================= +// FLV_MetaHandler::CacheFileData +// ============================== +// +// Look for the onXMPData and onMetaData script data tags at time 0. Cache all of onMetaData, it +// shouldn't be that big and this removes a need to know what is reconciled. It can't be more than +// 16MB anyway, the size field is only 24 bits. + +void FLV_MetaHandler::CacheFileData() +{ + XMP_Assert ( ! this->containsXMP ); + + XMP_AbortProc abortProc = this->parent->abortProc; + void * abortArg = this->parent->abortArg; + const bool checkAbort = (abortProc != 0); + + XMP_IO* fileRef = this->parent->ioRef; + XMP_Uns64 fileSize = fileRef->Length(); + + XMP_Uns8 buffer [16]; // Enough for 1+2+"onMetaData"+nul. + XMP_Uns32 ioCount; + TagInfo info; + + fileRef->Seek ( 5, kXMP_SeekFromStart ); + fileRef->ReadAll ( buffer, 4 ); + + this->flvHeaderLen = GetUns32BE ( &buffer[0] ); + XMP_Uns32 firstTagPos = this->flvHeaderLen + 4; // Include the initial zero back pointer. + + if ( firstTagPos >= fileSize ) return; // Quit now if the file is just a header. + + for ( XMP_Uns64 tagPos = firstTagPos; tagPos < fileSize; tagPos += (11 + info.dataSize + 4) ) { + + if ( checkAbort && abortProc(abortArg) ) { + XMP_Throw ( "FLV_MetaHandler::LookForMetadata - User abort", kXMPErr_UserAbort ); + } + + GetTagInfo ( fileRef, tagPos, &info ); // ! GetTagInfo seeks to the tag offset. + if ( info.time != 0 ) break; + if ( info.type != 18 ) continue; + + XMP_Assert ( sizeof(buffer) >= (1+2+10+1) ); // 02 000B onMetaData 00 + ioCount = fileRef->Read ( buffer, sizeof(buffer) ); + if ( (ioCount < 4) || (buffer[0] != 0x02) ) continue; + + XMP_Uns16 nameLen = GetUns16BE ( &buffer[1] ); + XMP_StringPtr namePtr = (XMP_StringPtr)(&buffer[3]); + + if ( this->onXMP.empty() && CheckName ( namePtr, nameLen, "onXMPData", 9 ) ) { + + // ! Put the raw data in onXMPData, analyze the value in ProcessXMP. + + this->xmpTagPos = tagPos; + this->xmpTagLen = 11 + info.dataSize + 4; // ! Includes the trailing back pointer. + + this->packetInfo.offset = tagPos + 11 + 1+2+nameLen; // ! Not the real offset yet, the offset of the onXMPData value. + + ioCount = info.dataSize - (1+2+nameLen); // Just the onXMPData value portion. + this->onXMP.reserve ( ioCount ); + this->onXMP.assign ( ioCount, ' ' ); + fileRef->Seek ( this->packetInfo.offset, kXMP_SeekFromStart ); + fileRef->ReadAll ( (void*)this->onXMP.data(), ioCount ); + + if ( ! this->onMetaData.empty() ) break; // Done if we've found both. + + } else if ( this->onMetaData.empty() && CheckName ( namePtr, nameLen, "onMetaData", 10 ) ) { + + this->omdTagPos = tagPos; + this->omdTagLen = 11 + info.dataSize + 4; // ! Includes the trailing back pointer. + + ioCount = info.dataSize - (1+2+nameLen); // Just the onMetaData value portion. + this->onMetaData.reserve ( ioCount ); + this->onMetaData.assign ( ioCount, ' ' ); + fileRef->Seek ( (tagPos + 11 + 1+2+nameLen), kXMP_SeekFromStart ); + fileRef->ReadAll ( (void*)this->onMetaData.data(), ioCount ); + + if ( ! this->onXMP.empty() ) break; // Done if we've found both. + + } + + } + +} // FLV_MetaHandler::CacheFileData + +// ================================================================================================= +// FLV_MetaHandler::MakeLegacyDigest +// ================================= + +#define kHexDigits "0123456789ABCDEF" + +void FLV_MetaHandler::MakeLegacyDigest ( std::string * digestStr ) +{ + MD5_CTX context; + unsigned char digestBin [16]; + + MD5Init ( &context ); + MD5Update ( &context, (XMP_Uns8*)this->onMetaData.data(), (unsigned int)this->onMetaData.size() ); + MD5Final ( digestBin, &context ); + + char buffer [40]; + for ( int in = 0, out = 0; in < 16; in += 1, out += 2 ) { + XMP_Uns8 byte = digestBin[in]; + buffer[out] = kHexDigits [ byte >> 4 ]; + buffer[out+1] = kHexDigits [ byte & 0xF ]; + } + buffer[32] = 0; + digestStr->erase(); + digestStr->append ( buffer, 32 ); + +} // FLV_MetaHandler::MakeLegacyDigest + +// ================================================================================================= +// FLV_MetaHandler::ExtractLiveXML +// =============================== +// +// Extract the XMP packet from the cached onXMPData ECMA array's "liveXMP" item. + +void FLV_MetaHandler::ExtractLiveXML() +{ + if ( this->onXMP[0] != 0x08 ) return; // Make sure onXMPData is an ECMA array. + const XMP_Uns8 * ecmaArray = (const XMP_Uns8 *) this->onXMP.c_str(); + const XMP_Uns8 * ecmaLimit = ecmaArray + this->onXMP.size(); + + if ( this->onXMP.size() >= 3 ) { // Omit the 0x000009 terminator, simplifies the loop. + if ( GetUns24BE ( ecmaLimit-3 ) == 9 ) ecmaLimit -= 3; + } + + for ( const XMP_Uns8 * itemPtr = ecmaArray + 5; itemPtr < ecmaLimit; /* internal increment */ ) { + + // Find the "liveXML" array item, make sure it is a short or long string. + + XMP_Uns16 nameLen = GetUns16BE ( itemPtr ); + const XMP_Uns8 * namePtr = itemPtr + 2; + + itemPtr += (2 + nameLen); // Move to the value portion. + XMP_Uns32 valueLen = GetASValueLen ( itemPtr, ecmaLimit ); + if ( valueLen == 0 ) return; // ! Unknown value type, can't look further. + + if ( CheckName ( (char*)namePtr, nameLen, "liveXML", 7 ) ) { + + XMP_Uns32 lenLen = 2; // Assume a short string. + if ( *itemPtr == 12 ) { + lenLen = 4; + this->longXMP = true; + } else if ( *itemPtr != 2 ) { + return; // Not a short or long string. + } + + valueLen -= (1 + lenLen); + itemPtr += (1 + lenLen); + + this->packetInfo.offset += (itemPtr - ecmaArray); + this->packetInfo.length += valueLen; + + this->xmpPacket.reserve ( valueLen ); + this->xmpPacket.assign ( (char*)itemPtr, valueLen ); + + return; + + } + + itemPtr += valueLen; // Move past the value portion. + + } + +} // FLV_MetaHandler::ExtractLiveXML + +// ================================================================================================= +// FLV_MetaHandler::ProcessXMP +// =========================== + +void FLV_MetaHandler::ProcessXMP() +{ + if ( this->processedXMP ) return; + this->processedXMP = true; // Make sure only called once. + + if ( ! this->onXMP.empty() ) { // Look for the XMP packet. + + this->ExtractLiveXML(); + if ( ! this->xmpPacket.empty() ) { + FillPacketInfo ( this->xmpPacket, &this->packetInfo ); + this->xmpObj.ParseFromBuffer ( this->xmpPacket.c_str(), (XMP_StringLen)this->xmpPacket.size() ); + this->containsXMP = true; + } + + } + + // Now process the legacy, if necessary. + + if ( this->onMetaData.empty() ) return; // No legacy, we're done. + + std::string oldDigest; + bool oldDigestFound = this->xmpObj.GetStructField ( kXMP_NS_XMP, "NativeDigests", kXMP_NS_XMP, "FLV", &oldDigest, 0 ); + + if ( oldDigestFound ) { + std::string newDigest; + this->MakeLegacyDigest ( &newDigest ); + if ( oldDigest == newDigest ) return; // No legacy changes. + } + + // *** No spec yet for what legacy to reconcile. + +} // FLV_MetaHandler::ProcessXMP + +// ================================================================================================= +// FLV_MetaHandler::UpdateFile +// =========================== + +void FLV_MetaHandler::UpdateFile ( bool doSafeUpdate ) +{ + if ( ! this->needsUpdate ) return; + XMP_Assert ( ! doSafeUpdate ); // This should only be called for "unsafe" updates. + + XMP_AbortProc abortProc = this->parent->abortProc; + void * abortArg = this->parent->abortArg; + const bool checkAbort = (abortProc != 0); + + XMP_IO* fileRef = this->parent->ioRef; + XMP_Uns64 fileSize = fileRef->Length(); + + // Make sure the XMP has a legacy digest if appropriate. + + if ( ! this->onMetaData.empty() ) { + + std::string newDigest; + this->MakeLegacyDigest ( &newDigest ); + this->xmpObj.SetStructField ( kXMP_NS_XMP, "NativeDigests", + kXMP_NS_XMP, "FLV", newDigest.c_str(), kXMP_DeleteExisting ); + + try { + XMP_StringLen xmpLen = (XMP_StringLen)this->xmpPacket.size(); + this->xmpObj.SerializeToBuffer ( &this->xmpPacket, (kXMP_UseCompactFormat | kXMP_ExactPacketLength), xmpLen ); + } catch ( ... ) { + this->xmpObj.SerializeToBuffer ( &this->xmpPacket, kXMP_UseCompactFormat ); + } + + } + + // Rewrite the packet in-place if it fits. Otherwise rewrite the whole file. + + if ( this->xmpPacket.size() == (size_t)this->packetInfo.length ) { + XMP_ProgressTracker* progressTracker = this->parent->progressTracker; + if ( progressTracker != 0 ) progressTracker->BeginWork ( (float)this->xmpPacket.size() ); + fileRef->Seek ( this->packetInfo.offset, kXMP_SeekFromStart ); + fileRef->Write ( this->xmpPacket.data(), (XMP_Int32)this->xmpPacket.size() ); + if ( progressTracker != 0 ) progressTracker->WorkComplete(); + + + } else { + + XMP_IO* tempRef = fileRef->DeriveTemp(); + if ( tempRef == 0 ) XMP_Throw ( "Failure creating FLV temp file", kXMPErr_InternalFailure ); + + this->WriteTempFile ( tempRef ); + fileRef->AbsorbTemp(); + + } + + this->needsUpdate = false; + +} // FLV_MetaHandler::UpdateFile + +// ================================================================================================= +// WriteOnXMP +// ========== +// +// Write the XMP packet wrapped up in an ECMA array script data tag: +// +// 0 UI8 tag type : 18 +// 1 UI24 content length : 1+2+9+1+4+2+7+1 + <2 or 4> + XMP packet size + 1 + 3 +// 4 UI24 time low : 0 +// 7 UI8 time high : 0 +// 8 UI24 stream ID : 0 +// +// 11 UI8 0x02 +// 12 UI16 name length : 9 +// 14 str9 tag name : "onXMPData", no nul terminator +// 23 UI8 value type : 8 +// 24 UI32 array count : 1 +// 28 UI16 name length : 7 +// 30 str7 item name : "liveXML", no nul terminator +// +// 37 UI8 value type : 2 for a short string, 12 for a long string +// 38 UIn XMP packet size + 1, UI16 or UI32 as needed +// -- str XMP packet, with nul terminator +// +// -- UI24 array terminator : 0x000009 +// -- UI32 back pointer : content length + 11 + +static void WriteOnXMP ( XMP_IO* fileRef, const std::string & xmpPacket ) +{ + char buffer [64]; + bool longXMP = false; + XMP_Uns32 tagLen = 1+2+9+1+4+2+7+1 + 2 + (XMP_Uns32)xmpPacket.size() + 1 + 3; + + if ( xmpPacket.size() > 0xFFFE ) { + longXMP = true; + tagLen += 2; + } + + if ( tagLen > 16*1024*1024 ) XMP_Throw ( "FLV tags can't be larger than 16MB", kXMPErr_TBD ); + + // Fill in the script data tag header. + + buffer[0] = 18; + PutUns24BE ( tagLen, &buffer[1] ); + PutUns24BE ( 0, &buffer[4] ); + buffer[7] = 0; + PutUns24BE ( 0, &buffer[8] ); + + // Fill in the "onXMPData" name, ECMA array start, and "liveXML" name. + + buffer[11] = 2; + PutUns16BE ( 9, &buffer[12] ); + memcpy ( &buffer[14], "onXMPData", 9 ); // AUDIT: Safe, buffer has 64 chars. + buffer[23] = 8; + PutUns32BE ( 1, &buffer[24] ); + PutUns16BE ( 7, &buffer[28] ); + memcpy ( &buffer[30], "liveXML", 7 ); // AUDIT: Safe, buffer has 64 chars. + + // Fill in the XMP packet string type and length, write what we have so far. + + fileRef->ToEOF(); + if ( ! longXMP ) { + buffer[37] = 2; + PutUns16BE ( (XMP_Uns16)xmpPacket.size()+1, &buffer[38] ); + fileRef->Write ( buffer, 40 ); + } else { + buffer[37] = 12; + PutUns32BE ( (XMP_Uns32)xmpPacket.size()+1, &buffer[38] ); + fileRef->Write ( buffer, 42 ); + } + + // Write the XMP packet, nul terminator, array terminator, and back pointer. + + fileRef->Write ( xmpPacket.c_str(), (XMP_Int32)xmpPacket.size()+1 ); + PutUns24BE ( 9, &buffer[0] ); + PutUns32BE ( tagLen+11, &buffer[3] ); + fileRef->Write ( buffer, 7 ); + +} // WriteOnXMP + +// ================================================================================================= +// FLV_MetaHandler::WriteTempFile +// ============================== +// +// Use a source (old) file and the current XMP to build a destination (new) file. All of the source +// file is copied except for previous XMP. The current XMP is inserted after onMetaData, or at least +// before the first time 0 audio or video tag. + +// ! We do not currently update anything in onMetaData. + +void FLV_MetaHandler::WriteTempFile ( XMP_IO* tempRef ) +{ + if ( ! this->needsUpdate ) return; + + XMP_AbortProc abortProc = this->parent->abortProc; + void * abortArg = this->parent->abortArg; + const bool checkAbort = (abortProc != 0); + + XMP_IO* originalRef = this->parent->ioRef; + + XMP_Uns64 sourceLen = originalRef->Length(); + XMP_Uns64 sourcePos = 0; + + originalRef->Rewind(); + tempRef->Rewind(); + tempRef->Truncate ( 0 ); + XMP_ProgressTracker* progressTracker = this->parent->progressTracker; + if ( progressTracker != 0 ) { + float fileSize=(float)(this->xmpPacket.size()+48); + if ( this->omdTagPos == 0 ) { + sourcePos=(this->flvHeaderLen+4); + fileSize+=sourcePos; + } else { + if ( this->xmpTagPos < this->omdTagPos ) { + fileSize+=this->xmpTagPos; + } + fileSize+=(this->omdTagPos + this->omdTagLen- + ((this->xmpTagPos!=0 && this->xmpTagPos < this->omdTagPos)? + (this->xmpTagPos + this->xmpTagLen):0)); + sourcePos =this->omdTagPos + this->omdTagLen; + } + if ( (this->xmpTagPos != 0) && (this->xmpTagPos >= sourcePos) ) { + fileSize+=(this->xmpTagPos - sourcePos); + sourcePos=this->xmpTagPos + this->xmpTagLen; + } + fileSize+=(sourceLen - sourcePos); + sourcePos=0; + progressTracker->BeginWork ( fileSize ); + } + // First do whatever is needed to put the new XMP after any existing onMetaData tag, or as the + // first time 0 tag. + + if ( this->omdTagPos == 0 ) { + + // There is no onMetaData tag. Copy the file header, then write the new XMP as the first tag. + // Allow the degenerate case of a file with just a header, no initial back pointer or tags. + + originalRef->Seek ( sourcePos, kXMP_SeekFromStart ); + XIO::Copy ( originalRef, tempRef, this->flvHeaderLen, abortProc, abortArg ); + + XMP_Uns32 zero = 0; // Ensure that the initial back offset really is zero. + tempRef->Write ( &zero, 4 ); + sourcePos = this->flvHeaderLen + 4; + + WriteOnXMP ( tempRef, this->xmpPacket ); + + } else { + + // There is an onMetaData tag. Copy the front of the file through the onMetaData tag, + // skipping any XMP that happens to be in the way. The XMP should not be before onMetaData, + // but let's be robust. Write the new XMP immediately after onMetaData, at the same time. + + XMP_Uns64 omdEnd = this->omdTagPos + this->omdTagLen; + + if ( (this->xmpTagPos != 0) && (this->xmpTagPos < this->omdTagPos) ) { + // The XMP tag was in front of the onMetaData tag. Copy up to it, then skip it. + originalRef->Seek ( sourcePos, kXMP_SeekFromStart ); + XIO::Copy ( originalRef, tempRef, this->xmpTagPos, abortProc, abortArg ); + sourcePos = this->xmpTagPos + this->xmpTagLen; // The tag length includes the trailing size field. + } + + // Copy through the onMetaData tag, then write the XMP. + originalRef->Seek ( sourcePos, kXMP_SeekFromStart ); + XIO::Copy ( originalRef, tempRef, (omdEnd - sourcePos), abortProc, abortArg ); + sourcePos = omdEnd; + + WriteOnXMP ( tempRef, this->xmpPacket ); + + } + + // Copy the rest of the file, skipping any XMP that is in the way. + + if ( (this->xmpTagPos != 0) && (this->xmpTagPos >= sourcePos) ) { + originalRef->Seek ( sourcePos, kXMP_SeekFromStart ); + XIO::Copy ( originalRef, tempRef, (this->xmpTagPos - sourcePos), abortProc, abortArg ); + sourcePos = this->xmpTagPos + this->xmpTagLen; + } + + originalRef->Seek ( sourcePos, kXMP_SeekFromStart ); + XIO::Copy ( originalRef, tempRef, (sourceLen - sourcePos), abortProc, abortArg ); + + this->needsUpdate = false; + + if ( progressTracker != 0 ) progressTracker->WorkComplete(); + +} // FLV_MetaHandler::WriteTempFile + +// ================================================================================================= diff --git a/core/libs/dngwriter/extra/xmp_sdk/XMPFiles/source/FileHandlers/FLV_Handler.hpp b/core/libs/dngwriter/extra/xmp_sdk/XMPFiles/source/FileHandlers/FLV_Handler.hpp new file mode 100644 index 0000000000..fe129deb60 --- /dev/null +++ b/core/libs/dngwriter/extra/xmp_sdk/XMPFiles/source/FileHandlers/FLV_Handler.hpp @@ -0,0 +1,81 @@ +#ifndef __FLV_Handler_hpp__ +#define __FLV_Handler_hpp__ 1 + +// ================================================================================================= +// ADOBE SYSTEMS INCORPORATED +// Copyright 2007 Adobe Systems Incorporated +// All Rights Reserved +// +// NOTICE: Adobe permits you to use, modify, and distribute this file in accordance with the terms +// of the Adobe license agreement accompanying it. +// ================================================================================================= + +#include "public/include/XMP_Environment.h" // ! XMP_Environment.h must be the first included header. + +#include "public/include/XMP_Const.h" +#include "public/include/XMP_IO.hpp" + +#include "XMPFiles/source/XMPFiles_Impl.hpp" +#include "source/XMPFiles_IO.hpp" + +// ================================================================================================ +/// \file FLV_Handler.hpp +/// \brief File format handler for FLV. +/// +/// This header ... +/// +// ================================================================================================ + +extern XMPFileHandler * FLV_MetaHandlerCTor ( XMPFiles * parent ); + +extern bool FLV_CheckFormat ( XMP_FileFormat format, + XMP_StringPtr filePath, + XMP_IO* fileRef, + XMPFiles * parent ); + +static const XMP_OptionBits kFLV_HandlerFlags = ( kXMPFiles_CanInjectXMP | + kXMPFiles_CanExpand | + kXMPFiles_CanRewrite | + kXMPFiles_PrefersInPlace | + kXMPFiles_CanReconcile | + kXMPFiles_AllowsOnlyXMP | + kXMPFiles_ReturnsRawPacket | + kXMPFiles_AllowsSafeUpdate | + kXMPFiles_CanNotifyProgress + ); + +class FLV_MetaHandler : public XMPFileHandler +{ +public: + + void CacheFileData(); + void ProcessXMP(); + + void UpdateFile ( bool doSafeUpdate ); + void WriteTempFile ( XMP_IO* tempRef ); + + + FLV_MetaHandler ( XMPFiles * _parent ); + virtual ~FLV_MetaHandler(); + +private: + + FLV_MetaHandler() : flvHeaderLen(0), longXMP(false), + xmpTagPos(0), omdTagPos(0), xmpTagLen(0), omdTagLen(0) {}; // Hidden on purpose. + + void ExtractLiveXML(); + void MakeLegacyDigest ( std::string * digestStr ); + + XMP_Uns32 flvHeaderLen; + bool longXMP; // True if the stored XMP is a long string (4 byte length). + + XMP_Uns64 xmpTagPos, omdTagPos; // The file offset and length of onXMP and onMetaData tags. + XMP_Uns32 xmpTagLen, omdTagLen; // Zero if the tag is not present. + + std::string onXMP, onMetaData; // ! Actually contains structured binary data. + +}; // FLV_MetaHandler + +// ================================================================================================= + +#endif // __FLV_Handler_hpp__ diff --git a/core/libs/dngwriter/extra/xmp_sdk/XMPFiles/source/FileHandlers/GIF_Handler.cpp b/core/libs/dngwriter/extra/xmp_sdk/XMPFiles/source/FileHandlers/GIF_Handler.cpp new file mode 100644 index 0000000000..ded9954bbe --- /dev/null +++ b/core/libs/dngwriter/extra/xmp_sdk/XMPFiles/source/FileHandlers/GIF_Handler.cpp @@ -0,0 +1,423 @@ +// ================================================================================================= +// ADOBE SYSTEMS INCORPORATED +// Copyright 2008 Adobe Systems Incorporated +// All Rights Reserved +// +// NOTICE: Adobe permits you to use, modify, and distribute this file in accordance with the terms +// of the Adobe license agreement accompanying it. +// +// This file includes implementation of GIF file metadata, according to GIF89a Specification. +// https://www.w3.org/Graphics/GIF/spec-gif89a.txt +// The Graphics Interchange Format(c) is the Copyright property of CompuServe Incorporated. +// GIF(sm) is a Service Mark property of CompuServe Incorporated. +// All Rights Reserved . http://www.w3.org/Consortium/Legal +// +// ================================================================================================= + +#include "public/include/XMP_Environment.h" // ! Must be the first #include! + +#include "XMPFiles/source/FileHandlers/GIF_Handler.hpp" +#include "source/XIO.hpp" + +// ================================================================================================= +/// \file GIF_Handler.hpp +/// \brief File format handler for GIF. +/// +/// This handler ... +/// +// ================================================================================================= + +// ================================================================================================= +// GIF_MetaHandlerCTor +// ==================== + +XMPFileHandler * GIF_MetaHandlerCTor ( XMPFiles * parent ) +{ + return new GIF_MetaHandler ( parent ); + +} // GIF_MetaHandlerCTor + +#define GIF_89_Header_LEN 6 +#define GIF_89_Header_DATA "\x47\x49\x46\x38\x39\x61" // must be GIF89a, nothing else as XMP is supported only in 89a version + +#define APP_ID_LEN 11 +#define XMP_APP_ID_DATA "\x58\x4D\x50\x20\x44\x61\x74\x61\x58\x4D\x50" + +#define MAGIC_TRAILER_LEN 258 + +// ================================================================================================= +// GIF_CheckFormat +// =============== + +bool GIF_CheckFormat ( XMP_FileFormat format, + XMP_StringPtr filePath, + XMP_IO* fileRef, + XMPFiles * parent ) +{ + IgnoreParam(format); IgnoreParam(fileRef); IgnoreParam(parent); + XMP_Assert ( format == kXMP_GIFFile ); + + if ( fileRef->Length() < GIF_89_Header_LEN ) return false; + XMP_Uns8 buffer[ GIF_89_Header_LEN ]; + + fileRef->Rewind(); + fileRef->Read( buffer, GIF_89_Header_LEN ); + if ( !CheckBytes( buffer, GIF_89_Header_DATA, GIF_89_Header_LEN ) ) return false; + + return true; + +} // GIF_CheckFormat + +// ================================================================================================= +// GIF_MetaHandler::GIF_MetaHandler +// ================================== + +GIF_MetaHandler::GIF_MetaHandler( XMPFiles * _parent ) : XMPPacketOffset( 0 ), XMPPacketLength( 0 ), trailerOffset( 0 ) +{ + this->parent = _parent; + this->handlerFlags = kGIF_HandlerFlags; + // It MUST be UTF-8. + this->stdCharForm = kXMP_Char8Bit; + +} + +// ================================================================================================= +// GIF_MetaHandler::~GIF_MetaHandler +// =================================== + +GIF_MetaHandler::~GIF_MetaHandler() +{ +} + +// ================================================================================================= +// GIF_MetaHandler::CacheFileData +// =============================== + +void GIF_MetaHandler::CacheFileData() +{ + this->containsXMP = false; + + XMP_IO * fileRef = this->parent->ioRef; + + // Try to navigate through the blocks to find the XMP block. + if ( this->ParseGIFBlocks( fileRef ) ) + { + // XMP packet present + this->xmpPacket.assign( XMPPacketLength, ' ' ); + + // 13 bytes for the block size and 2 bytes for Extension ID and Label + this->SeekFile( fileRef, XMPPacketOffset, kXMP_SeekFromStart ); + fileRef->ReadAll( ( void* )this->xmpPacket.data(), XMPPacketLength ); + + this->packetInfo.offset = XMPPacketOffset; + this->packetInfo.length = XMPPacketLength; + this->containsXMP = true; + } + // else no XMP + +} // GIF_MetaHandler::CacheFileData + +// ================================================================================================= +// GIF_MetaHandler::ProcessXMP +// =========================== +// +// Process the raw XMP and legacy metadata that was previously cached. + +void GIF_MetaHandler::ProcessXMP() +{ + this->processedXMP = true; // Make sure we only come through here once. + + // Process the XMP packet. + + if ( ! this->xmpPacket.empty() ) { + + XMP_Assert ( this->containsXMP ); + XMP_StringPtr packetStr = this->xmpPacket.c_str(); + XMP_StringLen packetLen = (XMP_StringLen) this->xmpPacket.size(); + + this->xmpObj.ParseFromBuffer ( packetStr, packetLen ); + + this->containsXMP = true; + + } + +} // GIF_MetaHandler::ProcessXMP + +// ================================================================================================= +// GIF_MetaHandler::ParseGIFBlocks +// =========================== + +bool GIF_MetaHandler::ParseGIFBlocks( XMP_IO* fileRef ) +{ + fileRef->Rewind(); + + // Checking for GIF header + XMP_Uns8 buffer[ GIF_89_Header_LEN ]; + + fileRef->Read( buffer, GIF_89_Header_LEN ); + XMP_Enforce( memcmp( buffer, GIF_89_Header_DATA, GIF_89_Header_LEN ) == 0 ); + + bool IsXMPExists = false; + bool IsTrailerExists = false; + + ReadLogicalScreenDesc( fileRef ); + + // Parsing rest of the blocks + while ( fileRef->Offset() != fileRef->Length() ) + { + XMP_Uns8 blockType; + + // Read the block type byte + fileRef->Read( &blockType, 1 ); + + if ( blockType == kXMP_block_ImageDesc ) + { + + // ImageDesc is a special case, So read data just like its structure. + long tableSize = 0; + XMP_Uns8 fields; + // Reading Dimesnions of image as + // 2 bytes = Image Left Position + // + 2 bytes = Image Right Position + // + 2 bytes = Image Width + // + 2 bytes = Image Height + // = 8 bytes + this->SeekFile( fileRef, 8, kXMP_SeekFromCurrent ); + + // Reading one byte for Packed Fields + fileRef->Read( &fields, 1 ); + + // Getting Local Table Size and skipping table size + if ( fields & 0x80 ) + { + tableSize = ( 1 << ( ( fields & 0x07 ) + 1 ) ) * 3; + this->SeekFile( fileRef, tableSize, kXMP_SeekFromCurrent ); + } + + // 1 byte LZW Minimum code size + this->SeekFile( fileRef, 1, kXMP_SeekFromCurrent ); + + XMP_Uns8 subBlockSize; + // 1 byte compressed sub-block size + fileRef->Read( &subBlockSize, 1 ); + + while ( subBlockSize != 0x00 ) + { + // Skipping compressed data sub-block + this->SeekFile( fileRef, subBlockSize, kXMP_SeekFromCurrent ); + + // 1 byte compressed sub-block size + fileRef->Read( &subBlockSize, 1 ); + } + + } + else if ( blockType == kXMP_block_Extension ) + { + XMP_Uns8 extensionLbl; + XMP_Uns32 blockSize = 0; + XMP_Uns64 blockOffset = fileRef->Offset(); + + // Extension Label + fileRef->Read( &extensionLbl, 1 ); + + // Block or Sub-Block size + fileRef->Read( &blockSize, 1 ); + + // Checking for Application Extension label and blockSize + if ( extensionLbl == 0xFF && blockSize == APP_ID_LEN ) + { + XMP_Uns8 idData[ APP_ID_LEN ]; + fileRef->Read( idData, APP_ID_LEN, true ); + + // Checking For XMP ID + if ( memcmp( idData, XMP_APP_ID_DATA, APP_ID_LEN ) == 0 ) + { + XMPPacketOffset = fileRef->Offset(); + IsXMPExists = true; + } + + // Parsing sub-blocks + XMP_Uns8 subBlockSize; + fileRef->Read( &subBlockSize, 1 ); + while ( subBlockSize != 0x00 ) + { + this->SeekFile( fileRef, subBlockSize, kXMP_SeekFromCurrent ); + fileRef->Read( &subBlockSize, 1 ); + } + if ( IsXMPExists ) + XMPPacketLength = static_cast< XMP_Uns32 >( fileRef->Offset() - XMPPacketOffset - MAGIC_TRAILER_LEN ); + } + else + { + // Extension block other than Application Extension + while ( blockSize != 0x00 ) + { + // Seeking block size or sub-block size + this->SeekFile( fileRef, blockSize, kXMP_SeekFromCurrent ); + + // Block Size + fileRef->Read( &blockSize, 1 ); + } + } + } + else if ( blockType == kXMP_block_Trailer ) + { + // 1 byte is subtracted for block type + trailerOffset = fileRef->Offset() - 1; + IsTrailerExists = true; + break; + } + else + XMP_Throw( "Invaild GIF Block", kXMPErr_BadBlockFormat ); + } + + if ( !IsTrailerExists ) + XMP_Throw( "No trailer exists for GIF file", kXMPErr_BadFileFormat ); + + return IsXMPExists; + +} // GIF_MetaHandler::ParseGIFBlocks + +// ================================================================================================= +// GIF_MetaHandler::ReadLogicalScreenDesc +// =========================== + +void GIF_MetaHandler::ReadLogicalScreenDesc( XMP_IO* fileRef ) +{ + XMP_Uns8 fields; + + // 2 bytes for Screen Width + // + 2 bytes for Screen Height + // = 4 Bytes + this->SeekFile( fileRef, 4, kXMP_SeekFromCurrent ); + + // 1 byte for Packed Fields + fileRef->Read( &fields, 1 ); + + // 1 byte for Background Color Index + // + 1 byte for Pixel Aspect Ratio + // = 2 bytes + this->SeekFile( fileRef, 2, kXMP_SeekFromCurrent ); + + // Look for Global Color Table if exists + if ( fields & 0x80 ) + { + long tableSize = ( 1 << ( ( fields & 0x07 ) + 1 ) ) * 3; + this->SeekFile( fileRef, tableSize, kXMP_SeekFromCurrent ); + } + +} // GIF_MetaHandler::ReadLogicalScreenDesc + +// ================================================================================================= +// GIF_MetaHandler::SeekFile +// =========================== + +void GIF_MetaHandler::SeekFile( XMP_IO * fileRef, XMP_Int64 offset, SeekMode mode ) +{ + if ( offset > fileRef->Length() || ( mode == kXMP_SeekFromCurrent && fileRef->Offset() + offset > fileRef->Length() ) ) + { + XMP_Throw( "Out of range seek operation", kXMPErr_InternalFailure ); + } + else + fileRef->Seek( offset, mode ); + +} // GIF_MetaHandler::SeekFile + +// ================================================================================================= +// GIF_MetaHandler::UpdateFile +// =========================== + +void GIF_MetaHandler::UpdateFile ( bool doSafeUpdate ) +{ + XMP_Assert( !doSafeUpdate ); // This should only be called for "unsafe" updates. + + if ( ! this->needsUpdate ) return; + + XMP_IO * fileRef = this->parent->ioRef; + + XMP_StringPtr packetStr = xmpPacket.c_str(); + XMP_StringLen newPacketLength = (XMP_StringLen)xmpPacket.size(); + + if ( newPacketLength == XMPPacketLength ) + { + this->SeekFile( fileRef, this->packetInfo.offset, kXMP_SeekFromStart ); + fileRef->Write( this->xmpPacket.c_str(), newPacketLength ); + } + else + { + XMP_IO* tempFile = fileRef->DeriveTemp(); + if ( tempFile == 0 ) XMP_Throw( "Failure creating GIF temp file", kXMPErr_InternalFailure ); + + this->WriteTempFile( tempFile ); + fileRef->AbsorbTemp(); + } + + this->needsUpdate = false; + +} // GIF_MetaHandler::UpdateFile + +// ================================================================================================= +// GIF_MetaHandler::WriteTempFile +// ============================== + +void GIF_MetaHandler::WriteTempFile ( XMP_IO* tempRef ) +{ + XMP_Assert( this->needsUpdate ); + + XMP_IO* originalRef = this->parent->ioRef; + originalRef->Rewind(); + + tempRef->Truncate ( 0 ); + + if ( XMPPacketOffset != 0 ) + { + // Copying blocks before XMP Application Block + XIO::Copy( originalRef, tempRef, XMPPacketOffset ); + + // Writing XMP Packet + tempRef->Write( this->xmpPacket.c_str(), (XMP_Uns32)this->xmpPacket.size() ); + + // Copying Rest of the file + originalRef->Seek( XMPPacketLength, kXMP_SeekFromCurrent ); + XIO::Copy( originalRef, tempRef, originalRef->Length() - originalRef->Offset() ); + + } + else + { + if ( trailerOffset == 0 ) + XMP_Throw( "Not able to write XMP packet in GIF file", kXMPErr_BadFileFormat ); + + // Copying blocks before XMP Application Block + XIO::Copy( originalRef, tempRef, trailerOffset ); + + // Writing Extension Introducer + XIO::WriteUns8( tempRef, kXMP_block_Extension ); + + // Writing Application Extension label + XIO::WriteUns8( tempRef, 0xFF ); + + // Writing Application Extension label + XIO::WriteUns8( tempRef, APP_ID_LEN ); + + // Writing Application Extension label + tempRef->Write( XMP_APP_ID_DATA, APP_ID_LEN ); + + // Writing XMP Packet + tempRef->Write( this->xmpPacket.c_str(), (XMP_Uns32)this->xmpPacket.size() ); + + // Writing Magic trailer + XMP_Uns8 magicByte = 0x01; + tempRef->Write( &magicByte, 1 ); + for ( magicByte = 0xFF; magicByte != 0x00; --magicByte ) + tempRef->Write( &magicByte, 1 ); + tempRef->Write( &magicByte, 1 ); + tempRef->Write( &magicByte, 1 ); + + // Copying Rest of the file + XIO::Copy( originalRef, tempRef, originalRef->Length() - originalRef->Offset() ); + + } + +} // GIF_MetaHandler::WriteTempFile + +// ================================================================================================= diff --git a/core/libs/dngwriter/extra/xmp_sdk/XMPFiles/source/FileHandlers/GIF_Handler.hpp b/core/libs/dngwriter/extra/xmp_sdk/XMPFiles/source/FileHandlers/GIF_Handler.hpp new file mode 100644 index 0000000000..10ef3f9db9 --- /dev/null +++ b/core/libs/dngwriter/extra/xmp_sdk/XMPFiles/source/FileHandlers/GIF_Handler.hpp @@ -0,0 +1,90 @@ +#ifndef __GIF_Handler_hpp__ +#define __GIF_Handler_hpp__ 1 + +// ================================================================================================= +// ADOBE SYSTEMS INCORPORATED +// Copyright 2008 Adobe Systems Incorporated +// All Rights Reserved +// +// NOTICE: Adobe permits you to use, modify, and distribute this file in accordance with the terms +// of the Adobe license agreement accompanying it. +// +// This file includes implementation of GIF file metadata, according to GIF89a Specification. +// https://www.w3.org/Graphics/GIF/spec-gif89a.txt +// The Graphics Interchange Format(c) is the Copyright property of CompuServe Incorporated. +// GIF(sm) is a Service Mark property of CompuServe Incorporated. +// All Rights Reserved . http://www.w3.org/Consortium/Legal +// +//Derived from PNG_Handler.hpp by Ian Jacobi +// ================================================================================================= + +#include "public/include/XMP_Environment.h" // ! Must be the first #include! + +#include "public/include/XMP_Const.h" +#include "public/include/XMP_IO.hpp" + +#include "XMPFiles/source/XMPFiles_Impl.hpp" + +// ================================================================================================= +/// \file GIF_Handler.hpp +/// \brief File format handler for GIF. +/// +/// This header ... +/// +// ================================================================================================= + +// *** Could derive from Basic_Handler - buffer file tail in a temp file. + +extern XMPFileHandler* GIF_MetaHandlerCTor ( XMPFiles* parent ); + +extern bool GIF_CheckFormat ( XMP_FileFormat format, + XMP_StringPtr filePath, + XMP_IO* fileRef, + XMPFiles* parent ); + +static const XMP_OptionBits kGIF_HandlerFlags = ( kXMPFiles_CanInjectXMP | + kXMPFiles_CanExpand | + kXMPFiles_PrefersInPlace | + kXMPFiles_AllowsOnlyXMP | + kXMPFiles_ReturnsRawPacket | + kXMPFiles_NeedsReadOnlyPacket + ); + +class GIF_MetaHandler : public XMPFileHandler +{ +public: + + void CacheFileData(); + void ProcessXMP(); + + void UpdateFile ( bool doSafeUpdate ); + void WriteTempFile ( XMP_IO* tempRef ); + + bool SafeWriteFile (); + + GIF_MetaHandler ( XMPFiles* parent ); + virtual ~GIF_MetaHandler(); + +private: + + enum GIFBlockType + { + kXMP_block_ImageDesc = 0x2C, + kXMP_block_Extension = 0x21, + kXMP_block_Trailer = 0x3B, + kXMP_block_Header = 0x47 + }; + + XMP_Uns64 XMPPacketOffset; + XMP_Uns32 XMPPacketLength; + XMP_Uns64 trailerOffset; + + bool ParseGIFBlocks( XMP_IO * fileRef ); + void ReadLogicalScreenDesc( XMP_IO* fileRef ); + void SeekFile( XMP_IO * fileRef, XMP_Int64 offset, SeekMode mode ); + +}; // GIF_MetaHandler + +// ================================================================================================= + +#endif /* __GIF_Handler_hpp__ */ diff --git a/core/libs/dngwriter/extra/xmp_sdk/XMPFiles/source/FileHandlers/InDesign_Handler.cpp b/core/libs/dngwriter/extra/xmp_sdk/XMPFiles/source/FileHandlers/InDesign_Handler.cpp new file mode 100644 index 0000000000..d6275808ad --- /dev/null +++ b/core/libs/dngwriter/extra/xmp_sdk/XMPFiles/source/FileHandlers/InDesign_Handler.cpp @@ -0,0 +1,422 @@ +// ================================================================================================= +// ADOBE SYSTEMS INCORPORATED +// Copyright 2004 Adobe Systems Incorporated +// All Rights Reserved +// +// NOTICE: Adobe permits you to use, modify, and distribute this file in accordance with the terms +// of the Adobe license agreement accompanying it. +// ================================================================================================= + +#include "public/include/XMP_Environment.h" // ! XMP_Environment.h must be the first included header. +#include "public/include/XMP_Const.h" + +#include "XMPFiles/source/FileHandlers/InDesign_Handler.hpp" + +#include "source/XIO.hpp" + +using namespace std; + +// ================================================================================================= +/// \file InDesign_Handler.cpp +/// \brief File format handler for InDesign files. +/// +/// This header ... +/// +/// The layout of an InDesign file in terms of the Basic_MetaHandler model is: +/// +/// \li The front of the file. This is everything up to the XMP contiguous object section. The file +/// starts with a pair of master pages, followed by the data pages, followed by contiguous object +/// sections, finished with padding to a page boundary. +/// +/// \li A prefix for the XMP section. This is the contiguous object header. The offset is +/// (this->packetInfo.offset - this->xmpPrefixSize). +/// +/// \li The XMP packet. The offset is this->packetInfo.offset. +/// +/// \li A suffix for the XMP section. This is the contiguous object header. The offset is +/// (this->packetInfo.offset + this->packetInfo.length). +/// +/// \li Trailing file content. This is the contiguous objects that follow the XMP. The offset is +/// (this->packetInfo.offset + this->packetInfo.length + this->xmpSuffixSize). +/// +/// \li The back of the file. This is the final padding to a page boundary. The offset is +/// (this->packetInfo.offset + this->packetInfo.length + this->xmpSuffixSize + this->trailingContentSize). +/// +// ================================================================================================= + +// *** Add PutXMP overrides that throw if the file does not contain XMP. + +#ifndef TraceInDesignHandler + #define TraceInDesignHandler 0 +#endif + +enum { kInDesignGUIDSize = 16 }; + +struct InDesignMasterPage { + XMP_Uns8 fGUID [kInDesignGUIDSize]; + XMP_Uns8 fMagicBytes [8]; + XMP_Uns8 fObjectStreamEndian; + XMP_Uns8 fIrrelevant1 [239]; + XMP_Uns64 fSequenceNumber; + XMP_Uns8 fIrrelevant2 [8]; + XMP_Uns32 fFilePages; + XMP_Uns8 fIrrelevant3 [3812]; +}; + +enum { + kINDD_PageSize = 4096, + kINDD_PageMask = (kINDD_PageSize - 1), + kINDD_LittleEndian = 1, + kINDD_BigEndian = 2 }; + +struct InDesignContigObjMarker { + XMP_Uns8 fGUID [kInDesignGUIDSize]; + XMP_Uns32 fObjectUID; + XMP_Uns32 fObjectClassID; + XMP_Uns32 fStreamLength; + XMP_Uns32 fChecksum; +}; + +static const XMP_Uns8 * kINDD_MasterPageGUID = + (const XMP_Uns8 *) "\x06\x06\xED\xF5\xD8\x1D\x46\xE5\xBD\x31\xEF\xE7\xFE\x74\xB7\x1D"; + +static const XMP_Uns8 * kINDDContigObjHeaderGUID = + (const XMP_Uns8 *) "\xDE\x39\x39\x79\x51\x88\x4B\x6C\x8E\x63\xEE\xF8\xAE\xE0\xDD\x38"; + +static const XMP_Uns8 * kINDDContigObjTrailerGUID = + (const XMP_Uns8 *) "\xFD\xCE\xDB\x70\xF7\x86\x4B\x4F\xA4\xD3\xC7\x28\xB3\x41\x71\x06"; + +// ================================================================================================= +// InDesign_MetaHandlerCTor +// ======================== + +XMPFileHandler * InDesign_MetaHandlerCTor ( XMPFiles * parent ) +{ + return new InDesign_MetaHandler ( parent ); + +} // InDesign_MetaHandlerCTor + +// ================================================================================================= +// InDesign_CheckFormat +// ==================== +// +// For InDesign we check that the pair of master pages begin with the 16 byte GUID. + +bool InDesign_CheckFormat ( XMP_FileFormat format, + XMP_StringPtr filePath, + XMP_IO* fileRef, + XMPFiles * parent ) +{ + IgnoreParam(format); IgnoreParam(filePath); IgnoreParam(parent); + XMP_Assert ( format == kXMP_InDesignFile ); + XMP_Assert ( strlen ( (const char *) kINDD_MasterPageGUID ) == kInDesignGUIDSize ); + + enum { kBufferSize = 2*kINDD_PageSize }; + XMP_Uns8 buffer [kBufferSize]; + + XMP_Int64 filePos = 0; + XMP_Uns8 * bufPtr = buffer; + XMP_Uns8 * bufLimit = bufPtr + kBufferSize; + + fileRef->Rewind(); + size_t bufLen = fileRef->Read ( buffer, kBufferSize ); + if ( bufLen != kBufferSize ) return false; + + if ( ! CheckBytes ( bufPtr, kINDD_MasterPageGUID, kInDesignGUIDSize ) ) return false; + if ( ! CheckBytes ( bufPtr+kINDD_PageSize, kINDD_MasterPageGUID, kInDesignGUIDSize ) ) return false; + + return true; + +} // InDesign_CheckFormat + +// ================================================================================================= +// InDesign_MetaHandler::InDesign_MetaHandler +// ========================================== + +InDesign_MetaHandler::InDesign_MetaHandler ( XMPFiles * _parent ) : streamBigEndian(0), xmpObjID(0), xmpClassID(0) +{ + this->parent = _parent; + this->handlerFlags = kInDesign_HandlerFlags; + this->stdCharForm = kXMP_Char8Bit; + +} // InDesign_MetaHandler::InDesign_MetaHandler + +// ================================================================================================= +// InDesign_MetaHandler::~InDesign_MetaHandler +// =========================================== + +InDesign_MetaHandler::~InDesign_MetaHandler() +{ + // Nothing to do here. + +} // InDesign_MetaHandler::~InDesign_MetaHandler + +// ================================================================================================= +// InDesign_MetaHandler::CacheFileData +// =================================== +// +// Look for the XMP in an InDesign database file. This is a paged database using 4K byte pages, +// followed by redundant "contiguous object streams". Each contiguous object stream is a copy of a +// database object stored as a contiguous byte stream. The XMP that we want is one of these. +// +// The first 2 pages of the database are alternating master pages. A generation number is used to +// select the active master page. The master page contains an offset to the start of the contiguous +// object streams. Each of the contiguous object streams contains a header and trailer, allowing +// fast motion from one stream to the next. +// +// There is no unique "what am I" tagging to the contiguous object streams, so we simply pick the +// first one that looks right. At present this is a 4 byte little endian packet size followed by the +// packet. + +// ! Note that insertion of XMP is not allowed for InDesign, the XMP must be a contiguous copy of an +// ! internal database object. So we don't set the packet offset to an insertion point if not found. + +void InDesign_MetaHandler::CacheFileData() +{ + XMP_IO* fileRef = this->parent->ioRef; + XMP_PacketInfo & packetInfo = this->packetInfo; + + XMP_Assert ( kINDD_PageSize == sizeof(InDesignMasterPage) ); + static const size_t kBufferSize = (2 * kINDD_PageSize); + XMP_Uns8 buffer [kBufferSize]; + + size_t dbPages; + XMP_Uns8 cobjEndian; + + XMP_AbortProc abortProc = this->parent->abortProc; + void * abortArg = this->parent->abortArg; + const bool checkAbort = (abortProc != 0); + + this->containsXMP = false; + + // --------------------------------------------------------------------------------- + // Figure out which master page is active and seek to the contiguous object portion. + + { + fileRef->Rewind(); + fileRef->ReadAll ( buffer, (2 * kINDD_PageSize) ); + + InDesignMasterPage * masters = (InDesignMasterPage *) &buffer[0]; + XMP_Uns64 seq0 = GetUns64LE ( (XMP_Uns8 *) &masters[0].fSequenceNumber ); + XMP_Uns64 seq1 = GetUns64LE ( (XMP_Uns8 *) &masters[1].fSequenceNumber ); + + dbPages = GetUns32LE ( (XMP_Uns8 *) &masters[0].fFilePages ); + cobjEndian = masters[0].fObjectStreamEndian; + if ( seq1 > seq0 ) { + dbPages = GetUns32LE ( (XMP_Uns8 *) &masters[1].fFilePages ); + cobjEndian = masters[1].fObjectStreamEndian; + } + } + + XMP_Assert ( ! this->streamBigEndian ); + if ( cobjEndian == kINDD_BigEndian ) this->streamBigEndian = true; + + // --------------------------------------------------------------------------------------------- + // Look for the XMP contiguous object. Each contiguous object has a header and trailer, both of + // the InDesignContigObjMarker structure. The stream size in the header/trailer is the number of + // data bytes between the header and trailer. The XMP stream begins with a 4 byte size of the + // XMP packet. Yes, this is the contiguous object data size minus 4 - silly but true. The XMP + // must have a packet wrapper, the leading xpacket PI is used as the marker of XMP. + + XMP_Int64 cobjPos = (XMP_Int64)dbPages * kINDD_PageSize; // ! Use a 64 bit multiply! + cobjPos -= (2 * sizeof(InDesignContigObjMarker)); // ! For the first pass in the loop. + XMP_Uns32 streamLength = 0; // ! For the first pass in the loop. + + while ( true ) { + + if ( checkAbort && abortProc(abortArg) ) { + XMP_Throw ( "InDesign_MetaHandler::LocateXMP - User abort", kXMPErr_UserAbort ); + } + + // Fetch the start of the next stream and check the contiguous object header. + // ! The writeable bit of fObjectClassID is ignored, we use the packet trailer flag. + + cobjPos += streamLength + (2 * sizeof(InDesignContigObjMarker)); + fileRef->Seek ( cobjPos, kXMP_SeekFromStart ); + fileRef->ReadAll ( buffer, sizeof(InDesignContigObjMarker) ); + + const InDesignContigObjMarker * cobjHeader = (const InDesignContigObjMarker *) &buffer[0]; + if ( ! CheckBytes ( Uns8Ptr(&cobjHeader->fGUID), kINDDContigObjHeaderGUID, kInDesignGUIDSize ) ) break; // Not a contiguous object header. + this->xmpObjID = cobjHeader->fObjectUID; // Save these now while the buffer is good. + this->xmpClassID = cobjHeader->fObjectClassID; + streamLength = GetUns32LE ( (XMP_Uns8 *) &cobjHeader->fStreamLength ); + + // See if this is the XMP stream. + + if ( streamLength < (4 + kUTF8_PacketHeaderLen + kUTF8_PacketTrailerLen) ) continue; // Too small, can't possibly be XMP. + + fileRef->ReadAll ( buffer, (4 + kUTF8_PacketHeaderLen) ); + XMP_Uns32 innerLength = GetUns32LE ( &buffer[0] ); + if ( this->streamBigEndian ) innerLength = GetUns32BE ( &buffer[0] ); + if ( innerLength != (streamLength - 4) ) { + // Be tolerant of a mistake with the endian flag. + innerLength = Flip4 ( innerLength ); + if ( innerLength != (streamLength - 4) ) continue; // Not legit XMP. + } + + XMP_Uns8 * chPtr = &buffer[4]; + size_t startLen = strlen((char*)kUTF8_PacketStart); + size_t idLen = strlen((char*)kUTF8_PacketID); + + if ( ! CheckBytes ( chPtr, kUTF8_PacketStart, startLen ) ) continue; + chPtr += startLen; + + XMP_Uns8 quote = *chPtr; + if ( (quote != '\'') && (quote != '"') ) continue; + chPtr += 1; + if ( *chPtr != quote ) { + if ( ! CheckBytes ( chPtr, Uns8Ptr("\xEF\xBB\xBF"), 3 ) ) continue; + chPtr += 3; + } + if ( *chPtr != quote ) continue; + chPtr += 1; + + if ( ! CheckBytes ( chPtr, Uns8Ptr(" id="), 4 ) ) continue; + chPtr += 4; + quote = *chPtr; + if ( (quote != '\'') && (quote != '"') ) continue; + chPtr += 1; + if ( ! CheckBytes ( chPtr, kUTF8_PacketID, idLen ) ) continue; + chPtr += idLen; + if ( *chPtr != quote ) continue; + chPtr += 1; + + // We've seen enough, it is the XMP. To fit the Basic_Handler model we need to compute the + // total size of remaining contiguous objects, the trailingContentSize. We don't use the + // size to EOF, that would wrongly include the final zero padding for 4KB alignment. + + this->xmpPrefixSize = sizeof(InDesignContigObjMarker) + 4; + this->xmpSuffixSize = sizeof(InDesignContigObjMarker); + packetInfo.offset = cobjPos + this->xmpPrefixSize; + packetInfo.length = innerLength; + + XMP_Int64 tcStart = cobjPos + streamLength + (2 * sizeof(InDesignContigObjMarker)); + while ( true ) { + if ( checkAbort && abortProc(abortArg) ) { + XMP_Throw ( "InDesign_MetaHandler::LocateXMP - User abort", kXMPErr_UserAbort ); + } + cobjPos += streamLength + (2 * sizeof(InDesignContigObjMarker)); + XMP_Uns32 len = fileRef->Read ( buffer, sizeof(InDesignContigObjMarker) ); + if ( len < sizeof(InDesignContigObjMarker) ) break; // Too small, must be end of file. + cobjHeader = (const InDesignContigObjMarker *) &buffer[0]; + if ( ! CheckBytes ( Uns8Ptr(&cobjHeader->fGUID), kINDDContigObjHeaderGUID, kInDesignGUIDSize ) ) break; // Not a contiguous object header. + streamLength = GetUns32LE ( (XMP_Uns8 *) &cobjHeader->fStreamLength ); + } + this->trailingContentSize = cobjPos - tcStart; + + #if TraceInDesignHandler + XMP_Uns32 pktOffset = (XMP_Uns32)this->packetInfo.offset; + printf ( "Found XMP in InDesign file, offsets:\n" ); + printf ( " CObj head %X, XMP %X, CObj tail %X, file tail %X, padding %X\n", + (pktOffset - this->xmpPrefixSize), pktOffset, (pktOffset + this->packetInfo.length), + (pktOffset + this->packetInfo.length + this->xmpSuffixSize), + (pktOffset + this->packetInfo.length + this->xmpSuffixSize + (XMP_Uns32)this->trailingContentSize) ); + #endif + + this->containsXMP = true; + break; + + } + + if ( this->containsXMP ) { + this->xmpFileOffset = packetInfo.offset; + this->xmpFileSize = packetInfo.length; + ReadXMPPacket ( this ); + } + +} // InDesign_MetaHandler::CacheFileData + +// ================================================================================================= +// InDesign_MetaHandler::WriteXMPPrefix +// ==================================== + +void InDesign_MetaHandler::WriteXMPPrefix ( XMP_IO* fileRef ) +{ + // Write the contiguous object header and the 4 byte length of the XMP packet. + + XMP_Uns32 packetSize = (XMP_Uns32)this->xmpPacket.size(); + + InDesignContigObjMarker header; + memcpy ( header.fGUID, kINDDContigObjHeaderGUID, sizeof(header.fGUID) ); // AUDIT: Use of dest sizeof for length is safe. + header.fObjectUID = this->xmpObjID; + header.fObjectClassID = this->xmpClassID; + header.fStreamLength = MakeUns32LE ( 4 + packetSize ); + header.fChecksum = (XMP_Uns32)(-1); + fileRef->Write ( &header, sizeof(header) ); + + XMP_Uns32 pktLength = MakeUns32LE ( packetSize ); + if ( this->streamBigEndian ) pktLength = MakeUns32BE ( packetSize ); + fileRef->Write ( &pktLength, sizeof(pktLength) ); + +} // InDesign_MetaHandler::WriteXMPPrefix + +// ================================================================================================= +// InDesign_MetaHandler::WriteXMPSuffix +// ==================================== + +void InDesign_MetaHandler::WriteXMPSuffix ( XMP_IO* fileRef ) +{ + // Write the contiguous object trailer. + + XMP_Uns32 packetSize = (XMP_Uns32)this->xmpPacket.size(); + + InDesignContigObjMarker trailer; + + memcpy ( trailer.fGUID, kINDDContigObjTrailerGUID, sizeof(trailer.fGUID) ); // AUDIT: Use of dest sizeof for length is safe. + trailer.fObjectUID = this->xmpObjID; + trailer.fObjectClassID = this->xmpClassID; + trailer.fStreamLength = MakeUns32LE ( 4 + packetSize ); + trailer.fChecksum = (XMP_Uns32)(-1); + + fileRef->Write ( &trailer, sizeof(trailer) ); + +} // InDesign_MetaHandler::WriteXMPSuffix + +// ================================================================================================= +// InDesign_MetaHandler::NoteXMPRemoval +// ==================================== + +void InDesign_MetaHandler::NoteXMPRemoval ( XMP_IO* fileRef ) +{ + // Nothing to do. + +} // InDesign_MetaHandler::NoteXMPRemoval + +// ================================================================================================= +// InDesign_MetaHandler::NoteXMPInsertion +// ====================================== + +void InDesign_MetaHandler::NoteXMPInsertion ( XMP_IO* fileRef ) +{ + // Nothing to do. + +} // InDesign_MetaHandler::NoteXMPInsertion + +// ================================================================================================= +// InDesign_MetaHandler::CaptureFileEnding +// ======================================= + +void InDesign_MetaHandler::CaptureFileEnding ( XMP_IO* fileRef ) +{ + // Nothing to do. The back of an InDesign file is the final zero padding. + +} // InDesign_MetaHandler::CaptureFileEnding + +// ================================================================================================= +// InDesign_MetaHandler::RestoreFileEnding +// ======================================= + +void InDesign_MetaHandler::RestoreFileEnding ( XMP_IO* fileRef ) +{ + // Pad the file with zeros to a page boundary. + + XMP_Int64 dataLength = fileRef->Length(); + XMP_Int32 padLength = (kINDD_PageSize - ((XMP_Int32)dataLength & kINDD_PageMask)) & kINDD_PageMask; + + XMP_Uns8 buffer [kINDD_PageSize]; + memset ( buffer, 0, kINDD_PageSize ); + fileRef->Write ( buffer, padLength ); + +} // InDesign_MetaHandler::RestoreFileEnding + +// ================================================================================================= diff --git a/core/libs/dngwriter/extra/xmp_sdk/XMPFiles/source/FileHandlers/InDesign_Handler.hpp b/core/libs/dngwriter/extra/xmp_sdk/XMPFiles/source/FileHandlers/InDesign_Handler.hpp new file mode 100644 index 0000000000..66d9f7900d --- /dev/null +++ b/core/libs/dngwriter/extra/xmp_sdk/XMPFiles/source/FileHandlers/InDesign_Handler.hpp @@ -0,0 +1,68 @@ +#ifndef __InDesign_Handler_hpp__ +#define __InDesign_Handler_hpp__ 1 + +// ================================================================================================= +// ADOBE SYSTEMS INCORPORATED +// Copyright 2004 Adobe Systems Incorporated +// All Rights Reserved +// +// NOTICE: Adobe permits you to use, modify, and distribute this file in accordance with the terms +// of the Adobe license agreement accompanying it. +// ================================================================================================= + +#include "public/include/XMP_Environment.h" // ! XMP_Environment.h must be the first included header. + +#include "public/include/XMP_Const.h" +#include "public/include/XMP_IO.hpp" + +#include "XMPFiles/source/XMPFiles_Impl.hpp" +#include "source/XMPFiles_IO.hpp" + +#include "XMPFiles/source/FileHandlers/Basic_Handler.hpp" + +// ================================================================================================= +/// \file InDesign_Handler.hpp +/// \brief File format handler for InDesign files. +/// +/// This header ... +/// +// ================================================================================================= + +extern XMPFileHandler * InDesign_MetaHandlerCTor ( XMPFiles * parent ); + +extern bool InDesign_CheckFormat ( XMP_FileFormat format, + XMP_StringPtr filePath, + XMP_IO* fileRef, + XMPFiles * parent ); + +static const XMP_OptionBits kInDesign_HandlerFlags = kBasic_HandlerFlags & (~kXMPFiles_CanInjectXMP); // ! InDesign can't inject. + +class InDesign_MetaHandler : public Basic_MetaHandler +{ +public: + + InDesign_MetaHandler ( XMPFiles * parent ); + ~InDesign_MetaHandler(); + + void CacheFileData(); + +protected: + + void WriteXMPPrefix ( XMP_IO* fileRef ); + void WriteXMPSuffix ( XMP_IO* fileRef ); + + void NoteXMPRemoval ( XMP_IO* fileRef ); + void NoteXMPInsertion ( XMP_IO* fileRef ); + + void CaptureFileEnding ( XMP_IO* fileRef ); + void RestoreFileEnding ( XMP_IO* fileRef ); + + bool streamBigEndian; // Set from master page's fObjectStreamEndian. + XMP_Uns32 xmpObjID; // Set from contiguous object's fObjectID, still as little endian. + XMP_Uns32 xmpClassID; // Set from contiguous object's fObjectClassID, still as little endian. + +}; // InDesign_MetaHandler + +// ================================================================================================= + +#endif /* __InDesign_Handler_hpp__ */ diff --git a/core/libs/dngwriter/extra/xmp_sdk/XMPFiles/source/FileHandlers/JPEG_Handler.cpp b/core/libs/dngwriter/extra/xmp_sdk/XMPFiles/source/FileHandlers/JPEG_Handler.cpp new file mode 100644 index 0000000000..373734c27b --- /dev/null +++ b/core/libs/dngwriter/extra/xmp_sdk/XMPFiles/source/FileHandlers/JPEG_Handler.cpp @@ -0,0 +1,988 @@ +// ================================================================================================= +// ADOBE SYSTEMS INCORPORATED +// Copyright 2004 Adobe Systems Incorporated +// All Rights Reserved +// +// NOTICE: Adobe permits you to use, modify, and distribute this file in accordance with the terms +// of the Adobe license agreement accompanying it. +// ================================================================================================= + +#include "public/include/XMP_Environment.h" // ! This must be the first include. +#include "public/include/XMP_Const.h" +#include "public/include/XMP_IO.hpp" + +#include "XMPFiles/source/XMPFiles_Impl.hpp" +#include "source/XIO.hpp" + +#include "XMPFiles/source/FileHandlers/JPEG_Handler.hpp" + +#include "XMPFiles/source/FormatSupport/TIFF_Support.hpp" +#include "XMPFiles/source/FormatSupport/PSIR_Support.hpp" +#include "XMPFiles/source/FormatSupport/IPTC_Support.hpp" +#include "XMPFiles/source/FormatSupport/ReconcileLegacy.hpp" +#include "XMPFiles/source/FormatSupport/Reconcile_Impl.hpp" + +#include "XMP_MD5.h" + +#include + +using namespace std; + +// ================================================================================================= +/// \file JPEG_Handler.cpp +/// \brief File format handler for JPEG. +/// +/// This handler ... +/// +// ================================================================================================= + +static const char * kExifSignatureString = "Exif\0\x00"; // There are supposed to be two zero bytes, +static const char * kExifSignatureAltStr = "Exif\0\xFF"; // but files have been seen with just one. +static const size_t kExifSignatureLength = 6; +static const size_t kExifMaxDataLength = 0xFFFF - 2 - kExifSignatureLength; + +static const char * kPSIRSignatureString = "Photoshop 3.0\0"; +static const size_t kPSIRSignatureLength = 14; +static const size_t kPSIRMaxDataLength = 0xFFFF - 2 - kPSIRSignatureLength; + +static const char * kMainXMPSignatureString = "http://ns.adobe.com/xap/1.0/\0"; +static const size_t kMainXMPSignatureLength = 29; + +static const char * kExtXMPSignatureString = "http://ns.adobe.com/xmp/extension/\0"; +static const size_t kExtXMPSignatureLength = 35; +static const size_t kExtXMPPrefixLength = kExtXMPSignatureLength + 32 + 4 + 4; + +typedef std::map < XMP_Uns32 /* offset */, std::string /* portion */ > ExtXMPPortions; + +struct ExtXMPContent { + XMP_Uns32 length; + ExtXMPPortions portions; + ExtXMPContent() : length(0) {}; + ExtXMPContent ( XMP_Uns32 _length ) : length(_length) {}; +}; + +typedef std::map < JPEG_MetaHandler::GUID_32 /* guid */, ExtXMPContent /* content */ > ExtendedXMPInfo; + +#ifndef Trace_UnlimitedJPEG + #define Trace_UnlimitedJPEG 0 +#endif + +// ================================================================================================= +// JPEG_MetaHandlerCTor +// ==================== + +XMPFileHandler * JPEG_MetaHandlerCTor ( XMPFiles * parent ) +{ + return new JPEG_MetaHandler ( parent ); + +} // JPEG_MetaHandlerCTor + +// ================================================================================================= +// JPEG_CheckFormat +// ================ + +// For JPEG we just check for the initial SOI standalone marker followed by any of the other markers +// that might, well, follow it. A more aggressive check might be to read 4KB then check for legit +// marker segments within that portion. Probably won't buy much, and thrashes the dCache more. We +// tolerate only a small amount of 0xFF padding between the SOI and following marker. This formally +// violates the rules of JPEG, but in practice there won't be any padding anyway. +// +// ! The CheckXyzFormat routines don't track the filePos, that is left to ScanXyzFile. + +bool JPEG_CheckFormat ( XMP_FileFormat format, + XMP_StringPtr filePath, + XMP_IO * fileRef, + XMPFiles * parent ) +{ + IgnoreParam(format); IgnoreParam(filePath); IgnoreParam(parent); + XMP_Assert ( format == kXMP_JPEGFile ); + + XMP_Uns8 buffer [100]; + XMP_Uns16 marker; + + fileRef->Rewind(); + if ( fileRef->Length() < 2 ) return false; // Need at least the SOI marker. + size_t bufferLen = fileRef->Read ( buffer, sizeof(buffer) ); + + marker = GetUns16BE ( &buffer[0] ); + if ( marker != 0xFFD8 ) return false; // Offset 0 must have the SOI marker. + + // Skip 0xFF padding and high order 0xFF of next marker. + size_t bufferPos = 2; + while ( (bufferPos < bufferLen) && (buffer[bufferPos] == 0xFF) ) bufferPos += 1; + if ( bufferPos == bufferLen ) return true; // Nothing but 0xFF bytes, close enough. + + XMP_Uns8 id = buffer[bufferPos]; // Check the ID of the second marker. + if ( id >= 0xDD ) return true; // The most probable cases. + if ( (id < 0xC0) || ((id & 0xF8) == 0xD0) || (id == 0xD8) || (id == 0xDA) || (id == 0xDC) ) return false; + return true; + +} // JPEG_CheckFormat + +// ================================================================================================= +// JPEG_MetaHandler::JPEG_MetaHandler +// ================================== + +JPEG_MetaHandler::JPEG_MetaHandler ( XMPFiles * _parent ) + : exifMgr(0), psirMgr(0), iptcMgr(0), skipReconcile(false) +{ + this->parent = _parent; + this->handlerFlags = kJPEG_HandlerFlags; + this->stdCharForm = kXMP_Char8Bit; + +} // JPEG_MetaHandler::JPEG_MetaHandler + +// ================================================================================================= +// JPEG_MetaHandler::~JPEG_MetaHandler +// =================================== + +JPEG_MetaHandler::~JPEG_MetaHandler() +{ + + if ( exifMgr != 0 ) delete ( exifMgr ); + if ( psirMgr != 0 ) delete ( psirMgr ); + if ( iptcMgr != 0 ) delete ( iptcMgr ); + +} // JPEG_MetaHandler::~JPEG_MetaHandler + +// ================================================================================================= +// CacheExtendedXMP +// ================ + +static void CacheExtendedXMP ( ExtendedXMPInfo * extXMP, XMP_Uns8 * buffer, size_t bufferLen ) +{ + + // Have a portion of the extended XMP, cache the contents. This is complicated by the need to + // tolerate files where the extension portions are not in order. The local ExtendedXMPInfo map + // uses the GUID as the key and maps that to a struct that has the full length and a map of the + // known portions. This known portion map uses the offset of the portion as the key and maps + // that to a string. Only fully seen extended XMP streams are kept, the right one gets picked in + // ProcessXMP. + + // The extended XMP JPEG marker segment content holds: + // - a signature string, "http://ns.adobe.com/xmp/extension/\0", already verified + // - a 128 bit GUID stored as a 32 byte ASCII hex string + // - a UInt32 full length of the entire extended XMP + // - a UInt32 offset for this portion of the extended XMP + // - the UTF-8 text for this portion of the extended XMP + + if ( bufferLen < kExtXMPPrefixLength ) return; // Ignore bad input. + XMP_Assert ( CheckBytes ( &buffer[0], kExtXMPSignatureString, kExtXMPSignatureLength ) ); + + XMP_Uns8 * bufferPtr = buffer + kExtXMPSignatureLength; // Start at the GUID. + + JPEG_MetaHandler::GUID_32 guid; + XMP_Assert ( sizeof(guid.data) == 32 ); + memcpy ( &guid.data[0], bufferPtr, sizeof(guid.data) ); // AUDIT: Use of sizeof(guid.data) is safe. + + bufferPtr += sizeof(guid.data); // Move to the length and offset. + XMP_Uns32 fullLen = GetUns32BE ( bufferPtr ); + XMP_Uns32 offset = GetUns32BE ( bufferPtr+4 ); + + bufferPtr += 8; // Move to the XMP stream portion. + size_t xmpLen = bufferLen - kExtXMPPrefixLength; + + #if Trace_UnlimitedJPEG + printf ( "New extended XMP portion: fullLen %d, offset %d, GUID %.32s\n", fullLen, offset, guid.data ); + #endif + + // Find the ExtXMPContent for this GUID, and the string for this portion's offset. + + ExtendedXMPInfo::iterator guidPos = extXMP->find ( guid ); + if ( guidPos == extXMP->end() ) { + ExtXMPContent newExtContent ( fullLen ); + guidPos = extXMP->insert ( extXMP->begin(), ExtendedXMPInfo::value_type ( guid, newExtContent ) ); + } + + ExtXMPPortions::iterator offsetPos; + ExtXMPContent & extContent = guidPos->second; + + if ( extContent.portions.empty() ) { + // When new create a full size offset 0 string, to which all in-order portions will get appended. + offsetPos = extContent.portions.insert ( extContent.portions.begin(), + ExtXMPPortions::value_type ( 0, std::string() ) ); + offsetPos->second.reserve ( extContent.length ); + } + + // Try to append this portion to a logically contiguous preceeding one. + + if ( offset == 0 ) { + offsetPos = extContent.portions.begin(); + XMP_Assert ( (offsetPos->first == 0) && (offsetPos->second.size() == 0) ); + } else { + offsetPos = extContent.portions.lower_bound ( offset ); + --offsetPos; // Back up to the portion whose offset is less than the new offset. + if ( (offsetPos->first + offsetPos->second.size()) != offset ) { + // Can't append, create a new portion. + offsetPos = extContent.portions.insert ( extContent.portions.begin(), + ExtXMPPortions::value_type ( offset, std::string() ) ); + } + } + + // Cache this portion of the extended XMP. + + std::string & extPortion = offsetPos->second; + extPortion.append ( (XMP_StringPtr)bufferPtr, xmpLen ); + +} // CacheExtendedXMP + +// ================================================================================================= +// JPEG_MetaHandler::CacheFileData +// =============================== +// +// Look for the Exif metadata, Photoshop image resources, and XMP in a JPEG (JFIF) file. The native +// thumbnail is inside the Exif. The general layout of a JPEG file is: +// SOI marker, 2 bytes, 0xFFD8 +// Marker segments for tables and metadata +// SOFn marker segment +// Image data +// EOI marker, 2 bytes, 0xFFD9 +// +// Each marker segment begins with a 2 byte big endian marker and a 2 byte big endian length. The +// length includes the 2 bytes of the length field but not the marker. The high order byte of a +// marker is 0xFF, the low order byte tells what kind of marker. A marker can be preceeded by any +// number of 0xFF fill bytes, however there are no alignment constraints. +// +// There are virtually no constraints on the order of the marker segments before the SOFn. A reader +// must be prepared to handle any order. +// +// The Exif metadata is in an APP1 marker segment with a 6 byte signature string of "Exif\0\0" at +// the start of the data. The rest of the data is a TIFF stream. +// +// The Photoshop image resources are in an APP13 marker segment with a 14 byte signature string of +// "Photoshop 3.0\0". The rest of the data is a sequence of image resources. +// +// The main XMP is in an APP1 marker segment with a 29 byte signature string of +// "http://ns.adobe.com/xap/1.0/\0". The rest of the data is the serialized XMP packet. This is the +// only XMP if everything fits within the 64KB limit for marker segment data. If not, there will be +// a series of XMP extension segments. +// +// Each XMP extension segment is an APP1 marker segment whose data contains: +// - A 35 byte signature string of "http://ns.adobe.com/xmp/extension/\0". +// - A 128 bit GUID stored as 32 ASCII hex digits, capital A-F, no nul termination. +// - A 32 bit unsigned integer length for the full extended XMP serialization. +// - A 32 bit unsigned integer offset for this portion of the extended XMP serialization. +// - A portion of the extended XMP serialization, up to about 65400 bytes (at most 65458). +// +// A reader must be prepared to encounter the extended XMP portions out of order. Also to encounter +// defective files that have differing extended XMP according to the GUID. The main XMP contains the +// GUID for the associated extended XMP. + +// *** This implementation simply returns when invalid JPEG is encountered. Should we throw instead? + +void JPEG_MetaHandler::CacheFileData() +{ + XMP_IO* fileRef = this->parent->ioRef; + XMP_PacketInfo & packetInfo = this->packetInfo; + + static const size_t kBufferSize = 64*1024; // Enough for maximum segment contents. + XMP_Uns8 buffer [kBufferSize]; + + psirContents.clear(); + exifContents.clear(); + + XMP_AbortProc abortProc = this->parent->abortProc; + void * abortArg = this->parent->abortArg; + const bool checkAbort = (abortProc != 0); + + ExtendedXMPInfo extXMP; + + XMP_Assert ( ! this->containsXMP ); + // Set containsXMP to true here only if the standard XMP packet is found. + + XMP_Assert ( kPSIRSignatureLength == (strlen(kPSIRSignatureString) + 1) ); + XMP_Assert ( kMainXMPSignatureLength == (strlen(kMainXMPSignatureString) + 1) ); + XMP_Assert ( kExtXMPSignatureLength == (strlen(kExtXMPSignatureString) + 1) ); + + // ------------------------------------------------------------------------------------------- + // Look for any of the Exif, PSIR, main XMP, or extended XMP marker segments. Quit when we hit + // an SOFn, EOI, or invalid/unexpected marker. + + fileRef->Seek ( 2, kXMP_SeekFromStart ); // Skip the SOI, CheckFormat made sure it is present. + + while ( true ) { + + if ( checkAbort && abortProc(abortArg) ) { + XMP_Throw ( "JPEG_MetaHandler::CacheFileData - User abort", kXMPErr_UserAbort ); + } + + if ( ! XIO::CheckFileSpace ( fileRef, 2 ) ) return; // Quit, don't throw, if the file ends unexpectedly. + + XMP_Uns16 marker = XIO::ReadUns16_BE ( fileRef ); // Read the next marker. + if ( marker == 0xFFFF ) { + // Have a pad byte, skip it. These are almost unheard of, so efficiency isn't critical. + fileRef->Seek ( -1, kXMP_SeekFromCurrent ); // Skip the first 0xFF, read the second again. + continue; + } + + if ( (marker == 0xFFDA) || (marker == 0xFFD9) ) break; // Quit reading at the first SOS marker or at EOI. + + if ( (marker == 0xFF01) || // Ill-formed file if we encounter a TEM or RSTn marker. + ((0xFFD0 <= marker) && (marker <= 0xFFD7)) ) return; + + XMP_Uns16 contentLen = XIO::ReadUns16_BE ( fileRef ); // Read this segment's length. + if ( contentLen < 2 ) XMP_Throw ( "Invalid JPEG segment length", kXMPErr_BadJPEG ); + contentLen -= 2; // Reduce to just the content length. + + XMP_Int64 contentOrigin = fileRef->Offset(); + size_t signatureLen; + + if ( (marker == 0xFFED) && (contentLen >= kPSIRSignatureLength) ) { + + // This is an APP13 marker, is it the Photoshop image resources? + + signatureLen = fileRef->Read ( buffer, kPSIRSignatureLength ); + if ( (signatureLen == kPSIRSignatureLength) && + CheckBytes ( &buffer[0], kPSIRSignatureString, kPSIRSignatureLength ) ) { + + size_t psirLen = contentLen - kPSIRSignatureLength; + fileRef->Seek ( (contentOrigin + kPSIRSignatureLength), kXMP_SeekFromStart ); + fileRef->ReadAll ( buffer, psirLen ); + this->psirContents.append( (char *) buffer, psirLen ); + continue; // Move on to the next marker. + + } + + } else if ( (marker == 0xFFE1) && (contentLen >= kExifSignatureLength) ) { // Check for the shortest signature. + + // This is an APP1 marker, is it the Exif, main XMP, or extended XMP? + // ! Check in that order, which is in increasing signature string length. + + XMP_Assert ( (kExifSignatureLength < kMainXMPSignatureLength) && + (kMainXMPSignatureLength < kExtXMPSignatureLength) ); + signatureLen = fileRef->Read ( buffer, kExtXMPSignatureLength ); // Read for the longest signature. + + if ( (signatureLen >= kExifSignatureLength) && + (CheckBytes ( &buffer[0], kExifSignatureString, kExifSignatureLength ) || + CheckBytes ( &buffer[0], kExifSignatureAltStr, kExifSignatureLength )) ) { + + size_t exifLen = contentLen - kExifSignatureLength; + fileRef->Seek ( (contentOrigin + kExifSignatureLength), kXMP_SeekFromStart ); + fileRef->ReadAll ( buffer, exifLen ); + this->exifContents.append ( (char*)buffer, exifLen ); + continue; // Move on to the next marker. + + } + + if ( (signatureLen >= kMainXMPSignatureLength) && + CheckBytes ( &buffer[0], kMainXMPSignatureString, kMainXMPSignatureLength ) ) { + + this->containsXMP = true; // Found the standard XMP packet. + size_t xmpLen = contentLen - kMainXMPSignatureLength; + fileRef->Seek ( (contentOrigin + kMainXMPSignatureLength), kXMP_SeekFromStart ); + fileRef->ReadAll ( buffer, xmpLen ); + this->xmpPacket.assign ( (char*)buffer, xmpLen ); + this->packetInfo.offset = contentOrigin + kMainXMPSignatureLength; + this->packetInfo.length = (XMP_Int32)xmpLen; + this->packetInfo.padSize = 0; // Assume the rest for now, set later in ProcessXMP. + this->packetInfo.charForm = kXMP_CharUnknown; + this->packetInfo.writeable = true; + continue; // Move on to the next marker. + + } + + if ( (signatureLen >= kExtXMPSignatureLength) && + CheckBytes ( &buffer[0], kExtXMPSignatureString, kExtXMPSignatureLength ) ) { + + fileRef->Seek ( contentOrigin, kXMP_SeekFromStart ); + fileRef->ReadAll ( buffer, contentLen ); + CacheExtendedXMP ( &extXMP, buffer, contentLen ); + continue; // Move on to the next marker. + + } + + } + + // None of the above, seek to the next marker. + fileRef->Seek ( (contentOrigin + contentLen) , kXMP_SeekFromStart ); + + } + + if ( ! extXMP.empty() ) { + + // We have extended XMP. Find out which ones are complete, collapse them into a single + // string, and save them for ProcessXMP. + + ExtendedXMPInfo::iterator guidPos = extXMP.begin(); + ExtendedXMPInfo::iterator guidEnd = extXMP.end(); + + for ( ; guidPos != guidEnd; ++guidPos ) { + + ExtXMPContent & thisContent = guidPos->second; + ExtXMPPortions::iterator partZero = thisContent.portions.begin(); + ExtXMPPortions::iterator partEnd = thisContent.portions.end(); + ExtXMPPortions::iterator partPos = partZero; + + #if Trace_UnlimitedJPEG + printf ( "Extended XMP portions for GUID %.32s, full length %d\n", + guidPos->first.data, guidPos->second.length ); + printf ( " Offset %d, length %d, next offset %d\n", + partZero->first, partZero->second.size(), (partZero->first + partZero->second.size()) ); + #endif + + for ( ++partPos; partPos != partEnd; ++partPos ) { + #if Trace_UnlimitedJPEG + printf ( " Offset %d, length %d, next offset %d\n", + partPos->first, partPos->second.size(), (partPos->first + partPos->second.size()) ); + #endif + if ( partPos->first != partZero->second.size() ) break; // Quit if not contiguous. + partZero->second.append ( partPos->second ); + } + + if ( (partPos == partEnd) && (partZero->first == 0) && (partZero->second.size() == thisContent.length) ) { + // This is a complete extended XMP stream. + this->extendedXMP.insert ( ExtendedXMPMap::value_type ( guidPos->first, partZero->second ) ); + #if Trace_UnlimitedJPEG + printf ( "Full extended XMP for GUID %.32s, full length %d\n", + guidPos->first.data, partZero->second.size() ); + #endif + } + + } + + } + +} // JPEG_MetaHandler::CacheFileData + +// ================================================================================================= +// TrimFullExifAPP1 +// ================ +// +// Try to trim trailing padding from full Exif APP1 segment written by some Nikon cameras. Do a +// temporary read-only parse of the Exif APP1 contents, determine the highest used offset, trim the +// padding if all zero bytes. + +static const char * IFDNames[] = { "Primary", "TNail", "Exif", "GPS", "Interop", }; + +static void TrimFullExifAPP1 ( std::string * exifContents ) +{ + TIFF_MemoryReader tempMgr; + TIFF_MemoryReader::TagInfo tagInfo; + bool tagFound, isNikon; + + // ! Make a copy of the data to parse! The RO memory TIFF manager will flip bytes in-place! + tempMgr.ParseMemoryStream ( exifContents->data(), (XMP_Uns32)exifContents->size(), true /* copy data */ ); + + // Only trim the Exif APP1 from Nikon cameras. + tagFound = tempMgr.GetTag ( kTIFF_PrimaryIFD, kTIFF_Make, &tagInfo ); + isNikon = tagFound && (tagInfo.type == kTIFF_ASCIIType) && (tagInfo.count >= 5) && + (memcmp ( tagInfo.dataPtr, "NIKON", 5) == 0); + if ( ! isNikon ) return; + + // Find the start of the padding, one past the highest used offset. Look at the IFD structure, + // and the thumbnail info. Ignore the MakerNote tag, Nikon says they are self-contained. + + XMP_Uns32 padOffset = 0; + + for ( XMP_Uns8 ifd = 0; ifd < kTIFF_KnownIFDCount; ++ifd ) { + + TIFF_MemoryReader::TagInfoMap tagMap; + bool ifdFound = tempMgr.GetIFD ( ifd, &tagMap ); + if ( ! ifdFound ) continue; + + TIFF_MemoryReader::TagInfoMap::const_iterator mapPos = tagMap.begin(); + TIFF_MemoryReader::TagInfoMap::const_iterator mapEnd = tagMap.end(); + + for ( ; mapPos != mapEnd; ++mapPos ) { + const TIFF_MemoryReader::TagInfo & tagInfo = mapPos->second; + XMP_Uns32 tagEnd = tempMgr.GetValueOffset ( ifd, tagInfo.id ) + tagInfo.dataLen; + if ( tagEnd > padOffset ) padOffset = tagEnd; + } + + } + + tagFound = tempMgr.GetTag ( kTIFF_TNailIFD, kTIFF_JPEGInterchangeFormat, &tagInfo ); + if ( tagFound ) { + XMP_Uns32 tnailOffset = tempMgr.GetUns32 ( tagInfo.dataPtr ); + tagFound = tempMgr.GetTag ( kTIFF_TNailIFD, kTIFF_JPEGInterchangeFormatLength, &tagInfo ); + if ( ! tagFound ) return; // Don't trim if there is a TNail offset but no length. + tnailOffset += tempMgr.GetUns32 ( tagInfo.dataPtr ); + if ( tnailOffset > padOffset ) padOffset = tnailOffset; + } + + // Decide if it is OK to trim the Exif segment. It is OK if the padding is all zeros. It is OK + // if the last non-zero byte is no more than 64 bytes into the padding and there are at least + // an additional 64 bytes of padding after it. + + if ( padOffset >= exifContents->size() ) return; // Sanity check for an OK last used offset. + + size_t lastNonZero = exifContents->size() - 1; + while ( (lastNonZero >= padOffset) && ((*exifContents)[lastNonZero] == 0) ) --lastNonZero; + + bool ok = lastNonZero < padOffset; + if ( ! ok ) { + size_t nzSize = lastNonZero - padOffset + 1; + size_t finalSize = (exifContents->size() - 1) - lastNonZero; + if ( (nzSize < 64) && (finalSize > 64) ) { + padOffset = lastNonZero + 64; + assert ( padOffset < exifContents->size() ); + ok = true; + } + } + + if ( ok ) exifContents->erase ( padOffset ); + +} // TrimFullExifAPP1 + +// ================================================================================================= +// JPEG_MetaHandler::ProcessXMP +// ============================ +// +// Process the raw XMP and legacy metadata that was previously cached. + +void JPEG_MetaHandler::ProcessXMP() +{ + + XMP_Assert ( ! this->processedXMP ); + this->processedXMP = true; // Make sure we only come through here once. + + // Create the PSIR and IPTC handlers, even if there is no legacy. They might be needed for updates. + + XMP_Assert ( (this->psirMgr == 0) && (this->iptcMgr == 0) ); // ProcessTNail might create the exifMgr. + + bool readOnly = false; + if ( this->parent ){ + readOnly = ((this->parent->openFlags & kXMPFiles_OpenForUpdate) == 0); + } + if ( readOnly ) { + if ( this->exifMgr == 0 ) this->exifMgr = new TIFF_MemoryReader(); + this->psirMgr = new PSIR_MemoryReader(); + this->iptcMgr = new IPTC_Reader(); // ! Parse it later. + } else { + if ( this->exifContents.size() == (65534 - 2 - 6) ) TrimFullExifAPP1 ( &this->exifContents ); + if ( this->exifMgr == 0 ) this->exifMgr = new TIFF_FileWriter(); + this->psirMgr = new PSIR_FileWriter(); + this->iptcMgr = new IPTC_Writer(); // ! Parse it later. + } + if ( this->parent ) + exifMgr->SetErrorCallback( &this->parent->errorCallback ); + + // Set up everything for the legacy import, but don't do it yet. This lets us do a forced legacy + // import if the XMP packet gets parsing errors. + + TIFF_Manager & exif = *this->exifMgr; // Give the compiler help in recognizing non-aliases. + PSIR_Manager & psir = *this->psirMgr; + IPTC_Manager & iptc = *this->iptcMgr; + + bool haveExif = (! this->exifContents.empty()); + if ( haveExif ) { + exif.ParseMemoryStream ( this->exifContents.c_str(), (XMP_Uns32)this->exifContents.size() ); + } + + bool havePSIR = (! this->psirContents.empty()); + if ( havePSIR ) { + psir.ParseMemoryResources ( this->psirContents.c_str(), (XMP_Uns32)this->psirContents.size() ); + } + + PSIR_Manager::ImgRsrcInfo iptcInfo; + bool haveIPTC = false; + if ( havePSIR ) haveIPTC = psir.GetImgRsrc ( kPSIR_IPTC, &iptcInfo );; + int iptcDigestState = kDigestMatches; + + if ( haveIPTC ) { + + bool haveDigest = false; + PSIR_Manager::ImgRsrcInfo digestInfo; + if ( havePSIR ) haveDigest = psir.GetImgRsrc ( kPSIR_IPTCDigest, &digestInfo ); + if ( digestInfo.dataLen != 16 ) haveDigest = false; + + if ( ! haveDigest ) { + iptcDigestState = kDigestMissing; + } else { + iptcDigestState = PhotoDataUtils::CheckIPTCDigest ( iptcInfo.dataPtr, iptcInfo.dataLen, digestInfo.dataPtr ); + } + + } + + XMP_OptionBits options = 0; + if ( this->containsXMP ) options |= k2XMP_FileHadXMP; + if ( haveExif ) options |= k2XMP_FileHadExif; + if ( haveIPTC ) options |= k2XMP_FileHadIPTC; + + // Process the main XMP packet. If it fails to parse, do a forced legacy import but still throw + // an exception. This tells the caller that an error happened, but gives them recovered legacy + // should they want to proceed with that. + + bool haveXMP = false; + + if ( ! this->xmpPacket.empty() ) { + XMP_Assert ( this->containsXMP ); + // Common code takes care of packetInfo.charForm, .padSize, and .writeable. + XMP_StringPtr packetStr = this->xmpPacket.c_str(); + XMP_StringLen packetLen = (XMP_StringLen)this->xmpPacket.size(); + try { + this->xmpObj.ParseFromBuffer ( packetStr, packetLen ); + } catch ( ... ) { /* Ignore parsing failures, someday we hope to get partial XMP back. */ } + haveXMP = true; + } + + // Process the extended XMP if it has a matching GUID. + + if ( ! this->extendedXMP.empty() ) { + + bool found; + GUID_32 g32; + std::string extGUID, extPacket; + ExtendedXMPMap::iterator guidPos = this->extendedXMP.end(); + + found = this->xmpObj.GetProperty ( kXMP_NS_XMP_Note, "HasExtendedXMP", &extGUID, 0 ); + if ( found && (extGUID.size() == sizeof(g32.data)) ) { + XMP_Assert ( sizeof(g32.data) == 32 ); + memcpy ( g32.data, extGUID.c_str(), sizeof(g32.data) ); // AUDIT: Use of sizeof(g32.data) is safe. + guidPos = this->extendedXMP.find ( g32 ); + this->xmpObj.DeleteProperty ( kXMP_NS_XMP_Note, "HasExtendedXMP" ); // ! Must only be in the file. + #if Trace_UnlimitedJPEG + printf ( "%s extended XMP for GUID %s\n", + ((guidPos != this->extendedXMP.end()) ? "Found" : "Missing"), extGUID.c_str() ); + #endif + } + + if ( guidPos != this->extendedXMP.end() ) { + try { + XMP_StringPtr extStr = guidPos->second.c_str(); + XMP_StringLen extLen = (XMP_StringLen)guidPos->second.size(); + SXMPMeta extXMP ( extStr, extLen ); + SXMPUtils::MergeFromJPEG ( &this->xmpObj, extXMP ); + } catch ( ... ) { + // Ignore failures, let the rest of the XMP and legacy be kept. + } + } + + } + + // Process the legacy metadata. + + if ( haveIPTC && (! haveXMP) && (iptcDigestState == kDigestMatches) ) iptcDigestState = kDigestMissing; + if (iptcInfo.dataLen) iptc.ParseMemoryDataSets ( iptcInfo.dataPtr, iptcInfo.dataLen ); + ImportPhotoData ( exif, iptc, psir, iptcDigestState, &this->xmpObj, options ); + + this->containsXMP = true; // Assume we had something for the XMP. + +} // JPEG_MetaHandler::ProcessXMP + +// ================================================================================================= +// JPEG_MetaHandler::UpdateFile +// ============================ + +void JPEG_MetaHandler::UpdateFile ( bool doSafeUpdate ) +{ + XMP_Assert ( ! doSafeUpdate ); // This should only be called for "unsafe" updates. + + XMP_Int64 oldPacketOffset = this->packetInfo.offset; + XMP_Int32 oldPacketLength = this->packetInfo.length; + + if ( oldPacketOffset == kXMPFiles_UnknownOffset ) oldPacketOffset = 0; // ! Simplify checks. + if ( oldPacketLength == kXMPFiles_UnknownLength ) oldPacketLength = 0; + + bool fileHadXMP = ((oldPacketOffset != 0) && (oldPacketLength != 0)); + + // Update the IPTC-IIM and native TIFF/Exif metadata. ExportPhotoData also trips the tiff: and + // exif: copies from the XMP, so reserialize the now final XMP packet. + + ExportPhotoData ( kXMP_JPEGFile, &this->xmpObj, this->exifMgr, this->iptcMgr, this->psirMgr ); + + try { + XMP_OptionBits options = kXMP_UseCompactFormat; + if ( fileHadXMP ) options |= kXMP_ExactPacketLength; + this->xmpObj.SerializeToBuffer ( &this->xmpPacket, options, oldPacketLength ); + } catch ( ... ) { + this->xmpObj.SerializeToBuffer ( &this->xmpPacket, kXMP_UseCompactFormat ); + } + + // Decide whether to do an in-place update. This can only happen if all of the following are true: + // - There is a standard packet in the file. + // - There is no extended XMP in the file. + // - The are no changes to the legacy Exif or PSIR portions. (The IPTC is in the PSIR.) + // - The new XMP can fit in the old space, without extensions. + + bool doInPlace = (fileHadXMP && (this->xmpPacket.size() <= (size_t)oldPacketLength)); + + if ( ! this->extendedXMP.empty() ) doInPlace = false; + + if ( (this->exifMgr != 0) && (this->exifMgr->IsLegacyChanged()) ) doInPlace = false; + if ( (this->psirMgr != 0) && (this->psirMgr->IsLegacyChanged()) ) doInPlace = false; + + if ( doInPlace ) { + + #if GatherPerformanceData + sAPIPerf->back().extraInfo += ", JPEG in-place update"; + #endif + + if ( this->xmpPacket.size() < (size_t)this->packetInfo.length ) { + // They ought to match, cheap to be sure. + size_t extraSpace = (size_t)this->packetInfo.length - this->xmpPacket.size(); + this->xmpPacket.append ( extraSpace, ' ' ); + } + + XMP_IO* liveFile = this->parent->ioRef; + std::string & newPacket = this->xmpPacket; + + XMP_Assert ( newPacket.size() == (size_t)oldPacketLength ); // ! Done by common PutXMP logic. + + liveFile->Seek ( oldPacketOffset, kXMP_SeekFromStart ); + liveFile->Write ( newPacket.c_str(), (XMP_Int32)newPacket.size() ); + + } else { + + #if GatherPerformanceData + sAPIPerf->back().extraInfo += ", JPEG copy update"; + #endif + + XMP_IO* origRef = this->parent->ioRef; + XMP_IO* tempRef = origRef->DeriveTemp(); + + try { + XMP_Assert ( ! this->skipReconcile ); + this->skipReconcile = true; + this->WriteTempFile ( tempRef ); + this->skipReconcile = false; + } catch ( ... ) { + this->skipReconcile = false; + origRef->DeleteTemp(); + throw; + } + + origRef->AbsorbTemp(); + + } + + this->needsUpdate = false; + +} // JPEG_MetaHandler::UpdateFile + +// ================================================================================================= +// JPEG_MetaHandler::WriteTempFile +// =============================== +// +// The metadata parts of a JPEG file are APP1 marker segments for Exif and XMP, and an APP13 marker +// segment for Photoshop image resources which contain the IPTC. Corresponding marker segments in +// the source file are ignored, other parts of the source file are copied. Any initial APP0 marker +// segments are copied first. Then the new Exif, XMP, and PSIR marker segments are written. Then the +// rest of the file is copied, skipping the old Exif, XMP, and PSIR. The checking for old metadata +// stops at the first SOFn marker. + +void JPEG_MetaHandler::WriteTempFile ( XMP_IO* tempRef ) +{ + XMP_IO* origRef = this->parent->ioRef; + + XMP_AbortProc abortProc = this->parent->abortProc; + void * abortArg = this->parent->abortArg; + const bool checkAbort = (abortProc != 0); + + XMP_Uns16 marker, contentLen; + + static const size_t kBufferSize = 64*1024; // Enough for a segment with maximum contents. + XMP_Uns8 buffer [kBufferSize]; + + XMP_Int64 origLength = origRef->Length(); + if ( origLength == 0 ) return; // Tolerate empty files. + if ( origLength < 4 ) { + XMP_Throw ( "JPEG must have at least SOI and EOI markers", kXMPErr_BadJPEG ); + } + + if ( ! skipReconcile ) { + // Update the IPTC-IIM and native TIFF/Exif metadata, and reserialize the now final XMP packet. + ExportPhotoData ( kXMP_JPEGFile, &this->xmpObj, this->exifMgr, this->iptcMgr, this->psirMgr ); + this->xmpObj.SerializeToBuffer ( &this->xmpPacket, kXMP_UseCompactFormat ); + } + + origRef->Rewind(); + tempRef->Truncate ( 0 ); + + marker = XIO::ReadUns16_BE ( origRef ); // Just read the SOI marker. + if ( marker != 0xFFD8 ) XMP_Throw ( "Missing SOI marker", kXMPErr_BadJPEG ); + XIO::WriteUns16_BE ( tempRef, marker ); + + // Copy any leading APP0 marker segments. + + while ( true ) { + + if ( checkAbort && abortProc(abortArg) ) { + XMP_Throw ( "JPEG_MetaHandler::WriteFile - User abort", kXMPErr_UserAbort ); + } + + if ( ! XIO::CheckFileSpace ( origRef, 2 ) ) break; // Tolerate a file that ends abruptly. + + marker = XIO::ReadUns16_BE ( origRef ); // Read the next marker. + if ( marker == 0xFFFF ) { + // Have a pad byte, skip it. These are almost unheard of, so efficiency isn't critical. + origRef->Seek ( -1, kXMP_SeekFromCurrent ); // Skip the first 0xFF, read the second again. + continue; + } + + if ( marker != 0xFFE0 ) break; // Have a non-APP0 marker. + XIO::WriteUns16_BE ( tempRef, marker ); // Write the APP0 marker. + + contentLen = XIO::ReadUns16_BE ( origRef ); // Copy the APP0 segment's length. + XIO::WriteUns16_BE ( tempRef, contentLen ); + + if ( contentLen < 2 ) XMP_Throw ( "Invalid JPEG segment length", kXMPErr_BadJPEG ); + contentLen -= 2; // Reduce to just the content length. + origRef->ReadAll ( buffer, contentLen ); // Copy the APP0 segment's content. + tempRef->Write ( buffer, contentLen ); + + } + + // Write the new Exif APP1 marker segment. + + XMP_Uns32 first4; + + if ( this->exifMgr != 0 ) { + + void* exifPtr; + XMP_Uns32 exifLen = this->exifMgr->UpdateMemoryStream ( &exifPtr ); + if ( exifLen > kExifMaxDataLength ) exifLen = this->exifMgr->UpdateMemoryStream ( &exifPtr, true /* compact */ ); + + while ( exifLen > 0 ) { + XMP_Uns32 count = std::min ( exifLen, (XMP_Uns32) kExifMaxDataLength ); + first4 = MakeUns32BE ( 0xFFE10000 + 2 + kExifSignatureLength + count ); + tempRef->Write ( &first4, 4 ); + tempRef->Write ( kExifSignatureString, kExifSignatureLength ); + tempRef->Write ( exifPtr, count ); + exifPtr = (XMP_Uns8 *) exifPtr + count; + exifLen -= count; + } + } + + // Write the new XMP APP1 marker segment, with possible extension marker segments. + + std::string mainXMP, extXMP, extDigest; + SXMPUtils::PackageForJPEG ( this->xmpObj, &mainXMP, &extXMP, &extDigest ); + XMP_Assert ( (extXMP.size() == 0) || (extDigest.size() == 32) ); + + first4 = MakeUns32BE ( 0xFFE10000 + 2 + kMainXMPSignatureLength + (XMP_Uns32)mainXMP.size() ); + tempRef->Write ( &first4, 4 ); + tempRef->Write ( kMainXMPSignatureString, kMainXMPSignatureLength ); + tempRef->Write ( mainXMP.c_str(), (XMP_Int32)mainXMP.size() ); + + size_t extPos = 0; + size_t extLen = extXMP.size(); + + while ( extLen > 0 ) { + + size_t partLen = extLen; + if ( partLen > 65000 ) partLen = 65000; + + first4 = MakeUns32BE ( 0xFFE10000 + 2 + kExtXMPPrefixLength + (XMP_Uns32)partLen ); + tempRef->Write ( &first4, 4 ); + + tempRef->Write ( kExtXMPSignatureString, kExtXMPSignatureLength ); + tempRef->Write ( extDigest.c_str(), (XMP_Int32)extDigest.size() ); + + first4 = MakeUns32BE ( (XMP_Int32)extXMP.size() ); + tempRef->Write ( &first4, 4 ); + first4 = MakeUns32BE ( (XMP_Int32)extPos ); + tempRef->Write ( &first4, 4 ); + + tempRef->Write ( &extXMP[extPos], (XMP_Int32)partLen ); + + extPos += partLen; + extLen -= partLen; + + } + + // Write the new PSIR APP13 marker segments. + if ( this->psirMgr != 0 ) { + + void* psirPtr; + XMP_Uns32 psirLen = this->psirMgr->UpdateMemoryResources ( &psirPtr ); + while ( psirLen > 0 ) { + XMP_Uns32 count = std::min ( psirLen, (XMP_Uns32) kPSIRMaxDataLength ); + first4 = MakeUns32BE ( 0xFFED0000 + 2 + kPSIRSignatureLength + count ); + tempRef->Write ( &first4, 4 ); + tempRef->Write ( kPSIRSignatureString, kPSIRSignatureLength ); + tempRef->Write ( psirPtr, count ); + psirPtr = (XMP_Uns8 *) psirPtr + count; + psirLen -= count; + } + } + + // Copy remaining marker segments, skipping old metadata, to the first SOS marker or to EOI. + origRef->Seek ( -2, kXMP_SeekFromCurrent ); // Back up to the marker from the end of the APP0 copy loop. + + while ( true ) { + + if ( checkAbort && abortProc(abortArg) ) { + XMP_Throw ( "JPEG_MetaHandler::WriteFile - User abort", kXMPErr_UserAbort ); + } + + if ( ! XIO::CheckFileSpace ( origRef, 2 ) ) break; // Tolerate a file that ends abruptly. + + marker = XIO::ReadUns16_BE ( origRef ); // Read the next marker. + if ( marker == 0xFFFF ) { + // Have a pad byte, skip it. These are almost unheard of, so efficiency isn't critical. + origRef->Seek ( -1, kXMP_SeekFromCurrent ); // Skip the first 0xFF, read the second again. + continue; + } + + if ( (marker == 0xFFDA) || (marker == 0xFFD9) ) { // Quit at the first SOS marker or at EOI. + origRef->Seek ( -2, kXMP_SeekFromCurrent ); // The tail copy must include this marker. + break; + } + + if ( (marker == 0xFF01) || // Ill-formed file if we encounter a TEM or RSTn marker. + ((0xFFD0 <= marker) && (marker <= 0xFFD7)) ) { + XMP_Throw ( "Unexpected TEM or RSTn marker", kXMPErr_BadJPEG ); + } + + contentLen = XIO::ReadUns16_BE ( origRef ); // Read this segment's length. + if ( contentLen < 2 ) XMP_Throw ( "Invalid JPEG segment length", kXMPErr_BadJPEG ); + contentLen -= 2; // Reduce to just the content length. + + XMP_Int64 contentOrigin = origRef->Offset(); + bool copySegment = true; + size_t signatureLen; + + if ( (marker == 0xFFED) && (contentLen >= kPSIRSignatureLength) ) { + + // This is an APP13 segment, skip if it is the old PSIR. + signatureLen = origRef->Read ( buffer, kPSIRSignatureLength ); + if ( (signatureLen == kPSIRSignatureLength) && + CheckBytes ( &buffer[0], kPSIRSignatureString, kPSIRSignatureLength ) ) { + copySegment = false; + } + + } else if ( (marker == 0xFFE1) && (contentLen >= kExifSignatureLength) ) { // Check for the shortest signature. + + // This is an APP1 segment, skip if it is the old Exif or XMP. + + XMP_Assert ( (kExifSignatureLength < kMainXMPSignatureLength) && + (kMainXMPSignatureLength < kExtXMPSignatureLength) ); + signatureLen = origRef->Read ( buffer, kExtXMPSignatureLength ); // Read for the longest signature. + + if ( (signatureLen >= kExifSignatureLength) && + (CheckBytes ( &buffer[0], kExifSignatureString, kExifSignatureLength ) || + CheckBytes ( &buffer[0], kExifSignatureAltStr, kExifSignatureLength )) ) { + copySegment = false; + } + + if ( copySegment && (signatureLen >= kMainXMPSignatureLength) && + CheckBytes ( &buffer[0], kMainXMPSignatureString, kMainXMPSignatureLength ) ) { + copySegment = false; + } + + if ( copySegment && (signatureLen == kExtXMPSignatureLength) && + CheckBytes ( &buffer[0], kExtXMPSignatureString, kExtXMPSignatureLength ) ) { + copySegment = false; + } + + } + + if ( ! copySegment ) { + origRef->Seek ( (contentOrigin + contentLen), kXMP_SeekFromStart ); + } else { + XIO::WriteUns16_BE ( tempRef, marker ); + XIO::WriteUns16_BE ( tempRef, (contentLen + 2) ); + origRef->Seek ( contentOrigin, kXMP_SeekFromStart ); + origRef->ReadAll ( buffer, contentLen ); + tempRef->Write ( buffer, contentLen ); + } + + } + + // Copy the remainder of the source file. + + XIO::Copy ( origRef, tempRef, (origLength - origRef->Offset()) ); + this->needsUpdate = false; + +} // JPEG_MetaHandler::WriteTempFile diff --git a/core/libs/dngwriter/extra/xmp_sdk/XMPFiles/source/FileHandlers/JPEG_Handler.hpp b/core/libs/dngwriter/extra/xmp_sdk/XMPFiles/source/FileHandlers/JPEG_Handler.hpp new file mode 100644 index 0000000000..9e859c1bd2 --- /dev/null +++ b/core/libs/dngwriter/extra/xmp_sdk/XMPFiles/source/FileHandlers/JPEG_Handler.hpp @@ -0,0 +1,99 @@ +#ifndef __JPEG_Handler_hpp__ +#define __JPEG_Handler_hpp__ 1 + +// ================================================================================================= +// ADOBE SYSTEMS INCORPORATED +// Copyright 2004 Adobe Systems Incorporated +// All Rights Reserved +// +// NOTICE: Adobe permits you to use, modify, and distribute this file in accordance with the terms +// of the Adobe license agreement accompanying it. +// ================================================================================================= + +#include "public/include/XMP_Environment.h" // ! Must be the first #include! + +#include "public/include/XMP_Const.h" +#include "public/include/XMP_IO.hpp" + +#include "XMPFiles/source/FormatSupport/TIFF_Support.hpp" +#include "XMPFiles/source/FormatSupport/PSIR_Support.hpp" +#include "XMPFiles/source/FormatSupport/IPTC_Support.hpp" + +// ================================================================================================= +/// \file JPEG_Handler.hpp +/// \brief File format handler for JPEG. +/// +/// This header ... +/// +// ================================================================================================= + +// *** Could derive from Basic_Handler - buffer file tail in a temp file. + +extern XMPFileHandler * JPEG_MetaHandlerCTor ( XMPFiles * parent ); + +extern bool JPEG_CheckFormat ( XMP_FileFormat format, + XMP_StringPtr filePath, + XMP_IO * fileRef, + XMPFiles * parent ); + +static const XMP_OptionBits kJPEG_HandlerFlags = (kXMPFiles_CanInjectXMP | + kXMPFiles_CanExpand | + kXMPFiles_CanRewrite | + kXMPFiles_PrefersInPlace | + kXMPFiles_CanReconcile | + kXMPFiles_AllowsOnlyXMP | + kXMPFiles_ReturnsRawPacket | + kXMPFiles_AllowsSafeUpdate); + +class JPEG_MetaHandler : public XMPFileHandler +{ +public: + + void CacheFileData(); + void ProcessXMP(); + + void UpdateFile ( bool doSafeUpdate ); + void WriteTempFile ( XMP_IO* tempRef ); + + struct GUID_32 { // A hack to get an assignment operator for an array. + char data [32]; + void operator= ( const GUID_32 & in ) + { + memcpy ( this->data, in.data, sizeof(this->data) ); // AUDIT: Use of sizeof(this->data) is safe. + }; + bool operator< ( const GUID_32 & right ) const + { + return (memcmp ( this->data, right.data, sizeof(this->data) ) < 0); + }; + bool operator== ( const GUID_32 & right ) const + { + return (memcmp ( this->data, right.data, sizeof(this->data) ) == 0); + }; + }; + + JPEG_MetaHandler ( XMPFiles * parent ); + virtual ~JPEG_MetaHandler(); + +private: + + JPEG_MetaHandler() : exifMgr(0), psirMgr(0), iptcMgr(0), skipReconcile(false) {}; // Hidden on purpose. + + std::string exifContents; + std::string psirContents; + + TIFF_Manager * exifMgr; // The Exif manager will be created by ProcessTNail or ProcessXMP. + PSIR_Manager * psirMgr; // Need to use pointers so we can properly select between read-only and + IPTC_Manager * iptcMgr; // read-write modes of usage. + + bool skipReconcile; // ! Used between UpdateFile and WriteFile. + + typedef std::map < GUID_32, std::string > ExtendedXMPMap; + + ExtendedXMPMap extendedXMP; // ! Only contains those with complete data. +// void CacheExtendedXMP ( ExtendedXMPInfo * extXMP, XMP_Uns8 * buffer, size_t bufferLen ); + +}; // JPEG_MetaHandler + +// ================================================================================================= + +#endif /* __JPEG_Handler_hpp__ */ diff --git a/core/libs/dngwriter/extra/xmp_sdk/XMPFiles/source/FileHandlers/MP3_Handler.cpp b/core/libs/dngwriter/extra/xmp_sdk/XMPFiles/source/FileHandlers/MP3_Handler.cpp new file mode 100644 index 0000000000..e9520f33c6 --- /dev/null +++ b/core/libs/dngwriter/extra/xmp_sdk/XMPFiles/source/FileHandlers/MP3_Handler.cpp @@ -0,0 +1,735 @@ +// ================================================================================================= +// ADOBE SYSTEMS INCORPORATED +// Copyright 2008 Adobe Systems Incorporated +// All Rights Reserved +// +// NOTICE: Adobe permits you to use, modify, and distribute this file in accordance with the terms +// of the Adobe license agreement accompanying it. +// ================================================================================================= + +#include "public/include/XMP_Environment.h" // ! XMP_Environment.h must be the first included header. + +#include "public/include/XMP_Const.h" +#include "public/include/XMP_IO.hpp" + +#include "XMPFiles/source/XMPFiles_Impl.hpp" +#include "XMPFiles/source/FormatSupport/Reconcile_Impl.hpp" +#include "source/UnicodeConversions.hpp" +#include "source/XMPFiles_IO.hpp" +#include "source/XIO.hpp" + +#include "XMPFiles/source/FileHandlers/MP3_Handler.hpp" + +#include + +// ================================================================================================= +/// \file MP3_Handler.cpp +/// \brief MP3 handler class. +// ================================================================================================= + +// ================================================================================================= +// Helper structs and private routines +// ==================== +struct ReconProps { + const char* mainID; // The stored v2.3 and v2.4 ID, also used as the main logical ID. + const char* v22ID; // The stored v2.2 ID. + const char* ns; + const char* prop; +}; + +const static XMP_Uns32 XMP_V23_ID = 0x50524956; // PRIV +const static XMP_Uns32 XMP_V22_ID = 0x50525600; // PRV + +const static ReconProps reconProps[] = { + { "TPE1", "TP1", kXMP_NS_DM, "artist" }, + { "TALB", "TAL", kXMP_NS_DM, "album" }, + { "TRCK", "TRK", kXMP_NS_DM, "trackNumber" }, + // exceptions that need attention: + { "TCON", "TCO", kXMP_NS_DM, "genre" }, // genres might be numeric + { "TIT2", "TT2", kXMP_NS_DC, "title" }, // ["x-default"] language alternatives + { "COMM", "COM", kXMP_NS_DM, "logComment" }, // two distinct strings, language alternative + + { "TYER", "TYE", kXMP_NS_XMP, "CreateDate" }, // Year (YYYY) Deprecated in 2.4 + { "TDAT", "TDA", kXMP_NS_XMP, "CreateDate" }, // Date (DDMM) Deprecated in 2.4 + { "TIME", "TIM", kXMP_NS_XMP, "CreateDate" }, // Time (HHMM) Deprecated in 2.4 + { "TDRC", "", kXMP_NS_XMP, "CreateDate" }, // assembled date/time v2.4 + + // new reconciliations introduced in Version 5 + { "TCMP", "TCP", kXMP_NS_DM, "partOfCompilation" }, // presence/absence of TCMP frame dedides + { "USLT", "ULT", kXMP_NS_DM, "lyrics" }, + { "TCOM", "TCM", kXMP_NS_DM, "composer" }, + { "TPOS", "TPA", kXMP_NS_DM, "discNumber" }, // * a text field! might contain "/" + { "TCOP", "TCR", kXMP_NS_DC, "rights" }, // ["x-default"] language alternatives + { "TPE4", "TP4", kXMP_NS_DM, "engineer" }, + { "WCOP", "WCP", kXMP_NS_XMP_Rights, "WebStatement" }, + + { 0, 0, 0, 0 } // must be last as a sentinel +}; + +// ================================================================================================= +// MP3_MetaHandlerCTor +// ==================== + +XMPFileHandler * MP3_MetaHandlerCTor ( XMPFiles * parent ) +{ + return new MP3_MetaHandler ( parent ); +} + +// ================================================================================================= +// MP3_CheckFormat +// =============== + +// For MP3 we check parts .... See the MP3 spec for offset info. + +bool MP3_CheckFormat ( XMP_FileFormat format, + XMP_StringPtr filePath, + XMP_IO* file, + XMPFiles * parent ) +{ + IgnoreParam(filePath); IgnoreParam(parent); //supress warnings + XMP_Assert ( format == kXMP_MP3File ); //standard assert + + if ( file->Length() < 10 ) return false; + file ->Rewind(); + + XMP_Uns8 header[3]; + file->ReadAll ( header, 3 ); + if ( ! CheckBytes( &header[0], "ID3", 3 ) ) return (parent->format == kXMP_MP3File); + + XMP_Uns8 major = XIO::ReadUns8( file ); + XMP_Uns8 minor = XIO::ReadUns8( file ); + + if ( (major < 2) || (major > 4) || (minor == 0xFF) ) return false; + + XMP_Uns8 flags = XIO::ReadUns8 ( file ); + + //TODO + if ( flags & 0x10 ) XMP_Throw ( "no support for MP3 with footer", kXMPErr_Unimplemented ); + if ( flags & 0x80 ) return false; //no support for unsynchronized MP3 (as before, also see [1219125]) + if ( flags & 0x0F ) XMP_Throw ( "illegal header lower bits", kXMPErr_Unimplemented ); + + XMP_Uns32 size = XIO::ReadUns32_BE ( file ); + if ( (size & 0x80808080) != 0 ) return false; //if any bit survives -> not a valid synchsafe 32 bit integer + + return true; + +} // MP3_CheckFormat + + +// ================================================================================================= +// MP3_MetaHandler::MP3_MetaHandler +// ================================ + +MP3_MetaHandler::MP3_MetaHandler ( XMPFiles * _parent ) +{ + this->oldTagSize = 0; + this->oldPadding = 0; + this->oldFramesSize = 0; + this->newTagSize = 0; + this->newPadding = 0; + this->newFramesSize = 0; + this->tagIsDirty = false; + this->mustShift = false; + this->majorVersion = 2.3; + this->minorVersion = 2.3; + this->hasID3Tag = false; + this->hasFooter = false; + this->extHeaderSize = 0; + this->hasExtHeader = false; + this->parent = _parent; + this->handlerFlags = kMP3_HandlerFlags; + this->stdCharForm = kXMP_Char8Bit; +} + +// ================================================================================================= +// MP3_MetaHandler::~MP3_MetaHandler +// ================================= + +MP3_MetaHandler::~MP3_MetaHandler() +{ + // free frames + ID3v2Frame* curFrame; + while ( !this->framesVector.empty() ) { + curFrame = this->framesVector.back(); + delete curFrame; + framesVector.pop_back(); + } +} + +// ================================================================================================= +// MP3_MetaHandler::CacheFileData +// ============================== + +void MP3_MetaHandler::CacheFileData() +{ + + //*** abort procedures + this->containsXMP = false; //assume no XMP for now + + XMP_IO* file = this->parent->ioRef; + XMP_PacketInfo &packetInfo = this->packetInfo; + + file->Rewind(); + + this->hasID3Tag = this->id3Header.read( file ); + this->majorVersion = this->id3Header.fields[ID3Header::o_vMajor]; + this->minorVersion = this->id3Header.fields[ID3Header::o_vMinor]; + this->hasExtHeader = (0 != ( 0x40 & this->id3Header.fields[ID3Header::o_flags])); //'naturally' false if no ID3Tag + this->hasFooter = ( 0 != ( 0x10 & this->id3Header.fields[ID3Header::o_flags])); //'naturally' false if no ID3Tag + + // stored size is w/o initial header (thus adding 10) + // + but extended header (if existing) + // + padding + frames after unsynchronisation (?) + // (if no ID3 tag existing, constructed default correctly sets size to 10.) + this->oldTagSize = ID3Header::kID3_TagHeaderSize + synchToInt32(GetUns32BE( &id3Header.fields[ID3Header::o_size] )); + + if ( ! hasExtHeader ) { + + this->extHeaderSize = 0; // := there is no such header. + + } else { + + this->extHeaderSize = synchToInt32( XIO::ReadInt32_BE( file)); + XMP_Uns8 extHeaderNumFlagBytes = XIO::ReadUns8( file ); + + // v2.3 doesn't include the size, while v2.4 does + if ( this->majorVersion < 4 ) this->extHeaderSize += 4; + XMP_Validate( this->extHeaderSize >= 6, "extHeader size too small", kXMPErr_BadFileFormat ); + + file->Seek ( this->extHeaderSize - 6, kXMP_SeekFromCurrent ); + + } + + this->framesVector.clear(); //mac precaution + ID3v2Frame* curFrame = 0; // reusable + + //////////////////////////////////////////////////// + // read frames + + XMP_Uns32 xmpID = XMP_V23_ID; + if ( this->majorVersion == 2 ) + { + xmpID = XMP_V22_ID; + } + + while ( file->Offset() < this->oldTagSize ) { + + curFrame = new ID3v2Frame(); + + try { + XMP_Int64 frameSize = curFrame->read ( file, this->majorVersion ); + if ( frameSize == 0 ) { + delete curFrame; // ..since not becoming part of vector for latter delete. + break; // not a throw. There's nothing wrong with padding. + } + this->containsXMP = true; + } catch ( ... ) { + delete curFrame; + throw; + } + + // these are both pointer assignments, no (copy) construction + // (MemLeak Note: for all things pushed, memory cleanup is taken care of in destructor.) + this->framesVector.push_back ( curFrame ); + + //remember XMP-Frame, if it occurs: + if ( (curFrame->id ==xmpID) && + (curFrame->contentSize > 8) && CheckBytes ( &curFrame->content[0], "XMP\0", 4 ) ) { + + // be sure that this is the first packet (all else would be illegal format) + XMP_Validate ( this->framesMap[xmpID] == 0, "two XMP packets in one file", kXMPErr_BadFileFormat ); + //add this to map, needed on reconciliation + this->framesMap[xmpID] = curFrame; + + this->packetInfo.length = curFrame->contentSize - 4; // content minus "XMP\0" + this->packetInfo.offset = ( file->Offset() - this->packetInfo.length ); + + this->xmpPacket.erase(); //safety + this->xmpPacket.assign( &curFrame->content[4], curFrame->contentSize - 4 ); + this->containsXMP = true; // do this last, after all possible failure + + } + + // No space for another frame? => assume into ID3v2.4 padding. + XMP_Int64 newPos = file->Offset(); + XMP_Int64 spaceLeft = this->oldTagSize - newPos; // Depends on first check below! + if ( (newPos > this->oldTagSize) || (spaceLeft < (XMP_Int64)ID3Header::kID3_TagHeaderSize) ) break; + + } + + //////////////////////////////////////////////////// + // padding + + this->oldPadding = this->oldTagSize - file->Offset(); + this->oldFramesSize = this->oldTagSize - ID3Header::kID3_TagHeaderSize - this->oldPadding; + + XMP_Validate ( (this->oldPadding >= 0), "illegal oldTagSize or padding value", kXMPErr_BadFileFormat ); + + for ( XMP_Int64 i = this->oldPadding; i > 0; ) { + if ( i >= 8 ) { + if ( XIO::ReadInt64_BE(file) != 0 ) XMP_Throw ( "padding not nulled out", kXMPErr_BadFileFormat ); + i -= 8; + continue; + } + if ( XIO::ReadUns8(file) != 0) XMP_Throw ( "padding(2) not nulled out", kXMPErr_BadFileFormat ); + i--; + } + + //// read ID3v1 tag + if ( ! this->containsXMP ) this->containsXMP = id3v1Tag.read ( file, &this->xmpObj ); + +} // MP3_MetaHandler::CacheFileData + + +// ================================================================================================= +// MP3_MetaHandler::ProcessXMP +// =========================== +// +// Process the raw XMP and legacy metadata that was previously cached. + +void MP3_MetaHandler::ProcessXMP() +{ + + // Process the XMP packet. + if ( ! this->xmpPacket.empty() ) { + XMP_Assert ( this->containsXMP ); + XMP_StringPtr packetStr = this->xmpPacket.c_str(); + XMP_StringLen packetLen = (XMP_StringLen) this->xmpPacket.size(); + this->xmpObj.ParseFromBuffer ( packetStr, packetLen ); + this->processedXMP = true; + } + + /////////////////////////////////////////////////////////////////// + // assumptions on presence-absence "flag tags" + // ( unless no xmp whatsoever present ) + if ( ! this->xmpPacket.empty() ) this->xmpObj.SetProperty ( kXMP_NS_DM, "partOfCompilation", "false" ); + + //////////////////////////////////////////////////////////////////// + // import of legacy properties + ID3v2Frame* curFrame; + XMP_Bool hasTDRC = false; + XMP_DateTime newDateTime; + + if ( this->hasID3Tag ) { // otherwise pretty pointless... + + for ( int r = 0; reconProps[r].mainID != 0; ++r ) { + + //get the frame ID to look for + XMP_Uns32 logicalID = GetUns32BE ( reconProps[r].mainID ); + XMP_Uns32 storedID = logicalID; + if ( this->majorVersion == 2 ) storedID = GetUns32BE ( reconProps[r].v22ID ); + + // deal with each such frame in the frameVector + // (since there might be several, some of them not applicable, i.e. COMM) + + vector::iterator it; + for ( it = this->framesVector.begin(); it != this->framesVector.end(); ++it ) { + + curFrame = *it; + if ( storedID != curFrame->id ) continue; + + // go deal with it! + // get the property + std::string id3Text, xmpText; + bool result = curFrame->getFrameValue ( this->majorVersion, logicalID, &id3Text ); + if ( ! result ) continue; //ignore but preserve this frame (i.e. not applicable COMM frame) + + ////////////////////////////////////////////////////////////////////////////////// + // if we come as far as here, it's proven that there's a relevant XMP property + + this->containsXMP = true; + + ID3_Support::ID3v2Frame* t = this->framesMap [ storedID ]; + if ( t != 0 ) t->active = false; + + // add this to map (needed on reconciliation) + // note: above code reaches, that COMM/USLT frames + // only then reach this map, if they are 'eng'(lish) + // multiple occurences indeed leads to last one survives + // ( in this map, all survive in the file ) + this->framesMap [ storedID ] = curFrame; + + // now write away as needed; + // merely based on existence, relevant even if empty: + if ( logicalID == 0x54434D50) { // TCMP if exists: part of compilation + + this->xmpObj.SetProperty ( kXMP_NS_DM, "partOfCompilation", "true" ); + + } else if ( ! id3Text.empty() ) { + + switch ( logicalID ) { + + case 0x54495432: // TIT2 -> title["x-default"] + case 0x54434F50: // TCOP -> rights["x-default"] + this->xmpObj.SetLocalizedText ( reconProps[r].ns, reconProps[r].prop,"", "x-default", id3Text ); + break; + + case 0x54434F4E: // TCON -> genre + ID3_Support::GenreUtils::ConvertGenreToXMP ( id3Text.c_str(), &xmpText ); + if ( ! xmpText.empty() ) { + this->xmpObj.SetProperty ( reconProps[r].ns, reconProps[r].prop, xmpText ); + } + break; + + case 0x54594552: // TYER -> xmp:CreateDate.year + { + try { // Don't let wrong dates in id3 stop import. + if ( ! hasTDRC ) { + newDateTime.year = SXMPUtils::ConvertToInt ( id3Text ); + newDateTime.hasDate = true; + } + } catch ( ... ) { + // Do nothing, let other imports proceed. + } + break; + } + + case 0x54444154: //TDAT -> xmp:CreateDate.month and day + { + try { // Don't let wrong dates in id3 stop import. + // only if no TDRC has been found before + //&& must have the format DDMM + if ( (! hasTDRC) && (id3Text.length() == 4) ) { + newDateTime.day = SXMPUtils::ConvertToInt (id3Text.substr(0,2)); + newDateTime.month = SXMPUtils::ConvertToInt ( id3Text.substr(2,2)); + newDateTime.hasDate = true; + } + } catch ( ... ) { + // Do nothing, let other imports proceed. + } + break; + } + + case 0x54494D45: //TIME -> xmp:CreateDate.hours and minutes + { + try { // Don't let wrong dates in id3 stop import. + // only if no TDRC has been found before + // && must have the format HHMM + if ( (! hasTDRC) && (id3Text.length() == 4) ) { + newDateTime.hour = SXMPUtils::ConvertToInt (id3Text.substr(0,2)); + newDateTime.minute = SXMPUtils::ConvertToInt ( id3Text.substr(2,2)); + newDateTime.hasTime = true; + } + } catch ( ... ) { + // Do nothing, let other imports proceed. + } + break; + } + + case 0x54445243: // TDRC -> xmp:CreateDate //id3 v2.4 + { + try { // Don't let wrong dates in id3 stop import. + hasTDRC = true; + // This always wins over TYER, TDAT and TIME + SXMPUtils::ConvertToDate ( id3Text, &newDateTime ); + } catch ( ... ) { + // Do nothing, let other imports proceed. + } + break; + } + + default: + // NB: COMM/USLT need no special fork regarding language alternatives/multiple occurence. + // relevant code forks are in ID3_Support::getFrameValue() + this->xmpObj.SetProperty ( reconProps[r].ns, reconProps[r].prop, id3Text ); + break; + + }//switch + + } + + } //for iterator + + }//for reconProps + + // import DateTime + XMP_DateTime oldDateTime; + bool haveNewDateTime = newDateTime.year != 0 ; + if ( xmpObj.GetProperty_Date ( kXMP_NS_XMP, "CreateDate", &oldDateTime, 0 ) ) + { + + haveNewDateTime = haveNewDateTime && ( (newDateTime.year != oldDateTime.year) || ( (newDateTime.month != 0 ) + && ( (newDateTime.day != oldDateTime.day) || (newDateTime.month != oldDateTime.month) ) ) + || ( newDateTime.hasTime && ( (newDateTime.hour != oldDateTime.hour) || (newDateTime.minute != oldDateTime.minute) ) ) ); + } + // NOTE: no further validation nessesary the function "SetProperty_Date" will care about validating date and time + // any exception will be caught and block import + try { + if ( haveNewDateTime ) { + this->xmpObj.SetProperty_Date ( kXMP_NS_XMP, "CreateDate", newDateTime ); + } + } catch ( ... ) { + // Dont import invalid dates from ID3 + } + + } + + // very important to avoid multiple runs! (in which case I'd need to clean certain + // fields (i.e. regarding ->active setting) + this->processedXMP = true; + +} // MP3_MetaHandler::ProcessXMP + + +// ================================================================================================= +// MP3_MetaHandler::UpdateFile +// =========================== +void MP3_MetaHandler::UpdateFile ( bool doSafeUpdate ) +{ + if ( doSafeUpdate ) XMP_Throw ( "MP3_MetaHandler::UpdateFile: Safe update not supported", kXMPErr_Unavailable ); + + XMP_IO* file = this->parent->ioRef; + + // leave 2.3 resp. 2.4 header, since we want to let alone + // and don't know enough about the encoding of unrelated frames... + XMP_Assert( this->containsXMP ); + + tagIsDirty = false; + mustShift = false; + + // write out native properties: + // * update existing ones + // * create new frames as needed + // * delete frames if property is gone! + // see what there is to do for us: + + // RECON LOOP START + for (int r = 0; reconProps[r].mainID != 0; r++ ) { + + std::string value; + bool needDescriptor = false; + bool needEncodingByte = true; + + XMP_Uns32 logicalID = GetUns32BE ( reconProps[r].mainID ); + XMP_Uns32 storedID = logicalID; + if ( this->majorVersion == 2 ) storedID = GetUns32BE ( reconProps[r].v22ID ); + + ID3v2Frame* frame = framesMap[ storedID ]; // the actual frame (if already existing) + + // get XMP property + // * honour specific exceptions + // * leave value empty() if it doesn't exist ==> frame must be delete/not created + switch ( logicalID ) { + + case 0x54434D50: // TCMP if exists: part of compilation + if ( xmpObj.GetProperty( kXMP_NS_DM, "partOfCompilation", &value, 0 ) && ( 0 == stricmp( value.c_str(), "true" ) )) { + value = "1"; // set a TCMP frame of value 1 + } else { + value.erase(); // delete/prevent creation of frame + } + break; + + case 0x54495432: // TIT2 -> title["x-default"] + case 0x54434F50: // TCOP -> rights["x-default"] + if (! xmpObj.GetLocalizedText( reconProps[r].ns, reconProps[r].prop, "", "x-default", 0, &value, 0 )) value.erase(); // if not, erase string. + break; + + case 0x54434F4E: // TCON -> genre + { + bool found = xmpObj.GetProperty ( reconProps[r].ns, reconProps[r].prop, &value, 0 ); + if ( found ) { + std::string xmpValue = value; + ID3_Support::GenreUtils::ConvertGenreToID3 ( xmpValue.c_str(), &value ); + } + } + break; + + case 0x434F4D4D: // COMM + case 0x55534C54: // USLT, both need descriptor. + needDescriptor = true; + if (! xmpObj.GetProperty( reconProps[r].ns, reconProps[r].prop, &value, 0 )) value.erase(); + break; + + case 0x54594552: //TYER + case 0x54444154: //TDAT + case 0x54494D45: //TIME + { + if ( majorVersion <= 3 ) { // TYER, TIME and TDAT depricated since v. 2.4 -> else use TDRC + + XMP_DateTime dateTime; + if (! xmpObj.GetProperty_Date( reconProps[r].ns, reconProps[r].prop, &dateTime, 0 )) { // nothing found? -> Erase string. (Leads to Unset below) + value.erase(); + break; + } + + // TYER + if ( logicalID == 0x54594552 ) { + XMP_Validate( dateTime.year <= 9999 && dateTime.year > 0, "Year is out of range", kXMPErr_BadParam); + // get only Year! + SXMPUtils::ConvertFromInt( dateTime.year, "", &value ); + break; + } else if ( logicalID == 0x54444154 && dateTime.hasDate ) { + std::string day, month; + SXMPUtils::ConvertFromInt( dateTime.day, "", &day ); + SXMPUtils::ConvertFromInt( dateTime.month, "", &month ); + if ( dateTime.day < 10 ) + value = "0"; + value += day; + if ( dateTime.month < 10 ) + value += "0"; + value += month; + break; + } else if ( logicalID == 0x54494D45 && dateTime.hasTime ) { + std::string hour, minute; + SXMPUtils::ConvertFromInt( dateTime.hour, "", &hour ); + SXMPUtils::ConvertFromInt( dateTime.minute, "", &minute ); + if ( dateTime.hour < 10 ) + value = "0"; + value += hour; + if ( dateTime.minute < 10 ) + value += "0"; + value += minute; + break; + } else { + value.erase(); + break; + } + } else { + value.erase(); + break; + } + } + break; + + case 0x54445243: //TDRC (only v2.4) + { + // only export for id3 > v2.4 + if ( majorVersion > 3 ) { + if (! xmpObj.GetProperty( reconProps[r].ns, reconProps[r].prop, &value, 0 )) value.erase(); + } + break; + } + break; + + case 0x57434F50: //WCOP + needEncodingByte = false; + if (! xmpObj.GetProperty( reconProps[r].ns, reconProps[r].prop, &value, 0 )) value.erase(); // if not, erase string + break; + + case 0x5452434B: // TRCK + case 0x54504F53: // TPOS + // no break, go on: + + default: + if (! xmpObj.GetProperty( reconProps[r].ns, reconProps[r].prop, &value, 0 )) value.erase(); // if not, erase string + break; + + } + + // [XMP exist] x [frame exist] => four cases: + // 1/4) nothing before, nothing now + if ( value.empty() && (frame==0)) continue; // nothing to do + + // all else means there will be rewrite work to do: + tagIsDirty = true; + + // 2/4) value before, now gone: + if ( value.empty() && (frame!=0)) { + frame->active = false; //mark for non-use + continue; + } + + // 3/4) no old value, create new value + bool needUTF16 = false; + if ( needEncodingByte ) needUTF16 = (! ReconcileUtils::IsASCII ( value.c_str(), value.size() ) ); + if ( frame != 0 ) { + frame->setFrameValue( value, needDescriptor, needUTF16, false, needEncodingByte ); + } else { + ID3v2Frame* newFrame=new ID3v2Frame( storedID ); + newFrame->setFrameValue( value, needDescriptor, needUTF16, false, needEncodingByte ); //always write as utf16-le incl. BOM + framesVector.push_back( newFrame ); + framesMap[ storedID ] = newFrame; + continue; + } + + } // RECON LOOP END + + ///////////////////////////////////////////////////////////////////////////////// + // (Re)Build XMP frame: + + XMP_Uns32 xmpID = XMP_V23_ID; + if ( this->majorVersion == 2 ) xmpID = XMP_V22_ID; + + ID3v2Frame* frame = framesMap[ xmpID ]; + if ( frame != 0 ) { + frame->setFrameValue( this->xmpPacket, false, false, true ); + } else { + ID3v2Frame* newFrame=new ID3v2Frame( xmpID ); + newFrame->setFrameValue ( this->xmpPacket, false, false, true ); + framesVector.push_back ( newFrame ); + framesMap[ xmpID ] = newFrame; + } + + //////////////////////////////////////////////////////////////////////////////// + // Decision making + + XMP_Int32 frameHeaderSize = ID3v2Frame::kV23_FrameHeaderSize; + if ( this->majorVersion == 2 ) frameHeaderSize = ID3v2Frame::kV22_FrameHeaderSize; + + newFramesSize = 0; + for ( XMP_Uns32 i = 0; i < framesVector.size(); i++ ) { + if ( framesVector[i]->active ) newFramesSize += (frameHeaderSize + framesVector[i]->contentSize); + } + + mustShift = (newFramesSize > (XMP_Int64)(oldTagSize - ID3Header::kID3_TagHeaderSize)) || + //optimization: If more than 8K can be saved by rewriting the MP3, go do it: + ((newFramesSize + 8*1024) < oldTagSize ); + + if ( ! mustShift ) { // fill what we got + newTagSize = oldTagSize; + } else { // if need to shift anyway, get some nice 2K padding + newTagSize = newFramesSize + 2048 + ID3Header::kID3_TagHeaderSize; + } + newPadding = newTagSize - ID3Header::kID3_TagHeaderSize - newFramesSize; + + // shifting needed? -> shift + if ( mustShift ) { + XMP_Int64 filesize = file ->Length(); + if ( this->hasID3Tag ) { + XIO::Move ( file, oldTagSize, file, newTagSize, filesize - oldTagSize ); //fix [2338569] + } else { + XIO::Move ( file, 0, file, newTagSize, filesize ); // move entire file up. + } + } + + // correct size stuff, write out header + file ->Rewind(); + id3Header.write ( file, newTagSize ); + + // write out tags + for ( XMP_Uns32 i = 0; i < framesVector.size(); i++ ) { + if ( framesVector[i]->active ) framesVector[i]->write ( file, majorVersion ); + } + + // write out padding: + for ( XMP_Int64 i = newPadding; i > 0; ) { + const XMP_Uns64 zero = 0; + if ( i >= 8 ) { + file->Write ( &zero, 8 ); + i -= 8; + continue; + } + file->Write ( &zero, 1 ); + i--; + } + + // check end of file for ID3v1 tag + XMP_Int64 possibleTruncationPoint = file->Seek ( -128, kXMP_SeekFromEnd ); + bool alreadyHasID3v1 = (XIO::ReadInt32_BE( file ) & 0xFFFFFF00) == 0x54414700; // "TAG" + if ( ! alreadyHasID3v1 ) file->Seek ( 128, kXMP_SeekFromEnd ); // Seek will extend the file. + id3v1Tag.write( file, &this->xmpObj ); + + this->needsUpdate = false; //do last for safety reasons + +} // MP3_MetaHandler::UpdateFile + +// ================================================================================================= +// MP3_MetaHandler::WriteTempFile +// ============================== + +void MP3_MetaHandler::WriteTempFile ( XMP_IO* tempRef ) +{ + IgnoreParam(tempRef); + XMP_Throw ( "MP3_MetaHandler::WriteTempFile: Not supported", kXMPErr_Unimplemented ); +} // MP3_MetaHandler::WriteTempFile + diff --git a/core/libs/dngwriter/extra/xmp_sdk/XMPFiles/source/FileHandlers/MP3_Handler.hpp b/core/libs/dngwriter/extra/xmp_sdk/XMPFiles/source/FileHandlers/MP3_Handler.hpp new file mode 100644 index 0000000000..6916f2e940 --- /dev/null +++ b/core/libs/dngwriter/extra/xmp_sdk/XMPFiles/source/FileHandlers/MP3_Handler.hpp @@ -0,0 +1,90 @@ +#ifndef __MP3_Handler_hpp__ +#define __MP3_Handler_hpp__ 1 + +// ================================================================================================= +// ADOBE SYSTEMS INCORPORATED +// Copyright 2008 Adobe Systems Incorporated +// All Rights Reserved +// +// NOTICE: Adobe permits you to use, modify, and distribute this file in accordance with the terms +// of the Adobe license agreement accompanying it. +// ================================================================================================= + +#include "public/include/XMP_Environment.h" // ! This must be the first include. +#include "XMPFiles/source/XMPFiles_Impl.hpp" +#include "XMPFiles/source/FormatSupport/ID3_Support.hpp" + +using namespace std; +using namespace ID3_Support; + +extern XMPFileHandler * MP3_MetaHandlerCTor ( XMPFiles * parent ); + +extern bool MP3_CheckFormat ( XMP_FileFormat format, + XMP_StringPtr filePath, + XMP_IO* fileRef, + XMPFiles * parent ); +static const XMP_OptionBits kMP3_HandlerFlags = (kXMPFiles_CanInjectXMP | + kXMPFiles_CanExpand | + kXMPFiles_PrefersInPlace | + kXMPFiles_AllowsOnlyXMP | + kXMPFiles_ReturnsRawPacket | + kXMPFiles_CanReconcile); +class MP3_MetaHandler : public XMPFileHandler +{ +public: + MP3_MetaHandler ( XMPFiles * parent ); + ~MP3_MetaHandler(); + + void CacheFileData(); + + void UpdateFile ( bool doSafeUpdate ); + void WriteTempFile ( XMP_IO* tempRef ); + + void ProcessXMP(); +private: + //////////////////////////////////////////////////////////////////////////////////// + // instance vars + XMP_Int64 oldTagSize; // the entire tag, including padding, including 10B header + XMP_Int64 oldPadding; // number of padding bytes + XMP_Int64 oldFramesSize; // actual space needed by frames := oldTagSize - 10 - oldPadding + + XMP_Int64 newTagSize; + XMP_Int64 newPadding; + XMP_Int64 newFramesSize; + + // decision making: + bool tagIsDirty; // true, if any legacy properties changed. + bool mustShift; // entire tag to rewrite? (or possibly just XMP?) + + + XMP_Uns8 majorVersion, minorVersion; // Version Number post ID3v2, i.e. 3 0 ==> ID3v2.3.0 + bool hasID3Tag; //incoming file has an ID3 tag? + bool hasFooter; + //bool legacyChanged; // tag rewrite certainly needed? + + ID3Header id3Header; + + XMP_Int64 extHeaderSize; + bool hasExtHeader; + + // the frames + // * all to be kept till write-out + // * parsed/understood only if needed + // * vector used to free memory in handler destructor + std::vector framesVector; + + // ID3v1 - treated as a single object + ID3v1Tag id3v1Tag; + + // * also kept in a map for better import<->export access + // * only keeps legacy 'relevant frames' (i.e. no abused COMM frames) + // * only keeps last relevant frame + // * earlier 'relevant frames' will be deleted. This map also helps in this + // * key shall be the FrameID, always interpreted as BE + std::map framesMap; + +}; // MP3_MetaHandler + +// ================================================================================================= + +#endif /* __MP3_Handler_hpp__ */ diff --git a/core/libs/dngwriter/extra/xmp_sdk/XMPFiles/source/FileHandlers/MPEG2_Handler.cpp b/core/libs/dngwriter/extra/xmp_sdk/XMPFiles/source/FileHandlers/MPEG2_Handler.cpp new file mode 100644 index 0000000000..abeb781762 --- /dev/null +++ b/core/libs/dngwriter/extra/xmp_sdk/XMPFiles/source/FileHandlers/MPEG2_Handler.cpp @@ -0,0 +1,231 @@ +// ================================================================================================= +// ADOBE SYSTEMS INCORPORATED +// Copyright 2005 Adobe Systems Incorporated +// All Rights Reserved +// +// NOTICE: Adobe permits you to use, modify, and distribute this file in accordance with the terms +// of the Adobe license agreement accompanying it. +// ================================================================================================= + +#if XMP_WinBuild + #pragma warning ( disable : 4996 ) // '...' was declared deprecated +#endif + +#include "public/include/XMP_Environment.h" // ! XMP_Environment.h must be the first included header. + +#include "public/include/XMP_Const.h" +#include "public/include/XMP_IO.hpp" + +#include "XMPFiles/source/XMPFiles_Impl.hpp" +#include "source/XMPFiles_IO.hpp" +#include "source/XIO.hpp" +#include "source/IOUtils.hpp" + +#include "XMPFiles/source/FileHandlers/MPEG2_Handler.hpp" +#include "../FormatSupport/PackageFormat_Support.hpp" +using namespace std; + +// ================================================================================================= +/// \file MPEG2_Handler.cpp +/// \brief File format handler for MPEG2. +/// +/// BLECH! YUCK! GAG! MPEG-2 is done using a sidecar and recognition only by file extension! BARF!!!!! +/// +// ================================================================================================= + +// ================================================================================================= +// FindFileExtension +// ================= + +static inline XMP_StringPtr FindFileExtension ( XMP_StringPtr filePath ) +{ + + XMP_StringPtr pathEnd = filePath + strlen(filePath); + XMP_StringPtr extPtr; + + for ( extPtr = pathEnd-1; extPtr > filePath; --extPtr ) { + if ( (*extPtr == '.') || (*extPtr == '/') ) break; + #if XMP_WinBuild + if ( (*extPtr == '\\') || (*extPtr == ':') ) break; + #endif + } + + if ( (extPtr < filePath) || (*extPtr != '.') ) return pathEnd; + return extPtr; + +} // FindFileExtension + +// ================================================================================================= +// MPEG2_MetaHandlerCTor +// ===================== + +XMPFileHandler * MPEG2_MetaHandlerCTor ( XMPFiles * parent ) +{ + return new MPEG2_MetaHandler ( parent ); + +} // MPEG2_MetaHandlerCTor + +// ================================================================================================= +// MPEG2_CheckFormat +// ================= + +// The MPEG-2 handler uses just the file extension, not the file content. Worse yet, it also uses a +// sidecar file for the XMP. This works better if the handler owns the file, we open the sidecar +// instead of the actual MPEG-2 file. + +bool MPEG2_CheckFormat ( XMP_FileFormat format, + XMP_StringPtr filePath, + XMP_IO* fileRef, + XMPFiles * parent ) +{ + IgnoreParam(format); IgnoreParam(filePath); IgnoreParam(fileRef); + + XMP_Assert ( (format == kXMP_MPEGFile) || (format == kXMP_MPEG2File) ); + XMP_Assert ( fileRef == 0 ); + + return ( (parent->format == kXMP_MPEGFile) || (parent->format == kXMP_MPEG2File) ); // ! Just use the first call's format hint. + +} // MPEG2_CheckFormat + +// ================================================================================================= +// MPEG2_MetaHandler::MPEG2_MetaHandler +// ==================================== + +MPEG2_MetaHandler::MPEG2_MetaHandler ( XMPFiles * _parent ) +{ + this->parent = _parent; + this->handlerFlags = kMPEG2_HandlerFlags; + this->stdCharForm = kXMP_Char8Bit; + + XMP_StringPtr filePath = this->parent->GetFilePath().c_str(); + XMP_StringPtr extPtr = FindFileExtension ( filePath ); + this->sidecarPath.assign ( filePath, (extPtr - filePath) ); + this->sidecarPath += ".xmp"; +} // MPEG2_MetaHandler::MPEG2_MetaHandler + +// ================================================================================================= +// MPEG2_MetaHandler::~MPEG2_MetaHandler +// ===================================== + +MPEG2_MetaHandler::~MPEG2_MetaHandler() +{ + // Nothing to do. + +} // MPEG2_MetaHandler::~MPEG2_MetaHandler + +// ================================================================================================= +// MPEG2_MetaHandler::GetFileModDate +// ================================= + +bool MPEG2_MetaHandler::GetFileModDate ( XMP_DateTime * modDate ) +{ + if ( ! Host_IO::Exists ( this->sidecarPath.c_str() ) ) return false; + return Host_IO::GetModifyDate ( this->sidecarPath.c_str(), modDate ); + +} // MPEG2_MetaHandler::GetFileModDate + +// ================================================================================================= +// MPEG2_MetaHandler::FillAssociatedResources +// ================================= +void MPEG2_MetaHandler::FillAssociatedResources ( std::vector * resourceList ) +{ + resourceList->push_back(this->parent->GetFilePath()); + PackageFormat_Support::AddResourceIfExists(resourceList, this->sidecarPath); +} // MPEG2_MetaHandler::FillAssociatedResources + +// ================================================================================================= +// MPEG2_MetaHandler::IsMetadataWritable +// ================================= +bool MPEG2_MetaHandler::IsMetadataWritable ( ) +{ + return Host_IO::Writable( this->sidecarPath.c_str(), true ); +} // MPEG2_MetaHandler::IsMetadataWritable + +// ================================================================================================= +// MPEG2_MetaHandler::CacheFileData +// ================================ + +void MPEG2_MetaHandler::CacheFileData() +{ + bool readOnly = (! (this->parent->openFlags & kXMPFiles_OpenForUpdate)); + + if ( this->parent->UsesClientIO() ) { + XMP_Throw ( "MPEG2 cannot be used with client-managed I/O", kXMPErr_InternalFailure ); + } + + this->containsXMP = false; + this->processedXMP = true; // Whatever we do here is all that we do for XMPFiles::OpenFile. + + // Try to open the sidecar XMP file. Tolerate an open failure, there might not be any XMP. + // Note that MPEG2_CheckFormat can't save the sidecar path because the handler doesn't exist then. + + if ( ! Host_IO::Exists ( this->sidecarPath.c_str() ) ) return; // OK to not have XMP. + + XMPFiles_IO * localFile = XMPFiles_IO::New_XMPFiles_IO ( this->sidecarPath.c_str(), readOnly ); + if ( localFile == 0 ) XMP_Throw ( "Failure opening MPEG-2 XMP file", kXMPErr_ExternalFailure ); + this->parent->ioRef = localFile; + + // Extract the sidecar's contents and parse. + + this->packetInfo.offset = 0; // We take the whole sidecar file. + this->packetInfo.length = (XMP_Int32) localFile->Length(); + + if ( this->packetInfo.length > 0 ) { + + this->xmpPacket.assign ( this->packetInfo.length, ' ' ); + localFile->ReadAll ( (void*)this->xmpPacket.c_str(), this->packetInfo.length ); + + this->xmpObj.ParseFromBuffer ( this->xmpPacket.c_str(), (XMP_StringLen)this->xmpPacket.size() ); + this->containsXMP = true; + + } + + if ( readOnly ) { + localFile->Close(); + delete localFile; + this->parent->ioRef = 0; + } + +} // MPEG2_MetaHandler::CacheFileData + +// ================================================================================================= +// MPEG2_MetaHandler::UpdateFile +// ============================= + +void MPEG2_MetaHandler::UpdateFile ( bool doSafeUpdate ) +{ + if ( ! this->needsUpdate ) return; + + XMP_Assert ( this->parent->UsesLocalIO() ); + + if ( this->parent->ioRef == 0 ) { + XMP_Assert ( ! Host_IO::Exists ( this->sidecarPath.c_str() ) ); + Host_IO::Create ( this->sidecarPath.c_str() ); + this->parent->ioRef = XMPFiles_IO::New_XMPFiles_IO ( this->sidecarPath.c_str(), Host_IO::openReadWrite ); + if ( this->parent->ioRef == 0 ) XMP_Throw ( "Failure opening MPEG-2 XMP file", kXMPErr_ExternalFailure ); + } + + XMP_IO* fileRef = this->parent->ioRef; + XMP_Assert ( fileRef != 0 ); + XIO::ReplaceTextFile ( fileRef, this->xmpPacket, doSafeUpdate ); + + XMPFiles_IO* localFile = (XMPFiles_IO*)fileRef; + localFile->Close(); + delete localFile; + this->parent->ioRef = 0; + + this->needsUpdate = false; + +} // MPEG2_MetaHandler::UpdateFile + +// ================================================================================================= +// MPEG2_MetaHandler::WriteTempFile +// ================================ + +void MPEG2_MetaHandler::WriteTempFile ( XMP_IO* tempRef ) +{ + IgnoreParam(tempRef); + + XMP_Throw ( "MPEG2_MetaHandler::WriteTempFile: Should never be called", kXMPErr_Unavailable ); + +} // MPEG2_MetaHandler::WriteTempFile diff --git a/core/libs/dngwriter/extra/xmp_sdk/XMPFiles/source/FileHandlers/MPEG2_Handler.hpp b/core/libs/dngwriter/extra/xmp_sdk/XMPFiles/source/FileHandlers/MPEG2_Handler.hpp new file mode 100644 index 0000000000..549781ff9c --- /dev/null +++ b/core/libs/dngwriter/extra/xmp_sdk/XMPFiles/source/FileHandlers/MPEG2_Handler.hpp @@ -0,0 +1,67 @@ +#ifndef __MPEG2_Handler_hpp__ +#define __MPEG2_Handler_hpp__ 1 + +// ================================================================================================= +// ADOBE SYSTEMS INCORPORATED +// Copyright 2005 Adobe Systems Incorporated +// All Rights Reserved +// +// NOTICE: Adobe permits you to use, modify, and distribute this file in accordance with the terms +// of the Adobe license agreement accompanying it. +// ================================================================================================= + +#include "public/include/XMP_Environment.h" // ! XMP_Environment.h must be the first included header. + +#include "public/include/XMP_Const.h" +#include "public/include/XMP_IO.hpp" + +#include "XMPFiles/source/XMPFiles_Impl.hpp" + +// ================================================================================================= +/// \file MPEG2_Handler.hpp +/// \brief File format handler for MPEG2. +/// +/// This header ... +/// +// ================================================================================================= + +extern XMPFileHandler * MPEG2_MetaHandlerCTor ( XMPFiles * parent ); + +extern bool MPEG2_CheckFormat ( XMP_FileFormat format, + XMP_StringPtr filePath, + XMP_IO * fileRef, + XMPFiles * parent); + +static const XMP_OptionBits kMPEG2_HandlerFlags = ( kXMPFiles_CanInjectXMP | + kXMPFiles_CanExpand | + kXMPFiles_CanRewrite | + kXMPFiles_AllowsOnlyXMP | + kXMPFiles_ReturnsRawPacket | + kXMPFiles_HandlerOwnsFile | + kXMPFiles_AllowsSafeUpdate | + kXMPFiles_UsesSidecarXMP ); + +class MPEG2_MetaHandler : public XMPFileHandler +{ +public: + + std::string sidecarPath; + + MPEG2_MetaHandler ( XMPFiles * parent ); + ~MPEG2_MetaHandler(); + + bool GetFileModDate ( XMP_DateTime * modDate ); + void FillAssociatedResources ( std::vector * resourceList ); + bool IsMetadataWritable ( ) ; + + void CacheFileData(); + + void UpdateFile ( bool doSafeUpdate ); + void WriteTempFile ( XMP_IO * tempRef ); + + +}; // MPEG2_MetaHandler + +// ================================================================================================= + +#endif /* __MPEG2_Handler_hpp__ */ diff --git a/core/libs/dngwriter/extra/xmp_sdk/XMPFiles/source/FileHandlers/MPEG4_Handler.cpp b/core/libs/dngwriter/extra/xmp_sdk/XMPFiles/source/FileHandlers/MPEG4_Handler.cpp new file mode 100644 index 0000000000..7c25ea86cb --- /dev/null +++ b/core/libs/dngwriter/extra/xmp_sdk/XMPFiles/source/FileHandlers/MPEG4_Handler.cpp @@ -0,0 +1,3066 @@ +// ================================================================================================= +// ADOBE SYSTEMS INCORPORATED +// Copyright 2006 Adobe Systems Incorporated +// All Rights Reserved +// +// NOTICE: Adobe permits you to use, modify, and distribute this file in accordance with the terms +// of the Adobe license agreement accompanying it. +// ================================================================================================= + +#include "public/include/XMP_Environment.h" // ! XMP_Environment.h must be the first included header. + +#include "public/include/XMP_Const.h" +#include "public/include/XMP_IO.hpp" + +#include "XMPFiles/source/XMPFiles_Impl.hpp" +#include "source/XMPFiles_IO.hpp" +#include "source/XIO.hpp" + +#include "XMPFiles/source/FileHandlers/MPEG4_Handler.hpp" + +#include "XMPFiles/source/FormatSupport/ISOBaseMedia_Support.hpp" +#include "XMPFiles/source/FormatSupport/MOOV_Support.hpp" + +#include "source/XMP_ProgressTracker.hpp" +#include "source/UnicodeConversions.hpp" +#include "XMP_MD5.h" +#include + +#if XMP_WinBuild + #pragma warning ( disable : 4996 ) // '...' was declared deprecated +#endif + +using namespace std; + +// ================================================================================================= +/// \file MPEG4_Handler.cpp +/// \brief File format handler for MPEG-4, a flavor of the ISO Base Media File Format. +/// +/// This handler ... +/// +// ================================================================================================= + +// The basic content of a timecode sample description table entry. Does not include trailing boxes. + +#if SUNOS_SPARC || SUNOS_X86 +#pragma pack ( 1 ) +#else +#pragma pack ( push, 1 ) +#endif //#if SUNOS_SPARC || SUNOS_X86 + +struct stsdBasicEntry { + XMP_Uns32 entrySize; + XMP_Uns32 format; + XMP_Uns8 reserved_1 [6]; + XMP_Uns16 dataRefIndex; + XMP_Uns32 reserved_2; + XMP_Uns32 flags; + XMP_Uns32 timeScale; + XMP_Uns32 frameDuration; + XMP_Uns8 frameCount; + XMP_Uns8 reserved_3; +}; + +#if SUNOS_SPARC || SUNOS_X86 +#pragma pack ( ) +#else +#pragma pack ( pop ) +#endif //#if SUNOS_SPARC || SUNOS_X86 + +// ================================================================================================= + +// ! The buffer and constants are both already big endian. +#define Get4CharCode(buffPtr) (*((XMP_Uns32*)(buffPtr))) + +// ================================================================================================= + +static inline bool IsClassicQuickTimeBox ( XMP_Uns32 boxType ) +{ + if ( (boxType == ISOMedia::k_moov) || (boxType == ISOMedia::k_mdat) || (boxType == ISOMedia::k_pnot) || + (boxType == ISOMedia::k_free) || (boxType == ISOMedia::k_skip) || (boxType == ISOMedia::k_wide) ) return true; + return false; +} // IsClassicQuickTimeBox + +// ================================================================================================= + +// Pairs of 3 letter ISO 639-2 codes mapped to 2 letter ISO 639-1 codes from: +// http://www.loc.gov/standards/iso639-2/php/code_list.php +// Would have to write an "==" operator to use std::map, must compare values not pointers. +// ! Not fully sorted, do not use a binary search. + +static XMP_StringPtr kKnownLangs[] = + { "aar", "aa", "abk", "ab", "afr", "af", "aka", "ak", "alb", "sq", "sqi", "sq", "amh", "am", + "ara", "ar", "arg", "an", "arm", "hy", "hye", "hy", "asm", "as", "ava", "av", "ave", "ae", + "aym", "ay", "aze", "az", "bak", "ba", "bam", "bm", "baq", "eu", "eus", "eu", "bel", "be", + "ben", "bn", "bih", "bh", "bis", "bi", "bod", "bo", "tib", "bo", "bos", "bs", "bre", "br", + "bul", "bg", "bur", "my", "mya", "my", "cat", "ca", "ces", "cs", "cze", "cs", "cha", "ch", + "che", "ce", "chi", "zh", "zho", "zh", "chu", "cu", "chv", "cv", "cor", "kw", "cos", "co", + "cre", "cr", "cym", "cy", "wel", "cy", "cze", "cs", "ces", "cs", "dan", "da", "deu", "de", + "ger", "de", "div", "dv", "dut", "nl", "nld", "nl", "dzo", "dz", "ell", "el", "gre", "el", + "eng", "en", "epo", "eo", "est", "et", "eus", "eu", "baq", "eu", "ewe", "ee", "fao", "fo", + "fas", "fa", "per", "fa", "fij", "fj", "fin", "fi", "fra", "fr", "fre", "fr", "fre", "fr", + "fra", "fr", "fry", "fy", "ful", "ff", "geo", "ka", "kat", "ka", "ger", "de", "deu", "de", + "gla", "gd", "gle", "ga", "glg", "gl", "glv", "gv", "gre", "el", "ell", "el", "grn", "gn", + "guj", "gu", "hat", "ht", "hau", "ha", "heb", "he", "her", "hz", "hin", "hi", "hmo", "ho", + "hrv", "hr", "scr", "hr", "hun", "hu", "hye", "hy", "arm", "hy", "ibo", "ig", "ice", "is", + "isl", "is", "ido", "io", "iii", "ii", "iku", "iu", "ile", "ie", "ina", "ia", "ind", "id", + "ipk", "ik", "isl", "is", "ice", "is", "ita", "it", "jav", "jv", "jpn", "ja", "kal", "kl", + "kan", "kn", "kas", "ks", "kat", "ka", "geo", "ka", "kau", "kr", "kaz", "kk", "khm", "km", + "kik", "ki", "kin", "rw", "kir", "ky", "kom", "kv", "kon", "kg", "kor", "ko", "kua", "kj", + "kur", "ku", "lao", "lo", "lat", "la", "lav", "lv", "lim", "li", "lin", "ln", "lit", "lt", + "ltz", "lb", "lub", "lu", "lug", "lg", "mac", "mk", "mkd", "mk", "mah", "mh", "mal", "ml", + "mao", "mi", "mri", "mi", "mar", "mr", "may", "ms", "msa", "ms", "mkd", "mk", "mac", "mk", + "mlg", "mg", "mlt", "mt", "mol", "mo", "mon", "mn", "mri", "mi", "mao", "mi", "msa", "ms", + "may", "ms", "mya", "my", "bur", "my", "nau", "na", "nav", "nv", "nbl", "nr", "nde", "nd", + "ndo", "ng", "nep", "ne", "nld", "nl", "dut", "nl", "nno", "nn", "nob", "nb", "nor", "no", + "nya", "ny", "oci", "oc", "oji", "oj", "ori", "or", "orm", "om", "oss", "os", "pan", "pa", + "per", "fa", "fas", "fa", "pli", "pi", "pol", "pl", "por", "pt", "pus", "ps", "que", "qu", + "roh", "rm", "ron", "ro", "rum", "ro", "rum", "ro", "ron", "ro", "run", "rn", "rus", "ru", + "sag", "sg", "san", "sa", "scc", "sr", "srp", "sr", "scr", "hr", "hrv", "hr", "sin", "si", + "slk", "sk", "slo", "sk", "slo", "sk", "slk", "sk", "slv", "sl", "sme", "se", "smo", "sm", + "sna", "sn", "snd", "sd", "som", "so", "sot", "st", "spa", "es", "sqi", "sq", "alb", "sq", + "srd", "sc", "srp", "sr", "scc", "sr", "ssw", "ss", "sun", "su", "swa", "sw", "swe", "sv", + "tah", "ty", "tam", "ta", "tat", "tt", "tel", "te", "tgk", "tg", "tgl", "tl", "tha", "th", + "tib", "bo", "bod", "bo", "tir", "ti", "ton", "to", "tsn", "tn", "tso", "ts", "tuk", "tk", + "tur", "tr", "twi", "tw", "uig", "ug", "ukr", "uk", "urd", "ur", "uzb", "uz", "ven", "ve", + "vie", "vi", "vol", "vo", "wel", "cy", "cym", "cy", "wln", "wa", "wol", "wo", "xho", "xh", + "yid", "yi", "yor", "yo", "zha", "za", "zho", "zh", "chi", "zh", "zul", "zu", + 0, 0 }; + +static inline XMP_StringPtr Lookup2LetterLang ( XMP_StringPtr lang3 ) +{ + for ( size_t i = 0; kKnownLangs[i] != 0; i += 2 ) { + if ( XMP_LitMatch ( lang3, kKnownLangs[i] ) ) return kKnownLangs[i+1]; + } + return ""; +} + +static inline XMP_StringPtr Lookup3LetterLang ( XMP_StringPtr lang2 ) +{ + for ( size_t i = 0; kKnownLangs[i] != 0; i += 2 ) { + if ( XMP_LitMatch ( lang2, kKnownLangs[i+1] ) ) return kKnownLangs[i]; + } + return ""; +} + + +#define IsTolerableBoxChar(ch) ( ((0x20 <= (ch)) && ((ch) <= 0x7E)) || ((ch) == 0xA9) ) + +static inline bool IsTolerableBox ( XMP_Uns32 boxType ) +{ + // Make sure the box type is 4 ASCII characters or 0xA9 (MacRoman copyright). + XMP_Uns8 b1 = (XMP_Uns8) (boxType >> 24); + XMP_Uns8 b2 = (XMP_Uns8) ((boxType >> 16) & 0xFF); + XMP_Uns8 b3 = (XMP_Uns8) ((boxType >> 8) & 0xFF); + XMP_Uns8 b4 = (XMP_Uns8) (boxType & 0xFF); + bool ok = IsTolerableBoxChar(b1) && IsTolerableBoxChar(b2) && + IsTolerableBoxChar(b3) && IsTolerableBoxChar(b4); + return ok; +} + +static inline bool IsXMPUUID ( XMP_IO * fileRef,XMP_Uns64 contentSize, bool unmovedFilePtr=false ) +{ + if ( contentSize < 16 ) return false; + XMP_Uns8 uuid [16]; + fileRef->ReadAll ( uuid, 16 ); + if (unmovedFilePtr) fileRef->Seek ( -16, kXMP_SeekFromCurrent ); + if ( memcmp ( uuid, ISOMedia::k_xmpUUID, 16 ) != 0 ) return false; // Check for the XMP GUID. + return true; +} + +// ================================================================================================= +// MPEG4_CheckFormat +// ================= +// +// There are 3 variations of recognized file: +// - Normal MPEG-4 - has an 'ftyp' box containing a known compatible brand but not 'qt '. +// - Modern QuickTime - has an 'ftyp' box containing 'qt ' as a compatible brand. +// - Classic QuickTime - has no 'ftyp' box, should have recognized top level boxes. +// +// An MPEG-4 or modern QuickTime file is an instance of an ISO Base Media file, ISO 14496-12 and -14. +// A classic QuickTime file has the same physical box structure, but somewhat different box types. +// The ISO files must begin with an 'ftyp' box containing 'mp41', 'mp42', 'f4v ', 'qt ', 'isom','3gp4', +// '3g2a','3g2b' or '3g2c' in the compatible brands. +// +// The general box structure is: +// +// 0 4 uns32 box size, 0 means "to EoF", 1 means 64-bit size follows +// 4 4 uns32 box type +// 8 8 uns64 box size, present only if uns32 size is 1 +// - * box content +// +// The 'ftyp' box content is: +// +// - 4 uns32 major brand +// - 4 uns32 minor version +// - * uns32 sequence of compatible brands, to the end of the box + +// ! With the addition of QuickTime support there is some change in behavior in OpenFile when the +// ! kXMPFiles_OpenStrictly option is used wth a specific file format. The kXMP_MPEG4File and +// ! kXMP_MOVFile formats are treated uniformly, you can't force "real MOV" or "real MPEG-4". You +// ! can check afterwards using GetFileInfo to see what the file happens to be. + +bool MPEG4_CheckFormat ( XMP_FileFormat format, + XMP_StringPtr filePath, + XMP_IO* fileRef, + XMPFiles* parent ) +{ + XMP_Uns8 buffer [4*1024]; + XMP_Uns32 ioCount, brandCount, brandOffset; + XMP_Uns64 fileSize, nextOffset; + ISOMedia::BoxInfo currBox; + + XMP_AbortProc abortProc = parent->abortProc; + void * abortArg = parent->abortArg; + const bool checkAbort = (abortProc != 0); + + bool openStrictly = XMP_OptionIsSet ( parent->openFlags, kXMPFiles_OpenStrictly); + + // Get the first box's info, see if it is 'ftyp' or not. + + XMP_Assert ( (parent->tempPtr == 0) && (parent->tempUI32 == 0) ); + + fileSize = fileRef->Length(); + if ( fileSize < 8 ) return false; + + nextOffset = ISOMedia::GetBoxInfo ( fileRef, 0, fileSize, &currBox ); + if ( currBox.headerSize < 8 ) return false; // Can't be an ISO or QuickTime file. + + if ( currBox.boxType == ISOMedia::k_ftyp ) { + + // Have an 'ftyp' box, look through the compatible brands. If 'qt ' is present then this is + // a modern QuickTime file, regardless of what else is found. Otherwise this is plain ISO if + // any of the other recognized brands are found. + + if ( currBox.contentSize < 12 ) return false; // No compatible brands at all. + if ( currBox.contentSize > 1024*1024 ) return false; // Sanity check and make sure count fits in 32 bits. + brandCount = ((XMP_Uns32)currBox.contentSize - 8) >> 2; + + fileRef->Seek ( 8, kXMP_SeekFromCurrent ); // Skip the major and minor brands. + ioCount = brandOffset = 0; + + bool haveCompatibleBrand = false; + + for ( ; brandCount > 0; --brandCount, brandOffset += 4 ) { + + if ( brandOffset >= ioCount ) { + if ( checkAbort && abortProc(abortArg) ) { + XMP_Throw ( "MPEG4_CheckFormat - User abort", kXMPErr_UserAbort ); + } + ioCount = 4 * brandCount; + if ( ioCount > sizeof(buffer) ) ioCount = sizeof(buffer); + ioCount = fileRef->ReadAll ( buffer, ioCount ); + brandOffset = 0; + } + + XMP_Uns32 brand = GetUns32BE ( &buffer[brandOffset] ); + if ( brand == ISOMedia::k_qt ) { // Don't need to look further. + if ( openStrictly && (format != kXMP_MOVFile) ) return false; + parent->format = kXMP_MOVFile; + parent->tempUI32 = MOOV_Manager::kFileIsModernQT; + return true; + } + else if ( ( brand == ISOMedia::k_mp41 ) || ( brand == ISOMedia::k_mp42 ) || + ( brand == ISOMedia::k_f4v ) || ( brand == ISOMedia::k_avc1 ) || ( brand == ISOMedia::k_isom ) || + ( brand == ISOMedia::k_3gp4 ) || ( brand == ISOMedia::k_3g2a ) || ( brand == ISOMedia::k_3g2b ) || + ( brand == ISOMedia::k_3g2c ) ) { + haveCompatibleBrand = true; // Need to keep looking in case 'qt ' follows. + } + + } + + if ( ! haveCompatibleBrand ) return false; + if ( openStrictly && (format != kXMP_MPEG4File) ) return false; + parent->format = kXMP_MPEG4File; + parent->tempUI32 = MOOV_Manager::kFileIsNormalISO; + return true; + + } else { + // No 'ftyp', look for classic QuickTime: 'moov', 'mdat', 'pnot', 'free', 'skip', and 'wide'. + // As an expedient, quit when a 'moov' box is found. Tolerate other boxes, they are in the + // wild for ill-formed files, e.g. seen when 'moov'/'udta' children get left at top level. + + while ( currBox.boxType != ISOMedia::k_moov ) { + + if ( ! IsClassicQuickTimeBox ( currBox.boxType ) ) { + if ( ! IsTolerableBox(currBox.boxType) ) return false; + } + if ( nextOffset >= fileSize ) return false; + if ( checkAbort && abortProc(abortArg) ) { + XMP_Throw ( "MPEG4_CheckFormat - User abort", kXMPErr_UserAbort ); + } + nextOffset = ISOMedia::GetBoxInfo ( fileRef, nextOffset, fileSize, &currBox ); + + } + + if ( openStrictly && (format != kXMP_MOVFile) ) return false; + parent->format = kXMP_MOVFile; + parent->tempUI32 = MOOV_Manager::kFileIsTraditionalQT; + return true; + + } + + return false; + +} // MPEG4_CheckFormat + +// ================================================================================================= +// MPEG4_MetaHandlerCTor +// ===================== + +XMPFileHandler * MPEG4_MetaHandlerCTor ( XMPFiles * parent ) +{ + + return new MPEG4_MetaHandler ( parent ); + +} // MPEG4_MetaHandlerCTor + +// ================================================================================================= +// MPEG4_MetaHandler::MPEG4_MetaHandler +// ==================================== + +MPEG4_MetaHandler::MPEG4_MetaHandler ( XMPFiles * _parent ) + : fileMode(0), havePreferredXMP(false), xmpBoxPos(0), moovBoxPos(0), xmpBoxSize(0), moovBoxSize(0) +{ + + this->parent = _parent; // Inherited, can't set in the prefix. + this->handlerFlags = kMPEG4_HandlerFlags; + this->stdCharForm = kXMP_Char8Bit; + this->fileMode = (XMP_Uns8)_parent->tempUI32; + _parent->tempUI32 = 0; + +} // MPEG4_MetaHandler::MPEG4_MetaHandler + +// ================================================================================================= +// MPEG4_MetaHandler::~MPEG4_MetaHandler +// ===================================== + +MPEG4_MetaHandler::~MPEG4_MetaHandler() +{ + + // Nothing to do. + +} // MPEG4_MetaHandler::~MPEG4_MetaHandler + +// ================================================================================================= +// SecondsToXMPDate +// ================ + +// *** ASF has similar code with different origin, should make a shared utility. + +static void SecondsToXMPDate ( XMP_Uns64 isoSeconds, XMP_DateTime * xmpDate ) +{ + memset ( xmpDate, 0, sizeof(XMP_DateTime) ); // AUDIT: Using sizeof(XMP_DateTime) is safe. + + XMP_Int32 days = (XMP_Int32) (isoSeconds / 86400); + isoSeconds -= ((XMP_Uns64)days * 86400); + + XMP_Int32 hour = (XMP_Int32) (isoSeconds / 3600); + isoSeconds -= ((XMP_Uns64)hour * 3600); + + XMP_Int32 minute = (XMP_Int32) (isoSeconds / 60); + isoSeconds -= ((XMP_Uns64)minute * 60); + + XMP_Int32 second = (XMP_Int32)isoSeconds; + + xmpDate->year = 1904; // Start with the ISO origin. + xmpDate->month = 1; + xmpDate->day = 1; + + xmpDate->day += days; // Add in the delta. + xmpDate->hour = hour; + xmpDate->minute = minute; + xmpDate->second = second; + + xmpDate->hasTimeZone = true; // ! Needed for ConvertToUTCTime to do anything. + SXMPUtils::ConvertToUTCTime ( xmpDate ); // Normalize the date/time. + +} // SecondsToXMPDate + +// ================================================================================================= +// XMPDateToSeconds +// ================ + +// *** ASF has similar code with different origin, should make a shared utility. + +static bool IsLeapYear ( XMP_Int32 year ) +{ + if ( year < 0 ) year = -year + 1; // Fold the negative years, assuming there is a year 0. + if ( (year % 4) != 0 ) return false; // Not a multiple of 4. + if ( (year % 100) != 0 ) return true; // A multiple of 4 but not a multiple of 100. + if ( (year % 400) == 0 ) return true; // A multiple of 400. + return false; // A multiple of 100 but not a multiple of 400. +} + +// ------------------------------------------------------------------------------------------------- + +static XMP_Int32 DaysInMonth ( XMP_Int32 year, XMP_Int32 month ) +{ + static XMP_Int32 daysInMonth[13] = { 0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 }; + // Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec + XMP_Int32 days = daysInMonth[month]; + if ( (month == 2) && IsLeapYear(year) ) days += 1; + return days; +} + +// ------------------------------------------------------------------------------------------------- + +static void XMPDateToSeconds ( const XMP_DateTime & _xmpDate, XMP_Uns64 * isoSeconds ) +{ + XMP_DateTime xmpDate = _xmpDate; + SXMPUtils::ConvertToUTCTime ( &xmpDate ); + + XMP_Uns64 tempSeconds = (XMP_Uns64)xmpDate.second; + tempSeconds += (XMP_Uns64)xmpDate.minute * 60; + tempSeconds += (XMP_Uns64)xmpDate.hour * 3600; + + XMP_Int32 days = (xmpDate.day - 1); + + --xmpDate.month; + while ( xmpDate.month >= 1 ) { + days += DaysInMonth ( xmpDate.year, xmpDate.month ); + --xmpDate.month; + } + + --xmpDate.year; + while ( xmpDate.year >= 1904 ) { + days += (IsLeapYear ( xmpDate.year) ? 366 : 365 ); + --xmpDate.year; + } + + tempSeconds += (XMP_Uns64)days * 86400; + *isoSeconds = tempSeconds; + +} // XMPDateToSeconds + +// ================================================================================================= +// ImportMVHDItems +// =============== + +static bool ImportMVHDItems ( MOOV_Manager::BoxInfo mvhdInfo, SXMPMeta * xmp ) +{ + XMP_Assert ( mvhdInfo.boxType == ISOMedia::k_mvhd ); + if ( mvhdInfo.contentSize < 4 ) return false; // Just enough to check the version/flags at first. + + XMP_Uns8 mvhdVersion = *mvhdInfo.content; + if ( mvhdVersion > 1 ) return false; + + XMP_Uns64 creationTime, modificationTime, duration; + XMP_Uns32 timescale; + + if ( mvhdVersion == 0 ) { + + if ( mvhdInfo.contentSize < sizeof ( MOOV_Manager::Content_mvhd_0 ) ) return false; + MOOV_Manager::Content_mvhd_0 * mvhdRaw_0 = (MOOV_Manager::Content_mvhd_0*) mvhdInfo.content; + + creationTime = (XMP_Uns64) GetUns32BE ( &mvhdRaw_0->creationTime ); + modificationTime = (XMP_Uns64) GetUns32BE ( &mvhdRaw_0->modificationTime ); + timescale = GetUns32BE ( &mvhdRaw_0->timescale ); + duration = (XMP_Uns64) GetUns32BE ( &mvhdRaw_0->duration ); + + } else { + + XMP_Assert ( mvhdVersion == 1 ); + if ( mvhdInfo.contentSize < sizeof ( MOOV_Manager::Content_mvhd_1 ) ) return false; + MOOV_Manager::Content_mvhd_1 * mvhdRaw_1 = (MOOV_Manager::Content_mvhd_1*) mvhdInfo.content; + + creationTime = GetUns64BE ( &mvhdRaw_1->creationTime ); + modificationTime = GetUns64BE ( &mvhdRaw_1->modificationTime ); + timescale = GetUns32BE ( &mvhdRaw_1->timescale ); + duration = GetUns64BE ( &mvhdRaw_1->duration ); + + } + + bool haveImports = false; + XMP_DateTime xmpDate; + + if ( (creationTime >> 32) < 0xFF ) { // Sanity check for bogus date info. + SecondsToXMPDate ( creationTime, &xmpDate ); + xmp->SetProperty_Date ( kXMP_NS_XMP, "CreateDate", xmpDate ); + haveImports = true; + } + + if ( (modificationTime >> 32) < 0xFF ) { // Sanity check for bogus date info. + SecondsToXMPDate ( modificationTime, &xmpDate ); + xmp->SetProperty_Date ( kXMP_NS_XMP, "ModifyDate", xmpDate ); + haveImports = true; + } + + if ( timescale != 0 ) { // Avoid 1/0 for the scale field. + char buffer [32]; // A 64-bit number is at most 20 digits. + xmp->DeleteProperty ( kXMP_NS_DM, "duration" ); // Delete the whole struct. + snprintf ( buffer, sizeof(buffer), "%llu", duration ); // AUDIT: The buffer is big enough. + xmp->SetStructField ( kXMP_NS_DM, "duration", kXMP_NS_DM, "value", &buffer[0] ); + snprintf ( buffer, sizeof(buffer), "1/%u", timescale ); // AUDIT: The buffer is big enough. + xmp->SetStructField ( kXMP_NS_DM, "duration", kXMP_NS_DM, "scale", &buffer[0] ); + haveImports = true; + } + + return haveImports; + +} // ImportMVHDItems + +// ================================================================================================= +// ExportMVHDItems +// =============== + +static void ExportMVHDItems ( const SXMPMeta & xmp, MOOV_Manager * moovMgr ) +{ + XMP_DateTime xmpDate; + bool createFound, modifyFound; + XMP_Uns64 createSeconds = 0, modifySeconds = 0; + + MOOV_Manager::BoxInfo mvhdInfo; + MOOV_Manager::BoxRef mvhdRef = moovMgr->GetBox ( "moov/mvhd", &mvhdInfo ); + if ( (mvhdRef == 0) || (mvhdInfo.contentSize < 4) ) return; + + XMP_Uns8 version = *mvhdInfo.content; + if ( version > 1 ) return; + + createFound = xmp.GetProperty_Date ( kXMP_NS_XMP, "CreateDate", &xmpDate, 0 ); + if ( createFound ) XMPDateToSeconds ( xmpDate, &createSeconds ); + + modifyFound = xmp.GetProperty_Date ( kXMP_NS_XMP, "ModifyDate", &xmpDate, 0 ); + if ( modifyFound ) XMPDateToSeconds ( xmpDate, &modifySeconds ); + + if ( version == 1 ) { + + // Modify the v1 box in-place. + + if ( mvhdInfo.contentSize < sizeof ( MOOV_Manager::Content_mvhd_1 ) ) return; + + XMP_Uns64 oldCreate = GetUns64BE ( mvhdInfo.content + 4 ); + XMP_Uns64 oldModify = GetUns64BE ( mvhdInfo.content + 12 ); + + if ( createFound ) { + if ( createSeconds != oldCreate ) PutUns64BE ( createSeconds, ((XMP_Uns8*)mvhdInfo.content + 4) ); + moovMgr->NoteChange(); + } + if ( modifyFound ) { + if ( modifySeconds != oldModify ) PutUns64BE ( modifySeconds, ((XMP_Uns8*)mvhdInfo.content + 12) ); + moovMgr->NoteChange(); + } + + } else if ( ((createSeconds >> 32) == 0) && ((modifySeconds >> 32) == 0) ) { + + // Modify the v0 box in-place. + + if ( mvhdInfo.contentSize < sizeof ( MOOV_Manager::Content_mvhd_0 ) ) return; + + XMP_Uns32 oldCreate = GetUns32BE ( mvhdInfo.content + 4 ); + XMP_Uns32 oldModify = GetUns32BE ( mvhdInfo.content + 8 ); + + if ( createFound ) { + if ( (XMP_Uns32)createSeconds != oldCreate ) PutUns32BE ( (XMP_Uns32)createSeconds, ((XMP_Uns8*)mvhdInfo.content + 4) ); + moovMgr->NoteChange(); + } + if ( modifyFound ) { + if ( (XMP_Uns32)modifySeconds != oldModify ) PutUns32BE ( (XMP_Uns32)modifySeconds, ((XMP_Uns8*)mvhdInfo.content + 8) ); + moovMgr->NoteChange(); + } + + } else { + + // Replace the v0 box with a v1 box. + + XMP_Assert ( createFound | modifyFound ); // One of them has high bits set. + if ( mvhdInfo.contentSize != sizeof ( MOOV_Manager::Content_mvhd_0 ) ) return; + + MOOV_Manager::Content_mvhd_0 * mvhdV0 = (MOOV_Manager::Content_mvhd_0*) mvhdInfo.content; + MOOV_Manager::Content_mvhd_1 mvhdV1; + + // Copy the unchanged fields directly. + + mvhdV1.timescale = mvhdV0->timescale; + mvhdV1.rate = mvhdV0->rate; + mvhdV1.volume = mvhdV0->volume; + mvhdV1.pad_1 = mvhdV0->pad_1; + mvhdV1.pad_2 = mvhdV0->pad_2; + mvhdV1.pad_3 = mvhdV0->pad_3; + for ( int i = 0; i < 9; ++i ) mvhdV1.matrix[i] = mvhdV0->matrix[i]; + for ( int i = 0; i < 6; ++i ) mvhdV1.preDef[i] = mvhdV0->preDef[i]; + mvhdV1.nextTrackID = mvhdV0->nextTrackID; + + // Set the fields that have changes. + + mvhdV1.vFlags = (1 << 24) | (mvhdV0->vFlags & 0xFFFFFF); + mvhdV1.duration = MakeUns64BE ( (XMP_Uns64) GetUns32BE ( &mvhdV0->duration ) ); + + XMP_Uns64 temp64; + + temp64 = (XMP_Uns64) GetUns32BE ( &mvhdV0->creationTime ); + if ( createFound ) temp64 = createSeconds; + mvhdV1.creationTime = MakeUns64BE ( temp64 ); + + temp64 = (XMP_Uns64) GetUns32BE ( &mvhdV0->modificationTime ); + if ( modifyFound ) temp64 = modifySeconds; + mvhdV1.modificationTime = MakeUns64BE ( temp64 ); + + moovMgr->SetBox ( mvhdRef, &mvhdV1, sizeof ( MOOV_Manager::Content_mvhd_1 ) ); + + } + +} // ExportMVHDItems + +// ================================================================================================= +// ImportISOCopyrights +// =================== +// +// The cached 'moov'/'udta'/'cprt' boxes are full boxes. The "real" content is a UInt16 packed 3 +// character language code and a UTF-8 or UTF-16 string. + +static bool ImportISOCopyrights ( const std::vector & cprtBoxes, SXMPMeta * xmp ) +{ + bool haveImports = false; + + std::string tempStr; + char lang3 [4]; // The unpacked ISO-639-2/T language code with final null. + lang3[3] = 0; + + for ( size_t i = 0, limit = cprtBoxes.size(); i < limit; ++i ) { + + const MOOV_Manager::BoxInfo & currBox = cprtBoxes[i]; + if ( currBox.contentSize < 4+2+1 ) continue; // Want enough for a non-empty value. + if ( *currBox.content != 0 ) continue; // Only proceed for version 0, ignore the flags. + + XMP_Uns16 packedLang = GetUns16BE ( currBox.content + 4 ); + lang3[0] = (packedLang >> 10) + 0x60; + lang3[1] = ((packedLang >> 5) & 0x1F) + 0x60; + lang3[2] = (packedLang & 0x1F) + 0x60; + XMP_StringPtr xmpLang = Lookup2LetterLang ( lang3 ); + if ( *xmpLang == 0 ) continue; + + XMP_StringPtr textPtr = (XMP_StringPtr) (currBox.content + 6); + XMP_StringLen textLen = currBox.contentSize - 6; + + if ( (textLen >= 2) && (GetUns16BE(textPtr) == 0xFEFF) ) { + FromUTF16 ( (UTF16Unit*)textPtr, textLen/2, &tempStr, true /* big endian */ ); + textPtr = tempStr.c_str(); + } + + xmp->SetLocalizedText ( kXMP_NS_DC, "rights", xmpLang, xmpLang, textPtr ); + haveImports = true; + + } + + return haveImports; + +} // ImportISOCopyrights + +// ================================================================================================= +// ExportISOCopyrights +// =================== + +static void ExportISOCopyrights ( const SXMPMeta & xmp, MOOV_Manager * moovMgr ) +{ + bool haveMappings = false; // True if any ISO-XMP language mappings are found. + + // Go through the ISO 'cprt' items and look for a corresponding XMP item. Ignore the ISO item if + // there is no language mapping to XMP. Update the ISO item if the mappable XMP exists, delete + // the ISO item if the mappable XMP does not exist. Since the import side would have made sure + // the mappable XMP items existed, if they don't now they must have been deleted. + + MOOV_Manager::BoxInfo udtaInfo; + MOOV_Manager::BoxRef udtaRef = moovMgr->GetBox ( "moov/udta", &udtaInfo ); + if ( udtaRef == 0 ) return; + + std::string xmpPath, xmpValue, xmpLang, tempStr; + char lang3 [4]; // An unpacked ISO-639-2/T language code. + lang3[3] = 0; + + for ( XMP_Uns32 ordinal = udtaInfo.childCount; ordinal > 0; --ordinal ) { // ! Go backwards because of deletions. + + MOOV_Manager::BoxInfo cprtInfo; + MOOV_Manager::BoxRef cprtRef = moovMgr->GetNthChild ( udtaRef, ordinal-1, &cprtInfo ); + if ( cprtRef == 0 ) break; // Sanity check, should not happen. + if ( (cprtInfo.boxType != ISOMedia::k_cprt) || (cprtInfo.contentSize < 6) ) continue; + if ( *cprtInfo.content != 0 ) continue; // Only accept version 0, ignore the flags. + + XMP_Uns16 packedLang = GetUns16BE ( cprtInfo.content + 4 ); + lang3[0] = (packedLang >> 10) + 0x60; + lang3[1] = ((packedLang >> 5) & 0x1F) + 0x60; + lang3[2] = (packedLang & 0x1F) + 0x60; + + XMP_StringPtr lang2 = Lookup2LetterLang ( lang3 ); + if ( *lang2 == 0 ) continue; // No language mapping to XMP. + haveMappings = true; + + bool xmpFound = xmp.GetLocalizedText ( kXMP_NS_DC, "rights", lang2, lang2, &xmpLang, &xmpValue, 0 ); + if ( xmpFound ) { + if ( (xmpLang.size() < 2) || + ( (xmpLang.size() == 2) && (xmpLang != lang2) ) || + ( (xmpLang.size() > 2) && ( (xmpLang[2] != '-') || (! XMP_LitNMatch ( xmpLang.c_str(), lang2, 2)) ) ) ) { + xmpFound = false; // The language does not match, the corresponding XMP does not exist. + } + } + + if ( ! xmpFound ) { + + // No XMP, delete the ISO item. + moovMgr->DeleteNthChild ( udtaRef, ordinal-1 ); + + } else { + + // Update the ISO item if necessary. + XMP_StringPtr isoStr = (XMP_StringPtr)cprtInfo.content + 6; + size_t rawLen = cprtInfo.contentSize - 6; + if ( (rawLen >= 8) && (GetUns16BE(isoStr) == 0xFEFF) ) { + FromUTF16 ( (UTF16Unit*)(isoStr+2), (rawLen-2)/2, &tempStr, true /* big endian */ ); + isoStr = tempStr.c_str(); + } + if ( xmpValue != isoStr ) { + std::string newContent = "vfffll"; + newContent += xmpValue; + memcpy ( (char*)newContent.c_str(), cprtInfo.content, 6 ); // Keep old version, flags, and language. + moovMgr->SetBox ( cprtRef, newContent.c_str(), (XMP_Uns32)(newContent.size() + 1) ); + } + + } + + } + + // Go through the XMP items and look for a corresponding ISO item. Skip if found (did it above), + // otherwise add a new ISO item. + + bool haveXDefault = false; + XMP_Index xmpCount = xmp.CountArrayItems ( kXMP_NS_DC, "rights" ); + + for ( XMP_Index xmpIndex = 1; xmpIndex <= xmpCount; ++xmpIndex ) { // ! The first XMP array index is 1. + + SXMPUtils::ComposeArrayItemPath ( kXMP_NS_DC, "rights", xmpIndex, &xmpPath ); + xmp.GetArrayItem ( kXMP_NS_DC, "rights", xmpIndex, &xmpValue, 0 ); + bool hasLang = xmp.GetQualifier ( kXMP_NS_DC, xmpPath.c_str(), kXMP_NS_XML, "lang", &xmpLang, 0 ); + if ( ! hasLang ) continue; // Sanity check. + if ( xmpLang == "x-default" ) { + haveXDefault = true; // See later special case. + continue; + } + + XMP_StringPtr isoLang = ""; + size_t rootLen = xmpLang.find ( '-' ); + if ( rootLen == std::string::npos ) rootLen = xmpLang.size(); + if ( rootLen == 2 ) { + if( xmpLang.size() > 2 ) xmpLang[2] = 0; + isoLang = Lookup3LetterLang ( xmpLang.c_str() ); + if ( *isoLang == 0 ) continue; + } else if ( rootLen == 3 ) { + if( xmpLang.size() > 3 ) xmpLang[3] = 0; + isoLang = xmpLang.c_str(); + } else { + continue; + } + haveMappings = true; + + bool isoFound = false; + XMP_Uns16 packedLang = ((isoLang[0] - 0x60) << 10) | ((isoLang[1] - 0x60) << 5) | (isoLang[2] - 0x60); + + for ( XMP_Uns32 isoIndex = 0; (isoIndex < udtaInfo.childCount) && (! isoFound); ++isoIndex ) { + + MOOV_Manager::BoxInfo cprtInfo; + MOOV_Manager::BoxRef cprtRef = moovMgr->GetNthChild ( udtaRef, isoIndex, &cprtInfo ); + if ( cprtRef == 0 ) break; // Sanity check, should not happen. + if ( (cprtInfo.boxType != ISOMedia::k_cprt) || (cprtInfo.contentSize < 6) ) continue; + if ( *cprtInfo.content != 0 ) continue; // Only accept version 0, ignore the flags. + if ( packedLang != GetUns16BE ( cprtInfo.content + 4 ) ) continue; // Look for matching language. + + isoFound = true; // Found the language entry, whether or not we update it. + + } + + if ( ! isoFound ) { + + std::string newContent = "vfffll"; + newContent += xmpValue; + *((XMP_Uns32*)newContent.c_str()) = 0; // Set the version and flags to zero. + PutUns16BE ( packedLang, (char*)newContent.c_str() + 4 ); + moovMgr->AddChildBox ( udtaRef, ISOMedia::k_cprt, newContent.c_str(), (XMP_Uns32)(newContent.size() + 1) ); + + } + + } + + // If there were no mappings in the loops, export the XMP "x-default" value to the first ISO item. + + if ( ! haveMappings ) { + + MOOV_Manager::BoxInfo cprtInfo; + MOOV_Manager::BoxRef cprtRef = moovMgr->GetTypeChild ( udtaRef, ISOMedia::k_cprt, &cprtInfo ); + + if ( (cprtRef != 0) && (cprtInfo.contentSize >= 6) && (*cprtInfo.content == 0) ) { + + bool xmpFound = xmp.GetLocalizedText ( kXMP_NS_DC, "rights", "", "x-default", &xmpLang, &xmpValue, 0 ); + + if ( xmpFound && (xmpLang == "x-default") ) { + + // Update the ISO item if necessary. + XMP_StringPtr isoStr = (XMP_StringPtr)cprtInfo.content + 6; + size_t rawLen = cprtInfo.contentSize - 6; + if ( (rawLen >= 8) && (GetUns16BE(isoStr) == 0xFEFF) ) { + FromUTF16 ( (UTF16Unit*)(isoStr+2), (rawLen-2)/2, &tempStr, true /* big endian */ ); + isoStr = tempStr.c_str(); + } + if ( xmpValue != isoStr ) { + std::string newContent = "vfffll"; + newContent += xmpValue; + memcpy ( (char*)newContent.c_str(), cprtInfo.content, 6 ); // Keep old version, flags, and language. + moovMgr->SetBox ( cprtRef, newContent.c_str(), (XMP_Uns32)(newContent.size() + 1) ); + } + + } + + } + + } + +} // ExportISOCopyrights + +// ================================================================================================= +// ExportQuickTimeItems +// ==================== + +static void ExportQuickTimeItems ( const SXMPMeta & xmp, TradQT_Manager * qtMgr, MOOV_Manager * moovMgr ) +{ + + // The QuickTime 'udta' timecode items are done here for simplicity. + + #define createWithZeroLang true + + qtMgr->ExportSimpleXMP ( kQTilst_Reel, xmp, kXMP_NS_DM, "tapeName" ); + qtMgr->ExportSimpleXMP ( kQTilst_Timecode, xmp, kXMP_NS_DM, "startTimecode/xmpDM:timeValue", createWithZeroLang ); + qtMgr->ExportSimpleXMP ( kQTilst_TimeScale, xmp, kXMP_NS_DM, "startTimeScale", createWithZeroLang ); + qtMgr->ExportSimpleXMP ( kQTilst_TimeSize, xmp, kXMP_NS_DM, "startTimeSampleSize", createWithZeroLang ); + + qtMgr->UpdateChangedBoxes ( moovMgr ); + +} // ExportQuickTimeItems + +// ================================================================================================= +// SelectTimeFormat +// ================ + +static const char * SelectTimeFormat ( const MPEG4_MetaHandler::TimecodeTrackInfo & tmcdInfo ) +{ + const char * timeFormat = 0; + + float fltFPS = (float)tmcdInfo.timeScale / (float)tmcdInfo.frameDuration; + int intFPS = (int) (fltFPS + 0.5); + + switch ( intFPS ) { + + case 30: + if ( fltFPS >= 29.985 ) { + timeFormat = "30Timecode"; + } else if ( tmcdInfo.isDropFrame ) { + timeFormat = "2997DropTimecode"; + } else { + timeFormat = "2997NonDropTimecode"; + } + break; + + case 24: + if ( fltFPS >= 23.988 ) { + timeFormat = "24Timecode"; + } else { + timeFormat = "23976Timecode"; + } + break; + + case 25: + timeFormat = "25Timecode"; + break; + + case 50: + timeFormat = "50Timecode"; + break; + + case 60: + if ( fltFPS >= 59.97 ) { + timeFormat = "60Timecode"; + } else if ( tmcdInfo.isDropFrame ) { + timeFormat = "5994DropTimecode"; + } else { + timeFormat = "5994NonDropTimecode"; + } + break; + + } + + return timeFormat; + +} // SelectTimeFormat + +// ================================================================================================= +// SelectTimeFormat +// ================ + +static const char * SelectTimeFormat ( const SXMPMeta & xmp ) +{ + bool ok; + MPEG4_MetaHandler::TimecodeTrackInfo tmcdInfo; + + XMP_Int64 timeScale; + ok = xmp.GetProperty_Int64 ( kXMP_NS_DM, "startTimeScale", &timeScale, 0 ); + if ( ! ok ) return 0; + tmcdInfo.timeScale = (XMP_Uns32)timeScale; + + XMP_Int64 frameDuration; + ok = xmp.GetProperty_Int64 ( kXMP_NS_DM, "startTimeSampleSize", &frameDuration, 0 ); + if ( ! ok ) return 0; + tmcdInfo.frameDuration = (XMP_Uns32)frameDuration; + + std::string timecode; + ok = xmp.GetProperty ( kXMP_NS_DM, "startTimecode/xmpDM:timeValue", &timecode, 0 ); + if ( ! ok ) return 0; + if ( (timecode.size() == 11) && (timecode[8] == ';') ) tmcdInfo.isDropFrame = true; + + return SelectTimeFormat ( tmcdInfo ); + +} // SelectTimeFormat + +// ================================================================================================= +// ComposeTimecode +// =============== + +static const char * kDecDigits = "0123456789"; +#define TwoDigits(val,str) (str)[0] = kDecDigits[(val)/10]; (str)[1] = kDecDigits[(val)%10] + +static bool ComposeTimecode ( const MPEG4_MetaHandler::TimecodeTrackInfo & tmcdInfo, std::string * strValue ) +{ + float fltFPS = (float)tmcdInfo.timeScale / (float)tmcdInfo.frameDuration; + int intFPS = (int) (fltFPS + 0.5); + if ( (intFPS != 30) && (intFPS != 24) && (intFPS != 25) && (intFPS != 50) && (intFPS != 60) ) return false; + + XMP_Uns32 framesPerDay = intFPS * 24*60*60; + XMP_Uns32 dropLimit = 2; // Used in the drop-frame correction. + + if ( tmcdInfo.isDropFrame ) { + if ( intFPS == 30 ) { + framesPerDay = 2589408; // = 29.97 * 24*60*60 + } else if ( intFPS == 60 ) { + framesPerDay = 5178816; // = 59.94 * 24*60*60 + dropLimit = 4; + } else { + strValue->erase(); + return false; // Dropframe can only apply to 29.97 and 59.94. + } + } + + XMP_Uns32 framesPerHour = framesPerDay / 24; + XMP_Uns32 framesPerTenMinutes = framesPerHour / 6; + XMP_Uns32 framesPerMinute = framesPerTenMinutes / 10; + + XMP_Uns32 frameCount = tmcdInfo.timecodeSample; + while (frameCount >= framesPerDay ) frameCount -= framesPerDay; // Normalize to be within 24 hours. + + XMP_Uns32 hours, minHigh, minLow, seconds; + + hours = frameCount / framesPerHour; + frameCount -= (hours * framesPerHour); + + minHigh = frameCount / framesPerTenMinutes; + frameCount -= (minHigh * framesPerTenMinutes); + + minLow = frameCount / framesPerMinute; + frameCount -= (minLow * framesPerMinute); + + // Do some drop-frame corrections at this point: If this is drop-frame and the units of minutes + // is non-zero, and the seconds are zero, and the frames are zero or one, the time is illegal. + // Perform correction by subtracting 1 from the units of minutes and adding 1798 to the frames.Ê + // For example, 1:00:00 becomes 59:28, and 1:00:01 becomes 59:29. A special case can occur for + // when the frameCount just before the minHigh calculation is less than framesPerTenMinutes but + // more than 10*framesPerMinute. This happens because of roundoff, and will result in a minHigh + // of 0 and a minLow of 10.ÊThe drop frame correction mustÊalso be performed for this case. + + if ( tmcdInfo.isDropFrame ) { + if ( (minLow == 10) || ((minLow != 0) && (frameCount < dropLimit)) ) { + minLow -= 1; + frameCount += framesPerMinute; + } + } + + seconds = frameCount / intFPS; + frameCount -= (seconds * intFPS); + + if ( tmcdInfo.isDropFrame ) { + *strValue = "hh;mm;ss;ff"; + } else { + *strValue = "hh:mm:ss:ff"; + } + + char * str = (char*)strValue->c_str(); + TwoDigits ( hours, str ); + str[3] = kDecDigits[minHigh]; str[4] = kDecDigits[minLow]; + TwoDigits ( seconds, str+6 ); + TwoDigits ( frameCount, str+9 ); + + return true; + +} // ComposeTimecode + +// ================================================================================================= +// DecomposeTimecode +// ================= + +static bool DecomposeTimecode ( const char * strValue, MPEG4_MetaHandler::TimecodeTrackInfo * tmcdInfo ) +{ + float fltFPS = (float)tmcdInfo->timeScale / (float)tmcdInfo->frameDuration; + int intFPS = (int) (fltFPS + 0.5); + if ( (intFPS != 30) && (intFPS != 24) && (intFPS != 25) && (intFPS != 50) && (intFPS != 60) ) return false; + + XMP_Uns32 framesPerDay = intFPS * 24*60*60; + + int items, hours, minutes, seconds, frames; + + if ( ! tmcdInfo->isDropFrame ) { + items = sscanf ( strValue, "%d:%d:%d:%d", &hours, &minutes, &seconds, &frames ); + } else { + items = sscanf ( strValue, "%d;%d;%d;%d", &hours, &minutes, &seconds, &frames ); + if ( intFPS == 30 ) { + framesPerDay = 2589408; // = 29.97 * 24*60*60 + } else if ( intFPS == 60 ) { + framesPerDay = 5178816; // = 59.94 * 24*60*60 + } else { + return false; // Dropframe can only apply to 29.97 and 59.94. + } + } + + if ( items != 4 ) return false; + int minHigh = minutes / 10; + int minLow = minutes % 10; + + XMP_Uns32 framesPerHour = framesPerDay / 24; + XMP_Uns32 framesPerTenMinutes = framesPerHour / 6; + XMP_Uns32 framesPerMinute = framesPerTenMinutes / 10; + + tmcdInfo->timecodeSample = (hours * framesPerHour) + (minHigh * framesPerTenMinutes) + + (minLow * framesPerMinute) + (seconds * intFPS) + frames; + + return true; + +} // DecomposeTimecode + +// ================================================================================================= +// FindTimecode_trak +// ================= +// +// Look for a well-formed timecode track, return the trak box ref. + +static MOOV_Manager::BoxRef FindTimecode_trak ( const MOOV_Manager & moovMgr ) +{ + + // Find a 'trak' box with a handler type of 'tmcd'. + + MOOV_Manager::BoxInfo moovInfo; + MOOV_Manager::BoxRef moovRef = moovMgr.GetBox ( "moov", &moovInfo ); + XMP_Assert ( moovRef != 0 ); + + MOOV_Manager::BoxInfo trakInfo; + MOOV_Manager::BoxRef trakRef; + + size_t i = 0; + for ( ; i < moovInfo.childCount; ++i ) { + + trakRef = moovMgr.GetNthChild ( moovRef, i, &trakInfo ); + if ( trakRef == 0 ) return 0; // Sanity check, should not happen. + if ( trakInfo.boxType != ISOMedia::k_trak ) continue; + + MOOV_Manager::BoxRef innerRef; + MOOV_Manager::BoxInfo innerInfo; + + innerRef = moovMgr.GetTypeChild ( trakRef, ISOMedia::k_mdia, &innerInfo ); + if ( innerRef == 0 ) continue; + + innerRef = moovMgr.GetTypeChild ( innerRef, ISOMedia::k_hdlr, &innerInfo ); + if ( (innerRef == 0) || (innerInfo.contentSize < sizeof ( MOOV_Manager::Content_hdlr )) ) continue; + + const MOOV_Manager::Content_hdlr * hdlr = (MOOV_Manager::Content_hdlr*) innerInfo.content; + if ( hdlr->versionFlags != 0 ) continue; + if ( GetUns32BE ( &hdlr->handlerType ) == ISOMedia::k_tmcd ) break; + + } + + if ( i == moovInfo.childCount ) return 0; + return trakRef; + +} // FindTimecode_trak + +// ================================================================================================= +// FindTimecode_dref +// ================= +// +// Look for the mdia/minf/dinf/dref box within a well-formed timecode track, return the dref box ref. + +static MOOV_Manager::BoxRef FindTimecode_dref ( const MOOV_Manager & moovMgr ) +{ + + MOOV_Manager::BoxRef trakRef = FindTimecode_trak ( moovMgr ); + if ( trakRef == 0 ) return 0; + + MOOV_Manager::BoxInfo tempInfo; + MOOV_Manager::BoxRef tempRef, drefRef; + + tempRef = moovMgr.GetTypeChild ( trakRef, ISOMedia::k_mdia, &tempInfo ); + if ( tempRef == 0 ) return 0; + + tempRef = moovMgr.GetTypeChild ( tempRef, ISOMedia::k_minf, &tempInfo ); + if ( tempRef == 0 ) return 0; + + tempRef = moovMgr.GetTypeChild ( tempRef, ISOMedia::k_dinf, &tempInfo ); + if ( tempRef == 0 ) return 0; + + drefRef = moovMgr.GetTypeChild ( tempRef, ISOMedia::k_dref, &tempInfo ); + + return drefRef; + +} // FindTimecode_dref + +// ================================================================================================= +// FindTimecode_stbl +// ================= +// +// Look for the mdia/minf/stbl box within a well-formed timecode track, return the stbl box ref. + +static MOOV_Manager::BoxRef FindTimecode_stbl ( const MOOV_Manager & moovMgr ) +{ + + MOOV_Manager::BoxRef trakRef = FindTimecode_trak ( moovMgr ); + if ( trakRef == 0 ) return 0; + + MOOV_Manager::BoxInfo tempInfo; + MOOV_Manager::BoxRef tempRef, stblRef; + + tempRef = moovMgr.GetTypeChild ( trakRef, ISOMedia::k_mdia, &tempInfo ); + if ( tempRef == 0 ) return 0; + + tempRef = moovMgr.GetTypeChild ( tempRef, ISOMedia::k_minf, &tempInfo ); + if ( tempRef == 0 ) return 0; + + stblRef = moovMgr.GetTypeChild ( tempRef, ISOMedia::k_stbl, &tempInfo ); + return stblRef; + +} // FindTimecode_stbl + +// ================================================================================================= +// FindTimecode_elst +// ================= +// +// Look for the edts/elst box within a well-formed timecode track, return the elst box ref. + +static MOOV_Manager::BoxRef FindTimecode_elst ( const MOOV_Manager & moovMgr ) +{ + + MOOV_Manager::BoxRef trakRef = FindTimecode_trak ( moovMgr ); + if ( trakRef == 0 ) return 0; + + MOOV_Manager::BoxInfo tempInfo; + MOOV_Manager::BoxRef tempRef, elstRef; + + tempRef = moovMgr.GetTypeChild ( trakRef, ISOMedia::k_edts, &tempInfo ); + if ( tempRef == 0 ) return 0; + + elstRef = moovMgr.GetTypeChild ( tempRef, ISOMedia::k_elst, &tempInfo ); + return elstRef; + +} // FindTimecode_elst + +// ================================================================================================= +// ImportTimecodeItems +// =================== + +static bool ImportTimecodeItems ( const MPEG4_MetaHandler::TimecodeTrackInfo & tmcdInfo, + const TradQT_Manager & qtInfo, SXMPMeta * xmp ) +{ + std::string xmpValue; + bool haveItem; + bool haveImports = false; + + // The QT user data item '©REL' goes into xmpDM:tapeName, and the 'name' box at the end of the + // timecode sample description goes into xmpDM:altTapeName. + haveImports |= qtInfo.ImportSimpleXMP ( kQTilst_Reel, xmp, kXMP_NS_DM, "tapeName" ); + if ( ! tmcdInfo.macName.empty() ) { + haveItem = ConvertFromMacLang ( tmcdInfo.macName, tmcdInfo.macLang, &xmpValue ); + if ( haveItem ) { + xmp->SetProperty ( kXMP_NS_DM, "altTapeName", xmpValue.c_str() ); + haveImports = true; + } + } + + // The QT user data item '©TSC' goes into xmpDM:startTimeScale. If that isn't present, then + // the timecode sample description's timeScale is used. + haveItem = qtInfo.ImportSimpleXMP ( kQTilst_TimeScale, xmp, kXMP_NS_DM, "startTimeScale" ); + if ( tmcdInfo.stsdBoxFound & (! haveItem) ) { + xmp->SetProperty_Int64 ( kXMP_NS_DM, "startTimeScale", tmcdInfo.timeScale ); + haveItem = true; + } + haveImports |= haveItem; + + // The QT user data item '©TSZ' goes into xmpDM:startTimeSampleSize. If that isn't present, then + // the timecode sample description's frameDuration is used. + haveItem = qtInfo.ImportSimpleXMP ( kQTilst_TimeSize, xmp, kXMP_NS_DM, "startTimeSampleSize" ); + if ( tmcdInfo.stsdBoxFound & (! haveItem) ) { + xmp->SetProperty_Int64 ( kXMP_NS_DM, "startTimeSampleSize", tmcdInfo.frameDuration ); + haveItem = true; + } + haveImports |= haveItem; + + const char * timeFormat; + + // The Timecode struct type is used for xmpDM:startTimecode and xmpDM:altTimecode. For both, only + // the xmpDM:timeValue and xmpDM:timeFormat fields are set. + + // The QT user data item '©TIM' goes into xmpDM:startTimecode/xmpDM:timeValue. This is an already + // formatted timecode string. The XMP values of xmpDM:startTimeScale, xmpDM:startTimeSampleSize, + // and xmpDM:startTimecode/xmpDM:timeValue are used to select the timeFormat value. + haveImports |= qtInfo.ImportSimpleXMP ( kQTilst_Timecode, xmp, kXMP_NS_DM, "startTimecode/xmpDM:timeValue" ); + timeFormat = SelectTimeFormat ( *xmp ); + if ( timeFormat != 0 ) { + xmp->SetProperty ( kXMP_NS_DM, "startTimecode/xmpDM:timeFormat", timeFormat ); + haveImports = true; + } + + if ( tmcdInfo.stsdBoxFound ) { + + haveItem = ComposeTimecode ( tmcdInfo, &xmpValue ); + if ( haveItem ) { + xmp->SetProperty ( kXMP_NS_DM, "altTimecode/xmpDM:timeValue", xmpValue.c_str() ); + haveImports = true; + } + + timeFormat = SelectTimeFormat ( tmcdInfo ); + if ( timeFormat != 0 ) { + xmp->SetProperty ( kXMP_NS_DM, "altTimecode/xmpDM:timeFormat", timeFormat ); + haveImports = true; + } + + } + + return haveImports; + +} // ImportTimecodeItems + +// ================================================================================================= +// ExportTimecodeItems +// =================== + +static void ExportTimecodeItems ( const SXMPMeta & xmp, MPEG4_MetaHandler::TimecodeTrackInfo * tmcdInfo, + TradQT_Manager * qtMgr, MOOV_Manager * moovMgr ) +{ + // Export the items that go into the timecode track: + // - the timescale and frame duration in the first 'stsd' table entry + // - the 'name' box appended to the first 'stsd' table entry + // - the first timecode sample + // ! The QuickTime 'udta' timecode items are handled in ExportQuickTimeItems. + + if ( ! tmcdInfo->stsdBoxFound ) return; // Don't make changes unless there is a well-formed timecode track. + + MOOV_Manager::BoxRef stblRef = FindTimecode_stbl ( *moovMgr ); + if ( stblRef == 0 ) return; + + MOOV_Manager::BoxInfo stsdInfo; + MOOV_Manager::BoxRef stsdRef; + + stsdRef = moovMgr->GetTypeChild ( stblRef, ISOMedia::k_stsd, &stsdInfo ); + if ( stsdRef == 0 ) return; + if ( stsdInfo.contentSize < (8 + sizeof ( MOOV_Manager::Content_stsd_entry )) ) return; + if ( GetUns32BE ( stsdInfo.content + 4 ) == 0 ) return; // Make sure the entry count is non-zero. + + MOOV_Manager::Content_stsd_entry * stsdRawEntry = (MOOV_Manager::Content_stsd_entry*) (stsdInfo.content + 8); + + XMP_Uns32 stsdEntrySize = GetUns32BE ( &stsdRawEntry->entrySize ); + if ( stsdEntrySize > (stsdInfo.contentSize - 4) ) stsdEntrySize = stsdInfo.contentSize - 4; + if ( stsdEntrySize < sizeof ( MOOV_Manager::Content_stsd_entry ) ) return; + + bool ok, haveScale = false, haveDuration = false; + std::string xmpValue; + XMP_Int64 int64; // Used to allow UInt32 values, GetProperty_Int is SInt32. + + // The tmcdInfo timeScale field is set from xmpDM:startTimeScale. + ok = xmp.GetProperty_Int64 ( kXMP_NS_DM, "startTimeScale", &int64, 0 ); + if ( ok && (int64 <= 0xFFFFFFFF) ) { + haveScale = true; + if ( tmcdInfo->timeScale != 0 ) { // Entry must not be created if not existing before + tmcdInfo->timeScale = (XMP_Uns32)int64; + PutUns32BE ( tmcdInfo->timeScale, (void*)&stsdRawEntry->timeScale ); + moovMgr->NoteChange(); + } + } + + // The tmcdInfo frameDuration field is set from xmpDM:startTimeSampleSize. + ok = xmp.GetProperty_Int64 ( kXMP_NS_DM, "startTimeSampleSize", &int64, 0 ); + if ( ok && (int64 <= 0xFFFFFFFF) ) { + haveDuration = true; + if ( tmcdInfo->frameDuration != 0 ) { // Entry must not be created if not existing before + tmcdInfo->frameDuration = (XMP_Uns32)int64; + PutUns32BE ( tmcdInfo->frameDuration, (void*)&stsdRawEntry->frameDuration ); + moovMgr->NoteChange(); + } + } + + // The tmcdInfo frameCount field is a simple ratio of the timeScale and frameDuration. + if ( (haveScale & haveDuration) && (tmcdInfo->frameDuration != 0) ) { + float floatScale = (float) tmcdInfo->timeScale; + float floatDuration = (float) tmcdInfo->frameDuration; + XMP_Uns8 newCount = (XMP_Uns8) ( (floatScale / floatDuration) + 0.5 ); + if ( newCount != stsdRawEntry->frameCount ) { + stsdRawEntry->frameCount = newCount; + moovMgr->NoteChange(); + } + } + + // The tmcdInfo isDropFrame flag is set from xmpDM:altTimecode/xmpDM:timeValue. The timeScale + // and frameDuration must be updated first, they are used by DecomposeTimecode. Compute the new + // UInt32 timecode sample, but it gets written to the file later by UpdateFile. + + ok = xmp.GetProperty ( kXMP_NS_DM, "altTimecode/xmpDM:timeValue", &xmpValue, 0 ); + if ( ok && (xmpValue.size() == 11) ) { + + bool oldDropFrame = tmcdInfo->isDropFrame; + tmcdInfo->isDropFrame = false; + if ( xmpValue[8] == ';' ) tmcdInfo->isDropFrame = true; + if ( oldDropFrame != tmcdInfo->isDropFrame ) { + XMP_Uns32 flags = GetUns32BE ( &stsdRawEntry->flags ); + flags = (flags & 0xFFFFFFFE) | (XMP_Uns32)tmcdInfo->isDropFrame; + PutUns32BE ( flags, (void*)&stsdRawEntry->flags ); + moovMgr->NoteChange(); + } + + XMP_Uns32 oldSample = tmcdInfo->timecodeSample; + ok = DecomposeTimecode ( xmpValue.c_str(), tmcdInfo ); + if ( ok && (oldSample != tmcdInfo->timecodeSample) ) moovMgr->NoteChange(); + + } + + // The 'name' box attached to the first 'stsd' table entry is set from xmpDM:altTapeName. + + bool replaceNameBox = false; + + ok = xmp.GetProperty ( kXMP_NS_DM, "altTapeName", &xmpValue, 0 ); + if ( (! ok) || xmpValue.empty() ) { + if ( tmcdInfo->nameOffset != 0 ) replaceNameBox = true; // No XMP, get rid of existing name. + } else { + std::string macValue; + ok = ConvertToMacLang ( xmpValue, tmcdInfo->macLang, &macValue ); + if ( ok && (macValue != tmcdInfo->macName) ) { + tmcdInfo->macName = macValue; + replaceNameBox = true; // Write changed name. + } + } + + if ( replaceNameBox ) { + + // To replace the 'name' box we have to create an entire new 'stsd' box, and attach the + // new name to the first 'stsd' table entry. The 'name' box content is a UInt16 text length, + // UInt16 language code, and Mac encoded text with no nul termination. + + if ( tmcdInfo->macName.size() > 0xFFFF ) tmcdInfo->macName.erase ( 0xFFFF ); + + ISOMedia::BoxInfo oldNameInfo; + XMP_Assert ( (oldNameInfo.headerSize == 0) && (oldNameInfo.contentSize == 0) ); + if ( tmcdInfo->nameOffset != 0 ) { + const XMP_Uns8 * oldNamePtr = stsdInfo.content + tmcdInfo->nameOffset; + const XMP_Uns8 * oldNameLimit = stsdInfo.content + stsdInfo.contentSize; + (void) ISOMedia::GetBoxInfo ( oldNamePtr, oldNameLimit, &oldNameInfo ); + } + + XMP_Uns32 oldNameBoxSize = (XMP_Uns32)oldNameInfo.headerSize + (XMP_Uns32)oldNameInfo.contentSize; + XMP_Uns32 newNameBoxSize = 0; + if ( ! tmcdInfo->macName.empty() ) newNameBoxSize = 4+4 + 2+2 + (XMP_Uns32)tmcdInfo->macName.size(); + + XMP_Uns32 stsdNewContentSize = stsdInfo.contentSize - oldNameBoxSize + newNameBoxSize; + RawDataBlock stsdNewContent; + stsdNewContent.assign ( stsdNewContentSize, 0 ); // Get the space allocated, direct fill below. + + XMP_Uns32 stsdPrefixSize = tmcdInfo->nameOffset; + if ( tmcdInfo->nameOffset == 0 ) stsdPrefixSize = 4+4 + sizeof ( MOOV_Manager::Content_stsd_entry ); + + XMP_Uns32 oldSuffixOffset = stsdPrefixSize + oldNameBoxSize; + XMP_Uns32 newSuffixOffset = stsdPrefixSize + newNameBoxSize; + XMP_Uns32 stsdSuffixSize = stsdInfo.contentSize - oldSuffixOffset; + + memcpy ( &stsdNewContent[0], stsdInfo.content, stsdPrefixSize ); + if ( stsdSuffixSize != 0 ) memcpy ( &stsdNewContent[newSuffixOffset], (stsdInfo.content + oldSuffixOffset), stsdSuffixSize ); + + XMP_Uns32 newEntrySize = stsdEntrySize - oldNameBoxSize + newNameBoxSize; + MOOV_Manager::Content_stsd_entry * stsdNewEntry = (MOOV_Manager::Content_stsd_entry*) (&stsdNewContent[0] + 8); + PutUns32BE ( newEntrySize, &stsdNewEntry->entrySize ); + + if ( newNameBoxSize != 0 ) { + PutUns32BE ( newNameBoxSize, &stsdNewContent[stsdPrefixSize] ); + PutUns32BE ( ISOMedia::k_name, &stsdNewContent[stsdPrefixSize+4] ); + PutUns16BE ( (XMP_Uns16)tmcdInfo->macName.size(), &stsdNewContent[stsdPrefixSize+8] ); + PutUns16BE ( tmcdInfo->macLang, &stsdNewContent[stsdPrefixSize+10] ); + memcpy ( &stsdNewContent[stsdPrefixSize+12], tmcdInfo->macName.c_str(), tmcdInfo->macName.size() ); + } + + moovMgr->SetBox ( stsdRef, &stsdNewContent[0], stsdNewContentSize ); + + } + +} // ExportTimecodeItems + +// ================================================================================================= +// ImportCr8rItems +// =============== + +#if SUNOS_SPARC || SUNOS_X86 +#pragma pack ( 1 ) +#else +#pragma pack ( push, 1 ) +#endif //#if SUNOS_SPARC || SUNOS_X86 + +struct PrmLBoxContent { + XMP_Uns32 magic; + XMP_Uns32 size; + XMP_Uns16 verAPI; + XMP_Uns16 verCode; + XMP_Uns32 exportType; + XMP_Uns16 MacVRefNum; + XMP_Uns32 MacParID; + char filePath[260]; +}; + +enum { kExportTypeMovie = 0, kExportTypeStill = 1, kExportTypeAudio = 2, kExportTypeCustom = 3 }; + +struct Cr8rBoxContent { + XMP_Uns32 magic; + XMP_Uns32 size; + XMP_Uns16 majorVer; + XMP_Uns16 minorVer; + XMP_Uns32 creatorCode; + XMP_Uns32 appleEvent; + char fileExt[16]; + char appOptions[16]; + char appName[32]; +}; + +#if SUNOS_SPARC || SUNOS_X86 +#pragma pack ( ) +#else +#pragma pack ( pop ) +#endif //#if SUNOS_SPARC || SUNOS_X86 + +// ------------------------------------------------------------------------------------------------- + +static bool ImportCr8rItems ( const MOOV_Manager & moovMgr, SXMPMeta * xmp ) +{ + bool haveXMP = false; + std::string fieldPath; + + MOOV_Manager::BoxInfo infoPrmL, infoCr8r; + MOOV_Manager::BoxRef refPrmL = moovMgr.GetBox ( "moov/udta/PrmL", &infoPrmL ); + MOOV_Manager::BoxRef refCr8r = moovMgr.GetBox ( "moov/udta/Cr8r", &infoCr8r ); + + bool havePrmL = ( (refPrmL != 0) && (infoPrmL.contentSize == sizeof ( PrmLBoxContent )) ); + bool haveCr8r = ( (refCr8r != 0) && (infoCr8r.contentSize == sizeof ( Cr8rBoxContent )) ); + + if ( havePrmL ) { + + PrmLBoxContent rawPrmL; + XMP_Assert ( sizeof ( rawPrmL ) == 282 ); + XMP_Assert ( sizeof ( rawPrmL.filePath ) == 260 ); + memcpy ( &rawPrmL, infoPrmL.content, sizeof ( rawPrmL ) ); + if ( rawPrmL.magic != 0xBEEFCAFE ) { + Flip4 ( &rawPrmL.exportType ); // The only numeric field that we care about. + } + + rawPrmL.filePath[259] = 0; // Ensure a terminating nul. + if ( rawPrmL.filePath[0] != 0 ) { + if ( rawPrmL.filePath[0] == '/' ) { + haveXMP = true; + SXMPUtils::ComposeStructFieldPath ( kXMP_NS_CreatorAtom, "macAtom", + kXMP_NS_CreatorAtom, "posixProjectPath", &fieldPath ); + if ( ! xmp->DoesPropertyExist ( kXMP_NS_CreatorAtom, fieldPath.c_str() ) ) { + xmp->SetProperty ( kXMP_NS_CreatorAtom, fieldPath.c_str(), rawPrmL.filePath ); + } + } else if ( XMP_LitNMatch ( rawPrmL.filePath, "\\\\?\\", 4 ) ) { + haveXMP = true; + SXMPUtils::ComposeStructFieldPath ( kXMP_NS_CreatorAtom, "windowsAtom", + kXMP_NS_CreatorAtom, "uncProjectPath", &fieldPath ); + if ( ! xmp->DoesPropertyExist ( kXMP_NS_CreatorAtom, fieldPath.c_str() ) ) { + xmp->SetProperty ( kXMP_NS_CreatorAtom, fieldPath.c_str(), rawPrmL.filePath ); + } + } + } + + const char * exportStr = 0; + switch ( rawPrmL.exportType ) { + case kExportTypeMovie : exportStr = "movie"; break; + case kExportTypeStill : exportStr = "still"; break; + case kExportTypeAudio : exportStr = "audio"; break; + case kExportTypeCustom : exportStr = "custom"; break; + } + if ( exportStr != 0 ) { + haveXMP = true; + SXMPUtils::ComposeStructFieldPath ( kXMP_NS_DM, "projectRef", kXMP_NS_DM, "type", &fieldPath ); + if ( ! xmp->DoesPropertyExist ( kXMP_NS_DM, fieldPath.c_str() ) ) { + xmp->SetProperty ( kXMP_NS_DM, fieldPath.c_str(), exportStr ); + } + } + + } + + if ( haveCr8r ) { + + Cr8rBoxContent rawCr8r; + XMP_Assert ( sizeof ( rawCr8r ) == 84 ); + XMP_Assert ( sizeof ( rawCr8r.fileExt ) == 16 ); + XMP_Assert ( sizeof ( rawCr8r.appOptions ) == 16 ); + XMP_Assert ( sizeof ( rawCr8r.appName ) == 32 ); + memcpy ( &rawCr8r, infoCr8r.content, sizeof ( rawCr8r ) ); + if ( rawCr8r.magic != 0xBEEFCAFE ) { + Flip4 ( &rawCr8r.creatorCode ); // The only numeric fields that we care about. + Flip4 ( &rawCr8r.appleEvent ); + } + + std::string fieldPath; + + SXMPUtils::ComposeStructFieldPath ( kXMP_NS_CreatorAtom, "macAtom", kXMP_NS_CreatorAtom, "applicationCode", &fieldPath ); + if ( (rawCr8r.creatorCode != 0) && (! xmp->DoesPropertyExist ( kXMP_NS_CreatorAtom, fieldPath.c_str() )) ) { + haveXMP = true; + xmp->SetProperty_Int64 ( kXMP_NS_CreatorAtom, fieldPath.c_str(), (XMP_Int64)rawCr8r.creatorCode ); // ! Unsigned trickery. + } + + SXMPUtils::ComposeStructFieldPath ( kXMP_NS_CreatorAtom, "macAtom", kXMP_NS_CreatorAtom, "invocationAppleEvent", &fieldPath ); + if ( (rawCr8r.appleEvent != 0) && (! xmp->DoesPropertyExist ( kXMP_NS_CreatorAtom, fieldPath.c_str() )) ) { + haveXMP = true; + xmp->SetProperty_Int64 ( kXMP_NS_CreatorAtom, fieldPath.c_str(), (XMP_Int64)rawCr8r.appleEvent ); // ! Unsigned trickery. + } + + rawCr8r.fileExt[15] = 0; // Ensure a terminating nul. + SXMPUtils::ComposeStructFieldPath ( kXMP_NS_CreatorAtom, "windowsAtom", kXMP_NS_CreatorAtom, "extension", &fieldPath ); + if ( (rawCr8r.fileExt[0] != 0) && (! xmp->DoesPropertyExist ( kXMP_NS_CreatorAtom, fieldPath.c_str() )) ) { + haveXMP = true; + xmp->SetProperty ( kXMP_NS_CreatorAtom, fieldPath.c_str(), rawCr8r.fileExt ); + } + + rawCr8r.appOptions[15] = 0; // Ensure a terminating nul. + SXMPUtils::ComposeStructFieldPath ( kXMP_NS_CreatorAtom, "windowsAtom", kXMP_NS_CreatorAtom, "invocationFlags", &fieldPath ); + if ( (rawCr8r.appOptions[0] != 0) && (! xmp->DoesPropertyExist ( kXMP_NS_CreatorAtom, fieldPath.c_str() )) ) { + haveXMP = true; + xmp->SetProperty ( kXMP_NS_CreatorAtom, fieldPath.c_str(), rawCr8r.appOptions ); + } + + rawCr8r.appName[31] = 0; // Ensure a terminating nul. + if ( (rawCr8r.appName[0] != 0) && (! xmp->DoesPropertyExist ( kXMP_NS_XMP, "CreatorTool" )) ) { + haveXMP = true; + xmp->SetProperty ( kXMP_NS_XMP, "CreatorTool", rawCr8r.appName ); + } + + } + + return haveXMP; + +} // ImportCr8rItems + +// ================================================================================================= +// ExportCr8rItems +// =============== + +static inline void SetBufferedString ( char * dest, const std::string source, size_t limit ) +{ + memset ( dest, 0, limit ); + size_t count = source.size(); + if ( count >= limit ) count = limit - 1; // Ensure a terminating nul. + memcpy ( dest, source.c_str(), count ); +} + +// ------------------------------------------------------------------------------------------------- + +static void ExportCr8rItems ( const SXMPMeta & xmp, MOOV_Manager * moovMgr ) +{ + bool haveNewCr8r = false; + std::string creatorCode, appleEvent, fileExt, appOptions, appName; + + haveNewCr8r |= xmp.GetStructField ( kXMP_NS_CreatorAtom, "macAtom", kXMP_NS_CreatorAtom, "applicationCode", &creatorCode, 0 ); + haveNewCr8r |= xmp.GetStructField ( kXMP_NS_CreatorAtom, "macAtom", kXMP_NS_CreatorAtom, "invocationAppleEvent", &appleEvent, 0 ); + haveNewCr8r |= xmp.GetStructField ( kXMP_NS_CreatorAtom, "windowsAtom", kXMP_NS_CreatorAtom, "extension", &fileExt, 0 ); + haveNewCr8r |= xmp.GetStructField ( kXMP_NS_CreatorAtom, "windowsAtom", kXMP_NS_CreatorAtom, "invocationFlags", &appOptions, 0 ); + haveNewCr8r |= xmp.GetProperty ( kXMP_NS_XMP, "CreatorTool", &appName, 0 ); + + MOOV_Manager::BoxInfo infoCr8r; + MOOV_Manager::BoxRef refCr8r = moovMgr->GetBox ( "moov/udta/Cr8r", &infoCr8r ); + bool haveOldCr8r = ( (refCr8r != 0) && (infoCr8r.contentSize == sizeof ( Cr8rBoxContent )) ); + + if ( ! haveNewCr8r ) { + if ( haveOldCr8r ) { + MOOV_Manager::BoxRef udtaRef = moovMgr->GetBox ( "moov/udta", 0 ); + moovMgr->DeleteTypeChild ( udtaRef, 0x43723872 /* 'Cr8r' */ ); + } + return; + } + + Cr8rBoxContent newCr8r; + const Cr8rBoxContent * oldCr8r = (Cr8rBoxContent*) infoCr8r.content; + + if ( ! haveOldCr8r ) { + + memset ( &newCr8r, 0, sizeof(newCr8r) ); + newCr8r.magic = MakeUns32BE ( 0xBEEFCAFE ); + newCr8r.size = MakeUns32BE ( sizeof ( newCr8r ) ); + newCr8r.majorVer = MakeUns16BE ( 1 ); + + } else { + + memcpy ( &newCr8r, oldCr8r, sizeof(newCr8r) ); + if ( GetUns32BE ( &newCr8r.magic ) != 0xBEEFCAFE ) { // Make sure we write BE numbers. + Flip4 ( &newCr8r.magic ); + Flip4 ( &newCr8r.size ); + Flip2 ( &newCr8r.majorVer ); + Flip2 ( &newCr8r.minorVer ); + Flip4 ( &newCr8r.creatorCode ); + Flip4 ( &newCr8r.appleEvent ); + } + + } + + if ( ! creatorCode.empty() ) { + newCr8r.creatorCode = MakeUns32BE ( (XMP_Uns32) strtoul ( creatorCode.c_str(), 0, 0 ) ); + } + + if ( ! appleEvent.empty() ) { + newCr8r.appleEvent = MakeUns32BE ( (XMP_Uns32) strtoul ( appleEvent.c_str(), 0, 0 ) ); + } + + if ( ! fileExt.empty() ) SetBufferedString ( newCr8r.fileExt, fileExt, sizeof ( newCr8r.fileExt ) ); + if ( ! appOptions.empty() ) SetBufferedString ( newCr8r.appOptions, appOptions, sizeof ( newCr8r.appOptions ) ); + if ( ! appName.empty() ) SetBufferedString ( newCr8r.appName, appName, sizeof ( newCr8r.appName ) ); + + moovMgr->SetBox ( "moov/udta/Cr8r", &newCr8r, sizeof(newCr8r) ); + +} // ExportCr8rItems + +// ================================================================================================= +// GetAtomInfo +// =========== + +struct AtomInfo { + XMP_Int64 atomSize; + XMP_Uns32 atomType; + bool hasLargeSize; +}; + +enum { // ! Do not rearrange, code depends on this order. + kBadQT_NoError = 0, // No errors. + kBadQT_SmallInner = 1, // An extra 1..7 bytes at the end of an inner span. + kBadQT_LargeInner = 2, // More serious inner garbage, found as invalid atom length. + kBadQT_SmallOuter = 3, // An extra 1..7 bytes at the end of the file. + kBadQT_LargeOuter = 4 // More serious EOF garbage, found as invalid atom length. +}; +typedef XMP_Uns8 QTErrorMode; + +static QTErrorMode GetAtomInfo ( XMP_IO* qtFile, XMP_Int64 spanSize, int nesting, AtomInfo * info ) +{ + QTErrorMode status = kBadQT_NoError; + XMP_Uns8 buffer [8]; + + info->hasLargeSize = false; + + qtFile->ReadAll ( buffer, 8 ); // Will throw if 8 bytes aren't available. + info->atomSize = GetUns32BE ( &buffer[0] ); // ! Yes, the initial size is big endian UInt32. + info->atomType = GetUns32BE ( &buffer[4] ); + + if ( info->atomSize == 0 ) { // Does the atom extend to EOF? + + if ( nesting != 0 ) return kBadQT_LargeInner; + info->atomSize = spanSize; // This outer atom goes to EOF. + + } else if ( info->atomSize == 1 ) { // Does the atom have a 64-bit size? + + if ( spanSize < 16 ) { // Is there room in the span for the 16 byte header? + status = kBadQT_LargeInner; + if ( nesting == 0 ) status += 2; // Convert to "outer". + return status; + } + + qtFile->ReadAll ( buffer, 8 ); + info->atomSize = (XMP_Int64) GetUns64BE ( &buffer[0] ); + info->hasLargeSize = true; + + } + + XMP_Assert ( status == kBadQT_NoError ); + return status; + +} // GetAtomInfo + +// ================================================================================================= +// CheckAtomList +// ============= +// +// Check that a sequence of atoms fills a given span. The I/O position must be at the start of the +// span, it is left just past the span on success. Recursive checks are done for top level 'moov' +// atoms, and second level 'udta' atoms ('udta' inside 'moov'). +// +// Checking continues for "small inner" errors. They will be reported if no other kinds of errors +// are found, otherwise the other error is reported. Checking is immediately aborted for any "large" +// error. The rationale is that QuickTime can apparently handle small inner errors. They might be +// arise from updates that shorten an atom by less than 8 bytes. Larger shrinkage should introduce a +// 'free' atom. + +static QTErrorMode CheckAtomList ( XMP_IO* qtFile, XMP_Int64 spanSize, int nesting ) +{ + QTErrorMode status = kBadQT_NoError; + AtomInfo info; + + const static XMP_Uns32 moovAtomType = 0x6D6F6F76; // ! Don't use MakeUns32BE, already big endian. + const static XMP_Uns32 udtaAtomType = 0x75647461; + + for ( ; spanSize >= 8; spanSize -= info.atomSize ) { + + QTErrorMode atomStatus = GetAtomInfo ( qtFile, spanSize, nesting, &info ); + if ( atomStatus != kBadQT_NoError ) return atomStatus; + + XMP_Int64 headerSize = 8; + if ( info.hasLargeSize ) headerSize = 16; + + if ( (info.atomSize < headerSize) || (info.atomSize > spanSize) ) { + status = kBadQT_LargeInner; + if ( nesting == 0 ) status += 2; // Convert to "outer". + return status; + } + + bool doChildren = false; + if ( (nesting == 0) && (info.atomType == moovAtomType) ) doChildren = true; + if ( (nesting == 1) && (info.atomType == udtaAtomType) ) doChildren = true; + + XMP_Int64 dataSize = info.atomSize - headerSize; + + if ( ! doChildren ) { + qtFile->Seek ( dataSize, kXMP_SeekFromCurrent ); + } else { + QTErrorMode innerStatus = CheckAtomList ( qtFile, dataSize, nesting+1 ); + if ( innerStatus > kBadQT_SmallInner ) return innerStatus; // Quit for serious errors. + if ( status == kBadQT_NoError ) status = innerStatus; // Remember small inner errors. + } + + } + + XMP_Assert ( status <= kBadQT_SmallInner ); // Else already returned. + // ! Make sure inner kBadQT_SmallInner is propagated if this span is OK. + + if ( spanSize != 0 ) { + qtFile->Seek ( spanSize, kXMP_SeekFromCurrent ); // ! Skip the trailing garbage of this span. + status = kBadQT_SmallInner; + if ( nesting == 0 ) status += 2; // Convert to "outer". + } + + return status; + +} // CheckAtomList + +// ================================================================================================= +// AttemptFileRepair +// ================= + +static void AttemptFileRepair ( XMP_IO* qtFile, XMP_Int64 fileSpace, QTErrorMode status, GenericErrorCallback * ec ) +{ + + switch ( status ) { + case kBadQT_NoError : return; // Sanity check. + case kBadQT_SmallInner : return; // Fixed in normal update code for the 'udta' box. + case kBadQT_LargeInner : + { + XMP_Error error ( kXMPErr_BadFileFormat,"Can't repair QuickTime file" ); + XMPFileHandler::NotifyClient(ec, kXMPErrSev_FileFatal, error); + break;// will never be here + } + case kBadQT_SmallOuter : // Truncate file below. + case kBadQT_LargeOuter : // Truncate file below. + { + break; + } + default : + { + XMP_Error error ( kXMPErr_InternalFailure, "Invalid QuickTime error mode" ); + XMPFileHandler::NotifyClient(ec, kXMPErrSev_FileFatal, error); + } + } + + AtomInfo info; + XMP_Int64 headerSize(0); + + // Process the top level atoms until an error is found. + + qtFile->Rewind(); + + for ( ; fileSpace >= 8; fileSpace -= info.atomSize ) { + + QTErrorMode atomStatus = GetAtomInfo ( qtFile, fileSpace, 0, &info ); + + headerSize = 8; // ! Set this before checking atomStatus, used after the loop. + if ( info.hasLargeSize ) headerSize = 16; + + if ( atomStatus != kBadQT_NoError ) break; + // If the atom size is less than header -case of kBadQT_SmallOuter + // If the atom size is more than left filespace -case of kBadQT_LargeOuter + if ( (info.atomSize < headerSize) || (info.atomSize > fileSpace ) ) break; + + XMP_Int64 dataSize = info.atomSize - headerSize; + qtFile->Seek ( dataSize, kXMP_SeekFromCurrent ); + + } + // Truncate only if the last box type was XMP boxes + // Refrain from truncating a known box as it + // might have some useful data which is incomplete + if ( fileSpace < 8 || + ! ISOMedia::IsKnownBoxType ( info.atomType ) || + ((info.atomType == ISOMedia::k_uuid) && IsXMPUUID(qtFile,info.atomSize-headerSize,true)) || + (info.atomType == ISOMedia::k_XMP_) + ){ + + XMP_Error error ( kXMPErr_BadFileFormat,"Truncate outer EOF Garbage" ); + XMPFileHandler::NotifyClient(ec, kXMPErrSev_Recoverable, error); + // Truncate the file. If fileSpace >= 8 then the loop exited early due to a bad atom, seek back + // to the atom's start. Otherwise, the loop exited because no more atoms are possible, no seek. + + if ( fileSpace >= 8 ) qtFile->Seek ( -headerSize, kXMP_SeekFromCurrent ); + XMP_Int64 currPos = qtFile->Offset(); + qtFile->Truncate ( currPos ); + } + else{ + + XMP_Error error ( kXMPErr_BadFileFormat,"Missing box Data at EOF" ); + XMPFileHandler::NotifyClient(ec, kXMPErrSev_FileFatal, error); + } + +} // AttemptFileRepair + +// ================================================================================================= +// CheckQTFileStructure +// ==================== + +static void CheckQTFileStructure ( XMPFileHandler * thiz,bool doRepair, GenericErrorCallback * ec ) +{ + XMPFiles * parent = thiz->parent; + XMP_IO* fileRef = parent->ioRef; + XMP_Int64 fileSize = fileRef->Length(); + + // Check the basic file structure and try to repair if asked. + + fileRef->Rewind(); + QTErrorMode status = CheckAtomList ( fileRef, fileSize, 0 ); + + if ( status != kBadQT_NoError ) { + if ( doRepair || (status == kBadQT_SmallInner) || (status == kBadQT_SmallOuter) ) { + AttemptFileRepair ( fileRef, fileSize, status,ec ); // Will throw if the attempt fails. + } else if ( status != kBadQT_SmallInner ) { + //don't truncate Large Outer EOF garbage unless the client wants it to + // Clients can pass their intent by setting the flag kXMPFiles_OpenRepairFile + XMP_Error error ( kXMPErr_BadFileFormat,"Ill-formed QuickTime file" ); + XMPFileHandler::NotifyClient(ec, kXMPErrSev_FileFatal, error); + } + } + +} // CheckQTFileStructure; + +// ================================================================================================= +// CheckFinalBox +// ============= +// +// Before appending anything new, check if the final top level box has a "to EoF" length. If so, fix +// it to have an explicit length. + +static void CheckFinalBox ( XMP_IO* fileRef, XMP_AbortProc abortProc, void * abortArg ) +{ + const bool checkAbort = (abortProc != 0); + + XMP_Uns64 fileSize = fileRef->Length(); + + // Find the last 2 boxes in the file. Need the previous to last in case it is an Apple 'wide' box. + + XMP_Uns64 prevPos, lastPos, nextPos; + ISOMedia::BoxInfo prevBox, lastBox; + XMP_Uns8 buffer [16]; // Enough to create an extended header. + + memset ( &prevBox, 0, sizeof(prevBox) ); // AUDIT: Using sizeof(prevBox) is safe. + memset ( &lastBox, 0, sizeof(lastBox) ); // AUDIT: Using sizeof(lastBox) is safe. + prevPos = lastPos = nextPos = 0; + while ( nextPos != fileSize ) { + if ( checkAbort && abortProc(abortArg) ) { + XMP_Throw ( "MPEG4_MetaHandler::CheckFinalBox - User abort", kXMPErr_UserAbort ); + } + prevBox = lastBox; + prevPos = lastPos; + lastPos = nextPos; + nextPos = ISOMedia::GetBoxInfo ( fileRef, lastPos, fileSize, &lastBox, true /* throw errors */ ); + } + + // See if the last box is valid and has a "to EoF" size. + + if ( lastBox.headerSize < 8 ) XMP_Throw ( "MPEG-4 final box is invalid", kXMPErr_EnforceFailure ); + fileRef->Seek ( lastPos, kXMP_SeekFromStart ); + fileRef->Read ( buffer, 4 ); + XMP_Uns64 lastSize = GetUns32BE ( &buffer[0] ); // ! Yes, the file has a 32-bit value. + if ( lastSize != 0 ) return; + + // Have a final "to EoF" box, try to write the explicit size. + + lastSize = lastBox.headerSize + lastBox.contentSize; + if ( lastSize <= 0xFFFFFFFFUL ) { + + // Fill in the 32-bit exact size. + PutUns32BE ( (XMP_Uns32)lastSize, &buffer[0] ); + fileRef->Seek ( lastPos, kXMP_SeekFromStart ); + fileRef->Write ( buffer, 4 ); + + } else { + + // Try to convert to using an extended header. + + if ( (prevBox.boxType != ISOMedia::k_wide) || (prevBox.headerSize != 8) || (prevBox.contentSize != 0) ) { + XMP_Throw ( "Can't expand final box header", kXMPErr_EnforceFailure ); + } + XMP_Assert ( prevPos == (lastPos - 8) ); + + PutUns32BE ( 1, &buffer[0] ); + PutUns32BE ( lastBox.boxType, &buffer[4] ); + PutUns64BE ( lastSize, &buffer[8] ); + fileRef->Seek ( prevPos, kXMP_SeekFromStart ); + fileRef->Write ( buffer, 16 ); + + } + +} // CheckFinalBox + +// ================================================================================================= +// WriteBoxHeader +// ============== + +static void WriteBoxHeader ( XMP_IO* fileRef, XMP_Uns32 boxType, XMP_Uns64 boxSize ) +{ + XMP_Uns32 u32; + XMP_Uns64 u64; + XMP_Enforce ( boxSize >= 8 ); // The size must be the full size, not just the content. + + if ( boxSize <= 0xFFFFFFFF ) { + + u32 = MakeUns32BE ( (XMP_Uns32)boxSize ); + fileRef->Write ( &u32, 4 ); + u32 = MakeUns32BE ( boxType ); + fileRef->Write ( &u32, 4 ); + + } else { + + u32 = MakeUns32BE ( 1 ); + fileRef->Write ( &u32, 4 ); + u32 = MakeUns32BE ( boxType ); + fileRef->Write ( &u32, 4 ); + u64 = MakeUns64BE ( boxSize ); + fileRef->Write ( &u64, 8 ); + + } + +} // WriteBoxHeader + +// ================================================================================================= +// WipeBoxFree +// =========== +// +// Change the box's type to 'free' (or create a 'free' box) and zero the content. + +static XMP_Uns8 kZeroes [64*1024]; // C semantics guarantee zero initialization. + +static void WipeBoxFree ( XMP_IO* fileRef, XMP_Uns64 boxOffset, XMP_Uns32 boxSize ) +{ + if ( boxSize == 0 ) return; + XMP_Enforce ( boxSize >= 8 ); + + fileRef->Seek ( boxOffset, kXMP_SeekFromStart ); + XMP_Uns32 u32; + u32 = MakeUns32BE ( boxSize ); // ! The actual size should not change, but might have had a long header. + fileRef->Write ( &u32, 4 ); + u32 = MakeUns32BE ( ISOMedia::k_free ); + fileRef->Write ( &u32, 4 ); + + XMP_Uns32 ioCount = sizeof ( kZeroes ); + for ( boxSize -= 8; boxSize > 0; boxSize -= ioCount ) { + if ( ioCount > boxSize ) ioCount = boxSize; + fileRef->Write ( &kZeroes[0], ioCount ); + } + +} // WipeBoxFree + +// ================================================================================================= +// CreateFreeSpaceList +// =================== + +struct SpaceInfo { + XMP_Uns64 offset, size; + SpaceInfo() : offset(0), size(0) {}; + SpaceInfo ( XMP_Uns64 _offset, XMP_Uns64 _size ) : offset(_offset), size(_size) {}; +}; + +typedef std::vector FreeSpaceList; + +static void CreateFreeSpaceList ( XMP_IO* fileRef, XMP_Uns64 fileSize, + XMP_Uns64 oldOffset, XMP_Uns32 oldSize, FreeSpaceList * spaceList ) +{ + XMP_Uns64 boxPos=0, boxNext=0, adjacentFree=0; + ISOMedia::BoxInfo currBox; + + fileRef->Rewind(); + spaceList->clear(); + + for ( boxPos = 0; boxPos < fileSize; boxPos = boxNext ) { + + boxNext = ISOMedia::GetBoxInfo ( fileRef, boxPos, fileSize, &currBox, true /* throw errors */ ); + XMP_Uns64 currSize = currBox.headerSize + currBox.contentSize; + + if ( (currBox.boxType == ISOMedia::k_free) || + (currBox.boxType == ISOMedia::k_skip) || + ((boxPos == oldOffset) && (currSize == oldSize)) ) { + + if ( spaceList->empty() || (boxPos != adjacentFree) ) { + spaceList->push_back ( SpaceInfo ( boxPos, currSize ) ); + adjacentFree = boxPos + currSize; + } else { + SpaceInfo * lastSpace = &spaceList->back(); + lastSpace->size += currSize; + } + + } + + } + +} // CreateFreeSpaceList + +// ================================================================================================= +// MPEG4_MetaHandler::CacheFileData +// ================================ +// +// There are 3 file variants: normal ISO Base Media, modern QuickTime, and classic QuickTime. The +// XMP is placed differently between the ISO and two QuickTime forms, and there is different but not +// colliding native metadata. The entire 'moov' subtree is cached, along with the top level 'uuid' +// box of XMP if present. + +void MPEG4_MetaHandler::CacheFileData() +{ + XMP_Assert ( ! this->containsXMP ); + + XMPFiles * parent = this->parent; + XMP_OptionBits openFlags = parent->openFlags; + + XMP_IO* fileRef = parent->ioRef; + + XMP_AbortProc abortProc = parent->abortProc; + void * abortArg = parent->abortArg; + const bool checkAbort = (abortProc != 0); + + // First do some special case repair to QuickTime files, based on bad files in the wild. + + const bool isUpdate = XMP_OptionIsSet ( openFlags, kXMPFiles_OpenForUpdate ); + const bool doRepair = XMP_OptionIsSet ( openFlags, kXMPFiles_OpenRepairFile ); + + if ( isUpdate ) { + CheckQTFileStructure ( this, doRepair, &parent->errorCallback ); // Will throw for failure. + } + + // Cache the top level 'moov' and 'uuid'/XMP boxes. + + XMP_Uns64 fileSize = fileRef->Length(); + + XMP_Uns64 boxPos, boxNext; + ISOMedia::BoxInfo currBox; + + bool xmpOnly = XMP_OptionIsSet ( openFlags, kXMPFiles_OpenOnlyXMP ); + bool haveISOFile = (this->fileMode == MOOV_Manager::kFileIsNormalISO); + + bool xmpUuidFound = (! haveISOFile); // Ignore the XMP 'uuid' box for QuickTime files. + bool moovIgnored = (xmpOnly & haveISOFile); // Ignore the 'moov' box for XMP-only ISO files. + bool moovFound = moovIgnored; + + for ( boxPos = 0; boxPos < fileSize; boxPos = boxNext ) { + + if ( checkAbort && abortProc(abortArg) ) { + XMP_Throw ( "MPEG4_MetaHandler::CacheFileData - User abort", kXMPErr_UserAbort ); + } + + + boxNext = ISOMedia::GetBoxInfo ( fileRef, boxPos, fileSize, &currBox ); + + if ( (! moovFound) && (currBox.boxType == ISOMedia::k_moov) ) { + + XMP_Uns64 fullMoovSize = currBox.headerSize + currBox.contentSize; + if ( fullMoovSize > moovBoxSizeLimit ) { // From here on we know 32-bit offsets are safe. + XMP_Throw ( "Oversize 'moov' box", kXMPErr_EnforceFailure ); + } + + this->moovMgr.fullSubtree.assign ( (XMP_Uns32)fullMoovSize, 0 ); + fileRef->Seek ( boxPos, kXMP_SeekFromStart ); + fileRef->Read ( &this->moovMgr.fullSubtree[0], (XMP_Uns32)fullMoovSize ); + + this->moovBoxPos = boxPos; + this->moovBoxSize = (XMP_Uns32)fullMoovSize; + moovFound = true; + if ( xmpUuidFound ) break; // Exit the loop when both are found. + + } else if ( (! xmpUuidFound) && (currBox.boxType == ISOMedia::k_uuid) && ( memcmp( currBox.idUUID, ISOMedia::k_xmpUUID, 16 ) == 0 ) ) { + + XMP_Uns64 fullUuidSize = currBox.headerSize + currBox.contentSize; + if ( fullUuidSize > moovBoxSizeLimit ) { // From here on we know 32-bit offsets are safe. + XMP_Throw ( "Oversize XMP 'uuid' box", kXMPErr_EnforceFailure ); + } + + this->packetInfo.offset = boxPos + currBox.headerSize ; + this->packetInfo.length = (XMP_Int32) (currBox.contentSize); + + this->xmpPacket.assign ( this->packetInfo.length, ' ' ); + fileRef->ReadAll ( (void*)this->xmpPacket.data(), this->packetInfo.length ); + + this->xmpBoxPos = boxPos; + this->xmpBoxSize = (XMP_Uns32)fullUuidSize; + xmpUuidFound = true; + if ( moovFound ) break; // Exit the loop when both are found. + + } + + } + + if ( (! moovFound) && (! moovIgnored) ){ + XMP_Error error ( kXMPErr_BadFileFormat,"No 'moov' box" ); + XMPFileHandler::NotifyClient(&parent->errorCallback, kXMPErrSev_FileFatal, error); + } + +} // MPEG4_MetaHandler::CacheFileData + +// ================================================================================================= +// MPEG4_MetaHandler::ProcessXMP +// ============================= + +void MPEG4_MetaHandler::ProcessXMP() +{ + if ( this->processedXMP ) return; + this->processedXMP = true; // Make sure only called once. + + XMPFiles * parent = this->parent; + XMP_OptionBits openFlags = parent->openFlags; + + bool xmpOnly = XMP_OptionIsSet ( openFlags, kXMPFiles_OpenOnlyXMP ); + bool haveISOFile = (this->fileMode == MOOV_Manager::kFileIsNormalISO); + + // Process the cached XMP (from the 'uuid' box) if that is all we want and this is an ISO file. + + if ( xmpOnly & haveISOFile ) { + + this->containsXMP = this->havePreferredXMP = (this->packetInfo.length != 0); + + if ( this->containsXMP ) { + FillPacketInfo ( this->xmpPacket, &this->packetInfo ); + this->xmpObj.ParseFromBuffer ( this->xmpPacket.c_str(), (XMP_StringLen)this->xmpPacket.size() ); + this->xmpObj.DeleteProperty ( kXMP_NS_XMP, "NativeDigests" ); // No longer used. + } + + return; + + } + + // Parse the cached 'moov' subtree, parse the preferred XMP. + + if ( this->moovMgr.fullSubtree.empty() ) { + XMP_Error error ( kXMPErr_BadFileFormat,"No 'moov' box" ); + XMPFileHandler::NotifyClient(&parent->errorCallback, kXMPErrSev_FileFatal, error); + } + this->moovMgr.ParseMemoryTree ( this->fileMode ); + + if ( (this->xmpBoxPos == 0) || (! haveISOFile) ) { + + // Look for the QuickTime moov/uuid/XMP_ box. + + MOOV_Manager::BoxInfo xmpInfo; + MOOV_Manager::BoxRef xmpRef = this->moovMgr.GetBox ( "moov/udta/XMP_", &xmpInfo ); + + if ( (xmpRef != 0) && (xmpInfo.contentSize != 0) ) { + + this->xmpBoxPos = this->moovBoxPos + this->moovMgr.GetParsedOffset ( xmpRef ); + this->packetInfo.offset = this->xmpBoxPos + this->moovMgr.GetHeaderSize ( xmpRef ); + this->packetInfo.length = xmpInfo.contentSize; + + this->xmpPacket.assign ( (char*)xmpInfo.content, this->packetInfo.length ); + this->havePreferredXMP = (! haveISOFile); + + } + + } + + if ( this->xmpBoxPos != 0 ) { + this->containsXMP = true; + FillPacketInfo ( this->xmpPacket, &this->packetInfo ); + this->xmpObj.ParseFromBuffer ( this->xmpPacket.c_str(), (XMP_StringLen)this->xmpPacket.size() ); + this->xmpObj.DeleteProperty ( kXMP_NS_XMP, "NativeDigests" ); // No longer used. + } + + // Import the non-XMP items. Do the imports in reverse priority order, last import wins! + + MOOV_Manager::BoxInfo mvhdInfo; + MOOV_Manager::BoxRef mvhdRef = this->moovMgr.GetBox ( "moov/mvhd", &mvhdInfo ); + bool mvhdFound = ((mvhdRef != 0) && (mvhdInfo.contentSize != 0)); + + MOOV_Manager::BoxInfo udtaInfo; + MOOV_Manager::BoxRef udtaRef = this->moovMgr.GetBox ( "moov/udta", &udtaInfo ); + std::vector cprtBoxes; + + if ( udtaRef != 0 ) { + for ( XMP_Uns32 i = 0; i < udtaInfo.childCount; ++i ) { + MOOV_Manager::BoxInfo currInfo; + MOOV_Manager::BoxRef currRef = this->moovMgr.GetNthChild ( udtaRef, i, &currInfo ); + if ( currRef == 0 ) break; // Sanity check, should not happen. + if ( currInfo.boxType != ISOMedia::k_cprt ) continue; + cprtBoxes.push_back ( currInfo ); + } + } + bool cprtFound = (! cprtBoxes.empty()); + + bool tradQTFound = this->tradQTMgr.ParseCachedBoxes ( this->moovMgr ); + bool tmcdFound = this->ParseTimecodeTrack(); + + if ( this->fileMode == MOOV_Manager::kFileIsNormalISO ) { + + if ( mvhdFound ) this->containsXMP |= ImportMVHDItems ( mvhdInfo, &this->xmpObj ); + if ( cprtFound ) this->containsXMP |= ImportISOCopyrights ( cprtBoxes, &this->xmpObj ); + if ( tmcdFound ) this->containsXMP |= ImportTimecodeItems ( this->tmcdInfo, this->tradQTMgr, &this->xmpObj ); + } else { // This is a QuickTime file, either traditional or modern. + + if ( mvhdFound ) this->containsXMP |= ImportMVHDItems ( mvhdInfo, &this->xmpObj ); + if ( cprtFound ) this->containsXMP |= ImportISOCopyrights ( cprtBoxes, &this->xmpObj ); + if ( tmcdFound | tradQTFound ) { + // Some of the timecode items are in the .../udta/©... set but handled by ImportTimecodeItems. + this->containsXMP |= ImportTimecodeItems ( this->tmcdInfo, this->tradQTMgr, &this->xmpObj ); + } + + this->containsXMP |= ImportCr8rItems ( this->moovMgr, &this->xmpObj ); + + } + +} // MPEG4_MetaHandler::ProcessXMP + +// ================================================================================================= +// MPEG4_MetaHandler::ParseTimecodeTrack +// ===================================== + +bool MPEG4_MetaHandler::ParseTimecodeTrack() +{ + MOOV_Manager::BoxInfo drefInfo; + MOOV_Manager::BoxRef drefRef = FindTimecode_dref ( this->moovMgr ); + bool qtTimecodeIsExternal=false; + if( drefRef != 0 ) + { + this->moovMgr.GetBoxInfo( drefRef , &drefInfo ); + // After dref atom in a QT file we should only + // proceed further to check the Data refernces + // if the total size of the content is greater + // than 8 bytes which suggests that there is atleast + // one data reference to check for external references. + if ( drefInfo.contentSize>8) + { + XMP_Uns32 noOfDrefs=GetUns32BE(drefInfo.content+4); + if(noOfDrefs>0) + { + const XMP_Uns8* dataReference = drefInfo.content + 8; + const XMP_Uns8* nextDataref = 0; + const XMP_Uns8* boxlimit = drefInfo.content + drefInfo.contentSize; + ISOMedia::BoxInfo dataRefernceInfo; + while(noOfDrefs--) + { + nextDataref= ISOMedia::GetBoxInfo( dataReference , boxlimit, + &dataRefernceInfo); + //The content atleast contains the flag and some data + if ( dataRefernceInfo.contentSize > 4 ) + { + if (dataRefernceInfo.boxType==ISOMedia::k_alis && + *((XMP_Uns8*)(dataReference + dataRefernceInfo.headerSize + 4)) !=1 ) + { + qtTimecodeIsExternal=true; + break; + } + } + dataReference=nextDataref; + } + } + } + } + + MOOV_Manager::BoxRef stblRef = FindTimecode_stbl ( this->moovMgr ); + if ( stblRef == 0 ) return false; + + // Find the .../stbl/stsd box and process the first table entry. + + MOOV_Manager::BoxInfo stsdInfo; + MOOV_Manager::BoxRef stsdRef; + + stsdRef = this->moovMgr.GetTypeChild ( stblRef, ISOMedia::k_stsd, &stsdInfo ); + if ( stsdRef == 0 ) return false; + if ( stsdInfo.contentSize < (8 + sizeof ( MOOV_Manager::Content_stsd_entry )) ) return false; + if ( GetUns32BE ( stsdInfo.content + 4 ) == 0 ) return false; // Make sure the entry count is non-zero. + + const MOOV_Manager::Content_stsd_entry * stsdRawEntry = (MOOV_Manager::Content_stsd_entry*) (stsdInfo.content + 8); + + XMP_Uns32 stsdEntrySize = GetUns32BE ( &stsdRawEntry->entrySize ); + if ( stsdEntrySize > (stsdInfo.contentSize - 4) ) stsdEntrySize = stsdInfo.contentSize - 4; + if ( stsdEntrySize < sizeof ( MOOV_Manager::Content_stsd_entry ) ) return false; + + XMP_Uns32 stsdEntryFormat = GetUns32BE ( &stsdRawEntry->format ); + if ( stsdEntryFormat != ISOMedia::k_tmcd ) return false; + + // If frame duration is zero it means tmcd sample is invalid + if(GetUns32BE(&stsdRawEntry->frameDuration)==0) + return false; + + this->tmcdInfo.timeScale = GetUns32BE ( &stsdRawEntry->timeScale ); + this->tmcdInfo.frameDuration = GetUns32BE ( &stsdRawEntry->frameDuration ); + + double floatCount = (double)this->tmcdInfo.timeScale / (double)this->tmcdInfo.frameDuration; + XMP_Uns8 expectedCount = (XMP_Uns8) (floatCount + 0.5); + if( expectedCount == 0 ) return false; + if ( expectedCount != stsdRawEntry->frameCount ) { + double countRatio = (double)stsdRawEntry->frameCount / (double)expectedCount; + this->tmcdInfo.timeScale = (XMP_Uns32) (((double)this->tmcdInfo.timeScale * countRatio) + 0.5); + } + + XMP_Uns32 flags = GetUns32BE ( &stsdRawEntry->flags ); + this->tmcdInfo.isDropFrame = flags & 0x1; + + // Look for a trailing 'name' box on the first stsd table entry. + + XMP_Uns32 stsdTrailerSize = stsdEntrySize - sizeof ( MOOV_Manager::Content_stsd_entry ); + if ( stsdTrailerSize > 8 ) { // Room for a non-empty 'name' box? + + const XMP_Uns8 * trailerStart = stsdInfo.content + 8 + sizeof ( MOOV_Manager::Content_stsd_entry ); + const XMP_Uns8 * trailerLimit = trailerStart + stsdTrailerSize; + const XMP_Uns8 * trailerPos; + const XMP_Uns8 * trailerNext; + ISOMedia::BoxInfo trailerInfo; + + for ( trailerPos = trailerStart; trailerPos < trailerLimit; trailerPos = trailerNext ) { + + trailerNext = ISOMedia::GetBoxInfo ( trailerPos, trailerLimit, &trailerInfo ); + + if ( trailerInfo.boxType == ISOMedia::k_name ) { + + this->tmcdInfo.nameOffset = (XMP_Uns32) (trailerPos - stsdInfo.content); + + if ( trailerInfo.contentSize > 4 ) { + + XMP_Uns16 textLen = GetUns16BE ( trailerPos + trailerInfo.headerSize ); + this->tmcdInfo.macLang = GetUns16BE ( trailerPos + trailerInfo.headerSize + 2 ); + + if ( trailerInfo.contentSize >= (XMP_Uns64)(textLen + 4) ) { + const char * textPtr = (char*) (trailerPos + trailerInfo.headerSize + 4); + this->tmcdInfo.macName.assign ( textPtr, textLen ); + } + + } + + break; // Done after finding the first 'name' box. + + } + + } + + } + + // Find the timecode sample. + // Read the timecode only if we are sure that it is not External + // This way we never find stsdBox and ExportTimecodeItems and + // ImportTimecodeItems doesn't do anything with timeCodeSample + // Also because sampleOffset is/remains zero UpdateFile doesn't + // update the timeCodeSample value + if(!qtTimecodeIsExternal) + { + XMP_Uns64 sampleOffset = 0; + MOOV_Manager::BoxInfo tempInfo; + MOOV_Manager::BoxRef tempRef; + + tempRef = this->moovMgr.GetTypeChild ( stblRef, ISOMedia::k_stsc, &tempInfo ); + if ( tempRef == 0 ) return false; + if ( tempInfo.contentSize < (8 + sizeof ( MOOV_Manager::Content_stsc_entry )) ) return false; + if ( GetUns32BE ( tempInfo.content + 4 ) == 0 ) return false; // Make sure the entry count is non-zero. + + XMP_Uns32 firstChunkNumber = GetUns32BE ( tempInfo.content + 8 ); // Want first field of first entry. + + tempRef = this->moovMgr.GetTypeChild ( stblRef, ISOMedia::k_stco, &tempInfo ); + + if ( tempRef != 0 ) { + + if ( tempInfo.contentSize < (8 + 4) ) return false; + XMP_Uns32 stcoCount = GetUns32BE ( tempInfo.content + 4 ); + if ( stcoCount < firstChunkNumber ) return false; + XMP_Uns32 * stcoPtr = (XMP_Uns32*) (tempInfo.content + 8); + sampleOffset = GetUns32BE ( &stcoPtr[firstChunkNumber-1] ); // ! Chunk number is 1-based. + + } else { + + tempRef = this->moovMgr.GetTypeChild ( stblRef, ISOMedia::k_co64, &tempInfo ); + if ( (tempRef == 0) || (tempInfo.contentSize < (8 + 8)) ) return false; + XMP_Uns32 co64Count = GetUns32BE ( tempInfo.content + 4 ); + if ( co64Count < firstChunkNumber ) return false; + XMP_Uns64 * co64Ptr = (XMP_Uns64*) (tempInfo.content + 8); + sampleOffset = GetUns64BE ( &co64Ptr[firstChunkNumber-1] ); // ! Chunk number is 1-based. + + } + + if ( sampleOffset != 0 ) { // Read the timecode sample. + + XMPFiles_IO* localFile = 0; + + if ( this->parent->ioRef == 0 ) { // Local read-only files get closed in CacheFileData. + XMP_Assert ( this->parent->UsesLocalIO() ); + localFile = XMPFiles_IO::New_XMPFiles_IO ( this->parent->GetFilePath().c_str(), Host_IO::openReadOnly, &this->parent->errorCallback); + XMP_Enforce ( localFile != 0 ); + this->parent->ioRef = localFile; + } + + this->parent->ioRef->Seek ( sampleOffset, kXMP_SeekFromStart ); + this->parent->ioRef->ReadAll ( &this->tmcdInfo.timecodeSample, 4 ); + this->tmcdInfo.timecodeSample = MakeUns32BE ( this->tmcdInfo.timecodeSample ); + if ( localFile != 0 ) { + localFile->Close(); + delete localFile; + this->parent->ioRef = 0; + } + + } + + // If this is a QT file, look for an edit list offset to add to the timecode sample. Look in the + // timecode track for an edts/elst box. The content is a UInt8 version, UInt8[3] flags, a UInt32 + // entry count, and a sequence of UInt32 triples (trackDuration, mediaTime, mediaRate). Take + // mediaTime from the first entry, divide it by tmcdInfo.frameDuration, add that to + // tmcdInfo.timecodeSample. + + bool isQT = (this->fileMode == MOOV_Manager::kFileIsModernQT) || + (this->fileMode == MOOV_Manager::kFileIsTraditionalQT); + + MOOV_Manager::BoxRef elstRef = 0; + if ( isQT ) elstRef = FindTimecode_elst ( this->moovMgr ); + if ( elstRef != 0 ) { + + MOOV_Manager::BoxInfo elstInfo; + this->moovMgr.GetBoxInfo ( elstRef, &elstInfo ); + + if ( elstInfo.contentSize >= (4+4+12) ) { + XMP_Uns32 elstCount = GetUns32BE ( elstInfo.content + 4 ); + if ( elstCount >= 1 ) { + XMP_Uns32 mediaTime = GetUns32BE ( elstInfo.content + (4+4+4) ); + this->tmcdInfo.timecodeSample += (mediaTime / this->tmcdInfo.frameDuration); + } + } + + } + + // Finally update this->tmcdInfo to remember (for update) that there is an OK timecode track. + + this->tmcdInfo.stsdBoxFound = true; + this->tmcdInfo.sampleOffset = sampleOffset; + } + return true; + +} // MPEG4_MetaHandler::ParseTimecodeTrack + +// ================================================================================================= +// MPEG4_MetaHandler::UpdateTopLevelBox +// ==================================== + +void MPEG4_MetaHandler::UpdateTopLevelBox ( XMP_Uns64 oldOffset, XMP_Uns32 oldSize, + const XMP_Uns8 * newBox, XMP_Uns32 newSize ) +{ + if ( (oldSize == 0) && (newSize == 0) ) return; // Sanity check, should not happen. + + XMP_IO* fileRef = this->parent->ioRef; + XMP_Uns64 oldFileSize = fileRef->Length(); + + XMP_AbortProc abortProc = this->parent->abortProc; + void * abortArg = this->parent->abortArg; + + if ( newSize == oldSize ) { + + // Trivial case, update the existing box in-place. + fileRef->Seek ( oldOffset, kXMP_SeekFromStart ); + fileRef->Write ( newBox, oldSize ); + + } else if ( (oldOffset + oldSize) == oldFileSize ) { + + // The old box was at the end, write the new and truncate the file if necessary. + fileRef->Seek ( oldOffset, kXMP_SeekFromStart ); + fileRef->Write ( newBox, newSize ); + fileRef->Truncate ( (oldOffset + newSize) ); // Does nothing if new size is bigger. + + } else if ( (newSize < oldSize) && ((oldSize - newSize) >= 8) ) { + + // The new size is smaller and there is enough room to create a free box. + fileRef->Seek ( oldOffset, kXMP_SeekFromStart ); + fileRef->Write ( newBox, newSize ); + WipeBoxFree ( fileRef, (oldOffset + newSize), (oldSize - newSize) ); + + } else { + + // Look for a trailing free box with enough space. If not found, consider any free space. + // If still not found, append the new box and make the old one free. + + ISOMedia::BoxInfo nextBoxInfo; + (void) ISOMedia::GetBoxInfo ( fileRef, (oldOffset + oldSize), oldFileSize, &nextBoxInfo, true /* throw errors */ ); + + XMP_Uns64 totalRoom = oldSize + nextBoxInfo.headerSize + nextBoxInfo.contentSize; + + bool nextIsFree = (nextBoxInfo.boxType == ISOMedia::k_free) || (nextBoxInfo.boxType == ISOMedia::k_skip); + bool haveEnoughRoom = (newSize == totalRoom) || + ( (newSize < totalRoom) && ((totalRoom - newSize) >= 8) ); + + if ( nextIsFree & haveEnoughRoom ) { + + fileRef->Seek ( oldOffset, kXMP_SeekFromStart ); + fileRef->Write ( newBox, newSize ); + + if ( newSize < totalRoom ) { + // Don't wipe, at most 7 old bytes left, it will be covered by the free header. + WriteBoxHeader ( fileRef, ISOMedia::k_free, (totalRoom - newSize) ); + } + + } else { + + // Create a list of all top level free space, including the old space as free. Use the + // earliest space that fits. If none, append. + + FreeSpaceList spaceList; + CreateFreeSpaceList ( fileRef, oldFileSize, oldOffset, oldSize, &spaceList ); + + size_t freeSlot, limit; + for ( freeSlot = 0, limit = spaceList.size(); freeSlot < limit; ++freeSlot ) { + XMP_Uns64 freeSize = spaceList[freeSlot].size; + if ( (newSize == freeSize) || ( (newSize < freeSize) && ((freeSize - newSize) >= 8) ) ) break; + } + + if ( freeSlot == spaceList.size() ) { + + // No available free space, append the new box. + CheckFinalBox ( fileRef, abortProc, abortArg ); + fileRef->ToEOF(); + fileRef->Write ( newBox, newSize ); + WipeBoxFree ( fileRef, oldOffset, oldSize ); + + } else { + + // Use the available free space. Wipe non-overlapping parts of the old box. The old + // box is either included in the new space, or is fully disjoint. + + SpaceInfo & newSpace = spaceList[freeSlot]; + + bool oldIsDisjoint = ((oldOffset + oldSize) <= newSpace.offset) || // Old is in front. + ((newSpace.offset + newSpace.size) <= oldOffset); // Old is behind. + + XMP_Assert ( (newSize == newSpace.size) || + ( (newSize < newSpace.size) && ((newSpace.size - newSize) >= 8) ) ); + + XMP_Assert ( oldIsDisjoint || + ( (newSpace.offset <= oldOffset) && + ((oldOffset + oldSize) <= (newSpace.offset + newSpace.size)) ) /* old is included */ ); + + XMP_Uns64 newFreeOffset = newSpace.offset + newSize; + XMP_Uns64 newFreeSize = newSpace.size - newSize; + + fileRef->Seek ( newSpace.offset, kXMP_SeekFromStart ); + fileRef->Write ( newBox, newSize ); + + if ( newFreeSize > 0 ) WriteBoxHeader ( fileRef, ISOMedia::k_free, newFreeSize ); + + if ( oldIsDisjoint ) { + + WipeBoxFree ( fileRef, oldOffset, oldSize ); + + } else { + + // Clear the exposed portion of the old box. + + XMP_Uns64 zeroStart = newFreeOffset + 8; + if ( newFreeSize > 0xFFFFFFFF ) zeroStart += 8; + if ( oldOffset > zeroStart ) zeroStart = oldOffset; + XMP_Uns64 zeroEnd = newFreeOffset + newFreeSize; + if ( (oldOffset + oldSize) < zeroEnd ) zeroEnd = oldOffset + oldSize; + + if ( zeroStart < zeroEnd ) { // The new box might cover the old. + XMP_Assert ( (zeroEnd - zeroStart) <= (XMP_Uns64)oldSize ); + XMP_Uns32 zeroSize = (XMP_Uns32) (zeroEnd - zeroStart); + fileRef->Seek ( zeroStart, kXMP_SeekFromStart ); + for ( XMP_Uns32 ioCount = sizeof ( kZeroes ); zeroSize > 0; zeroSize -= ioCount ) { + if ( ioCount > zeroSize ) ioCount = zeroSize; + fileRef->Write ( &kZeroes[0], ioCount ); + } + } + + } + + } + + } + + } + +} // MPEG4_MetaHandler::UpdateTopLevelBox + +// ================================================================================================= +// AdjustOffset +// ============ +// +// A utility for OptimizeFileLayout, adjusts a 'stco' or 'co64' table entry for the new layout. The +// map is keyed by the original box's last content offset, so that map.lower_bound does what we want. + +struct LayoutInfo { + XMP_Uns32 boxType; + XMP_Uns64 boxSize; // The full size, including the header. + XMP_Uns64 oldOffset, newOffset; + LayoutInfo() : boxType(0), boxSize(0), oldOffset(0), newOffset(0) {}; + LayoutInfo ( XMP_Uns32 type, XMP_Uns64 size, XMP_Uns64 offset ) + : boxType(type), boxSize(size), oldOffset(offset), newOffset(0) {}; +}; + +typedef std::vector < LayoutInfo > LayoutVector; +typedef std::map < XMP_Uns64, LayoutInfo* > LayoutMap; + +static XMP_Uns64 AdjustOffset ( XMP_Uns64 oldOffset, const LayoutMap & newMap , GenericErrorCallback * ec) +{ + + LayoutMap::const_iterator mapEntry = newMap.lower_bound ( oldOffset ); + if ( (mapEntry == newMap.end()) || (oldOffset < mapEntry->second->oldOffset) ) { + XMP_Error error ( kXMPErr_BadFileFormat,"Offset from 'stco' or 'co64' is not into kept box" ); + XMPFileHandler::NotifyClient(ec, kXMPErrSev_FileFatal, error); + } + + XMP_Assert ( (mapEntry->second->oldOffset <= oldOffset) && + (oldOffset <= (mapEntry->second->oldOffset + mapEntry->second->boxSize)) ); + + return mapEntry->second->newOffset + (oldOffset - mapEntry->second->oldOffset); + +} // AdjustOffset + +// ================================================================================================= +// MPEG4_MetaHandler::OptimizeFileLayout +// ===================================== +// +// Make sure the file is acceptable for streaming use: the 'moov' and XMP 'uuid' boxes must be +// before any 'mdat' box, other top level boxes after 'mdat' are accepted. If the file needs +// optimization, it is fully rewritten in this order: 'ftyp' (if ISO), 'moov', XMP 'uuid', other +// non-'mdat', all 'mdat' boxes. Top level 'free' and 'skip' boxes will be removed. Offsets in the +// 'stco' and 'co64' boxes will be adjusted. + +void MPEG4_MetaHandler::OptimizeFileLayout() +{ + XMP_IO* originalFile = this->parent->ioRef; + XMP_Uns64 originalSize = originalFile->Length(); + + XMP_AbortProc abortProc = parent->abortProc; + void * abortArg = parent->abortArg; + const bool checkAbort = (abortProc != 0); + + XMP_Uns64 currPos, nextPos; + ISOMedia::BoxInfo currBox; + + size_t boxCount = 0; + size_t moovIndex = 0, xmpIndex = 0; + + // Go through the top level boxes to see if the file layout needs to be optimized. Look until + // we find both the 'moov' and XMP 'uuid' boxes, saving their relative index in the file. + + bool needsOptimization = false; + bool moovFound = false, xmpFound = false, mdatFound = false; + + for ( currPos = 0; currPos < originalSize; currPos = nextPos ) { + + nextPos = ISOMedia::GetBoxInfo ( originalFile, currPos, originalSize, &currBox ); + if ( (currBox.boxType == ISOMedia::k_free) || + (currBox.boxType == ISOMedia::k_skip) || + (currBox.boxType == ISOMedia::k_wide) ) continue; + + ++boxCount; // ! Must be counted for all, continue statements below skip an end of loop increment. + + if ( currBox.boxType == ISOMedia::k_mdat ) { + + mdatFound = true; + XMP_Assert ( (! moovFound) | (! xmpFound) ); // The other cases should be exiting. + + } else if ( currBox.boxType == ISOMedia::k_moov ) { + + moovFound = true; + moovIndex = boxCount-1; // Need later for optimization. + needsOptimization = mdatFound; + if ( xmpFound ) break; // Don't need to look further. + + } else if ( currBox.boxType == ISOMedia::k_uuid && ( memcmp( currBox.idUUID, ISOMedia::k_xmpUUID, 16 ) == 0 ) ) { + + xmpFound = true; + xmpIndex = boxCount-1; // Need later for optimization. + needsOptimization = mdatFound; + if ( moovFound ) break; // Don't need to look further. + + } + + } + + if ( ! needsOptimization ) return; + + // The file needs to be optimized. Make sure that a file over 4 GB has 'co64', not 'stco' boxes. + // These are needed to hold 64-bit offsets. We don't go to the effort of changing from 'stco' + // to 'co64', the file needs to be OK from the start. (Yes, this eliminates a marginal case of + // a file growing beyond 4 GB due to metadata growth.) + + if ( originalSize >= 0xFFFFFFFF ) { + + MOOV_Manager::BoxRef moovRef, trakRef, tempRef, stcoRef; + MOOV_Manager::BoxInfo boxInfo; + + moovRef = this->moovMgr.GetBox ( "moov", &boxInfo ); + XMP_Enforce ( moovRef != 0 ); + + for ( size_t i = 0, limit = boxInfo.childCount; i < limit; ++i ) { + + trakRef = this->moovMgr.GetNthChild ( moovRef, i, &boxInfo ); + if ( boxInfo.boxType != ISOMedia::k_trak ) continue; + + tempRef = this->moovMgr.GetTypeChild ( trakRef, ISOMedia::k_mdia, 0 ); + if ( tempRef == 0 ) continue; + tempRef = this->moovMgr.GetTypeChild ( tempRef, ISOMedia::k_minf, 0 ); + if ( tempRef == 0 ) continue; + tempRef = this->moovMgr.GetTypeChild ( tempRef, ISOMedia::k_stbl, 0 ); + if ( tempRef == 0 ) continue; + + stcoRef = this->moovMgr.GetTypeChild ( tempRef, ISOMedia::k_stco, 0 ); + if ( stcoRef != 0 ) { + XMP_Error error ( kXMPErr_BadFileFormat,"Large MPEG-4 file must use 'co64' boxes" ); + XMPFileHandler::NotifyClient(&parent->errorCallback, kXMPErrSev_FileFatal, error); + } + + } + + } + + // Build a vector of info for the top level boxes, ignoring 'free', 'skip', and 'wide' boxes. + // Then determine the new offsets and create a map keyed by the new offset. + + // ! The box indices saved in the prior loop must match those in the vector built here! + + LayoutVector fileBoxes; + LayoutMap optLayout; + + for ( currPos = 0; currPos < originalSize; currPos = nextPos ) { + nextPos = ISOMedia::GetBoxInfo ( originalFile, currPos, originalSize, &currBox ); + if ( (currBox.boxType == ISOMedia::k_free) || + (currBox.boxType == ISOMedia::k_skip) || + (currBox.boxType == ISOMedia::k_wide) ) continue; + --boxCount; // For sanity check below. + fileBoxes.push_back ( LayoutInfo ( currBox.boxType, (currBox.headerSize + currBox.contentSize), currPos ) ); + } + + XMP_Assert ( boxCount == 0 ); // Must get the same count in both loops. + XMP_Assert ( fileBoxes.size() >= 2 ); // At least 'mdat', and 'moov' or XMP 'uuid'. + XMP_Assert ( (!moovFound) || (fileBoxes[moovIndex].boxType == ISOMedia::k_moov) ); + XMP_Assert ( (!xmpFound) || (fileBoxes[xmpIndex].boxType == ISOMedia::k_uuid) ); + + size_t currIndex = 0, limit = fileBoxes.size(); + XMP_Uns64 newSize = 0; + + if ( fileBoxes[0].boxType == ISOMedia::k_ftyp ) { + optLayout.insert ( optLayout.end(), LayoutMap::value_type ( 0, &fileBoxes[0] ) ); + newSize = fileBoxes[0].boxSize; + currIndex = 1; // Keep the 'ftyp' box in front. + } + + if ( moovFound ) { + optLayout.insert ( optLayout.end(), LayoutMap::value_type ( newSize, &fileBoxes[moovIndex] ) ); + fileBoxes[moovIndex].newOffset = newSize; + newSize += fileBoxes[moovIndex].boxSize; + } + + if ( xmpFound ) { + optLayout.insert ( optLayout.end(), LayoutMap::value_type ( newSize, &fileBoxes[xmpIndex] ) ); + fileBoxes[xmpIndex].newOffset = newSize; + newSize += fileBoxes[xmpIndex].boxSize; + } + + for ( ; currIndex < limit; ++currIndex ) { // Add all of the other non-'mdat' boxes to the map. + if ( moovFound && (currIndex == moovIndex) ) continue; + if ( xmpFound && (currIndex == xmpIndex) ) continue; + if ( fileBoxes[currIndex].boxType == ISOMedia::k_mdat ) continue; + optLayout.insert ( optLayout.end(), LayoutMap::value_type ( newSize, &fileBoxes[currIndex] ) ); + fileBoxes[currIndex].newOffset = newSize; + newSize += fileBoxes[currIndex].boxSize; + } + + for ( currIndex = 0; currIndex < limit; ++currIndex ) { // Add all of the 'mdat' boxes to the map. + if ( fileBoxes[currIndex].boxType != ISOMedia::k_mdat ) continue; + optLayout.insert ( optLayout.end(), LayoutMap::value_type ( newSize, &fileBoxes[currIndex] ) ); + fileBoxes[currIndex].newOffset = newSize; + newSize += fileBoxes[currIndex].boxSize; + } + + // Adjust the progress tracking if necessary. + + XMP_ProgressTracker * progressTracker = this->parent->progressTracker; + if ( progressTracker != 0 ) { + XMP_Assert ( progressTracker->WorkInProgress() ); + progressTracker->AddTotalWork ( (float) newSize ); + } + + // Create a temp file for the optimized layout, write it, update the offset tables. + + XMP_IO* tempFile = originalFile->DeriveTemp(); + XMP_Enforce ( tempFile != 0 ); + + // Iterate the map and write the new layout. + + LayoutMap::iterator layoutPos = optLayout.begin(); + LayoutMap::iterator layoutEnd = optLayout.end(); + + for ( ; layoutPos != layoutEnd; ++layoutPos ) { + LayoutInfo * currBox = layoutPos->second; + XMP_Assert ( (XMP_Int64)currBox->newOffset == tempFile->Length() ); + originalFile->Seek ( currBox->oldOffset, kXMP_SeekFromStart ); + XIO::Copy ( originalFile, tempFile, currBox->boxSize, abortProc, abortArg ); + } + + // Update the offset tables in the temp file. Create a layout map ordered by the last actual + // offset of the old box's content to enable fast lookup within AdjustOffset. + + LayoutMap oldEndMap; + for ( size_t i = 0, limit = fileBoxes.size(); i < limit; ++i ) { + XMP_Uns64 oldEnd = fileBoxes[i].oldOffset + fileBoxes[i].boxSize - 1; // ! Want the last actual offset! + oldEndMap.insert ( oldEndMap.end(), LayoutMap::value_type ( oldEnd, &fileBoxes[i] ) ); + } + + MOOV_Manager::BoxRef moovRef, trakRef, tempRef, stcoRef, co64Ref; + MOOV_Manager::BoxInfo boxInfo; + + moovRef = this->moovMgr.GetBox ( "moov", &boxInfo ); + XMP_Enforce ( moovRef != 0 ); + + for ( size_t i = 0, limit = boxInfo.childCount; i < limit; ++i ) { + + trakRef = this->moovMgr.GetNthChild ( moovRef, i, &boxInfo ); + if ( boxInfo.boxType != ISOMedia::k_trak ) continue; + + tempRef = this->moovMgr.GetTypeChild ( trakRef, ISOMedia::k_mdia, 0 ); + if ( tempRef == 0 ) continue; + tempRef = this->moovMgr.GetTypeChild ( tempRef, ISOMedia::k_minf, 0 ); + if ( tempRef == 0 ) continue; + tempRef = this->moovMgr.GetTypeChild ( tempRef, ISOMedia::k_stbl, 0 ); + if ( tempRef == 0 ) continue; + + co64Ref = 0; + XMP_Uns32 entrySize = 4; + stcoRef = this->moovMgr.GetTypeChild ( tempRef, ISOMedia::k_stco, &boxInfo ); + if ( stcoRef == 0 ) { + co64Ref = this->moovMgr.GetTypeChild ( tempRef, ISOMedia::k_co64, &boxInfo ); + if ( co64Ref == 0 ) continue; + entrySize = 8; + } + + XMP_Uns32 offsetCount = GetUns32BE ( boxInfo.content + 4 ); + if ( boxInfo.contentSize < (4+4 + entrySize*offsetCount) ) { + XMP_Error error ( kXMPErr_BadFileFormat, "Bad 'stco' size or count" ); + XMPFileHandler::NotifyClient(&parent->errorCallback, kXMPErrSev_FileFatal, error); + } + + if ( stcoRef != 0 ) { + + XMP_Uns64 stcoTableOffset = fileBoxes[moovIndex].newOffset + + (XMP_Uns64) this->moovMgr.GetParsedOffset ( stcoRef ) + + (XMP_Uns64) this->moovMgr.GetHeaderSize ( stcoRef ) + 4+4; + tempFile->Seek ( stcoTableOffset, kXMP_SeekFromStart ); + + XMP_Uns32 * rawOldU32 = (XMP_Uns32*) (boxInfo.content + 4+4); + for ( XMP_Uns32 i = 0; i < offsetCount; ++i, ++rawOldU32 ) { + XMP_Uns64 newOffset = AdjustOffset ( (XMP_Uns64)GetUns32BE(rawOldU32), oldEndMap,&parent->errorCallback ); + XMP_Uns32 u32 = MakeUns32BE ( (XMP_Uns32)newOffset ); + tempFile->Write ( &u32, 4 ); + } + + } else { + + XMP_Uns64 co64TableOffset = fileBoxes[moovIndex].newOffset + + (XMP_Uns64) this->moovMgr.GetParsedOffset ( co64Ref ) + + (XMP_Uns64) this->moovMgr.GetHeaderSize ( co64Ref ) + 4+4; + tempFile->Seek ( co64TableOffset, kXMP_SeekFromStart ); + + XMP_Uns64 * rawOldU64 = (XMP_Uns64*) (boxInfo.content + 4+4); + for ( XMP_Uns32 i = 0; i < offsetCount; ++i, ++rawOldU64 ) { + XMP_Uns64 newOffset = AdjustOffset ( GetUns64BE(rawOldU64), oldEndMap,&parent->errorCallback ); + XMP_Uns64 u64 = MakeUns64BE ( newOffset ); + tempFile->Write ( &u64, 8 ); + } + + } + + } + + // Swap the temp and original files. + + originalFile->AbsorbTemp(); + +} // MPEG4_MetaHandler::OptimizeFileLayout + +// ================================================================================================= +// MPEG4_MetaHandler::UpdateFile +// ============================= +// +// Revamp notes: +// The 'moov' subtree and possibly the XMP 'uuid' box get updated. Compose the new copy of each and +// see if it fits in existing space, incorporating adjacent 'free' boxes if necessary. If that won't +// work, look for a sufficient 'free' box anywhere in the file. As a last resort, append the new copy. +// Assume no location sensitive data within 'moov', i.e. no offsets into it. This lets it be moved +// and its children freely rearranged. + +void MPEG4_MetaHandler::UpdateFile ( bool doSafeUpdate ) +{ + + bool optimizeFileLayout = false; + if ( this->parent) + { + optimizeFileLayout = XMP_OptionIsSet ( this->parent->openFlags, kXMPFiles_OptimizeFileLayout ); + } + + if ( ! this->needsUpdate ) { // If needsUpdate is set then at least the XMP changed. + if ( optimizeFileLayout ) this->OptimizeFileLayout(); + return; + } + + this->needsUpdate = false; // Make sure only called once. + XMP_Assert ( ! doSafeUpdate ); // This should only be called for "unsafe" updates. + + XMP_AbortProc abortProc = this->parent->abortProc; + void * abortArg = this->parent->abortArg; + const bool checkAbort = (abortProc != 0); + + XMP_IO* fileRef = this->parent->ioRef; + XMP_Uns64 fileSize = fileRef->Length(); + + bool haveISOFile = (this->fileMode == MOOV_Manager::kFileIsNormalISO); + + // Update the 'moov' subtree with exports from the XMP, but not the XMP itself (for QT files). + + ExportMVHDItems ( this->xmpObj, &this->moovMgr ); + ExportISOCopyrights ( this->xmpObj, &this->moovMgr ); + ExportQuickTimeItems ( this->xmpObj, &this->tradQTMgr, &this->moovMgr ); + ExportTimecodeItems ( this->xmpObj, &this->tmcdInfo, &this->tradQTMgr, &this->moovMgr ); + + if ( ! haveISOFile ) ExportCr8rItems ( this->xmpObj, &this->moovMgr ); + + // Set up progress tracking if necessary. At this point just include the XMP size, we don't + // know the 'moov' box size until later. + + bool localProgressTracking = false; + XMP_ProgressTracker* progressTracker = this->parent->progressTracker; + if ( progressTracker != 0 ) { + float xmpSize = (float)this->xmpPacket.size(); + if ( progressTracker->WorkInProgress() ) { + progressTracker->AddTotalWork ( xmpSize ); + } else { + localProgressTracking = true; + progressTracker->BeginWork ( xmpSize ); + } + } + + // Try to update the XMP in-place if that is all that changed, or if it is in a preferred 'uuid' box. + // The XMP has already been serialized by common code to the appropriate length. Otherwise, update + // the 'moov'/'udta'/'XMP_' box in the MOOV_Manager, or the 'uuid' XMP box in the file. + + bool useUuidXMP = (this->fileMode == MOOV_Manager::kFileIsNormalISO); + bool inPlaceXMP = (this->xmpPacket.size() == (size_t)this->packetInfo.length) && + ( (useUuidXMP & this->havePreferredXMP) || (! this->moovMgr.IsChanged()) ); + + + if ( inPlaceXMP ) { + + // Update the existing XMP in-place. + fileRef->Seek ( this->packetInfo.offset, kXMP_SeekFromStart ); + fileRef->Write ( this->xmpPacket.c_str(), (XMP_Int32)this->xmpPacket.size() ); + + } else if ( useUuidXMP ) { + + // Don't leave an old 'moov'/'udta'/'XMP_' box around. + MOOV_Manager::BoxRef udtaRef = this->moovMgr.GetBox ( "moov/udta", 0 ); + if ( udtaRef != 0 ) this->moovMgr.DeleteTypeChild ( udtaRef, ISOMedia::k_XMP_ ); + + } else { + + // Don't leave an old uuid XMP around (if we know about it). + if ( (! havePreferredXMP) && (this->xmpBoxSize != 0) ) { + WipeBoxFree ( fileRef, this->xmpBoxPos, this->xmpBoxSize ); + } + + // The udta form of XMP has just the XMP packet. + this->moovMgr.SetBox ( "moov/udta/XMP_", this->xmpPacket.c_str(), (XMP_Uns32)this->xmpPacket.size() ); + + } + + // Update the 'moov' subtree if necessary, and finally update the timecode sample. + + if ( this->moovMgr.IsChanged() ) { + this->moovMgr.UpdateMemoryTree(); + if ( progressTracker != 0 ) { + progressTracker->AddTotalWork ( (float)this->moovMgr.fullSubtree.size() ); + } + this->UpdateTopLevelBox ( moovBoxPos, moovBoxSize, &this->moovMgr.fullSubtree[0], + (XMP_Uns32)this->moovMgr.fullSubtree.size() ); + } + + if ( this->tmcdInfo.sampleOffset != 0 ) { + fileRef->Seek ( this->tmcdInfo.sampleOffset, kXMP_SeekFromStart ); + XMP_Uns32 sample = MakeUns32BE ( this->tmcdInfo.timecodeSample ); + fileRef->Write ( &sample, 4 ); + } + + // Update the 'uuid' XMP box if necessary. + + if ( useUuidXMP & (! inPlaceXMP) ) { + + // The uuid form of XMP has the 16-byte UUID in front of the XMP packet. Form the complete + // box (including size/type header) for UpdateTopLevelBox. + RawDataBlock uuidBox; + XMP_Uns32 uuidSize = 4 + 4 + 16 + (XMP_Uns32)this->xmpPacket.size(); + uuidBox.assign ( uuidSize, 0 ); + PutUns32BE ( uuidSize, &uuidBox[0] ); + PutUns32BE ( ISOMedia::k_uuid, &uuidBox[4] ); + memcpy ( &uuidBox[8], ISOMedia::k_xmpUUID, 16 ); + memcpy ( &uuidBox[24], this->xmpPacket.c_str(), this->xmpPacket.size() ); + this->UpdateTopLevelBox ( this->xmpBoxPos, this->xmpBoxSize, &uuidBox[0], uuidSize ); + + } + + // Finally, optimize the file layout if asked. + if ( optimizeFileLayout ) this->OptimizeFileLayout(); + + if ( localProgressTracking ) progressTracker->WorkComplete(); + +} // MPEG4_MetaHandler::UpdateFile + +// ================================================================================================= +// MPEG4_MetaHandler::WriteTempFile +// ================================ +// +// Since the XMP and legacy is probably a miniscule part of the entire file, and since we can't +// change the offset of most of the boxes, just copy the entire original file to the temp file, then +// do an in-place update to the temp file. + +void MPEG4_MetaHandler::WriteTempFile ( XMP_IO* tempRef ) +{ + XMP_Assert ( this->needsUpdate ); + + XMP_IO* originalRef = this->parent->ioRef; + XMP_ProgressTracker* progressTracker = this->parent->progressTracker; + + tempRef->Rewind(); + originalRef->Rewind(); + if ( progressTracker != 0 ) progressTracker->BeginWork ( (float) originalRef->Length() ); + XIO::Copy ( originalRef, tempRef, originalRef->Length(), + this->parent->abortProc, this->parent->abortArg ); + + try { + this->parent->ioRef = tempRef; // ! Fool UpdateFile into using the temp file. + this->UpdateFile ( false ); + this->parent->ioRef = originalRef; + } catch ( ... ) { + this->parent->ioRef = originalRef; + throw; + } + + if ( progressTracker != 0 ) progressTracker->WorkComplete(); + +} // MPEG4_MetaHandler::WriteTempFile + +// ================================================================================================= diff --git a/core/libs/dngwriter/extra/xmp_sdk/XMPFiles/source/FileHandlers/MPEG4_Handler.hpp b/core/libs/dngwriter/extra/xmp_sdk/XMPFiles/source/FileHandlers/MPEG4_Handler.hpp new file mode 100644 index 0000000000..9ad06bbe90 --- /dev/null +++ b/core/libs/dngwriter/extra/xmp_sdk/XMPFiles/source/FileHandlers/MPEG4_Handler.hpp @@ -0,0 +1,98 @@ +#ifndef __MPEG4_Handler_hpp__ +#define __MPEG4_Handler_hpp__ 1 + +// ================================================================================================= +// ADOBE SYSTEMS INCORPORATED +// Copyright 2006 Adobe Systems Incorporated +// All Rights Reserved +// +// NOTICE: Adobe permits you to use, modify, and distribute this file in accordance with the terms +// of the Adobe license agreement accompanying it. +// ================================================================================================= + +#include "XMPFiles/source/XMPFiles_Impl.hpp" + +#include "XMPFiles/source/FormatSupport/MOOV_Support.hpp" +#include "XMPFiles/source/FormatSupport/QuickTime_Support.hpp" + +// ================================================================================================ +/// \file MPEG4_Handler.hpp +/// \brief File format handler for MPEG-4. +/// +/// This header ... +/// +// ================================================================================================ + +extern XMPFileHandler * MPEG4_MetaHandlerCTor ( XMPFiles * parent ); + +extern bool MPEG4_CheckFormat ( XMP_FileFormat format, + XMP_StringPtr filePath, + XMP_IO* fileRef, + XMPFiles * parent ); + +static const XMP_OptionBits kMPEG4_HandlerFlags = ( kXMPFiles_CanInjectXMP | + kXMPFiles_CanExpand | + kXMPFiles_CanRewrite | + kXMPFiles_PrefersInPlace | + kXMPFiles_CanReconcile | + kXMPFiles_AllowsOnlyXMP | + kXMPFiles_ReturnsRawPacket | + kXMPFiles_AllowsSafeUpdate | + kXMPFiles_CanNotifyProgress + ); + +class MPEG4_MetaHandler : public XMPFileHandler +{ +public: + + void CacheFileData(); + void ProcessXMP(); + + void UpdateFile ( bool doSafeUpdate ); + void WriteTempFile ( XMP_IO* tempRef ); + + + MPEG4_MetaHandler ( XMPFiles * _parent ); + virtual ~MPEG4_MetaHandler(); + + struct TimecodeTrackInfo { // Info about a QuickTime timecode track. + bool stsdBoxFound, isDropFrame; + XMP_Uns32 timeScale; + XMP_Uns32 frameDuration; + XMP_Uns32 timecodeSample; + XMP_Uns64 sampleOffset; // Absolute file offset of the timecode sample, 0 if none. + XMP_Uns32 nameOffset; // The offset of the 'name' box relative to the 'stsd' box content. + XMP_Uns16 macLang; // The Mac language code of the trailing 'name' box. + std::string macName; // The text part of the trailing 'name' box, in macLang encoding. + TimecodeTrackInfo() + : stsdBoxFound(false), isDropFrame(false), timeScale(0), frameDuration(0), + timecodeSample(0), sampleOffset(0), nameOffset(0), macLang(0) {}; + }; + +private: + + MPEG4_MetaHandler() : fileMode(0), havePreferredXMP(false), + xmpBoxPos(0), moovBoxPos(0), xmpBoxSize(0), moovBoxSize(0) {}; // Hidden on purpose. + + bool ParseTimecodeTrack(); + + void UpdateTopLevelBox ( XMP_Uns64 oldOffset, XMP_Uns32 oldSize, const XMP_Uns8 * newBox, XMP_Uns32 newSize ); + + void OptimizeFileLayout(); + + XMP_Uns8 fileMode; + bool havePreferredXMP; + XMP_Uns64 xmpBoxPos; // The file offset of the XMP box (the size field, not the content). + XMP_Uns64 moovBoxPos; // The file offset of the 'moov' box (the size field, not the content). + XMP_Uns32 xmpBoxSize, moovBoxSize; // The full size of the boxes, not just the content. + + MOOV_Manager moovMgr; + TradQT_Manager tradQTMgr; + + TimecodeTrackInfo tmcdInfo; + +}; // MPEG4_MetaHandler + +// ================================================================================================= + +#endif // __MPEG4_Handler_hpp__ diff --git a/core/libs/dngwriter/extra/xmp_sdk/XMPFiles/source/FileHandlers/P2_Handler.cpp b/core/libs/dngwriter/extra/xmp_sdk/XMPFiles/source/FileHandlers/P2_Handler.cpp new file mode 100644 index 0000000000..982744d86d --- /dev/null +++ b/core/libs/dngwriter/extra/xmp_sdk/XMPFiles/source/FileHandlers/P2_Handler.cpp @@ -0,0 +1,1393 @@ +// ================================================================================================= +// ADOBE SYSTEMS INCORPORATED +// Copyright 2007 Adobe Systems Incorporated +// All Rights Reserved +// +// NOTICE: Adobe permits you to use, modify, and distribute this file in accordance with the terms +// of the Adobe license agreement accompanying it. +// ================================================================================================= + +#include "public/include/XMP_Environment.h" // ! XMP_Environment.h must be the first included header. + +#include "public/include/XMP_Const.h" +#include "public/include/XMP_IO.hpp" + +#include "XMPFiles/source/XMPFiles_Impl.hpp" +#include "source/XMPFiles_IO.hpp" +#include "source/XIO.hpp" +#include "source/IOUtils.hpp" + +#include "XMPFiles/source/FileHandlers/P2_Handler.hpp" +#include "XMPFiles/source/FormatSupport/P2_Support.hpp" +#include "XMPFiles/source/FormatSupport/PackageFormat_Support.hpp" + +#include +#include + +using namespace std; + +// ================================================================================================= +/// \file P2_Handler.cpp +/// \brief Folder format handler for P2. +/// +/// This handler is for the P2 video format. This is a pseudo-package, visible files but with a very +/// well-defined layout and naming rules. A typical P2 example looks like: +/// +/// .../MyMovie +/// CONTENTS/ +/// CLIP/ +/// 0001AB.XML +/// 0001AB.XMP +/// 0002CD.XML +/// 0002CD.XMP +/// VIDEO/ +/// 0001AB.MXF +/// 0002CD.MXF +/// AUDIO/ +/// 0001AB00.MXF +/// 0001AB01.MXF +/// 0002CD00.MXF +/// 0002CD01.MXF +/// ICON/ +/// 0001AB.BMP +/// 0002CD.BMP +/// VOICE/ +/// 0001AB.WAV +/// 0002CD.WAV +/// PROXY/ +/// 0001AB.MP4 +/// 0002CD.MP4 +/// +/// From the user's point of view, .../MyMovie contains P2 stuff, in this case 2 clips whose raw +/// names are 0001AB and 0002CD. There may be mapping information for nicer clip names to the raw +/// names, but that can be ignored for now. Each clip is stored as a collection of files, each file +/// holding some specific aspect of the clip's data. +/// +/// The P2 handler operates on clips. The path from the client of XMPFiles can be either a logical +/// clip path, like ".../MyMovie/0001AB", or a full path to one of the files. In the latter case the +/// handler must figure out the intended clip, it must not blindly use the named file. +/// +/// Once the P2 structure and intended clip are identified, the handler only deals with the .XMP and +/// .XML files in the CLIP folder. The .XMP file, if present, contains the XMP for the clip. The .XML +/// file must be present to define the existance of the clip. It contains a variety of information +/// about the clip, including some legacy metadata. +/// +// ================================================================================================= + +static const char * kContentFolderNames[] = { "CLIP", "VIDEO", "AUDIO", "ICON", "VOICE", "PROXY", 0 }; +static int kNumRequiredContentFolders = 6; // All 6 of the above. + +static inline bool CheckContentFolderName ( const std::string & folderName ) +{ + for ( int i = 0; kContentFolderNames[i] != 0; ++i ) { + if ( folderName == kContentFolderNames[i] ) return true; + } + return false; +} + +// ================================================================================================= +// InternalMakeClipFilePath +// ======================== +// +// P2_CheckFormat can't use the member function. + +static void InternalMakeClipFilePath ( std::string * path, + const std::string & rootPath, + const std::string & clipName, + XMP_StringPtr suffix ) +{ + + *path = rootPath; + *path += kDirChar; + *path += "CONTENTS"; + *path += kDirChar; + *path += "CLIP"; + *path += kDirChar; + *path += clipName; + *path += suffix; + +} // InternalMakeClipFilePath + +// ================================================================================================= +// P2_CheckFormat +// ============== +// +// This version does fairly simple checks. The top level folder (.../MyMovie) must have a child +// folder called CONTENTS. This must have a subfolder called CLIP. It may also have subfolders +// called VIDEO, AUDIO, ICON, VOICE, and PROXY. Any mixture of these additional folders is allowed, +// but no other children are allowed in CONTENTS. The CLIP folder must contain a .XML file for the +// desired clip. The name checks are case insensitive. +// +// The state of the string parameters depends on the form of the path passed by the client. If the +// client passed a logical clip path, like ".../MyMovie/0001AB", the parameters are: +// rootPath - ".../MyMovie" +// gpName - empty +// parentName - empty +// leafName - "0001AB" +// If the client passed a full file path, like ".../MyMovie/CONTENTS/VOICE/0001AB.WAV", they are: +// rootPath - ".../MyMovie" +// gpName - "CONTENTS" +// parentName - "VOICE" +// leafName - "0001AB" +// +// For most of the content files the base file name is the raw clip name. Files in the AUDIO and +// VOICE folders have an extra 2 digits appended to the raw clip name. These must be trimmed. + +// ! The common code has shifted the gpName, parentName, and leafName strings to upper case. It has +// ! also made sure that for a logical clip path the rootPath is an existing folder, and that the +// ! file exists for a full file path. + +bool P2_CheckFormat ( XMP_FileFormat format, + const std::string & rootPath, + const std::string & gpName, + const std::string & parentName, + const std::string & leafName, + XMPFiles * parent ) +{ + Host_IO::AutoFolder aFolder; + std::string tempPath, childName; + + std::string clipName = leafName; + + // Do some basic checks on the gpName and parentName. + + if ( gpName.empty() != parentName.empty() ) return false; // Must be both empty or both non-empty. + + if ( ! gpName.empty() ) { + + if ( gpName != "CONTENTS" ) return false; + if ( ! CheckContentFolderName ( parentName ) ) return false; + + if ( (parentName == "AUDIO") | (parentName == "VOICE") ) { + if ( clipName.size() < 3 ) return false; + clipName.erase ( clipName.size() - 2 ); + } + + } + + tempPath = rootPath; + tempPath += kDirChar; + tempPath += "CONTENTS"; + if ( Host_IO::GetFileMode ( tempPath.c_str() ) != Host_IO::kFMode_IsFolder ) return false; + + aFolder.folder = Host_IO::OpenFolder ( tempPath.c_str() ); + int numChildrenFound = 0; + std::string childPath; + + while ( ( Host_IO::GetNextChild ( aFolder.folder, &childName ) && ( numChildrenFound < kNumRequiredContentFolders ) ) ) { // Make sure the children of CONTENTS are legit. + if ( CheckContentFolderName ( childName ) ) { + childPath = tempPath; + childPath += kDirChar; + childPath += childName; + if ( Host_IO::GetFileMode ( childPath.c_str() ) != Host_IO::kFMode_IsFolder ) return false; + ++numChildrenFound; + } + } + aFolder.Close(); + + // Make sure the clip's .XML file exists. + + InternalMakeClipFilePath ( &tempPath, rootPath, clipName, ".XML" ); + if ( Host_IO::GetFileMode ( tempPath.c_str() ) != Host_IO::kFMode_IsFile ) return false; + + // Make a bogus path to pass the root path and clip name to the handler. A bit of a hack, but + // the only way to get info from here to there. + + + tempPath = rootPath; + tempPath += kDirChar; + tempPath += clipName; + + size_t pathLen = tempPath.size() + 1; // Include a terminating nul. + parent->tempPtr = malloc ( pathLen ); + if ( parent->tempPtr == 0 ) XMP_Throw ( "No memory for P2 clip path", kXMPErr_NoMemory ); + memcpy ( parent->tempPtr, tempPath.c_str(), pathLen ); // AUDIT: Safe, allocated above. + + return true; + +} // P2_CheckFormat + +// ================================================================================================= + +static void* CreatePseudoClipPath ( const std::string & clientPath ) { + + // Used to create the clip pseudo path when the CheckFormat function is skipped. + + std::string pseudoPath = clientPath; + + size_t pathLen; + void* tempPtr = 0; + + if ( Host_IO::Exists ( pseudoPath.c_str() ) ) { + + // The client passed a physical path. The logical clip name is the leaf name, with the + // extension removed. Files in the AUDIO and VOICE folders have an extra 2 digits appended + // to the clip name. The movie root path ends two levels up. + + std::string clipName, parentName, ignored; + + XIO::SplitLeafName ( &pseudoPath, &clipName ); // Extract the logical clip name. + XIO::SplitFileExtension ( &clipName, &ignored ); + + XIO::SplitLeafName ( &pseudoPath, &parentName ); // Remove the 2 intermediate folder levels. + XIO::SplitLeafName ( &pseudoPath, &ignored ); + + if ( (parentName == "AUDIO") | (parentName == "VOICE") ) { + if ( clipName.size() >= 3 ) clipName.erase ( clipName.size() - 2 ); + } + + pseudoPath += kDirChar; + pseudoPath += clipName; + + } + + pathLen = pseudoPath.size() + 1; // Include a terminating nul. + tempPtr = malloc ( pathLen ); + if ( tempPtr == 0 ) XMP_Throw ( "No memory for P2 clip info", kXMPErr_NoMemory ); + memcpy ( tempPtr, pseudoPath.c_str(), pathLen ); + + return tempPtr; + +} // CreatePseudoClipPath + +// ================================================================================================= +// P2_MetaHandlerCTor +// ================== + +XMPFileHandler * P2_MetaHandlerCTor ( XMPFiles * parent ) +{ + return new P2_MetaHandler ( parent ); + +} // P2_MetaHandlerCTor + +// ================================================================================================= +// P2_MetaHandler::P2_MetaHandler +// ============================== + +P2_MetaHandler::P2_MetaHandler ( XMPFiles * _parent ) +{ + + this->parent = _parent; // Inherited, can't set in the prefix. + this->handlerFlags = kP2_HandlerFlags; + this->stdCharForm = kXMP_Char8Bit; + + // Extract the root path and clip name from tempPtr. + + if ( this->parent->tempPtr == 0 ) { + // The CheckFormat call might have been skipped. + this->parent->tempPtr = CreatePseudoClipPath ( this->parent->GetFilePath() ); + } + + this->rootPath.assign ( (char*) this->parent->tempPtr ); + free ( this->parent->tempPtr ); + this->parent->tempPtr = 0; + + XIO::SplitLeafName ( &this->rootPath, &this->clipName ); + + std::string xmlPath; + if ( this->MakeClipFilePath ( &xmlPath, ".XML", true ) ) + { + try + { + + p2ClipManager.ProcessClip(xmlPath); + std::string* clipnm = p2ClipManager.GetManagedClip()->GetClipName(); + if ( clipnm !=0 ) + { + std::string newpath,leafname; + newpath = p2ClipManager.GetManagedClip()->GetXMPFilePath(); + XIO::SplitLeafName(&newpath,&leafname); + if ( leafname == std::string(*clipnm+ ".XMP") ) + { + this->clipName=*clipnm; + } + } + } + catch(...) + { + } + } + +} // P2_MetaHandler::P2_MetaHandler + +// ================================================================================================= +// P2_MetaHandler::~P2_MetaHandler +// =============================== + +P2_MetaHandler::~P2_MetaHandler() +{ + + if ( this->parent->tempPtr != 0 ) { + free ( this->parent->tempPtr ); + this->parent->tempPtr = 0; + } + +} // P2_MetaHandler::~P2_MetaHandler + +// ================================================================================================= +// P2_MetaHandler::MakeClipFilePath +// ================================ + +bool P2_MetaHandler::MakeClipFilePath ( std::string * path, XMP_StringPtr suffix, bool checkFile /* = false */ ) +{ + + InternalMakeClipFilePath ( path, this->rootPath, this->clipName, suffix ); + if ( ! checkFile ) return true; + + return Host_IO::Exists ( path->c_str() ); + +} // P2_MetaHandler::MakeClipFilePath + + + +// ================================================================================================= +// P2_MetaHandler::SetXMPPropertyFromLegacyXML +// =========================================== + +void P2_MetaHandler::SetXMPPropertyFromLegacyXML ( bool digestFound, + XMP_VarString* refContext, + XMP_StringPtr schemaNS, + XMP_StringPtr propName, + bool isLocalized ) +{ + + if ( digestFound || (! this->xmpObj.DoesPropertyExist ( schemaNS, propName )) ) { + + if ( refContext !=0 ) + { + if ( isLocalized ) { + this->xmpObj.SetLocalizedText ( schemaNS, propName, "", "x-default", refContext->c_str(), kXMP_DeleteExisting ); + } else { + this->xmpObj.SetProperty ( schemaNS, propName, refContext->c_str(), kXMP_DeleteExisting ); + } + this->containsXMP = true; + } + } +} // P2_MetaHandler::SetXMPPropertyFromLegacyXML + +// ================================================================================================= +// P2_MetaHandler::SetXMPPropertyFromLegacyXML +// =========================================== +void P2_MetaHandler::SetXMPPropertyFromLegacyXML ( bool digestFound, + XML_NodePtr legacyContext, + XMP_StringPtr schemaNS, + XMP_StringPtr propName, + XMP_StringPtr legacyPropName, + bool isLocalized ) +{ + XMP_StringPtr p2NS = this->p2ClipManager.GetManagedClip()->GetP2RootNode()->ns.c_str(); + XML_NodePtr legacyProp = legacyContext->GetNamedElement ( p2NS, legacyPropName ); + + if ( (legacyProp != 0) && legacyProp->IsLeafContentNode() ) { + XMP_StringPtr legacyValue = legacyProp->GetLeafContentValue(); + + if ( ( legacyValue != 0 ) && + ( ( *legacyValue != 0 ) || (! this->xmpObj.DoesPropertyExist ( schemaNS, propName )) )) { + if ( isLocalized ) { + this->xmpObj.SetLocalizedText ( schemaNS, propName, "", "x-default", legacyValue, kXMP_DeleteExisting ); + } else { + this->xmpObj.SetProperty ( schemaNS, propName, legacyValue, kXMP_DeleteExisting ); + } + this->containsXMP = true; + } + } + +} +// ================================================================================================= +// P2_MetaHandler::SetRelationsFromLegacyXML +// ========================================= + +void P2_MetaHandler::SetRelationsFromLegacyXML ( bool digestFound ) +{ + + P2_Clip* p2Clip=this->p2ClipManager.GetManagedClip(); + + if ( digestFound || (! this->xmpObj.DoesPropertyExist ( kXMP_NS_DC, "relation" )) ) { + + XMP_VarString* globalShotId = p2Clip->GetShotId() ; + std::string relationString ; + if ( ( globalShotId != 0 ) ) { + + this->xmpObj.DeleteProperty ( kXMP_NS_DC, "relation" ); + relationString = std::string("globalShotID:") + *globalShotId ; + this->xmpObj.AppendArrayItem ( kXMP_NS_DC, "relation", kXMP_PropArrayIsUnordered, relationString ); + this->containsXMP = true; + + XMP_VarString* topId = p2Clip->GetTopClipId() ; + if ( topId != 0 ) { + relationString = std::string("topGlobalClipID:") + *topId ; + this->xmpObj.AppendArrayItem ( kXMP_NS_DC, "relation", kXMP_PropArrayIsUnordered, relationString ); + } + XMP_VarString* prevId = p2Clip->GetPreviousClipId() ; + if ( prevId != 0 ) { + relationString = std::string("previousGlobalClipID:") + *prevId ; + this->xmpObj.AppendArrayItem ( kXMP_NS_DC, "relation", kXMP_PropArrayIsUnordered, relationString ); + } + XMP_VarString* nextId = p2Clip->GetNextClipId() ; + if ( nextId != 0 ) { + relationString = std::string("nextGlobalClipID:") + *nextId ; + this->xmpObj.AppendArrayItem ( kXMP_NS_DC, "relation", kXMP_PropArrayIsUnordered, relationString ); + } + + } + + } + +} // P2_MetaHandler::SetRelationsFromLegacyXML + +// ================================================================================================= +// P2_MetaHandler::SetAudioInfoFromLegacyXML +// ========================================= + +void P2_MetaHandler::SetAudioInfoFromLegacyXML ( bool digestFound ) +{ + P2_Clip* p2Clip = this->p2ClipManager.GetManagedClip() ; + XMP_StringPtr p2NS = p2Clip->GetP2RootNode()->ns.c_str(); + XML_NodePtr legacyAudioContext = p2Clip->GetEssenceListNode(); + + if ( legacyAudioContext != 0 ) { + + legacyAudioContext = legacyAudioContext->GetNamedElement ( p2NS, "Audio" ); + + if ( legacyAudioContext != 0 ) { + + this->SetXMPPropertyFromLegacyXML ( digestFound, legacyAudioContext, kXMP_NS_DM, "audioSampleRate", "SamplingRate", false ); + + if ( digestFound || (! this->xmpObj.DoesPropertyExist ( kXMP_NS_DM, "audioSampleType" )) ) { + XML_NodePtr legacyProp = legacyAudioContext->GetNamedElement ( p2NS, "BitsPerSample" ); + + if ( (legacyProp != 0) && legacyProp->IsLeafContentNode() ) { + + const std::string p2BitsPerSample = legacyProp->GetLeafContentValue(); + std::string dmSampleType; + + if ( p2BitsPerSample == "16" ) { + dmSampleType = "16Int"; + } else if ( p2BitsPerSample == "24" ) { + dmSampleType = "32Int"; + } + + if ( ! dmSampleType.empty() ) { + this->xmpObj.SetProperty ( kXMP_NS_DM, "audioSampleType", dmSampleType, kXMP_DeleteExisting ); + this->containsXMP = true; + } + + } + + } + + } + + } + +} // P2_MetaHandler::SetAudioInfoFromLegacyXML + +// ================================================================================================= +// P2_MetaHandler::SetVideoInfoFromLegacyXML +// ========================================= + +void P2_MetaHandler::SetVideoInfoFromLegacyXML ( bool digestFound ) +{ + P2_Clip* p2Clip = this->p2ClipManager.GetManagedClip() ; + XMP_StringPtr p2NS = p2Clip->GetP2RootNode()->ns.c_str(); + XML_NodePtr legacyVideoContext = p2Clip->GetEssenceListNode(); + + if ( legacyVideoContext != 0 ) { + + legacyVideoContext = legacyVideoContext->GetNamedElement ( p2NS, "Video" ); + + if ( legacyVideoContext != 0 ) { + this->SetVideoFrameInfoFromLegacyXML ( legacyVideoContext, digestFound ); + this->SetStartTimecodeFromLegacyXML ( legacyVideoContext, digestFound ); + this->SetXMPPropertyFromLegacyXML ( digestFound, legacyVideoContext, kXMP_NS_DM, "videoFrameRate", "FrameRate", false ); + } + + } + +} // P2_MetaHandler::SetVideoInfoFromLegacyXML + +// ================================================================================================= +// P2_MetaHandler::SetDurationFromLegacyXML +// ======================================== + +void P2_MetaHandler::SetDurationFromLegacyXML ( bool digestFound ) +{ + + if ( digestFound || (! this->xmpObj.DoesPropertyExist ( kXMP_NS_DM, "duration" )) ) { + + P2_SpannedClip* p2Clip=this->p2ClipManager.GetSpannedClip(); + XMP_Uns32 dur = p2Clip->GetDuration(); + XMP_VarString* editunit= p2Clip->GetEditUnit(); + + if ( ( dur != 0) && ( editunit != 0 ) ) { + + ostringstream duration; + duration<xmpObj.DeleteProperty ( kXMP_NS_DM, "duration" ); + this->xmpObj.SetStructField ( kXMP_NS_DM, "duration", + kXMP_NS_DM, "value", duration.str().c_str() ); + + this->xmpObj.SetStructField ( kXMP_NS_DM, "duration", + kXMP_NS_DM, "scale", editunit->c_str() ); + this->containsXMP = true; + + } + + } + +} // P2_MetaHandler::SetDurationFromLegacyXML + +// ================================================================================================= +// P2_MetaHandler::SetVideoFrameInfoFromLegacyXML +// ============================================== + +void P2_MetaHandler::SetVideoFrameInfoFromLegacyXML ( XML_NodePtr legacyVideoContext, bool digestFound ) +{ + + // Map the P2 Codec field to various dynamic media schema fields. + if ( digestFound || (! this->xmpObj.DoesPropertyExist ( kXMP_NS_DM, "videoFrameSize" )) ) { + + P2_Clip* p2Clip = this->p2ClipManager.GetManagedClip() ; + XMP_StringPtr p2NS = p2Clip->GetP2RootNode()->ns.c_str(); + XML_NodePtr legacyProp = legacyVideoContext->GetNamedElement ( p2NS, "Codec" ); + + if ( (legacyProp != 0) && legacyProp->IsLeafContentNode() ) { + + const std::string p2Codec = legacyProp->GetLeafContentValue(); + std::string dmPixelAspectRatio, dmVideoCompressor, dmWidth, dmHeight; + + if ( p2Codec == "DV25_411" ) { + dmWidth = "720"; + dmVideoCompressor = "DV25 4:1:1"; + } else if ( p2Codec == "DV25_420" ) { + dmWidth = "720"; + dmVideoCompressor = "DV25 4:2:0"; + } else if ( p2Codec == "DV50_422" ) { + dmWidth = "720"; + dmVideoCompressor = "DV50 4:2:2"; + } else if ( ( p2Codec == "DV100_1080/59.94i" ) || ( p2Codec == "DV100_1080/50i" ) ) { + dmVideoCompressor = "DV100"; + dmHeight = "1080"; + + if ( p2Codec == "DV100_1080/59.94i" ) { + dmWidth = "1280"; + dmPixelAspectRatio = "3/2"; + } else { + dmWidth = "1440"; + dmPixelAspectRatio = "1920/1440"; + } + } else if ( ( p2Codec == "DV100_720/59.94p" ) || ( p2Codec == "DV100_720/50p" ) ) { + dmVideoCompressor = "DV100"; + dmHeight = "720"; + dmWidth = "960"; + dmPixelAspectRatio = "1920/1440"; + } else if ( ( p2Codec.compare ( 0, 6, "AVC-I_" ) == 0 ) ) { + + // This is AVC-Intra footage. The framerate and PAR depend on the "class" attribute in the P2 XML. + const XMP_StringPtr codecClass = legacyProp->GetAttrValue( "Class" ); + if ( codecClass != 0 ) + dmVideoCompressor = "AVC-Intra"; // initializing with default value + if ( XMP_LitMatch ( codecClass, "100" ) ) { + + dmVideoCompressor = "AVC-Intra 100"; + dmPixelAspectRatio = "1/1"; + + if ( p2Codec.compare ( 6, 4, "1080" ) == 0 ) { + dmHeight = "1080"; + dmWidth = "1920"; + } else if ( p2Codec.compare ( 6, 3, "720" ) == 0 ) { + dmHeight = "720"; + dmWidth = "1280"; + } + + } else if ( XMP_LitMatch ( codecClass, "50" ) ) { + + dmVideoCompressor = "AVC-Intra 50"; + dmPixelAspectRatio = "1920/1440"; + + if ( p2Codec.compare ( 6, 4, "1080" ) == 0 ) { + dmHeight = "1080"; + dmWidth = "1440"; + } else if ( p2Codec.compare ( 6, 3, "720" ) == 0 ) { + dmHeight = "720"; + dmWidth = "960"; + } + + } else { + // Unknown codec class -- we don't have enough info to determine the + // codec, PAR, or aspect ratio + dmVideoCompressor = "AVC-Intra"; + } + } + + if ( dmWidth == "720" ) { + + // This is SD footage -- calculate the frame height and pixel aspect ratio using the legacy P2 + // FrameRate and AspectRatio fields. + + legacyProp = legacyVideoContext->GetNamedElement ( p2NS, "FrameRate" ); + if ( (legacyProp != 0) && legacyProp->IsLeafContentNode() ) { + + const std::string p2FrameRate = legacyProp->GetLeafContentValue(); + + legacyProp = legacyVideoContext->GetNamedElement ( p2NS, "AspectRatio" ); + + if ( (legacyProp != 0) && legacyProp->IsLeafContentNode() ) { + const std::string p2AspectRatio = legacyProp->GetLeafContentValue(); + + if ( p2FrameRate == "50i" ) { + // Standard Definition PAL. + dmHeight = "576"; + if ( p2AspectRatio == "4:3" ) { + dmPixelAspectRatio = "768/702"; + } else if ( p2AspectRatio == "16:9" ) { + dmPixelAspectRatio = "1024/702"; + } + } else if ( p2FrameRate == "59.94i" ) { + // Standard Definition NTSC. + dmHeight = "480"; + if ( p2AspectRatio == "4:3" ) { + dmPixelAspectRatio = "10/11"; + } else if ( p2AspectRatio == "16:9" ) { + dmPixelAspectRatio = "40/33"; + } + } + + } + } + } + + if ( ! dmPixelAspectRatio.empty() ) { + this->xmpObj.SetProperty ( kXMP_NS_DM, "videoPixelAspectRatio", dmPixelAspectRatio, kXMP_DeleteExisting ); + this->containsXMP = true; + } + + if ( ! dmVideoCompressor.empty() ) { + this->xmpObj.SetProperty ( kXMP_NS_DM, "videoCompressor", dmVideoCompressor, kXMP_DeleteExisting ); + this->containsXMP = true; + } + + if ( ( ! dmWidth.empty() ) && ( ! dmHeight.empty() ) ) { + this->xmpObj.SetStructField ( kXMP_NS_DM, "videoFrameSize", kXMP_NS_XMP_Dimensions, "w", dmWidth, 0 ); + this->xmpObj.SetStructField ( kXMP_NS_DM, "videoFrameSize", kXMP_NS_XMP_Dimensions, "h", dmHeight, 0 ); + this->xmpObj.SetStructField ( kXMP_NS_DM, "videoFrameSize", kXMP_NS_XMP_Dimensions, "unit", "pixel", 0 ); + this->containsXMP = true; + } + + } + + } + +} // P2_MetaHandler::SetVideoFrameInfoFromLegacyXML + +// ================================================================================================= +// P2_MetaHandler::SetStartTimecodeFromLegacyXML +// ============================================= + +void P2_MetaHandler::SetStartTimecodeFromLegacyXML ( XML_NodePtr legacyVideoContext, bool digestFound ) +{ + + // Translate start timecode to the format specified by the dynamic media schema. + if ( digestFound || (! this->xmpObj.DoesPropertyExist ( kXMP_NS_DM, "startTimecode" )) ) { + + P2_Clip* p2Clip = this->p2ClipManager.GetManagedClip() ; + XMP_StringPtr p2NS = p2Clip->GetP2RootNode()->ns.c_str(); + XML_NodePtr legacyProp = legacyVideoContext->GetNamedElement ( p2NS, "StartTimecode" ); + + if ( (legacyProp != 0) && legacyProp->IsLeafContentNode() ) { + + std::string p2StartTimecode = legacyProp->GetLeafContentValue(); + + legacyProp = legacyVideoContext->GetNamedElement ( p2NS, "FrameRate" ); + + if ( (legacyProp != 0) && legacyProp->IsLeafContentNode() ) { + + const std::string p2FrameRate = legacyProp->GetLeafContentValue(); + XMP_StringPtr p2DropFrameFlag = legacyProp->GetAttrValue ( "DropFrameFlag" ); + if ( p2DropFrameFlag == 0 ) p2DropFrameFlag = ""; // Make tests easier. + std::string dmTimeFormat; + + if ( ( p2FrameRate == "50i" ) || ( p2FrameRate == "25p" ) ) { + + dmTimeFormat = "25Timecode"; + + } else if ( p2FrameRate == "23.98p" ) { + + dmTimeFormat = "23976Timecode"; + + } else if ( p2FrameRate == "50p" ) { + + dmTimeFormat = "50Timecode"; + this->AdjustTimeCode( p2StartTimecode, false ); + + } else if ( p2FrameRate == "59.94p" ) { + + if ( XMP_LitMatch ( p2DropFrameFlag, "true" ) ) { + dmTimeFormat = "5994DropTimecode"; + } else if ( XMP_LitMatch ( p2DropFrameFlag, "false" ) ) { + dmTimeFormat = "5994NonDropTimecode"; + } + this->AdjustTimeCode( p2StartTimecode, false ); + + } else if ( (p2FrameRate == "59.94i") || (p2FrameRate == "29.97p") ) { + + if ( p2DropFrameFlag != 0 ) { + + if ( XMP_LitMatch ( p2DropFrameFlag, "false" ) ) { + + dmTimeFormat = "2997NonDropTimecode"; + + } else if ( XMP_LitMatch ( p2DropFrameFlag, "true" ) ) { + + // Drop frame NTSC timecode uses semicolons instead of colons as separators. + std::string::iterator currCharIt = p2StartTimecode.begin(); + const std::string::iterator charsEndIt = p2StartTimecode.end(); + + for ( ; currCharIt != charsEndIt; ++currCharIt ) { + if ( *currCharIt == ':' ) *currCharIt = ';'; + } + + dmTimeFormat = "2997DropTimecode"; + + } + + } + + } + + if ( ( ! p2StartTimecode.empty() ) && ( ! dmTimeFormat.empty() ) ) { + this->xmpObj.SetStructField ( kXMP_NS_DM, "startTimecode", kXMP_NS_DM, "timeValue", p2StartTimecode, 0 ); + this->xmpObj.SetStructField ( kXMP_NS_DM, "startTimecode", kXMP_NS_DM, "timeFormat", dmTimeFormat, 0 ); + this->containsXMP = true; + } + + } + + } + + } + +} // P2_MetaHandler::SetStartTimecodeFromLegacyXML + +// ================================================================================================= +// P2_MetaHandler::AdjustTimeCode +// =========================================== + +void P2_MetaHandler::AdjustTimeCode( std::string & p2Timecode, const XMP_Bool & isXMPtoXMLConversion ) +{ + /* + XMP is storing frame number for 50P and 59.94P as [0-49] and [0-59] respectively, + but NRT XML can store frame number for these format as [0-29]. + So, XMP need to adjust values for these cases. + */ + try + { + XMP_Int64 strLength = p2Timecode.length(); + XMP_Int64 index = strLength - 1; + for (; index > 0; --index) + if (p2Timecode.at(index) == ':') + break; + std::string FFValue; + if ( index == strLength - 2 ) // HH:MM:SS:F + FFValue = p2Timecode.substr(index + 1, 1); + else if ( index == strLength - 3 ) + FFValue = p2Timecode.substr(index + 1, 2); // HH:MM:SS:FF + else + throw; // Invalid format + stringstream timeCodeStream (FFValue); + XMP_Uns32 frameNumber; + timeCodeStream >> frameNumber; + if (isXMPtoXMLConversion) // Conversion from XMP to XML so doing half the value + { + frameNumber /= 2; + XMP_Assert(frameNumber >= 0 && frameNumber < 30); + } + else // Conversion from XML to XMP so doubling the value + { + XMP_Assert(frameNumber >= 0 && frameNumber < 30); + frameNumber *= 2; + } + timeCodeStream.clear(); + timeCodeStream << p2Timecode.substr(0, index + 1); + if (frameNumber < 10) + timeCodeStream << '0'; + timeCodeStream << frameNumber; + p2Timecode = timeCodeStream.str(); + } + catch (...) + { + XMP_Throw("P2 Invalid Timecode.", kXMPErr_InternalFailure); + } +} // P2_MetaHandler::AdjustTimeCode + +// ================================================================================================= +// P2_MetaHandler::SetGPSPropertyFromLegacyXML +// =========================================== + +void P2_MetaHandler::SetGPSPropertyFromLegacyXML ( XML_NodePtr legacyLocationContext, bool digestFound, XMP_StringPtr propName, XMP_StringPtr legacyPropName ) +{ + + if ( digestFound || (! this->xmpObj.DoesPropertyExist ( kXMP_NS_EXIF, propName )) ) { + + P2_Clip* p2Clip = this->p2ClipManager.GetManagedClip() ; + XMP_StringPtr p2NS = p2Clip->GetP2RootNode()->ns.c_str(); + XML_NodePtr legacyGPSProp = legacyLocationContext->GetNamedElement ( p2NS, legacyPropName ); + + if ( ( legacyGPSProp != 0 ) && legacyGPSProp->IsLeafContentNode() ) { + + this->xmpObj.DeleteProperty ( kXMP_NS_EXIF, propName ); + + const std::string legacyGPSValue = legacyGPSProp->GetLeafContentValue(); + + if ( ! legacyGPSValue.empty() ) { + + // Convert from decimal to sexagesimal GPS coordinates + char direction = '\0'; + double degrees = 0.0; + const int numFieldsRead = sscanf ( legacyGPSValue.c_str(), "%c%lf", &direction, °rees ); + + if ( numFieldsRead == 2 ) { + double wholeDegrees = 0.0; + const double fractionalDegrees = modf ( degrees, &wholeDegrees ); + const double minutes = fractionalDegrees * 60.0; + char xmpValue [128]; + + sprintf ( xmpValue, "%d,%.5lf%c", static_cast(wholeDegrees), minutes, direction ); + this->xmpObj.SetProperty ( kXMP_NS_EXIF, propName, xmpValue ); + this->containsXMP = true; + + } + + } + + } + + } + +} // P2_MetaHandler::SetGPSPropertyFromLegacyXML + +// ================================================================================================= +// P2_MetaHandler::SetAltitudeFromLegacyXML +// ======================================== + +void P2_MetaHandler::SetAltitudeFromLegacyXML ( XML_NodePtr legacyLocationContext, bool digestFound ) +{ + + if ( digestFound || (! this->xmpObj.DoesPropertyExist ( kXMP_NS_EXIF, "GPSAltitude" )) ) { + + P2_Clip* p2Clip = this->p2ClipManager.GetManagedClip() ; + XMP_StringPtr p2NS = p2Clip->GetP2RootNode()->ns.c_str(); + XML_NodePtr legacyAltitudeProp = legacyLocationContext->GetNamedElement ( p2NS, "Altitude" ); + + if ( ( legacyAltitudeProp != 0 ) && legacyAltitudeProp->IsLeafContentNode() ) { + + this->xmpObj.DeleteProperty ( kXMP_NS_EXIF, "GPSAltitude" ); + + const std::string legacyGPSValue = legacyAltitudeProp->GetLeafContentValue(); + + if ( ! legacyGPSValue.empty() ) { + + int altitude = 0; + + if ( sscanf ( legacyGPSValue.c_str(), "%d", &altitude ) == 1) { + + if ( altitude >= 0 ) { + // At or above sea level. + this->xmpObj.SetProperty ( kXMP_NS_EXIF, "GPSAltitudeRef", "0" ); + } else { + // Below sea level. + altitude = -altitude; + this->xmpObj.SetProperty ( kXMP_NS_EXIF, "GPSAltitudeRef", "1" ); + } + + char xmpValue [128]; + + sprintf ( xmpValue, "%d/1", altitude ); + this->xmpObj.SetProperty ( kXMP_NS_EXIF, "GPSAltitude", xmpValue ); + this->containsXMP = true; + + } + + } + + } + + } + +} // P2_MetaHandler::SetAltitudeFromLegacyXML + +// ================================================================================================= +// P2_MetaHandler::ForceChildElement +// ================================= + +XML_Node * P2_MetaHandler::ForceChildElement ( XML_Node * parent, XMP_StringPtr localName, XMP_Int32 indent , XMP_Bool insertAtFront ) +{ + XML_Node * wsNodeBefore, * wsNodeAfter; + P2_Clip* p2Clip = this->p2ClipManager.GetManagedClip() ; + XMP_StringPtr p2NS = p2Clip->GetP2RootNode()->ns.c_str(); + XML_Node * childNode = parent->GetNamedElement ( p2NS, localName ); + // + + if ( childNode == 0 ) { + + // The indenting is a hack, assuming existing 2 spaces per level. + try { + wsNodeBefore = new XML_Node ( parent, "", kCDataNode ); + wsNodeBefore->value = " "; // Add 2 spaces to the existing WS before the parent's close tag. + + childNode = new XML_Node ( parent, localName, kElemNode ); + childNode->ns = parent->ns; + childNode->nsPrefixLen = parent->nsPrefixLen; + childNode->name.insert ( 0, parent->name, 0, parent->nsPrefixLen ); + + wsNodeAfter = new XML_Node ( parent, "", kCDataNode ); + } catch (...) { + if (wsNodeBefore) + delete wsNodeBefore; + if (childNode) + delete childNode; + + throw; + } + wsNodeAfter->value = '\n'; + for ( ; indent > 1; --indent ) wsNodeAfter->value += " "; // Indent less 1, to "outdent" the parent's close. + + if(insertAtFront){ + // we are asked to insert this child as the first child of it's parent.So if P is the parent and B,C are children + // already present. Then if we add a new child A as it's first child then we need to first add new line character right + // after "

" and then proper indentation to bring them on the level of other children. + //

+ // + // + //

+ std::vector indentedNode; + indentedNode.push_back(wsNodeAfter); + indentedNode.push_back(wsNodeBefore); + indentedNode.push_back(childNode); + parent->content.insert(parent->content.begin(), indentedNode.begin(), indentedNode.end()); + } + else{ + parent->content.push_back(wsNodeBefore); + parent->content.push_back(childNode); + parent->content.push_back(wsNodeAfter); + } + + } + + return childNode; + +} // P2_MetaHandler::ForceChildElement + +// ================================================================================================= +// P2_MetaHandler::IsMetadataWritable +// ======================================= + +bool P2_MetaHandler::IsMetadataWritable ( ) +{ + std::string noExtPath, filePath; + noExtPath = rootPath + kDirChar + "CONTENTS" + kDirChar + "CLIP" + kDirChar + this->clipName ; + filePath = noExtPath + ".XMP"; + // Check whether sidecar is writable, if not then check if it can be created. + bool writable = Host_IO::Writable( filePath.c_str(), true ); + filePath = noExtPath + ".XML"; + // Check if legacy XML is writable. + writable &= Host_IO::Writable( filePath.c_str(), false ); + return writable; +}// P2_MetaHandler::IsMetadataWritable + +// ================================================================================================= +// P2_MetaHandler::FillAssociatedResources +// ====================================== +void P2_MetaHandler::FillAssociatedResources ( std::vector * resourceList ) +{ + // The possible associated resources: + // CONTENTS/ + // CLIP/ + // XXXXXX.XML XXXXXX is clip name + // XXXXXX.XMP + // VIDEO/ + // XXXXXX.MXF + // AUDIO/ + // XXXXXXNN.MXF NN is a counter which can go from 00 to 15. + // ICON/ + // XXXXXX.BMP + // VOICE/ + // XXXXXXNN.WAV NN is a counter which can go from 00 to 99. + // PROXY/ + // XXXXXX.MP4 + // XXXXXX.BIN + XMP_VarString contentsPath = this->rootPath + kDirChar + "CONTENTS" + kDirChar; + XMP_VarString path; + + //Add RootPath + path = this->rootPath + kDirChar; + PackageFormat_Support::AddResourceIfExists(resourceList, path); + P2_SpannedClip* p2SpanClip=p2ClipManager.GetSpannedClip(); + if ( ! p2SpanClip ) return ; + std::vector clipNameList; + p2SpanClip->GetAllClipNames ( clipNameList ); + std::vector::iterator iter = clipNameList.begin(); + for(; iter!=clipNameList.end(); iter++) + { + + std::string clipPathNoExt = contentsPath + "CLIP" + kDirChar + *iter; + // Get the files present inside CLIP folder. + path = clipPathNoExt + ".XML"; + PackageFormat_Support::AddResourceIfExists(resourceList, path); + path = clipPathNoExt + ".XMP"; + PackageFormat_Support::AddResourceIfExists(resourceList, path); + + // Get the files present inside VIDEO folder. + path = contentsPath + "VIDEO" + kDirChar + *iter + ".MXF"; + PackageFormat_Support::AddResourceIfExists(resourceList, path); + + // Get the files present inside AUDIO folder. + path = contentsPath + "AUDIO" + kDirChar; + XMP_VarString regExp; + regExp = "^" + *iter + "\\d\\d.MXF$"; + IOUtils::GetMatchingChildren ( *resourceList, path, regExp, false, true, true ); + + // Get the files present inside ICON folder. + path = contentsPath + "ICON" + kDirChar + *iter + ".BMP"; + PackageFormat_Support::AddResourceIfExists(resourceList, path); + + // Get the files present inside VOICE folder. + path = contentsPath + "VOICE" + kDirChar; + regExp = "^" + *iter + "\\d\\d.WAV$"; + IOUtils::GetMatchingChildren ( *resourceList, path, regExp, false, true, true ); + + // Get the files present inside PROXY folder. + std::string proxyPathNoExt = contentsPath + "PROXY" + kDirChar + *iter; + + path = proxyPathNoExt + ".MP4"; + PackageFormat_Support::AddResourceIfExists(resourceList, path); + path = proxyPathNoExt + ".BIN"; + PackageFormat_Support::AddResourceIfExists(resourceList, path); + } +} // P2_MetaHandler::FillAssociatedResources + +// ================================================================================================= +// P2_MetaHandler::CacheFileData +// ============================= + +void P2_MetaHandler::CacheFileData() +{ + XMP_Assert ( ! this->containsXMP ); + + if ( this->parent->UsesClientIO() ) { + XMP_Throw ( "P2 cannot be used with client-managed I/O", kXMPErr_InternalFailure ); + } + + // Make sure the clip's .XMP file exists. + + std::string xmpPath; + + this->MakeClipFilePath ( &xmpPath, ".XMP" ); + if ( ! Host_IO::Exists ( xmpPath.c_str() ) ) return; // No XMP. + + // Read the entire .XMP file. We know the XMP exists, New_XMPFiles_IO is supposed to return 0 + // only if the file does not exist. + + bool readOnly = XMP_OptionIsClear ( this->parent->openFlags, kXMPFiles_OpenForUpdate ); + + XMP_Assert ( this->parent->ioRef == 0 ); + XMPFiles_IO* xmpFile = XMPFiles_IO::New_XMPFiles_IO ( xmpPath.c_str(), readOnly ); + if ( xmpFile == 0 ) XMP_Throw ( "P2 XMP file open failure", kXMPErr_InternalFailure ); + this->parent->ioRef = xmpFile; + + XMP_Int64 xmpLen = xmpFile->Length(); + if ( xmpLen > 100*1024*1024 ) { + XMP_Throw ( "P2 XMP is outrageously large", kXMPErr_InternalFailure ); // Sanity check. + } + + this->xmpPacket.erase(); + this->xmpPacket.append ( (size_t)xmpLen, ' ' ); + + XMP_Int32 ioCount = xmpFile->ReadAll ( (void*)this->xmpPacket.data(), (XMP_Int32)xmpLen ); + + this->packetInfo.offset = 0; + this->packetInfo.length = (XMP_Int32)xmpLen; + FillPacketInfo ( this->xmpPacket, &this->packetInfo ); + + this->containsXMP = true; + +} // P2_MetaHandler::CacheFileData + +// ================================================================================================= +// P2_MetaHandler::ProcessXMP +// ========================== + +void P2_MetaHandler::ProcessXMP() +{ + if ( this->processedXMP ) return; + this->processedXMP = true; // Make sure only called once. + + if ( this->containsXMP ) { + this->xmpObj.ParseFromBuffer ( this->xmpPacket.c_str(), (XMP_StringLen)this->xmpPacket.size() ); + } + + XML_NodePtr legacyContext, clipMetadata, legacyProp; + if ( ! this->p2ClipManager.IsValidP2() ) return; + P2_Clip* p2Clip=this->p2ClipManager.GetManagedClip(); + XMP_StringPtr p2NS = p2Clip->GetP2RootNode()->ns.c_str(); + std::string oldDigest, newDigest; + bool digestFound = this->xmpObj.GetStructField ( kXMP_NS_XMP, "NativeDigests", kXMP_NS_XMP, "P2", &oldDigest, 0 ); + if ( digestFound ) { + p2Clip->CreateDigest ( &newDigest ); + if ( oldDigest == newDigest ) return; + } + + // If we get here we need find and import the actual legacy elements using the current namespace. + // Either there is no old digest in the XMP, or the digests differ. In the former case keep any + // existing XMP, in the latter case take new legacy values. + std::string clipTitle= p2Clip->GetClipTitle();// needed for successful Mac Builds + this->SetXMPPropertyFromLegacyXML ( digestFound, &clipTitle , kXMP_NS_DC, "title", true ); + if ( p2Clip->IsValidClip() ) + this->SetXMPPropertyFromLegacyXML ( digestFound, p2Clip->GetClipId(), kXMP_NS_DC, "identifier", false ); + this->SetDurationFromLegacyXML (digestFound ); + this->SetRelationsFromLegacyXML ( digestFound ); + clipMetadata = p2Clip->GetClipMetadataNode(); + if ( clipMetadata == 0 ) return; + this->SetXMPPropertyFromLegacyXML ( digestFound,p2Clip->GetClipMetadataNode(), kXMP_NS_DM, "shotName", "UserClipName", false ); + this->SetAudioInfoFromLegacyXML ( digestFound ); + this->SetVideoInfoFromLegacyXML ( digestFound ); + + + legacyContext = clipMetadata->GetNamedElement ( p2NS, "Access" ); + if ( legacyContext == 0 ) return; + + if ( digestFound || (! this->xmpObj.DoesPropertyExist ( kXMP_NS_DC, "creator" )) ) { + legacyProp = legacyContext->GetNamedElement ( p2NS, "Creator" ); + if ( (legacyProp != 0) && legacyProp->IsLeafContentNode() ) { + this->xmpObj.DeleteProperty ( kXMP_NS_DC, "creator" ); + this->xmpObj.AppendArrayItem ( kXMP_NS_DC, "creator", kXMP_PropArrayIsOrdered, + legacyProp->GetLeafContentValue() ); + this->containsXMP = true; + } + } + + this->SetXMPPropertyFromLegacyXML ( digestFound, legacyContext, kXMP_NS_XMP, "CreateDate", "CreationDate", false ); + this->SetXMPPropertyFromLegacyXML ( digestFound, legacyContext, kXMP_NS_XMP, "ModifyDate", "LastUpdateDate", false ); + + if ( digestFound || (! this->xmpObj.DoesPropertyExist ( kXMP_NS_DM, "good" )) ) { + legacyProp = clipMetadata->GetNamedElement ( p2NS, "ShotMark" ); + if ( (legacyProp == 0) || (! legacyProp->IsLeafContentNode()) ) { + this->xmpObj.DeleteProperty ( kXMP_NS_DM, "good" ); + } else { + XMP_StringPtr markValue = legacyProp->GetLeafContentValue(); + if ( markValue == 0 ) { + this->xmpObj.DeleteProperty ( kXMP_NS_DM, "good" ); + } else if ( XMP_LitMatch ( markValue, "true" ) || XMP_LitMatch ( markValue, "1" ) ) { + this->xmpObj.SetProperty_Bool ( kXMP_NS_DM, "good", true, kXMP_DeleteExisting ); + this->containsXMP = true; + } else if ( XMP_LitMatch ( markValue, "false" ) || XMP_LitMatch ( markValue, "0" ) ) { + this->xmpObj.SetProperty_Bool ( kXMP_NS_DM, "good", false, kXMP_DeleteExisting ); + this->containsXMP = true; + } + } + } + + legacyContext = clipMetadata->GetNamedElement ( p2NS, "Shoot" ); + if ( legacyContext != 0 ) { + this->SetXMPPropertyFromLegacyXML ( digestFound, legacyContext, kXMP_NS_TIFF, "Artist", "Shooter", false ); + legacyContext = legacyContext->GetNamedElement ( p2NS, "Location" ); + } + + if ( legacyContext != 0 ) { + this->SetXMPPropertyFromLegacyXML ( digestFound, legacyContext, kXMP_NS_DM, "shotLocation", "PlaceName", false ); + this->SetGPSPropertyFromLegacyXML ( legacyContext, digestFound, "GPSLongitude", "Longitude" ); + this->SetGPSPropertyFromLegacyXML ( legacyContext, digestFound, "GPSLatitude", "Latitude" ); + this->SetAltitudeFromLegacyXML ( legacyContext, digestFound ); + } + + legacyContext = clipMetadata->GetNamedElement ( p2NS, "Device" ); + if ( legacyContext != 0 ) { + this->SetXMPPropertyFromLegacyXML ( digestFound, legacyContext, kXMP_NS_TIFF, "Make", "Manufacturer", false ); + this->SetXMPPropertyFromLegacyXML ( digestFound, legacyContext, kXMP_NS_EXIF_Aux, "SerialNumber", "SerialNo.", false ); + this->SetXMPPropertyFromLegacyXML ( digestFound, legacyContext, kXMP_NS_TIFF, "Model", "ModelName", false ); + } + + legacyContext = clipMetadata->GetNamedElement ( p2NS, "Scenario" ); + if ( legacyContext != 0 ) { + this->SetXMPPropertyFromLegacyXML ( digestFound, legacyContext, kXMP_NS_DM, "scene", "SceneNo.", false ); + this->SetXMPPropertyFromLegacyXML ( digestFound, legacyContext, kXMP_NS_DM, "takeNumber", "TakeNo.", false ); + } + + return; + +} // P2_MetaHandler::ProcessXMP + +// This function adds a dummy attribute to the clipContent/clipMetadata (whichever is non-null) +// with empty value and namespace as xmlns:xsi=http://www.w3.org/2001/XMLSchema-instance +static XML_Node* AddXSINamespace(XML_Node *clipContent, XML_Node *clipMetadata){ + + XML_Node *parent = clipContent ? clipContent : clipMetadata; + if(parent){ + XML_Node *attrWithXSINamespace = new XML_Node ( parent, "xsi:", kCDataNode ); + attrWithXSINamespace->value=""; + attrWithXSINamespace->ns="http://www.w3.org/2001/XMLSchema-instance"; + parent->attrs.push_back(attrWithXSINamespace); + return parent; + } + + return NULL; + +} + +// ================================================================================================= +// P2_MetaHandler::UpdateFile +// ========================== +// +// Note that UpdateFile is only called from XMPFiles::CloseFile, so it is OK to close the file here. + +void P2_MetaHandler::UpdateFile ( bool doSafeUpdate ) +{ + if ( ! this->needsUpdate ) return; + this->needsUpdate = false; // Make sure only called once. + + XMP_Assert ( this->parent->UsesLocalIO() ); + + // Update the internal legacy XML tree if we have one, and set the digest in the XMP. + + bool updateLegacyXML = false; + P2_Clip* p2Clip = 0; + XML_NodePtr clipMetadata = 0; + if ( this->p2ClipManager.IsValidP2() ) + { + p2Clip=this->p2ClipManager.GetManagedClip(); + clipMetadata = p2Clip->GetClipMetadataNode(); + if ( clipMetadata != 0 ) { + + bool xmpFound; + std::string xmpValue; + XML_Node * xmlNode; + + xmpFound = this->xmpObj.GetLocalizedText ( kXMP_NS_DC, "title", "", "x-default", 0, &xmpValue, 0 ); + + if ( xmpFound && p2Clip->GetClipContentNode()) { + + xmlNode = this->ForceChildElement ( p2Clip->GetClipContentNode(), "ClipName", 3, false ); + + if ( xmpValue != xmlNode->GetLeafContentValue() ) { + xmlNode->SetLeafContentValue ( xmpValue.c_str() ); + updateLegacyXML = true; + } + + } + + xmpFound = this->xmpObj.GetArrayItem ( kXMP_NS_DC, "creator", 1, &xmpValue, 0 ); + + if ( xmpFound ) { + xmlNode = this->ForceChildElement ( clipMetadata , "Access", 3, false ); + + // "Creator" must be first child of "Access" node else Panasonic P2 Viewer gives an error. + xmlNode = this->ForceChildElement ( xmlNode, "Creator", 4 , true); + if ( xmpValue != xmlNode->GetLeafContentValue() ) { + xmlNode->SetLeafContentValue ( xmpValue.c_str() ); + updateLegacyXML = true; + } + } + } + + // Half the startTimeCode frame number value in XML if require so + std::string xmpStartTimeCode; + bool isTimecodeExists = this->xmpObj.GetStructField(kXMP_NS_DM, "startTimecode", kXMP_NS_DM, "timeValue", &xmpStartTimeCode, 0); + if (isTimecodeExists) + { + std::string frameFormat; + this->xmpObj.GetStructField(kXMP_NS_DM, "startTimecode", kXMP_NS_DM, "timeFormat", &frameFormat, 0); + if (frameFormat == "50Timecode" || frameFormat == "5994DropTimecode" || frameFormat == "5994NonDropTimecode") + { + p2Clip = this->p2ClipManager.GetManagedClip(); + XMP_StringPtr p2NS = p2Clip->GetP2RootNode()->ns.c_str(); + XML_NodePtr legacyVideoContext = p2Clip->GetEssenceListNode(); + if (legacyVideoContext != 0) + { + legacyVideoContext = legacyVideoContext->GetNamedElement(p2NS, "Video"); + XML_NodePtr legacyProp = legacyVideoContext->GetNamedElement(p2NS, "StartTimecode"); + if ((legacyProp != 0) && legacyProp->IsLeafContentNode()) + { + AdjustTimeCode( xmpStartTimeCode, true ); + if (xmpStartTimeCode != legacyProp->GetLeafContentValue()) + { + legacyProp->SetLeafContentValue(xmpStartTimeCode.c_str()); + updateLegacyXML = true; + } + } + } + } + } + + std::string newDigest; + this->p2ClipManager.GetManagedClip()->CreateDigest ( &newDigest ); + this->xmpObj.SetStructField ( kXMP_NS_XMP, "NativeDigests", kXMP_NS_XMP, "P2", newDigest.c_str(), kXMP_DeleteExisting ); + } + + this->xmpObj.SerializeToBuffer ( &this->xmpPacket, this->GetSerializeOptions() ); + + // ----------------------------------------------------------------------- + // Update the XMP file first, don't let legacy XML failures block the XMP. + + std::string xmpPath; + this->MakeClipFilePath ( &xmpPath, ".XMP" ); + + bool haveXMP = Host_IO::Exists ( xmpPath.c_str() ); + if ( ! haveXMP ) { + XMP_Assert ( this->parent->ioRef == 0 ); + Host_IO::Create ( xmpPath.c_str() ); + this->parent->ioRef = XMPFiles_IO::New_XMPFiles_IO ( xmpPath.c_str(), Host_IO::openReadWrite ); + if ( this->parent->ioRef == 0 ) XMP_Throw ( "Failure opening P2 XMP file", kXMPErr_ExternalFailure ); + } + + XMP_IO* xmpFile = this->parent->ioRef; + XMP_Assert ( xmpFile != 0 ); + XIO::ReplaceTextFile ( xmpFile, this->xmpPacket, (haveXMP & doSafeUpdate) ); + + // -------------------------------------------- + // Now update the legacy XML file if necessary. + + if ( updateLegacyXML ) { + + std::string legacyXML, xmlPath; + + /*bug # 3217688: xmlns:xsi=http://www.w3.org/2001/XMLSchema-instance namespace must be defined at the + root node "P2Main" in legacy XML else Panasonic P2 Viewer gives an error. So we are adding a + dummy attribute with this namespace to clipContent/clipMetadata (whichever is non-null) before + serializing the XML tree. We are also undoing it below after serialization.*/ + + XML_Node *parentNode = AddXSINamespace(p2Clip->GetClipContentNode(), clipMetadata); + p2Clip->SerializeP2ClipContent ( legacyXML ); + if(parentNode){ + // Remove the dummy attribute added to clipContent/clipMetadata. + delete parentNode->attrs[parentNode->attrs.size()-1]; + parentNode->attrs.pop_back(); + } + + this->MakeClipFilePath ( &xmlPath, ".XML" ); + + bool haveXML = Host_IO::Exists ( xmlPath.c_str() ); + if ( ! haveXML ) Host_IO::Create ( xmlPath.c_str() ); + + Host_IO::FileRef hostRef = Host_IO::Open ( xmlPath.c_str(), Host_IO::openReadWrite ); + if ( hostRef == Host_IO::noFileRef ) XMP_Throw ( "Failure opening P2 legacy XML file", kXMPErr_ExternalFailure ); + XMPFiles_IO origXML ( hostRef, xmlPath.c_str(), Host_IO::openReadWrite ); + XIO::ReplaceTextFile ( &origXML, legacyXML, (haveXML & doSafeUpdate) ); + origXML.Close(); + + } + +} // P2_MetaHandler::UpdateFile + +// ================================================================================================= +// P2_MetaHandler::WriteTempFile +// ============================= + +void P2_MetaHandler::WriteTempFile ( XMP_IO* tempRef ) +{ + + // ! WriteTempFile is not supposed to be called for handlers that own the file. + XMP_Throw ( "P2_MetaHandler::WriteTempFile should not be called", kXMPErr_InternalFailure ); + +} // P2_MetaHandler::WriteTempFile + +// ================================================================================================= diff --git a/core/libs/dngwriter/extra/xmp_sdk/XMPFiles/source/FileHandlers/P2_Handler.hpp b/core/libs/dngwriter/extra/xmp_sdk/XMPFiles/source/FileHandlers/P2_Handler.hpp new file mode 100644 index 0000000000..b4b7dcd19a --- /dev/null +++ b/core/libs/dngwriter/extra/xmp_sdk/XMPFiles/source/FileHandlers/P2_Handler.hpp @@ -0,0 +1,114 @@ +#ifndef __P2_Handler_hpp__ +#define __P2_Handler_hpp__ 1 + +// ================================================================================================= +// ADOBE SYSTEMS INCORPORATED +// Copyright 2007 Adobe Systems Incorporated +// All Rights Reserved +// +// NOTICE: Adobe permits you to use, modify, and distribute this file in accordance with the terms +// of the Adobe license agreement accompanying it. +// ================================================================================================= + +#include "public/include/XMP_Environment.h" // ! This must be the first include. + +#include "XMPFiles/source/XMPFiles_Impl.hpp" + +#include "source/ExpatAdapter.hpp" + +#include "XMP_MD5.h" +#include "XMPFiles/source/FormatSupport/P2_Support.hpp" + +// ================================================================================================= +/// \file P2_Handler.hpp +/// \brief Folder format handler for P2. +/// +/// This header ... +/// +// ================================================================================================= + +// *** Could derive from Basic_Handler - buffer file tail in a temp file. + +extern XMPFileHandler * P2_MetaHandlerCTor ( XMPFiles * parent ); + +extern bool P2_CheckFormat ( XMP_FileFormat format, + const std::string & rootPath, + const std::string & gpName, + const std::string & parentName, + const std::string & leafName, + XMPFiles * parent ); + +static const XMP_OptionBits kP2_HandlerFlags = (kXMPFiles_CanInjectXMP | + kXMPFiles_CanExpand | + kXMPFiles_CanRewrite | + kXMPFiles_PrefersInPlace | + kXMPFiles_CanReconcile | + kXMPFiles_AllowsOnlyXMP | + kXMPFiles_ReturnsRawPacket | + kXMPFiles_HandlerOwnsFile | + kXMPFiles_AllowsSafeUpdate | + kXMPFiles_FolderBasedFormat); + +class P2_MetaHandler : public XMPFileHandler +{ +public: + + void FillAssociatedResources ( std::vector * resourceList ); + bool IsMetadataWritable ( ); + + void CacheFileData(); + void ProcessXMP(); + + void UpdateFile ( bool doSafeUpdate ); + void WriteTempFile ( XMP_IO* tempRef ); + + XMP_OptionBits GetSerializeOptions() // *** These should be standard for standalone XMP files. + { return (kXMP_UseCompactFormat | kXMP_OmitPacketWrapper); }; + + P2_MetaHandler ( XMPFiles * _parent ); + virtual ~P2_MetaHandler(); + +private: + + P2_MetaHandler() {}; // Hidden on purpose. + + bool MakeClipFilePath ( std::string * path, XMP_StringPtr suffix, bool checkFile = false ); + void MakeLegacyDigest ( std::string * digestStr ); + + void DigestLegacyItem ( MD5_CTX & md5Context, XML_NodePtr legacyContext, XMP_StringPtr legacyPropName ); + void DigestLegacyRelations ( MD5_CTX & md5Context ); + + void SetXMPPropertyFromLegacyXML ( bool digestFound, + XML_NodePtr legacyContext, + XMP_StringPtr schemaNS, + XMP_StringPtr propName, + XMP_StringPtr legacyPropName, + bool isLocalized ); + void SetXMPPropertyFromLegacyXML ( bool digestFound, + XMP_VarString* refContext, + XMP_StringPtr schemaNS, + XMP_StringPtr propName, + bool isLocalized ); + + void SetRelationsFromLegacyXML ( bool digestFound ); + void SetAudioInfoFromLegacyXML ( bool digestFound ); + void SetVideoInfoFromLegacyXML ( bool digestFound ); + void SetDurationFromLegacyXML ( bool digestFound ); + + void SetVideoFrameInfoFromLegacyXML ( XML_NodePtr legacyVideoContext, bool digestFound ); + void SetStartTimecodeFromLegacyXML ( XML_NodePtr legacyVideoContext, bool digestFound ); + void SetGPSPropertyFromLegacyXML ( XML_NodePtr legacyLocationContext, bool digestFound, XMP_StringPtr propName, XMP_StringPtr legacyPropName ); + void SetAltitudeFromLegacyXML ( XML_NodePtr legacyLocationContext, bool digestFound ); + void AdjustTimeCode( std::string & p2Timecode, const XMP_Bool & isXMPtoXMLConversion ); + + XML_Node * ForceChildElement ( XML_Node * parent, XMP_StringPtr localName, XMP_Int32 indent, XMP_Bool insertAtFront ); + + std::string rootPath , clipName; + + P2_Manager p2ClipManager; + +}; // P2_MetaHandler + +// ================================================================================================= + +#endif /* __P2_Handler_hpp__ */ diff --git a/core/libs/dngwriter/extra/xmp_sdk/XMPFiles/source/FileHandlers/PNG_Handler.cpp b/core/libs/dngwriter/extra/xmp_sdk/XMPFiles/source/FileHandlers/PNG_Handler.cpp new file mode 100644 index 0000000000..8c2fe8cb31 --- /dev/null +++ b/core/libs/dngwriter/extra/xmp_sdk/XMPFiles/source/FileHandlers/PNG_Handler.cpp @@ -0,0 +1,248 @@ +// ================================================================================================= +// ADOBE SYSTEMS INCORPORATED +// Copyright 2006 Adobe Systems Incorporated +// All Rights Reserved +// +// NOTICE: Adobe permits you to use, modify, and distribute this file in accordance with the terms +// of the Adobe license agreement accompanying it. +// ================================================================================================= + +#include "public/include/XMP_Environment.h" // ! XMP_Environment.h must be the first included header. +#include "public/include/XMP_Const.h" + +#include "XMPFiles/source/FileHandlers/PNG_Handler.hpp" +#include "XMPFiles/source/FormatSupport/PNG_Support.hpp" + +#include "source/XIO.hpp" + +using namespace std; + +// ================================================================================================= +/// \file PNG_Handler.hpp +/// \brief File format handler for PNG. +/// +/// This handler ... +/// +// ================================================================================================= + +// ================================================================================================= +// PNG_MetaHandlerCTor +// ==================== + +XMPFileHandler * PNG_MetaHandlerCTor ( XMPFiles * parent ) +{ + return new PNG_MetaHandler ( parent ); + +} // PNG_MetaHandlerCTor + +// ================================================================================================= +// PNG_CheckFormat +// =============== + +bool PNG_CheckFormat ( XMP_FileFormat format, + XMP_StringPtr filePath, + XMP_IO* fileRef, + XMPFiles * parent ) +{ + IgnoreParam(format); IgnoreParam(fileRef); IgnoreParam(parent); + XMP_Assert ( format == kXMP_PNGFile ); + + if ( fileRef->Length() < PNG_SIGNATURE_LEN ) return false; + XMP_Uns8 buffer [PNG_SIGNATURE_LEN]; + + fileRef->Rewind(); + fileRef->Read ( buffer, PNG_SIGNATURE_LEN ); + if ( ! CheckBytes ( buffer, PNG_SIGNATURE_DATA, PNG_SIGNATURE_LEN ) ) return false; + + return true; + +} // PNG_CheckFormat + +// ================================================================================================= +// PNG_MetaHandler::PNG_MetaHandler +// ================================== + +PNG_MetaHandler::PNG_MetaHandler ( XMPFiles * _parent ) +{ + this->parent = _parent; + this->handlerFlags = kPNG_HandlerFlags; + this->stdCharForm = kXMP_Char8Bit; + +} + +// ================================================================================================= +// PNG_MetaHandler::~PNG_MetaHandler +// =================================== + +PNG_MetaHandler::~PNG_MetaHandler() +{ +} + +// ================================================================================================= +// PNG_MetaHandler::CacheFileData +// =============================== + +void PNG_MetaHandler::CacheFileData() +{ + + this->containsXMP = false; + + XMP_IO* fileRef ( this->parent->ioRef ); + if ( fileRef == 0) return; + + PNG_Support::ChunkState chunkState; + long numChunks = PNG_Support::OpenPNG ( fileRef, chunkState ); + if ( numChunks == 0 ) return; + + if (chunkState.xmpLen != 0) + { + // XMP present + + this->xmpPacket.reserve(chunkState.xmpLen); + this->xmpPacket.assign(chunkState.xmpLen, ' '); + + if (PNG_Support::ReadBuffer ( fileRef, chunkState.xmpPos, chunkState.xmpLen, const_cast(this->xmpPacket.data()) )) + { + this->packetInfo.offset = chunkState.xmpPos; + this->packetInfo.length = chunkState.xmpLen; + this->containsXMP = true; + } + } + else + { + // no XMP + } + +} // PNG_MetaHandler::CacheFileData + +// ================================================================================================= +// PNG_MetaHandler::ProcessXMP +// ============================ +// +// Process the raw XMP and legacy metadata that was previously cached. + +void PNG_MetaHandler::ProcessXMP() +{ + this->processedXMP = true; // Make sure we only come through here once. + + // Process the XMP packet. + + if ( ! this->xmpPacket.empty() ) { + + XMP_Assert ( this->containsXMP ); + XMP_StringPtr packetStr = this->xmpPacket.c_str(); + XMP_StringLen packetLen = (XMP_StringLen)this->xmpPacket.size(); + + this->xmpObj.ParseFromBuffer ( packetStr, packetLen ); + + this->containsXMP = true; + + } + +} // PNG_MetaHandler::ProcessXMP + +// ================================================================================================= +// PNG_MetaHandler::UpdateFile +// ============================ + +void PNG_MetaHandler::UpdateFile ( bool doSafeUpdate ) +{ + bool updated = false; + + if ( ! this->needsUpdate ) return; + if ( doSafeUpdate ) XMP_Throw ( "PNG_MetaHandler::UpdateFile: Safe update not supported", kXMPErr_Unavailable ); + + XMP_StringPtr packetStr = xmpPacket.c_str(); + XMP_StringLen packetLen = (XMP_StringLen)xmpPacket.size(); + if ( packetLen == 0 ) return; + + XMP_IO* fileRef(this->parent->ioRef); + if ( fileRef == 0 ) return; + + PNG_Support::ChunkState chunkState; + long numChunks = PNG_Support::OpenPNG ( fileRef, chunkState ); + if ( numChunks == 0 ) return; + + // write/update chunk + if (chunkState.xmpLen == 0) + { + // no current chunk -> inject + updated = SafeWriteFile(); + } + else if (chunkState.xmpLen >= packetLen ) + { + // current chunk size is sufficient -> write and update CRC (in place update) + updated = PNG_Support::WriteBuffer(fileRef, chunkState.xmpPos, packetLen, packetStr ); + PNG_Support::UpdateChunkCRC(fileRef, chunkState.xmpChunk ); + } + else if (chunkState.xmpLen < packetLen) + { + // XMP is too large for current chunk -> expand + updated = SafeWriteFile(); + } + + if ( ! updated )return; // If there's an error writing the chunk, bail. + + this->needsUpdate = false; + +} // PNG_MetaHandler::UpdateFile + +// ================================================================================================= +// PNG_MetaHandler::WriteTempFile +// ============================== + +void PNG_MetaHandler::WriteTempFile ( XMP_IO* tempRef ) +{ + XMP_IO* originalRef = this->parent->ioRef; + + PNG_Support::ChunkState chunkState; + long numChunks = PNG_Support::OpenPNG ( originalRef, chunkState ); + if ( numChunks == 0 ) return; + + tempRef->Truncate ( 0 ); + tempRef->Write ( PNG_SIGNATURE_DATA, PNG_SIGNATURE_LEN ); + + PNG_Support::ChunkIterator curPos = chunkState.chunks.begin(); + PNG_Support::ChunkIterator endPos = chunkState.chunks.end(); + + for (; (curPos != endPos); ++curPos) + { + PNG_Support::ChunkData chunk = *curPos; + + // discard existing XMP chunk + if (chunk.xmp) + continue; + + // copy any other chunk + PNG_Support::CopyChunk(originalRef, tempRef, chunk); + + // place XMP chunk immediately after IHDR-chunk + if (PNG_Support::CheckIHDRChunkHeader(chunk)) + { + XMP_StringPtr packetStr = xmpPacket.c_str(); + XMP_StringLen packetLen = (XMP_StringLen)xmpPacket.size(); + + PNG_Support::WriteXMPChunk(tempRef, packetLen, packetStr ); + } + } + +} // PNG_MetaHandler::WriteTempFile + +// ================================================================================================= +// PNG_MetaHandler::SafeWriteFile +// ============================== + +bool PNG_MetaHandler::SafeWriteFile() +{ + XMP_IO* originalFile = this->parent->ioRef; + XMP_IO* tempFile = originalFile->DeriveTemp(); + if ( tempFile == 0 ) XMP_Throw ( "Failure creating PNG temp file", kXMPErr_InternalFailure ); + + this->WriteTempFile ( tempFile ); + originalFile->AbsorbTemp(); + + return true; + +} // PNG_MetaHandler::SafeWriteFile + +// ================================================================================================= diff --git a/core/libs/dngwriter/extra/xmp_sdk/XMPFiles/source/FileHandlers/PNG_Handler.hpp b/core/libs/dngwriter/extra/xmp_sdk/XMPFiles/source/FileHandlers/PNG_Handler.hpp new file mode 100644 index 0000000000..9298141984 --- /dev/null +++ b/core/libs/dngwriter/extra/xmp_sdk/XMPFiles/source/FileHandlers/PNG_Handler.hpp @@ -0,0 +1,62 @@ +#ifndef __PNG_Handler_hpp__ +#define __PNG_Handler_hpp__ 1 + +// ================================================================================================= +// ADOBE SYSTEMS INCORPORATED +// Copyright 2006 Adobe Systems Incorporated +// All Rights Reserved +// +// NOTICE: Adobe permits you to use, modify, and distribute this file in accordance with the terms +// of the Adobe license agreement accompanying it. +// ================================================================================================= + +#include "public/include/XMP_Environment.h" // ! XMP_Environment.h must be the first included header. +#include "public/include/XMP_Const.h" + +#include "XMPFiles/source/FormatSupport/PNG_Support.hpp" +#include "source/XIO.hpp" + +// ================================================================================================= +/// \file PNG_Handler.hpp +/// \brief File format handler for PNG. +/// +/// This header ... +/// +// ================================================================================================= + +// *** Could derive from Basic_Handler - buffer file tail in a temp file. + +extern XMPFileHandler* PNG_MetaHandlerCTor ( XMPFiles* parent ); + +extern bool PNG_CheckFormat ( XMP_FileFormat format, + XMP_StringPtr filePath, + XMP_IO * fileRef, + XMPFiles * parent ); + +static const XMP_OptionBits kPNG_HandlerFlags = ( kXMPFiles_CanInjectXMP | + kXMPFiles_CanExpand | + kXMPFiles_PrefersInPlace | + kXMPFiles_AllowsOnlyXMP | + kXMPFiles_ReturnsRawPacket | + kXMPFiles_NeedsReadOnlyPacket ); + +class PNG_MetaHandler : public XMPFileHandler +{ +public: + + void CacheFileData(); + void ProcessXMP(); + + void UpdateFile ( bool doSafeUpdate ); + void WriteTempFile ( XMP_IO* tempRef ); + + bool SafeWriteFile(); + + PNG_MetaHandler ( XMPFiles* parent ); + virtual ~PNG_MetaHandler(); + +}; // PNG_MetaHandler + +// ================================================================================================= + +#endif /* __PNG_Handler_hpp__ */ diff --git a/core/libs/dngwriter/extra/xmp_sdk/XMPFiles/source/FileHandlers/PSD_Handler.cpp b/core/libs/dngwriter/extra/xmp_sdk/XMPFiles/source/FileHandlers/PSD_Handler.cpp new file mode 100644 index 0000000000..03797619bc --- /dev/null +++ b/core/libs/dngwriter/extra/xmp_sdk/XMPFiles/source/FileHandlers/PSD_Handler.cpp @@ -0,0 +1,428 @@ +// ================================================================================================= +// ADOBE SYSTEMS INCORPORATED +// Copyright 2006 Adobe Systems Incorporated +// All Rights Reserved +// +// NOTICE: Adobe permits you to use, modify, and distribute this file in accordance with the terms +// of the Adobe license agreement accompanying it. +// ================================================================================================= + +#include "public/include/XMP_Environment.h" // ! This must be the first include. +#include "public/include/XMP_Const.h" +#include "public/include/XMP_IO.hpp" + +#include "XMPFiles/source/XMPFiles_Impl.hpp" +#include "source/XIO.hpp" + +#include "XMPFiles/source/FileHandlers/PSD_Handler.hpp" + +#include "XMPFiles/source/FormatSupport/TIFF_Support.hpp" +#include "XMPFiles/source/FormatSupport/PSIR_Support.hpp" +#include "XMPFiles/source/FormatSupport/IPTC_Support.hpp" +#include "XMPFiles/source/FormatSupport/ReconcileLegacy.hpp" +#include "XMPFiles/source/FormatSupport/Reconcile_Impl.hpp" + +#include "XMP_MD5.h" + +using namespace std; + +// ================================================================================================= +/// \file PSD_Handler.cpp +/// \brief File format handler for PSD (Photoshop). +/// +/// This handler ... +/// +// ================================================================================================= + +// ================================================================================================= +// PSD_CheckFormat +// =============== + +// For PSD we just check the "8BPS" signature, the following version, and that the file is at least +// 34 bytes long. This covers the 26 byte header, the 4 byte color mode section length (which might +// be 0), and the 4 byte image resource section length (which might be 0). The parsing logic in +// CacheFileData will do further checks that the image resources actually exist. Those checks are +// not needed to decide if this is a PSD file though, instead they decide if this is valid PSD. + +// ! The CheckXyzFormat routines don't track the filePos, that is left to ScanXyzFile. + +bool PSD_CheckFormat ( XMP_FileFormat format, + XMP_StringPtr filePath, + XMP_IO* fileRef, + XMPFiles * parent ) +{ + IgnoreParam(format); IgnoreParam(filePath); IgnoreParam(parent); + XMP_Assert ( format == kXMP_PhotoshopFile ); + + fileRef->Rewind ( ); + if ( fileRef->Length() < 34 ) return false; // 34 = header plus 2 lengths + + XMP_Uns8 buffer [4]; + fileRef->ReadAll ( buffer, 4 ); + if ( ! CheckBytes ( buffer, "8BPS", 4 ) ) return false; + XMP_Uns16 version = XIO::ReadUns16_BE ( fileRef ); + if ( (version != 1) && (version != 2) ) return false; + + return true; + +} // PSD_CheckFormat + +// ================================================================================================= +// PSD_MetaHandlerCTor +// =================== + +XMPFileHandler * PSD_MetaHandlerCTor ( XMPFiles * parent ) +{ + return new PSD_MetaHandler ( parent ); + +} // PSD_MetaHandlerCTor + +// ================================================================================================= +// PSD_MetaHandler::PSD_MetaHandler +// ================================ + +PSD_MetaHandler::PSD_MetaHandler ( XMPFiles * _parent ) : iptcMgr(0), exifMgr(0), skipReconcile(false),imageWidth(0),imageHeight(0) +{ + this->parent = _parent; + this->handlerFlags = kPSD_HandlerFlags; + this->stdCharForm = kXMP_Char8Bit; + +} // PSD_MetaHandler::PSD_MetaHandler + +// ================================================================================================= +// PSD_MetaHandler::~PSD_MetaHandler +// ================================= + +PSD_MetaHandler::~PSD_MetaHandler() +{ + + if ( this->iptcMgr != 0 ) delete ( this->iptcMgr ); + if ( this->exifMgr != 0 ) delete ( this->exifMgr ); + +} // PSD_MetaHandler::~PSD_MetaHandler + +// ================================================================================================= +// PSD_MetaHandler::CacheFileData +// ============================== +// +// Find and parse the image resource section, everything we want is in there. Don't simply capture +// the whole section, there could be lots of stuff we don't care about. + +// *** This implementation simply returns when an invalid file is encountered. Should we throw instead? + +void PSD_MetaHandler::CacheFileData() +{ + XMP_IO* fileRef = this->parent->ioRef; + XMP_PacketInfo & packetInfo = this->packetInfo; + + XMP_AbortProc abortProc = this->parent->abortProc; + void * abortArg = this->parent->abortArg; + const bool checkAbort = (abortProc != 0); + + XMP_Assert ( ! this->containsXMP ); + // Set containsXMP to true here only if the XMP image resource is found. + + if ( checkAbort && abortProc(abortArg) ) { + XMP_Throw ( "PSD_MetaHandler::CacheFileData - User abort", kXMPErr_UserAbort ); + } + + XMP_Uns8 psdHeader[30]; + XMP_Uns32 ioLen, cmLen; + + XMP_Int64 filePos = 0; + fileRef->Rewind ( ); + + ioLen = fileRef->Read ( psdHeader, 30 ); + if ( ioLen != 30 ) return; // Throw? + + this->imageHeight = GetUns32BE ( &psdHeader[14] ); + this->imageWidth = GetUns32BE ( &psdHeader[18] ); + + cmLen = GetUns32BE ( &psdHeader[26] ); + + XMP_Int64 psirOrigin = 26 + 4 + cmLen; + + filePos = fileRef->Seek ( psirOrigin, kXMP_SeekFromStart ); + if ( filePos != psirOrigin ) return; // Throw? + + if ( ! XIO::CheckFileSpace ( fileRef, 4 ) ) return; // Throw? + XMP_Uns32 psirLen = XIO::ReadUns32_BE ( fileRef ); + + this->psirMgr.ParseFileResources ( fileRef, psirLen ); + + PSIR_Manager::ImgRsrcInfo xmpInfo; + bool found = this->psirMgr.GetImgRsrc ( kPSIR_XMP, &xmpInfo ); + + if ( found ) { + + // printf ( "PSD_MetaHandler::CacheFileData - XMP packet offset %d (0x%X), size %d\n", + // xmpInfo.origOffset, xmpInfo.origOffset, xmpInfo.dataLen ); + this->packetInfo.offset = xmpInfo.origOffset; + this->packetInfo.length = xmpInfo.dataLen; + this->packetInfo.padSize = 0; // Assume for now, set these properly in ProcessXMP. + this->packetInfo.charForm = kXMP_CharUnknown; + this->packetInfo.writeable = true; + + this->xmpPacket.assign ( (XMP_StringPtr)xmpInfo.dataPtr, xmpInfo.dataLen ); + + this->containsXMP = true; + + } + +} // PSD_MetaHandler::CacheFileData + +// ================================================================================================= +// PSD_MetaHandler::ProcessXMP +// =========================== +// +// Process the raw XMP and legacy metadata that was previously cached. + +void PSD_MetaHandler::ProcessXMP() +{ + + this->processedXMP = true; // Make sure we only come through here once. + + // Set up everything for the legacy import, but don't do it yet. This lets us do a forced legacy + // import if the XMP packet gets parsing errors. + + bool readOnly = false; + if ( this->parent ) + readOnly = ((this->parent->openFlags & kXMPFiles_OpenForUpdate) == 0); + + if ( readOnly ) { + this->iptcMgr = new IPTC_Reader(); + this->exifMgr = new TIFF_MemoryReader(); + } else { + this->iptcMgr = new IPTC_Writer(); // ! Parse it later. + this->exifMgr = new TIFF_FileWriter(); + } + if ( this->parent ) + exifMgr->SetErrorCallback( &this->parent->errorCallback ); + + PSIR_Manager & psir = this->psirMgr; // Give the compiler help in recognizing non-aliases. + IPTC_Manager & iptc = *this->iptcMgr; + TIFF_Manager & exif = *this->exifMgr; + + PSIR_Manager::ImgRsrcInfo iptcInfo, exifInfo; + bool haveIPTC = psir.GetImgRsrc ( kPSIR_IPTC, &iptcInfo ); + bool haveExif = psir.GetImgRsrc ( kPSIR_Exif, &exifInfo ); + int iptcDigestState = kDigestMatches; + + if ( haveExif ) exif.ParseMemoryStream ( exifInfo.dataPtr, exifInfo.dataLen ); + + if ( haveIPTC ) { + + bool haveDigest = false; + PSIR_Manager::ImgRsrcInfo digestInfo; + haveDigest = psir.GetImgRsrc ( kPSIR_IPTCDigest, &digestInfo ); + if ( digestInfo.dataLen != 16 ) haveDigest = false; + + if ( ! haveDigest ) { + iptcDigestState = kDigestMissing; + } else { + iptcDigestState = PhotoDataUtils::CheckIPTCDigest ( iptcInfo.dataPtr, iptcInfo.dataLen, digestInfo.dataPtr ); + } + + } + + XMP_OptionBits options = 0; + if ( this->containsXMP ) options |= k2XMP_FileHadXMP; + if ( haveIPTC ) options |= k2XMP_FileHadIPTC; + if ( haveExif ) options |= k2XMP_FileHadExif; + + // Process the XMP packet. If it fails to parse, do a forced legacy import but still throw an + // exception. This tells the caller that an error happened, but gives them recovered legacy + // should they want to proceed with that. + + bool haveXMP = false; + + if ( ! this->xmpPacket.empty() ) { + XMP_Assert ( this->containsXMP ); + // Common code takes care of packetInfo.charForm, .padSize, and .writeable. + XMP_StringPtr packetStr = this->xmpPacket.c_str(); + XMP_StringLen packetLen = (XMP_StringLen)this->xmpPacket.size(); + try { + this->xmpObj.ParseFromBuffer ( packetStr, packetLen ); + } catch ( ... ) { /* Ignore parsing failures, someday we hope to get partial XMP back. */ } + haveXMP = true; + } + + // Process the legacy metadata. + + if ( haveIPTC && (! haveXMP) && (iptcDigestState == kDigestMatches) ) iptcDigestState = kDigestMissing; + if (iptcInfo.dataLen) iptc.ParseMemoryDataSets ( iptcInfo.dataPtr, iptcInfo.dataLen ); + ImportPhotoData ( exif, iptc, psir, iptcDigestState, &this->xmpObj, options ); + this->containsXMP = true; // Assume we now have something in the XMP. + +} // PSD_MetaHandler::ProcessXMP + +// ================================================================================================= +// PSD_MetaHandler::UpdateFile +// =========================== + +void PSD_MetaHandler::UpdateFile ( bool doSafeUpdate ) +{ + XMP_Assert ( ! doSafeUpdate ); // This should only be called for "unsafe" updates. + + XMP_Int64 oldPacketOffset = this->packetInfo.offset; + XMP_Int32 oldPacketLength = this->packetInfo.length; + + if ( oldPacketOffset == kXMPFiles_UnknownOffset ) oldPacketOffset = 0; // ! Simplify checks. + if ( oldPacketLength == kXMPFiles_UnknownLength ) oldPacketLength = 0; + + bool fileHadXMP = ((oldPacketOffset != 0) && (oldPacketLength != 0)); + + // Update the IPTC-IIM and native TIFF/Exif metadata. ExportPhotoData also trips the tiff: and + // exif: copies from the XMP, so reserialize the now final XMP packet. + + ExportPhotoData ( kXMP_PhotoshopFile, &this->xmpObj, this->exifMgr, this->iptcMgr, &this->psirMgr ); + + try { + XMP_OptionBits options = kXMP_UseCompactFormat; + if ( fileHadXMP ) options |= kXMP_ExactPacketLength; + this->xmpObj.SerializeToBuffer ( &this->xmpPacket, options, oldPacketLength ); + } catch ( ... ) { + this->xmpObj.SerializeToBuffer ( &this->xmpPacket, kXMP_UseCompactFormat ); + } + + // Decide whether to do an in-place update. This can only happen if all of the following are true: + // - There is an XMP packet in the file. + // - The are no changes to the legacy image resources. (The IPTC and EXIF are in the PSIR.) + // - The new XMP can fit in the old space. + + bool doInPlace = (fileHadXMP && (this->xmpPacket.size() <= (size_t)oldPacketLength)); + if ( this->psirMgr.IsLegacyChanged() ) doInPlace = false; + XMP_ProgressTracker* progressTracker = this->parent->progressTracker; + + if ( doInPlace ) { + + #if GatherPerformanceData + sAPIPerf->back().extraInfo += ", PSD in-place update"; + #endif + + if ( this->xmpPacket.size() < (size_t)this->packetInfo.length ) { + // They ought to match, cheap to be sure. + size_t extraSpace = (size_t)this->packetInfo.length - this->xmpPacket.size(); + this->xmpPacket.append ( extraSpace, ' ' ); + } + + XMP_IO* liveFile = this->parent->ioRef; + + XMP_Assert ( this->xmpPacket.size() == (size_t)oldPacketLength ); // ! Done by common PutXMP logic. + + if ( progressTracker != 0 ) progressTracker->BeginWork ( this->xmpPacket.size() ); + liveFile->Seek ( oldPacketOffset, kXMP_SeekFromStart ); + liveFile->Write ( this->xmpPacket.c_str(), (XMP_StringLen)this->xmpPacket.size() ); + if ( progressTracker != 0 ) progressTracker->WorkComplete(); + + } else { + + #if GatherPerformanceData + sAPIPerf->back().extraInfo += ", PSD copy update"; + #endif + + XMP_IO* origRef = this->parent->ioRef; + XMP_IO* tempRef = origRef->DeriveTemp(); + + try { + XMP_Assert ( ! this->skipReconcile ); + this->skipReconcile = true; + this->WriteTempFile ( tempRef ); + this->skipReconcile = false; + } catch ( ... ) { + this->skipReconcile = false; + origRef->DeleteTemp(); + throw; + } + + origRef->AbsorbTemp(); + + } + + this->needsUpdate = false; + +} // PSD_MetaHandler::UpdateFile + +// ================================================================================================= +// PSD_MetaHandler::WriteTempFile +// ============================== + +// The metadata parts of a Photoshop file are all in the image resources. The PSIR_Manager's +// UpdateFileResources method will take care of the image resource portion of the file, updating +// those resources that have changed and preserving those that have not. + +void PSD_MetaHandler::WriteTempFile ( XMP_IO* tempRef ) +{ + XMP_IO* origRef = this->parent->ioRef; + + XMP_AbortProc abortProc = this->parent->abortProc; + void * abortArg = this->parent->abortArg; + const bool checkAbort = (abortProc != 0); + XMP_ProgressTracker* progressTracker = this->parent->progressTracker; + + XMP_Uns64 sourceLen = origRef->Length(); + if ( sourceLen == 0 ) return; // Tolerate empty files. + + // Reconcile the legacy metadata, unless this is called from UpdateFile. Reserialize the XMP to + // get standard padding, PutXMP has probably done an in-place serialize. Set the XMP image resource. + + if ( ! skipReconcile ) { + // Update the IPTC-IIM and native TIFF/Exif metadata, and reserialize the now final XMP packet. + ExportPhotoData ( kXMP_JPEGFile, &this->xmpObj, this->exifMgr, this->iptcMgr, &this->psirMgr ); + this->xmpObj.SerializeToBuffer ( &this->xmpPacket, kXMP_UseCompactFormat ); + } + + this->xmpObj.SerializeToBuffer ( &this->xmpPacket, kXMP_UseCompactFormat ); + this->packetInfo.offset = kXMPFiles_UnknownOffset; + this->packetInfo.length = (XMP_StringLen)this->xmpPacket.size(); + FillPacketInfo ( this->xmpPacket, &this->packetInfo ); + + this->psirMgr.SetImgRsrc ( kPSIR_XMP, this->xmpPacket.c_str(), (XMP_StringLen)this->xmpPacket.size() ); + + // Calculate the total writes I/O to be done by this method. This includes header section, color + // mode section and tail length after the image resources section. The write I/O for image + // resources section is added to total work in PSIR_FileWriter::UpdateFileResources. + + origRef->Seek ( 26, kXMP_SeekFromStart ); //move to the point after Header 26 is the header length + + XMP_Uns32 cmLen,cmLen1; + origRef->Read ( &cmLen, 4 ); // get the length of color mode section + cmLen1 = GetUns32BE ( &cmLen ); + origRef->Seek ( cmLen1, kXMP_SeekFromCurrent ); //move to the end of color mode section + + XMP_Uns32 irLen; + origRef->Read ( &irLen, 4 ); // Get the source image resource section length. + irLen = GetUns32BE ( &irLen ); + + XMP_Uns64 tailOffset = 26 + 4 + cmLen1 + 4 + irLen; + XMP_Uns64 tailLength = sourceLen - tailOffset; + + // Add work for 26 bytes header, 4 bytes color mode section length, color mode section length + // and tail length after the image resources section length. + + if ( progressTracker != 0 ) progressTracker->BeginWork ( (float)(26.0f + 4.0f + cmLen1 + tailLength) ); + + // Copy the file header and color mode section, then write the updated image resource section, + // and copy the tail of the source file (layer and mask section to EOF). + + origRef->Rewind ( ); + tempRef->Truncate ( 0 ); + XIO::Copy ( origRef, tempRef, 26 ); // Copy the file header. + + origRef->Seek ( 4, kXMP_SeekFromCurrent ); + tempRef->Write ( &cmLen, 4 ); // Copy the color mode section length. + + XIO::Copy ( origRef, tempRef, cmLen1 ); // Copy the color mode section contents. + + this->psirMgr.UpdateFileResources ( origRef, tempRef, abortProc, abortArg ,progressTracker ); + + origRef->Seek ( tailOffset, kXMP_SeekFromStart ); + tempRef->Seek ( 0, kXMP_SeekFromEnd ); + XIO::Copy ( origRef, tempRef, tailLength ); // Copy the tail of the file. + + this->needsUpdate = false; + if ( progressTracker != 0 ) progressTracker->WorkComplete(); + +} // PSD_MetaHandler::WriteTempFile + +// ================================================================================================= diff --git a/core/libs/dngwriter/extra/xmp_sdk/XMPFiles/source/FileHandlers/PSD_Handler.hpp b/core/libs/dngwriter/extra/xmp_sdk/XMPFiles/source/FileHandlers/PSD_Handler.hpp new file mode 100644 index 0000000000..51dd4b9c3a --- /dev/null +++ b/core/libs/dngwriter/extra/xmp_sdk/XMPFiles/source/FileHandlers/PSD_Handler.hpp @@ -0,0 +1,73 @@ +#ifndef __PSD_Handler_hpp__ +#define __PSD_Handler_hpp__ 1 + +// ================================================================================================= +// ADOBE SYSTEMS INCORPORATED +// Copyright 2006 Adobe Systems Incorporated +// All Rights Reserved +// +// NOTICE: Adobe permits you to use, modify, and distribute this file in accordance with the terms +// of the Adobe license agreement accompanying it. +// ================================================================================================= + +#include "XMPFiles/source/FormatSupport/TIFF_Support.hpp" +#include "XMPFiles/source/FormatSupport/PSIR_Support.hpp" +#include "XMPFiles/source/FormatSupport/IPTC_Support.hpp" + +// ================================================================================================= +/// \file PSD_Handler.hpp +/// \brief File format handler for PSD (Photoshop). +/// +/// This header ... +/// +// ================================================================================================= + +// *** Could derive from Basic_Handler - buffer file tail in a temp file. + +extern XMPFileHandler * PSD_MetaHandlerCTor ( XMPFiles * parent ); + +extern bool PSD_CheckFormat ( XMP_FileFormat format, + XMP_StringPtr filePath, + XMP_IO* fileRef, + XMPFiles * parent ); + +static const XMP_OptionBits kPSD_HandlerFlags = (kXMPFiles_CanInjectXMP | + kXMPFiles_CanExpand | + kXMPFiles_CanRewrite | + kXMPFiles_PrefersInPlace | + kXMPFiles_CanReconcile | + kXMPFiles_AllowsOnlyXMP | + kXMPFiles_ReturnsRawPacket | + kXMPFiles_AllowsSafeUpdate | + kXMPFiles_CanNotifyProgress); + +class PSD_MetaHandler : public XMPFileHandler +{ +public: + + void CacheFileData(); + void ProcessXMP(); + + void UpdateFile ( bool doSafeUpdate ); + void WriteTempFile ( XMP_IO* tempRef ); + + bool skipReconcile; // ! Used between UpdateFile and WriteFile. + + PSD_MetaHandler ( XMPFiles * parent ); + virtual ~PSD_MetaHandler(); + +private: + + PSD_MetaHandler() : iptcMgr(0), exifMgr(0), skipReconcile(false) {}; // Hidden on purpose. + + PSIR_FileWriter psirMgr; // Don't need a pointer, the PSIR part is always file-based. + IPTC_Manager * iptcMgr; // Need to use pointers so we can properly select between read-only + TIFF_Manager * exifMgr; // and read-write modes of usage. + + XMP_Uns32 imageWidth, imageHeight; // Pixel dimensions, used with thumbnail info. + +}; // PSD_MetaHandler + +// ================================================================================================= + +#endif /* __PSD_Handler_hpp__ */ diff --git a/core/libs/dngwriter/extra/xmp_sdk/XMPFiles/source/FileHandlers/PostScript_Handler.cpp b/core/libs/dngwriter/extra/xmp_sdk/XMPFiles/source/FileHandlers/PostScript_Handler.cpp new file mode 100644 index 0000000000..44677945a5 --- /dev/null +++ b/core/libs/dngwriter/extra/xmp_sdk/XMPFiles/source/FileHandlers/PostScript_Handler.cpp @@ -0,0 +1,1844 @@ +// ================================================================================================= +// ADOBE SYSTEMS INCORPORATED +// Copyright 2004 Adobe Systems Incorporated +// All Rights Reserved +// +// NOTICE: Adobe permits you to use, modify, and distribute this file in accordance with the terms +// of the Adobe license agreement accompanying it. +// ================================================================================================= + +#include "public/include/XMP_Environment.h" // ! XMP_Environment.h must be the first included header. + +#include "public/include/XMP_Const.h" +#include "public/include/XMP_IO.hpp" + +#include "XMPFiles/source/XMPFiles_Impl.hpp" +#include "source/XMPFiles_IO.hpp" +#include "source/XIO.hpp" + +#include "XMPFiles/source/FileHandlers/PostScript_Handler.hpp" + +#include "XMPFiles/source/FormatSupport/XMPScanner.hpp" +#include "XMPFiles/source/FileHandlers/Scanner_Handler.hpp" + +#include + +using namespace std; +// ================================================================================================= +/// \file PostScript_Handler.cpp +/// \brief File format handler for PostScript and EPS files. +/// +// ================================================================================================= + +// ================================================================================================= +// PostScript_MetaHandlerCTor +// ========================== + +XMPFileHandler * PostScript_MetaHandlerCTor ( XMPFiles * parent ) +{ + XMPFileHandler * newHandler = new PostScript_MetaHandler ( parent ); + + return newHandler; + +} // PostScript_MetaHandlerCTor + +// ================================================================================================= +// PostScript_CheckFormat +// ====================== + +bool PostScript_CheckFormat ( XMP_FileFormat format, + XMP_StringPtr filePath, + XMP_IO* fileRef, + XMPFiles * parent ) +{ + IgnoreParam(filePath); IgnoreParam(parent); + XMP_Assert ( (format == kXMP_EPSFile) || (format == kXMP_PostScriptFile) ); + + return PostScript_Support::IsValidPSFile(fileRef,format) ; + +} // PostScript_CheckFormat + +// ================================================================================================= +// PostScript_MetaHandler::PostScript_MetaHandler +// ============================================== + +PostScript_MetaHandler::PostScript_MetaHandler ( XMPFiles * _parent ):dscFlags(0),docInfoFlags(0) + ,containsXMPHint(false),fileformat(kXMP_UnknownFile) +{ + this->parent = _parent; + this->handlerFlags = kPostScript_HandlerFlags; + this->stdCharForm = kXMP_Char8Bit; + this->psHint = kPSHint_NoMarker; + +} // PostScript_MetaHandler::PostScript_MetaHandler + +// ================================================================================================= +// PostScript_MetaHandler::~PostScript_MetaHandler +// =============================================== + +PostScript_MetaHandler::~PostScript_MetaHandler() +{ + // ! Inherit the base cleanup. + +} // PostScript_MetaHandler::~PostScript_MetaHandler + +// ================================================================================================= +// PostScript_MetaHandler::FindPostScriptHint +// ========================================== +// +// Search for "%ADO_ContainsXMP:" at the beginning of a line, it must be before "%%EndComments". If +// the XMP marker is found, look for the MainFirst/MainLast/NoMain options. + +int PostScript_MetaHandler::FindPostScriptHint() +{ + bool found = false; + IOBuffer ioBuf; + XMP_Uns8 ch; + + XMP_IO* fileRef = this->parent->ioRef; + + XMP_AbortProc abortProc = this->parent->abortProc; + void * abortArg = this->parent->abortArg; + const bool checkAbort = (abortProc != 0); + + // Check for the binary EPSF preview header. + + fileRef->Rewind(); + if ( ! CheckFileSpace ( fileRef, &ioBuf, 4 ) ) return false; + XMP_Uns32 fileheader = GetUns32BE ( ioBuf.ptr ); + + if ( fileheader == 0xC5D0D3C6 ) { + + if ( ! CheckFileSpace ( fileRef, &ioBuf, 30 ) ) return false; + + XMP_Uns32 psOffset = GetUns32LE ( ioBuf.ptr+4 ); // PostScript offset. + XMP_Uns32 psLength = GetUns32LE ( ioBuf.ptr+8 ); // PostScript length. + + MoveToOffset ( fileRef, psOffset, &ioBuf ); + + } + + // Look for the ContainsXMP comment. + + while ( true ) { + + if ( checkAbort && abortProc(abortArg) ) { + XMP_Throw ( "PostScript_MetaHandler::FindPostScriptHint - User abort", kXMPErr_UserAbort ); + } + + if ( ! CheckFileSpace ( fileRef, &ioBuf, kPSContainsXMPString.length() ) ) return kPSHint_NoMarker; + + if ( CheckBytes ( ioBuf.ptr, Uns8Ptr(kPSEndCommentString.c_str()), kPSEndCommentString.length() ) ) { + + // Found "%%EndComments", don't look any further. + return kPSHint_NoMarker; + + } else if ( ! CheckBytes ( ioBuf.ptr, Uns8Ptr(kPSContainsXMPString.c_str()), kPSContainsXMPString.length() ) ) { + + // Not "%%EndComments" or "%ADO_ContainsXMP:", skip past the end of this line. + do { + if ( ! CheckFileSpace ( fileRef, &ioBuf, 1 ) ) return kPSHint_NoMarker; + ch = *ioBuf.ptr; + ++ioBuf.ptr; + } while ( ! IsNewline ( ch ) ); + + } else { + + // Found "%ADO_ContainsXMP:", look for the main packet location option. + + ioBuf.ptr += kPSContainsXMPString.length(); + int xmpHint = kPSHint_NoMain; // ! From here on, a failure means "no main", not "no marker". + if ( ! CheckFileSpace ( fileRef, &ioBuf, 1 ) ) return kPSHint_NoMain; + if ( ! IsSpaceOrTab ( *ioBuf.ptr ) ) return kPSHint_NoMain; + + while ( true ) { + + while ( true ) { // Skip leading spaces and tabs. + if ( ! CheckFileSpace ( fileRef, &ioBuf, 1 ) ) return kPSHint_NoMain; + if ( ! IsSpaceOrTab ( *ioBuf.ptr ) ) break; + ++ioBuf.ptr; + } + if ( IsNewline ( *ioBuf.ptr ) ) return kPSHint_NoMain; // Reached the end of the ContainsXMP comment. + + if ( ! CheckFileSpace ( fileRef, &ioBuf, 6 ) ) return kPSHint_NoMain; + + if ( CheckBytes ( ioBuf.ptr, Uns8Ptr("NoMain"), 6 ) ) { + + ioBuf.ptr += 6; + xmpHint = kPSHint_NoMain; + break; + + } else if ( CheckBytes ( ioBuf.ptr, Uns8Ptr("MainFi"), 6 ) ) { + + ioBuf.ptr += 6; + if ( ! CheckFileSpace ( fileRef, &ioBuf, 3 ) ) return kPSHint_NoMain; + if ( CheckBytes ( ioBuf.ptr, Uns8Ptr("rst"), 3 ) ) { + ioBuf.ptr += 3; + xmpHint = kPSHint_MainFirst; + } + break; + + } else if ( CheckBytes ( ioBuf.ptr, Uns8Ptr("MainLa"), 6 ) ) { + + ioBuf.ptr += 6; + if ( ! CheckFileSpace ( fileRef, &ioBuf, 2 ) ) return kPSHint_NoMain; + if ( CheckBytes ( ioBuf.ptr, Uns8Ptr("st"), 2 ) ) { + ioBuf.ptr += 2; + xmpHint = kPSHint_MainLast; + } + break; + + } else { + + while ( true ) { // Skip until whitespace. + if ( ! CheckFileSpace ( fileRef, &ioBuf, 1 ) ) return kPSHint_NoMain; + if ( IsWhitespace ( *ioBuf.ptr ) ) break; + ++ioBuf.ptr; + } + + } + + } // Look for the main packet location option. + + // Make sure we found exactly a known option. + if ( ! CheckFileSpace ( fileRef, &ioBuf, 1 ) ) return kPSHint_NoMain; + if ( ! IsWhitespace ( *ioBuf.ptr ) ) return kPSHint_NoMain; + return xmpHint; + + } // Found "%ADO_ContainsXMP:". + + } // Outer marker loop. + + return kPSHint_NoMarker; // Should never reach here. + +} // PostScript_MetaHandler::FindPostScriptHint + + +// ================================================================================================= +// PostScript_MetaHandler::FindFirstPacket +// ======================================= +// +// Run the packet scanner until we find a valid packet. The first one is the main. For simplicity, +// the state of all snips is checked after each buffer is read. In theory only the last of the +// previous snips might change from partial to valid, but then we would have to special case the +// first pass when there is no previous set of snips. Since we have to get a full report to look at +// the last snip anyway, it costs virtually nothing extra to recheck all of the snips. + +bool PostScript_MetaHandler::FindFirstPacket() +{ + int snipCount; + bool found = false; + size_t bufPos, bufLen; + + XMP_IO* fileRef = this->parent->ioRef; + XMP_Int64 fileLen = fileRef->Length(); + XMP_PacketInfo & packetInfo = this->packetInfo; + + XMPScanner scanner ( fileLen ); + XMPScanner::SnipInfoVector snips; + + enum { kBufferSize = 64*1024 }; + XMP_Uns8 buffer [kBufferSize]; + + XMP_AbortProc abortProc = this->parent->abortProc; + void * abortArg = this->parent->abortArg; + const bool checkAbort = (abortProc != 0); + + bufPos = 0; + bufLen = 0; + + fileRef->Rewind(); // Seek back to the beginning of the file. + bool firstfound=false; + + while ( true ) + { + + if ( checkAbort && abortProc(abortArg) ) + { + XMP_Throw ( "PostScript_MetaHandler::FindFirstPacket - User abort", kXMPErr_UserAbort ); + } + + bufPos += bufLen; + bufLen = fileRef->Read ( buffer, kBufferSize ); + if ( bufLen == 0 ) return firstfound; // Must be at EoF, no packets found. + + scanner.Scan ( buffer, bufPos, bufLen ); + snipCount = scanner.GetSnipCount(); + scanner.Report ( snips ); + for ( int i = 0; i < snipCount; ++i ) + { + if ( snips[i].fState == XMPScanner::eValidPacketSnip ) + { + if (!firstfound) + { + if ( snips[i].fLength > 0x7FFFFFFF ) XMP_Throw ( "PostScript_MetaHandler::FindFirstPacket: Oversize packet", kXMPErr_BadXMP ); + packetInfo.offset = snips[i].fOffset; + packetInfo.length = (XMP_Int32)snips[i].fLength; + packetInfo.charForm = snips[i].fCharForm; + packetInfo.writeable = (snips[i].fAccess == 'w'); + firstPacketInfo=packetInfo; + lastPacketInfo=packetInfo; + firstfound=true; + } + else + { + lastPacketInfo.offset = snips[i].fOffset; + lastPacketInfo.length = (XMP_Int32)snips[i].fLength; + lastPacketInfo.charForm = snips[i].fCharForm; + lastPacketInfo.writeable = (snips[i].fAccess == 'w'); + } + } + } + + } + + return firstfound; + +} // FindFirstPacket + + +// ================================================================================================= +// PostScript_MetaHandler::FindLastPacket +// ====================================== +// +// Run the packet scanner backwards until we find the start of a packet, or a valid packet. If we +// found a packet start, resume forward scanning to see if it is a valid packet. For simplicity, all +// of the snips are checked on each pass, for much the same reasons as in FindFirstPacket. + +bool PostScript_MetaHandler::FindLastPacket() +{ + size_t bufPos, bufLen; + + XMP_IO* fileRef = this->parent->ioRef; + XMP_Int64 fileLen = fileRef->Length(); + XMP_PacketInfo & packetInfo = this->packetInfo; + + // ------------------------------------------------------ + // Scan the entire file to find all of the valid packets. + + XMPScanner scanner ( fileLen ); + + enum { kBufferSize = 64*1024 }; + XMP_Uns8 buffer [kBufferSize]; + + XMP_AbortProc abortProc = this->parent->abortProc; + void * abortArg = this->parent->abortArg; + const bool checkAbort = (abortProc != 0); + + fileRef->Rewind(); // Seek back to the beginning of the file. + + for ( bufPos = 0; bufPos < (size_t)fileLen; bufPos += bufLen ) + { + if ( checkAbort && abortProc(abortArg) ) + { + XMP_Throw ( "PostScript_MetaHandler::FindLastPacket - User abort", kXMPErr_UserAbort ); + } + bufLen = fileRef->Read ( buffer, kBufferSize ); + if ( bufLen == 0 ) XMP_Throw ( "PostScript_MetaHandler::FindLastPacket: Read failure", kXMPErr_ExternalFailure ); + scanner.Scan ( buffer, bufPos, bufLen ); + } + + // ------------------------------- + // Pick the last the valid packet. + + int snipCount = scanner.GetSnipCount(); + + XMPScanner::SnipInfoVector snips ( snipCount ); + scanner.Report ( snips ); + + bool lastfound=false; + for ( int i = 0; i < snipCount; ++i ) + { + if ( snips[i].fState == XMPScanner::eValidPacketSnip ) + { + if (!lastfound) + { + if ( snips[i].fLength > 0x7FFFFFFF ) XMP_Throw ( "PostScript_MetaHandler::FindLastPacket: Oversize packet", kXMPErr_BadXMP ); + packetInfo.offset = snips[i].fOffset; + packetInfo.length = (XMP_Int32)snips[i].fLength; + packetInfo.charForm = snips[i].fCharForm; + packetInfo.writeable = (snips[i].fAccess == 'w'); + firstPacketInfo=packetInfo; + lastPacketInfo=packetInfo; + lastfound=true; + } + else + { + lastPacketInfo.offset = snips[i].fOffset; + lastPacketInfo.length = (XMP_Int32)snips[i].fLength; + lastPacketInfo.charForm = snips[i].fCharForm; + lastPacketInfo.writeable = (snips[i].fAccess == 'w'); + packetInfo=lastPacketInfo; + } + } + } + return lastfound; + +} // PostScript_MetaHandler::FindLastPacket + +// ================================================================================================= +// PostScript_MetaHandler::setTokenInfo +// ==================================== +// +// Function Sets the docInfo flag for tokens(PostScript DSC comments/ Docinfo Dictionary values) +// when parsing the file stream.Also takes note of the token offset from the start of the file +// and the length of the token +void PostScript_MetaHandler::setTokenInfo(TokenFlag tFlag,XMP_Int64 offset,XMP_Int64 length) +{ + if (!(docInfoFlags&tFlag)&&tFlag>=kPS_ADOContainsXMP && tFlag<=kPS_EndPostScript) + { + size_t index=0; + XMP_Uns64 flag=tFlag; + while(flag>>=1) index++; + fileTokenInfo[index].offsetStart=offset; + fileTokenInfo[index].tokenlen=length; + docInfoFlags|=tFlag; + } +} + +// ================================================================================================= +// PostScript_MetaHandler::getTokenInfo +// ==================================== +// +// Function returns the token info for the flag, which was collected in parsing the input file +// +PostScript_MetaHandler::TokenLocation& PostScript_MetaHandler::getTokenInfo(TokenFlag tFlag) +{ + if ((docInfoFlags&tFlag)&&tFlag>=kPS_ADOContainsXMP && tFlag<=kPS_EndPostScript) + { + size_t index=0; + XMP_Uns64 flag=tFlag; + while(flag>>=1) index++; + return fileTokenInfo[index]; + } + return fileTokenInfo[kPS_NoData]; +} + +// ================================================================================================= +// PostScript_MetaHandler::ExtractDSCCommentValue +// ============================================== +// +// Function extracts the DSC comment value when parsing the file.This may be later used to reconcile +// +bool PostScript_MetaHandler::ExtractDSCCommentValue(IOBuffer &ioBuf,NativeMetadataIndex index) +{ + + XMP_IO* fileRef = this->parent->ioRef; + if ( ! PostScript_Support::SkipTabsAndSpaces(fileRef,ioBuf) ) return false; + if ( !IsNewline ( *ioBuf.ptr ) ) + { + do + { + if ( ! CheckFileSpace ( fileRef, &ioBuf, 1 ) ) return false; + nativeMeta[index] += *ioBuf.ptr; + ++ioBuf.ptr; + } while ( ! IsNewline ( *ioBuf.ptr) ); + if (!PostScript_Support::HasCodesGT127(nativeMeta[index])) + { + dscFlags|=nativeIndextoFlag[index]; + } + else + nativeMeta[index].clear(); + } + return true; +} + + +// ================================================================================================= +// PostScript_MetaHandler::ExtractContainsXMPHint +// ============================================== +// +// Function extracts the the value of "ADOContainsXMP:" DSC comment's value +// +bool PostScript_MetaHandler::ExtractContainsXMPHint(IOBuffer &ioBuf,XMP_Int64 containsXMPStartpos) +{ + XMP_IO* fileRef = this->parent->ioRef; + int xmpHint = kPSHint_NoMain; // ! From here on, a failure means "no main", not "no marker". + //checkfor atleast one whitespace + if ( ! CheckFileSpace ( fileRef, &ioBuf, 1 ) ) return false; + if ( ! IsSpaceOrTab ( *ioBuf.ptr ) ) return false; + //skip extra ones + if ( ! PostScript_Support::SkipTabsAndSpaces(fileRef,ioBuf) ) return false; + if ( IsNewline ( *ioBuf.ptr ) ) return false; // Reached the end of the ContainsXMP comment. + + if ( ! CheckFileSpace ( fileRef, &ioBuf, 6 ) ) return false ; + + if ( CheckBytes ( ioBuf.ptr, Uns8Ptr("NoMain"), 6 ) ) + { + ioBuf.ptr += 6; + if ( ! PostScript_Support::SkipTabsAndSpaces(fileRef,ioBuf) ) return false; + if ( ! IsNewline( *ioBuf.ptr) ) return false; + this->psHint = kPSHint_NoMain; + setTokenInfo(kPS_ADOContainsXMP,containsXMPStartpos,ioBuf.filePos+ioBuf.ptr-ioBuf.data-containsXMPStartpos); + + } + else if ( CheckBytes ( ioBuf.ptr, Uns8Ptr("MainFi"), 6 ) ) + { + ioBuf.ptr += 6; + if ( ! CheckFileSpace ( fileRef, &ioBuf, 3 ) ) return false; + if ( CheckBytes ( ioBuf.ptr, Uns8Ptr("rst"), 3 ) ) + { + ioBuf.ptr += 3; + if ( ! PostScript_Support::SkipTabsAndSpaces(fileRef,ioBuf) ) return false; + if ( ! IsNewline( *ioBuf.ptr) ) return false; + this->psHint = kPSHint_MainFirst; + setTokenInfo(kPS_ADOContainsXMP,containsXMPStartpos,ioBuf.filePos+ioBuf.ptr-ioBuf.data-containsXMPStartpos); + containsXMPHint=true; + } + } + else if ( CheckBytes ( ioBuf.ptr, Uns8Ptr("MainLa"), 6 ) ) + { + ioBuf.ptr += 6; + if ( ! CheckFileSpace ( fileRef, &ioBuf, 2 ) ) return false; + if ( CheckBytes ( ioBuf.ptr, Uns8Ptr("st"), 2 ) ) { + ioBuf.ptr += 2; + if ( ! PostScript_Support::SkipTabsAndSpaces(fileRef,ioBuf) ) return false; + if ( ! IsNewline( *ioBuf.ptr) ) return false; + this->psHint = kPSHint_MainLast; + setTokenInfo(kPS_ADOContainsXMP,containsXMPStartpos,ioBuf.filePos+ioBuf.ptr-ioBuf.data-containsXMPStartpos); + containsXMPHint=true; + } + } + else + { + if ( ! PostScript_Support::SkipUntilNewline(fileRef,ioBuf) ) return false; + } + return true; +} + + +// ================================================================================================= +// PostScript_MetaHandler::ExtractDocInfoDict +// ============================================== +// +// Function extracts the the value of DocInfo Dictionary.The keys that are looked in the dictionary +// are Creator, CreationDate, ModDate, Author, Title, Subject and Keywords.Other keys for the +// Dictionary are ignored +bool PostScript_MetaHandler::ExtractDocInfoDict(IOBuffer &ioBuf) +{ + XMP_Uns8 ch; + XMP_IO* fileRef = this->parent->ioRef; + XMP_Int64 endofDocInfo=(ioBuf.ptr-ioBuf.data)+ioBuf.filePos; + if ( ! CheckFileSpace ( fileRef, &ioBuf, 1 ) ) return false; + if ( IsWhitespace (*ioBuf.ptr)) + { + //skip the whitespaces + if ( ! ( PostScript_Support::SkipTabsAndSpaces(fileRef, ioBuf))) return false; + //check the pdfmark + if ( ! CheckFileSpace ( fileRef, &ioBuf, kPSContainsPdfmarkString.length() ) ) return false; + if ( ! CheckBytes ( ioBuf.ptr, Uns8Ptr(kPSContainsPdfmarkString.c_str()), kPSContainsPdfmarkString.length() ) ) return false; + //reverse the direction to collect data + while(true) + { + if ( ! PostScript_Support::RevCheckFileSpace ( fileRef, &ioBuf, 1 ) ) return false; + ch=*ioBuf.ptr; + --ioBuf.ptr; + if (ch=='/') break;//slash of /DOCINFO + } + //skip white spaces + while(true) + { + if ( ! PostScript_Support::RevCheckFileSpace ( fileRef, &ioBuf, 1 ) ) return false; + if (!IsWhitespace(*ioBuf.ptr)) break; + --ioBuf.ptr; + } + + bool findingkey=false; + std::string key, value; + while(true) + { + XMP_Uns32 noOfMarks=0; + if ( ! PostScript_Support::RevCheckFileSpace ( fileRef, &ioBuf, 1 ) ) return false; + if (*ioBuf.ptr==')') + { + --ioBuf.ptr; + while(true) + { + //get the string till '(' + if (*ioBuf.ptr=='(') + { + if(findingkey) + { + reverse(key.begin(), key.end()); + reverse(value.begin(), value.end()); + RegisterKeyValue(key,value); + } + if ( ! PostScript_Support::RevCheckFileSpace ( fileRef, &ioBuf, 1 ) ) return false; + --ioBuf.ptr; + findingkey=!findingkey; + break; + } + else + { + if ( ! PostScript_Support::RevCheckFileSpace ( fileRef, &ioBuf, 1 ) ) return false; + if (findingkey) + key+=*ioBuf.ptr; + else + value+=*ioBuf.ptr; + --ioBuf.ptr; + } + } + } + else if(*ioBuf.ptr=='[') + { + //end of Doc Info parsing + //return; + break; + } + else + { + while(true) + { + if ( ! PostScript_Support::RevCheckFileSpace ( fileRef, &ioBuf, 1 ) ) return false; + if (findingkey) + key+=*ioBuf.ptr; + else + value+=*ioBuf.ptr; + --ioBuf.ptr; + if (*ioBuf.ptr=='/') + { + if(findingkey) + { + reverse(key.begin(), key.end()); + reverse(value.begin(), value.end()); + RegisterKeyValue(key,value); + } + if ( ! PostScript_Support::RevCheckFileSpace ( fileRef, &ioBuf, 1 ) ) return false; + --ioBuf.ptr; + findingkey=!findingkey; + break; + } + else if(IsWhitespace(*ioBuf.ptr)) + { + //something not expected in Doc Info + break; + } + } + } + while(true) + { + if ( ! PostScript_Support::RevCheckFileSpace ( fileRef, &ioBuf, 1 ) ) return false; + if (!IsWhitespace(*ioBuf.ptr)) break; + --ioBuf.ptr; + } + + } + + fileRef->Rewind(); + FillBuffer (fileRef, endofDocInfo, &ioBuf ); + return true; + }//white space not after DOCINFO + return false; +} + +// ================================================================================================= +// PostScript_MetaHandler::ParsePSFile +// =================================== +// +// Main parser for the Post script file.This is where all the DSC comments , Docinfo key value pairs +// and other insertion related Data is looked for and stored +void PostScript_MetaHandler::ParsePSFile() +{ + bool found = false; + IOBuffer ioBuf; + + XMP_IO* fileRef = this->parent->ioRef; + + XMP_AbortProc abortProc = this->parent->abortProc; + void * abortArg = this->parent->abortArg; + const bool checkAbort = (abortProc != 0); + + //Determine the file type PS or EPS + if ( ! PostScript_Support::IsValidPSFile(fileRef,this->fileformat) ) return ; + // Check for the binary EPSF preview header. + + fileRef->Rewind(); + if ( ! CheckFileSpace ( fileRef, &ioBuf, 4 ) ) return ; + XMP_Uns32 fileheader = GetUns32BE ( ioBuf.ptr ); + + if ( fileheader == 0xC5D0D3C6 ) + { + + if ( ! CheckFileSpace ( fileRef, &ioBuf, 30 ) ) return ; + + XMP_Uns32 psOffset = GetUns32LE ( ioBuf.ptr+4 ); // PostScript offset. + XMP_Uns32 psLength = GetUns32LE ( ioBuf.ptr+8 ); // PostScript length. + + setTokenInfo(kPS_EndPostScript,psOffset+psLength,0); + MoveToOffset ( fileRef, psOffset, &ioBuf ); + + } + + while ( true ) + { + if ( checkAbort && abortProc(abortArg) ) { + XMP_Throw ( "PostScript_MetaHandler::FindPostScriptHint - User abort", kXMPErr_UserAbort ); + } + + if ( ! CheckFileSpace ( fileRef, &ioBuf, kPSContainsForString.length() ) ) return ; + + if ( (CheckFileSpace ( fileRef, &ioBuf, kPSEndCommentString.length() )&& + CheckBytes ( ioBuf.ptr, Uns8Ptr(kPSEndCommentString.c_str()), kPSEndCommentString.length() ) + )|| *ioBuf.ptr!='%' || !(*(ioBuf.ptr+1)>32 && *(ioBuf.ptr+1)<=126 )) // implicit endcomment check + { + if (CheckBytes ( ioBuf.ptr, Uns8Ptr(kPSEndCommentString.c_str()), kPSEndCommentString.length() )) + { + setTokenInfo(kPS_EndComments,ioBuf.filePos+ioBuf.ptr-ioBuf.data,kPSEndCommentString.length()); + ioBuf.ptr+=kPSEndCommentString.length(); + } + else + { + setTokenInfo(kPS_EndComments,ioBuf.filePos+ioBuf.ptr-ioBuf.data,0); + } + // Found "%%EndComments", look for docInfo Dictionary + // skip past the end of this line. + while(true) + { + if ( ! CheckFileSpace ( fileRef, &ioBuf, 1 ) ) return ; + if (! IsWhitespace (*ioBuf.ptr)) break; + ++ioBuf.ptr; + } + // search for /DOCINFO + while(true) + { + if ( ! CheckFileSpace ( fileRef, &ioBuf, 5 ) ) return ; + if (CheckBytes ( ioBuf.ptr, Uns8Ptr("/DOCI"), 5 ) + && CheckFileSpace ( fileRef, &ioBuf, kPSContainsDocInfoString.length() ) + &&CheckBytes ( ioBuf.ptr, Uns8Ptr(kPSContainsDocInfoString.c_str()), kPSContainsDocInfoString.length() )) + + { + + ioBuf.ptr+=kPSContainsDocInfoString.length(); + ExtractDocInfoDict(ioBuf); + }// DOCINFO Not found in document + else if(CheckBytes ( ioBuf.ptr, Uns8Ptr("%%Beg"), 5 )) + {//possibly one of %%BeginProlog %%BeginSetup %%BeginBinary %%BeginData + // %%BeginDocument %%BeginPageSetup + XMP_Int64 begStartpos=ioBuf.filePos+ioBuf.ptr-ioBuf.data; + ioBuf.ptr+=5; + if (!CheckFileSpace ( fileRef, &ioBuf, 6 )) return; + if(CheckBytes ( ioBuf.ptr, Uns8Ptr("inProl"), 6 )) + {//%%BeginProlog + ioBuf.ptr+=6; + if (!CheckFileSpace ( fileRef, &ioBuf, 2 ))return; + if(CheckBytes ( ioBuf.ptr, Uns8Ptr("og"), 2 )) + { + ioBuf.ptr+=2; + setTokenInfo(kPS_BeginProlog,begStartpos,13); + } + } + else if (CheckBytes ( ioBuf.ptr, Uns8Ptr("inSetu"), 6 )) + {//%%BeginSetup + ioBuf.ptr+=6; + if (!CheckFileSpace ( fileRef, &ioBuf, 1 ))return; + if(CheckBytes ( ioBuf.ptr, Uns8Ptr("p"), 1 )) + { + ioBuf.ptr+=1; + setTokenInfo(kPS_BeginSetup,begStartpos,12); + } + } + else if (CheckBytes ( ioBuf.ptr, Uns8Ptr("inBina"), 6 )) + {//%%BeginBinary + ioBuf.ptr+=6; + if (!CheckFileSpace ( fileRef, &ioBuf, 3 ))return; + if(CheckBytes ( ioBuf.ptr, Uns8Ptr("ry"), 3 )) + { + ioBuf.ptr+=3; + //ignore till %%EndBinary + while(true) + { + if (!CheckFileSpace ( fileRef, &ioBuf, 12 ))return; + if (CheckBytes ( ioBuf.ptr, Uns8Ptr("%%EndBinary"), 11 )) + { + ioBuf.ptr+=11; + if (IsWhitespace(*ioBuf.ptr)) + { + ioBuf.ptr++; + break; + } + } + ++ioBuf.ptr; + } + } + } + else if (CheckBytes ( ioBuf.ptr, Uns8Ptr("inData"), 6 )) + {//%%BeginData + ioBuf.ptr+=6; + if (!CheckFileSpace ( fileRef, &ioBuf, 1 ))return; + if(CheckBytes ( ioBuf.ptr, Uns8Ptr(":"), 1 )) + { + //ignore till %%EndData + while(true) + { + if (!CheckFileSpace ( fileRef, &ioBuf, 10 ))return; + if (CheckBytes ( ioBuf.ptr, Uns8Ptr("%%EndData"), 9 )) + { + ioBuf.ptr+=9; + if (IsWhitespace(*ioBuf.ptr)) + { + ioBuf.ptr++; + break; + } + } + ++ioBuf.ptr; + } + } + } + else if (CheckBytes ( ioBuf.ptr, Uns8Ptr("inDocu"), 6 )) + {// %%BeginDocument + ioBuf.ptr+=6; + if (!CheckFileSpace ( fileRef, &ioBuf, 5 ))return; + if(CheckBytes ( ioBuf.ptr, Uns8Ptr("ment:"), 5 )) + { + ioBuf.ptr+=5; + //ignore till %%EndDocument + while(true) + { + if (!CheckFileSpace ( fileRef, &ioBuf, 14 ))return; + if (CheckBytes ( ioBuf.ptr, Uns8Ptr("%%EndDocument"), 13 )) + { + ioBuf.ptr+=13; + if (IsWhitespace(*ioBuf.ptr)) + { + ioBuf.ptr++; + break; + } + } + ++ioBuf.ptr; + } + } + } + else if (CheckBytes ( ioBuf.ptr, Uns8Ptr("inPage"), 6 )) + {// %%BeginPageSetup + ioBuf.ptr+=6; + if (!CheckFileSpace ( fileRef, &ioBuf, 5 ))return; + if(CheckBytes ( ioBuf.ptr, Uns8Ptr("Setup"), 5 )) + { + ioBuf.ptr+=5; + setTokenInfo(kPS_BeginPageSetup,begStartpos,16); + } + } + } + else if(CheckBytes ( ioBuf.ptr, Uns8Ptr("%%End"), 5 )) + {//possibly %%EndProlog %%EndSetup %%EndPageSetup %%EndPageComments + XMP_Int64 begStartpos=ioBuf.filePos+ioBuf.ptr-ioBuf.data; + ioBuf.ptr+=5; + if ( ! CheckFileSpace ( fileRef, &ioBuf, 5 ) ) return ; + if (CheckBytes ( ioBuf.ptr, Uns8Ptr("Prolo"), 5 )) + {// %%EndProlog + ioBuf.ptr+=5; + if ( ! CheckFileSpace ( fileRef, &ioBuf, 1 ) ) return ; + if (CheckBytes ( ioBuf.ptr, Uns8Ptr("g"), 1 )) + { + ioBuf.ptr+=1; + setTokenInfo(kPS_EndProlog,begStartpos,11); + } + } + else if (CheckBytes ( ioBuf.ptr, Uns8Ptr("Setup"), 5 )) + {//%%EndSetup + ioBuf.ptr+=5; + setTokenInfo(kPS_EndSetup,begStartpos,10); + } + else if (CheckBytes ( ioBuf.ptr, Uns8Ptr("PageS"), 5 )) + {//%%EndPageSetup + ioBuf.ptr+=5; + if ( ! CheckFileSpace ( fileRef, &ioBuf, 4 ) ) return ; + if (CheckBytes ( ioBuf.ptr, Uns8Ptr("etup"), 4 )) + { + ioBuf.ptr+=4; + setTokenInfo(kPS_EndPageSetup,begStartpos,14); + } + } + else if (CheckBytes ( ioBuf.ptr, Uns8Ptr("PageC"), 5 )) + {//%%EndPageComments + ioBuf.ptr+=5; + if ( ! CheckFileSpace ( fileRef, &ioBuf, 7 ) ) return ; + if (CheckBytes ( ioBuf.ptr, Uns8Ptr("omments"), 7 )) + { + ioBuf.ptr+=7; + setTokenInfo(kPS_EndPageComments,begStartpos,17); + } + } + } + else if(CheckBytes ( ioBuf.ptr, Uns8Ptr("%%Pag"), 5 )) + { + XMP_Int64 begStartpos=ioBuf.filePos+ioBuf.ptr-ioBuf.data; + ioBuf.ptr+=5; + if ( ! CheckFileSpace ( fileRef, &ioBuf, 2 ) ) return ; + if (CheckBytes ( ioBuf.ptr, Uns8Ptr(":"), 2 )) + { + ioBuf.ptr+=2; + while(!IsNewline(*ioBuf.ptr)) + { + if ( ! CheckFileSpace ( fileRef, &ioBuf, 1 ) ) return ; + ++ioBuf.ptr; + } + setTokenInfo(kPS_Page,begStartpos,ioBuf.filePos+ioBuf.ptr-ioBuf.data-begStartpos); + } + + } + else if(CheckBytes ( ioBuf.ptr, Uns8Ptr("%%Tra"), 5 )) + { + XMP_Int64 begStartpos=ioBuf.filePos+ioBuf.ptr-ioBuf.data; + ioBuf.ptr+=5; + if ( ! CheckFileSpace ( fileRef, &ioBuf, 4 ) ) return ; + if (CheckBytes ( ioBuf.ptr, Uns8Ptr("iler"), 4 )) + { + ioBuf.ptr+=4; + while ( !IsNewline( *ioBuf.ptr ) ) + { + if ( !CheckFileSpace( fileRef, &ioBuf, 1 ) ) return; + ++ioBuf.ptr; + } + setTokenInfo(kPS_Trailer,begStartpos,ioBuf.filePos+ioBuf.ptr-ioBuf.data-begStartpos); + } + } + else if(CheckBytes ( ioBuf.ptr, Uns8Ptr("%%EOF"), 5 )) + { + ioBuf.ptr+=5; + setTokenInfo(kPS_EOF,ioBuf.filePos+ioBuf.ptr-ioBuf.data,5); + } + if ( ! CheckFileSpace ( fileRef, &ioBuf, 1 ) ) return ; + ++ioBuf.ptr; + } + //dont have to search after this DOCINFO last thing + return; + + }else if (!(kPS_Creator & dscFlags) && + CheckFileSpace ( fileRef, &ioBuf, kPSContainsForString.length() )&& + CheckBytes ( ioBuf.ptr, Uns8Ptr(kPSContainsForString.c_str()), kPSContainsForString.length() )) + { + ioBuf.ptr+=kPSContainsForString.length(); + if ( ! ExtractDSCCommentValue(ioBuf,kPS_dscFor) ) return ; + } + else if (!(kPS_CreatorTool & dscFlags) && + CheckFileSpace ( fileRef, &ioBuf, kPSContainsCreatorString.length() )&& + CheckBytes ( ioBuf.ptr, Uns8Ptr(kPSContainsCreatorString.c_str()), kPSContainsCreatorString.length() )) + { + ioBuf.ptr+=kPSContainsCreatorString.length(); + if ( ! ExtractDSCCommentValue(ioBuf,kPS_dscCreator) ) return ; + } + else if (!(kPS_CreateDate & dscFlags) && + CheckFileSpace ( fileRef, &ioBuf, kPSContainsCreateDateString.length() )&& + CheckBytes ( ioBuf.ptr, Uns8Ptr(kPSContainsCreateDateString.c_str()), kPSContainsCreateDateString.length() )) + { + + ioBuf.ptr+=kPSContainsCreateDateString.length(); + if ( ! ExtractDSCCommentValue(ioBuf,kPS_dscCreateDate) ) return ; + } + else if (!(kPS_Title & dscFlags) && + CheckFileSpace ( fileRef, &ioBuf, kPSContainsTitleString.length() )&& + CheckBytes ( ioBuf.ptr, Uns8Ptr(kPSContainsTitleString.c_str()), kPSContainsTitleString.length() )) + { + + ioBuf.ptr+=kPSContainsTitleString.length(); + if ( ! ExtractDSCCommentValue(ioBuf,kPS_dscTitle) ) return ; + } + else if( CheckFileSpace ( fileRef, &ioBuf, kPSContainsXMPString.length() )&& + ( CheckBytes ( ioBuf.ptr, Uns8Ptr(kPSContainsXMPString.c_str()), kPSContainsXMPString.length() ) )) { + + // Found "%ADO_ContainsXMP:", look for the main packet location option. + + XMP_Int64 containsXMPStartpos=ioBuf.filePos+ioBuf.ptr-ioBuf.data; + ioBuf.ptr += kPSContainsXMPString.length(); + ExtractContainsXMPHint(ioBuf,containsXMPStartpos); + + } // Found "%ADO_ContainsXMP:". + //Some other DSC comments skip past the end of this line. + if ( ! PostScript_Support::SkipUntilNewline(fileRef,ioBuf) ) return ; + + } // Outer marker loop. + + return ; // Should never reach here. + +} + +// ================================================================================================= +// PostScript_MetaHandler::ReadXMPPacket +// ===================================== +// +// Helper method read the raw xmp into a string from a file +void PostScript_MetaHandler::ReadXMPPacket (std::string & xmpPacket ) +{ + if ( packetInfo.length == 0 ) XMP_Throw ( "ReadXMPPacket - No XMP packet", kXMPErr_BadXMP ); + + xmpPacket.erase(); + xmpPacket.reserve ( packetInfo.length ); + xmpPacket.append ( packetInfo.length, ' ' ); + + XMP_StringPtr packetStr = XMP_StringPtr ( xmpPacket.c_str() ); // Don't set until after reserving the space! + + this->parent->ioRef->Seek ( packetInfo.offset, kXMP_SeekFromStart ); + this->parent->ioRef->ReadAll ( (char*)packetStr, packetInfo.length ); + +} // ReadXMPPacket + + +// ================================================================================================= +// PostScript_MetaHandler::RegisterKeyValue +// ========================================= +// +// Helper method registers the Key value pairs for the DocInfo dictionary and sets the Appriopriate +// DocInfo flags +void PostScript_MetaHandler::RegisterKeyValue(std::string& key, std::string& value) +{ + size_t vallen=value.length(); + if (key.length()==0||vallen==0) + { + key.clear(); + value.clear(); + return; + } + for (size_t index=0;index127) + { + key.clear(); + value.clear(); + return; + } + } + switch (key[0]) + { + case 'A': // probably Author + { + if (!key.compare("Author")) + { + nativeMeta[kPS_docInfoAuthor]=value; + docInfoFlags|=kPS_Creator; + } + break; + } + case 'C': //probably Creator or CreationDate + { + if (!key.compare("Creator")) + { + nativeMeta[kPS_docInfoCreator]=value; + docInfoFlags|=kPS_CreatorTool; + } + else if (!key.compare("CreationDate")) + { + nativeMeta[kPS_docInfoCreateDate]=value; + docInfoFlags|=kPS_CreateDate; + } + break; + } + case 'T': // probably Title + { + if (!key.compare("Title")) + { + nativeMeta[kPS_docInfoTitle]=value; + docInfoFlags|=kPS_Title; + } + break; + } + case 'S':// probably Subject + { + if (!key.compare("Subject")) + { + nativeMeta[kPS_docInfoSubject]=value; + docInfoFlags|=kPS_Description; + } + break; + } + case 'K':// probably Keywords + { + if (!key.compare("Keywords")) + { + nativeMeta[kPS_docInfoKeywords]=value; + docInfoFlags|=kPS_Subject; + } + break; + } + case 'M': // probably ModDate + { + if (!key.compare("ModDate")) + { + nativeMeta[kPS_docInfoModDate]=value; + docInfoFlags|=kPS_ModifyDate; + } + break; + } + default: //ignore everything else + { + ; + } + } + key.clear(); + value.clear(); +} + + +// ================================================================================================= +// PostScript_MetaHandler::ReconcileXMP +// ========================================= +// +// Helper method that facilitates the read time reconcilliation of native metadata + +void PostScript_MetaHandler::ReconcileXMP( const std::string &xmpStr, std::string *outStr ) +{ + SXMPMeta xmp; + xmp.ParseFromBuffer( xmpStr.c_str(), xmpStr.length() ); + // Adding creator Toll if any + if (!xmp.DoesPropertyExist ( kXMP_NS_XMP,"CreatorTool" )) + { + if(docInfoFlags&kPS_CreatorTool) + { + xmp.SetProperty( kXMP_NS_XMP, "CreatorTool", nativeMeta[kPS_docInfoCreator] ); + } + else if (dscFlags&kPS_CreatorTool) + { + xmp.SetProperty( kXMP_NS_XMP, "CreatorTool", nativeMeta[kPS_dscCreator] ); + } + } + if (!xmp.DoesPropertyExist ( kXMP_NS_XMP,"CreateDate" )) + { + if(docInfoFlags&kPS_CreateDate && nativeMeta[kPS_docInfoCreateDate].length()>0) + { + std::string xmpdate=PostScript_Support::ConvertToDate(nativeMeta[kPS_docInfoCreateDate].c_str()); + if (xmpdate.length()>0) + { + xmp.SetProperty( kXMP_NS_XMP, "CreateDate", xmpdate ); + } + } + else if (dscFlags&kPS_CreateDate&& nativeMeta[kPS_dscCreateDate].length()>0) + { + std::string xmpdate=PostScript_Support::ConvertToDate(nativeMeta[kPS_dscCreateDate].c_str()); + xmp.SetProperty( kXMP_NS_XMP, "CreateDate", xmpdate ); + } + } + if (!xmp.DoesPropertyExist ( kXMP_NS_XMP,"ModifyDate" )) + { + if(docInfoFlags&kPS_ModifyDate && nativeMeta[kPS_docInfoModDate].length()>0) + { + std::string xmpdate=PostScript_Support::ConvertToDate(nativeMeta[kPS_docInfoModDate].c_str()); + if (xmpdate.length()>0) + { + xmp.SetProperty( kXMP_NS_XMP, "ModifyDate", xmpdate ); + } + } + } + if (!xmp.DoesPropertyExist ( kXMP_NS_DC,"creator" )) + { + if(docInfoFlags&kPS_Creator) + { + xmp.AppendArrayItem ( kXMP_NS_DC, "creator", kXMP_PropArrayIsOrdered, + nativeMeta[kPS_docInfoAuthor] ); + } + else if ( dscFlags&kPS_Creator) + { + xmp.AppendArrayItem ( kXMP_NS_DC, "creator", kXMP_PropArrayIsOrdered, + nativeMeta[kPS_dscFor] ); + } + } + if (!xmp.DoesPropertyExist ( kXMP_NS_DC,"title" )) + { + if(docInfoFlags&kPS_Title) + { + xmp.SetLocalizedText( kXMP_NS_DC, "title",NULL,"x-default", nativeMeta[kPS_docInfoTitle] ); + } + else if ( dscFlags&kPS_Title) + { + xmp.SetLocalizedText( kXMP_NS_DC, "title",NULL,"x-default", nativeMeta[kPS_dscTitle] ); + } + } + if (!xmp.DoesPropertyExist ( kXMP_NS_DC,"description" )) + { + if(docInfoFlags&kPS_Description) + { + xmp.SetLocalizedText( kXMP_NS_DC, "description",NULL,"x-default", nativeMeta[kPS_docInfoSubject] ); + } + } + if (!xmp.DoesPropertyExist ( kXMP_NS_DC,"subject" )) + { + if(docInfoFlags&kPS_Subject) + { + xmp.AppendArrayItem( kXMP_NS_DC, "subject", kXMP_PropArrayIsUnordered, + nativeMeta[kPS_docInfoKeywords], kXMP_NoOptions ); + } + } + + if (packetInfo.length>0) + { + try + { + xmp.SerializeToBuffer( outStr, kXMP_ExactPacketLength|kXMP_UseCompactFormat,packetInfo.length); + } + catch(...) + { + xmp.SerializeToBuffer( outStr, kXMP_UseCompactFormat,0); + } + } + else + { + xmp.SerializeToBuffer( outStr, kXMP_UseCompactFormat,0); + } +} + + +// ================================================================================================= +// PostScript_MetaHandler::CacheFileData +// ===================================== +void PostScript_MetaHandler::CacheFileData() +{ + this->containsXMP = false; + this->psHint = kPSHint_NoMarker; + ParsePSFile(); + + if ( this->psHint == kPSHint_MainFirst ) { + this->containsXMP = FindFirstPacket(); + } else if ( this->psHint == kPSHint_MainLast ) { + this->containsXMP = FindLastPacket(); + }else + { + //find first packet in case of NoMain or absence of hint + //When inserting new packet should be inserted in front + //any other existing packet + FindFirstPacket(); + } + if ( this->containsXMP ) + { + ReadXMPPacket ( xmpPacket ); + } +} // PostScript_MetaHandler::CacheFileData + +// ================================================================================================= +// PostScript_MetaHandler::ProcessXMP +// ================================== +void PostScript_MetaHandler::ProcessXMP() +{ + + XMP_Assert ( ! this->processedXMP ); + this->processedXMP = true; // Make sure we only come through here once. + + std::string xmptempStr=xmpPacket; + + //Read time reconciliation with native metadata + try + { + ReconcileXMP(xmptempStr, &xmpPacket); + } + catch(...) + { + } + if ( ! this->xmpPacket.empty() ) + { + XMP_StringPtr packetStr = this->xmpPacket.c_str(); + XMP_StringLen packetLen = (XMP_StringLen)this->xmpPacket.size(); + this->xmpObj.ParseFromBuffer ( packetStr, packetLen ); + } + if (xmpPacket.length()>0) + { + this->containsXMP = true; // Assume we had something for the XMP. + } +} + + +// ================================================================================================= +// PostScript_MetaHandler::modifyHeader +// ===================================== +// +// Method modifies the header (if one is present) for the postscript offset, tiff offset etc. +// when an XMP update resulted in increase in the file size(non-inplace updates) +void PostScript_MetaHandler::modifyHeader(XMP_IO* fileRef,XMP_Int64 extrabytes,XMP_Int64 offset ) +{ + //change the header + IOBuffer temp; + fileRef->Rewind(); + if ( ! CheckFileSpace ( fileRef, &temp, 4 ) ) return ; + XMP_Uns32 fileheader = GetUns32BE ( temp.ptr ); + + if ( fileheader == 0xC5D0D3C6 ) + { + XMP_Uns8 buffLE[4]; + if ( ! CheckFileSpace ( fileRef, &temp, 32 ) ) return ; + XMP_Uns32 psLength = GetUns32LE ( temp.ptr+8 ); // PostScript length. + if (psLength>0) + { + psLength+=extrabytes; + PutUns32LE ( psLength, buffLE); + fileRef->Seek ( 8, kXMP_SeekFromStart ); + fileRef->Write(buffLE,4); + } + XMP_Uns32 wmfOffset = GetUns32LE ( temp.ptr+12 ); // WMF offset. + if (wmfOffset>0 && wmfOffset>offset) + { + wmfOffset+=extrabytes; + PutUns32LE ( wmfOffset, buffLE); + fileRef->Seek ( 12, kXMP_SeekFromStart ); + fileRef->Write(buffLE,4); + } + + XMP_Uns32 tiffOffset = GetUns32LE ( temp.ptr+20 ); // Tiff offset. + if (tiffOffset>0 && tiffOffset>offset) + { + tiffOffset+=extrabytes; + PutUns32LE ( tiffOffset, buffLE); + fileRef->Seek ( 20, kXMP_SeekFromStart ); + fileRef->Write(buffLE,4); + } + //set the checksum to 0xFFFFFFFF + XMP_Uns16 checksum=0xFFFF; + PutUns16LE ( checksum, buffLE); + fileRef->Seek ( 28, kXMP_SeekFromStart ); + fileRef->Write(buffLE,2); + } +} + +// ================================================================================================= +// PostScript_MetaHandler::DetermineUpdateMethod +// ============================================= +// +// The policy followed to update a Postscript file is +// a) if the update can fit into the existing xpacket size, go for inplace update. +// b) If sub file decode filter is used to embed the metadata expand the metadata +// and the move the rest contents(after the xpacket) by some calc offset. +// c) If some other method is used to embed the xpacket readstring or readline +// insert a new metadata xpacket before the existing xpacket. +// The preference to use these methods is in the same order a , b and then c +// DetermineUpdateMethod helps to decide which method be used for the given +// input file +// +UpdateMethod PostScript_MetaHandler::DetermineUpdateMethod(std::string & outStr) +{ + SXMPMeta xmp; + std::string & xmpPacket = this->xmpPacket; + XMP_PacketInfo & packetInfo = this->packetInfo; + xmp.ParseFromBuffer( xmpPacket.c_str(), xmpPacket.length() ); + if (packetInfo.length>0) + { + try + { + //First try to fit the modified XMP data into existing XMP packet length + //prefers Inplace + xmp.SerializeToBuffer( &outStr, kXMP_ExactPacketLength|kXMP_UseCompactFormat,packetInfo.length); + } + catch(...) + { + // if it doesnt fit :( + xmp.SerializeToBuffer( &outStr, kXMP_UseCompactFormat,0); + } + } + else + { + // this will be the case for Injecting new metadata + xmp.SerializeToBuffer( &outStr, kXMP_UseCompactFormat,0); + } + if ( this->containsXMPHint && (outStr.size() == (size_t)packetInfo.length) ) + { + return kPS_Inplace; + } + else if (this->containsXMPHint && PostScript_Support::IsSFDFilterUsed(this->parent->ioRef,packetInfo.offset)) + { + return kPS_ExpandSFDFilter; + } + else + { + return kPS_InjectNew; + } + +} + +// ================================================================================================= +// PostScript_MetaHandler::InplaceUpdate +// ===================================== +// +// Method does the inplace update of the metadata +void PostScript_MetaHandler::InplaceUpdate (std::string &outStr,XMP_IO* &tempRef ,bool doSafeUpdate) +{ + + XMP_IO* fileRef = this->parent->ioRef; + XMP_Int64 pos = 0; + XMP_ProgressTracker* progressTracker = this->parent->progressTracker; + //Inplace update of metadata + if (!doSafeUpdate) + { + if ( progressTracker != 0 ) progressTracker->AddTotalWork ((float) outStr.length() ); + fileRef->Seek(packetInfo.offset,kXMP_SeekFromStart); + fileRef->Write((void *)outStr.c_str(), static_cast(outStr.length())); + } + else + { + if ( ! tempRef ) tempRef=fileRef->DeriveTemp(); + pos=fileRef->Length(); + if ( progressTracker != 0 ) progressTracker->AddTotalWork ((float) pos ); + //copy data till xmp Packet + fileRef->Seek(0,kXMP_SeekFromStart); + XIO::Copy ( fileRef, tempRef, packetInfo.offset, this->parent->abortProc, this->parent->abortArg ); + + //insert the new XMP packet + fileRef->Seek(packetInfo.offset+packetInfo.length,kXMP_SeekFromStart); + tempRef->Write((void *)outStr.c_str(), static_cast(outStr.length())); + + //copy the rest of data + XIO::Copy ( fileRef, tempRef,pos-packetInfo.offset-packetInfo.length, this->parent->abortProc, this->parent->abortArg ); + } +} + + +// ================================================================================================= +// PostScript_MetaHandler::ExpandingSFDFilterUpdate +// ================================================ +// +// Method updates the metadata by expanding it +void PostScript_MetaHandler::ExpandingSFDFilterUpdate (std::string &outStr,XMP_IO* &tempRef ,bool doSafeUpdate) +{ + //If SubFileDecode Filter is present expanding the + //existing metadata is easy + + XMP_IO* fileRef = this->parent->ioRef; + XMP_Int64 pos = 0; + XMP_Int32 extrapacketlength=outStr.length()-packetInfo.length; + XMP_ProgressTracker* progressTracker = this->parent->progressTracker; + if ( progressTracker != 0 ) progressTracker->AddTotalWork ((float) (extrapacketlength + fileRef->Length() -packetInfo.offset+14) ); + if (!doSafeUpdate) + { + size_t bufSize=extrapacketlength/(kIOBufferSize) +1*(extrapacketlength!=(kIOBufferSize)); + std::vector tempfilebuffer1(bufSize); + IOBuffer temp; + XMP_Int64 readpoint=packetInfo.offset+packetInfo.length,writepoint=packetInfo.offset; + fileRef->Seek ( readpoint, kXMP_SeekFromStart ); + + for(size_t x=0;xRead(tempfilebuffer1[x].data,kIOBufferSize,false); + readpoint+=tempfilebuffer1[x].len; + } + + fileRef->Seek ( writepoint, kXMP_SeekFromStart ); + fileRef->Write( (void *)outStr.c_str(), static_cast(outStr.length())); + writepoint+=static_cast(outStr.length()); + size_t y=0; + bool continueread=(tempfilebuffer1[bufSize-1].len==kIOBufferSize); + size_t loop=bufSize; + while(loop) + { + if(continueread) + { + fileRef->Seek ( readpoint, kXMP_SeekFromStart ); + temp.len=fileRef->Read(temp.data,kIOBufferSize,false); + readpoint+=temp.len; + } + fileRef->Seek ( writepoint, kXMP_SeekFromStart ); + fileRef->Write(tempfilebuffer1[y].data,tempfilebuffer1[y].len); + writepoint+=tempfilebuffer1[y].len; + if (continueread) + tempfilebuffer1[y]=temp; + else + --loop; + if (temp.lenAddTotalWork ((float) (packetInfo.offset) ); + if ( ! tempRef ) tempRef=fileRef->DeriveTemp(); + //copy data till xmp Packet + fileRef->Seek(0,kXMP_SeekFromStart); + XIO::Copy ( fileRef, tempRef, packetInfo.offset, this->parent->abortProc, this->parent->abortArg ); + + //insert the new XMP packet + fileRef->Seek(packetInfo.offset+packetInfo.length,kXMP_SeekFromStart); + tempRef->Write((void *)outStr.c_str(), static_cast(outStr.length())); + + //copy the rest of data + pos=fileRef->Length(); + XIO::Copy ( fileRef, tempRef,pos-packetInfo.offset-packetInfo.length, this->parent->abortProc, this->parent->abortArg ); + modifyHeader(tempRef,extrapacketlength,packetInfo.offset ); + } +} + +// ================================================================================================= +// PostScript_MetaHandler::DetermineInsertionOffsets +// ============================================= +// +// Method determines the offsets at which the new xpacket and other postscript code be inserted +void PostScript_MetaHandler::DetermineInsertionOffsets(XMP_Int64& ADOhintOffset,XMP_Int64& InjectData1Offset, + XMP_Int64& InjectData3Offset) +{ + //find the position to place ADOContainsXMP hint + if(psHint!=kPSHint_MainFirst && (fileformat==kXMP_EPSFile||kXMPFiles_UnknownLength==packetInfo.offset)) + { + TokenLocation& tokenLoc= getTokenInfo(kPS_ADOContainsXMP); + if(tokenLoc.offsetStart==-1) + { + TokenLocation& tokenLoc1= getTokenInfo(kPS_EndComments); + if(tokenLoc1.offsetStart==-1) + { + //should never reach here + throw XMP_Error(kXMPErr_BadFileFormat,"%%EndComment Missing"); + } + ADOhintOffset=tokenLoc1.offsetStart; + } + else + { + ADOhintOffset= tokenLoc.offsetStart; + } + } + else if (psHint!=kPSHint_MainLast &&fileformat==kXMP_PostScriptFile) + { + TokenLocation& tokenLoc= getTokenInfo(kPS_ADOContainsXMP); + if(tokenLoc.offsetStart==-1) + { + TokenLocation& tokenLoc1= getTokenInfo(kPS_EndComments); + if(tokenLoc1.offsetStart==-1) + { + //should never reach here + throw XMP_Error(kXMPErr_BadFileFormat,"%%EndComment Missing"); + } + ADOhintOffset=tokenLoc1.offsetStart; + } + else + { + ADOhintOffset= tokenLoc.offsetStart; + } + } + //Find the location to insert kEPS_Injectdata1 + XMP_Uns64 xpacketLoc; + if ( (fileformat == kXMP_PostScriptFile) && (kXMPFiles_UnknownLength != packetInfo.offset) ) + { + xpacketLoc = (XMP_Uns64)lastPacketInfo.offset; + TokenLocation& endPagsetuploc = getTokenInfo(kPS_EndPageSetup); + if ( (endPagsetuploc.offsetStart > -1) && (xpacketLoc < (XMP_Uns64)endPagsetuploc.offsetStart) ) + { + InjectData1Offset=endPagsetuploc.offsetStart; + } + else + { + TokenLocation& trailerloc= getTokenInfo(kPS_Trailer); + if ( (trailerloc.offsetStart > -1) && (xpacketLoc < (XMP_Uns64)trailerloc.offsetStart) ) + { + InjectData1Offset=trailerloc.offsetStart; + } + else + { + TokenLocation& eofloc= getTokenInfo(kPS_EOF); + if ( (eofloc.offsetStart > -1) && (xpacketLoc < (XMP_Uns64)eofloc.offsetStart) ) + { + InjectData1Offset=eofloc.offsetStart; + } + else + { + TokenLocation& endPostScriptloc= getTokenInfo(kPS_EndPostScript); + if ( (endPostScriptloc.offsetStart > -1) && (xpacketLoc < (XMP_Uns64)endPostScriptloc.offsetStart) ) + { + InjectData1Offset=endPostScriptloc.offsetStart; + } + } + } + } + } + else + { + xpacketLoc = (XMP_Uns64)firstPacketInfo.offset; + TokenLocation& endPagsetuploc = getTokenInfo(kPS_EndPageSetup); + if ( (endPagsetuploc.offsetStart > -1) && (xpacketLoc > (XMP_Uns64)endPagsetuploc.offsetStart) ) + { + InjectData1Offset=endPagsetuploc.offsetStart; + } + else + { + TokenLocation& beginPagsetuploc= getTokenInfo(kPS_BeginPageSetup); + if ( (beginPagsetuploc.offsetStart > -1) && + (xpacketLoc > (XMP_Uns64)(beginPagsetuploc.offsetStart + beginPagsetuploc.tokenlen)) ) + { + InjectData1Offset=beginPagsetuploc.offsetStart+beginPagsetuploc.tokenlen; + } + else + { + TokenLocation& endPageCommentsloc= getTokenInfo(kPS_EndPageComments); + if ( (endPageCommentsloc.offsetStart > -1) && + (xpacketLoc > (XMP_Uns64)(endPageCommentsloc.offsetStart + endPageCommentsloc.tokenlen)) ) + { + InjectData1Offset=endPageCommentsloc.offsetStart+endPageCommentsloc.tokenlen; + } + else + { + TokenLocation& pageLoc= getTokenInfo(kPS_Page); + if ( (pageLoc.offsetStart > -1) && + (xpacketLoc > (XMP_Uns64)(pageLoc.offsetStart + pageLoc.tokenlen)) ) + { + InjectData1Offset=pageLoc.offsetStart+pageLoc.tokenlen; + } + else + { + TokenLocation& endSetupLoc= getTokenInfo(kPS_EndSetup); + if ( (endSetupLoc.offsetStart > -1) && (xpacketLoc > (XMP_Uns64)endSetupLoc.offsetStart) ) + { + InjectData1Offset=endSetupLoc.offsetStart; + } + else + { + TokenLocation& beginSetupLoc= getTokenInfo(kPS_BeginSetup); + if ( (beginSetupLoc.offsetStart > -1) && + (xpacketLoc > (XMP_Uns64)(beginSetupLoc.offsetStart + beginSetupLoc.tokenlen)) ) + { + InjectData1Offset=beginSetupLoc.offsetStart+beginSetupLoc.tokenlen; + } + else + { + TokenLocation& endPrologLoc= getTokenInfo(kPS_EndProlog); + if ( (endPrologLoc.offsetStart > -1) && + (xpacketLoc > (XMP_Uns64)(endPrologLoc.offsetStart + endPrologLoc.tokenlen)) ) + { + InjectData1Offset=endPrologLoc.offsetStart+endPrologLoc.tokenlen; + } + else + { + TokenLocation& endCommentsLoc= getTokenInfo(kPS_EndComments); + if ( (endCommentsLoc.offsetStart > -1) && + (xpacketLoc > (XMP_Uns64)(endCommentsLoc.offsetStart + endCommentsLoc.tokenlen)) ) + { + InjectData1Offset=endCommentsLoc.offsetStart+endCommentsLoc.tokenlen; + } + else + { + //should never reach here + throw XMP_Error(kXMPErr_BadFileFormat,"%%EndComment Missing"); + } + } + + } + } + } + } + } + } + } + + + //Find the location to insert kEPS_Injectdata3 + TokenLocation& trailerloc= getTokenInfo(kPS_Trailer); + if(trailerloc.offsetStart>-1 ) + { + InjectData3Offset=trailerloc.offsetStart+trailerloc.tokenlen; + } + else + { + TokenLocation& eofloc= getTokenInfo(kPS_EOF); + if(eofloc.offsetStart>-1 ) + { + InjectData3Offset=eofloc.offsetStart; + } + else + { + TokenLocation& endPostScriptloc= getTokenInfo(kPS_EndPostScript); + if(endPostScriptloc.offsetStart>-1 ) + { + InjectData3Offset=endPostScriptloc.offsetStart; + } + } + } +} + +// ================================================================================================= +// PostScript_MetaHandler::InsertNewUpdate +// ======================================= +// +// Method inserts a new Xpacket in the postscript file.This will be called in two cases +// a) If there is no xpacket in the PS file +// b) If the existing xpacket is embedded using readstring or readline method +void PostScript_MetaHandler::InsertNewUpdate (std::string &outStr,XMP_IO* &tempRef,bool doSafeUpdate ) +{ + // In this case it is better to have safe update + // as non-safe update implementation is going to be complex + // and more time consuming + // ignoring doSafeUpdate for this update method + + //No SubFileDecode Filter + // Need to insert new Metadata before existing metadata + // with SubFileDecode Filter + + XMP_IO* fileRef = this->parent->ioRef; + if ( ! tempRef ) tempRef=fileRef->DeriveTemp(); + //inject metadata at the right place + XMP_Int64 ADOhintOffset=-1,InjectData1Offset=-1,InjectData3Offset=-1; + DetermineInsertionOffsets(ADOhintOffset,InjectData1Offset,InjectData3Offset); + XMP_Int64 tempInjectData1Offset=InjectData1Offset; + fileRef->Rewind(); + XMP_ProgressTracker* progressTracker = this->parent->progressTracker; + if ( progressTracker != 0 ) + { + progressTracker->AddTotalWork ((float) ( fileRef->Length() +outStr.length() + 14) ); + if (fileformat==kXMP_EPSFile) + { + progressTracker->AddTotalWork ((float) ( kEPS_Injectdata1.length() + kEPS_Injectdata2.length() + kEPS_Injectdata3.length()) ); + } + else + { + progressTracker->AddTotalWork ((float) ( kPS_Injectdata1.length() + kPS_Injectdata2.length()) ); + } + } + XMP_Int64 totalReadLength=0; + //copy contents from orignal file to Temp File + if(ADOhintOffset!=-1) + { + XIO::Copy(fileRef,tempRef,ADOhintOffset,this->parent->abortProc,this->parent->abortArg); + totalReadLength+=ADOhintOffset; + if (fileformat==kXMP_EPSFile || kXMPFiles_UnknownLength==packetInfo.offset) + { + if ( progressTracker != 0 ) progressTracker->AddTotalWork ((float) ( kPS_XMPHintMainFirst.length()) ); + tempRef->Write(kPS_XMPHintMainFirst.c_str(),kPS_XMPHintMainFirst.length()); + } + else + { + if ( progressTracker != 0 ) progressTracker->AddTotalWork ((float) ( kPS_XMPHintMainLast.length()) ); + tempRef->Write(kPS_XMPHintMainLast.c_str(),kPS_XMPHintMainLast.length()); + } + } + InjectData1Offset-=totalReadLength; + XIO::Copy(fileRef,tempRef,InjectData1Offset,this->parent->abortProc,this->parent->abortArg); + totalReadLength+=InjectData1Offset; + if (fileformat==kXMP_EPSFile) + { + tempRef->Write(kEPS_Injectdata1.c_str(),kEPS_Injectdata1.length()); + tempRef->Write((void *)outStr.c_str(), static_cast(outStr.length())); + tempRef->Write(kEPS_Injectdata2.c_str(),kEPS_Injectdata2.length()); + } + else + { + tempRef->Write(kPS_Injectdata1.c_str(),kPS_Injectdata1.length()); + tempRef->Write((void *)outStr.c_str(), static_cast(outStr.length())); + tempRef->Write(kPS_Injectdata2.c_str(),kPS_Injectdata2.length()); + } + if (InjectData3Offset!=-1) + { + InjectData3Offset-=totalReadLength; + XIO::Copy(fileRef,tempRef,InjectData3Offset,this->parent->abortProc,this->parent->abortArg); + totalReadLength+=InjectData3Offset; + if (fileformat==kXMP_EPSFile) + { + tempRef->Write(kEPS_Injectdata3.c_str(),kEPS_Injectdata3.length()); + } + XMP_Int64 remlength=fileRef->Length()-totalReadLength; + XIO::Copy(fileRef,tempRef,remlength,this->parent->abortProc,this->parent->abortArg); + totalReadLength+=remlength; + } + else + { + XMP_Int64 remlength=fileRef->Length()-totalReadLength; + XIO::Copy(fileRef,tempRef,remlength,this->parent->abortProc,this->parent->abortArg); + totalReadLength+=remlength; + if (fileformat==kXMP_EPSFile) + { + tempRef->Write(kEPS_Injectdata3.c_str(),kEPS_Injectdata3.length()); + } + } + XMP_Int64 extraBytes; + if (fileformat==kXMP_EPSFile ) + { + extraBytes=((ADOhintOffset!=-1)?kPS_XMPHintMainFirst.length():0)+kEPS_Injectdata3.length()+kEPS_Injectdata2.length()+ + kEPS_Injectdata1.length()+outStr.length(); + } + else + { + extraBytes=((ADOhintOffset!=-1)?(kXMPFiles_UnknownLength!=packetInfo.offset?kPS_XMPHintMainLast.length():kPS_XMPHintMainFirst.length()):0)+kPS_Injectdata2.length()+kPS_Injectdata1.length()+outStr.length(); + } + modifyHeader(tempRef,extraBytes,tempInjectData1Offset ); +} + +// ================================================================================================= +// PostScript_MetaHandler::UpdateFile +// ================================== +// +// Virtual Method implementation to update XMP metadata in a PS file +void PostScript_MetaHandler::UpdateFile ( bool doSafeUpdate ) +{ + IgnoreParam ( doSafeUpdate ); + if ( ! this->needsUpdate ) return; + + XMP_IO * tempRef = 0; + XMP_IO* fileRef = this->parent->ioRef; + std::string & xmpPacket = this->xmpPacket; + std::string outStr; + + if (!fileRef ) + { + XMP_Throw ( "Invalid File Refernce Cannot update XMP", kXMPErr_BadOptions ); + } + bool localProgressTracking = false; + XMP_ProgressTracker* progressTracker = this->parent->progressTracker; + if ( progressTracker != 0 ) + { + if ( ! progressTracker->WorkInProgress() ) + { + localProgressTracking = true; + progressTracker->BeginWork (); + } + } + try + { switch(DetermineUpdateMethod(outStr)) + { + case kPS_Inplace: + { + InplaceUpdate ( outStr, tempRef, doSafeUpdate ); + break; + } + case kPS_ExpandSFDFilter: + { + ExpandingSFDFilterUpdate ( outStr, tempRef, doSafeUpdate ); + break; + } + case kPS_InjectNew: + { + InsertNewUpdate ( outStr, tempRef, doSafeUpdate ); + break; + } + case kPS_None: + default: + { + XMP_Throw ( "XMP Write Failed ", kXMPErr_BadOptions ); + } + } + } + catch(...) + { + if( tempRef ) fileRef->DeleteTemp(); + throw; + } + // rename the modified temp file and then delete the temp file + if ( tempRef ) fileRef->AbsorbTemp(); + if ( localProgressTracking ) progressTracker->WorkComplete(); + this->needsUpdate = false; + +} // PostScript_MetaHandler::UpdateFile + + +// ================================================================================================= +// PostScript_MetaHandler::UpdateFile +// ================================== +// +// Method to write the file with updated XMP metadata to the passed temp file reference +void PostScript_MetaHandler::WriteTempFile ( XMP_IO* tempRef ) +{ + XMP_IO* origRef = this->parent->ioRef; + + XMP_AbortProc abortProc = this->parent->abortProc; + void * abortArg = this->parent->abortArg; + + XMP_Int64 fileLen = origRef->Length(); + + XMP_ProgressTracker* progressTracker = this->parent->progressTracker; + if ( progressTracker != 0 ) progressTracker->BeginWork ((float) fileLen ); + origRef->Rewind ( ); + tempRef->Truncate ( 0 ); + XIO::Copy ( origRef, tempRef, fileLen, abortProc, abortArg ); + + try + { + this->parent->ioRef = tempRef; // ! Make UpdateFile update the temp. + this->UpdateFile ( false ); + this->parent->ioRef = origRef; + } + catch ( ... ) + { + this->parent->ioRef = origRef; + throw; + } + + if ( progressTracker != 0 ) progressTracker->WorkComplete(); +} +// ================================================================================================= diff --git a/core/libs/dngwriter/extra/xmp_sdk/XMPFiles/source/FileHandlers/PostScript_Handler.hpp b/core/libs/dngwriter/extra/xmp_sdk/XMPFiles/source/FileHandlers/PostScript_Handler.hpp new file mode 100644 index 0000000000..7bff0fcdc1 --- /dev/null +++ b/core/libs/dngwriter/extra/xmp_sdk/XMPFiles/source/FileHandlers/PostScript_Handler.hpp @@ -0,0 +1,147 @@ +#ifndef __PostScript_Handler_hpp__ +#define __PostScript_Handler_hpp__ 1 + +// ================================================================================================= +// ADOBE SYSTEMS INCORPORATED +// Copyright 2004 Adobe Systems Incorporated +// All Rights Reserved +// +// NOTICE: Adobe permits you to use, modify, and distribute this file in accordance with the terms +// of the Adobe license agreement accompanying it. +// ================================================================================================= + +#include "public/include/XMP_Environment.h" // ! XMP_Environment.h must be the first included header. + +#include "public/include/XMP_Const.h" +#include "public/include/XMP_IO.hpp" + +#include "XMPFiles/source/FormatSupport/PostScript_Support.hpp" + + + +// ================================================================================================= +/// \file PostScript_Handler.hpp +/// \brief File format handler for PostScript and EPS files. +/// +/// This header ... +/// +// ================================================================================================= + +extern XMPFileHandler * PostScript_MetaHandlerCTor ( XMPFiles * parent ); + +extern bool PostScript_CheckFormat ( XMP_FileFormat format, + XMP_StringPtr filePath, + XMP_IO * fileRef, + XMPFiles * parent ); + +static const XMP_OptionBits kPostScript_HandlerFlags = ( + kXMPFiles_CanInjectXMP + |kXMPFiles_CanExpand + |kXMPFiles_CanRewrite + |kXMPFiles_PrefersInPlace + |kXMPFiles_CanReconcile + |kXMPFiles_AllowsOnlyXMP + |kXMPFiles_ReturnsRawPacket + |kXMPFiles_AllowsSafeUpdate + |kXMPFiles_CanNotifyProgress ); + +class PostScript_MetaHandler : public XMPFileHandler +{ +public: + + PostScript_MetaHandler ( XMPFiles * parent ); + ~PostScript_MetaHandler(); + + void CacheFileData(); + void UpdateFile ( bool doSafeUpdate ); + void ProcessXMP ( ); + void WriteTempFile ( XMP_IO* tempRef ); + + int psHint; + /* Structure used to keep + Track of Tokens in + EPS files + */ + struct TokenLocation{ + //offset from the begining of the file + // at which the token string starts + XMP_Int64 offsetStart; + //Total length of the token string + XMP_Int64 tokenlen; + TokenLocation():offsetStart(-1),tokenlen(0) + {} + }; +protected: + //Determines the postscript hint in the DSC comments + int FindPostScriptHint(); + + // Helper methods to get the First or the Last packet from the + // PS file based upon the PostScript hint that is present in the PS file + bool FindFirstPacket(); + bool FindLastPacket(); + + + //Facilitates read time reconciliation of PS native metadata + void ReconcileXMP( const std::string &xmpStr, std::string *outStr ); + + //Facilitates reading of XMP packet , if one exists + void ReadXMPPacket ( std::string & xmpPacket); + + // Parses the PS file to record th epresence and location of + // XMP packet and native metadata in the file + void ParsePSFile(); + + // Helper function to record the native metadata key/avlue pairs + // when parsing the PS file + void RegisterKeyValue(std::string& key, std::string& value); + + // Helper Function to record the location and length of the Tokens + // in the opened PS file + void setTokenInfo(TokenFlag tFlag,XMP_Int64 offset,XMP_Int64 length); + + // Getter to get the location of a token ina PS file. + TokenLocation& getTokenInfo(TokenFlag tFlag); + + //modifies the Binary Header of a PS file as per the modifications + void modifyHeader(XMP_IO* fileRef,XMP_Int64 extrabytes,XMP_Int64 offset ); + + //Extract the values for different DSC comments + bool ExtractDSCCommentValue(IOBuffer &ioBuf,NativeMetadataIndex index); + + //Extract value for ADO_ContainsXMP Comment + bool ExtractContainsXMPHint(IOBuffer &ioBuf,XMP_Int64 containsXMPStartpos); + + //Extract values from DocInfo Dict + bool ExtractDocInfoDict(IOBuffer &ioBuf); + + //Determine the update method to be used + UpdateMethod DetermineUpdateMethod(std::string & outStr); + void DetermineInsertionOffsets(XMP_Int64& ADOhintOffset,XMP_Int64& InjectData1Offset, + XMP_Int64& InjectData3Offset); + //Different update methods + void InplaceUpdate (std::string &outStr,XMP_IO* &tempRef, bool doSafeUpdate); + void ExpandingSFDFilterUpdate (std::string &outStr,XMP_IO* &tempRef, bool doSafeUpdate ); + void InsertNewUpdate ( std::string &outStr,XMP_IO* &tempRef, bool doSafeUpdate ); +private: + //Flag tracks DSC comments + XMP_Uns32 dscFlags; + //Flag tracks DOCINFO keys + XMP_Uns32 docInfoFlags; + //stores the native metadata values. Index values an enum var NativeMetadataIndex + std::string nativeMeta[kPS_MaxNativeIndexValue]; + //all offsets are to the end of the comment after atleast one whitespace + TokenLocation fileTokenInfo[25]; + //Indicates the presence of both XMP hint and XMP + bool containsXMPHint; + //Keeps track whether a PS or EPS + XMP_FileFormat fileformat; + //keep the first packet info + XMP_PacketInfo firstPacketInfo; + //keep the last packet info + XMP_PacketInfo lastPacketInfo; + +}; // PostScript_MetaHandler + +// ================================================================================================= + +#endif /* __PostScript_Handler_hpp__ */ diff --git a/core/libs/dngwriter/extra/xmp_sdk/XMPFiles/source/FileHandlers/RIFF_Handler.cpp b/core/libs/dngwriter/extra/xmp_sdk/XMPFiles/source/FileHandlers/RIFF_Handler.cpp new file mode 100644 index 0000000000..5884390cf1 --- /dev/null +++ b/core/libs/dngwriter/extra/xmp_sdk/XMPFiles/source/FileHandlers/RIFF_Handler.cpp @@ -0,0 +1,357 @@ +// ================================================================================================= +// ADOBE SYSTEMS INCORPORATED +// Copyright 2009 Adobe Systems Incorporated +// All Rights Reserved +// +// NOTICE: Adobe permits you to use, modify, and distribute this file in accordance with the terms +// of the Adobe license agreement accompanying it. +// ================================================================================================= + +#include "public/include/XMP_Environment.h" // ! XMP_Environment.h must be the first included header. +#include "public/include/XMP_Const.h" + +#include "XMPFiles/source/FormatSupport/RIFF.hpp" +#include "XMPFiles/source/FileHandlers/RIFF_Handler.hpp" +#include "source/XIO.hpp" + +using namespace std; + +// ================================================================================================= +/// \file RIFF_Handler.cpp +/// \brief File format handler for RIFF. +// ================================================================================================= + +// ================================================================================================= +// RIFF_MetaHandlerCTor +// ==================== + +XMPFileHandler * RIFF_MetaHandlerCTor ( XMPFiles * parent ) +{ + return new RIFF_MetaHandler ( parent ); +} + +// ================================================================================================= +// RIFF_CheckFormat +// =============== +// +// An RIFF file must begin with "RIFF", a 4 byte length, then the chunkType (AVI or WAV) +// The specified length MUST (in practice: SHOULD) match fileSize-8, but we don't bother checking this here. + +bool RIFF_CheckFormat ( XMP_FileFormat format, + XMP_StringPtr filePath, + XMP_IO* file, + XMPFiles* parent ) +{ + IgnoreParam(format); IgnoreParam(parent); + XMP_Assert ( (format == kXMP_AVIFile) || (format == kXMP_WAVFile) ); + + if ( file->Length() < 12 ) return false; + file ->Rewind(); + + XMP_Uns8 chunkID[12]; + file->ReadAll ( chunkID, 12 ); + if ( ! CheckBytes( &chunkID[0], "RIFF", 4 )) return false; + + if ( CheckBytes(&chunkID[8],"AVI ",4) && format == kXMP_AVIFile ) return true; + if ( CheckBytes(&chunkID[8],"WAVE",4) && format == kXMP_WAVFile ) return true; + + return false; + +} // RIFF_CheckFormat + +// ================================================================================================= +// RIFF_MetaHandler::RIFF_MetaHandler +// ================================ + +RIFF_MetaHandler::RIFF_MetaHandler ( XMPFiles * _parent ) +{ + this->parent = _parent; + this->handlerFlags = kRIFF_HandlerFlags; + this->stdCharForm = kXMP_Char8Bit; + + this->oldFileSize = this->newFileSize = this->trailingGarbageSize = 0; + this->level = 0; + this->listInfoChunk = this->listTdatChunk = this->listHdlrChunk = 0; + this->dispChunk = this->bextChunk = this->cr8rChunk = this->prmlChunk = 0; + this->xmpChunk = 0; + this->lastChunk = 0; + this->iditChunk = 0; + this->hasListInfoINAM = false; +} + +// ================================================================================================= +// RIFF_MetaHandler::~RIFF_MetaHandler +// ================================= + +RIFF_MetaHandler::~RIFF_MetaHandler() +{ + while ( ! this->riffChunks.empty() ) + { + delete this->riffChunks.back(); + this->riffChunks.pop_back(); + } +} + +// ================================================================================================= +// RIFF_MetaHandler::CacheFileData +// ============================== + +void RIFF_MetaHandler::CacheFileData() +{ + this->containsXMP = false; //assume for now + + XMP_IO* file = this->parent->ioRef; + this->oldFileSize = file ->Length(); + if ( (this->parent->format == kXMP_WAVFile) && (this->oldFileSize > 0xFFFFFFFF) ) + XMP_Throw ( "RIFF_MetaHandler::CacheFileData: WAV Files larger 4GB not supported", kXMPErr_Unimplemented ); + + file ->Rewind(); + this->level = 0; + + // parse top-level chunks (most likely just one, except large avi files) + XMP_Int64 filePos = 0; + while ( filePos < this->oldFileSize ) + { + + this->riffChunks.push_back( (RIFF::ContainerChunk*) RIFF::getChunk( NULL, this ) ); + + // Tolerate limited forms of trailing garbage in a file. Some apps append private data. + + filePos = file->Offset(); + XMP_Int64 fileTail = this->oldFileSize - filePos; + + if ( fileTail != 0 ) { + + if ( fileTail < 12 ) { + + this->oldFileSize = filePos; // Pretend the file is smaller. + this->trailingGarbageSize = fileTail; + + } else if ( this->parent->format == kXMP_WAVFile ) { + + if ( fileTail < 1024*1024 ) { + this->oldFileSize = filePos; // Pretend the file is smaller. + this->trailingGarbageSize = fileTail; + } else { + XMP_Throw ( "Excessive garbage at end of file", kXMPErr_BadFileFormat ) + } + + } else { + + XMP_Int32 chunkInfo [3]; + file->ReadAll ( &chunkInfo, 12 ); + file->Seek ( -12, kXMP_SeekFromCurrent ); + if ( (GetUns32LE ( &chunkInfo[0] ) != RIFF::kChunk_RIFF) || (GetUns32LE ( &chunkInfo[2] ) != RIFF::kType_AVIX) ) { + if ( fileTail < 1024*1024 ) { + this->oldFileSize = filePos; // Pretend the file is smaller. + this->trailingGarbageSize = fileTail; + } else { + XMP_Throw ( "Excessive garbage at end of file", kXMPErr_BadFileFormat ) + } + } + + } + + } + + } + + // covered before => internal error if it occurs + XMP_Validate( file->Offset() == this->oldFileSize, + "RIFF_MetaHandler::CacheFileData: unknown data at end of file", + kXMPErr_InternalFailure ); + +} // RIFF_MetaHandler::CacheFileData + +// ================================================================================================= +// RIFF_MetaHandler::ProcessXMP +// ============================ + +void RIFF_MetaHandler::ProcessXMP() +{ + SXMPUtils::RemoveProperties ( &this->xmpObj, 0, 0, kXMPUtil_DoAllProperties ); + // process physical packet first + if ( this->containsXMP ) this->xmpObj.ParseFromBuffer ( this->xmpPacket.c_str(), (XMP_StringLen)this->xmpPacket.size() ); + // then import native properties: + RIFF::importProperties( this ); + this->processedXMP = true; +} + +// ================================================================================================= +// RIFF_MetaHandler::UpdateFile +// =========================== + +void RIFF_MetaHandler::UpdateFile ( bool doSafeUpdate ) +{ + XMP_Validate( this->needsUpdate, "nothing to update", kXMPErr_InternalFailure ); + + //////////////////////////////////////////////////////////////////////////////////////// + //////////// PASS 1: basics, exports, packet reserialze + XMP_IO* file = this->parent->ioRef; + RIFF::containerVect *rc = &this->riffChunks; + + //temptemp + //printf( "BEFORE:\n%s\n", rc->at(0)->toString().c_str() ); + + XMP_Enforce( rc->size() >= 1); + RIFF::ContainerChunk* mainChunk = rc->at(0); + this->lastChunk = rc->at( rc->size() - 1 ); // (may or may not coincide with mainChunk: ) + XMP_Enforce( mainChunk != NULL ); + + RIFF::relocateWronglyPlacedXMPChunk( this ); + // [2435625] lifted disablement for AVI + RIFF::exportAndRemoveProperties( this ); + + // always rewrite both LISTs (implicit size changes, e.g. through 0-term corrections may + // have very well led to size changes...) + // set XMP packet info, re-serialize + this->packetInfo.charForm = stdCharForm; + this->packetInfo.writeable = true; + this->packetInfo.offset = kXMPFiles_UnknownOffset; + this->packetInfo.length = kXMPFiles_UnknownLength; + + // re-serialization ( needed because of above exportAndRemoveProperties() ) + try { + if ( this->xmpChunk == 0 ) // new chunk? pad with 2K + this->xmpObj.SerializeToBuffer ( &this->xmpPacket, kXMP_NoOptions , 2048 ); + else // otherwise try to match former size + this->xmpObj.SerializeToBuffer ( &this->xmpPacket, kXMP_ExactPacketLength , (XMP_Uns32) this->xmpChunk->oldSize-8 ); + } catch ( ... ) { // if that fails, be happy with whatever. + this->xmpObj.SerializeToBuffer ( &this->xmpPacket, kXMP_NoOptions ); + } + + if ( (this->xmpPacket.size() & 1) == 1 ) this->xmpPacket += ' '; // Force the XMP to have an even size. + + // if missing, add xmp packet at end: + if( this->xmpChunk == 0 ) + this->xmpChunk = new RIFF::XMPChunk( this->lastChunk ); + // * parenting happens within this call. + // * size computation will happen in XMPChunk::changesAndSize() + // * actual data will be set in XMPChunk::write() + + //////////////////////////////////////////////////////////////////////////////////////// + // PASS 2: compute sizes, optimize container structure (no writing yet) + { + this->newFileSize = 0; + + // note: going through forward (not vice versa) is an important condition, + // so that parking LIST:Tdat,Cr8r, PrmL to last chunk is doable + // when encountered en route + for ( XMP_Uns32 chunkNo = 0; chunkNo < rc->size(); chunkNo++ ) + { + RIFF::Chunk* cur = rc->at(chunkNo); + cur->changesAndSize( this ); + this->newFileSize += cur->newSize; + if ( this->newFileSize % 2 == 1 ) this->newFileSize++; // pad byte + } + this->newFileSize += this->trailingGarbageSize; + } // PASS2 + + //////////////////////////////////////////////////////////////////////////////////////// + // PASS 2a: verify no chunk violates 2GB boundaries + switch( this->parent->format ) + { + // NB: <4GB for ALL chunks asserted before in ContainerChunk::changesAndSize() + + case kXMP_AVIFile: + // ensure no chunk (single or multi, no matter) crosses 2 GB ... + for ( XMP_Int32 chunkNo = 0; chunkNo < (XMP_Int32)rc->size(); chunkNo++ ) + { + if ( rc->at(chunkNo)->oldSize <= 0x80000000LL ) // ... if <2GB before + XMP_Validate( rc->at(chunkNo)->newSize <= 0x80000000LL, + "Chunk grew beyond 2 GB", kXMPErr_Unimplemented ); + } + + // compatibility: if single-chunk AND below <1GB, ensure <1GB + if ( ( rc->size() > 1 ) && ( rc->at(0)->oldSize < 0x40000000 ) ) + { + XMP_Validate( rc->at(0)->newSize < 0x40000000LL, "compatibility: mainChunk must remain < 1GB" , kXMPErr_Unimplemented ); + } + + // [2473381] compatibility: if single-chunk AND >2GB,<4GB, ensure <4GB + if ( ( rc->size() > 1 ) && + ( rc->at(0)->oldSize > 0x80000000LL ) && // 2GB + ( rc->at(0)->oldSize < 0x100000000LL ) ) // 4GB + { + XMP_Validate( rc->at(0)->newSize < 0x100000000LL, "compatibility: mainChunk must remain < 4GB" , kXMPErr_Unimplemented ); + } + + break; + + case kXMP_WAVFile: + XMP_Validate( 1 == rc->size(), "WAV must be single-chunk", kXMPErr_InternalFailure ); + XMP_Validate( rc->at(0)->newSize <= 0xFFFFFFFFLL, "WAV above 4 GB not supported", kXMPErr_Unimplemented ); + break; + + default: + XMP_Throw( "unknown format", kXMPErr_InternalFailure ); + } + + //////////////////////////////////////////////////////////////////////////////////////// + // PASS 3: write avix chunk(s) if applicable (shrinks or stays) + // and main chunk. -- operation order depends on mainHasShrunk. + { + // if needed, extend file beforehand + if ( this->newFileSize > this->oldFileSize ) { + file->Seek ( newFileSize, kXMP_SeekFromStart ); + file->Rewind(); + } + + RIFF::Chunk* mainChunk = rc->at(0); + + XMP_Int64 mainGrowth = mainChunk->newSize - mainChunk->oldSize; + XMP_Enforce( mainGrowth >= 0 ); // main always stays or grows + + //temptemp + //printf( "AFTER:\n%s\n", rc->at(0)->toString().c_str() ); + + if ( rc->size() > 1 ) // [2457482] + XMP_Validate( mainGrowth == 0, "mainChunk must not grow, if multiple RIFF chunks", kXMPErr_InternalFailure ); + + // back to front: + + XMP_Int64 avixStart = newFileSize; // count from the back + + if ( this->trailingGarbageSize != 0 ) { + XMP_Int64 goodDataEnd = this->newFileSize - this->trailingGarbageSize; + XIO::Move ( file, this->oldFileSize, file, goodDataEnd, this->trailingGarbageSize ); + avixStart = goodDataEnd; + } + + for ( XMP_Int32 chunkNo = ((XMP_Int32)rc->size()) -1; chunkNo >= 0; chunkNo-- ) + { + RIFF::Chunk* cur = rc->at(chunkNo); + + avixStart -= cur->newSize; + if ( avixStart % 2 == 1 ) // rewind one more + avixStart -= 1; + + file->Seek ( avixStart , kXMP_SeekFromStart ); + + if ( cur->hasChange ) // need explicit write-out ? + cur->write( this, file, chunkNo == 0 ); + else // or will a simple move do? + { + XMP_Enforce( cur->oldSize == cur->newSize ); + if ( cur->oldPos != avixStart ) // important optimization: only move if there's a need to + XIO::Move( file, cur->oldPos, file, avixStart, cur->newSize ); + } + } + + // if needed, shrink file afterwards + if ( this->newFileSize < this->oldFileSize ) file->Truncate ( this->newFileSize ); + } // PASS 3 + + this->needsUpdate = false; //do last for safety +} // RIFF_MetaHandler::UpdateFile + +// ================================================================================================= +// RIFF_MetaHandler::WriteTempFile +// =============================== + +void RIFF_MetaHandler::WriteTempFile ( XMP_IO* tempRef ) +{ + IgnoreParam( tempRef ); + XMP_Throw ( "RIFF_MetaHandler::WriteTempFile: Not supported (must go through UpdateFile", kXMPErr_Unavailable ); +} + diff --git a/core/libs/dngwriter/extra/xmp_sdk/XMPFiles/source/FileHandlers/RIFF_Handler.hpp b/core/libs/dngwriter/extra/xmp_sdk/XMPFiles/source/FileHandlers/RIFF_Handler.hpp new file mode 100644 index 0000000000..bfa7b2a6db --- /dev/null +++ b/core/libs/dngwriter/extra/xmp_sdk/XMPFiles/source/FileHandlers/RIFF_Handler.hpp @@ -0,0 +1,74 @@ +// ================================================================================================= +// ADOBE SYSTEMS INCORPORATED +// Copyright 2009 Adobe Systems Incorporated +// All Rights Reserved +// +// NOTICE: Adobe permits you to use, modify, and distribute this file in accordance with the terms +// of the Adobe license agreement accompanying it. +// ================================================================================================= +#ifndef __RIFF_Handler_hpp__ +#define __RIFF_Handler_hpp__ 1 + +#include "public/include/XMP_Environment.h" // ! XMP_Environment.h must be the first included header. +#include "public/include/XMP_Const.h" + +#include "XMPFiles/source/FormatSupport/RIFF_Support.hpp" +#include "source/XIO.hpp" + +// ================================================================================================= +/// \file RIFF_Handler.hpp +/// \brief File format handler for RIFF (AVI, WAV). +// ================================================================================================= + +extern XMPFileHandler * RIFF_MetaHandlerCTor ( XMPFiles * parent ); + +extern bool RIFF_CheckFormat ( XMP_FileFormat format, + XMP_StringPtr filePath, + XMP_IO * fileRef, + XMPFiles * parent ); + +static const XMP_OptionBits kRIFF_HandlerFlags = (kXMPFiles_CanInjectXMP | + kXMPFiles_CanExpand | + kXMPFiles_PrefersInPlace | + kXMPFiles_AllowsOnlyXMP | + kXMPFiles_ReturnsRawPacket | + kXMPFiles_CanReconcile + ); + +class RIFF_MetaHandler : public XMPFileHandler +{ +public: + RIFF_MetaHandler ( XMPFiles* parent ); + ~RIFF_MetaHandler(); + + void CacheFileData(); + void ProcessXMP(); + + void UpdateFile ( bool doSafeUpdate ); + void WriteTempFile ( XMP_IO* tempRef ); + + + //////////////////////////////////////////////////////////////////////////////////// + // instance vars + // most often just one RIFF:* (except for AVI,[AVIX] >1 GB) + std::vector riffChunks; + XMP_Int64 oldFileSize, newFileSize, trailingGarbageSize; + + // state variables, needed during parsing + XMP_Uns8 level; + + RIFF::ContainerChunk *listInfoChunk, *listTdatChunk,*listHdlrChunk; + RIFF::ValueChunk* dispChunk; + RIFF::ValueChunk* bextChunk; + RIFF::ValueChunk* cr8rChunk; + RIFF::ValueChunk* prmlChunk; + RIFF::ValueChunk* iditChunk; + RIFF::XMPChunk* xmpChunk; + RIFF::ContainerChunk* lastChunk; + bool hasListInfoINAM; // needs to be known for the special 3-way merge around dc:title + +}; // RIFF_MetaHandler + +// ================================================================================================= + +#endif /* __RIFF_Handler_hpp__ */ diff --git a/core/libs/dngwriter/extra/xmp_sdk/XMPFiles/source/FileHandlers/SVG_Handler.cpp b/core/libs/dngwriter/extra/xmp_sdk/XMPFiles/source/FileHandlers/SVG_Handler.cpp new file mode 100644 index 0000000000..93ef108bf9 --- /dev/null +++ b/core/libs/dngwriter/extra/xmp_sdk/XMPFiles/source/FileHandlers/SVG_Handler.cpp @@ -0,0 +1,689 @@ +// ================================================================================================= +// ADOBE SYSTEMS INCORPORATED +// Copyright 2015 Adobe Systems Incorporated +// All Rights Reserved +// +// NOTICE: Adobe permits you to use, modify, and distribute this file in accordance with the terms +// of the Adobe license agreement accompanying it. +// +// This file includes implementation of SVG metadata, according to Scalable Vector Graphics (SVG) 1.1 Specification. +// "https://www.w3.org/TR/2003/REC-SVG11-20030114/" +// Copyright © 1994-2002 World Wide Web Consortium, (Massachusetts Institute of Technology, +// Institut National de Recherche en Informatique et en Automatique, Keio University). +// All Rights Reserved . http://www.w3.org/Consortium/Legal +// +// ================================================================================================= + +#include "public/include/XMP_Environment.h" // ! XMP_Environment.h must be the first included header. + +#include "public/include/XMP_Const.h" +#include "public/include/XMP_IO.hpp" + +#include "XMPFiles/source/XMPFiles_Impl.hpp" +#include "source/XMPFiles_IO.hpp" +#include "source/XIO.hpp" + +#include "XMPFiles/source/FileHandlers/SVG_Handler.hpp" + +using namespace std; + +/* + Currently supporting only UTF-8 encoded SVG +*/ + +// ================================================================================================= +// SVG_CheckFormat +// =============== + +bool SVG_CheckFormat( XMP_FileFormat format, + XMP_StringPtr filePath, + XMP_IO * fileRef, + XMPFiles * parent ) +{ + // 8K buffer is provided just to handle maximum SVG files + // We can't check for SVG element in whole file which could take a lot of time for valid XML files + IgnoreParam( filePath ); IgnoreParam( parent ); + + XMP_Assert( format == kXMP_SVGFile ); + + fileRef->Rewind(); + + XMP_Uns8 buffer[ 1024 ]; + + // Reading 4 bytes for BOM + XMP_Uns32 bytesRead = fileRef->Read( buffer, 4 ); + if ( bytesRead != 4 ) + return false; + + // Checking for UTF-16 BOM and UTF-32 BOM + if ( ( buffer[ 0 ] == 0xFF && buffer[ 1 ] == 0xFE ) || ( buffer[ 0 ] == 0xFE && buffer[ 1 ] == 0xFF ) || ( buffer[ 0 ] == buffer[ 1 ] == 0x00 && buffer[ 2 ] == 0xFE && buffer[ 3 ] == 0xFF ) ) + { + return false; + } + + // Initially we are intersted only in "svg" element. + SVG_Adapter * svgChecker = new SVG_Adapter(); + if ( svgChecker == 0 ) + return false; + + bool isSVG = false; + + fileRef->Rewind(); + for ( XMP_Uns8 index = 0; index < 8; ++index ) + { + XMP_Int32 ioCount = fileRef->Read( buffer, sizeof( buffer ) ); + if ( ioCount == 0 ) break; + + // Checking for well formed XML + if ( !svgChecker->ParseBufferNoThrow( buffer, ioCount, false /* not the end */ ) ) + break; + + if ( svgChecker->tree.GetNamedElement( "http://www.w3.org/2000/svg", "svg" ) ) + { + isSVG = true; + break; + } + } + + if ( svgChecker ) + delete ( svgChecker ); + + return isSVG; + +} // SVG_CheckFormat + +// ================================================================================================= +// SVG_MetaHandlerCTor +// =================== + +XMPFileHandler * SVG_MetaHandlerCTor( XMPFiles * parent ) +{ + return new SVG_MetaHandler( parent ); + +} // SVG_MetaHandlerCTor + +// ================================================================================================= +// SVG_MetaHandler::SVG_MetaHandler +// ================================ + +SVG_MetaHandler::SVG_MetaHandler( XMPFiles * _parent ) : svgNode( 0 ), svgAdapter( 0 ), isTitleUpdateReq( false ), isDescUpdateReq( false ) +{ + this->parent = _parent; + this->handlerFlags = kSVG_HandlerFlags; + this->stdCharForm = kXMP_Char8Bit; + +} + +// ================================================================================================= +// SVG_MetaHandler::~SVG_MetaHandler +// ================================= + +SVG_MetaHandler::~SVG_MetaHandler() +{ + + if ( this->svgAdapter != 0 ) + { + delete ( this->svgAdapter ); + this->svgAdapter = 0; + } +} + +// ================================================================================================= +// SVG_MetaHandler::GetSerializeOptions +// =================================== +// +// Override default implementation to ensure Canonical packet. + +XMP_OptionBits SVG_MetaHandler::GetSerializeOptions() +{ + + return ( kXMP_UseCanonicalFormat ); + +} // SVG_MetaHandler::GetSerializeOptions + +// ================================================================================================= +// SVG_MetaHandler::CacheFileData +// ============================== + +void SVG_MetaHandler::CacheFileData() +{ + XMP_Assert( !this->containsXMP ); + + XMP_IO * fileRef = this->parent->ioRef; + + XMP_Uns8 marker[ 4 ]; + fileRef->Rewind(); + fileRef->Read( marker, 4 ); + + // Checking for UTF-16 BOM and UTF-32 BOM + if ( ( marker[ 0 ] == 0xFF && marker[ 1 ] == 0xFE ) || ( marker[ 0 ] == 0xFE && marker[ 1 ] == 0xFF ) || ( marker[ 0 ] == marker[ 1 ] == 0x00 && marker[ 2 ] == 0xFE && marker[ 3 ] == 0xFF ) ) + { + XMP_Error error( kXMPErr_BadXML, "Invalid SVG file" ); + this->NotifyClient( &this->parent->errorCallback, kXMPErrSev_OperationFatal, error ); + } + + // Creating a new SVG Parser + svgAdapter = new SVG_Adapter(); + if ( svgAdapter == 0 ) + XMP_Throw( "SVG_MetaHandler: Can't create SVG adapter", kXMPErr_NoMemory ); + svgAdapter->SetErrorCallback( &this->parent->errorCallback ); + + // Registering all the required tags to SVG Parser + svgAdapter->RegisterPI( "xpacket" ); + svgAdapter->RegisterElement( "metadata", "svg" ); + svgAdapter->RegisterElement( "xmpmeta", "metadata" ); + svgAdapter->RegisterElement( "RDF", "metadata" ); + svgAdapter->RegisterElement( "title", "svg" ); + svgAdapter->RegisterElement( "desc", "svg" ); + + // Parsing the whole buffer + fileRef->Rewind(); + XMP_Uns8 buffer[ 64 * 1024 ]; + while ( true ) { + XMP_Int32 ioCount = fileRef->Read( buffer, sizeof( buffer ) ); + if ( ioCount == 0 || !svgAdapter->IsParsingRequire() ) break; + svgAdapter->ParseBuffer( buffer, ioCount, false /* not the end */ ); + } + svgAdapter->ParseBuffer( 0, 0, true ); // End the parse. + + XML_Node & xmlTree = this->svgAdapter->tree; + XML_NodePtr rootElem = 0; + + for ( size_t i = 0, limit = xmlTree.content.size(); i < limit; ++i ) + { + if ( xmlTree.content[ i ]->kind == kElemNode ) { + rootElem = xmlTree.content[ i ]; + } + } + if ( rootElem == 0 ) + XMP_Throw( "Not a valid SVG File", kXMPErr_BadFileFormat ); + + XMP_StringPtr rootLocalName = rootElem->name.c_str() + rootElem->nsPrefixLen; + + if ( ! XMP_LitMatch( rootLocalName, "svg" ) ) + XMP_Throw( "Not able to parse such SVG File", kXMPErr_BadFileFormat ); + + // Making SVG node as Root Node + svgNode = rootElem; + + bool FoundPI = false; + bool FoundWrapper = false; + XML_NodePtr metadataNode = svgNode->GetNamedElement( rootElem->ns.c_str(), "metadata" ); + + // We are intersted only in the Metadata tag of outer SVG element + // XMP should be present only in metadata Node of SVG + if ( metadataNode != NULL ) + { + XMP_Int64 packetLength = -1; + XMP_Int64 packetOffset = -1; + XMP_Int64 PIOffset = svgAdapter->GetPIOffset( "xpacket", 1 ); + OffsetStruct wrapperOffset = svgAdapter->GetElementOffsets( "xmpmeta" ); + OffsetStruct rdfOffset = svgAdapter->GetElementOffsets( "RDF" ); + + // Checking XMP PI's position + if ( PIOffset != -1 ) + { + if ( wrapperOffset.startOffset != -1 && wrapperOffset.startOffset < PIOffset ) + packetOffset = wrapperOffset.startOffset; + else + { + XMP_Int64 trailerOffset = svgAdapter->GetPIOffset( "xpacket", 2 ); + XML_NodePtr trailerNode = metadataNode->GetNamedElement( "", "xpacket", 1 ); + if ( trailerOffset != -1 || trailerNode != 0 ) + { + packetLength = 2; // "name.length(); // Node's name + packetLength += 1; // Empty Space after Node's name + packetLength += trailerNode->value.length(); // Value + packetLength += 2; // "?>" = 2 + packetLength += ( trailerOffset - PIOffset ); + packetOffset = PIOffset; + } + } + } + else if ( wrapperOffset.startOffset != -1 ) // XMP Wrapper is present without PI + { + XML_NodePtr wrapperNode = metadataNode->GetNamedElement( "adobe:ns:meta/", "xmpmeta" ); + if ( wrapperNode != 0 ) + { + std::string trailerWrapper = ""; + packetLength = trailerWrapper.length(); + packetLength += ( wrapperOffset.endOffset - wrapperOffset.startOffset ); + packetOffset = wrapperOffset.startOffset; + } + } + else // RDF packet is present without PI and wrapper + { + XML_NodePtr rdfNode = metadataNode->GetNamedElement( "http://www.w3.org/1999/02/22-rdf-syntax-ns#", "RDF" ); + if ( rdfNode != 0 ) + { + std::string rdfTrailer = ""; + packetLength = rdfTrailer.length(); + packetLength += ( rdfOffset.endOffset - rdfOffset.startOffset ); + packetOffset = rdfOffset.startOffset; + } + } + + // Fill the necesarry information and packet with XMP data + if ( packetOffset != -1 ) + { + this->packetInfo.offset = packetOffset; + this->packetInfo.length = ( XMP_Int32 ) packetLength; + this->xmpPacket.assign( this->packetInfo.length, ' ' ); + fileRef->Seek( packetOffset, kXMP_SeekFromStart ); + fileRef->ReadAll( ( void* )this->xmpPacket.data(), this->packetInfo.length ); + FillPacketInfo( this->xmpPacket, &this->packetInfo ); + this->containsXMP = true; + return; + } + } + this->containsXMP = false; + +} // SVG_MetaHandler::CacheFileData + +// ================================================================================================= +// SVG_MetaHandler::ProcessXMP +// ============================== + +void SVG_MetaHandler::ProcessXMP() +{ + // + // Here we are intersted in Only 2 childs, title and desc + // + this->processedXMP = true; // Make sure we only come through here once. + + if ( svgNode == NULL ) + return; + + if ( !this->xmpPacket.empty() ) { + XMP_Assert( this->containsXMP ); + this->xmpObj.ParseFromBuffer( this->xmpPacket.c_str(), ( XMP_StringLen )this->xmpPacket.size() ); + } + + // Description + XML_NodePtr descNode = svgNode->GetNamedElement( svgNode->ns.c_str(), "desc" ); + if ( descNode != 0 && descNode->content.size() == 1 && descNode->content[0]->kind == kCDataNode ) + { + this->xmpObj.SetLocalizedText( kXMP_NS_DC, "description", "", "x-default", descNode->content[0]->value, kXMP_DeleteExisting ); + this->containsXMP = true; + } + + // Title + XML_NodePtr titleNode = svgNode->GetNamedElement( svgNode->ns.c_str(), "title" ); + if ( titleNode != 0 && titleNode->content.size() == 1 && titleNode->content[ 0 ]->kind == kCDataNode ) + { + this->xmpObj.SetLocalizedText( kXMP_NS_DC, "title", "", "x-default", titleNode->content[0]->value, kXMP_DeleteExisting ); + this->containsXMP = true; + } + +} // SVG_MetaHandler::ProcessXMP + +// ================================================================================================= +// SVG_MetaHandler::ProcessTitle +// =========================== +// It is handling the updation and deletion case +void SVG_MetaHandler::ProcessTitle( XMP_IO* sourceRef, XMP_IO * destRef, const std::string &value, XMP_Int64 ¤tOffset, const OffsetStruct & titleOffset ) +{ + if ( value.empty() ) + { + XIO::Copy( sourceRef, destRef, titleOffset.startOffset - currentOffset ); + sourceRef->Seek( titleOffset.nextOffset, kXMP_SeekFromStart ); + currentOffset = titleOffset.nextOffset; + } + else + { + std::string titleElement = ""; + XIO::Copy( sourceRef, destRef, titleOffset.startOffset - currentOffset + titleElement.length() ); + destRef->Write( value.c_str(), static_cast< int >( value.length() ) ); + sourceRef->Seek( titleOffset.endOffset, kXMP_SeekFromStart ); + currentOffset = titleOffset.endOffset; + } +} // SVG_MetaHandler::ProcessTitle + +// ================================================================================================= +// SVG_MetaHandler::ProcessDescription +// =========================== +// It is handling the updation and deletion case +void SVG_MetaHandler::ProcessDescription( XMP_IO* sourceRef, XMP_IO * destRef, const std::string &value, XMP_Int64 ¤tOffset, const OffsetStruct & descOffset ) +{ + if ( value.empty() ) + { + XIO::Copy( sourceRef, destRef, descOffset.startOffset - currentOffset ); + sourceRef->Seek( descOffset.nextOffset, kXMP_SeekFromStart ); + currentOffset = descOffset.nextOffset; + } + else + { + std::string descElement = "<desc>"; + XIO::Copy( sourceRef, destRef, descOffset.startOffset - currentOffset + descElement.length() ); + destRef->Write( value.c_str(), static_cast< int >( value.length() ) ); + sourceRef->Seek( descOffset.endOffset, kXMP_SeekFromStart ); + currentOffset = descOffset.endOffset; + } + +} // SVG_MetaHandler::ProcessDescription + +// ================================================================================================= +// SVG_MetaHandler::InsertNewTitle +// =========================== +// It is handling the insertion case +void SVG_MetaHandler::InsertNewTitle( XMP_IO * destRef, const std::string &value ) +{ + std::string titleElement = "<title>"; + destRef->Write( titleElement.c_str(), static_cast< int >( titleElement.length() ) ); + destRef->Write( value.c_str(), static_cast< int >( value.length() ) ); + titleElement = "\n"; + destRef->Write( titleElement.c_str(), static_cast< int >( titleElement.length() ) ); + +} // SVG_MetaHandler::InsertNewTitle + +// ================================================================================================= +// SVG_MetaHandler::InsertNewDescription +// =========================== +// It is handling the insertion case +void SVG_MetaHandler::InsertNewDescription( XMP_IO * destRef, const std::string &value ) +{ + std::string descElement = ""; + destRef->Write( descElement.c_str(), static_cast< int >( descElement.length() ) ); + destRef->Write( value.c_str(), static_cast< int >( value.length() ) ); + descElement = "\n"; + destRef->Write( descElement.c_str(), static_cast< int >( descElement.length() ) ); + +} // SVG_MetaHandler::InsertNewDescription + +// ================================================================================================= +// SVG_MetaHandler::InsertNewMetadata +// =========================== +// It is handling the insertion case +void SVG_MetaHandler::InsertNewMetadata( XMP_IO * destRef, const std::string &value ) +{ + + std::string metadataElement = ""; + destRef->Write( metadataElement.c_str(), static_cast< int >( metadataElement.length() ) ); + destRef->Write( value.c_str(), static_cast< int >( value.length() ) ); + metadataElement = "\n"; + destRef->Write( metadataElement.c_str(), static_cast< int >( metadataElement.length() ) ); + +} // SVG_MetaHandler::InsertNewMetadata + +// ================================================================================================= +// SVG_MetaHandler::UpdateFile +// =========================== + +void SVG_MetaHandler::UpdateFile( bool doSafeUpdate ) +{ + XMP_Assert( !doSafeUpdate ); // This should only be called for "unsafe" updates. + + XMP_IO* sourceRef = this->parent->ioRef; + + if ( sourceRef == NULL || svgNode == NULL ) + return; + + // Checking whether Title updation requires or not + std::string title; + XML_NodePtr titleNode = svgNode->GetNamedElement( svgNode->ns.c_str(), "title" ); + (void) this->xmpObj.GetLocalizedText( kXMP_NS_DC, "title", "", "x-default", 0, &title, 0 ); + if ( ( titleNode == NULL ) == ( title.empty() ) ) + { + if ( titleNode != NULL && titleNode->content.size() == 1 && titleNode->content[ 0 ]->kind == kCDataNode && !XMP_LitMatch( titleNode->content[ 0 ]->value.c_str(), title.c_str() ) ) + isTitleUpdateReq = true; + } + else + isTitleUpdateReq = true; + + // Checking whether Description updation requires or not + std::string description; + XML_NodePtr descNode = svgNode->GetNamedElement( svgNode->ns.c_str(), "desc" ); + ( void ) this->xmpObj.GetLocalizedText( kXMP_NS_DC, "description", "", "x-default", 0, &description, 0 ); + if ( ( descNode == NULL ) == ( description.empty() ) ) + { + if ( descNode != NULL && descNode->content.size() == 1 && descNode->content[ 0 ]->kind == kCDataNode && !XMP_LitMatch( descNode->content[ 0 ]->value.c_str(), description.c_str() ) ) + isDescUpdateReq = true; + } + else + isDescUpdateReq = true; + + // If any updation is required then don't do inplace replace + bool isUpdateRequire = isTitleUpdateReq | isDescUpdateReq | (this->packetInfo.offset == kXMPFiles_UnknownOffset); + + // Inplace Updation of XMP + if ( !isUpdateRequire && this->xmpPacket.size() == this->packetInfo.length ) + { + sourceRef->Seek( this->packetInfo.offset, kXMP_SeekFromStart ); + sourceRef->Write( this->xmpPacket.c_str(), static_cast< int >( this->xmpPacket.size() ) ); + } + else + { + // Inplace is not possibe, So perform full updation + try + { + XMP_IO* tempRef = sourceRef->DeriveTemp(); + this->WriteTempFile( tempRef ); + } + catch ( ... ) + { + sourceRef->DeleteTemp(); + throw; + } + + sourceRef->AbsorbTemp(); + } + + this->needsUpdate = false; + +} // SVG_MetaHandler::UpdateFile + +// ================================================================================================= +// SVG_MetaHandler::WriteTempFile +// ============================== +// +void SVG_MetaHandler::WriteTempFile( XMP_IO* tempRef ) +{ + XMP_Assert( this->needsUpdate ); + + XMP_IO* sourceRef = this->parent->ioRef; + if ( sourceRef == NULL || svgNode == NULL ) + return; + + tempRef->Rewind(); + sourceRef->Rewind(); + + XMP_Int64 currentOffset = svgAdapter->firstSVGElementOffset; + XIO::Copy( sourceRef, tempRef, currentOffset ); + + OffsetStruct titleOffset = svgAdapter->GetElementOffsets( "title" ); + OffsetStruct descOffset = svgAdapter->GetElementOffsets( "desc" ); + OffsetStruct metadataOffset = svgAdapter->GetElementOffsets( "metadata" ); + + std::string title; + std::string description; + + XML_NodePtr titleNode = svgNode->GetNamedElement( svgNode->ns.c_str(), "title" ); + ( void ) this->xmpObj.GetLocalizedText( kXMP_NS_DC, "title", "", "x-default", 0, &title, 0 ); + + XML_NodePtr descNode = svgNode->GetNamedElement( svgNode->ns.c_str(), "desc" ); + ( void ) this->xmpObj.GetLocalizedText( kXMP_NS_DC, "description", "", "x-default", 0, &description, 0 ); + + // Need to cover the case of both workflows + // This would have been called after inplace is not possible + // This would have called for safe update + if ( !isTitleUpdateReq ) + { + if ( ( titleNode == NULL ) == ( title.empty() ) ) + { + if ( titleNode != NULL && titleNode->content.size() == 1 && titleNode->content[ 0 ]->kind == kCDataNode && !XMP_LitMatch( titleNode->content[ 0 ]->value.c_str(), title.c_str() ) ) + isTitleUpdateReq = true; + } + else + isTitleUpdateReq = true; + } + if ( !isDescUpdateReq ) + { + if ( ( descNode == NULL ) == ( description.empty() ) ) + { + if ( descNode != NULL && descNode->content.size() == 1 && descNode->content[ 0 ]->kind == kCDataNode && !XMP_LitMatch( descNode->content[ 0 ]->value.c_str(), description.c_str() ) ) + isDescUpdateReq = true; + } + else + isDescUpdateReq = true; + } + + // Initial Insertion/Updation + + // Insert/Update Title if requires + // Don't insert/update it if Metadata or desc child comes before title child + bool isTitleWritten = !isTitleUpdateReq; + if ( isTitleUpdateReq ) + { + // Insertion Case + if ( titleNode == NULL ) + { + InsertNewTitle( tempRef, title ); + isTitleWritten = true; + } + else if ( ( descOffset.startOffset == -1 || titleOffset.startOffset < descOffset.startOffset ) // Updation/Deletion Case + && ( metadataOffset.startOffset == -1 || titleOffset.startOffset < metadataOffset.startOffset ) ) + { + ProcessTitle( sourceRef, tempRef, title, currentOffset, titleOffset ); + isTitleWritten = true; + } + } + + // Insert/Update Description if requires + // Don't insert/update it if Metadata child comes before desc child + bool isDescWritten = !isDescUpdateReq; + if ( isDescUpdateReq ) + { + if ( descNode == NULL ) + { + if ( titleOffset.nextOffset != -1 ) + { + XIO::Copy( sourceRef, tempRef, titleOffset.nextOffset - currentOffset ); + currentOffset = titleOffset.nextOffset; + } + InsertNewDescription( tempRef, description ); + isDescWritten = true; + } + else if ( metadataOffset.startOffset == -1 || descOffset.startOffset < metadataOffset.startOffset ) + { + ProcessDescription( sourceRef, tempRef, description, currentOffset, descOffset ); + isDescWritten = true; + } + } + + // Insert/Update Metadata if requires + // Don't insert/update it if case is DTM + bool isMetadataWritten = false; + if ( metadataOffset.startOffset == -1 ) + { + if ( descOffset.nextOffset != -1 ) + { + XIO::Copy( sourceRef, tempRef, descOffset.nextOffset - currentOffset ); + currentOffset = descOffset.nextOffset; + } + else if ( titleOffset.nextOffset != -1 ) + { + XIO::Copy( sourceRef, tempRef, titleOffset.nextOffset - currentOffset ); + currentOffset = titleOffset.nextOffset; + } + InsertNewMetadata( tempRef, this->xmpPacket ); + isMetadataWritten = true; + } + else if ( !( !isTitleWritten && isDescWritten && titleOffset.startOffset < metadataOffset.startOffset ) ) // Not DTM + { + // No XMP packet was present in the file + if ( this->packetInfo.offset == kXMPFiles_UnknownOffset ) + { + std::string metadataElement = ""; + XIO::Copy( sourceRef, tempRef, metadataOffset.startOffset - currentOffset + metadataElement.length() ); + currentOffset = sourceRef->Offset(); + tempRef->Write( this->xmpPacket.c_str(), static_cast< int >( this->xmpPacket.length() ) ); + } + else // Replace XMP Packet + { + XIO::Copy( sourceRef, tempRef, this->packetInfo.offset - currentOffset ); + tempRef->Write( this->xmpPacket.c_str(), static_cast< int >( this->xmpPacket.length() ) ); + sourceRef->Seek( this->packetInfo.offset + this->packetInfo.length, kXMP_SeekFromStart ); + currentOffset = sourceRef->Offset(); + } + isMetadataWritten = true; + } + + // If simple cases was followed then copy rest file + if ( isTitleWritten && isDescWritten && isMetadataWritten ) + { + XIO::Copy( sourceRef, tempRef, ( sourceRef->Length() - currentOffset ) ); + return; + } + + // If the case is not Simple (TDM) then perform these operations + if ( isDescWritten ) // TDM, DTM, DMT + { + if ( !isTitleWritten ) // DTM, DMT + { + if ( titleOffset.startOffset < metadataOffset.startOffset ) // DTM + { + ProcessTitle( sourceRef, tempRef, title, currentOffset, titleOffset ); + isTitleWritten = true; + + if ( this->packetInfo.offset == kXMPFiles_UnknownOffset ) + { + std::string metadataElement = ""; + XIO::Copy( sourceRef, tempRef, metadataOffset.startOffset - currentOffset + metadataElement.length() ); + currentOffset = sourceRef->Offset(); + tempRef->Write( this->xmpPacket.c_str(), static_cast< int >( this->xmpPacket.length() ) ); + } + else + { + XIO::Copy( sourceRef, tempRef, this->packetInfo.offset - currentOffset ); + tempRef->Write( this->xmpPacket.c_str(), static_cast< int >( this->xmpPacket.length() ) ); + sourceRef->Seek( this->packetInfo.offset + this->packetInfo.length, kXMP_SeekFromStart ); + currentOffset = sourceRef->Offset(); + } + isMetadataWritten = true; + + } + else // DMT + { + ProcessTitle( sourceRef, tempRef, title, currentOffset, titleOffset ); + isTitleWritten = true; + } + } + // Else + // Would have already covered this case: TDM + + } + else // TMD, MDT, MTD + { + if ( isTitleWritten ) // TMD + { + ProcessDescription( sourceRef, tempRef, description, currentOffset, descOffset ); + isDescWritten = true; + } + else // MDT or MTD + { + if ( titleOffset.startOffset < descOffset.startOffset ) // MTD + { + ProcessTitle( sourceRef, tempRef, title, currentOffset, titleOffset ); + isTitleWritten = true; + + ProcessDescription( sourceRef, tempRef, description, currentOffset, descOffset ); + isDescWritten = true; + } + else // MDT + { + ProcessDescription( sourceRef, tempRef, description, currentOffset, descOffset ); + isDescWritten = true; + + ProcessTitle( sourceRef, tempRef, title, currentOffset, titleOffset ); + isTitleWritten = true; + } + } + } + + // Finally Everything would have been written + XMP_Enforce( isTitleWritten && isDescWritten && isMetadataWritten ); + XIO::Copy( sourceRef, tempRef, ( sourceRef->Length() - currentOffset ) ); + this->needsUpdate = false; + +} // SVG_MetaHandler::WriteTempFile diff --git a/core/libs/dngwriter/extra/xmp_sdk/XMPFiles/source/FileHandlers/SVG_Handler.hpp b/core/libs/dngwriter/extra/xmp_sdk/XMPFiles/source/FileHandlers/SVG_Handler.hpp new file mode 100644 index 0000000000..4790a984b9 --- /dev/null +++ b/core/libs/dngwriter/extra/xmp_sdk/XMPFiles/source/FileHandlers/SVG_Handler.hpp @@ -0,0 +1,76 @@ +#ifndef __SVG_Handler_hpp__ +#define __SVG_Handler_hpp__ 1 + +// ================================================================================================= +// ADOBE SYSTEMS INCORPORATED +// Copyright 2015 Adobe Systems Incorporated +// All Rights Reserved +// +// NOTICE: Adobe permits you to use, modify, and distribute this file in accordance with the terms +// of the Adobe license agreement accompanying it. +// +// This file includes implementation of SVG metadata, according to Scalable Vector Graphics (SVG) 1.1 Specification. +// "https://www.w3.org/TR/2003/REC-SVG11-20030114/" +// Copyright © 1994-2002 World Wide Web Consortium, (Massachusetts Institute of Technology, +// Institut National de Recherche en Informatique et en Automatique, Keio University). +// All Rights Reserved . http://www.w3.org/Consortium/Legal +// +// ================================================================================================= + +#include "public/include/XMP_Environment.h" // ! XMP_Environment.h must be the first included header. + +#include "public/include/XMP_Const.h" +#include "public/include/XMP_IO.hpp" + +#include "XMPFiles/source/XMPFiles_Impl.hpp" +#include "XMPFiles/source/FormatSupport/SVG_Adapter.hpp" + +extern XMPFileHandler* SVG_MetaHandlerCTor( XMPFiles* parent ); + +extern bool SVG_CheckFormat( XMP_FileFormat format, + XMP_StringPtr filePath, + XMP_IO * fileRef, + XMPFiles * parent ); + +static const XMP_OptionBits kSVG_HandlerFlags = ( kXMPFiles_CanInjectXMP | + kXMPFiles_CanExpand | + kXMPFiles_CanRewrite | + kXMPFiles_PrefersInPlace | + kXMPFiles_CanReconcile | + kXMPFiles_ReturnsRawPacket | + kXMPFiles_AllowsSafeUpdate ); + +class SVG_MetaHandler : public XMPFileHandler { + +public: + + void CacheFileData(); + void ProcessXMP(); + + void UpdateFile( bool doSafeUpdate ); + void WriteTempFile( XMP_IO* tempRef ); + + XMP_OptionBits GetSerializeOptions(); + + SVG_MetaHandler( XMPFiles* parent ); + virtual ~SVG_MetaHandler(); + +private: + + SVG_MetaHandler() {}; + SVG_Adapter * svgAdapter; + XML_NodePtr svgNode; + bool isTitleUpdateReq; + bool isDescUpdateReq; + + void ProcessTitle( XMP_IO* sourceRef, XMP_IO * destRef, const std::string &value, XMP_Int64 ¤tOffset, const OffsetStruct & titleOffset ); + void ProcessDescription( XMP_IO* sourceRef, XMP_IO * destRef, const std::string &value, XMP_Int64 ¤tOffset, const OffsetStruct & descOffset ); + void InsertNewTitle( XMP_IO * destRef, const std::string &value ); + void InsertNewDescription( XMP_IO * destRef, const std::string &value ); + void InsertNewMetadata( XMP_IO * destRef, const std::string &value ); + +}; // SVG_MetaHandler + +// ================================================================================================= + +#endif /* __SVG_Handler_hpp__ */ diff --git a/core/libs/dngwriter/extra/xmp_sdk/XMPFiles/source/FileHandlers/SWF_Handler.cpp b/core/libs/dngwriter/extra/xmp_sdk/XMPFiles/source/FileHandlers/SWF_Handler.cpp new file mode 100644 index 0000000000..3266e94c13 --- /dev/null +++ b/core/libs/dngwriter/extra/xmp_sdk/XMPFiles/source/FileHandlers/SWF_Handler.cpp @@ -0,0 +1,337 @@ +// ================================================================================================= +// ADOBE SYSTEMS INCORPORATED +// Copyright 2006 Adobe Systems Incorporated +// All Rights Reserved +// +// NOTICE: Adobe permits you to use, modify, and distribute this file in accordance with the terms +// of the Adobe license agreement accompanying it. +// ================================================================================================= + +#include "public/include/XMP_Environment.h" // ! XMP_Environment.h must be the first included header. + +#include "public/include/XMP_Const.h" +#include "public/include/XMP_IO.hpp" + +#include "XMPFiles/source/XMPFiles_Impl.hpp" +#include "source/XMPFiles_IO.hpp" +#include "source/XIO.hpp" + +#include "XMPFiles/source/FileHandlers/SWF_Handler.hpp" +#include "XMPFiles/source/FormatSupport/SWF_Support.hpp" + +using namespace std; + +// ================================================================================================= +/// \file SWF_Handler.hpp +/// \brief File format handler for SWF. +/// +/// This handler ... +/// +// ================================================================================================= + +// ================================================================================================= +// SWF_MetaHandlerCTor +// =================== + +XMPFileHandler * SWF_MetaHandlerCTor ( XMPFiles * parent ) +{ + return new SWF_MetaHandler ( parent ); + +} // SWF_MetaHandlerCTor + +// ================================================================================================= +// SWF_CheckFormat +// =============== + +bool SWF_CheckFormat ( XMP_FileFormat format, + XMP_StringPtr filePath, + XMP_IO * fileRef, + XMPFiles * parent ) +{ + IgnoreParam(format); IgnoreParam(fileRef); IgnoreParam(parent); + XMP_Assert ( format == kXMP_SWFFile ); + + // Make sure the file is long enough for an empty SWF stream. Check the signature. + + if ( fileRef->Length() < (XMP_Int64)SWF_IO::HeaderPrefixSize ) return false; + + fileRef->Rewind(); + XMP_Uns8 buffer [4]; + fileRef->ReadAll ( buffer, 4 ); + XMP_Uns32 signature = GetUns32LE ( &buffer[0] ) & 0xFFFFFF; // Discard the version byte. + + return ( (signature == SWF_IO::CompressedSignature) || (signature == SWF_IO::ExpandedSignature) ); + +} // SWF_CheckFormat + +// ================================================================================================= +// SWF_MetaHandler::SWF_MetaHandler +// ================================ + +SWF_MetaHandler::SWF_MetaHandler ( XMPFiles * _parent ) + : isCompressed(false), hasFileAttributes(false), hasMetadata(false), brokenSWF(false), expandedSize(0), firstTagOffset(0) +{ + this->parent = _parent; + this->handlerFlags = kSWF_HandlerFlags; + this->stdCharForm = kXMP_Char8Bit; + +} + +// ================================================================================================= +// SWF_MetaHandler::~SWF_MetaHandler +// ================================= + +SWF_MetaHandler::~SWF_MetaHandler() +{ + // Nothing to do at this time. +} + +// ================================================================================================= +// SWF_MetaHandler::CacheFileData +// ============================== +// +// SWF files are pretty small, have simple metadata, and often have ZIP compression. Because they +// are small and often compressed, we always cache the fully expanded SWF in memory. That is used +// for both reading and updating. Note that SWF_CheckFormat has already done basic checks on the +// size and signature, they don't need to be repeated here. +// +// Try to find the FileAttributes and Metadata tags, saving their offsets for later use if updating +// the file. We need to be tolerant when reading, allowing the FileAttributes tag to be anywhere and +// allowing Metadata without FileAttributes or with the HasMetadata flag clear. The SWF spec is not +// clear enough about the rules for SWF 7 and earlier, there are 3rd party tools that don't put +// FileAttributes first. + +void SWF_MetaHandler::CacheFileData() { + + XMP_Assert ( (! this->processedXMP) && (! this->containsXMP) ); + XMP_Assert ( this->expandedSWF.empty() ); + + XMP_IO * fileRef = this->parent->ioRef; + XMP_Int64 fileLength = fileRef->Length(); + XMP_Enforce ( fileLength <= SWF_IO::MaxExpandedSize ); + + // Get the uncompressed SWF stream into memory. + + fileRef->Rewind(); + XMP_Uns8 buffer [SWF_IO::HeaderPrefixSize]; // Read the uncompressed file header prefix. + fileRef->ReadAll ( buffer, SWF_IO::HeaderPrefixSize ); + + XMP_Uns32 signature = GetUns32LE ( &buffer[0] ) & 0xFFFFFF; // Discard the version byte. + this->expandedSize = GetUns32LE ( &buffer[4] ); + if ( signature == SWF_IO::CompressedSignature ) this->isCompressed = true; + + if ( this->isCompressed ) { + + // Expand the SWF file into memory. + this->expandedSWF.reserve ( this->expandedSize ); // Try to avoid reallocations. + SWF_IO::DecompressFileToMemory ( fileRef, &this->expandedSWF ); + this->expandedSize = this->expandedSWF.size(); // Use the true length. + + } else { + + // Read the entire uncompressed file into memory. + this->expandedSize = (XMP_Uns32)fileLength; // Use the true length. + this->expandedSWF.insert ( this->expandedSWF.end(), (size_t)fileLength, 0 ); + fileRef->Rewind(); + fileRef->ReadAll ( &this->expandedSWF[0], (XMP_Uns32)fileLength ); + + } + + // Look for the FileAttributes and Metadata tags. + + this->firstTagOffset = SWF_IO::FileHeaderSize ( this->expandedSWF[SWF_IO::HeaderPrefixSize] ); + + XMP_Uns32 currOffset = this->firstTagOffset; + SWF_IO::TagInfo currTag; + + for ( ; currOffset < this->expandedSize; currOffset = SWF_IO::NextTagOffset(currTag) ) { + + bool ok = SWF_IO::GetTagInfo ( this->expandedSWF, currOffset, &currTag ); + if ( ! ok ) { + this->brokenSWF = true; // Let the read finish, but refuse to update. + break; + } + + if ( currTag.tagID == SWF_IO::FileAttributesTagID ) { + this->fileAttributesTag = currTag; + this->hasFileAttributes = true; + if ( this->hasMetadata ) break; // Exit if we have both. + } + + if ( currTag.tagID == SWF_IO::MetadataTagID ) { + this->metadataTag = currTag; + this->hasMetadata = true; + if ( this->hasFileAttributes ) break; // Exit if we have both. + } + + } + + if ( this->hasMetadata ) { + this->packetInfo.offset = SWF_IO::ContentOffset ( this->metadataTag ); + this->packetInfo.length = this->metadataTag.contentLength; + this->xmpPacket.assign ( (char*)&this->expandedSWF[(size_t)this->packetInfo.offset], (size_t)this->packetInfo.length ); + FillPacketInfo ( this->xmpPacket, &this->packetInfo ); + this->containsXMP = true; + } + +} // SWF_MetaHandler::CacheFileData + +// ================================================================================================= +// SWF_MetaHandler::ProcessXMP +// =========================== + +void SWF_MetaHandler::ProcessXMP() +{ + + this->processedXMP = true; // Make sure we only come through here once. + + if ( ! this->xmpPacket.empty() ) { + XMP_Assert ( this->containsXMP ); + XMP_StringPtr packetStr = this->xmpPacket.c_str(); + XMP_StringLen packetLen = (XMP_StringLen)this->xmpPacket.size(); + this->xmpObj.ParseFromBuffer ( packetStr, packetLen ); + } + +} // SWF_MetaHandler::ProcessXMP + +// ================================================================================================= +// XMPFileHandler::GetSerializeOptions +// =================================== +// +// Override default implementation to ensure omitting XMP wrapper. + +XMP_OptionBits SWF_MetaHandler::GetSerializeOptions() +{ + + return (kXMP_OmitPacketWrapper | kXMP_OmitAllFormatting | kXMP_OmitXMPMetaElement); + +} // XMPFileHandler::GetSerializeOptions + +// ================================================================================================= +// SWF_MetaHandler::UpdateFile +// =========================== +// +// Update the expanded SWF in memory, then write it to the file. + +void SWF_MetaHandler::UpdateFile ( bool doSafeUpdate ) +{ + + if ( doSafeUpdate ) XMP_Throw ( "SWF_MetaHandler::UpdateFile: Safe update not supported", kXMPErr_Unavailable ); + + if ( ! this->needsUpdate ) return; + this->needsUpdate = false; // Don't come through here twice, even if there are errors. + + if ( this->brokenSWF ) { + XMP_Throw ( "SWF is broken, can't update.", kXMPErr_BadFileFormat ); + } + + // Make sure there is a FileAttributes tag at the front, with the HasMetadata flag set. + + if ( ! this->hasFileAttributes ) { + + // Insert a new FileAttributes tag as the first tag. + + XMP_Uns8 buffer [6]; // Two byte header plus four byte content. + PutUns16LE ( ((SWF_IO::FileAttributesTagID << 6) | 4), &buffer[0] ); + PutUns32LE ( SWF_IO::HasMetadataMask, &buffer[2] ); + + this->expandedSWF.insert ( (this->expandedSWF.begin() + this->firstTagOffset), 6, 0 ); + memcpy ( &this->expandedSWF[this->firstTagOffset], &buffer[0], 6 ); + + this->hasFileAttributes = true; + bool ok = SWF_IO::GetTagInfo ( this->expandedSWF, this->firstTagOffset, &this->fileAttributesTag ); + XMP_Assert ( ok ); + + if ( this->hasMetadata ) this->metadataTag.tagOffset += 6; // The Metadata tag is now further back. + + } else { + + // Make sure the HasMetadata flag is set. + if ( this->fileAttributesTag.contentLength > 0 ) { + XMP_Uns32 flagsOffset = SWF_IO::ContentOffset ( this->fileAttributesTag ); + this->expandedSWF[flagsOffset] |= SWF_IO::HasMetadataMask; + } + + // Make sure the FileAttributes tag is the first tag. + if ( this->fileAttributesTag.tagOffset != this->firstTagOffset ) { + + RawDataBlock attrTag; + XMP_Uns32 attrTagLength = SWF_IO::FullTagLength ( this->fileAttributesTag ); + attrTag.assign ( attrTagLength, 0 ); + memcpy ( &attrTag[0], &this->expandedSWF[this->fileAttributesTag.tagOffset], attrTagLength ); + + RawDataBlock::iterator attrTagPos = this->expandedSWF.begin() + this->fileAttributesTag.tagOffset; + RawDataBlock::iterator attrTagEnd = attrTagPos + attrTagLength; + this->expandedSWF.erase ( attrTagPos, attrTagEnd ); // Remove the old FileAttributes tag; + + if ( this->hasMetadata && (this->metadataTag.tagOffset < this->fileAttributesTag.tagOffset) ) { + this->metadataTag.tagOffset += attrTagLength; // The FileAttributes tag will become in front. + } + + this->expandedSWF.insert ( (this->expandedSWF.begin() + this->firstTagOffset), attrTagLength, 0 ); + memcpy ( &this->expandedSWF[this->firstTagOffset], &attrTag[0], attrTagLength ); + + this->fileAttributesTag.tagOffset = this->firstTagOffset; + + } + + } + + // Make sure the XMP is as small as possible. Write the XMP as the second tag. + + XMP_Assert ( this->hasFileAttributes ); + + XMP_OptionBits smallOptions = kXMP_OmitPacketWrapper | kXMP_UseCompactFormat | kXMP_OmitAllFormatting | kXMP_OmitXMPMetaElement; + this->xmpObj.SerializeToBuffer ( &this->xmpPacket, smallOptions ); + + if ( this->hasMetadata ) { + // Remove the old XMP, the size and location have probably changed. + XMP_Uns32 oldMetaLength = SWF_IO::FullTagLength ( this->metadataTag ); + RawDataBlock::iterator oldMetaPos = this->expandedSWF.begin() + this->metadataTag.tagOffset; + RawDataBlock::iterator oldMetaEnd = oldMetaPos + oldMetaLength; + this->expandedSWF.erase ( oldMetaPos, oldMetaEnd ); + } + + this->metadataTag.hasLongHeader = true; + this->metadataTag.tagID = SWF_IO::MetadataTagID; + this->metadataTag.tagOffset = SWF_IO::NextTagOffset ( this->fileAttributesTag ); + this->metadataTag.contentLength = this->xmpPacket.size(); + + XMP_Uns32 newMetaLength = 6 + this->metadataTag.contentLength; // Always use a long tag header. + this->expandedSWF.insert ( (this->expandedSWF.begin() + this->metadataTag.tagOffset), newMetaLength, 0 ); + + PutUns16LE ( ((SWF_IO::MetadataTagID << 6) | SWF_IO::TagLengthMask), &this->expandedSWF[this->metadataTag.tagOffset] ); + PutUns32LE ( this->metadataTag.contentLength, &this->expandedSWF[this->metadataTag.tagOffset+2] ); + memcpy ( &this->expandedSWF[this->metadataTag.tagOffset+6], this->xmpPacket.c_str(), this->metadataTag.contentLength ); + + this->hasMetadata = true; + + // Update the uncompressed file length and rewrite the file. + + PutUns32LE ( this->expandedSWF.size(), &this->expandedSWF[4] ); + + XMP_IO * fileRef = this->parent->ioRef; + fileRef->Rewind(); + fileRef->Truncate ( 0 ); + + if ( this->isCompressed ) { + SWF_IO::CompressMemoryToFile ( this->expandedSWF, fileRef ); + } else { + fileRef->Write ( &this->expandedSWF[0], this->expandedSWF.size() ); + } + +} // SWF_MetaHandler::UpdateFile + +// ================================================================================================= +// SWF_MetaHandler::WriteTempFile +// ============================== + +// ! See important notes in SWF_Handler.hpp about file handling. + +void SWF_MetaHandler::WriteTempFile ( XMP_IO* tempRef ) +{ + + // ! WriteTempFile is not supposed to be called for SWF. + XMP_Throw ( "SWF_MetaHandler::WriteTempFile should not be called", kXMPErr_InternalFailure ); + +} // SWF_MetaHandler::WriteTempFile diff --git a/core/libs/dngwriter/extra/xmp_sdk/XMPFiles/source/FileHandlers/SWF_Handler.hpp b/core/libs/dngwriter/extra/xmp_sdk/XMPFiles/source/FileHandlers/SWF_Handler.hpp new file mode 100644 index 0000000000..f2ca7cb27a --- /dev/null +++ b/core/libs/dngwriter/extra/xmp_sdk/XMPFiles/source/FileHandlers/SWF_Handler.hpp @@ -0,0 +1,72 @@ +#ifndef __SWF_Handler_hpp__ +#define __SWF_Handler_hpp__ 1 + +// ================================================================================================= +// ADOBE SYSTEMS INCORPORATED +// Copyright 2006 Adobe Systems Incorporated +// All Rights Reserved +// +// NOTICE: Adobe permits you to use, modify, and distribute this file in accordance with the terms +// of the Adobe license agreement accompanying it. +// ================================================================================================= + +#include "public/include/XMP_Environment.h" // ! XMP_Environment.h must be the first included header. + +#include "public/include/XMP_Const.h" +#include "public/include/XMP_IO.hpp" + +#include "XMPFiles/source/XMPFiles_Impl.hpp" +#include "XMPFiles/source/FormatSupport/SWF_Support.hpp" + +// ================================================================================================= +/// \file SWF_Handler.hpp +/// \brief File format handler for SWF. +/// +/// This header ... +/// +// ================================================================================================= + +extern XMPFileHandler* SWF_MetaHandlerCTor ( XMPFiles* parent ); + +extern bool SWF_CheckFormat ( XMP_FileFormat format, + XMP_StringPtr filePath, + XMP_IO * fileRef, + XMPFiles * parent ); + +static const XMP_OptionBits kSWF_HandlerFlags = ( kXMPFiles_CanInjectXMP | + kXMPFiles_CanExpand | + kXMPFiles_PrefersInPlace | + kXMPFiles_AllowsOnlyXMP | + kXMPFiles_ReturnsRawPacket ); + +class SWF_MetaHandler : public XMPFileHandler { + +public: + + void CacheFileData(); + void ProcessXMP(); + + void UpdateFile ( bool doSafeUpdate ); + void WriteTempFile ( XMP_IO* tempRef ); + + XMP_OptionBits GetSerializeOptions(); + + SWF_MetaHandler ( XMPFiles* parent ); + virtual ~SWF_MetaHandler(); + +private: + + SWF_MetaHandler() : isCompressed(false), hasFileAttributes(false), hasMetadata(false), brokenSWF(false), + expandedSize(0), firstTagOffset(0) {}; + + bool isCompressed, hasFileAttributes, hasMetadata, brokenSWF; + XMP_Uns32 expandedSize, firstTagOffset; + RawDataBlock expandedSWF; + + SWF_IO::TagInfo fileAttributesTag, metadataTag; + +}; // SWF_MetaHandler + +// ================================================================================================= + +#endif /* __SWF_Handler_hpp__ */ diff --git a/core/libs/dngwriter/extra/xmp_sdk/XMPFiles/source/FileHandlers/Scanner_Handler.cpp b/core/libs/dngwriter/extra/xmp_sdk/XMPFiles/source/FileHandlers/Scanner_Handler.cpp new file mode 100644 index 0000000000..2d6308dbc4 --- /dev/null +++ b/core/libs/dngwriter/extra/xmp_sdk/XMPFiles/source/FileHandlers/Scanner_Handler.cpp @@ -0,0 +1,347 @@ +// ================================================================================================= +// ADOBE SYSTEMS INCORPORATED +// Copyright 2004 Adobe Systems Incorporated +// All Rights Reserved +// +// NOTICE: Adobe permits you to use, modify, and distribute this file in accordance with the terms +// of the Adobe license agreement accompanying it. +// ================================================================================================= + +#include "public/include/XMP_Environment.h" // ! XMP_Environment.h must be the first included header. + +#include "public/include/XMP_Const.h" +#include "public/include/XMP_IO.hpp" + +#include "XMPFiles/source/XMPFiles_Impl.hpp" +#include "source/XMPFiles_IO.hpp" +#include "source/XIO.hpp" + +#include "XMPFiles/source/FormatSupport/XMPScanner.hpp" +#include "XMPFiles/source/FileHandlers/Scanner_Handler.hpp" + +#include + +using namespace std; + +#if EnablePacketScanning + +// ================================================================================================= +/// \file Scanner_Handler.cpp +/// \brief File format handler for packet scanning. +/// +/// This header ... +/// +// ================================================================================================= + +struct CandidateInfo { + XMP_PacketInfo packetInfo; + std::string xmpPacket; + SXMPMeta * xmpObj; +}; + +// ================================================================================================= +// Scanner_MetaHandlerCTor +// ======================= + +XMPFileHandler * Scanner_MetaHandlerCTor ( XMPFiles * parent ) +{ + return new Scanner_MetaHandler ( parent ); + +} // Scanner_MetaHandlerCTor + +// ================================================================================================= +// Scanner_MetaHandler::Scanner_MetaHandler +// ======================================== + +Scanner_MetaHandler::Scanner_MetaHandler ( XMPFiles * _parent ) +{ + this->parent = _parent; + this->handlerFlags = kScanner_HandlerFlags; + +} // Scanner_MetaHandler::Scanner_MetaHandler + +// ================================================================================================= +// Scanner_MetaHandler::~Scanner_MetaHandler +// ========================================= + +Scanner_MetaHandler::~Scanner_MetaHandler() +{ + // ! Inherit the base cleanup. + +} // Scanner_MetaHandler::~Scanner_MetaHandler + +// ================================================================================================= +// PickMainPacket +// ============== +// +// Pick the main packet from the vector of candidates. The rules: +// 1. Use the manifest find containment. Prune contained packets. +// 2. Use the metadata date to pick the most recent. +// 3. if lenient, pick the last writeable packet, or the last if all are read only. + +static int +PickMainPacket ( std::vector& candidates, bool beLenient ) +{ + int pkt; // ! Must be signed. + int main = -1; // Assume the worst. + XMP_OptionBits options; + + int metaCount = (int)candidates.size(); + if ( metaCount == 0 ) return -1; + if ( metaCount == 1 ) return 0; + + // --------------------------------------------------------------------------------------------- + // 1. Look at each packet to see if it has a manifest. If it does, prune all of the others that + // this one says it contains. Hopefully we'll end up with just one packet. Note that we have to + // mark all the children first, then prune. Pruning on the fly means that we won't do a proper + // tree discovery if we prune a parent before a child. This would happen if we happened to visit + // a grandparent first. + + int child; + + std::vector pruned ( metaCount, false ); + + for ( pkt = 0; pkt < (int)candidates.size(); ++pkt ) { + + // First see if this candidate has a manifest. + + try { + std::string voidValue; + bool found = candidates[pkt].xmpObj->GetProperty ( kXMP_NS_XMP_MM, "Manifest", &voidValue, &options ); + if ( (! found) || (! XMP_PropIsArray ( options )) ) continue; // No manifest, or not an array. + } catch ( ... ) { + continue; // No manifest. + }; + + // Mark all other candidates that are referred to in this manifest. + + for ( child = 0; child < (int)candidates.size(); ++child ) { + if ( pruned[child] || (child == pkt) ) continue; // Skip already pruned ones and self. + } + + } + + // Go ahead and actually remove the marked packets. + + for ( pkt = 0; pkt < (int)candidates.size(); ++pkt ) { + if ( pruned[pkt] ) { + delete candidates[pkt].xmpObj; + candidates[pkt].xmpObj = 0; + metaCount -= 1; + } + } + + // We're done if the containment pruning left us with 0 or 1 candidate. + + if ( metaCount == 0 ) { + XMP_Throw ( "GetMainPacket/PickMainPacket: Recursive containment", kXMPErr_BadXMP ); + } else if ( metaCount == 1 ) { + for ( pkt = 0; pkt < (int)candidates.size(); ++pkt ) { + if ( candidates[pkt].xmpObj != 0 ) { + main = pkt; + break; + } + } + } + + if ( main != -1 ) return main; // We found the main. + + // ------------------------------------------------------------------------------------------- + // 2. Pick the packet with the most recent metadata date. If we are being lenient then missing + // dates are older than any real date, and equal dates pick the last packet. If we are being + // strict then any missing or equal dates mean we can't pick. + + XMP_DateTime latestTime, currTime; + + for ( pkt = 0; pkt < (int)candidates.size(); ++pkt ) { + + if ( candidates[pkt].xmpObj == 0 ) continue; // This was pruned in the manifest stage. + + bool haveDate = candidates[pkt].xmpObj->GetProperty_Date ( kXMP_NS_XMP, "MetadataDate", &currTime, &options ); + + if ( ! haveDate ) { + + if ( ! beLenient ) return -1; + if ( main == -1 ) { + main = pkt; + memset ( &latestTime, 0, sizeof(latestTime) ); + } + + } else if ( main == -1 ) { + + main = pkt; + latestTime = currTime; + + } else { + + int timeOp = SXMPUtils::CompareDateTime ( currTime, latestTime ); + + if ( timeOp > 0 ) { + main = pkt; + latestTime = currTime; + } else if ( timeOp == 0 ) { + if ( ! beLenient ) return -1; + main = pkt; + latestTime = currTime; + } + + } + + } + + if ( main != -1 ) return main; // We found the main. + + // -------------------------------------------------------------------------------------------- + // 3. If we're being lenient, pick the last writeable packet, or the last if all are read only. + + if ( beLenient ) { + + for ( pkt = (int)candidates.size()-1; pkt >= 0; --pkt ) { + if ( candidates[pkt].xmpObj == 0 ) continue; // This was pruned in the manifest stage. + if ( candidates[pkt].packetInfo.writeable ) { + main = pkt; + break; + } + } + + if ( main == -1 ) { + for ( pkt = (int)candidates.size()-1; pkt >= 0; --pkt ) { + if ( candidates[pkt].xmpObj != 0 ) { + main = pkt; + break; + } + } + } + + } + + return main; + +} // PickMainPacket + +// ================================================================================================= +// Scanner_MetaHandler::CacheFileData +// ================================== + +void Scanner_MetaHandler::CacheFileData() +{ + XMP_IO* fileRef = this->parent->ioRef; + bool beLenient = XMP_OptionIsClear ( this->parent->openFlags, kXMPFiles_OpenStrictly ); + + int pkt; + XMP_Int64 bufPos; + size_t bufLen; + SXMPMeta * newMeta; + + XMP_AbortProc abortProc = this->parent->abortProc; + void * abortArg = this->parent->abortArg; + const bool checkAbort = (abortProc != 0); + + std::vector candidates; // ! These have SXMPMeta* fields, don't leak on exceptions. + + this->containsXMP = false; + + try { + + // ------------------------------------------------------ + // Scan the entire file to find all of the valid packets. + + XMP_Int64 fileLen = fileRef->Length(); + XMPScanner scanner ( fileLen ); + + enum { kBufferSize = 64*1024 }; + XMP_Uns8 buffer [kBufferSize]; + + fileRef->Rewind(); + + for ( bufPos = 0; bufPos < fileLen; bufPos += bufLen ) { + if ( checkAbort && abortProc(abortArg) ) { + XMP_Throw ( "Scanner_MetaHandler::LocateXMP - User abort", kXMPErr_UserAbort ); + } + bufLen = fileRef->Read ( buffer, kBufferSize ); + if ( bufLen == 0 ) XMP_Throw ( "Scanner_MetaHandler::LocateXMP: Read failure", kXMPErr_ExternalFailure ); + scanner.Scan ( buffer, bufPos, bufLen ); + } + + // -------------------------------------------------------------- + // Parse the valid packet snips, building a vector of candidates. + + long snipCount = scanner.GetSnipCount(); + + XMPScanner::SnipInfoVector snips ( snipCount ); + scanner.Report ( snips ); + + for ( pkt = 0; pkt < snipCount; ++pkt ) { + + if ( checkAbort && abortProc(abortArg) ) { + XMP_Throw ( "Scanner_MetaHandler::LocateXMP - User abort", kXMPErr_UserAbort ); + } + + // Seek to the packet then try to parse it. + + if ( snips[pkt].fState != XMPScanner::eValidPacketSnip ) continue; + fileRef->Seek ( snips[pkt].fOffset, kXMP_SeekFromStart ); + newMeta = new SXMPMeta(); + std::string xmpPacket; + xmpPacket.reserve ( (size_t)snips[pkt].fLength ); + + try { + for ( bufPos = 0; bufPos < snips[pkt].fLength; bufPos += bufLen ) { + bufLen = kBufferSize; + if ( (bufPos + bufLen) > (size_t)snips[pkt].fLength ) bufLen = size_t ( snips[pkt].fLength - bufPos ); + (void) fileRef->ReadAll ( buffer, (XMP_Int32)bufLen ); + xmpPacket.append ( (const char *)buffer, bufLen ); + newMeta->ParseFromBuffer ( (char *)buffer, (XMP_StringLen)bufLen, kXMP_ParseMoreBuffers ); + } + newMeta->ParseFromBuffer ( 0, 0, kXMP_NoOptions ); + } catch ( ... ) { + delete newMeta; + if ( beLenient ) continue; // Skip if we're being lenient, else rethrow. + throw; + } + + // It parsed OK, add it to the array of candidates. + + candidates.push_back ( CandidateInfo() ); + CandidateInfo & newInfo = candidates.back(); + newInfo.xmpObj = newMeta; + newInfo.xmpPacket.swap ( xmpPacket ); + newInfo.packetInfo.offset = snips[pkt].fOffset; + newInfo.packetInfo.length = (XMP_Int32)snips[pkt].fLength; + newInfo.packetInfo.charForm = snips[pkt].fCharForm; + newInfo.packetInfo.writeable = (snips[pkt].fAccess == 'w'); + + } + + // ---------------------------------------- + // Figure out which packet is the main one. + + int main = PickMainPacket ( candidates, beLenient ); + + if ( main != -1 ) { + this->packetInfo = candidates[main].packetInfo; + this->xmpPacket.swap ( candidates[main].xmpPacket ); + this->xmpObj = *candidates[main].xmpObj; + this->containsXMP = true; + this->processedXMP = true; + } + + for ( pkt = 0; pkt < (int)candidates.size(); ++pkt ) { + if ( candidates[pkt].xmpObj != 0 ) delete candidates[pkt].xmpObj; + } + + } catch ( ... ) { + + // Clean up the SXMPMeta* fields from the vector of candidates. + for ( pkt = 0; pkt < (int)candidates.size(); ++pkt ) { + if ( candidates[pkt].xmpObj != 0 ) delete candidates[pkt].xmpObj; + } + throw; + + } + +} // Scanner_MetaHandler::CacheFileData + +// ================================================================================================= + +#endif // IncludePacketScanning diff --git a/core/libs/dngwriter/extra/xmp_sdk/XMPFiles/source/FileHandlers/Scanner_Handler.hpp b/core/libs/dngwriter/extra/xmp_sdk/XMPFiles/source/FileHandlers/Scanner_Handler.hpp new file mode 100644 index 0000000000..53c4820230 --- /dev/null +++ b/core/libs/dngwriter/extra/xmp_sdk/XMPFiles/source/FileHandlers/Scanner_Handler.hpp @@ -0,0 +1,42 @@ +#ifndef __Scanner_Handler_hpp__ +#define __Scanner_Handler_hpp__ 1 + +// ================================================================================================= +// ADOBE SYSTEMS INCORPORATED +// Copyright 2004 Adobe Systems Incorporated +// All Rights Reserved +// +// NOTICE: Adobe permits you to use, modify, and distribute this file in accordance with the terms +// of the Adobe license agreement accompanying it. +// ================================================================================================= + +#include "XMPFiles/source/FileHandlers/Trivial_Handler.hpp" + +// ================================================================================================= +/// \file Scanner_Handler.hpp +/// \brief File format handler for packet scanning. +/// +/// This header ... +/// +// ================================================================================================= + +extern XMPFileHandler * Scanner_MetaHandlerCTor ( XMPFiles * parent ); + +static const XMP_OptionBits kScanner_HandlerFlags = kTrivial_HandlerFlags; + +class Scanner_MetaHandler : public Trivial_MetaHandler +{ +public: + + Scanner_MetaHandler () {}; + Scanner_MetaHandler ( XMPFiles * parent ); + + ~Scanner_MetaHandler(); + + void CacheFileData(); + +}; // Scanner_MetaHandler + +// ================================================================================================= + +#endif /* __Scanner_Handler_hpp__ */ diff --git a/core/libs/dngwriter/extra/xmp_sdk/XMPFiles/source/FileHandlers/SonyHDV_Handler.cpp b/core/libs/dngwriter/extra/xmp_sdk/XMPFiles/source/FileHandlers/SonyHDV_Handler.cpp new file mode 100644 index 0000000000..ca3566c68c --- /dev/null +++ b/core/libs/dngwriter/extra/xmp_sdk/XMPFiles/source/FileHandlers/SonyHDV_Handler.cpp @@ -0,0 +1,952 @@ +// ================================================================================================= +// ADOBE SYSTEMS INCORPORATED +// Copyright 2007 Adobe Systems Incorporated +// All Rights Reserved +// +// NOTICE: Adobe permits you to use, modify, and distribute this file in accordance with the terms +// of the Adobe license agreement accompanying it. +// ================================================================================================= + +#include "public/include/XMP_Environment.h" // ! XMP_Environment.h must be the first included header. + +#include "public/include/XMP_Const.h" +#include "public/include/XMP_IO.hpp" + +#include "XMPFiles/source/XMPFiles_Impl.hpp" +#include "source/XMPFiles_IO.hpp" +#include "source/XIO.hpp" +#include "source/IOUtils.hpp" + +#include "XMPFiles/source/FileHandlers/SonyHDV_Handler.hpp" +#include "XMPFiles/source/FormatSupport/PackageFormat_Support.hpp" +#include "XMP_MD5.h" + +#if XMP_WinBuild + #pragma warning ( disable : 4996 ) // '...' was declared deprecated +#endif + +using namespace std; + +// ================================================================================================= +/// \file SonyHDV_Handler.cpp +/// \brief Folder format handler for Sony HDV. +/// +/// This handler is for the Sony HDV video format. This is a pseudo-package, visible files but with +/// a very well-defined layout and naming rules. +/// +/// A typical Sony HDV layout looks like: +/// +/// .../MyMovie/ +/// VIDEO/ +/// HVR/ +/// 00_0001_2007-08-06_165555.IDX +/// 00_0001_2007-08-06_165555.M2T +/// 00_0001_2007-08-06_171740.M2T +/// 00_0001_2007-08-06_171740.M2T.ese +/// tracks.dat +/// +/// The logical clip name can be "00_0001" or "00_0001_" plus anything. We'll find the .IDX file, +/// which defines the existence of the clip. Full file names as input will pull out the camera/clip +/// parts and match in the same way. The .XMP file will use the date/time suffix from the .IDX file. +// ================================================================================================= + +// ================================================================================================= +// SonyHDV_CheckFormat +// =================== +// +// This version does fairly simple checks. The top level folder (.../MyMovie) must contain the +// VIDEO/HVR subtree. The HVR folder must contain a .IDX file for the desired clip. The name checks +// are case insensitive. +// +// The state of the string parameters depends on the form of the path passed by the client. If the +// client passed a logical clip path, like ".../MyMovie/00_0001", the parameters are: +// rootPath - ".../MyMovie" +// gpName - empty +// parentName - empty +// leafName - "00_0001" +// +// If the client passed a full file path, like ".../MyMovie/VIDEO/HVR/00_0001_2007-08-06_165555.M2T", +// they are: +// rootPath - ".../MyMovie" +// gpName - "VIDEO" +// parentName - "HVR" +// leafName - "00_0001_2007-08-06_165555.M2T" +// +// The logical clip name can be short like "00_0001", or long like "00_0001_2007-08-06_165555". We +// only key off of the portion before a second underscore. + +// ! The common code has shifted the gpName, parentName, and leafName strings to upper case. It has +// ! also made sure that for a logical clip path the rootPath is an existing folder, and that the +// ! file exists for a full file path. + +bool SonyHDV_CheckFormat ( XMP_FileFormat format, + const std::string & rootPath, + const std::string & gpName, + const std::string & parentName, + const std::string & leafName, + XMPFiles * parent ) +{ + // Do some basic checks on the root path and component names. + + if ( gpName.empty() != parentName.empty() ) return false; // Must be both empty or both non-empty. + + std::string tempPath = rootPath; + tempPath += kDirChar; + tempPath += "VIDEO"; + + if ( gpName.empty() ) { + // This is the logical clip path case. Look for VIDEO/HVR subtree. + if ( Host_IO::GetChildMode ( tempPath.c_str(), "HVR" ) != Host_IO::kFMode_IsFolder ) return false; + } else { + // This is the existing file case. Check the parent and grandparent names. + if ( (gpName != "VIDEO") || (parentName != "HVR") ) return false; + } + + // Look for the clip's .IDX file. If found use that as the full clip name. + + tempPath += kDirChar; + tempPath += "HVR"; + + std::string clipName = leafName; + +#if 0 + + // Disabled until Sony HDV clip spanning is supported. Since segments of spanned clips are + // currently considered separate entities, information such as frame count needs to be + // considered on a per segment basis. + + int usCount = 0; + size_t i, limit = leafName.size(); + for ( i = 0; i < limit; ++i ) { + if ( clipName[i] == '_' ) { + ++usCount; + if ( usCount == 2 ) break; + } + } + if ( i < limit ) clipName.erase ( i ); + clipName += '_'; // Make sure a final '_' is there for the search comparisons. + + Host_IO::AutoFolder aFolder; + std::string childName; + bool found = false; + + aFolder.folder = Host_IO::OpenFolder ( tempPath.c_str() ); + while ( (! found) && Host_IO::GetNextChild ( aFolder.folder, &childName ) ) { + size_t childLen = childName.size(); + if ( childLen < 4 ) continue; + MakeUpperCase ( &childName ); + if ( childName.compare ( childLen-4, 4, ".IDX" ) != 0 ) continue; + if ( childName.compare ( 0, clipName.size(), clipName ) == 0 ) { + found = true; + clipName = childName; + clipName.erase ( childLen-4 ); + } + } + aFolder.Close(); + if ( ! found ) return false; + +#endif + + tempPath = rootPath; + tempPath += kDirChar; + tempPath += clipName; + + size_t pathLen = tempPath.size() + 1; // Include a terminating nul. + parent->tempPtr = malloc ( pathLen ); + if ( parent->tempPtr == 0 ) XMP_Throw ( "No memory for SonyHDV clip info", kXMPErr_NoMemory ); + memcpy ( parent->tempPtr, tempPath.c_str(), pathLen ); // AUDIT: Safe, allocated above. + + return true; + +} // SonyHDV_CheckFormat + +// ================================================================================================= + +static void* CreatePseudoClipPath ( const std::string & clientPath ) { + + // Used to create the clip pseudo path when the CheckFormat function is skipped. + + std::string pseudoPath = clientPath; + + size_t pathLen; + void* tempPtr = 0; + + if ( Host_IO::Exists ( pseudoPath.c_str() ) ) { + + // The client passed a physical path. The logical clip name is the leaf name, with the + // extension removed. There are no extra suffixes on Sony HDV files. The movie root path ends + // two levels up. + + std::string clipName, ignored; + + XIO::SplitLeafName ( &pseudoPath, &clipName ); // Extract the logical clip name. + XIO::SplitFileExtension ( &clipName, &ignored ); + + XIO::SplitLeafName ( &pseudoPath, &ignored ); // Remove the 2 intermediate folder levels. + XIO::SplitLeafName ( &pseudoPath, &ignored ); + + pseudoPath += kDirChar; + pseudoPath += clipName; + + } + + pathLen = pseudoPath.size() + 1; // Include a terminating nul. + tempPtr = malloc ( pathLen ); + if ( tempPtr == 0 ) XMP_Throw ( "No memory for SonyHDV clip info", kXMPErr_NoMemory ); + memcpy ( tempPtr, pseudoPath.c_str(), pathLen ); + + return tempPtr; + +} // CreatePseudoClipPath + +// ================================================================================================= +// ReadIDXFile +// =========== + +#define ExtractTimeCodeByte(ch,mask) ( (((ch & mask) >> 4) * 10) + (ch & 0xF) ) + +static bool ReadIDXFile ( const std::string& idxPath, + const std::string& clipName, + SXMPMeta* xmpObj, + bool& containsXMP, + MD5_CTX* md5Context, + bool digestFound ) +{ + bool result = true; + containsXMP = false; + + if ( clipName.size() != 25 ) return false; + + try { + + + Host_IO::FileRef hostRef = Host_IO::Open ( idxPath.c_str(), Host_IO::openReadOnly ); + if ( hostRef == Host_IO::noFileRef ) return false; // The open failed. + XMPFiles_IO idxFile ( hostRef, idxPath.c_str(), Host_IO::openReadOnly ); + + struct SHDV_HeaderBlock + { + char mHeader[8]; + unsigned char mValidFlag; + unsigned char mReserved; + unsigned char mECCTB; + unsigned char mSignalMode; + unsigned char mFileThousands; + unsigned char mFileHundreds; + unsigned char mFileTens; + unsigned char mFileUnits; + }; + + SHDV_HeaderBlock hdvHeaderBlock; + memset ( &hdvHeaderBlock, 0, sizeof(SHDV_HeaderBlock) ); + + idxFile.ReadAll ( hdvHeaderBlock.mHeader, 8 ); + idxFile.ReadAll ( &hdvHeaderBlock.mValidFlag, 1 ); + idxFile.ReadAll ( &hdvHeaderBlock.mReserved, 1 ); + idxFile.ReadAll ( &hdvHeaderBlock.mECCTB, 1 ); + idxFile.ReadAll ( &hdvHeaderBlock.mSignalMode, 1 ); + idxFile.ReadAll ( &hdvHeaderBlock.mFileThousands, 1 ); + idxFile.ReadAll ( &hdvHeaderBlock.mFileHundreds, 1 ); + idxFile.ReadAll ( &hdvHeaderBlock.mFileTens, 1 ); + idxFile.ReadAll ( &hdvHeaderBlock.mFileUnits, 1 ); + + const int fileCount = (hdvHeaderBlock.mFileThousands - '0') * 1000 + + (hdvHeaderBlock.mFileHundreds - '0') * 100 + + (hdvHeaderBlock.mFileTens - '0') * 10 + + (hdvHeaderBlock.mFileUnits - '0'); + + // Read file info block. + struct SHDV_FileBlock + { + char mDT[2]; + unsigned char mFileNameYear; + unsigned char mFileNameMonth; + unsigned char mFileNameDay; + unsigned char mFileNameHour; + unsigned char mFileNameMinute; + unsigned char mFileNameSecond; + unsigned char mStartTimeCode[4]; + unsigned char mTotalFrame[4]; + }; + + SHDV_FileBlock hdvFileBlock; + memset ( &hdvFileBlock, 0, sizeof(SHDV_FileBlock) ); + + char filenameBuffer[256]; + std::string fileDateAndTime = clipName.substr(8); + + bool foundFileBlock = false; + + for ( int i=0; ((i < fileCount) && (! foundFileBlock)); ++i ) { + + idxFile.ReadAll ( hdvFileBlock.mDT, 2 ); + idxFile.ReadAll ( &hdvFileBlock.mFileNameYear, 1 ); + idxFile.ReadAll ( &hdvFileBlock.mFileNameMonth, 1 ); + idxFile.ReadAll ( &hdvFileBlock.mFileNameDay, 1 ); + idxFile.ReadAll ( &hdvFileBlock.mFileNameHour, 1 ); + idxFile.ReadAll ( &hdvFileBlock.mFileNameMinute, 1 ); + idxFile.ReadAll ( &hdvFileBlock.mFileNameSecond, 1 ); + idxFile.ReadAll ( &hdvFileBlock.mStartTimeCode, 4 ); + idxFile.ReadAll ( &hdvFileBlock.mTotalFrame, 4 ); + + // Compose file name we expect from file contents and break out on match. + sprintf ( filenameBuffer, "%02d-%02d-%02d_%02d%02d%02d", + hdvFileBlock.mFileNameYear + 2000, + hdvFileBlock.mFileNameMonth, + hdvFileBlock.mFileNameDay, + hdvFileBlock.mFileNameHour, + hdvFileBlock.mFileNameMinute, + hdvFileBlock.mFileNameSecond ); + + foundFileBlock = (fileDateAndTime==filenameBuffer); + + } + + idxFile.Close(); + if ( ! foundFileBlock ) return false; + + // If digest calculation requested, calculate it and return. + if ( md5Context != 0 ) { + MD5Update ( md5Context, (XMP_Uns8*)(&hdvHeaderBlock), sizeof(SHDV_HeaderBlock) ); + MD5Update ( md5Context, (XMP_Uns8*)(&hdvFileBlock), sizeof(SHDV_FileBlock) ); + } + + // The xmpObj parameter must be provided in order to extract XMP + if ( xmpObj == 0 ) return (md5Context != 0); + + // Standard def? + const bool isSD = ((hdvHeaderBlock.mSignalMode == 0x80) || (hdvHeaderBlock.mSignalMode == 0)); + + // Progressive vs interlaced extracted from high bit of ECCTB byte + const bool clipIsProgressive = ((hdvHeaderBlock.mECCTB & 0x80) != 0); + + // Lowest three bits contain frame rate information + const int sfr = (hdvHeaderBlock.mECCTB & 7) + (clipIsProgressive ? 0 : 8); + + // Sample scale and sample size. + int clipSampleScale = 0; + int clipSampleSize = 0; + std::string frameRate; + + // Frame rate + switch ( sfr ) { + case 0 : break; // Not valid in spec, but it's happening in test files. + case 1 : clipSampleScale = 24000; clipSampleSize = 1001; frameRate = "23.98p"; break; + case 3 : clipSampleScale = 25; clipSampleSize = 1; frameRate = "25p"; break; + case 4 : clipSampleScale = 30000; clipSampleSize = 1001; frameRate = "29.97p"; break; + case 11 : clipSampleScale = 25; clipSampleSize = 1; frameRate = "50i"; break; + case 12 : clipSampleScale = 30000; clipSampleSize = 1001; frameRate = "59.94i"; break; + } + + containsXMP = true; + + // Frame size and PAR for HD (not clear on SD yet). + std::string xmpString; + XMP_StringPtr xmpValue = 0; + + if ( ! isSD ) { + + if ( digestFound || (! xmpObj->DoesPropertyExist ( kXMP_NS_DM, "videoFrameSize" )) ) { + + xmpValue = "1440"; + xmpObj->GetStructField ( kXMP_NS_DM, "videoFrameSize", kXMP_NS_DM, "w", &xmpString, 0 ); + if ( xmpString != xmpValue ) { + xmpObj->SetStructField ( kXMP_NS_DM, "videoFrameSize", kXMP_NS_XMP_Dimensions, "w", xmpValue, 0 ); + } + + xmpValue = "1080"; + xmpObj->GetStructField ( kXMP_NS_DM, "videoFrameSize", kXMP_NS_DM, "h", &xmpString, 0 ); + if ( xmpString != xmpValue ) { + xmpObj->SetStructField ( kXMP_NS_DM, "videoFrameSize", kXMP_NS_XMP_Dimensions, "h", xmpValue, 0 ); + } + + xmpValue = "pixels"; + xmpObj->GetStructField ( kXMP_NS_DM, "videoFrameSize", kXMP_NS_DM, "unit", &xmpString, 0 ); + if ( xmpString != xmpValue ) { + xmpObj->SetStructField ( kXMP_NS_DM, "videoFrameSize", kXMP_NS_XMP_Dimensions, "unit", xmpValue, 0 ); + } + } + + xmpValue = "4/3"; + if ( digestFound || (! xmpObj->DoesPropertyExist ( kXMP_NS_DM, "videoPixelAspectRatio" )) ) { + xmpObj->SetProperty ( kXMP_NS_DM, "videoPixelAspectRatio", xmpValue, kXMP_DeleteExisting ); + } + + } + + // Sample size and scale. + if ( clipSampleScale != 0 ) { + + char buffer[255]; + + if ( digestFound || (! xmpObj->DoesPropertyExist ( kXMP_NS_DM, "startTimeScale" )) ) { + sprintf(buffer, "%d", clipSampleScale); + xmpValue = buffer; + xmpObj->SetProperty ( kXMP_NS_DM, "startTimeScale", xmpValue, kXMP_DeleteExisting ); + } + + if ( digestFound || (! xmpObj->DoesPropertyExist ( kXMP_NS_DM, "startTimeSampleSize" )) ) { + sprintf(buffer, "%d", clipSampleSize); + xmpValue = buffer; + xmpObj->SetProperty ( kXMP_NS_DM, "startTimeSampleSize", xmpValue, kXMP_DeleteExisting ); + } + + if ( digestFound || (! xmpObj->DoesPropertyExist ( kXMP_NS_DM, "duration" )) ) { + + const int frameCount = (hdvFileBlock.mTotalFrame[0] << 24) + (hdvFileBlock.mTotalFrame[1] << 16) + + (hdvFileBlock.mTotalFrame[2] << 8) + hdvFileBlock.mTotalFrame[3]; + + sprintf ( buffer, "%d", frameCount ); + xmpValue = buffer; + xmpObj->SetStructField ( kXMP_NS_DM, "duration", kXMP_NS_DM, "value", xmpValue, 0 ); + + sprintf ( buffer, "%d/%d", clipSampleSize, clipSampleScale ); + xmpValue = buffer; + xmpObj->SetStructField ( kXMP_NS_DM, "duration", kXMP_NS_DM, "scale", xmpValue, 0 ); + + } + + } + + // Time Code. + if ( digestFound || (! xmpObj->DoesPropertyExist ( kXMP_NS_DM, "startTimecode" )) ) { + + if ( (clipSampleScale != 0) && (clipSampleSize != 0) ) { + + const bool dropFrame = ( (0x40 & hdvFileBlock.mStartTimeCode[0]) != 0 ) && ( sfr == 4 || sfr == 12 ); + const char chDF = dropFrame ? ';' : ':'; + const int tcFrames = ExtractTimeCodeByte ( hdvFileBlock.mStartTimeCode[0], 0x30 ); + const int tcSeconds = ExtractTimeCodeByte ( hdvFileBlock.mStartTimeCode[1], 0x70 ); + const int tcMinutes = ExtractTimeCodeByte ( hdvFileBlock.mStartTimeCode[2], 0x70 ); + const int tcHours = ExtractTimeCodeByte ( hdvFileBlock.mStartTimeCode[3], 0x30 ); + + // HH:MM:SS:FF or HH;MM;SS;FF + char timecode[256]; + sprintf ( timecode, "%02d%c%02d%c%02d%c%02d", tcHours, chDF, tcMinutes, chDF, tcSeconds, chDF, tcFrames ); + std::string sonyTimeString = timecode; + + xmpObj->GetStructField ( kXMP_NS_DM, "startTimecode", kXMP_NS_DM, "timeValue", &xmpString, 0 ); + if ( xmpString != sonyTimeString ) { + + xmpObj->SetStructField ( kXMP_NS_DM, "startTimecode", kXMP_NS_DM, "timeValue", sonyTimeString, 0 ); + + std::string timeFormat; + if ( clipSampleSize == 1 ) { + + // 24, 25, 40, 50, 60 + switch ( clipSampleScale ) { + case 24 : timeFormat = "24"; break; + case 25 : timeFormat = "25"; break; + case 50 : timeFormat = "50"; break; + default : XMP_Assert ( false ); + } + + timeFormat += "Timecode"; + + } else { + + // 23.976, 29.97, 59.94 + XMP_Assert ( clipSampleSize == 1001 ); + switch ( clipSampleScale ) { + case 24000 : timeFormat = "23976"; break; + case 30000 : timeFormat = "2997"; break; + case 60000 : timeFormat = "5994"; break; + default : XMP_Assert( false ); break; + } + + timeFormat += dropFrame ? "DropTimecode" : "NonDropTimecode"; + + } + + xmpObj->SetStructField ( kXMP_NS_DM, "startTimecode", kXMP_NS_DM, "timeFormat", timeFormat, 0 ); + + } + + } + + } + + if ( digestFound || (! xmpObj->DoesPropertyExist ( kXMP_NS_DM, "CreateDate" )) ) { + + // Clip has date and time in the case of DT (otherwise date and time haven't been set). + bool clipHasDate = ((hdvFileBlock.mDT[0] == 'D') && (hdvFileBlock.mDT[1] == 'T')); + + // Creation date + if ( clipHasDate ) { + + // YYYY-MM-DDThh:mm:ssZ + char date[256]; + sprintf ( date, "%4d-%02d-%02dT%02d:%02d:%02dZ", + hdvFileBlock.mFileNameYear + 2000, + hdvFileBlock.mFileNameMonth, + hdvFileBlock.mFileNameDay, + hdvFileBlock.mFileNameHour, + hdvFileBlock.mFileNameMinute, + hdvFileBlock.mFileNameSecond ); + + XMP_StringPtr xmpDate = date; + xmpObj->SetProperty ( kXMP_NS_XMP, "CreateDate", xmpDate, kXMP_DeleteExisting ); + + } + + } + + // Frame rate. + if ( digestFound || (! xmpObj->DoesPropertyExist ( kXMP_NS_DM, "videoFrameRate" )) ) { + + if ( frameRate.size() != 0 ) { + xmpString = frameRate; + xmpObj->SetProperty ( kXMP_NS_DM, "videoFrameRate", xmpString, kXMP_DeleteExisting ); + } + + } + + } catch ( ... ) { + + result = false; + + } + + return result; + +} // ReadIDXFile + +// ================================================================================================= +// SonyHDV_MetaHandlerCTor +// ======================= + +XMPFileHandler * SonyHDV_MetaHandlerCTor ( XMPFiles * parent ) +{ + return new SonyHDV_MetaHandler ( parent ); + +} // SonyHDV_MetaHandlerCTor + +// ================================================================================================= +// SonyHDV_MetaHandler::SonyHDV_MetaHandler +// ======================================== + +SonyHDV_MetaHandler::SonyHDV_MetaHandler ( XMPFiles * _parent ) +{ + + this->parent = _parent; // Inherited, can't set in the prefix. + this->handlerFlags = kSonyHDV_HandlerFlags; + this->stdCharForm = kXMP_Char8Bit; + + // Extract the root path and clip name. + + if ( this->parent->tempPtr == 0 ) { + // The CheckFormat call might have been skipped. + this->parent->tempPtr = CreatePseudoClipPath ( this->parent->GetFilePath() ); + } + + this->rootPath.assign ( (char*) this->parent->tempPtr ); + free ( this->parent->tempPtr ); + this->parent->tempPtr = 0; + + XIO::SplitLeafName ( &this->rootPath, &this->clipName ); + +} // SonyHDV_MetaHandler::SonyHDV_MetaHandler + +// ================================================================================================= +// SonyHDV_MetaHandler::~SonyHDV_MetaHandler +// ========================================= + +SonyHDV_MetaHandler::~SonyHDV_MetaHandler() +{ + + if ( this->parent->tempPtr != 0 ) { + free ( this->parent->tempPtr ); + this->parent->tempPtr = 0; + } + +} // SonyHDV_MetaHandler::~SonyHDV_MetaHandler + +// ================================================================================================= +// SonyHDV_MetaHandler::MakeClipFilePath +// ===================================== + +bool SonyHDV_MetaHandler::MakeClipFilePath ( std::string * path, XMP_StringPtr suffix, bool checkFile /* = false */ ) +{ + + *path = this->rootPath; + *path += kDirChar; + *path += "VIDEO"; + *path += kDirChar; + *path += "HVR"; + *path += kDirChar; + *path += this->clipName; + *path += suffix; + + if ( ! checkFile ) return true; + return Host_IO::Exists ( path->c_str() ); + +} // SonyHDV_MetaHandler::MakeClipFilePath + +// This method removes the timestamp information from a clip name. It returns the clip name with a following "_". +// For example: The clip name "00_0001_2007-08-06_165555" becomes "00_0001_". +static void RemoveTimeStampFromClipName(std::string &clipName) +{ + int usCount = 0; + size_t i, limit = clipName.size(); + + for ( i = 0; i < limit; ++i ) { + if ( clipName[i] == '_' ) { + ++usCount; + if ( usCount == 2 ) break; + } + } + + if ( i < limit ) clipName.erase ( i ); + clipName += '_'; // Make sure a final '_' is there for the search comparisons. +} + +// ================================================================================================= +// SonyHDV_MetaHandler::MakeIndexFilePath +// ====================================== + +bool SonyHDV_MetaHandler::MakeIndexFilePath ( std::string& idxPath, const std::string& rootPath, const std::string& leafName ) +{ + std::string tempPath; + tempPath = rootPath; + tempPath += kDirChar; + tempPath += "VIDEO"; + tempPath += kDirChar; + tempPath += "HVR"; + + idxPath = tempPath; + idxPath += kDirChar; + idxPath += leafName; + idxPath += ".IDX"; + + // Default case + if ( Host_IO::GetFileMode ( idxPath.c_str() ) == Host_IO::kFMode_IsFile ) return true; + + // Spanned clip case + + // Scanning code taken from SonyHDV_CheckFormat + // Can be isolated to a separate function. + + std::string clipName = leafName; + RemoveTimeStampFromClipName(clipName); + + Host_IO::AutoFolder aFolder; + std::string childName; + bool found = false; + + aFolder.folder = Host_IO::OpenFolder ( tempPath.c_str() ); + while ( (! found) && Host_IO::GetNextChild ( aFolder.folder, &childName ) ) { + size_t childLen = childName.size(); + if ( childLen < 4 ) continue; + MakeUpperCase ( &childName ); + if ( childName.compare ( childLen-4, 4, ".IDX" ) != 0 ) continue; + if ( childName.compare ( 0, clipName.size(), clipName ) == 0 ) { + found = true; + clipName = childName; + clipName.erase ( childLen-4 ); + } + } + aFolder.Close(); + if ( ! found ) return false; + + idxPath = tempPath; + idxPath += kDirChar; + idxPath += clipName; + idxPath += ".IDX"; + + return true; + +} + +// ================================================================================================= +// SonyHDV_MetaHandler::MakeLegacyDigest +// ===================================== + +#define kHexDigits "0123456789ABCDEF" + +void SonyHDV_MetaHandler::MakeLegacyDigest ( std::string * digestStr ) +{ + std::string idxPath; + if ( ! this->MakeIndexFilePath ( idxPath, this->rootPath, this->clipName ) ) return; + + MD5_CTX context; + unsigned char digestBin [16]; + bool dummy = false; + MD5Init ( &context ); + ReadIDXFile ( idxPath, this->clipName, 0, dummy, &context, false ); + MD5Final ( digestBin, &context ); + + char buffer [40]; + for ( int in = 0, out = 0; in < 16; in += 1, out += 2 ) { + XMP_Uns8 byte = digestBin[in]; + buffer[out] = kHexDigits [ byte >> 4 ]; + buffer[out+1] = kHexDigits [ byte & 0xF ]; + } + buffer[32] = 0; + digestStr->erase(); + digestStr->append ( buffer, 32 ); + +} // MakeLegacyDigest + +// ================================================================================================= +// SonyHDV_MetaHandler::GetFileModDate +// =================================== + +static inline bool operator< ( const XMP_DateTime & left, const XMP_DateTime & right ) { + int compare = SXMPUtils::CompareDateTime ( left, right ); + return (compare < 0); +} + +bool SonyHDV_MetaHandler::GetFileModDate ( XMP_DateTime * modDate ) +{ + + // The Sony HDV locations of metadata: + // VIDEO/ + // HVR/ + // 00_0001_2007-08-06_165555.IDX + // 00_0001_2007-08-06_165555.XMP + + bool ok, haveDate = false; + std::string fullPath; + XMP_DateTime oneDate, junkDate; + if ( modDate == 0 ) modDate = &junkDate; + + ok = this->MakeIndexFilePath ( fullPath, this->rootPath, this->clipName ); + if ( ok ) ok = Host_IO::GetModifyDate ( fullPath.c_str(), &oneDate ); + if ( ok ) { + if ( *modDate < oneDate ) *modDate = oneDate; + haveDate = true; + } + + ok = this->MakeClipFilePath ( &fullPath, ".XMP", true /* checkFile */ ); + if ( ok ) ok = Host_IO::GetModifyDate ( fullPath.c_str(), &oneDate ); + if ( ok ) { + if ( (! haveDate) || (*modDate < oneDate) ) *modDate = oneDate; + haveDate = true; + } + + return haveDate; + +} // SonyHDV_MetaHandler::GetFileModDate + +// ================================================================================================= +// SonyHDV_MetaHandler::FillMetadataFiles +// ================================ +void SonyHDV_MetaHandler::FillMetadataFiles ( std::vector* metadataFiles ) +{ + std::string noExtPath, filePath; + + noExtPath = rootPath + kDirChar + "VIDEO" + kDirChar + "HVR" + kDirChar + clipName; + + filePath = noExtPath + ".XMP"; + metadataFiles->push_back ( filePath ); + filePath = noExtPath + ".IDX"; + metadataFiles->push_back ( filePath ); + +} // FillMetadataFiles_SonyHDV + +// ================================================================================================= +// SonyHDV_MetaHandler::IsMetadataWritable +// ======================================= + +bool SonyHDV_MetaHandler::IsMetadataWritable ( ) +{ + std::vector metadataFiles; + FillMetadataFiles(&metadataFiles); + std::vector::iterator itr = metadataFiles.begin(); + // Check whether sidecar is writable, if not then check if it can be created. + return Host_IO::Writable( itr->c_str(), true ); +}// SonyHDV_MetaHandler::IsMetadataWritable + + +// ================================================================================================= +// SonyHDV_MetaHandler::FillAssociatedResources +// ====================================== +// +// This method returns all clip associated "media files","index files" whose name +// starts with XX_CCCC_ and side cars starting with XX_CCCC. +void SonyHDV_MetaHandler::FillAssociatedResources ( std::vector * resourceList ) +{ + // The possible associated resources: + // VIDEO/ + // HVR/ + // XX_CCCC_YYYY-MM-DD_hhmmss.M2T // HDV media + // XX_CCCC_YYYY-MM-DD_hhmmss.IDX // Metadata Index file + // XX_CCCC_YYYY-MM-DD_hhmmss.XMP // sidecar + // + // XX_CCCC_YYYY-MM-DD_hhmmss.AVI // DV(AVI) medi + // XX_CCCC_YYYY-MM-DD_hhmmss.IDX // Metadata Index file + // XX_CCCC_YYYY-MM-DD_hhmmss.XMP // sidecar + // + // XX_CCCC_YYYY-MM-DD_hhmmss.DV // DV(RAW) media + // XX_CCCC_YYYY-MM-DD_hhmmss.IDX // Metadata Index file + // XX_CCCC_YYYY-MM-DD_hhmmss.XMP // sidecar + // + // tracks.dat // Clip database file + + std:: string hvrPath = this->rootPath + kDirChar + "VIDEO" + kDirChar + "HVR"; + std::string filePath; + + //Add RootPath + filePath = this->rootPath + kDirChar; + PackageFormat_Support::AddResourceIfExists( resourceList, filePath ); + + // If XX_CCCC_YYYY-MM-DD_hhmmss is clip name then we remove YYYY-MM-DD_hhmmss from this and return + // all files starting with XX_CCCC_ and having required extension. + std::string clipNameWithoutTimeStamp = this->clipName; + RemoveTimeStampFromClipName(clipNameWithoutTimeStamp); + + // Add media files. + // We don't know the extension of the media so we will check for all + // three possible extensions and add whichever is existing. + + // "AddResourceIfExists" will add all spanned clips that match the clip prefix "clipNameWithoutTimeStamp" + // and specified extensions. + PackageFormat_Support::AddResourceIfExists(resourceList, hvrPath, clipNameWithoutTimeStamp.c_str(), ".M2T"); + + PackageFormat_Support::AddResourceIfExists(resourceList, hvrPath, clipNameWithoutTimeStamp.c_str(), ".AVI"); + + PackageFormat_Support::AddResourceIfExists(resourceList, hvrPath, clipNameWithoutTimeStamp.c_str(), ".DV"); + + // Add Index files. + PackageFormat_Support::AddResourceIfExists(resourceList, hvrPath, clipNameWithoutTimeStamp.c_str(), ".IDX"); + + // Add sidecars. + // For sidecars we will look for XX_CCCC*.XMP instead of XX_CCCC_*.XMP because we may generate such files + // in case of spanning (in future) or logical paths. + clipNameWithoutTimeStamp.erase(clipNameWithoutTimeStamp.end()-1); + PackageFormat_Support::AddResourceIfExists(resourceList, hvrPath, clipNameWithoutTimeStamp.c_str(), ".XMP"); + + //Add clip database file + filePath = hvrPath + kDirChar + "tracks.dat"; + PackageFormat_Support::AddResourceIfExists(resourceList, filePath); + +} // SonyHDV_MetaHandler::FillAssociatedResources + + +// ================================================================================================= +// SonyHDV_MetaHandler::CacheFileData +// ================================== + +void SonyHDV_MetaHandler::CacheFileData() +{ + XMP_Assert ( ! this->containsXMP ); + + if ( this->parent->UsesClientIO() ) { + XMP_Throw ( "SonyHDV cannot be used with client-managed I/O", kXMPErr_InternalFailure ); + } + + // See if the clip's .XMP file exists. + + std::string xmpPath; + this->MakeClipFilePath ( &xmpPath, ".XMP" ); + if ( ! Host_IO::Exists ( xmpPath.c_str() ) ) return; // No XMP. + + // Read the entire .XMP file. We know the XMP exists, New_XMPFiles_IO is supposed to return 0 + // only if the file does not exist. + + bool readOnly = XMP_OptionIsClear ( this->parent->openFlags, kXMPFiles_OpenForUpdate ); + + XMP_Assert ( this->parent->ioRef == 0 ); + XMPFiles_IO* xmpFile = XMPFiles_IO::New_XMPFiles_IO ( xmpPath.c_str(), readOnly ); + if ( xmpFile == 0 ) XMP_Throw ( "SonyHDV XMP file open failure", kXMPErr_InternalFailure ); + this->parent->ioRef = xmpFile; + + XMP_Int64 xmpLen = xmpFile->Length(); + if ( xmpLen > 100*1024*1024 ) { + XMP_Throw ( "SonyHDV XMP is outrageously large", kXMPErr_InternalFailure ); // Sanity check. + } + + this->xmpPacket.erase(); + this->xmpPacket.append ( (size_t)xmpLen, ' ' ); + + XMP_Int32 ioCount = xmpFile->ReadAll ( (void*)this->xmpPacket.data(), (XMP_Int32)xmpLen ); + + this->packetInfo.offset = 0; + this->packetInfo.length = (XMP_Int32)xmpLen; + FillPacketInfo ( this->xmpPacket, &this->packetInfo ); + + this->containsXMP = true; + +} // SonyHDV_MetaHandler::CacheFileData + +// ================================================================================================= +// SonyHDV_MetaHandler::ProcessXMP +// =============================== + +void SonyHDV_MetaHandler::ProcessXMP() +{ + if ( this->processedXMP ) return; + this->processedXMP = true; // Make sure only called once. + + if ( this->containsXMP ) { + this->xmpObj.ParseFromBuffer ( this->xmpPacket.c_str(), (XMP_StringLen)this->xmpPacket.size() ); + } + + // Check the legacy digest. + std::string oldDigest, newDigest; + bool digestFound; + digestFound = this->xmpObj.GetStructField ( kXMP_NS_XMP, "NativeDigests", kXMP_NS_XMP, "SonyHDV", &oldDigest, 0 ); + if ( digestFound ) { + this->MakeLegacyDigest ( &newDigest ); + if ( oldDigest == newDigest ) return; + } + + // Read the IDX legacy. + std::string idxPath; + if ( ! this->MakeIndexFilePath ( idxPath, this->rootPath, this->clipName ) ) return; + ReadIDXFile ( idxPath, this->clipName, &this->xmpObj, this->containsXMP, 0, digestFound ); + +} // SonyHDV_MetaHandler::ProcessXMP + +// ================================================================================================= +// SonyHDV_MetaHandler::UpdateFile +// =============================== +// +// Note that UpdateFile is only called from XMPFiles::CloseFile, so it is OK to close the file here. + +void SonyHDV_MetaHandler::UpdateFile ( bool doSafeUpdate ) +{ + if ( ! this->needsUpdate ) return; + this->needsUpdate = false; // Make sure only called once. + + XMP_Assert ( this->parent->UsesLocalIO() ); + + std::string newDigest; + this->MakeLegacyDigest ( &newDigest ); + this->xmpObj.SetStructField ( kXMP_NS_XMP, "NativeDigests", kXMP_NS_XMP, "SonyHDV", newDigest.c_str(), kXMP_DeleteExisting ); + + this->xmpObj.SerializeToBuffer ( &this->xmpPacket, this->GetSerializeOptions() ); + + // ------------------------------------------------- + // Update just the XMP file not the native IDX file. + + std::string xmpPath; + this->MakeClipFilePath ( &xmpPath, ".XMP" ); + + bool haveXMP = Host_IO::Exists ( xmpPath.c_str() ); + if ( ! haveXMP ) { + XMP_Assert ( this->parent->ioRef == 0 ); + Host_IO::Create ( xmpPath.c_str() ); + this->parent->ioRef = XMPFiles_IO::New_XMPFiles_IO ( xmpPath.c_str(), Host_IO::openReadWrite ); + if ( this->parent->ioRef == 0 ) XMP_Throw ( "Failure opening SonyHDV XMP file", kXMPErr_ExternalFailure ); + } + + XMP_IO* xmpFile = this->parent->ioRef; + XMP_Assert ( xmpFile != 0 ); + XIO::ReplaceTextFile ( xmpFile, this->xmpPacket, (haveXMP & doSafeUpdate) ); + +} // SonyHDV_MetaHandler::UpdateFile + +// ================================================================================================= +// SonyHDV_MetaHandler::WriteTempFile +// ================================== + +void SonyHDV_MetaHandler::WriteTempFile ( XMP_IO* tempRef ) +{ + + // ! WriteTempFile is not supposed to be called for handlers that own the file. + XMP_Throw ( "SonyHDV_MetaHandler::WriteTempFile should not be called", kXMPErr_InternalFailure ); + +} // SonyHDV_MetaHandler::WriteTempFile + +// ================================================================================================= diff --git a/core/libs/dngwriter/extra/xmp_sdk/XMPFiles/source/FileHandlers/SonyHDV_Handler.hpp b/core/libs/dngwriter/extra/xmp_sdk/XMPFiles/source/FileHandlers/SonyHDV_Handler.hpp new file mode 100644 index 0000000000..190930d6be --- /dev/null +++ b/core/libs/dngwriter/extra/xmp_sdk/XMPFiles/source/FileHandlers/SonyHDV_Handler.hpp @@ -0,0 +1,82 @@ +#ifndef __SonyHDV_Handler_hpp__ +#define __SonyHDV_Handler_hpp__ 1 + +// ================================================================================================= +// ADOBE SYSTEMS INCORPORATED +// Copyright 2007 Adobe Systems Incorporated +// All Rights Reserved +// +// NOTICE: Adobe permits you to use, modify, and distribute this file in accordance with the terms +// of the Adobe license agreement accompanying it. +// ================================================================================================= + +#include "public/include/XMP_Environment.h" // ! This must be the first include. + +#include "XMPFiles/source/XMPFiles_Impl.hpp" + +#include "source/ExpatAdapter.hpp" + +// ================================================================================================= +/// \file SonyHDV_Handler.hpp +/// \brief Folder format handler for SonyHDV. +/// +/// This header ... +/// +// ================================================================================================= + +extern XMPFileHandler * SonyHDV_MetaHandlerCTor ( XMPFiles * parent ); + +extern bool SonyHDV_CheckFormat ( XMP_FileFormat format, + const std::string & rootPath, + const std::string & gpName, + const std::string & parentName, + const std::string & leafName, + XMPFiles * parent ); + +static const XMP_OptionBits kSonyHDV_HandlerFlags = (kXMPFiles_CanInjectXMP | + kXMPFiles_CanExpand | + kXMPFiles_CanRewrite | + kXMPFiles_PrefersInPlace | + kXMPFiles_CanReconcile | + kXMPFiles_AllowsOnlyXMP | + kXMPFiles_ReturnsRawPacket | + kXMPFiles_HandlerOwnsFile | + kXMPFiles_AllowsSafeUpdate | + kXMPFiles_FolderBasedFormat); + +class SonyHDV_MetaHandler : public XMPFileHandler +{ +public: + + bool GetFileModDate ( XMP_DateTime * modDate ); + void FillMetadataFiles(std::vector* metadataFiles ); + void FillAssociatedResources ( std::vector * resourceList ); + bool IsMetadataWritable ( ); + + void CacheFileData(); + void ProcessXMP(); + + void UpdateFile ( bool doSafeUpdate ); + void WriteTempFile ( XMP_IO* tempRef ); + + XMP_OptionBits GetSerializeOptions() // *** These should be standard for standalone XMP files. + { return (kXMP_UseCompactFormat | kXMP_OmitPacketWrapper); }; + + SonyHDV_MetaHandler ( XMPFiles * _parent ); + virtual ~SonyHDV_MetaHandler(); + +private: + + SonyHDV_MetaHandler() {}; // Hidden on purpose. + + bool MakeClipFilePath ( std::string * path, XMP_StringPtr suffix, bool checkFile = false ); + bool MakeIndexFilePath ( std::string& idxPath, const std::string& rootPath, const std::string& leafName ); + void MakeLegacyDigest ( std::string * digestStr ); + + std::string rootPath, clipName; + +}; // SonyHDV_MetaHandler + +// ================================================================================================= + +#endif /* __SonyHDV_Handler_hpp__ */ diff --git a/core/libs/dngwriter/extra/xmp_sdk/XMPFiles/source/FileHandlers/TIFF_Handler.cpp b/core/libs/dngwriter/extra/xmp_sdk/XMPFiles/source/FileHandlers/TIFF_Handler.cpp new file mode 100644 index 0000000000..9bfe96a68e --- /dev/null +++ b/core/libs/dngwriter/extra/xmp_sdk/XMPFiles/source/FileHandlers/TIFF_Handler.cpp @@ -0,0 +1,420 @@ +// ================================================================================================= +// ADOBE SYSTEMS INCORPORATED +// Copyright 2006 Adobe Systems Incorporated +// All Rights Reserved +// +// NOTICE: Adobe permits you to use, modify, and distribute this file in accordance with the terms +// of the Adobe license agreement accompanying it. +// ================================================================================================= + +#include "public/include/XMP_Environment.h" // ! This must be the first include. +#include "public/include/XMP_Const.h" +#include "public/include/XMP_IO.hpp" + +#include "XMPFiles/source/XMPFiles_Impl.hpp" +#include "source/XIO.hpp" + +#include "XMPFiles/source/FileHandlers/TIFF_Handler.hpp" + +#include "XMPFiles/source/FormatSupport/TIFF_Support.hpp" +#include "XMPFiles/source/FormatSupport/PSIR_Support.hpp" +#include "XMPFiles/source/FormatSupport/IPTC_Support.hpp" +#include "XMPFiles/source/FormatSupport/ReconcileLegacy.hpp" +#include "XMPFiles/source/FormatSupport/Reconcile_Impl.hpp" + +#include "XMP_MD5.h" + +using namespace std; + +// ================================================================================================= +/// \file TIFF_Handler.cpp +/// \brief File format handler for TIFF. +/// +/// This handler ... +/// +// ================================================================================================= + +// ================================================================================================= +// TIFF_CheckFormat +// ================ + +// For TIFF we just check for the II/42 or MM/42 in the first 4 bytes and that there are at least +// 26 bytes of data (4+4+2+12+4). +// +// ! The CheckXyzFormat routines don't track the filePos, that is left to ScanXyzFile. + +bool TIFF_CheckFormat ( XMP_FileFormat format, + XMP_StringPtr filePath, + XMP_IO* fileRef, + XMPFiles * parent ) +{ + IgnoreParam(format); IgnoreParam(filePath); IgnoreParam(parent); + XMP_Assert ( format == kXMP_TIFFFile ); + + enum { kMinimalTIFFSize = 4+4+2+12+4 }; // Header plus IFD with 1 entry. + + fileRef->Rewind ( ); + if ( ! XIO::CheckFileSpace ( fileRef, kMinimalTIFFSize ) ) return false; + + XMP_Uns8 buffer [4]; + fileRef->Read ( buffer, 4 ); + + bool leTIFF = CheckBytes ( buffer, "\x49\x49\x2A\x00", 4 ); + bool beTIFF = CheckBytes ( buffer, "\x4D\x4D\x00\x2A", 4 ); + + return (leTIFF | beTIFF); + +} // TIFF_CheckFormat + +// ================================================================================================= +// TIFF_MetaHandlerCTor +// ==================== + +XMPFileHandler * TIFF_MetaHandlerCTor ( XMPFiles * parent ) +{ + return new TIFF_MetaHandler ( parent ); + +} // TIFF_MetaHandlerCTor + +// ================================================================================================= +// TIFF_MetaHandler::TIFF_MetaHandler +// ================================== + +TIFF_MetaHandler::TIFF_MetaHandler ( XMPFiles * _parent ) : psirMgr(0), iptcMgr(0) +{ + this->parent = _parent; + this->handlerFlags = kTIFF_HandlerFlags; + this->stdCharForm = kXMP_Char8Bit; + +} // TIFF_MetaHandler::TIFF_MetaHandler + +// ================================================================================================= +// TIFF_MetaHandler::~TIFF_MetaHandler +// =================================== + +TIFF_MetaHandler::~TIFF_MetaHandler() +{ + + if ( this->psirMgr != 0 ) delete ( this->psirMgr ); + if ( this->iptcMgr != 0 ) delete ( this->iptcMgr ); + +} // TIFF_MetaHandler::~TIFF_MetaHandler + +// ================================================================================================= +// TIFF_MetaHandler::CacheFileData +// =============================== +// +// The data caching for TIFF is easy to explain and implement, but does more processing than one +// might at first expect. This seems unavoidable given the need to close the disk file after calling +// CacheFileData. We parse the TIFF stream and cache the values for all tags of interest, and note +// whether XMP is present. We do not parse the XMP, Photoshop image resources, or IPTC datasets. + +// *** This implementation simply returns when invalid TIFF is encountered. Should we throw instead? + +void TIFF_MetaHandler::CacheFileData() +{ + XMP_IO* fileRef = this->parent->ioRef; + XMP_PacketInfo & packetInfo = this->packetInfo; + + XMP_AbortProc abortProc = this->parent->abortProc; + void * abortArg = this->parent->abortArg; + const bool checkAbort = (abortProc != 0); + + XMP_Assert ( ! this->containsXMP ); + // Set containsXMP to true here only if the XMP tag is found. + + if ( checkAbort && abortProc(abortArg) ) { + XMP_Throw ( "TIFF_MetaHandler::CacheFileData - User abort", kXMPErr_UserAbort ); + } + + this->tiffMgr.ParseFileStream ( fileRef ); + + TIFF_Manager::TagInfo dngInfo; + if ( this->tiffMgr.GetTag ( kTIFF_PrimaryIFD, kTIFF_DNGVersion, &dngInfo ) ) { + + // Reject DNG files that are version 2.0 or beyond, this is being written at the time of + // DNG version 1.2. The DNG team says it is OK to use 2.0, not strictly 1.2. Use the + // DNGBackwardVersion if it is present, else the DNGVersion. Note that the version value is + // supposed to be type BYTE, so the file order is always essentially big endian. + + XMP_Uns8 majorVersion = *((XMP_Uns8*)dngInfo.dataPtr); // Start with DNGVersion. + if ( this->tiffMgr.GetTag ( kTIFF_PrimaryIFD, kTIFF_DNGBackwardVersion, &dngInfo ) ) { + majorVersion = *((XMP_Uns8*)dngInfo.dataPtr); // Use DNGBackwardVersion if possible. + } + if ( majorVersion > 1 ) XMP_Throw ( "DNG version beyond 1.x", kXMPErr_BadTIFF ); + + } + + TIFF_Manager::TagInfo xmpInfo; + bool found = this->tiffMgr.GetTag ( kTIFF_PrimaryIFD, kTIFF_XMP, &xmpInfo ); + + if ( found ) { + + this->packetInfo.offset = this->tiffMgr.GetValueOffset ( kTIFF_PrimaryIFD, kTIFF_XMP ); + this->packetInfo.length = xmpInfo.dataLen; + this->packetInfo.padSize = 0; // Assume for now, set these properly in ProcessXMP. + this->packetInfo.charForm = kXMP_CharUnknown; + this->packetInfo.writeable = true; + + this->xmpPacket.assign ( (XMP_StringPtr)xmpInfo.dataPtr, xmpInfo.dataLen ); + + this->containsXMP = true; + + } + +} // TIFF_MetaHandler::CacheFileData + +// ================================================================================================= +// TIFF_MetaHandler::ProcessXMP +// ============================ +// +// Process the raw XMP and legacy metadata that was previously cached. The legacy metadata in TIFF +// is messy because there are 2 copies of the IPTC and because of a Photoshop 6 bug/quirk in the way +// Exif metadata is saved. + +void TIFF_MetaHandler::ProcessXMP() +{ + + this->processedXMP = true; // Make sure we only come through here once. + + // Set up everything for the legacy import, but don't do it yet. This lets us do a forced legacy + // import if the XMP packet gets parsing errors. + + // ! Photoshop 6 wrote annoyingly wacky TIFF files. It buried a lot of the Exif metadata inside + // ! image resource 1058, itself inside of tag 34377 in the 0th IFD. Take care of this before + // ! doing any of the legacy metadata presence or priority analysis. Delete image resource 1058 + // ! to get rid of the buried Exif, but don't mark the XMPFiles object as changed. This change + // ! should not trigger an update, but should be included as part of a normal update. + + bool found; + bool readOnly = ((this->parent->openFlags & kXMPFiles_OpenForUpdate) == 0); + + if ( readOnly ) { + this->psirMgr = new PSIR_MemoryReader(); + this->iptcMgr = new IPTC_Reader(); + } else { + this->psirMgr = new PSIR_FileWriter(); + this->iptcMgr = new IPTC_Writer(); // ! Parse it later. + } + + TIFF_Manager & tiff = this->tiffMgr; // Give the compiler help in recognizing non-aliases. + PSIR_Manager & psir = *this->psirMgr; + IPTC_Manager & iptc = *this->iptcMgr; + + TIFF_Manager::TagInfo psirInfo; + bool havePSIR = tiff.GetTag ( kTIFF_PrimaryIFD, kTIFF_PSIR, &psirInfo ); + + if ( havePSIR ) { // ! Do the Photoshop 6 integration before other legacy analysis. + psir.ParseMemoryResources ( psirInfo.dataPtr, psirInfo.dataLen ); + PSIR_Manager::ImgRsrcInfo buriedExif; + found = psir.GetImgRsrc ( kPSIR_Exif, &buriedExif ); + if ( found ) { + tiff.IntegrateFromPShop6 ( buriedExif.dataPtr, buriedExif.dataLen ); + if ( ! readOnly ) psir.DeleteImgRsrc ( kPSIR_Exif ); + } + } + + TIFF_Manager::TagInfo iptcInfo; + bool haveIPTC = tiff.GetTag ( kTIFF_PrimaryIFD, kTIFF_IPTC, &iptcInfo ); // The TIFF IPTC tag. + int iptcDigestState = kDigestMatches; + + if ( haveIPTC ) { + + bool haveDigest = false; + PSIR_Manager::ImgRsrcInfo digestInfo; + if ( havePSIR ) haveDigest = psir.GetImgRsrc ( kPSIR_IPTCDigest, &digestInfo ); + if ( digestInfo.dataLen != 16 ) haveDigest = false; + + if ( ! haveDigest ) { + + iptcDigestState = kDigestMissing; + + } else { + + // Older versions of Photoshop wrote tag 33723 with type LONG, but ignored the trailing + // zero padding for the IPTC digest. If the full digest differs, recheck without the padding. + + iptcDigestState = PhotoDataUtils::CheckIPTCDigest ( iptcInfo.dataPtr, iptcInfo.dataLen, digestInfo.dataPtr ); + + if ( (iptcDigestState == kDigestDiffers) && (kTIFF_TypeSizes[iptcInfo.type] > 1) ) { + XMP_Uns8 * endPtr = (XMP_Uns8*)iptcInfo.dataPtr + iptcInfo.dataLen - 1; + XMP_Uns8 * minPtr = endPtr - kTIFF_TypeSizes[iptcInfo.type] + 1; + while ( (endPtr >= minPtr) && (*endPtr == 0) ) --endPtr; + XMP_Uns32 unpaddedLen = (XMP_Uns32) (endPtr - (XMP_Uns8*)iptcInfo.dataPtr + 1); + iptcDigestState = PhotoDataUtils::CheckIPTCDigest ( iptcInfo.dataPtr, unpaddedLen, digestInfo.dataPtr ); + } + + } + + } + + XMP_OptionBits options = k2XMP_FileHadExif; // TIFF files are presumed to have Exif legacy. + if ( haveIPTC ) options |= k2XMP_FileHadIPTC; + if ( this->containsXMP ) options |= k2XMP_FileHadXMP; + + // Process the XMP packet. If it fails to parse, do a forced legacy import but still throw an + // exception. This tells the caller that an error happened, but gives them recovered legacy + // should they want to proceed with that. + + bool haveXMP = false; + + if ( ! this->xmpPacket.empty() ) { + XMP_Assert ( this->containsXMP ); + // Common code takes care of packetInfo.charForm, .padSize, and .writeable. + XMP_StringPtr packetStr = this->xmpPacket.c_str(); + XMP_StringLen packetLen = (XMP_StringLen)this->xmpPacket.size(); + try { + this->xmpObj.ParseFromBuffer ( packetStr, packetLen ); + } catch ( ... ) { /* Ignore parsing failures, someday we hope to get partial XMP back. */ } + haveXMP = true; + } + + // Process the legacy metadata. + + if ( haveIPTC && (! haveXMP) && (iptcDigestState == kDigestMatches) ) iptcDigestState = kDigestMissing; + if (iptcInfo.dataLen) iptc.ParseMemoryDataSets ( iptcInfo.dataPtr, iptcInfo.dataLen ); + ImportPhotoData ( tiff, iptc, psir, iptcDigestState, &this->xmpObj, options ); + + this->containsXMP = true; // Assume we now have something in the XMP. + +} // TIFF_MetaHandler::ProcessXMP + +// ================================================================================================= +// TIFF_MetaHandler::UpdateFile +// ============================ +// +// There is very little to do directly in UpdateFile. ExportXMPtoJTP takes care of setting all of +// the necessary TIFF tags, including things like the 2nd copy of the IPTC in the Photoshop image +// resources in tag 34377. TIFF_FileWriter::UpdateFileStream does all of the update-by-append I/O. + +// *** Need to pass the abort proc and arg to TIFF_FileWriter::UpdateFileStream. + +void TIFF_MetaHandler::UpdateFile ( bool doSafeUpdate ) +{ + XMP_Assert ( ! doSafeUpdate ); // This should only be called for "unsafe" updates. + + XMP_IO* destRef = this->parent->ioRef; + XMP_AbortProc abortProc = this->parent->abortProc; + void * abortArg = this->parent->abortArg; + + XMP_Int64 oldPacketOffset = this->packetInfo.offset; + XMP_Int32 oldPacketLength = this->packetInfo.length; + + if ( oldPacketOffset == kXMPFiles_UnknownOffset ) oldPacketOffset = 0; // ! Simplify checks. + if ( oldPacketLength == kXMPFiles_UnknownLength ) oldPacketLength = 0; + + bool fileHadXMP = ((oldPacketOffset != 0) && (oldPacketLength != 0)); + + // Update the IPTC-IIM and native TIFF/Exif metadata. ExportPhotoData also trips the tiff: and + // exif: copies from the XMP, so reserialize the now final XMP packet. + + ExportPhotoData ( kXMP_TIFFFile, &this->xmpObj, &this->tiffMgr, this->iptcMgr, this->psirMgr ); + + try { + XMP_OptionBits options = kXMP_UseCompactFormat; + if ( fileHadXMP ) options |= kXMP_ExactPacketLength; + this->xmpObj.SerializeToBuffer ( &this->xmpPacket, options, oldPacketLength ); + } catch ( ... ) { + this->xmpObj.SerializeToBuffer ( &this->xmpPacket, kXMP_UseCompactFormat ); + } + + // Decide whether to do an in-place update. This can only happen if all of the following are true: + // - There is an XMP packet in the file. + // - The are no changes to the legacy tags. (The IPTC and PSIR are in the TIFF tags.) + // - The new XMP can fit in the old space. + + bool doInPlace = (fileHadXMP && (this->xmpPacket.size() <= (size_t)oldPacketLength)); + if ( this->tiffMgr.IsLegacyChanged() ) doInPlace = false; + + bool localProgressTracking = false; + XMP_ProgressTracker* progressTracker = this->parent->progressTracker; + + if ( ! doInPlace ) { + + #if GatherPerformanceData + sAPIPerf->back().extraInfo += ", TIFF append update"; + #endif + + if ( (progressTracker != 0) && (! progressTracker->WorkInProgress()) ) { + localProgressTracking = true; + progressTracker->BeginWork(); + } + + this->tiffMgr.SetTag ( kTIFF_PrimaryIFD, kTIFF_XMP, kTIFF_UndefinedType, (XMP_Uns32)this->xmpPacket.size(), this->xmpPacket.c_str() ); + this->tiffMgr.UpdateFileStream ( destRef, progressTracker ); + + } else { + + #if GatherPerformanceData + sAPIPerf->back().extraInfo += ", TIFF in-place update"; + #endif + + if ( this->xmpPacket.size() < (size_t)this->packetInfo.length ) { + // They ought to match, cheap to be sure. + size_t extraSpace = (size_t)this->packetInfo.length - this->xmpPacket.size(); + this->xmpPacket.append ( extraSpace, ' ' ); + } + + XMP_IO* liveFile = this->parent->ioRef; + + XMP_Assert ( this->xmpPacket.size() == (size_t)oldPacketLength ); // ! Done by common PutXMP logic. + + if ( progressTracker != 0 ) { + if ( progressTracker->WorkInProgress() ) { + progressTracker->AddTotalWork ( this->xmpPacket.size() ); + } else { + localProgressTracking = true; + progressTracker->BeginWork ( this->xmpPacket.size() ); + } + } + + liveFile->Seek ( oldPacketOffset, kXMP_SeekFromStart ); + liveFile->Write ( this->xmpPacket.c_str(), (XMP_Int32)this->xmpPacket.size() ); + + } + + if ( localProgressTracking ) progressTracker->WorkComplete(); + this->needsUpdate = false; + +} // TIFF_MetaHandler::UpdateFile + +// ================================================================================================= +// TIFF_MetaHandler::WriteTempFile +// =============================== +// +// The structure of TIFF makes it hard to do a sequential source-to-dest copy with interleaved +// updates. So, copy the existing source to the destination and call UpdateFile. + +void TIFF_MetaHandler::WriteTempFile ( XMP_IO* tempRef ) +{ + XMP_IO* origRef = this->parent->ioRef; + + XMP_AbortProc abortProc = this->parent->abortProc; + void * abortArg = this->parent->abortArg; + + XMP_Int64 fileLen = origRef->Length(); + if ( fileLen > 0xFFFFFFFFLL ) { // Check before making a copy of the file. + XMP_Throw ( "TIFF fles can't exceed 4GB", kXMPErr_BadTIFF ); + } + + XMP_ProgressTracker* progressTracker = this->parent->progressTracker; + if ( progressTracker != 0 ) progressTracker->BeginWork ( (float)fileLen ); + + origRef->Rewind ( ); + tempRef->Truncate ( 0 ); + XIO::Copy ( origRef, tempRef, fileLen, abortProc, abortArg ); + + try { + this->parent->ioRef = tempRef; // ! Make UpdateFile update the temp. + this->UpdateFile ( false ); + this->parent->ioRef = origRef; + } catch ( ... ) { + this->parent->ioRef = origRef; + throw; + } + + if ( progressTracker != 0 ) progressTracker->WorkComplete(); + +} // TIFF_MetaHandler::WriteTempFile + +// ================================================================================================= diff --git a/core/libs/dngwriter/extra/xmp_sdk/XMPFiles/source/FileHandlers/TIFF_Handler.hpp b/core/libs/dngwriter/extra/xmp_sdk/XMPFiles/source/FileHandlers/TIFF_Handler.hpp new file mode 100644 index 0000000000..bc653b232c --- /dev/null +++ b/core/libs/dngwriter/extra/xmp_sdk/XMPFiles/source/FileHandlers/TIFF_Handler.hpp @@ -0,0 +1,69 @@ +#ifndef __TIFF_Handler_hpp__ +#define __TIFF_Handler_hpp__ 1 + +// ================================================================================================= +// ADOBE SYSTEMS INCORPORATED +// Copyright 2006 Adobe Systems Incorporated +// All Rights Reserved +// +// NOTICE: Adobe permits you to use, modify, and distribute this file in accordance with the terms +// of the Adobe license agreement accompanying it. +// ================================================================================================= + +#include "XMPFiles/source/FormatSupport/TIFF_Support.hpp" +#include "XMPFiles/source/FormatSupport/PSIR_Support.hpp" +#include "XMPFiles/source/FormatSupport/IPTC_Support.hpp" + +// ================================================================================================= +/// \file TIFF_Handler.hpp +/// \brief File format handler for TIFF. +/// +/// This header ... +/// +// ================================================================================================= + +// *** Could derive from Basic_Handler - buffer file tail in a temp file. + +extern XMPFileHandler * TIFF_MetaHandlerCTor ( XMPFiles * parent ); + +extern bool TIFF_CheckFormat ( XMP_FileFormat format, + XMP_StringPtr filePath, + XMP_IO* fileRef, + XMPFiles * parent ); + +static const XMP_OptionBits kTIFF_HandlerFlags = (kXMPFiles_CanInjectXMP | + kXMPFiles_CanExpand | + kXMPFiles_CanRewrite | + kXMPFiles_PrefersInPlace | + kXMPFiles_CanReconcile | + kXMPFiles_AllowsOnlyXMP | + kXMPFiles_ReturnsRawPacket | + kXMPFiles_AllowsSafeUpdate | + kXMPFiles_CanNotifyProgress); + +class TIFF_MetaHandler : public XMPFileHandler +{ +public: + + void CacheFileData(); + void ProcessXMP(); + + void UpdateFile ( bool doSafeUpdate ); + void WriteTempFile ( XMP_IO* tempRef ); + + TIFF_MetaHandler ( XMPFiles * parent ); + virtual ~TIFF_MetaHandler(); + +private: + + TIFF_MetaHandler() : psirMgr(0), iptcMgr(0) {}; // Hidden on purpose. + + TIFF_FileWriter tiffMgr; // The TIFF part is always file-based. + PSIR_Manager * psirMgr; // Need to use pointers so we can properly select between read-only and + IPTC_Manager * iptcMgr; // read-write modes of usage. + +}; // TIFF_MetaHandler + +// ================================================================================================= + +#endif /* __TIFF_Handler_hpp__ */ diff --git a/core/libs/dngwriter/extra/xmp_sdk/XMPFiles/source/FileHandlers/Trivial_Handler.cpp b/core/libs/dngwriter/extra/xmp_sdk/XMPFiles/source/FileHandlers/Trivial_Handler.cpp new file mode 100644 index 0000000000..ac8b468e2d --- /dev/null +++ b/core/libs/dngwriter/extra/xmp_sdk/XMPFiles/source/FileHandlers/Trivial_Handler.cpp @@ -0,0 +1,74 @@ +// ================================================================================================= +// ADOBE SYSTEMS INCORPORATED +// Copyright 2004 Adobe Systems Incorporated +// All Rights Reserved +// +// NOTICE: Adobe permits you to use, modify, and distribute this file in accordance with the terms +// of the Adobe license agreement accompanying it. +// ================================================================================================= + +#include "public/include/XMP_Environment.h" // ! XMP_Environment.h must be the first included header. + +#include "public/include/XMP_Const.h" +#include "public/include/XMP_IO.hpp" + +#include "XMPFiles/source/XMPFiles_Impl.hpp" +#include "source/XIO.hpp" + +#include "XMPFiles/source/FileHandlers/Trivial_Handler.hpp" + +using namespace std; + +// ================================================================================================= +/// \file Trivial_Handler.cpp +/// \brief Base class for trivial handlers that only process in-place XMP. +/// +/// This header ... +/// +// ================================================================================================= + +// ================================================================================================= +// Trivial_MetaHandler::~Trivial_MetaHandler +// ========================================= + +Trivial_MetaHandler::~Trivial_MetaHandler() +{ + // Nothing to do. + +} // Trivial_MetaHandler::~Trivial_MetaHandler + +// ================================================================================================= +// Trivial_MetaHandler::UpdateFile +// =============================== + +void Trivial_MetaHandler::UpdateFile ( bool doSafeUpdate ) +{ + IgnoreParam ( doSafeUpdate ); + XMP_Assert ( ! doSafeUpdate ); // Not supported at this level. + if ( ! this->needsUpdate ) return; + + XMP_IO* fileRef = this->parent->ioRef; + XMP_PacketInfo & packetInfo = this->packetInfo; + std::string & xmpPacket = this->xmpPacket; + + fileRef->Seek ( packetInfo.offset, kXMP_SeekFromStart ); + fileRef->Write ( xmpPacket.c_str(), packetInfo.length ); + XMP_Assert ( xmpPacket.size() == (size_t)packetInfo.length ); + + this->needsUpdate = false; + +} // Trivial_MetaHandler::UpdateFile + +// ================================================================================================= +// Trivial_MetaHandler::WriteTempFile +// ================================== + +void Trivial_MetaHandler::WriteTempFile ( XMP_IO* tempRef ) +{ + IgnoreParam ( tempRef ); + + XMP_Throw ( "Trivial_MetaHandler::WriteTempFile: Not supported", kXMPErr_Unavailable ); + +} // Trivial_MetaHandler::WriteTempFile + +// ================================================================================================= diff --git a/core/libs/dngwriter/extra/xmp_sdk/XMPFiles/source/FileHandlers/Trivial_Handler.hpp b/core/libs/dngwriter/extra/xmp_sdk/XMPFiles/source/FileHandlers/Trivial_Handler.hpp new file mode 100644 index 0000000000..d9e0e15279 --- /dev/null +++ b/core/libs/dngwriter/extra/xmp_sdk/XMPFiles/source/FileHandlers/Trivial_Handler.hpp @@ -0,0 +1,47 @@ +#ifndef __Trivial_Handler_hpp__ +#define __Trivial_Handler_hpp__ 1 + +// ================================================================================================= +// ADOBE SYSTEMS INCORPORATED +// Copyright 2004 Adobe Systems Incorporated +// All Rights Reserved +// +// NOTICE: Adobe permits you to use, modify, and distribute this file in accordance with the terms +// of the Adobe license agreement accompanying it. +// ================================================================================================= + +#include "XMPFiles/source/XMPFiles_Impl.hpp" + +// ================================================================================================= +/// \file Trivial_Handler.hpp +/// \brief Base class for trivial handlers that only process in-place XMP. +/// +/// This header ... +/// +/// \note There is no general promise here about crash-safe I/O. An update to an existing file might +/// have invalid partial state while rewriting existing XMP in-place. Crash-safe updates are managed +/// at a higher level of XMPFiles, using a temporary file and final swap of file content. +/// +// ================================================================================================= + +static const XMP_OptionBits kTrivial_HandlerFlags = ( kXMPFiles_AllowsOnlyXMP | + kXMPFiles_ReturnsRawPacket | + kXMPFiles_AllowsSafeUpdate ); + +class Trivial_MetaHandler : public XMPFileHandler +{ +public: + + Trivial_MetaHandler() {}; + ~Trivial_MetaHandler(); + + virtual void CacheFileData() = 0; + + void UpdateFile ( bool doSafeUpdate ); + void WriteTempFile ( XMP_IO* tempRef ); + +}; // Trivial_MetaHandler + +// ================================================================================================= + +#endif /* __Trivial_Handler_hpp__ */ diff --git a/core/libs/dngwriter/extra/xmp_sdk/XMPFiles/source/FileHandlers/UCF_Handler.cpp b/core/libs/dngwriter/extra/xmp_sdk/XMPFiles/source/FileHandlers/UCF_Handler.cpp new file mode 100644 index 0000000000..4d6d434cd2 --- /dev/null +++ b/core/libs/dngwriter/extra/xmp_sdk/XMPFiles/source/FileHandlers/UCF_Handler.cpp @@ -0,0 +1,880 @@ +// ================================================================================================= +// ADOBE SYSTEMS INCORPORATED +// Copyright 2007 Adobe Systems Incorporated +// All Rights Reserved +// +// NOTICE: Adobe permits you to use, modify, and distribute this file in accordance with the terms +// of the Adobe license agreement accompanying it. +// =============================================================================================== + +#include "public/include/XMP_Environment.h" // ! XMP_Environment.h must be the first included header. + +#include "public/include/XMP_Const.h" +#include "public/include/XMP_IO.hpp" + +#include "XMPFiles/source/XMPFiles_Impl.hpp" +#include "source/XMPFiles_IO.hpp" +#include "source/XIO.hpp" + +#include "XMPFiles/source/FileHandlers/UCF_Handler.hpp" + +#include "zlib.h" + +#include + +#ifdef DYNAMIC_CRC_TABLE + #error "unexpectedly DYNAMIC_CRC_TABLE defined." + //Must implement get_crc_table prior to any multi-threading (see notes there) +#endif + +#if XMP_WinBuild + #pragma warning ( disable : 4996 ) // '...' was declared deprecated +#endif + +using namespace std; + +// ================================================================================================= +/// \file UCF_Handler.cpp +/// \brief UCF handler class +// ================================================================================================= +const XMP_Uns16 xmpFilenameLen = 21; +const char* xmpFilename = "META-INF/metadata.xml"; + +// ================================================================================================= +// UCF_MetaHandlerCTor +// ==================== +XMPFileHandler* UCF_MetaHandlerCTor ( XMPFiles * parent ) +{ + return new UCF_MetaHandler ( parent ); +} // UCF_MetaHandlerCTor + +// ================================================================================================= +// UCF_CheckFormat +// ================ +// * lenght must at least be 114 bytes +// * first bytes must be \x50\x4B\x03\x04 for *any* zip file +// * at offset 30 it must spell "mimetype" + +#define MIN_UCF_LENGTH 114 +// zip minimum considerations: +// the shortest legal zip is 100 byte: +// 30+1* bytes file header +//+ 0 byte content file (uncompressed) +//+ 46+1* bytes central directory file header +//+ 22 byte end of central directory record +//------- +//100 bytes +// +//1 byte is the shortest legal filename. anything below is no valid zip. +// +//==> the mandatory+first "mimetype" content file has a filename length of 8 bytes, +// thus even if empty (arguably incorrect but tolerable), +// the shortest legal UCF is 114 bytes (30 + 8 + 0 + 46 + 8 + 22 ) +// anything below is with certainty not a valid ucf. + +bool UCF_CheckFormat ( XMP_FileFormat format, + XMP_StringPtr filePath, + XMP_IO* fileRef, + XMPFiles * parent ) +{ + // *not* using buffer functionality here, all we need + // to detect UCF securely is in the first 38 bytes... + IgnoreParam(filePath); IgnoreParam(parent); //suppress warnings + XMP_Assert ( format == kXMP_UCFFile ); //standard assert + + XMP_Uns8 buffer[MIN_UCF_LENGTH]; + + fileRef->Rewind(); + if ( MIN_UCF_LENGTH != fileRef->Read ( buffer, MIN_UCF_LENGTH) ) //NO requireall (->no throw), just return false + return false; + if ( !CheckBytes ( &buffer[0], "\x50\x4B\x03\x04", 4 ) ) // "PK 03 04" + return false; + // UCF spec says: there must be a content file mimetype, and be first and be uncompressed... + if ( !CheckBytes ( &buffer[30], "mimetype", 8 ) ) + return false; + + ////////////////////////////////////////////////////////////////////////////// + //figure out mimetype, decide on writeability + // grab mimetype + fileRef->Seek ( 18, kXMP_SeekFromStart ); + XMP_Uns32 mimeLength = XIO::ReadUns32_LE ( fileRef ); + XMP_Uns32 mimeCompressedLength = XIO::ReadUns32_LE ( fileRef ); // must be same since uncompressed + + XMP_Validate( mimeLength == mimeCompressedLength, + "mimetype compressed and uncompressed length differ", + kXMPErr_BadFileFormat ); + + XMP_Validate( mimeLength != 0, "0-byte mimetype", kXMPErr_BadFileFormat ); + + // determine writability based on mimetype + fileRef->Seek ( 30 + 8, kXMP_SeekFromStart ); + char* mimetype = new char[ mimeLength + 1 ]; + fileRef->ReadAll ( mimetype, mimeLength ); + mimetype[mimeLength] = '\0'; + + bool okMimetype; + + // be lenient on extraneous CR (0xA) [non-XMP bug #16980028] + if ( mimeLength > 0 ) //avoid potential crash (will properly fail below anyhow) + if ( mimetype[mimeLength-1] == 0xA ) + mimetype[mimeLength-1] = '\0'; + + if ( + XMP_LitMatch( mimetype, "application/vnd.adobe.xfl" ) || //Flash Diesel team + XMP_LitMatch( mimetype, "application/vnd.adobe.xfl+zip") || //Flash Diesel team + XMP_LitMatch( mimetype, "application/vnd.adobe.x-mars" ) || //Mars plugin(labs only), Acrobat8 + XMP_LitMatch( mimetype, "application/vnd.adobe.pdfxml" ) || //Mars plugin(labs only), Acrobat 9 + XMP_LitMatch( mimetype, "vnd.adobe.x-asnd" ) || //Adobe Sound Document (Soundbooth Team) + XMP_LitMatch( mimetype, "application/vnd.adobe.indesign-idml-package" ) || //inCopy (inDesign) IDML Document + XMP_LitMatch( mimetype, "application/vnd.adobe.incopy-package" ) || // InDesign Document + XMP_LitMatch( mimetype, "application/vnd.adobe.indesign-package" ) || // InDesign Document + XMP_LitMatch( mimetype, "application/vnd.adobe.collage" ) || //Adobe Collage + XMP_LitMatch( mimetype, "application/vnd.adobe.ideas" ) || //Adobe Ideas + XMP_LitMatch( mimetype, "application/vnd.adobe.proto" ) || //Adobe Proto + false ) // "sentinel" + + // *** ==> unknown are also treated as not acceptable + okMimetype = true; + else + okMimetype = false; + + // not accepted (neither read nor write + //.air - Adobe Air Files + //application/vnd.adobe.air-application-installer-package+zip + //.airi - temporary Adobe Air Files + //application/vnd.adobe.air-application-intermediate-package+zip + + delete [] mimetype; + return okMimetype; + +} // UCF_CheckFormat + +// ================================================================================================= +// UCF_MetaHandler::UCF_MetaHandler +// ================================== + +UCF_MetaHandler::UCF_MetaHandler ( XMPFiles * _parent ) +{ + this->cdx2 = 0 ; + this->z = 0; + this->z2 = 0; + this->h = 0; + this->h2 = 0; + this->al = 0; + this->bl = 0; + this->xl = 0; + this->x2l = 0; + this->cdl = 0; + this->cd2l = 0; + this->cdxl = 0; + this->cdx2l = 0; + this->z2l = 0; + this->hl = 0; + this->fl = 0; + this->f2l = 0; + this->numCF = 0; + this->numCF2 = 0; + this->wasCompressed = false; + this->compressXMP = false; + this->inPlacePossible = false; + this->uncomprPacketLen = 0; + this->uncomprPacketStr = NULL; + this->finalPacketStr = NULL; + this->finalPacketLen = 0; + this->parent = _parent; + this->handlerFlags = kUCF_HandlerFlags; + this->stdCharForm = kXMP_Char8Bit; +} // UCF_MetaHandler::UCF_MetaHandler + +// ================================================================================================= +// UCF_MetaHandler::~UCF_MetaHandler +// ===================================== + +UCF_MetaHandler::~UCF_MetaHandler() +{ + // nothing +} + +// ================================================================================================= +// UCF_MetaHandler::CacheFileData +// =============================== +// +void UCF_MetaHandler::CacheFileData() +{ + //*** abort procedures + this->containsXMP = false; //assume no XMP for now (beware of exceptions...) + XMP_IO* file = this->parent->ioRef; + XMP_PacketInfo &packetInfo = this->packetInfo; + + // clear file positioning info --------------------------------------------------- + b=0;b2=0;x=0;x2=0;cd=0;cd2=0;cdx=0;cdx2=0;h=0;h2=0,fl=0;f2l=0; + al=0;bl=0;xl=0;x2l=0;cdl=0;cd2l=0;cdxl=0;cdx2l=0;hl=0,z=0,z2=0,z2l=0; + numCF=0;numCF2=0; + wasCompressed = false; + + // ------------------------------------------------------------------------------- + fl=file ->Length(); + if ( fl < MIN_UCF_LENGTH ) XMP_Throw("file too short, can't be correct UCF",kXMPErr_Unimplemented); + + ////////////////////////////////////////////////////////////////////////////// + // find central directory before optional comment + // things have to go bottom-up, since description headers are allowed in UCF + // "scan backwards" until feasible field found (plus sig sanity check) + // OS buffering should be smart enough, so not doing anything on top + // plus almost all comments will be zero or rather short + + //no need to check anything but the 21 chars of "METADATA-INF/metadata.xml" + char filenameToTest[22]; + filenameToTest[21]='\0'; + + XMP_Int32 zipCommentLen = 0; + for ( ; zipCommentLen <= EndOfDirectory::COMMENT_MAX; zipCommentLen++ ) + { + file->Seek ( -zipCommentLen -2, kXMP_SeekFromEnd ); + if ( XIO::ReadUns16_LE( file ) == zipCommentLen ) //found it? + { + //double check, might just look like comment length (actually be 'evil' comment) + file ->Seek ( - EndOfDirectory::FIXED_SIZE, kXMP_SeekFromCurrent ); + if ( XIO::ReadUns32_LE( file ) == EndOfDirectory::ID ) break; //heureka, directory ID + // 'else': pretend nothing happended, just go on + } + } + //was it a break or just not found ? + if ( zipCommentLen > EndOfDirectory::COMMENT_MAX ) XMP_Throw( "zip broken near end or invalid comment" , kXMPErr_BadFileFormat ); + + //////////////////////////////////////////////////////////////////////////// + //read central directory + hl = zipCommentLen + EndOfDirectory::FIXED_SIZE; + h = fl - hl; + file ->Seek ( h , kXMP_SeekFromStart ); + + if ( XIO::ReadUns32_LE( file ) != EndOfDirectory::ID ) + XMP_Throw("directory header id not found. or broken comment",kXMPErr_BadFileFormat); + if ( XIO::ReadUns16_LE( file ) != 0 ) + XMP_Throw("UCF must be 'first' zip volume",kXMPErr_BadFileFormat); + if ( XIO::ReadUns16_LE( file ) != 0 ) + XMP_Throw("UCF must be single-volume zip",kXMPErr_BadFileFormat); + + numCF = XIO::ReadUns16_LE( file ); //number of content files + if ( numCF != XIO::ReadUns16_LE( file ) ) + XMP_Throw( "per volume and total number of dirs differ" , kXMPErr_BadFileFormat ); + cdl = XIO::ReadUns32_LE( file ); + cd = XIO::ReadUns32_LE( file ); + file->Seek ( 2, kXMP_SeekFromCurrent ); //skip comment len, needed since next LFA is kXMP_SeekFromCurrent ! + + ////////////////////////////////////////////////////////////////////////////// + // check for zip64-end-of-CD-locator/ zip64-end-of-CD + // to to central directory + if ( cd == 0xffffffff ) + { // deal with zip 64, otherwise continue + XMP_Int64 tmp = file->Seek ( -(EndOfDirectory::FIXED_SIZE + Zip64Locator::TOTAL_SIZE), + kXMP_SeekFromCurrent ); //go to begining of zip64 locator + //relative movement , absolute would imho only require another -zipCommentLen + + if ( Zip64Locator::ID == XIO::ReadUns32_LE(file) ) // prevent 'coincidental length' ffffffff + { + XMP_Validate( 0 == XIO::ReadUns32_LE(file), + "zip64 CD disk must be 0", kXMPErr_BadFileFormat ); + + z = XIO::ReadUns64_LE(file); + XMP_Validate( z < 0xffffffffffffLL, "file in terrabyte range?", kXMPErr_BadFileFormat ); // 3* ffff, sanity test + + XMP_Uns32 totalNumOfDisks = XIO::ReadUns32_LE(file); + /* tolerated while pkglib bug #1742179 */ + XMP_Validate( totalNumOfDisks == 0 || totalNumOfDisks == 1, + "zip64 total num of disks must be 0", kXMPErr_BadFileFormat ); + + /////////////////////////////////////////////// + /// on to end-of-CD itself + file->Seek ( z, kXMP_SeekFromStart ); + XMP_Validate( Zip64EndOfDirectory::ID == XIO::ReadUns32_LE(file), + "invalid zip64 end of CD sig", kXMPErr_BadFileFormat ); + + XMP_Int64 sizeOfZip64EOD = XIO::ReadUns64_LE(file); + file->Seek ( 12, kXMP_SeekFromCurrent ); + //yes twice "total" and "per disk" + XMP_Int64 tmp64 = XIO::ReadUns64_LE(file); + XMP_Validate( tmp64 == numCF, "num of content files differs to zip64 (1)", kXMPErr_BadFileFormat ); + tmp64 = XIO::ReadUns64_LE(file); + XMP_Validate( tmp64 == numCF, "num of content files differs to zip64 (2)", kXMPErr_BadFileFormat ); + // cd length verification + tmp64 = XIO::ReadUns64_LE(file); + XMP_Validate( tmp64 == cdl, "CD length differs in zip64", kXMPErr_BadFileFormat ); + + cd = XIO::ReadUns64_LE(file); // wipe out invalid 0xffffffff with the real thing + //ignoring "extensible data sector (would need fullLength - fixed length) for now + } + } // of zip64 fork + ///////////////////////////////////////////////////////////////////////////// + // parse central directory + // 'foundXMP' <=> cdx != 0 + + file->Seek ( cd, kXMP_SeekFromStart ); + XMP_Int64 cdx_suspect=0; + XMP_Int64 cdxl_suspect=0; + CDFileHeader curCDHeader; + + for ( XMP_Uns16 entryNum=1 ; entryNum <= numCF ; entryNum++ ) + { + cdx_suspect = file->Offset(); //just suspect for now + curCDHeader.read( file ); + + if ( GetUns32LE( &curCDHeader.fields[CDFileHeader::o_sig] ) != 0x02014b50 ) + XMP_Throw("&invalid file header",kXMPErr_BadFileFormat); + + cdxl_suspect = curCDHeader.FIXED_SIZE + + GetUns16LE(&curCDHeader.fields[CDFileHeader::o_fileNameLength]) + + GetUns16LE(&curCDHeader.fields[CDFileHeader::o_extraFieldLength]) + + GetUns16LE(&curCDHeader.fields[CDFileHeader::o_commentLength]); + + // we only look 21 characters, that's META-INF/metadata.xml, no \0 attached + if ( curCDHeader.filenameLen == xmpFilenameLen /*21*/ ) + if( XMP_LitNMatch( curCDHeader.filename , "META-INF/metadata.xml", 21 ) ) + { + cdx = cdx_suspect; + cdxl = cdxl_suspect; + break; + } + //hop to next + file->Seek ( cdx_suspect + cdxl_suspect , kXMP_SeekFromStart ); + } //for-loop, iterating *all* central directory headers (also beyond found) + + if ( !cdx ) // not found xmp + { + // b and bl remain 0, x and xl remain 0 + // ==> a is everything before directory + al = cd; + return; + } + + // from here is if-found-only + ////////////////////////////////////////////////////////////////////////////// + //CD values needed, most serve counter-validation purposes (below) only + // read whole object (incl. all 3 fields) again properly + // to get extra Fields, etc + file->Seek ( cdx, kXMP_SeekFromStart ); + xmpCDHeader.read( file ); + + XMP_Validate( xmpFilenameLen == GetUns16LE( &xmpCDHeader.fields[CDFileHeader::o_fileNameLength]), + "content file length not ok", kXMPErr_BadFileFormat ); + + XMP_Uns16 CD_compression = GetUns16LE( &xmpCDHeader.fields[CDFileHeader::o_compression] ); + XMP_Validate(( CD_compression == 0 || CD_compression == 0x08), + "illegal compression, must be flate or none", kXMPErr_BadFileFormat ); + XMP_Uns16 CD_flags = GetUns16LE( &xmpCDHeader.fields[CDFileHeader::o_flags] ); + XMP_Uns32 CD_crc = GetUns32LE( &xmpCDHeader.fields[CDFileHeader::o_crc32] ); + + // parse (actual, non-CD!) file header //////////////////////////////////////////////// + x = xmpCDHeader.offsetLocalHeader; + file ->Seek ( x , kXMP_SeekFromStart ); + xmpFileHeader.read( file ); + xl = xmpFileHeader.sizeHeader() + xmpCDHeader.sizeCompressed; + + //values needed + XMP_Uns16 fileNameLength = GetUns16LE( &xmpFileHeader.fields[FileHeader::o_fileNameLength] ); + XMP_Uns16 extraFieldLength = GetUns16LE( &xmpFileHeader.fields[FileHeader::o_extraFieldLength] ); + XMP_Uns16 compression = GetUns16LE( &xmpFileHeader.fields[FileHeader::o_compression] ); + XMP_Uns32 sig = GetUns32LE( &xmpFileHeader.fields[FileHeader::o_sig] ); + XMP_Uns16 flags = GetUns16LE( &xmpFileHeader.fields[FileHeader::o_flags] ); + XMP_Uns32 sizeCompressed = GetUns32LE( &xmpFileHeader.fields[FileHeader::o_sizeCompressed] ); + XMP_Uns32 sizeUncompressed = GetUns32LE( &xmpFileHeader.fields[FileHeader::o_sizeUncompressed] ); + XMP_Uns32 crc = GetUns32LE( &xmpFileHeader.fields[FileHeader::o_crc32] ); + + // check filename + XMP_Validate( fileNameLength == 21, "filename size contradiction" , kXMPErr_BadFileFormat ); + XMP_Enforce ( xmpFileHeader.filename != 0 ); + XMP_Validate( !memcmp( "META-INF/metadata.xml", xmpFileHeader.filename , xmpFilenameLen ) , "filename is cf header is not META-INF/metadata.xml" , kXMPErr_BadFileFormat ); + + // deal with data descriptor if needed + if ( flags & FileHeader::kdataDescriptorFlag ) + { + if ( sizeCompressed!=0 || sizeUncompressed!=0 || crc!=0 ) XMP_Throw("data descriptor must mean 3x zero",kXMPErr_BadFileFormat); + file->Seek ( xmpCDHeader.sizeCompressed + fileNameLength + xmpCDHeader.extraFieldLen, kXMP_SeekFromCurrent ); //skip actual data to get to descriptor + crc = XIO::ReadUns32_LE( file ); + if ( crc == 0x08074b50 ) //data descriptor may or may not have signature (see spec) + { + crc = XIO::ReadUns32_LE( file ); //if it does, re-read + } + sizeCompressed = XIO::ReadUns32_LE( file ); + sizeUncompressed = XIO::ReadUns32_LE( file ); + // *** cater for zip64 plus 'streamed' data-descriptor stuff + } + + // more integrity checks (post data descriptor handling) + if ( sig != 0x04034b50 ) XMP_Throw("invalid content file header",kXMPErr_BadFileFormat); + if ( compression != CD_compression ) XMP_Throw("compression contradiction",kXMPErr_BadFileFormat); + if ( sizeUncompressed != xmpCDHeader.sizeUncompressed ) XMP_Throw("contradicting uncompressed lengths",kXMPErr_BadFileFormat); + if ( sizeCompressed != xmpCDHeader.sizeCompressed ) XMP_Throw("contradicting compressed lengths",kXMPErr_BadFileFormat); + if ( sizeUncompressed == 0 ) XMP_Throw("0-byte uncompressed size", kXMPErr_BadFileFormat ); + + //////////////////////////////////////////////////////////////////// + // packet Info + this->packetInfo.charForm = stdCharForm; + this->packetInfo.writeable = false; + this->packetInfo.offset = kXMPFiles_UnknownOffset; // checksum!, hide position to not give funny ideas + this->packetInfo.length = kXMPFiles_UnknownLength; + + //////////////////////////////////////////////////////////////////// + // prepare packet (compressed or not) + this->xmpPacket.erase(); + this->xmpPacket.reserve( sizeUncompressed ); + this->xmpPacket.append( sizeUncompressed, ' ' ); + XMP_StringPtr packetStr = XMP_StringPtr ( xmpPacket.c_str() ); // only set after reserving the space! + + // go to packet offset + file->Seek ( x + xmpFileHeader.FIXED_SIZE + fileNameLength + extraFieldLength , kXMP_SeekFromStart); + + // compression fork -------------------------------------------------- + switch (compression) + { + case 0x8: // FLATE + { + wasCompressed = true; + XMP_Uns32 bytesRead = 0; + XMP_Uns32 bytesWritten = 0; // for writing into packetString + const unsigned int CHUNK = 16384; + + int ret; + unsigned int have; //added type + z_stream strm; + unsigned char in[CHUNK]; + unsigned char out[CHUNK]; + // does need this intermediate stage, no direct compressio to packetStr possible, + // since also partially filled buffers must be picked up. That's how it works. + // in addition: internal zlib variables might have 16 bit limits... + + /* allocate inflate state */ + strm.zalloc = Z_NULL; + strm.zfree = Z_NULL; + strm.opaque = Z_NULL; + strm.avail_in = 0; + strm.next_in = Z_NULL; + + /* must use windowBits = -15, for raw inflate, no zlib header */ + ret = inflateInit2(&strm,-MAX_WBITS); + + if (ret != Z_OK) + XMP_Throw("zlib error ",kXMPErr_ExternalFailure); + + /* decompress until deflate stream ends or end of file */ + do { + // must take care here not to read too much, thus whichever is smaller: + XMP_Int32 bytesRemaining = sizeCompressed - bytesRead; + if ( (XMP_Int32)CHUNK < bytesRemaining ) bytesRemaining = (XMP_Int32)CHUNK; + strm.avail_in=file ->ReadAll ( in , bytesRemaining ); + bytesRead += strm.avail_in; // NB: avail_in is "unsigned_int", so might be 16 bit (not harmfull) + + if (strm.avail_in == 0) break; + strm.next_in = in; + + do { + strm.avail_out = CHUNK; + strm.next_out = out; + ret = inflate(&strm, Z_NO_FLUSH); + XMP_Assert( ret != Z_STREAM_ERROR ); /* state not clobbered */ + switch (ret) + { + case Z_NEED_DICT: + (void)inflateEnd(&strm); + XMP_Throw("zlib error: Z_NEED_DICT",kXMPErr_ExternalFailure); + case Z_DATA_ERROR: + (void)inflateEnd(&strm); + XMP_Throw("zlib error: Z_DATA_ERROR",kXMPErr_ExternalFailure); + case Z_MEM_ERROR: + (void)inflateEnd(&strm); + XMP_Throw("zlib error: Z_MEM_ERROR",kXMPErr_ExternalFailure); + } + + have = CHUNK - strm.avail_out; + memcpy( (unsigned char*) packetStr + bytesWritten , out , have ); + bytesWritten += have; + + } while (strm.avail_out == 0); + + /* it's done when inflate() says it's done */ + } while (ret != Z_STREAM_END); + + /* clean up and return */ + (void)inflateEnd(&strm); + if (ret != Z_STREAM_END) + XMP_Throw("zlib error ",kXMPErr_ExternalFailure); + break; + } + case 0x0: // no compression - read directly into the right place + { + wasCompressed = false; + XMP_Enforce( file->ReadAll ( (char*)packetStr, sizeUncompressed ) ); + break; + } + } + this->containsXMP = true; // do this last, after all possible failure/execptions +} + + +// ================================================================================================= +// UCF_MetaHandler::ProcessXMP +// ============================ + +void UCF_MetaHandler::ProcessXMP() +{ + // we have no legacy, CacheFileData did all that was needed + // ==> default implementation is fine + XMPFileHandler::ProcessXMP(); +} + +// ================================================================================================= +// UCF_MetaHandler::UpdateFile +// ============================= + +// TODO: xmp packet with data descriptor + +void UCF_MetaHandler::UpdateFile ( bool doSafeUpdate ) +{ + //sanity + XMP_Enforce( (x!=0) == (cdx!=0) ); + if (!cdx) + xmpCDHeader.setXMPFilename(); //if new, set filename (impacts length, thus before computation) + if ( ! this->needsUpdate ) + return; + + // *** + if ( doSafeUpdate ) XMP_Throw ( "UCF_MetaHandler::UpdateFile: Safe update not supported", kXMPErr_Unavailable ); + + XMP_IO* file = this->parent->ioRef; + + // final may mean compressed or not, whatever is to-be-embedded + uncomprPacketStr = xmpPacket.c_str(); + uncomprPacketLen = (XMP_StringLen) xmpPacket.size(); + finalPacketStr = uncomprPacketStr; // will be overriden if compressedXMP==true + finalPacketLen = uncomprPacketLen; + std::string compressedPacket; // moot if non-compressed, still here for scope reasons (having to keep a .c_str() alive) + + if ( !x ) // if new XMP... + { + xmpFileHeader.clear(); + xmpFileHeader.setXMPFilename(); + // ZIP64 TODO: extra Fields, impact on cdxl2 and x2l + } + + //////////////////////////////////////////////////////////////////////////////////////////////// + // COMPRESSION DECISION + + // for large files compression is bad: + // a) size of XMP becomes irrelevant on large files ==> why worry over compression ? + // b) more importantly: no such thing as padding possible, compression == ever changing sizes + // => never in-place rewrites, *ugly* performance impact on large files + inPlacePossible = false; //assume for now + + if ( !x ) // no prior XMP? -> decide on filesize + compressXMP = ( fl > 1024*50 /* 100 kB */ ) ? false : true; + else + compressXMP = wasCompressed; // don't change a thing + + if ( !wasCompressed && !compressXMP && + ( GetUns32LE( &xmpFileHeader.fields[FileHeader::o_sizeUncompressed] ) == uncomprPacketLen )) + { + inPlacePossible = true; + } + //////////////////////////////////////////////////////////////////////////////////////////////// + // COMPRESS XMP + if ( compressXMP ) + { + const unsigned int CHUNK = 16384; + int ret, flush; + unsigned int have; + z_stream strm; + unsigned char out[CHUNK]; + + /* allocate deflate state */ + strm.zalloc = Z_NULL; strm.zfree = Z_NULL; strm.opaque = Z_NULL; + if ( deflateInit2(&strm, Z_DEFAULT_COMPRESSION, Z_DEFLATED, -MAX_WBITS, 8 /*memlevel*/, Z_DEFAULT_STRATEGY) ) + XMP_Throw("zlib error ",kXMPErr_ExternalFailure); + + //write at once, since we got it in mem anyway: + strm.avail_in = uncomprPacketLen; + flush = Z_FINISH; // that's all, folks + strm.next_in = (unsigned char*) uncomprPacketStr; + + do { + strm.avail_out = CHUNK; + strm.next_out = out; + + ret = deflate(&strm, flush); /* no bad return value (!=0 acceptable) */ + XMP_Enforce(ret != Z_STREAM_ERROR); /* state not clobbered */ + //fwrite(buffer,size,count,file) + have = CHUNK - strm.avail_out; + compressedPacket.append( (const char*) out, have); + } while (strm.avail_out == 0); + + if (ret != Z_STREAM_END) + XMP_Throw("zlib stream incomplete ",kXMPErr_ExternalFailure); + XMP_Enforce(strm.avail_in == 0); // all input will be used + (void)deflateEnd(&strm); //clean up (do prior to checks) + + finalPacketStr = compressedPacket.c_str(); + finalPacketLen = (XMP_StringLen)compressedPacket.size(); + } + + PutUns32LE ( uncomprPacketLen, &xmpFileHeader.fields[FileHeader::o_sizeUncompressed] ); + PutUns32LE ( finalPacketLen, &xmpFileHeader.fields[FileHeader::o_sizeCompressed] ); + PutUns16LE ( compressXMP ? 8:0, &xmpFileHeader.fields[FileHeader::o_compression] ); + + //////////////////////////////////////////////////////////////////////////////////////////////// + // CRC (always of uncompressed data) + XMP_Uns32 crc = crc32( 0 , (Bytef*)uncomprPacketStr, uncomprPacketLen ); + PutUns32LE( crc, &xmpFileHeader.fields[FileHeader::o_crc32] ); + + //////////////////////////////////////////////////////////////////////////////////////////////// + // TIME calculation for timestamp + // will be applied both to xmp content file and CD header + XMP_Uns16 lastModTime, lastModDate; + XMP_DateTime time; + SXMPUtils::CurrentDateTime( &time ); + + if ( (time.year - 1900) < 80) + { + lastModTime = 0; // 1.1.1980 00:00h + lastModDate = 21; + } + + // typedef unsigned short ush; //2 bytes + lastModDate = (XMP_Uns16) (((time.year) - 1980 ) << 9 | ((time.month) << 5) | time.day); + lastModTime = ((XMP_Uns16)time.hour << 11) | ((XMP_Uns16)time.minute << 5) | ((XMP_Uns16)time.second >> 1); + + PutUns16LE ( lastModDate, &xmpFileHeader.fields[FileHeader::o_lastmodDate] ); + PutUns16LE ( lastModTime, &xmpFileHeader.fields[FileHeader::o_lastmodTime] ); + + //////////////////////////////////////////////////////////////////////////////////////////////// + // adjustments depending on 4GB Border, + // decisions on in-place update + // so far only z, zl have been determined + + // Zip64 related assurances, see (15) + XMP_Enforce(!z2); + XMP_Enforce(h+hl == fl ); + + //////////////////////////////////////////////////////////////////////////////////////////////// + // COMPUTE MISSING VARIABLES + // A - based on xmp existence + // + // already known: x, xl, cd + // most left side vars, + // + // finalPacketStr, finalPacketLen + + if ( x ) // previous xmp? + { + al = x; + b = x + xl; + bl = cd - b; + } + else + { + al = cd; + //b,bl left at zero + } + + if ( inPlacePossible ) + { // leave xmp right after A + x2 = al; + x2l = xmpFileHeader.sizeTotalCF(); //COULDDO: assert (x2l == xl) + if (b) b2 = x2 + x2l; // b follows x as last content part + cd2 = b2 + bl; // CD follows B2 + } + else + { // move xmp to end + if (b) b2 = al; // b follows + // x follows as last content part (B existing or not) + x2 = al + bl; + x2l = xmpFileHeader.sizeTotalCF(); + cd2 = x2 + x2l; // CD follows X + } + + /// create new XMP header /////////////////////////////////////////////////// + // written into actual fields + generation of extraField at .write()-time... + // however has impact on .size() computation -- thus enter before cdx2l computation + xmpCDHeader.sizeUncompressed = uncomprPacketLen; + xmpCDHeader.sizeCompressed = finalPacketLen; + xmpCDHeader.offsetLocalHeader = x2; + PutUns32LE ( crc, &xmpCDHeader.fields[CDFileHeader::o_crc32] ); + PutUns16LE ( compressXMP ? 8:0, &xmpCDHeader.fields[CDFileHeader::o_compression] ); + PutUns16LE ( lastModDate, &xmpCDHeader.fields[CDFileHeader::o_lastmodDate] ); + PutUns16LE ( lastModTime, &xmpCDHeader.fields[CDFileHeader::o_lastmodTime] ); + + // for + if ( inPlacePossible ) + { + cdx2 = cdx; //same, same + writeOut( file, file, false, true ); + return; + } + + //////////////////////////////////////////////////////////////////////// + // temporarily store (those few, small) trailing things that might not survive the move around: + file->Seek ( cd, kXMP_SeekFromStart ); // seek to central directory + cdEntries.clear(); //mac precaution + + ////////////////////////////////////////////////////////////////////////////// + // parse headers + // * stick together output header list + cd2l = 0; //sum up below + + CDFileHeader tempHeader; + for( XMP_Uns16 pos=1 ; pos <= numCF ; pos++ ) + { + if ( (cdx) && (file->Offset() == cdx) ) + { + tempHeader.read( file ); //read, even if not use, to advance file pointer + } + else + { + tempHeader.read( file ); + // adjust b2 offset for files that were behind the xmp: + // may (if xmp moved to back) + // or may not (inPlace Update) make a difference + if ( (x) && ( tempHeader.offsetLocalHeader > x) ) // if xmp existed before and this was a file behind it + tempHeader.offsetLocalHeader += b2 - b; + cd2l += tempHeader.size(); // prior offset change might have impact + cdEntries.push_back( tempHeader ); + } + } + + //push in XMP packet as last one (new or not) + cdEntries.push_back( xmpCDHeader ); + cdx2l = xmpCDHeader.size(); + cd2l += cdx2l; // true, no matter which order + + //OLD cd2l = : cdl - cdxl + cdx2l; // (NB: cdxl might be 0) + numCF2 = numCF + ( (cdx)?0:1 ); //xmp packet for the first time? -> add one more CF + + XMP_Validate( numCF2 > 0, "no content files", kXMPErr_BadFileFormat ); + XMP_Validate( numCF2 <= 0xFFFE, "max number of 0xFFFE entries reached", kXMPErr_BadFileFormat ); + + cdx2 = cd2 + cd2l - cdx2l; // xmp content entry comes last (since beyond inPlace Update) + + // zip64 decision + if ( ( cd2 + cd2l + hl ) > 0xffffffff ) // predict non-zip size ==> do we need a zip-64? + { + z2 = cd2 + cd2l; + z2l = Zip64EndOfDirectory::FIXED_SIZE + Zip64Locator::TOTAL_SIZE; + } + + // header and output length, + h2 = cd2 + cd2l + z2l; // (z2l might be 0) + f2l = h2 + hl; + + //////////////////////////////////////////////////////////////////////////////////////////////// + // read H (endOfCD), correct offset + file->Seek ( h, kXMP_SeekFromStart ); + + endOfCD.read( file ); + if ( cd2 <= 0xffffffff ) + PutUns32LE( (XMP_Int32) cd2 , &endOfCD.fields[ endOfCD.o_CdOffset ] ); + else + PutUns32LE( 0xffffffff , &endOfCD.fields[ endOfCD.o_CdOffset ] ); + PutUns16LE( numCF2, &endOfCD.fields[ endOfCD.o_CdNumEntriesDisk ] ); + PutUns16LE( numCF2, &endOfCD.fields[ endOfCD.o_CdNumEntriesTotal ] ); + + XMP_Enforce( cd2l <= 0xffffffff ); // _size_ of directory itself certainly under 4GB + PutUns32LE( (XMP_Uns32)cd2l, &endOfCD.fields[ endOfCD.o_CdSize ] ); + + //////////////////////////////////////////////////////////////////////////////////////////////// + // MOVING + writeOut( file, file, false, false ); + + this->needsUpdate = false; //do last for safety reasons +} // UCF_MetaHandler::UpdateFile + +// ================================================================================================= +// UCF_MetaHandler::WriteTempFile +// ============================== +void UCF_MetaHandler::WriteTempFile ( XMP_IO* tempRef ) +{ + IgnoreParam ( tempRef ); + XMP_Throw ( "UCF_MetaHandler::WriteTempFile: TO BE IMPLEMENTED", kXMPErr_Unimplemented ); +} + +// ================================================================================================= +// own approach to unify Update and WriteFile: +// ============================ + +void UCF_MetaHandler::writeOut( XMP_IO* sourceFile, XMP_IO* targetFile, bool isRewrite, bool isInPlace) +{ + // isInPlace is only possible when it's not a complete rewrite + XMP_Enforce( (!isInPlace) || (!isRewrite) ); + + ///////////////////////////////////////////////////////// + // A + if (isRewrite) //move over A block + XIO::Move( sourceFile , 0 , targetFile, 0 , al ); + + ///////////////////////////////////////////////////////// + // B / X (not necessarily in this order) + if ( !isInPlace ) // B does not change a thing (important optimization) + { + targetFile ->Seek ( b2 , kXMP_SeekFromStart ); + XIO::Move( sourceFile , b , targetFile, b2 , bl ); + } + + targetFile ->Seek ( x2 , kXMP_SeekFromStart ); + xmpFileHeader.write( targetFile ); + targetFile->Write ( finalPacketStr, finalPacketLen ); + //TODO: cover reverse case / inplace ... + + ///////////////////////////////////////////////////////// + // CD + // No Seek here on purpose. + // This assert must still be valid + + // if inPlace, the only thing that needs still correction is the CRC in CDX: + if ( isInPlace ) + { + XMP_Uns32 crc; //TEMP, not actually needed + crc = GetUns32LE( &xmpFileHeader.fields[FileHeader::o_crc32] ); + + // go there, + // do the job (take value directly from (non-CD-)fileheader), + // end of story. + targetFile ->Seek ( cdx2 + CDFileHeader::o_crc32 , kXMP_SeekFromStart ); + targetFile->Write ( &xmpFileHeader.fields[FileHeader::o_crc32], 4 ); + + return; + } + + targetFile ->Seek ( cd2 , kXMP_SeekFromStart ); + + std::vector::iterator iter; + int tmptmp=1; + for( iter = cdEntries.begin(); iter != cdEntries.end(); iter++ ) { + CDFileHeader* p=&(*iter); + XMP_Int64 before = targetFile->Offset(); + p->write( targetFile ); + XMP_Int64 total = targetFile->Offset() - before; + XMP_Int64 tmpSize = p->size(); + tmptmp++; + } + + ///////////////////////////////////////////////////////// + // Z + if ( z2 ) // yes, that simple + { + XMP_Assert( z2 == targetFile->Offset()); + targetFile ->Seek ( z2 , kXMP_SeekFromStart ); + + //no use in copying, always construct from scratch + Zip64EndOfDirectory zip64EndOfDirectory( cd2, cd2l, numCF2) ; + Zip64Locator zip64Locator( z2 ); + + zip64EndOfDirectory.write( targetFile ); + zip64Locator.write( targetFile ); + } + + ///////////////////////////////////////////////////////// + // H + XMP_Assert( h2 == targetFile->Offset()); + endOfCD.write( targetFile ); + + XMP_Assert( f2l == targetFile->Offset()); + if ( f2l< fl) + targetFile->Truncate ( f2l ); //file may have shrunk +} diff --git a/core/libs/dngwriter/extra/xmp_sdk/XMPFiles/source/FileHandlers/UCF_Handler.hpp b/core/libs/dngwriter/extra/xmp_sdk/XMPFiles/source/FileHandlers/UCF_Handler.hpp new file mode 100644 index 0000000000..3140c23d07 --- /dev/null +++ b/core/libs/dngwriter/extra/xmp_sdk/XMPFiles/source/FileHandlers/UCF_Handler.hpp @@ -0,0 +1,722 @@ +#ifndef __UCF_Handler_hpp__ +#define __UCF_Handler_hpp__ 1 + +// ================================================================================================= +// ADOBE SYSTEMS INCORPORATED +// Copyright 2007 Adobe Systems Incorporated +// All Rights Reserved +// +// NOTICE: Adobe permits you to use, modify, and distribute this file in accordance with the terms +// of the Adobe license agreement accompanying it. +// ================================================================================================= + +#include "public/include/XMP_Environment.h" // ! XMP_Environment.h must be the first included header. + +#include "public/include/XMP_Const.h" +#include "public/include/XMP_IO.hpp" + +#include "XMPFiles/source/XMPFiles_Impl.hpp" +#include "source/XMPFiles_IO.hpp" + +// ================================================================================================= +/// \file UCF_Handler.hpp +// +// underlying math: +// __ 0 ______ 0 ______ __ +// | A | | A | +// | | | | +// al | | (a2l)| | +// x |------| b2 |------| +// xl | X | | B |_ +// b |------| (b2l)| | | +// | B | x2 |------| | B2 could also be +// bl | | x2l | X2 | | _after_ X2 +// cd |------| cd2 |------|<' +// |//CD//| |//CD2/| +// cdx |------| cdx2 |------| +// cdxl|------| cdx2l|------| +// cdl|//////| cd2l|//////| +// z |------| z2|------| +// | | | | +// [zl]| Z | z2l | Z2 | +// h |------| h2 |------| +// fl | H | | H2 | f2l +// __ hl |______| (h2l)|______| __ +// +// fl file length pre (2 = post) +// numCf number of content files prior to injection +// numCf2 " post +// +// +// l length variable, all else offset +// [ ] variable is not needed +// ( ) variable is identical to left +// a content files prior to xmp (possibly: all) +// b content files behind xmp (possibly: 0) +// x xmp packet (possibly: 0) +// cd central directory +// h end of central directory +// +// z zip64 record and locator (if existing) +// +// general rules: +// the bigger A, the less rewrite effort. +// (also within the CD) +// putting XMP at the end maximizes A. +// +// bool previousXMP == x!=0 +// +// (x==0) == (cdx==0) == (xl==0) == (cdxl==0) +// +// std::vector cdOffsetsPre; +// +// ----------------- +// asserts: +//( 1) a == a2 == 0, making these variables obsolete +//( 2) a2l == al, this block is not touched +//( 3) b2 <= b, b is only moved closer to the beginning of file +//( 4) b2l == bl, b does not change in size +//( 5) x2 >= x, b is only moved further down in the file +// +//( 6) x != 0, x2l != 0, cd != 0, cdl != 0 +// none of these blocks is at the beginning ('mimetype' by spec), +// nor is any of them zero byte long +//( 7) h!=0, hl >= 22 header is not at the beginning, minimum size 22 +// +// file size computation: +//( 8) al + bl + xl +cdl +hl = fl +//( 9) al + bl + x2l+cd2l+hl = fl2 +// +//(10) ( x==0 ) <=> ( cdx == 0 ) +// if there's a packet in the pre-file, or there isn't +//(11) (x==0) => xl=0 +//(12) (cdx==0)=> cdx=0 +// +//(13) x==0 ==> b,bl,b2,b2l==0 +// if there is no pre-xmp, B does not exist +//(14) x!=0 ==> al:=x, b:=x+xl, bl:=cd-b +// +// zip 64: +//(15) zl and z2l are basically equal, except _one_ of them is 0 : +// +//(16) b2l is indeed never different t +// +// FIXED_SIZE means the fixed (minimal) portion of a struct +// TOTAL_SIZE indicates, that this struct indeed has a fixed, known total length +// +// ================================================================================================= + +extern XMPFileHandler* UCF_MetaHandlerCTor ( XMPFiles * parent ); + +extern bool UCF_CheckFormat ( XMP_FileFormat format, + XMP_StringPtr filePath, + XMP_IO* fileRef, + XMPFiles * parent ); + +static const XMP_OptionBits kUCF_HandlerFlags = ( + kXMPFiles_CanInjectXMP | + kXMPFiles_CanExpand | + kXMPFiles_CanRewrite | + /* kXMPFiles_PrefersInPlace | removed, only reasonable for formats where difference is significant */ + kXMPFiles_AllowsOnlyXMP | + kXMPFiles_ReturnsRawPacket | + // *** kXMPFiles_AllowsSafeUpdate | + kXMPFiles_NeedsReadOnlyPacket //UCF/zip has checksums... + ); + +enum { // data descriptor + // may or may not have a signature: 0x08074b50 + kUCF_DD_crc32 = 0, + kUCF_DD_sizeCompressed = 4, + kUCF_DD_sizeUncompressed = 8, +}; + +class UCF_MetaHandler : public XMPFileHandler +{ +public: + UCF_MetaHandler ( XMPFiles * _parent ); + ~UCF_MetaHandler(); + + void CacheFileData(); + void ProcessXMP(); + + void UpdateFile ( bool doSafeUpdate ); + void WriteTempFile ( XMP_IO* tempRef ); + +protected: + const static XMP_Uns16 xmpFilenameLen = 21; + const static char* xmpFilename; + +private: + class Zip64EndOfDirectory { + private: + + public: + const static XMP_Uns16 o_sig = 0; // 0x06064b50 + const static XMP_Uns16 o_size = 4; // of this, excluding leading 12 bytes + // == FIXED_SIZE -12, since we're never creating the extensible data sector... + const static XMP_Uns16 o_VersionMade = 12; + const static XMP_Uns16 o_VersionNeededExtr = 14; + const static XMP_Uns16 o_numDisk = 16; // force 0 + const static XMP_Uns16 o_numCDDisk = 20; // force 0 + const static XMP_Uns16 o_numCFsThisDisk = 24; + const static XMP_Uns16 o_numCFsTotal = 32; // force equal + const static XMP_Uns16 o_sizeOfCD = 40; // (regular one, not Z64) + const static XMP_Uns16 o_offsetCD = 48; // " + + const static XMP_Int32 FIXED_SIZE = 56; + char fields[FIXED_SIZE]; + + const static XMP_Uns32 ID = 0x06064b50; + + Zip64EndOfDirectory( XMP_Int64 offsetCD, XMP_Int64 sizeOfCD, XMP_Uns64 numCFs ) + { + memset(fields,'\0',FIXED_SIZE); + + PutUns32LE(ID ,&fields[o_sig] ); + PutUns64LE(FIXED_SIZE - 12, &fields[o_size] ); //see above + PutUns16LE( 45 ,&fields[o_VersionMade] ); + PutUns16LE( 45 ,&fields[o_VersionNeededExtr] ); + // fine at 0: o_numDisk + // fine at 0: o_numCDDisk + PutUns64LE( numCFs, &fields[o_numCFsThisDisk] ); + PutUns64LE( numCFs, &fields[o_numCFsTotal] ); + PutUns64LE( sizeOfCD, &fields[o_sizeOfCD] ); + PutUns64LE( offsetCD, &fields[o_offsetCD] ); + } + + void write(XMP_IO* file) + { + XMP_Validate( ID == GetUns32LE( &this->fields[o_sig] ), "invalid header on write", kXMPErr_BadFileFormat ); + file ->Write ( fields , FIXED_SIZE ); + } + + }; + + class Zip64Locator { + public: + const static XMP_Uns16 o_sig = 0; // 0x07064b50 + const static XMP_Uns16 o_numDiskZ64CD = 4; // force 0 + const static XMP_Uns16 o_offsZ64EOD = 8; + const static XMP_Uns16 o_numDisks = 16; // set 1, tolerate 0 + + const static XMP_Int32 TOTAL_SIZE = 20; + char fields[TOTAL_SIZE]; + + const static XMP_Uns32 ID = 0x07064b50; + + Zip64Locator( XMP_Int64 offsetZ64EOD ) + { + memset(fields,'\0',TOTAL_SIZE); + PutUns32LE(ID, &fields[Zip64Locator::o_sig] ); + PutUns32LE(0, &fields[Zip64Locator::o_numDiskZ64CD] ); + PutUns64LE(offsetZ64EOD, &fields[Zip64Locator::o_offsZ64EOD] ); + PutUns32LE(1, &fields[Zip64Locator::o_numDisks] ); + } + + // writes structure to file (starting at current position) + void write(XMP_IO* file) + { + XMP_Validate( ID == GetUns32LE( &this->fields[o_sig] ), "invalid header on write", kXMPErr_BadFileFormat ); + file ->Write ( fields , TOTAL_SIZE ); + } + }; + + struct EndOfDirectory { + public: + const static XMP_Int32 FIXED_SIZE = 22; //32 bit type is important to not overrun on maxcomment + const static XMP_Uns32 ID = 0x06054b50; + const static XMP_Int32 COMMENT_MAX = 0xFFFF; + //offsets + const static XMP_Int32 o_CentralDirectorySize = 12; + const static XMP_Int32 o_CentralDirectoryOffset = 16; + }; + + class FileHeader { + private: + //TODO intergrate in clear() + void release() // avoid terminus free() since subject to a #define (mem-leak-check) + { + if (filename) delete filename; + if (extraField) delete extraField; + filename=0; + extraField=0; + } + + public: + const static XMP_Uns32 SIG = 0x04034b50; + const static XMP_Uns16 kdataDescriptorFlag = 0x8; + + const static XMP_Uns16 o_sig = 0; + const static XMP_Uns16 o_extractVersion = 4; + const static XMP_Uns16 o_flags = 6; + const static XMP_Uns16 o_compression = 8; + const static XMP_Uns16 o_lastmodTime = 10; + const static XMP_Uns16 o_lastmodDate = 12; + const static XMP_Uns16 o_crc32 = 14; + const static XMP_Uns16 o_sizeCompressed = 18; + const static XMP_Uns16 o_sizeUncompressed = 22; + const static XMP_Uns16 o_fileNameLength = 26; + const static XMP_Uns16 o_extraFieldLength = 28; + // total 30 + + const static int FIXED_SIZE = 30; + char fields[FIXED_SIZE]; + + char* filename; + char* extraField; + XMP_Uns16 filenameLen; + XMP_Uns16 extraFieldLen; + + void clear() + { + this->release(); + memset(fields,'\0',FIXED_SIZE); + //arm with minimal default values: + PutUns32LE(0x04034b50, &fields[FileHeader::o_sig] ); + PutUns16LE(0x14, &fields[FileHeader::o_extractVersion] ); + } + + FileHeader() : filename(0),filenameLen(0),extraField(0),extraFieldLen(0) + { + clear(); + }; + + // reads entire *FileHeader* structure from file (starting at current position) + void read(XMP_IO* file) + { + this->release(); + + file ->ReadAll ( fields , FIXED_SIZE ); + + XMP_Uns32 tmp32 = GetUns32LE( &this->fields[FileHeader::o_sig] ); + XMP_Validate( SIG == tmp32, "invalid header", kXMPErr_BadFileFormat ); + filenameLen = GetUns16LE( &this->fields[FileHeader::o_fileNameLength] ); + extraFieldLen = GetUns16LE( &this->fields[FileHeader::o_extraFieldLength] ); + + // nb unlike the CDFileHeader the FileHeader will in practice never have + // extra fields. Reasoning: File headers never carry (their own) offsets, + // (un)compressed size of XMP will hardly ever reach 4 GB + + if (filenameLen) { + filename = new char[filenameLen]; + file->ReadAll ( filename, filenameLen ); + } + if (extraFieldLen) { + extraField = new char[extraFieldLen]; + file->ReadAll ( extraField, extraFieldLen ); + // *** NB: this WOULD need parsing for content files that are + // compressed or uncompressed >4GB (VERY unlikely for XMP) + } + } + + // writes structure to file (starting at current position) + void write(XMP_IO* file) + { + XMP_Validate( SIG == GetUns32LE( &this->fields[FileHeader::o_sig] ), "invalid header on write", kXMPErr_BadFileFormat ); + + filenameLen = GetUns16LE( &this->fields[FileHeader::o_fileNameLength] ); + extraFieldLen = GetUns16LE( &this->fields[FileHeader::o_extraFieldLength] ); + + file ->Write ( fields , FIXED_SIZE ); + if (filenameLen) file->Write ( filename, filenameLen ); + if (extraFieldLen) file->Write ( extraField, extraFieldLen ); + } + + void transfer(const FileHeader &orig) + { + memcpy(fields,orig.fields,FIXED_SIZE); + if (orig.extraField) + { + extraFieldLen=orig.extraFieldLen; + extraField = new char[extraFieldLen]; + memcpy(extraField,orig.extraField,extraFieldLen); + } + if (orig.filename) + { + filenameLen=orig.filenameLen; + filename = new char[filenameLen]; + memcpy(filename,orig.filename,filenameLen); + } + }; + + void setXMPFilename() + { + // only needed for fresh structs, thus enforcing rather than catering to memory issues + XMP_Enforce( (filenameLen==0) && (extraFieldLen == 0) ); + filenameLen = xmpFilenameLen; + PutUns16LE(filenameLen, &fields[FileHeader::o_fileNameLength] ); + filename = new char[xmpFilenameLen]; + memcpy(filename,"META-INF/metadata.xml",xmpFilenameLen); + } + + XMP_Uns32 sizeHeader() + { + return this->FIXED_SIZE + this->filenameLen + this->extraFieldLen; + } + + XMP_Uns32 sizeTotalCF() + { + //*** not zip64 bit safe yet, use only for non-large xmp packet + return this->sizeHeader() + GetUns32LE( &fields[FileHeader::o_sizeCompressed] ); + } + + ~FileHeader() + { + this->release(); + }; + + }; //class FileHeader + + ////// yes, this needs an own class + ////// offsets must be extracted, added, modified, + ////// come&go depending on being >0xffffff + ////class extraField { + //// private: + + + class CDFileHeader { + private: + void release() //*** needed or can go? + { + if (filename) delete filename; + if (extraField) delete extraField; + if (comment) delete comment; + filename=0; filenameLen=0; + extraField=0; extraFieldLen=0; + comment=0; commentLen=0; + } + + const static XMP_Uns32 SIG = 0x02014b50; + + public: + const static XMP_Uns16 o_sig = 0; //0x02014b50 + const static XMP_Uns16 o_versionMadeBy = 4; + const static XMP_Uns16 o_extractVersion = 6; + const static XMP_Uns16 o_flags = 8; + const static XMP_Uns16 o_compression = 10; + const static XMP_Uns16 o_lastmodTime = 12; + const static XMP_Uns16 o_lastmodDate = 14; + const static XMP_Uns16 o_crc32 = 16; + const static XMP_Uns16 o_sizeCompressed = 20; // 16bit stub + const static XMP_Uns16 o_sizeUncompressed = 24; // 16bit stub + const static XMP_Uns16 o_fileNameLength = 28; + const static XMP_Uns16 o_extraFieldLength = 30; + const static XMP_Uns16 o_commentLength = 32; + const static XMP_Uns16 o_diskNo = 34; + const static XMP_Uns16 o_internalAttribs = 36; + const static XMP_Uns16 o_externalAttribs = 38; + const static XMP_Uns16 o_offsetLocalHeader = 42; // 16bit stub + // total size is 4+12+12+10+8=46 + + const static int FIXED_SIZE = 46; + char fields[FIXED_SIZE]; + + // do not bet on any zero-freeness, + // certainly no zero termination (pascal strings), + // treat as data blocks + char* filename; + char* extraField; + char* comment; + XMP_Uns16 filenameLen; + XMP_Uns16 extraFieldLen; + XMP_Uns16 commentLen; + + // full, real, parsed 64 bit values + XMP_Int64 sizeUncompressed; + XMP_Int64 sizeCompressed; + XMP_Int64 offsetLocalHeader; + + CDFileHeader() : filename(0),extraField(0),comment(0),filenameLen(0), + extraFieldLen(0),commentLen(0),sizeUncompressed(0),sizeCompressed(0),offsetLocalHeader(0) + { + memset(fields,'\0',FIXED_SIZE); + //already arm with appropriate values where applicable: + PutUns32LE(0x02014b50, &fields[CDFileHeader::o_sig] ); + PutUns16LE(0x14, &fields[CDFileHeader::o_extractVersion] ); + }; + + // copy constructor + CDFileHeader(const CDFileHeader& orig) : filename(0),extraField(0),comment(0),filenameLen(0), + extraFieldLen(0),commentLen(0),sizeUncompressed(0),sizeCompressed(0),offsetLocalHeader(0) + { + memcpy(fields,orig.fields,FIXED_SIZE); + if (orig.extraField) + { + extraFieldLen=orig.extraFieldLen; + extraField = new char[extraFieldLen]; + memcpy(extraField , orig.extraField , extraFieldLen); + } + if (orig.filename) + { + filenameLen=orig.filenameLen; + filename = new char[filenameLen]; + memcpy(filename , orig.filename , filenameLen); + } + if (orig.comment) + { + commentLen=orig.commentLen; + comment = new char[commentLen]; + memcpy(comment , orig.comment , commentLen); + } + + filenameLen = orig.filenameLen; + extraFieldLen = orig.extraFieldLen; + commentLen = orig.commentLen; + + sizeUncompressed = orig.sizeUncompressed; + sizeCompressed = orig.sizeCompressed; + offsetLocalHeader = orig.offsetLocalHeader; + } + + // Assignment operator + CDFileHeader& operator=(const CDFileHeader& obj) + { + XMP_Throw("not supported",kXMPErr_Unimplemented); + } + + // reads entire structure from file (starting at current position) + void read(XMP_IO* file) + { + this->release(); + + file->ReadAll ( fields, FIXED_SIZE ); + XMP_Validate( SIG == GetUns32LE( &this->fields[CDFileHeader::o_sig] ), "invalid header", kXMPErr_BadFileFormat ); + + filenameLen = GetUns16LE( &this->fields[CDFileHeader::o_fileNameLength] ); + extraFieldLen = GetUns16LE( &this->fields[CDFileHeader::o_extraFieldLength] ); + commentLen = GetUns16LE( &this->fields[CDFileHeader::o_commentLength] ); + + if (filenameLen) { + filename = new char[filenameLen]; + file->ReadAll ( filename, filenameLen ); + } + if (extraFieldLen) { + extraField = new char[extraFieldLen]; + file->ReadAll ( extraField, extraFieldLen ); + } + if (commentLen) { + comment = new char[commentLen]; + file->ReadAll ( comment, commentLen ); + } + + ////// GET ACTUAL 64 BIT VALUES ////////////////////////////////////////////// + // get 32bit goodies first, correct later + sizeUncompressed = GetUns32LE( &fields[o_sizeUncompressed] ); + sizeCompressed = GetUns32LE( &fields[o_sizeCompressed] ); + offsetLocalHeader = GetUns32LE( &fields[o_offsetLocalHeader] ); + + XMP_Int32 offset = 0; + while ( offset < extraFieldLen ) + { + XMP_Validate( (extraFieldLen - offset) >= 4, "need 4 bytes for next header ID+len", kXMPErr_BadFileFormat); + XMP_Uns16 headerID = GetUns16LE( &extraField[offset] ); + XMP_Uns16 dataSize = GetUns16LE( &extraField[offset+2] ); + offset += 4; + + XMP_Validate( (extraFieldLen - offset) <= dataSize, + "actual field lenght not given", kXMPErr_BadFileFormat); + if ( headerID == 0x1 ) //we only care about "Zip64 extended information extra field" + { + XMP_Validate( offset < extraFieldLen, "extra field too short", kXMPErr_BadFileFormat); + if (sizeUncompressed == 0xffffffff) + { + sizeUncompressed = GetUns64LE( &extraField[offset] ); + offset += 8; + } + if (sizeCompressed == 0xffffffff) + { + sizeCompressed = GetUns64LE( &extraField[offset] ); + offset += 8; + } + if (offsetLocalHeader == 0xffffffff) + { + offsetLocalHeader = GetUns64LE( &extraField[offset] ); + offset += 8; + } + } + else + { + offset += dataSize; + } // if + } // while + } // read() + + // writes structure to file (starting at current position) + void write(XMP_IO* file) + { + //// WRITE BACK REAL 64 BIT VALUES, CREATE EXTRA FIELD /////////////// + //may only wipe extra field after obtaining all Info from it + if (extraField) delete extraField; + extraFieldLen=0; + + if ( ( sizeUncompressed > 0xffffffff ) || + ( sizeCompressed > 0xffffffff ) || + ( offsetLocalHeader > 0xffffffff ) ) + { + extraField = new char[64]; // actual maxlen is 32 + extraFieldLen = 4; //first fields are for ID, size + if ( sizeUncompressed > 0xffffffff ) + { + PutUns64LE( sizeUncompressed, &extraField[extraFieldLen] ); + extraFieldLen += 8; + sizeUncompressed = 0xffffffff; + } + if ( sizeCompressed > 0xffffffff ) + { + PutUns64LE( sizeCompressed, &extraField[extraFieldLen] ); + extraFieldLen += 8; + sizeCompressed = 0xffffffff; + } + if ( offsetLocalHeader > 0xffffffff ) + { + PutUns64LE( offsetLocalHeader, &extraField[extraFieldLen] ); + extraFieldLen += 8; + offsetLocalHeader = 0xffffffff; + } + + //write ID, dataSize + PutUns16LE( 0x0001, &extraField[0] ); + PutUns16LE( extraFieldLen-4, &extraField[2] ); + //extraFieldSize + PutUns16LE( extraFieldLen, &this->fields[CDFileHeader::o_extraFieldLength] ); + } + + // write out 32-bit ('ff-stubs' or not) + PutUns32LE( (XMP_Uns32)sizeUncompressed, &fields[o_sizeUncompressed] ); + PutUns32LE( (XMP_Uns32)sizeCompressed, &fields[o_sizeCompressed] ); + PutUns32LE( (XMP_Uns32)offsetLocalHeader, &fields[o_offsetLocalHeader] ); + + /// WRITE ///////////////////////////////////////////////////////////////// + XMP_Enforce( SIG == GetUns32LE( &this->fields[CDFileHeader::o_sig] ) ); + + file ->Write ( fields , FIXED_SIZE ); + if (filenameLen) file->Write ( filename , filenameLen ); + if (extraFieldLen) file->Write ( extraField , extraFieldLen ); + if (commentLen) file->Write ( comment , commentLen ); + } + + void setXMPFilename() + { + if (filename) delete filename; + filenameLen = xmpFilenameLen; + filename = new char[xmpFilenameLen]; + PutUns16LE(filenameLen, &fields[CDFileHeader::o_fileNameLength] ); + memcpy(filename,"META-INF/metadata.xml",xmpFilenameLen); + } + + XMP_Int64 size() + { + XMP_Int64 r = this->FIXED_SIZE + this->filenameLen + this->commentLen; + // predict serialization size + if ( (sizeUncompressed > 0xffffffff)||(sizeCompressed > 0xffffffff)||(offsetLocalHeader>0xffffffff) ) + { + r += 4; //extra fields necessary + if (sizeUncompressed > 0xffffffff) r += 8; + if (sizeCompressed > 0xffffffff) r += 8; + if (offsetLocalHeader > 0xffffffff) r += 8; + } + return r; + } + + ~CDFileHeader() + { + this->release(); + }; + }; // class CDFileHeader + + class EndOfCD { + private: + const static XMP_Uns32 SIG = 0x06054b50; + void UCFECD_Free() + { + if(commentLen) delete comment; + commentLen = 0; + } + public: + const static XMP_Int32 o_Sig = 0; + const static XMP_Int32 o_CdNumEntriesDisk = 8; // same-same for UCF, since single-volume + const static XMP_Int32 o_CdNumEntriesTotal = 10;// must update both + const static XMP_Int32 o_CdSize = 12; + const static XMP_Int32 o_CdOffset = 16; + const static XMP_Int32 o_CommentLen = 20; + + const static int FIXED_SIZE = 22; + char fields[FIXED_SIZE]; + + char* comment; + XMP_Uns16 commentLen; + + EndOfCD() : comment(0), commentLen(0) + { + //nothing + }; + + void read (XMP_IO* file) + { + UCFECD_Free(); + + file->ReadAll ( fields, FIXED_SIZE ); + XMP_Validate( this->SIG == GetUns32LE( &this->fields[o_Sig] ), "invalid header", kXMPErr_BadFileFormat ); + + commentLen = GetUns16LE( &this->fields[o_CommentLen] ); + if(commentLen) + { + comment = new char[commentLen]; + file->ReadAll ( comment, commentLen ); + } + }; + + void write(XMP_IO* file) + { + XMP_Enforce( this->SIG == GetUns32LE( &this->fields[o_Sig] ) ); + commentLen = GetUns16LE( &this->fields[o_CommentLen] ); + file ->Write ( fields , FIXED_SIZE ); + if (commentLen) + file->Write ( comment, commentLen ); + } + + ~EndOfCD() + { + if (comment) delete comment; + }; + }; //class EndOfCD + + //////////////////////////////////////////////////////////////////////////////////// + // EMBEDDING MATH + // + // a = content files before xmp (always 0 thus ommited) + // b/b2 = content files behind xmp (before/after injection) + // x/x2 = offset xmp content header + content file (before/after injection) + // cd/cd = central directory + // h/h2 = end of central directory record + XMP_Int64 b,b2,x,x2,cd,cd2,cdx,cdx2,z,z2,h,h2, + // length thereof ('2' only where possibly different) + // using XMP_Int64 here also for length (not XMP_Int32), + // to be prepared for zip64, our LFA functions might need things in multiple chunks... + al,bl,xl,x2l,cdl,cd2l,cdxl,cdx2l,z2l,hl,fl,f2l; + XMP_Uns16 numCF,numCF2; + + bool wasCompressed; // ..before, false if no prior xmp + bool compressXMP; // compress this time? + bool inPlacePossible; + /* bool isZip64; <=> z2 != 0 */ + + FileHeader xmpFileHeader; + CDFileHeader xmpCDHeader; + + XMP_StringPtr uncomprPacketStr; + XMP_StringLen uncomprPacketLen; + XMP_StringPtr finalPacketStr; + XMP_StringLen finalPacketLen; + std::vector cdEntries; + EndOfCD endOfCD; + void writeOut( XMP_IO* sourceFile, XMP_IO* targetFile, bool isRewrite, bool isInPlace); + +}; // UCF_MetaHandler + +// ================================================================================================= + +#endif /* __UCF_Handler_hpp__ */ + + diff --git a/core/libs/dngwriter/extra/xmp_sdk/XMPFiles/source/FileHandlers/WAVE_Handler.cpp b/core/libs/dngwriter/extra/xmp_sdk/XMPFiles/source/FileHandlers/WAVE_Handler.cpp new file mode 100644 index 0000000000..1695013374 --- /dev/null +++ b/core/libs/dngwriter/extra/xmp_sdk/XMPFiles/source/FileHandlers/WAVE_Handler.cpp @@ -0,0 +1,516 @@ +// ================================================================================================= +// ADOBE SYSTEMS INCORPORATED +// Copyright 2010 Adobe Systems Incorporated +// All Rights Reserved +// +// NOTICE: Adobe permits you to use, modify, and distribute this file in accordance with the terms +// of the Adobe license agreement accompanying it. +// ================================================================================================= + +#include "public/include/XMP_Environment.h" // ! XMP_Environment.h must be the first included header. +#include "public/include/XMP_Const.h" + +#include "XMPFiles/source/FileHandlers/WAVE_Handler.hpp" +#include "XMPFiles/source/FormatSupport/WAVE/WAVEBehavior.h" +#include "XMPFiles/source/FormatSupport/WAVE/WAVEReconcile.h" +#include "XMPFiles/source/NativeMetadataSupport/MetadataSet.h" +#include "source/XIO.hpp" + +using namespace IFF_RIFF; + +// ================================================================================================= +/// \file WAVE_Handler.cpp +/// \brief File format handler for WAVE. +// ================================================================================================= + + +// ================================================================================================= +// WAVE_MetaHandlerCTor +// ==================== + +XMPFileHandler * WAVE_MetaHandlerCTor ( XMPFiles * parent ) +{ + return new WAVE_MetaHandler ( parent ); +} + +// ================================================================================================= +// WAVE_CheckFormat +// =============== +// +// Checks if the given file is a valid WAVE file. +// The first 12 bytes are checked. The first 4 must be "RIFF" +// Bytes 8 to 12 must be "WAVE" + +bool WAVE_CheckFormat ( XMP_FileFormat format, + XMP_StringPtr filePath, + XMP_IO* file, + XMPFiles* parent ) +{ + // Reset file pointer position + file->Rewind(); + + XMP_Uns8 buffer[12]; + XMP_Int32 got = file->Read ( buffer, 12 ); + // Reset file pointer position + file->Rewind(); + + // Need to have at least ID, size and Type of first chunk + if ( got < 12 ) + { + return false; + } + + XMP_Uns32 type = WAVE_MetaHandler::whatRIFFFormat( buffer ); + if ( type != kChunk_RIFF && type != kChunk_RF64 ) + { + return false; + } + + const BigEndian& endian = BigEndian::getInstance(); + if ( endian.getUns32(&buffer[8]) == kType_WAVE ) + { + return true; + } + + return false; +} // WAVE_CheckFormat + + +// ================================================================================================= +// WAVE_MetaHandler::whatRIFFFormat +// =============== + +XMP_Uns32 WAVE_MetaHandler::whatRIFFFormat( XMP_Uns8* buffer ) +{ + XMP_Uns32 type = 0; + + const BigEndian& endian = BigEndian::getInstance(); + + if( buffer != 0 ) + { + if( endian.getUns32( buffer ) == kChunk_RIFF ) + { + type = kChunk_RIFF; + } + else if( endian.getUns32( buffer ) == kChunk_RF64 ) + { + type = kChunk_RF64; + } + } + + return type; +} // whatRIFFFormat + + +// Static inits + +// ChunkIdentifier +// RIFF:WAVE/PMX_ +const ChunkIdentifier WAVE_MetaHandler::kRIFFXMP[2] = { { kChunk_RIFF, kType_WAVE }, { kChunk_XMP, kType_NONE} }; +// RIFF:WAVE/LIST:INFO +const ChunkIdentifier WAVE_MetaHandler::kRIFFInfo[2] = { { kChunk_RIFF, kType_WAVE }, { kChunk_LIST, kType_INFO } }; +// RIFF:WAVE/DISP +const ChunkIdentifier WAVE_MetaHandler::kRIFFDisp[2] = { { kChunk_RIFF, kType_WAVE }, { kChunk_DISP, kType_NONE } }; +// RIFF:WAVE/BEXT +const ChunkIdentifier WAVE_MetaHandler::kRIFFBext[2] = { { kChunk_RIFF, kType_WAVE }, { kChunk_bext, kType_NONE } }; +// RIFF:WAVE/cart +const ChunkIdentifier WAVE_MetaHandler::kRIFFCart[2] = { { kChunk_RIFF, kType_WAVE }, { kChunk_cart, kType_NONE } }; +// cr8r is not yet required for WAVE +// RIFF:WAVE/Cr8r +// const ChunkIdentifier WAVE_MetaHandler::kWAVECr8r[2] = { { kChunk_RIFF, kType_WAVE }, { kChunk_Cr8r, kType_NONE } }; +// RIFF:WAVE/iXML +const ChunkIdentifier WAVE_MetaHandler::kRIFFiXML[2] = { { kChunk_RIFF, kType_WAVE }, { kChunk_iXML, kType_NONE } }; +// RF64:WAVE/PMX_ +const ChunkIdentifier WAVE_MetaHandler::kRF64XMP[2] = { { kChunk_RF64, kType_WAVE }, { kChunk_XMP, kType_NONE} }; +// RF64:WAVE/LIST:INFO +const ChunkIdentifier WAVE_MetaHandler::kRF64Info[2] = { { kChunk_RF64, kType_WAVE }, { kChunk_LIST, kType_INFO } }; +// RF64:WAVE/DISP +const ChunkIdentifier WAVE_MetaHandler::kRF64Disp[2] = { { kChunk_RF64, kType_WAVE }, { kChunk_DISP, kType_NONE } }; +// RF64:WAVE/BEXT +const ChunkIdentifier WAVE_MetaHandler::kRF64Bext[2] = { { kChunk_RF64, kType_WAVE }, { kChunk_bext, kType_NONE } }; +// RF64:WAVE/cart +const ChunkIdentifier WAVE_MetaHandler::kRF64Cart[2] = { { kChunk_RF64, kType_WAVE }, { kChunk_cart, kType_NONE } }; +// cr8r is not yet required for WAVE +// RF64:WAVE/Cr8r +// const ChunkIdentifier WAVE_MetaHandler::kRF64Cr8r[2] = { { kChunk_RF64, kType_WAVE }, { kChunk_Cr8r, kType_NONE } }; +const ChunkIdentifier WAVE_MetaHandler::kRF64iXML[2] = { { kChunk_RF64, kType_WAVE }, { kChunk_iXML, kType_NONE } }; + +// ================================================================================================= +// WAVE_MetaHandler::WAVE_MetaHandler +// ================================ + +WAVE_MetaHandler::WAVE_MetaHandler ( XMPFiles * _parent ) + : mChunkBehavior(NULL), mChunkController(NULL), + mINFOMeta(), mBEXTMeta(), mCartMeta(), mDISPMeta(), miXMLMeta(), + mXMPChunk(NULL), mINFOChunk(NULL), + mBEXTChunk(NULL), mCartChunk(NULL), mDISPChunk(NULL), miXMLChunk(NULL) +{ + this->parent = _parent; + this->handlerFlags = kWAVE_HandlerFlags; + this->stdCharForm = kXMP_Char8Bit; + + this->mChunkBehavior = new WAVEBehavior(); + this->mChunkController = new ChunkController( mChunkBehavior, false ); + miXMLMeta.SetErrorCallback( &parent->errorCallback ); + +} // WAVE_MetaHandler::WAVE_MetaHandler + + +// ================================================================================================= +// WAVE_MetaHandler::~WAVE_MetaHandler +// ================================= + +WAVE_MetaHandler::~WAVE_MetaHandler() +{ + if( mChunkController != NULL ) + { + delete mChunkController; + } + + if( mChunkBehavior != NULL ) + { + delete mChunkBehavior; + } +} // WAVE_MetaHandler::~WAVE_MetaHandler + + +// ================================================================================================= +// WAVE_MetaHandler::CacheFileData +// ============================== + +void WAVE_MetaHandler::CacheFileData() +{ + // Need to determine the file type, need the first four bytes of the file + + // Reset file pointer position + this->parent->ioRef->Rewind(); + + XMP_Uns8 buffer[4]; + XMP_Int32 got = this->parent->ioRef->Read ( buffer, 4 ); + XMP_Assert( got == 4 ); + + XMP_Uns32 type = WAVE_MetaHandler::whatRIFFFormat( buffer ); + XMP_Assert( type == kChunk_RIFF || type == kChunk_RF64 ); + + // Reset file pointer position + this->parent->ioRef->Rewind(); + + // Add the relevant chunk paths for the determined RIFF format + if( type == kChunk_RIFF ) + { + mWAVEXMPChunkPath.append( kRIFFXMP, SizeOfCIArray(kRIFFXMP) ); + mWAVEInfoChunkPath.append( kRIFFInfo, SizeOfCIArray(kRIFFInfo) ); + mWAVEDispChunkPath.append( kRIFFDisp, SizeOfCIArray(kRIFFDisp) ); + mWAVEiXMLChunkPath.append( kRIFFiXML, SizeOfCIArray(kRIFFiXML) ); + mWAVEBextChunkPath.append( kRIFFBext, SizeOfCIArray(kRIFFBext) ); + mWAVECartChunkPath.append( kRIFFCart, SizeOfCIArray(kRIFFCart) ); + // cr8r is not yet required for WAVE + //mWAVECr8rChunkPath.append( kWAVECr8r, SizeOfCIArray(kWAVECr8r) ); + } + else // RF64 + { + mWAVEXMPChunkPath.append( kRF64XMP, SizeOfCIArray(kRF64XMP) ); + mWAVEInfoChunkPath.append( kRF64Info, SizeOfCIArray(kRF64Info) ); + mWAVEDispChunkPath.append( kRF64Disp, SizeOfCIArray(kRF64Disp) ); + mWAVEiXMLChunkPath.append( kRF64iXML, SizeOfCIArray(kRF64iXML) ); + mWAVEBextChunkPath.append( kRF64Bext, SizeOfCIArray(kRF64Bext) ); + mWAVECartChunkPath.append( kRF64Cart, SizeOfCIArray(kRF64Cart) ); + // cr8r is not yet required for WAVE + //mWAVECr8rChunkPath.append( kRF64Cr8r, SizeOfCIArray(kRF64Cr8r) ); + } + + mChunkController->addChunkPath( mWAVEXMPChunkPath ); + mChunkController->addChunkPath( mWAVEInfoChunkPath ); + mChunkController->addChunkPath( mWAVEDispChunkPath ); + mChunkController->addChunkPath( mWAVEiXMLChunkPath ); + mChunkController->addChunkPath( mWAVEBextChunkPath ); + mChunkController->addChunkPath( mWAVECartChunkPath ); + // cr8r is not yet required for WAVE + //mChunkController->addChunkPath( mWAVECr8rChunkPath ); + + // Parse the given file + // Throws exception if the file cannot be parsed + mChunkController->parseFile( this->parent->ioRef, &this->parent->openFlags ); + + // Retrieve the file type, it must have at least FORM:WAVE + std::vector typeList = mChunkController->getTopLevelTypes(); + + // If file is neither WAVE, throw exception + XMP_Validate( typeList.at(0) == kType_WAVE , "File is not of type WAVE", kXMPErr_BadFileFormat ); + + // Check if the file contains XMP (last if there are duplicates) + mXMPChunk = mChunkController->getChunk( mWAVEXMPChunkPath, true ); + + + // Retrieve XMP packet info + if( mXMPChunk != NULL ) + { + this->packetInfo.length = static_cast(mXMPChunk->getSize()); + this->packetInfo.charForm = kXMP_Char8Bit; + this->packetInfo.writeable = true; + + // Get actual the XMP packet + this->xmpPacket.assign ( mXMPChunk->getString( this->packetInfo.length) ); + + // set state + this->containsXMP = true; + } +} // WAVE_MetaHandler::CacheFileData + + +// ================================================================================================= +// WAVE_MetaHandler::ProcessXMP +// ============================ + +void WAVE_MetaHandler::ProcessXMP() +{ + // Must be done only once + if ( this->processedXMP ) + { + return; + } + // Set the status at start, in case something goes wrong in this method + this->processedXMP = true; + + // Parse the XMP + if ( ! this->xmpPacket.empty() ) { + + XMP_Assert ( this->containsXMP ); + + FillPacketInfo ( this->xmpPacket, &this->packetInfo ); + + this->xmpObj.ParseFromBuffer ( this->xmpPacket.c_str(), (XMP_StringLen)this->xmpPacket.size() ); + + this->containsXMP = true; + } + + // Then import native properties + MetadataSet metaSet; + WAVEReconcile recon; + + // Parse the WAVE metadata object with values + + const XMP_Uns8* buffer = NULL; // temporary buffer + XMP_Uns64 size = 0; + // Get LIST:INFO legacy chunk + mINFOChunk = mChunkController->getChunk( mWAVEInfoChunkPath, true ); + if( mINFOChunk != NULL ) + { + size = mINFOChunk->getData( &buffer ); + mINFOMeta.parse( buffer, size ); + } + + // Parse Bext legacy chunk + mBEXTChunk = mChunkController->getChunk( mWAVEBextChunkPath, true ); + if( mBEXTChunk != NULL ) + { + size = mBEXTChunk->getData( &buffer ); + mBEXTMeta.parse( buffer, size ); + } + + // Parse cart legacy chunk + mCartChunk = mChunkController->getChunk( mWAVECartChunkPath, true ); + if( mCartChunk != NULL ) + { + size = mCartChunk->getData( &buffer ); + mCartMeta.parse( buffer, size ); + } + + // Parse DISP legacy chunk + const std::vector& disps = mChunkController->getChunks( mWAVEDispChunkPath ); + + if( ! disps.empty() ) + { + for( std::vector::const_reverse_iterator iter=disps.rbegin(); iter!=disps.rend(); iter++ ) + { + size = (*iter)->getData( &buffer ); + + if( DISPMetadata::isValidDISP( buffer, size ) ) + { + mDISPChunk = (*iter); + break; + } + } + } + + if( mDISPChunk != NULL ) + { + size = mDISPChunk->getData( &buffer ); + mDISPMeta.parse( buffer, size ); + } + + //cr8r is not yet required for WAVE + //// Parse Cr8r legacy chunk + //mCr8rChunk = mChunkController->getChunk( mWAVECr8rChunkPath ); + //if( mCr8rChunk != NULL ) + //{ + // size = mCr8rChunk->getData( &buffer ); + // mCr8rMeta.parse( buffer, size ); + //} + + // Parse iXML legacy chunk + miXMLChunk = mChunkController->getChunk( mWAVEiXMLChunkPath, true ); + if( miXMLChunk != NULL ) + { + size = miXMLChunk->getData( &buffer ); + miXMLMeta.parse( buffer, size ); + } + + // app legacy to the metadata list + metaSet.append( &mINFOMeta ); + metaSet.append( &miXMLMeta ); + metaSet.append( &mBEXTMeta ); + metaSet.append( &mCartMeta ); + metaSet.append( &mDISPMeta ); + + // cr8r is not yet required for WAVE + // metaSet.append( &mCr8rMeta ); + + // Do the import + if( recon.importToXMP( this->xmpObj, metaSet ) ) + { + // Remember if anything has changed + this->containsXMP = true; + } + +} // WAVE_MetaHandler::ProcessXMP + + +// ================================================================================================= +// RIFF_MetaHandler::UpdateFile +// =========================== + +void WAVE_MetaHandler::UpdateFile ( bool doSafeUpdate ) +{ + if ( ! this->needsUpdate ) { // If needsUpdate is set then at least the XMP changed. + return; + } + + if ( doSafeUpdate ) + { + XMP_Throw ( "WAVE_MetaHandler::UpdateFile: Safe update not supported", kXMPErr_Unavailable ); + } + + // Export XMP to legacy chunks. Create/delete them if necessary + MetadataSet metaSet; + WAVEReconcile recon; + + metaSet.append( &mINFOMeta ); + metaSet.append( &miXMLMeta ); + metaSet.append( &mBEXTMeta ); + metaSet.append( &mCartMeta ); + metaSet.append( &mDISPMeta ); + + // cr8r is not yet required for WAVE + // metaSet.append( &mCr8rMeta ); + + // If anything changes, update/create/delete the legacy chunks + if( recon.exportFromXMP( metaSet, this->xmpObj ) ) + { + if ( mINFOMeta.hasChanged( )) + { + updateLegacyChunk( &mINFOChunk, kChunk_LIST, kType_INFO, mINFOMeta ); + } + + if ( mBEXTMeta.hasChanged( )) + { + updateLegacyChunk( &mBEXTChunk, kChunk_bext, kType_NONE, mBEXTMeta ); + } + + if ( mCartMeta.hasChanged( )) + { + updateLegacyChunk( &mCartChunk, kChunk_cart, kType_NONE, mCartMeta ); + } + + if ( mDISPMeta.hasChanged( )) + { + updateLegacyChunk( &mDISPChunk, kChunk_DISP, kType_NONE, mDISPMeta ); + } + + //cr8r is not yet required for WAVE + //if ( mCr8rMeta.hasChanged( )) + //{ + // updateLegacyChunk( &mCr8rChunk, kChunk_Cr8r, kType_NONE, mCr8rMeta ); + //} + + if ( miXMLMeta.hasChanged( )) + { + updateLegacyChunk( &miXMLChunk, kChunk_iXML, kType_NONE, miXMLMeta ); + } + } + + //update/create XMP chunk + if( this->containsXMP ) + { + this->xmpObj.SerializeToBuffer ( &(this->xmpPacket) ); + + if( mXMPChunk != NULL ) + { + mXMPChunk->setData( reinterpret_cast(this->xmpPacket.c_str()), this->xmpPacket.length() ); + } + else // create XMP chunk + { + mXMPChunk = mChunkController->createChunk( kChunk_XMP, kType_NONE ); + mXMPChunk->setData( reinterpret_cast(this->xmpPacket.c_str()), this->xmpPacket.length() ); + mChunkController->insertChunk( mXMPChunk ); + } + } + // XMP Packet is never completely removed from the file. + + XMP_ProgressTracker* progressTracker=this->parent->progressTracker; + // local progess tracking required because for Handlers incapable of + // kXMPFiles_CanRewrite XMPFiles call this Update method after making + // a copy of the orignal file + bool localProgressTracking=false; + if ( progressTracker != 0 ) + { + if ( ! progressTracker->WorkInProgress() ) + { + localProgressTracking = true; + progressTracker->BeginWork (); + } + } + //write tree back to file + mChunkController->writeFile( this->parent->ioRef ,progressTracker); + if ( localProgressTracking && progressTracker != 0 ) progressTracker->WorkComplete(); + + this->needsUpdate = false; // Make sure this is only called once. +} // WAVE_MetaHandler::UpdateFile + + +void WAVE_MetaHandler::updateLegacyChunk( IChunkData **chunk, XMP_Uns32 chunkID, XMP_Uns32 chunkType, IMetadata &legacyData ) +{ + // If there is a legacy value, update/create the appropriate chunk + if( ! legacyData.isEmpty() ) + { + XMP_Uns8* buffer = NULL; + XMP_Uns64 size = legacyData.serialize( &buffer ); + + if( *chunk != NULL ) + { + (*chunk)->setData( buffer, size, false ); + } + else + { + *chunk = mChunkController->createChunk( chunkID, chunkType ); + (*chunk)->setData( buffer, size, false ); + mChunkController->insertChunk( *chunk ); + } + + delete[] buffer; + } + else //delete chunk if existing + { + mChunkController->removeChunk ( *chunk ); + } +}//updateLegacyChunk + + +// ================================================================================================= +// RIFF_MetaHandler::WriteFile +// ========================== + +void WAVE_MetaHandler::WriteTempFile ( XMP_IO* tempRef ) +{ + XMP_Throw( "WAVE_MetaHandler::WriteTempFile is not Implemented!", kXMPErr_Unimplemented ); +}//WAVE_MetaHandler::WriteFile diff --git a/core/libs/dngwriter/extra/xmp_sdk/XMPFiles/source/FileHandlers/WAVE_Handler.hpp b/core/libs/dngwriter/extra/xmp_sdk/XMPFiles/source/FileHandlers/WAVE_Handler.hpp new file mode 100644 index 0000000000..29c1c866a8 --- /dev/null +++ b/core/libs/dngwriter/extra/xmp_sdk/XMPFiles/source/FileHandlers/WAVE_Handler.hpp @@ -0,0 +1,179 @@ +// ================================================================================================= +// ADOBE SYSTEMS INCORPORATED +// Copyright 2010 Adobe Systems Incorporated +// All Rights Reserved +// +// NOTICE: Adobe permits you to use, modify, and distribute this file in accordance with the terms +// of the Adobe license agreement accompanying it. +// ================================================================================================= + +#ifndef __WAVE_Handler_hpp__ +#define __WAVE_Handler_hpp__ 1 + +#include "public/include/XMP_Environment.h" // ! XMP_Environment.h must be the first included header. +#include "public/include/XMP_Const.h" + +#include "XMPFiles/source/FormatSupport/IFF/ChunkController.h" +#include "XMPFiles/source/FormatSupport/IFF/IChunkBehavior.h" +#include "XMPFiles/source/FormatSupport/IFF/IChunkData.h" +#include "source/Endian.h" +#include "XMPFiles/source/FormatSupport/IFF/ChunkPath.h" +#include "XMPFiles/source/FormatSupport/WAVE/iXMLMetadata.h" +#include "XMPFiles/source/FormatSupport/WAVE/BEXTMetadata.h" +#include "XMPFiles/source/FormatSupport/WAVE/CartMetadata.h" +#include "XMPFiles/source/FormatSupport/WAVE/DISPMetadata.h" +#include "XMPFiles/source/FormatSupport/WAVE/INFOMetadata.h" +#include "source/XIO.hpp" +#include "XMPFiles/source/XMPFiles_Impl.hpp" + +using namespace IFF_RIFF; + +// ================================================================================================= +/// \file WAV_Handler.hpp +/// \brief File format handler for AIFF. +// ================================================================================================= + +/** + * Contructor for the handler. + */ +extern XMPFileHandler * WAVE_MetaHandlerCTor ( XMPFiles * parent ); + +/** + * Checks the format of the file, see common code. + */ +extern bool WAVE_CheckFormat ( XMP_FileFormat format, + XMP_StringPtr filePath, + XMP_IO* fileRef, + XMPFiles * parent ); + +/** WAVE does not need kXMPFiles_CanRewrite as we can always use UpdateFile to either do + * in-place update or append to the file. */ +static const XMP_OptionBits kWAVE_HandlerFlags = (kXMPFiles_CanInjectXMP | + kXMPFiles_CanExpand | + kXMPFiles_PrefersInPlace | + kXMPFiles_CanReconcile | + kXMPFiles_ReturnsRawPacket | + kXMPFiles_AllowsSafeUpdate | + kXMPFiles_CanNotifyProgress + ); + +/** + * Main class for the the WAVE file handler. + */ +class WAVE_MetaHandler : public XMPFileHandler +{ +public: + WAVE_MetaHandler ( XMPFiles* parent ); + ~WAVE_MetaHandler(); + + void CacheFileData(); + void ProcessXMP(); + + void UpdateFile ( bool doSafeUpdate ); + void WriteTempFile ( XMP_IO* tempRef ); + + /** + * Checks if the first 4 bytes of the given buffer are either type RIFF or RF64 + * @param buffer a byte buffer that must contain at least 4 bytes and point to the correct byte + * @return Either kChunk_RIFF, kChunk_RF64 0 if no type could be determined + */ + static XMP_Uns32 whatRIFFFormat( XMP_Uns8* buffer ); + +private: + /** + * Updates/creates/deletes a given legacy chunk depending on the given new legacy value + * If the Chunk exists and is not empty, it is updated. If it is empty the + * Chunk is removed from the tree. If the Chunk does not exist but a value is given, it is created + * and initialized with that value + * + * @param chunk OUT pointer to the legacy chunk + * @param chunkID Id of the Chunk if it needs to be created + * @param chunkType Type of the Chunk if it needs to be created + * @param legacyData the new legacy metadata object (can be empty) + */ + void updateLegacyChunk( IChunkData **chunk, XMP_Uns32 chunkID, XMP_Uns32 chunkType, IMetadata &legacyData ); + + + /** private standard Ctor, not to be used */ + WAVE_MetaHandler (): mChunkController(NULL), mChunkBehavior(NULL), + mINFOMeta(), mBEXTMeta(), mCartMeta(), mDISPMeta(), + mXMPChunk(NULL), mINFOChunk(NULL), + mBEXTChunk(NULL), mCartChunk(NULL), mDISPChunk(NULL) {}; + + // ----- MEMBERS ----- // + + /** Controls the parsing and writing of the passed stream. */ + ChunkController *mChunkController; + /** Represents the rules how chunks are added, removed or rearranged */ + IChunkBehavior *mChunkBehavior; + /** container for Legacy metadata */ + INFOMetadata mINFOMeta; + BEXTMetadata mBEXTMeta; + CartMetadata mCartMeta; + DISPMetadata mDISPMeta; + iXMLMetadata miXMLMeta; + + // cr8r is not yet required for WAVE + // Cr8rMetadata mCr8rMeta; + + /** pointer to the XMP chunk */ + IChunkData *mXMPChunk; + /** pointer to legacy chunks */ + IChunkData *mINFOChunk; + IChunkData *mBEXTChunk; + IChunkData *mCartChunk; + IChunkData *mDISPChunk; + IChunkData *miXMLChunk; + + // cr8r is not yet required for WAVE + // IChunkData *mCr8rChunk; + + // ----- CONSTANTS ----- // + + /** Chunk path identifier of interest in WAVE */ + static const ChunkIdentifier kRIFFXMP[2]; + static const ChunkIdentifier kRIFFInfo[2]; + static const ChunkIdentifier kRIFFDisp[2]; + static const ChunkIdentifier kRIFFBext[2]; + static const ChunkIdentifier kRIFFCart[2]; + static const ChunkIdentifier kRIFFiXML[2]; + // cr8r is not yet required for WAVE + // static const ChunkIdentifier kWAVECr8r[2]; + + /** Chunk path identifier of interest in RF64 */ + static const ChunkIdentifier kRF64XMP[2]; + static const ChunkIdentifier kRF64Info[2]; + static const ChunkIdentifier kRF64Disp[2]; + static const ChunkIdentifier kRF64Bext[2]; + static const ChunkIdentifier kRF64Cart[2]; + static const ChunkIdentifier kRF64iXML[2]; + // cr8r is not yet required for WAVE + // static const ChunkIdentifier kRF64Cr8r[2]; + + /** Path to XMP chunk */ + ChunkPath mWAVEXMPChunkPath; + + /** Path to INFO chunk */ + ChunkPath mWAVEInfoChunkPath; + + /** Path to DISP chunk */ + ChunkPath mWAVEDispChunkPath; + + /** Path to BEXT chunk */ + ChunkPath mWAVEBextChunkPath; + + /** Path to cart chunk */ + ChunkPath mWAVECartChunkPath; + + /** Path to IXML chunk */ + ChunkPath mWAVEiXMLChunkPath; + + //cr8r is not yet required for WAVE + ///** Path to Cr8r chunk */ + //const ChunkPath mWAVECr8rChunkPath; + +}; // WAVE_MetaHandler + +// ================================================================================================= + +#endif /* __WAVE_Handler_hpp__ */ diff --git a/core/libs/dngwriter/extra/xmp_sdk/XMPFiles/source/FileHandlers/XDCAMEX_Handler.cpp b/core/libs/dngwriter/extra/xmp_sdk/XMPFiles/source/FileHandlers/XDCAMEX_Handler.cpp new file mode 100644 index 0000000000..e53692eb66 --- /dev/null +++ b/core/libs/dngwriter/extra/xmp_sdk/XMPFiles/source/FileHandlers/XDCAMEX_Handler.cpp @@ -0,0 +1,1046 @@ +// ================================================================================================= +// ADOBE SYSTEMS INCORPORATED +// Copyright 2008 Adobe Systems Incorporated +// All Rights Reserved +// +// NOTICE: Adobe permits you to use, modify, and distribute this file in accordance with the terms +// of the Adobe license agreement accompanying it. +// ================================================================================================= + +#include "public/include/XMP_Environment.h" // ! XMP_Environment.h must be the first included header. + +#include "public/include/XMP_Const.h" +#include "public/include/XMP_IO.hpp" + +#include "XMPFiles/source/XMPFiles_Impl.hpp" +#include "source/XMPFiles_IO.hpp" +#include "source/XIO.hpp" +#include "source/IOUtils.hpp" + +#include "XMPFiles/source/FileHandlers/XDCAMEX_Handler.hpp" +#include "XMPFiles/source/FormatSupport/XDCAM_Support.hpp" +#include "XMP_MD5.h" +#include "XMPFiles/source/FormatSupport/PackageFormat_Support.hpp" + +using namespace std; + +// ================================================================================================= +/// \file XDCAMEX_Handler.cpp +/// \brief Folder format handler for XDCAMEX. +/// +/// This handler is for the XDCAMEX video format. +/// +/// .../MyMovie/ +/// BPAV/ +/// MEDIAPRO.XML +/// MEDIAPRO.BUP +/// CLPR/ +/// 709_001_01/ +/// 709_001_01.SMI +/// 709_001_01.MP4 +/// 709_001_01M01.XML +/// 709_001_01R01.BIM +/// 709_001_01I01.PPN +/// 709_001_02/ +/// 709_002_01/ +/// 709_003_01/ +/// TAKR/ +/// 709_001/ +/// 709_001.SMI +/// 709_001M01.XML +/// +/// The Backup files (.BUP) are optional. No files or directories other than those listed are +/// allowed in the BPAV directory. The CLPR (clip root) directory may contain only clip directories, +/// which may only contain the clip files listed. The TAKR (take root) direcory may contail only +/// take directories, which may only contain take files. The take root directory can be empty. +/// MEDIPRO.XML contains information on clip and take management. +/// +/// Each clip directory contains a media file (.MP4), a clip info file (.SMI), a real time metadata +/// file (.BIM), a non real time metadata file (.XML), and a picture pointer file (.PPN). A take +/// directory conatins a take info and non real time take metadata files. +// ================================================================================================= + +// ================================================================================================= +// XDCAMEX_CheckFormat +// =================== +// +// This version checks for the presence of a top level BPAV directory, and the required files and +// directories immediately within it. The CLPR and TAKR subfolders are required, as is MEDIAPRO.XML. +// +// The state of the string parameters depends on the form of the path passed by the client. If the +// client passed a logical clip path, like ".../MyMovie/012_3456_01", the parameters are: +// rootPath - ".../MyMovie" +// gpName - empty +// parentName - empty +// leafName - "012_3456_01" +// If the client passed a full file path, like ".../MyMovie/BPAV/CLPR/012_3456_01/012_3456_01M01.XML", they are: +// rootPath - ".../MyMovie/BPAV" +// gpName - "CLPR" +// parentName - "012_3456_01" +// leafName - "012_3456_01M01" + +// ! The common code has shifted the gpName, parentName, and leafName strings to upper case. It has +// ! also made sure that for a logical clip path the rootPath is an existing folder, and that the +// ! file exists for a full file path. + +// ! Using explicit '/' as a separator when creating paths, it works on Windows. + +bool XDCAMEX_CheckFormat ( XMP_FileFormat format, + const std::string & _rootPath, + const std::string & gpName, + const std::string & parentName, + const std::string & _leafName, + XMPFiles * parent ) +{ + std::string rootPath = _rootPath; + std::string clipName = _leafName; + std::string grandGPName; + + std::string bpavPath ( rootPath ); + + // Do some initial checks on the gpName and parentName. + + if ( gpName.empty() != parentName.empty() ) return false; // Must be both empty or both non-empty. + + if ( gpName.empty() ) { + + // This is the logical clip path case. Make sure .../MyMovie/BPAV/CLPR is a folder. + bpavPath += kDirChar; // The rootPath was just ".../MyMovie". + bpavPath += "BPAV"; + if ( Host_IO::GetChildMode ( bpavPath.c_str(), "CLPR" ) != Host_IO::kFMode_IsFolder ) return false; + + } else { + + // This is the explicit file case. Make sure the ancestry is OK, compare using the parent's + // length since the file can have a suffix like "M01". Use the leafName as the clipName to + // preserve lower case, but truncate to the parent's length to remove any suffix. + + if ( gpName != "CLPR" ) return false; + XIO::SplitLeafName ( &rootPath, &grandGPName ); + MakeUpperCase ( &grandGPName ); + if ( grandGPName != "BPAV" ) return false; + + if ( ! XMP_LitNMatch ( parentName.c_str(), clipName.c_str(), parentName.size() ) ) { + std::string tempName = clipName; + MakeUpperCase ( &tempName ); + if ( ! XMP_LitNMatch ( parentName.c_str(), tempName.c_str(), parentName.size() ) ) return false; + } + + clipName.erase ( parentName.size() ); + + } + + // Check the rest of the required general structure. + if ( Host_IO::GetChildMode ( bpavPath.c_str(), "TAKR" ) != Host_IO::kFMode_IsFolder ) return false; + if ( Host_IO::GetChildMode ( bpavPath.c_str(), "MEDIAPRO.XML" ) != Host_IO::kFMode_IsFile ) return false; + + // Make sure the clip's .MP4 and .SMI files exist. + std::string tempPath ( bpavPath ); + tempPath += kDirChar; + tempPath += "CLPR"; + tempPath += kDirChar; + tempPath += clipName; + tempPath += kDirChar; + tempPath += clipName; + tempPath += ".MP4"; + if ( Host_IO::GetFileMode ( tempPath.c_str() ) != Host_IO::kFMode_IsFile ) return false; + tempPath.erase ( tempPath.size()-3 ); + tempPath += "SMI"; + if ( Host_IO::GetFileMode ( tempPath.c_str() ) != Host_IO::kFMode_IsFile ) return false; + + // And now save the psuedo path for the handler object. + tempPath = rootPath; + tempPath += kDirChar; + tempPath += clipName; + size_t pathLen = tempPath.size() + 1; // Include a terminating nul. + parent->tempPtr = malloc ( pathLen ); + if ( parent->tempPtr == 0 ) XMP_Throw ( "No memory for XDCAMEX clip info", kXMPErr_NoMemory ); + memcpy ( parent->tempPtr, tempPath.c_str(), pathLen ); + + return true; + +} // XDCAMEX_CheckFormat + +// ================================================================================================= + +static void* CreatePseudoClipPath ( const std::string & clientPath ) +{ + + // Used to create the clip pseudo path when the CheckFormat function is skipped. + + std::string pseudoPath = clientPath; + + size_t pathLen; + void* tempPtr = 0; + + if ( Host_IO::Exists ( pseudoPath.c_str() ) ) { + + // The client passed a physical path. The logical clip name is the last folder name, the + // parent of the file. This is best since some files have suffixes. + + std::string clipName, ignored; + + XIO::SplitLeafName ( &pseudoPath, &ignored ); // Split the file name. + XIO::SplitLeafName ( &pseudoPath, &clipName ); // Use the parent folder name. + + XIO::SplitLeafName ( &pseudoPath, &ignored ); // Remove the 2 intermediate folder levels. + XIO::SplitLeafName ( &pseudoPath, &ignored ); + + pseudoPath += kDirChar; + pseudoPath += clipName; + + } + + pathLen = pseudoPath.size() + 1; // Include a terminating nul. + tempPtr = malloc ( pathLen ); + if ( tempPtr == 0 ) XMP_Throw ( "No memory for XDCAMEX clip info", kXMPErr_NoMemory ); + memcpy ( tempPtr, pseudoPath.c_str(), pathLen ); + + return tempPtr; + +} // CreatePseudoClipPath + +// ================================================================================================= +// XDCAMEX_MetaHandlerCTor +// ======================= + +XMPFileHandler * XDCAMEX_MetaHandlerCTor ( XMPFiles * parent ) +{ + return new XDCAMEX_MetaHandler ( parent ); + +} // XDCAMEX_MetaHandlerCTor + +// ================================================================================================= +// XDCAMEX_MetaHandler::XDCAMEX_MetaHandler +// ======================================== + +XDCAMEX_MetaHandler::XDCAMEX_MetaHandler ( XMPFiles * _parent ) : expat(0),clipMetadata(0) +{ + this->parent = _parent; // Inherited, can't set in the prefix. + this->handlerFlags = kXDCAMEX_HandlerFlags; + this->stdCharForm = kXMP_Char8Bit; + + // Extract the root path and clip name from tempPtr. + + if ( this->parent->tempPtr == 0 ) { + // The CheckFormat call might have been skipped. + this->parent->tempPtr = CreatePseudoClipPath ( this->parent->GetFilePath() ); + } + + this->rootPath.assign ( (char*) this->parent->tempPtr ); + free ( this->parent->tempPtr ); + this->parent->tempPtr = 0; + + XIO::SplitLeafName ( &this->rootPath, &this->clipName ); + +} // XDCAMEX_MetaHandler::XDCAMEX_MetaHandler + +// ================================================================================================= +// XDCAMEX_MetaHandler::~XDCAMEX_MetaHandler +// ========================================= + +XDCAMEX_MetaHandler::~XDCAMEX_MetaHandler() +{ + + this->CleanupLegacyXML(); + if ( this->parent->tempPtr != 0 ) { + free ( this->parent->tempPtr ); + this->parent->tempPtr = 0; + } + +} // XDCAMEX_MetaHandler::~XDCAMEX_MetaHandler + +// ================================================================================================= +// XDCAMEX_MetaHandler::MakeClipFilePath +// ===================================== + +bool XDCAMEX_MetaHandler::MakeClipFilePath ( std::string * path, XMP_StringPtr suffix, bool checkFile /* = false */ ) +{ + + *path = this->rootPath; + *path += kDirChar; + *path += "BPAV"; + *path += kDirChar; + *path += "CLPR"; + *path += kDirChar; + *path += this->clipName; + *path += kDirChar; + *path += this->clipName; + *path += suffix; + + if ( ! checkFile ) return true; + return Host_IO::Exists ( path->c_str() ); + +} // XDCAMEX_MetaHandler::MakeClipFilePath + +// ================================================================================================= +// XDCAMEX_MetaHandler::MakeMediaproPath +// ===================================== + +bool XDCAMEX_MetaHandler::MakeMediaproPath ( std::string * path, bool checkFile /* = false */ ) +{ + + *path = this->rootPath; + *path += kDirChar; + *path += "BPAV"; + *path += kDirChar; + *path += "MEDIAPRO.XML"; + + if ( ! checkFile ) return true; + return Host_IO::Exists ( path->c_str() ); + +} // XDCAMEX_MetaHandler::MakeMediaproPath + +// ================================================================================================= +// XDCAMEX_MetaHandler::MakeLegacyDigest +// ===================================== + +// *** Early hack version. + +#define kHexDigits "0123456789ABCDEF" + +void XDCAMEX_MetaHandler::MakeLegacyDigest ( std::string * digestStr ) +{ + digestStr->erase(); + if ( this->clipMetadata == 0 ) return; // Bail if we don't have any legacy XML. + XMP_Assert ( this->expat != 0 ); + + XMP_StringPtr xdcNS = this->xdcNS.c_str(); + XML_NodePtr legacyContext, legacyProp; + + legacyContext = this->clipMetadata->GetNamedElement ( xdcNS, "Access" ); + if ( legacyContext == 0 ) return; + + MD5_CTX context; + unsigned char digestBin [16]; + MD5Init ( &context ); + + legacyProp = legacyContext->GetNamedElement ( xdcNS, "Creator" ); + if ( (legacyProp != 0) && legacyProp->IsLeafContentNode() && (! legacyProp->content.empty()) ) { + const XML_Node * xmlValue = legacyProp->content[0]; + MD5Update ( &context, (XMP_Uns8*)xmlValue->value.c_str(), (unsigned int)xmlValue->value.size() ); + } + + legacyProp = legacyContext->GetNamedElement ( xdcNS, "CreationDate" ); + if ( (legacyProp != 0) && legacyProp->IsLeafContentNode() && (! legacyProp->content.empty()) ) { + const XML_Node * xmlValue = legacyProp->content[0]; + MD5Update ( &context, (XMP_Uns8*)xmlValue->value.c_str(), (unsigned int)xmlValue->value.size() ); + } + + legacyProp = legacyContext->GetNamedElement ( xdcNS, "LastUpdateDate" ); + if ( (legacyProp != 0) && legacyProp->IsLeafContentNode() && (! legacyProp->content.empty()) ) { + const XML_Node * xmlValue = legacyProp->content[0]; + MD5Update ( &context, (XMP_Uns8*)xmlValue->value.c_str(), (unsigned int)xmlValue->value.size() ); + } + + MD5Final ( digestBin, &context ); + + char buffer [40]; + for ( int in = 0, out = 0; in < 16; in += 1, out += 2 ) { + XMP_Uns8 byte = digestBin[in]; + buffer[out] = kHexDigits [ byte >> 4 ]; + buffer[out+1] = kHexDigits [ byte & 0xF ]; + } + buffer[32] = 0; + digestStr->append ( buffer ); + +} // XDCAMEX_MetaHandler::MakeLegacyDigest + +// ================================================================================================= +// XDCAMEX_MetaHandler::CleanupLegacyXML +// ===================================== + +void XDCAMEX_MetaHandler::CleanupLegacyXML() +{ + + delete this->expat; + this->expat = 0; + + clipMetadata = 0; // ! Was a pointer into the expat tree. + +} // XDCAMEX_MetaHandler::CleanupLegacyXML + +// ================================================================================================= +// XDCAMEX_MetaHandler::GetFileModDate +// =================================== + +static inline bool operator< ( const XMP_DateTime & left, const XMP_DateTime & right ) { + int compare = SXMPUtils::CompareDateTime ( left, right ); + return (compare < 0); +} + +bool XDCAMEX_MetaHandler::GetFileModDate ( XMP_DateTime * modDate ) +{ + + // The XDCAM EX locations of metadata: + // BPAV/ + // MEDIAPRO.XML // Has non-XMP metadata. + // CLPR/ + // 709_3001_01: + // 709_3001_01M01.XML // Has non-XMP metadata. + // 709_3001_01M01.XMP + + bool ok, haveDate = false; + std::string fullPath; + XMP_DateTime oneDate, junkDate; + if ( modDate == 0 ) modDate = &junkDate; + + ok = this->MakeMediaproPath ( &fullPath, true /* checkFile */ ); + if ( ok ) ok = Host_IO::GetModifyDate ( fullPath.c_str(), &oneDate ); + if ( ok ) { + if ( *modDate < oneDate ) *modDate = oneDate; + haveDate = true; + } + + ok = this->MakeClipFilePath ( &fullPath, "M01.XML", true /* checkFile */ ); + if ( ok ) ok = Host_IO::GetModifyDate ( fullPath.c_str(), &oneDate ); + if ( ok ) { + if ( (! haveDate) || (*modDate < oneDate) ) *modDate = oneDate; + haveDate = true; + } + + ok = this->MakeClipFilePath ( &fullPath, "M01.XMP", true /* checkFile */ ); + if ( ok ) ok = Host_IO::GetModifyDate ( fullPath.c_str(), &oneDate ); + if ( ok ) { + if ( (! haveDate) || (*modDate < oneDate) ) *modDate = oneDate; + haveDate = true; + } + + return haveDate; + +} // XDCAMEX_MetaHandler::GetFileModDate + +// Adds all the associated resources for the specified clip only (not related spanned ones) +static void FillClipAssociatedResources( std::vector * resourceList, std::string &clipPath, std::string &clipName ) +{ + std::string filePath; + std::string spannedClipFolderPath = clipPath + clipName + kDirChar; + + std::string clipPathNoExt = spannedClipFolderPath + clipName; + // Get the files present inside clip folder. + std::vector regExpStringVec; + std::string regExpString; + regExpString = "^" + clipName + ".MP4$"; + regExpStringVec.push_back(regExpString); + regExpString = "^" + clipName + "M\\d\\d.XMP$"; + regExpStringVec.push_back(regExpString); + regExpString = "^" + clipName + "M\\d\\d.XML$"; + regExpStringVec.push_back(regExpString); + regExpString = "^" + clipName + "I\\d\\d.PPN$"; + regExpStringVec.push_back(regExpString); + regExpString = "^" + clipName + "R\\d\\d.BIM$"; + regExpStringVec.push_back(regExpString); + regExpString = "^" + clipName + ".SMI$"; + regExpStringVec.push_back(regExpString); + + IOUtils::GetMatchingChildren (*resourceList, spannedClipFolderPath, regExpStringVec, false, true, true ); + +} + + +// ================================================================================================= +// XDCAMEX_MetaHandler::FillAssociatedResources +// ====================================== +void XDCAMEX_MetaHandler::FillAssociatedResources ( std::vector * resourceList ) +{ + // The possible associated resources: + // BPAV/ + // MEDIAPRO.XML + // CUEUP.XML + // CLPR/ + // MIXXXX_YY: MI is MachineID, XXXX is TakeSerial, + // YY is ClipSuffix(as single take can be divided across multiple clips.) + // In case of spanning, all the clip folders starting from "MIXXXX_" are looked for. + // MIXXXX_YY.MP4 + // MIXXXX_YYMNN.XML NN is a counter which will start from from 01 and can go upto 99 based + // on number of files present in this folder with same extension. + // MIXXXX_YYMNN.XMP + // MIXXXX_YYINN.PPN + // MIXXXX_YYRNN.BIM + // MXXXX_YY.SMI + // TAKR/ + // MIXXXX: + // MIXXXXMNN.XML NN is a counter which will start from from 01 and can go upto 99 based + // on number of files present in this folder with same extension. + // MIXXXX.SMI + // MIXXXXUNN.SMI NN is a counter which goes from 01 to N-1 where N is number of media, this + // take is divided into. For Nth, MIXXXX.SMI shall be picked up. + XMP_VarString bpavPath = this->rootPath + kDirChar + "BPAV" + kDirChar; + XMP_VarString filePath; + //Add RootPath + filePath = this->rootPath + kDirChar; + PackageFormat_Support::AddResourceIfExists ( resourceList, filePath ); + + // Get the files present directly inside BPAV folder. + filePath = bpavPath + "MEDIAPRO.XML"; + PackageFormat_Support::AddResourceIfExists ( resourceList, filePath ); + filePath = bpavPath + "MEDIAPRO.BUP"; + PackageFormat_Support::AddResourceIfExists ( resourceList, filePath ); + filePath = bpavPath + "CUEUP.XML"; + PackageFormat_Support::AddResourceIfExists ( resourceList, filePath ); + filePath = bpavPath + "CUEUP.BUP"; + PackageFormat_Support::AddResourceIfExists ( resourceList, filePath ); + + XMP_VarString clipPath = bpavPath + "CLPR" + kDirChar; + size_t clipSuffixIndex = this->clipName.find_last_of('_'); + XMP_VarString takeName = this->clipName.substr(0, clipSuffixIndex); + + // Add spanned clip files. + // Here, we iterate over all the folders present inside "/BPAV/CLPR/" and whose name starts from + // "MIXXXX_". All valid files present inside such folders are added to the list. + XMP_VarString regExpString; + regExpString = "^" + takeName + "_\\d\\d$"; + XMP_StringVector list; + + IOUtils::GetMatchingChildren ( list, clipPath, regExpString, true, false, false ); + size_t spaningClipsCount = list.size(); + for ( size_t index = 0; index < spaningClipsCount; index++ ) { + FillClipAssociatedResources ( resourceList, clipPath, list[index] ); + } + list.clear(); + + size_t sizeWithoutTakeFiles = resourceList->size(); + XMP_VarString takeFolderPath = bpavPath + "TAKR" + kDirChar + takeName + kDirChar; + XMP_StringVector regExpStringVec; + + // Get the files present inside take folder. + regExpString = "^" + takeName + "M\\d\\d.XML$"; + regExpStringVec.push_back ( regExpString ); + regExpString = "^" + takeName + "U\\d\\d.SMI$"; + regExpStringVec.push_back ( regExpString ); + regExpString = "^" + takeName + ".SMI$"; + regExpStringVec.push_back ( regExpString ); + IOUtils::GetMatchingChildren ( *resourceList, takeFolderPath, regExpStringVec, false, true, true ); + + if ( sizeWithoutTakeFiles == resourceList->size() ) + { + // no Take files added to resource list. But "TAKR" folder is necessary to recognize this format + // so let's add it to the list. + filePath = bpavPath + "TAKR" + kDirChar; + PackageFormat_Support::AddResourceIfExists(resourceList, filePath); + } +} // XDCAMEX_MetaHandler::FillAssociatedResources + +// ================================================================================================= +// XDCAMEX_MetaHandler::FillMetadataFiles +// ====================================== +void XDCAMEX_MetaHandler::FillMetadataFiles ( std::vector * metadataFiles ) +{ + std::string noExtPath, filePath; + + noExtPath = rootPath + kDirChar + "BPAV" + kDirChar + "CLPR" + + kDirChar + clipName + kDirChar + clipName; + + filePath = noExtPath + "M01.XMP"; + metadataFiles->push_back ( filePath ); + filePath = noExtPath + "M01.XML"; + metadataFiles->push_back ( filePath ); + filePath = rootPath + kDirChar + "BPAV" + kDirChar + "MEDIAPRO.XML"; + metadataFiles->push_back ( filePath ); + +} // FillMetadataFiles_XDCAM_EX + +// ================================================================================================= +// XDCAMEX_MetaHandler::IsMetadataWritable +// ======================================= + +bool XDCAMEX_MetaHandler::IsMetadataWritable ( ) +{ + std::vector metadataFiles; + FillMetadataFiles(&metadataFiles); + std::vector::iterator itr = metadataFiles.begin(); + // Check whether sidecar is writable, if not then check if it can be created. + XMP_Bool xmpWritable = Host_IO::Writable( itr->c_str(), true ); + // Check for legacy metadata file. + XMP_Bool xmlWritable = Host_IO::Writable( (++itr)->c_str(), false ); + return ( xmlWritable && xmpWritable ); +}// XDCAMEX_MetaHandler::IsMetadataWritable + +// ================================================================================================= +// XDCAMEX_MetaHandler::CacheFileData +// ================================== + +void XDCAMEX_MetaHandler::CacheFileData() +{ + XMP_Assert ( ! this->containsXMP ); + + if ( this->parent->UsesClientIO() ) { + XMP_Throw ( "XDCAMEX cannot be used with client-managed I/O", kXMPErr_InternalFailure ); + } + + // See if the clip's .XMP file exists. + + std::string xmpPath; + this->MakeClipFilePath ( &xmpPath, "M01.XMP" ); + if ( ! Host_IO::Exists ( xmpPath.c_str() ) ) return; // No XMP. + + // Read the entire .XMP file. We know the XMP exists, New_XMPFiles_IO is supposed to return 0 + // only if the file does not exist. + + bool readOnly = XMP_OptionIsClear ( this->parent->openFlags, kXMPFiles_OpenForUpdate ); + + XMP_Assert ( this->parent->ioRef == 0 ); + XMPFiles_IO* xmpFile = XMPFiles_IO::New_XMPFiles_IO ( xmpPath.c_str(), readOnly ); + if ( xmpFile == 0 ) XMP_Throw ( "XDCAMEX XMP file open failure", kXMPErr_InternalFailure ); + this->parent->ioRef = xmpFile; + + XMP_Int64 xmpLen = xmpFile->Length(); + if ( xmpLen > 100*1024*1024 ) { + XMP_Throw ( "XDCAMEX XMP is outrageously large", kXMPErr_InternalFailure ); // Sanity check. + } + + this->xmpPacket.erase(); + this->xmpPacket.append ( (size_t)xmpLen, ' ' ); + + XMP_Int32 ioCount = xmpFile->ReadAll ( (void*)this->xmpPacket.data(), (XMP_Int32)xmpLen ); + + this->packetInfo.offset = 0; + this->packetInfo.length = (XMP_Int32)xmpLen; + FillPacketInfo ( this->xmpPacket, &this->packetInfo ); + + this->containsXMP = true; + +} // XDCAMEX_MetaHandler::CacheFileData + +// ================================================================================================= +// XDCAMEX_MetaHandler::GetTakeDuration +// ==================================== + +void XDCAMEX_MetaHandler::GetTakeDuration ( const std::string & takeURI, std::string & duration ) +{ + + // Some versions of gcc can't tolerate goto's across declarations. + // *** Better yet, avoid this cruft with self-cleaning objects. + #define CleanupAndExit \ + { \ + delete expatMediaPro; \ + takeXMLFile.Close(); \ + return; \ + } + + duration.clear(); + + // Build a directory string to the take .xml file. + + std::string takeDir ( takeURI ); + takeDir.erase ( 0, 1 ); // Change the leading "//" to "/", then all '/' to kDirChar. +#if XMP_MacBuild +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wunreachable-code" +#endif + if ( kDirChar != '/' ) { + for ( size_t i = 0, limit = takeDir.size(); i < limit; ++i ) { + if ( takeDir[i] == '/' ) takeDir[i] = kDirChar; + } + } +#if XMP_MacBuild +#pragma clang diagnostic pop +#endif + std::string takePath ( this->rootPath ); + takePath += kDirChar; + takePath += "BPAV"; + takePath += takeDir; + + // Replace .SMI with M01.XML. + if ( takePath.size() > 4 ) { + takePath.erase ( takePath.size() - 4, 4 ); + takePath += "M01.XML"; + } + + // Parse MEDIAPRO.XML + + XML_NodePtr takeRootElem = 0; + XML_NodePtr context = 0; + + Host_IO::FileRef hostRef = Host_IO::Open ( takePath.c_str(), Host_IO::openReadOnly ); + if ( hostRef == Host_IO::noFileRef ) return; // The open failed. + XMPFiles_IO takeXMLFile ( hostRef, takePath.c_str(), Host_IO::openReadOnly ); + + ExpatAdapter * expatMediaPro = XMP_NewExpatAdapter ( ExpatAdapter::kUseLocalNamespaces ); + if ( expatMediaPro == 0 ) return; + + XMP_Uns8 buffer [64*1024]; + while ( true ) { + XMP_Int32 ioCount = takeXMLFile.Read ( buffer, sizeof(buffer) ); + if ( ioCount == 0 ) break; + expatMediaPro->ParseBuffer ( buffer, ioCount, false /* not the end */ ); + } + + expatMediaPro->ParseBuffer ( 0, 0, true ); // End the parse. + takeXMLFile.Close(); + + // Get the root node of the XML tree. + + XML_Node & mediaproXMLTree = expatMediaPro->tree; + for ( size_t i = 0, limit = mediaproXMLTree.content.size(); i < limit; ++i ) { + if ( mediaproXMLTree.content[i]->kind == kElemNode ) { + takeRootElem = mediaproXMLTree.content[i]; + } + } + if ( takeRootElem == 0 ) CleanupAndExit + + XMP_StringPtr rlName = takeRootElem->name.c_str() + takeRootElem->nsPrefixLen; + if ( ! XMP_LitMatch ( rlName, "NonRealTimeMeta" ) ) CleanupAndExit + + // MediaProfile, Contents + XMP_StringPtr ns = takeRootElem->ns.c_str(); + context = takeRootElem->GetNamedElement ( ns, "Duration" ); + if ( context != 0 ) { + XMP_StringPtr durationValue = context->GetAttrValue ( "value" ); + if ( durationValue != 0 ) duration = durationValue; + } + + CleanupAndExit + #undef CleanupAndExit + +} // XDCAMEX_MetaHandler::GetTakeDuration + +// ================================================================================================= +// XDCAMEX_MetaHandler::GetMediaProMetadata +// ======================================== + +bool XDCAMEX_MetaHandler::GetMediaProMetadata ( SXMPMeta * xmpObjPtr, + const std::string& clipUMID, + bool digestFound ) +{ + // Build a directory string to the MEDIAPRO file. + + std::string mediaproPath; + this->MakeMediaproPath ( &mediaproPath ); + return XDCAM_Support::GetMediaProLegacyMetadata ( xmpObjPtr, clipUMID, mediaproPath, digestFound ); + +} // XDCAMEX_MetaHandler::GetMediaProMetadata + +// ================================================================================================= +// XDCAMEX_MetaHandler::GetTakeUMID +// ================================ + +void XDCAMEX_MetaHandler::GetTakeUMID ( const std::string& clipUMID, + std::string& takeUMID, + std::string& takeXMLURI ) +{ + + // Some versions of gcc can't tolerate goto's across declarations. + // *** Better yet, avoid this cruft with self-cleaning objects. + #define CleanupAndExit \ + { \ + delete expatMediaPro; \ + mediaproXMLFile.Close(); \ + return; \ + } + + takeUMID.clear(); + takeXMLURI.clear(); + + // Build a directory string to the MEDIAPRO file. + + std::string mediapropath ( this->rootPath ); + mediapropath += kDirChar; + mediapropath += "BPAV"; + mediapropath += kDirChar; + mediapropath += "MEDIAPRO.XML"; + + // Parse MEDIAPRO.XML. + + XML_NodePtr mediaproRootElem = 0; + XML_NodePtr contentContext = 0, materialContext = 0; + + Host_IO::FileRef hostRef = Host_IO::Open ( mediapropath.c_str(), Host_IO::openReadOnly ); + if ( hostRef == Host_IO::noFileRef ) return; // The open failed. + XMPFiles_IO mediaproXMLFile ( hostRef, mediapropath.c_str(), Host_IO::openReadOnly ); + + ExpatAdapter * expatMediaPro = XMP_NewExpatAdapter ( ExpatAdapter::kUseLocalNamespaces ); + if ( expatMediaPro == 0 ) return; + + XMP_Uns8 buffer [64*1024]; + while ( true ) { + XMP_Int32 ioCount = mediaproXMLFile.Read ( buffer, sizeof(buffer) ); + if ( ioCount == 0 ) break; + expatMediaPro->ParseBuffer ( buffer, ioCount, false /* not the end */ ); + } + + expatMediaPro->ParseBuffer ( 0, 0, true ); // End the parse. + mediaproXMLFile.Close(); + + // Get the root node of the XML tree. + + XML_Node & mediaproXMLTree = expatMediaPro->tree; + for ( size_t i = 0, limit = mediaproXMLTree.content.size(); i < limit; ++i ) { + if ( mediaproXMLTree.content[i]->kind == kElemNode ) { + mediaproRootElem = mediaproXMLTree.content[i]; + } + } + + if ( mediaproRootElem == 0 ) CleanupAndExit + XMP_StringPtr rlName = mediaproRootElem->name.c_str() + mediaproRootElem->nsPrefixLen; + if ( ! XMP_LitMatch ( rlName, "MediaProfile" ) ) CleanupAndExit + + // MediaProfile, Contents + + XMP_StringPtr ns = mediaproRootElem->ns.c_str(); + contentContext = mediaproRootElem->GetNamedElement ( ns, "Contents" ); + + if ( contentContext != 0 ) { + + size_t numMaterialElems = contentContext->CountNamedElements ( ns, "Material" ); + + for ( size_t i = 0; i < numMaterialElems; ++i ) { // Iterate over Material tags. + + XML_NodePtr materialElement = contentContext->GetNamedElement ( ns, "Material", i ); + XMP_Assert ( materialElement != 0 ); + + XMP_StringPtr umid = materialElement->GetAttrValue ( "umid" ); + XMP_StringPtr uri = materialElement->GetAttrValue ( "uri" ); + + if ( umid == 0 ) umid = ""; + if ( uri == 0 ) uri = ""; + + size_t numComponents = materialElement->CountNamedElements ( ns, "Component" ); + + for ( size_t j = 0; j < numComponents; ++j ) { + + XML_NodePtr componentElement = materialElement->GetNamedElement ( ns, "Component", j ); + XMP_Assert ( componentElement != 0 ); + + XMP_StringPtr compUMID = componentElement->GetAttrValue ( "umid" ); + + if ( (compUMID != 0) && (compUMID == clipUMID) ) { + takeUMID = umid; + takeXMLURI = uri; + break; + } + + } + + if ( ! takeUMID.empty() ) break; + + } + + } + + CleanupAndExit + #undef CleanupAndExit + +} + +// ================================================================================================= +// XDCAMEX_MetaHandler::ProcessXMP +// =============================== + +void XDCAMEX_MetaHandler::ProcessXMP() +{ + + // Some versions of gcc can't tolerate goto's across declarations. + // *** Better yet, avoid this cruft with self-cleaning objects. + #define CleanupAndExit \ + { \ + bool openForUpdate = XMP_OptionIsSet ( this->parent->openFlags, kXMPFiles_OpenForUpdate ); \ + if ( ! openForUpdate ) this->CleanupLegacyXML(); \ + xmlFile.Close(); \ + return; \ + } + + if ( this->processedXMP ) return; + this->processedXMP = true; // Make sure only called once. + + if ( this->containsXMP ) { + this->xmpObj.ParseFromBuffer ( this->xmpPacket.c_str(), (XMP_StringLen)this->xmpPacket.size() ); + } + + // NonRealTimeMeta -> XMP by schema. + std::string thisUMID, takeUMID, takeXMLURI, takeDuration; + std::string xmlPath; + this->MakeClipFilePath ( &xmlPath, "M01.XML" ); + + Host_IO::FileRef hostRef = Host_IO::Open ( xmlPath.c_str(), Host_IO::openReadOnly ); + if ( hostRef == Host_IO::noFileRef ) return; // The open failed. + XMPFiles_IO xmlFile ( hostRef, xmlPath.c_str(), Host_IO::openReadOnly ); + + this->expat = XMP_NewExpatAdapter ( ExpatAdapter::kUseLocalNamespaces ); + if ( this->expat == 0 ) XMP_Throw ( "XDCAMEX_MetaHandler: Can't create Expat adapter", kXMPErr_NoMemory ); + + XMP_Uns8 buffer [64*1024]; + while ( true ) { + XMP_Int32 ioCount = xmlFile.Read ( buffer, sizeof(buffer) ); + if ( ioCount == 0 ) break; + this->expat->ParseBuffer ( buffer, ioCount, false /* not the end */ ); + } + this->expat->ParseBuffer ( 0, 0, true ); // End the parse. + + xmlFile.Close(); + + // The root element should be NonRealTimeMeta in some namespace. Take whatever this file uses. + + XML_Node & xmlTree = this->expat->tree; + XML_NodePtr rootElem = 0; + + for ( size_t i = 0, limit = xmlTree.content.size(); i < limit; ++i ) { + if ( xmlTree.content[i]->kind == kElemNode ) rootElem = xmlTree.content[i]; + } + + if ( rootElem == 0 ) CleanupAndExit + XMP_StringPtr rootLocalName = rootElem->name.c_str() + rootElem->nsPrefixLen; + if ( ! XMP_LitMatch ( rootLocalName, "NonRealTimeMeta" ) ) CleanupAndExit + + this->legacyNS = rootElem->ns; + + // Check the legacy digest. + + XMP_StringPtr legacyNS = this->legacyNS.c_str(); + this->clipMetadata = rootElem; // ! Save the NonRealTimeMeta pointer for other use. + + std::string oldDigest, newDigest; + bool digestFound = this->xmpObj.GetStructField ( kXMP_NS_XMP, "NativeDigests", kXMP_NS_XMP, "XDCAMEX", &oldDigest, 0 ); + if ( digestFound ) { + this->MakeLegacyDigest ( &newDigest ); + if ( oldDigest == newDigest ) CleanupAndExit + } + + // If we get here we need find and import the actual legacy elements using the current namespace. + // Either there is no old digest in the XMP, or the digests differ. In the former case keep any + // existing XMP, in the latter case take new legacy values. + this->containsXMP = XDCAM_Support::GetLegacyMetadata ( &this->xmpObj, rootElem, legacyNS, digestFound, thisUMID ); + + // If this clip is part of a take, add the take number to the relation field, and get the + // duration from the take metadata. + GetTakeUMID ( thisUMID, takeUMID, takeXMLURI ); + + // If this clip is part of a take, update the duration to reflect the take duration rather than + // the clip duration, and add the take name as a shot name. + + if ( ! takeXMLURI.empty() ) { + + // Update duration. This property already exists from clip legacy metadata. + GetTakeDuration ( takeXMLURI, takeDuration ); + if ( ! takeDuration.empty() ) { + this->xmpObj.SetStructField ( kXMP_NS_DM, "duration", kXMP_NS_DM, "value", takeDuration ); + containsXMP = true; + } + + if ( digestFound || (! this->xmpObj.DoesPropertyExist ( kXMP_NS_DM, "shotName" )) ) { + + std::string takeName; + XIO::SplitLeafName ( &takeXMLURI, &takeName ); + + // Check for the xml suffix, and delete if it exists. + size_t pos = takeName.rfind(".SMI"); + if ( pos != std::string::npos ) { + + takeName.erase ( pos ); + + // delete the take number suffix if it exists. + if ( takeName.size() > 3 ) { + + size_t suffix = takeName.size() - 3; + char c1 = takeName[suffix]; + char c2 = takeName[suffix+1]; + char c3 = takeName[suffix+2]; + if ( ('U' == c1) && ('0' <= c2) && (c2 <= '9') && ('0' <= c3) && (c3 <= '9') ) { + takeName.erase ( suffix ); + } + + this->xmpObj.SetProperty ( kXMP_NS_DM, "shotName", takeName, kXMP_DeleteExisting ); + containsXMP = true; + + } + + } + + } + + } + + if ( (! takeUMID.empty()) && + (digestFound || (! this->xmpObj.DoesPropertyExist ( kXMP_NS_DC, "relation" ))) ) { + this->xmpObj.DeleteProperty ( kXMP_NS_DC, "relation" ); + this->xmpObj.AppendArrayItem ( kXMP_NS_DC, "relation", kXMP_PropArrayIsUnordered, takeUMID ); + this->containsXMP = true; + } + + this->containsXMP |= GetMediaProMetadata ( &this->xmpObj, thisUMID, digestFound ); + + CleanupAndExit + #undef CleanupAndExit + +} // XDCAMEX_MetaHandler::ProcessXMP + + +// ================================================================================================= +// XDCAMEX_MetaHandler::UpdateFile +// =============================== +// +// Note that UpdateFile is only called from XMPFiles::CloseFile, so it is OK to close the file here. + +void XDCAMEX_MetaHandler::UpdateFile ( bool doSafeUpdate ) +{ + if ( ! this->needsUpdate ) return; + this->needsUpdate = false; // Make sure only called once. + + XMP_Assert ( this->parent->UsesLocalIO() ); + + // Update the internal legacy XML tree if we have one, and set the digest in the XMP. + + bool updateLegacyXML = false; + + if ( this->clipMetadata != 0 ) { + updateLegacyXML = XDCAM_Support::SetLegacyMetadata ( this->clipMetadata, &this->xmpObj, this->legacyNS.c_str()); + } + + std::string newDigest; + this->MakeLegacyDigest ( &newDigest ); + this->xmpObj.SetStructField ( kXMP_NS_XMP, "NativeDigests", kXMP_NS_XMP, "XDCAMEX", newDigest.c_str(), kXMP_DeleteExisting ); + this->xmpObj.SerializeToBuffer ( &this->xmpPacket, this->GetSerializeOptions() ); + + // ----------------------------------------------------------------------- + // Update the XMP file first, don't let legacy XML failures block the XMP. + + std::string xmpPath; + this->MakeClipFilePath ( &xmpPath, "M01.XMP" ); + + bool haveXMP = Host_IO::Exists ( xmpPath.c_str() ); + if ( ! haveXMP ) { + XMP_Assert ( this->parent->ioRef == 0 ); + Host_IO::Create ( xmpPath.c_str() ); + this->parent->ioRef = XMPFiles_IO::New_XMPFiles_IO ( xmpPath.c_str(), Host_IO::openReadWrite ); + if ( this->parent->ioRef == 0 ) XMP_Throw ( "Failure opening XDCAMEX XMP file", kXMPErr_ExternalFailure ); + } + + XMP_IO* xmpFile = this->parent->ioRef; + XMP_Assert ( xmpFile != 0 ); + XIO::ReplaceTextFile ( xmpFile, this->xmpPacket, (haveXMP & doSafeUpdate) ); + + // -------------------------------------------- + // Now update the legacy XML file if necessary. + + if ( updateLegacyXML ) { + + std::string legacyXML, xmlPath; + this->expat->tree.Serialize ( &legacyXML ); + this->MakeClipFilePath ( &xmlPath, "M01.XML" ); + + bool haveXML = Host_IO::Exists ( xmlPath.c_str() ); + if ( ! haveXML ) Host_IO::Create ( xmlPath.c_str() ); + + Host_IO::FileRef hostRef = Host_IO::Open ( xmlPath.c_str(), Host_IO::openReadWrite ); + if ( hostRef == Host_IO::noFileRef ) XMP_Throw ( "Failure opening XDCAMEX legacy XML file", kXMPErr_ExternalFailure ); + XMPFiles_IO origXML ( hostRef, xmlPath.c_str(), Host_IO::openReadWrite ); + XIO::ReplaceTextFile ( &origXML, legacyXML, (haveXML & doSafeUpdate) ); + origXML.Close(); + + } + +} // XDCAMEX_MetaHandler::UpdateFile + +// ================================================================================================= +// XDCAMEX_MetaHandler::WriteTempFile +// ================================== + +void XDCAMEX_MetaHandler::WriteTempFile ( XMP_IO* tempRef ) +{ + + // ! WriteTempFile is not supposed to be called for handlers that own the file. + XMP_Throw ( "XDCAMEX_MetaHandler::WriteTempFile should not be called", kXMPErr_InternalFailure ); + +} // XDCAMEX_MetaHandler::WriteTempFile + +// ================================================================================================= diff --git a/core/libs/dngwriter/extra/xmp_sdk/XMPFiles/source/FileHandlers/XDCAMEX_Handler.hpp b/core/libs/dngwriter/extra/xmp_sdk/XMPFiles/source/FileHandlers/XDCAMEX_Handler.hpp new file mode 100644 index 0000000000..3673b1f1e7 --- /dev/null +++ b/core/libs/dngwriter/extra/xmp_sdk/XMPFiles/source/FileHandlers/XDCAMEX_Handler.hpp @@ -0,0 +1,90 @@ +#ifndef __XDCAMEX_Handler_hpp__ +#define __XDCAMEX_Handler_hpp__ 1 + +// ================================================================================================= +// ADOBE SYSTEMS INCORPORATED +// Copyright 2008 Adobe Systems Incorporated +// All Rights Reserved +// +// NOTICE: Adobe permits you to use, modify, and distribute this file in accordance with the terms +// of the Adobe license agreement accompanying it. +// ================================================================================================= + +#include "public/include/XMP_Environment.h" // ! This must be the first include. + +#include "XMPFiles/source/XMPFiles_Impl.hpp" + +#include "source/ExpatAdapter.hpp" + +// ================================================================================================= +/// \file XDCAMEX_Handler.hpp +/// \brief Folder format handler for XDCAMEX. +// ================================================================================================= + +extern XMPFileHandler * XDCAMEX_MetaHandlerCTor ( XMPFiles * parent ); + +extern bool XDCAMEX_CheckFormat ( XMP_FileFormat format, + const std::string & rootPath, + const std::string & gpName, + const std::string & parentName, + const std::string & leafName, + XMPFiles * parent ); + +static const XMP_OptionBits kXDCAMEX_HandlerFlags = (kXMPFiles_CanInjectXMP | + kXMPFiles_CanExpand | + kXMPFiles_CanRewrite | + kXMPFiles_PrefersInPlace | + kXMPFiles_CanReconcile | + kXMPFiles_AllowsOnlyXMP | + kXMPFiles_ReturnsRawPacket | + kXMPFiles_HandlerOwnsFile | + kXMPFiles_AllowsSafeUpdate | + kXMPFiles_FolderBasedFormat); + +class XDCAMEX_MetaHandler : public XMPFileHandler +{ +public: + + bool GetFileModDate ( XMP_DateTime * modDate ); + + void FillMetadataFiles ( std::vector * metadataFiles ); + void FillAssociatedResources ( std::vector * resourceList ); + bool IsMetadataWritable ( ); + + void CacheFileData(); + void ProcessXMP(); + + void UpdateFile ( bool doSafeUpdate ); + void WriteTempFile ( XMP_IO* tempRef ); + + XMP_OptionBits GetSerializeOptions() // *** These should be standard for standalone XMP files. + { return (kXMP_UseCompactFormat | kXMP_OmitPacketWrapper); }; + + XDCAMEX_MetaHandler ( XMPFiles * _parent ); + virtual ~XDCAMEX_MetaHandler(); + +private: + + XDCAMEX_MetaHandler() : expat(0), clipMetadata(0) {}; // Hidden on purpose. + + bool MakeClipFilePath ( std::string * path, XMP_StringPtr suffix, bool checkFile = false ); + bool MakeMediaproPath ( std::string * path, bool checkFile = false ); + void MakeLegacyDigest ( std::string * digestStr ); + + void GetTakeUMID ( const std::string& clipUMID, std::string& takeUMID, std::string& takeXMLURI ); + void GetTakeDuration ( const std::string& takeUMID, std::string& duration ); + bool GetMediaProMetadata ( SXMPMeta * xmpObjPtr, const std::string& clipUMID, bool digestFound ); + + void CleanupLegacyXML(); + + std::string rootPath, clipName, xdcNS, legacyNS, clipUMID; + + // Used to Parse the Non-XMP /non real time metadata file associated with the clip + ExpatAdapter * expat; + XML_Node * clipMetadata; + +}; // XDCAMEX_MetaHandler + +// ================================================================================================= + +#endif /* __XDCAMEX_Handler_hpp__ */ diff --git a/core/libs/dngwriter/extra/xmp_sdk/XMPFiles/source/FileHandlers/XDCAMFAM_Handler.cpp b/core/libs/dngwriter/extra/xmp_sdk/XMPFiles/source/FileHandlers/XDCAMFAM_Handler.cpp new file mode 100644 index 0000000000..ded65c4ff5 --- /dev/null +++ b/core/libs/dngwriter/extra/xmp_sdk/XMPFiles/source/FileHandlers/XDCAMFAM_Handler.cpp @@ -0,0 +1,677 @@ +// ================================================================================================= +// ADOBE SYSTEMS INCORPORATED +// Copyright 2015 Adobe Systems Incorporated +// All Rights Reserved +// +// NOTICE: Adobe permits you to use, modify, and distribute this file in accordance with the terms +// of the Adobe license agreement accompanying it. +// ================================================================================================= + +// ================================================================================================= +// +// This handler will handle FAM/FTP variant of XDCAM. +// More information could be found in XDCAM_Handler.cpp +// +// ================================================================================================= + +#include "public/include/XMP_Environment.h" // ! XMP_Environment.h must be the first included header. + +#include "public/include/XMP_Const.h" +#include "public/include/XMP_IO.hpp" + +#include "XMPFiles/source/XMPFiles_Impl.hpp" +#include "source/XMPFiles_IO.hpp" +#include "source/XIO.hpp" +#include "source/IOUtils.hpp" + +#include "XMPFiles/source/FileHandlers/XDCAMFAM_Handler.hpp" +#include "XMPFiles/source/FormatSupport/XDCAM_Support.hpp" +#include "XMPFiles/source/FormatSupport/PackageFormat_Support.hpp" + +bool XDCAMFAM_CheckFormat ( XMP_FileFormat format, + const std::string & rootPath, + const std::string & groupName, + const std::string & parentName, + const std::string & leafName, + XMPFiles * parent ) +{ + /* isXDStyle = true Means SxS Memory or XDStyle + , = false Means Professional Disk */ + bool isXDStyle = false; + if ( (format != kXMP_XDCAM_FAMFile) && (format != kXMP_UnknownFile) ) return false; + if ( groupName.empty() != parentName.empty() ) return false; + + if ( groupName.empty() && ( Host_IO::GetChildMode ( rootPath.c_str(), "PROAV" ) == Host_IO::kFMode_IsFolder ) ) return false; + + std::string tempPath = rootPath; + + if ( !parentName.empty() ) + { + // Real Absolute Path exists + if ( ! ( parentName == "CLIP" || parentName == "SUB" || parentName == "LOCAL" ) ) + return false; + tempPath += kDirChar + groupName; + } + + // Some basic Checks + if ( Host_IO::GetChildMode ( tempPath.c_str(), "DISCMETA.XML" ) != Host_IO::kFMode_IsFile ) return false; + if ( Host_IO::GetChildMode( tempPath.c_str(), "MEDIAPRO.XML" ) != Host_IO::kFMode_IsFile ) return false; + if ( ( Host_IO::GetChildMode( tempPath.c_str(), "Take" ) == Host_IO::kFMode_IsFolder ) || ( Host_IO::GetChildMode( tempPath.c_str(), "Local" ) == Host_IO::kFMode_IsFolder ) ) + isXDStyle = true; + + // XDStyle can't have INDEX.XML + if ( isXDStyle && ( Host_IO::GetChildMode( tempPath.c_str(), "INDEX.XML" ) == Host_IO::kFMode_IsFile ) ) + return false; + // XDStyle can't have ALIAS.XML + if( isXDStyle && ( Host_IO::GetChildMode( tempPath.c_str(), "ALIAS.XML" ) == Host_IO::kFMode_IsFile ) ) + return false; + // Non-XDStyle can't have CUEUP.XML file + if( ( !isXDStyle ) && ( Host_IO::GetChildMode( tempPath.c_str(), "CUEUP.XML" ) == Host_IO::kFMode_IsFile ) ) + return false; + + // We will get metadata from NRT file inside Clip folder only + tempPath += kDirChar; + tempPath += "Clip"; + tempPath += kDirChar; + + std::string clipName = leafName; + size_t length = clipName.length(); + + // Proxy file support + if ( ( parentName == "SUB" ) ) + { + if( clipName.at( length - 3 ) != 'S' || ( ! IsDigit( clipName.at( length - 2 ) ) ) || ( ! IsDigit( clipName.at( length - 1 ) ) ) ) + return false; + clipName.erase( clipName.begin() + length - 3, clipName.end() ); + } + + tempPath += clipName; + + // .MXF file Existence with case sensitive is the new check inserted + std::string mxfPath = tempPath + ".MXF"; + if ( Host_IO::GetFileMode ( mxfPath.c_str() ) != Host_IO::kFMode_IsFile ) + { + mxfPath = tempPath + ".mxf"; + if ( Host_IO::GetFileMode ( mxfPath.c_str() ) != Host_IO::kFMode_IsFile ) + return false; + } + + tempPath += "M01.XML"; + if ( Host_IO::GetFileMode ( tempPath.c_str() ) != Host_IO::kFMode_IsFile ) + return false; + return true; + +} // XDCAMFAM_CheckFormat + +XMPFileHandler * XDCAMFAM_MetaHandlerCTor ( XMPFiles * parent ) +{ + return new XDCAMFAM_MetaHandler ( parent ); + +} // XDCAM_MetaHandlerCTor + +// ================================================================================================= +// XDCAMFAM_MetaHandler::XDCAMFAM_MetaHandler +// ==================================== +XDCAMFAM_MetaHandler::XDCAMFAM_MetaHandler ( XMPFiles * _parent ) : XDCAM_MetaHandler(_parent), isXDStyle( false ) +{ + this->handlerFlags = kXDCAMFAM_HandlerFlags; + // Setting the various path variables + this->SetPathVariables ( this->parent->GetFilePath() ); +} // XDCAMFAM_MetaHandler::XDCAMFAM_MetaHandler + + +// ================================================================================================= +// XDCAMFAM_MetaHandler::SetPathVariables +// ==================================== +void XDCAMFAM_MetaHandler::SetPathVariables ( const std::string & clientPath ) +{ + // No need to check for existing or non existing as would have been done at check file format if ForceGivenHandler flag is not provided + std::string tempPath = clientPath; + std::string parentName, GroupName; + std::string ignored; + + XIO::SplitLeafName ( &tempPath, &this->clipName ); + + this->rootPath = tempPath; + + if ( ! Host_IO::Exists( clientPath.c_str() ) ) + { + // Logical Path exists + // No need to extract extension as clipname is given without extension + if ( ( Host_IO::GetChildMode( tempPath.c_str(), "INDEX.XML" ) != Host_IO::kFMode_IsFile ) ) + this->isXDStyle = true; + tempPath += kDirChar; + tempPath += "Clip"; + XMP_Assert( Host_IO::GetFileMode( tempPath.c_str() ) == Host_IO::kFMode_IsFolder ); + } + else + { + // Real Absolute Path exists + XIO::SplitFileExtension ( &this->clipName, &ignored ); + XIO::SplitLeafName ( &tempPath, &parentName ); + if ( ( Host_IO::GetChildMode( tempPath.c_str(), "INDEX.XML" ) != Host_IO::kFMode_IsFile ) ) + this->isXDStyle = true; + this->rootPath = tempPath; + + size_t length = this->clipName.length(); + + // Proxy file support + if ( parentName == "Sub" ) + { + XMP_Assert( IsDigit( clipName.at( length - 2 ) ) && IsDigit( clipName.at( length - 1 ) ) ); + XMP_Assert( this->clipName.at( length - 3 ) == 'S' ); + this->clipName.erase( this->clipName.begin() + length - 3, clipName.end() ); + tempPath += kDirChar ; + tempPath += "Clip"; + } + else + tempPath += kDirChar + parentName; + } + + // Checks for Clip folder in XDCAM + XMP_Assert ( Host_IO::GetChildMode ( rootPath.c_str(), "Clip" ) == Host_IO::kFMode_IsFolder ); + + tempPath += kDirChar; + tempPath += this->clipName; + std::string mxfPath; + + // Case sensitive Extension support to check for clipname.MXF or clipname.mxf as already cover in Checkformat + if ( !( MakeClipFilePath( &mxfPath, ".MXF", true ) || MakeClipFilePath( &mxfPath, ".mxf", true ) ) ) + { + XMP_Error error( kXMPErr_FilePathNotAFile, "Clip MXF file must be exist" ); + NotifyClient( &this->parent->errorCallback, kXMPErrSev_FileFatal, error ); + } + + // NRT file Check as already cover in checkformat + if ( ! MakeClipFilePath ( &this->mNRTFilePath, "M01.XML", true ) ) + { + XMP_Error error( kXMPErr_FilePathNotAFile, "Clip NRT XML file must be exist" ); + NotifyClient( &this->parent->errorCallback, kXMPErrSev_FileFatal, error ); + } + + // Setting correct sidecar path + if ( this->isXDStyle || (Host_IO::GetChildMode ( rootPath.c_str(), "UserData" ) == Host_IO::kFMode_IsFolder ) ) + { + if ( ! ( MakeClipFilePath( &this->sidecarPath, ".xmp", true ) || MakeClipFilePath( &this->sidecarPath, ".XMP", true ) ) ) + this->sidecarPath = mxfPath + ".xmp"; + } + else + { + if ( ! ( MakeClipFilePath( &this->sidecarPath, "M01.XMP", true ) || MakeClipFilePath( &this->sidecarPath, "M01.xmp", true ) ) ) + this->sidecarPath = tempPath + "M01.XMP"; + } + +} // XDCAMFAM_MetaHandler::SetPathVariables + +// ================================================================================================= +// XDCAMFAM_MetaHandler::FillAssociatedResources +// ==================================== +void XDCAMFAM_MetaHandler::FillAssociatedResources( std::vector * resourceList ) +{ + //Add RootPath + std::string filePath = rootPath + kDirChar; + PackageFormat_Support::AddResourceIfExists( resourceList, filePath ); + + // Get the files present directly inside root folder. + filePath = rootPath + kDirChar + "ALIAS.XML"; + PackageFormat_Support::AddResourceIfExists(resourceList, filePath); + + // INDEX.XML doesn't exist for XDStyle + if( ! this->isXDStyle ) + { + filePath = rootPath + kDirChar + "INDEX.XML"; + PackageFormat_Support::AddResourceIfExists(resourceList, filePath); + } + + filePath = rootPath + kDirChar + "DISCMETA.XML"; + PackageFormat_Support::AddResourceIfExists(resourceList, filePath); + + filePath = rootPath + kDirChar + "MEDIAPRO.XML"; + PackageFormat_Support::AddResourceIfExists(resourceList, filePath); + filePath = rootPath + kDirChar + "MEDIAPRO.BUP"; + PackageFormat_Support::AddResourceIfExists(resourceList, filePath); + + // CUEUP.XML don't exist for Professional Disk XDCAM + if( this->isXDStyle ) + { + filePath = rootPath + kDirChar + "CUEUP.XML"; + PackageFormat_Support::AddResourceIfExists(resourceList, filePath); + filePath = rootPath + kDirChar + "CUEUP.BUP"; + PackageFormat_Support::AddResourceIfExists(resourceList, filePath); + } + + // Add the UserData folder + filePath = rootPath + kDirChar + "UserData" + kDirChar; + PackageFormat_Support::AddResourceIfExists(resourceList, filePath); + + + // Get the files present inside clip folder. + XMP_VarString clipPath = rootPath + kDirChar + "Clip" + kDirChar ; + size_t oldCount = resourceList->size(); + + XMP_VarString regExp; + XMP_StringVector regExpVec; + + regExp = "^" + clipName + ".MXF$"; + regExpVec.push_back ( regExp ); + regExp = "^" + clipName + "M\\d\\d.XML$"; + regExpVec.push_back ( regExp ); + if ( this->isXDStyle ) + { + regExp = "^" + clipName + "R\\d\\d.BIM$"; + regExpVec.push_back ( regExp ); + } + else + { + regExp = "^" + clipName + "M\\d\\d.KLV$"; + regExpVec.push_back ( regExp ); + } + IOUtils::GetMatchingChildren ( *resourceList, clipPath, regExpVec, false, true, true ); + PackageFormat_Support::AddResourceIfExists( resourceList, this->sidecarPath); + if ( resourceList->size() <= oldCount ) + { + PackageFormat_Support::AddResourceIfExists(resourceList, clipPath); + } + + //Get the files Under Sub folder + clipPath = rootPath + kDirChar + "Sub" + kDirChar ; + regExpVec.clear(); + regExp = "^" + clipName + "S\\d\\d.MXF$"; + regExpVec.push_back ( regExp ); + oldCount = resourceList->size(); + IOUtils::GetMatchingChildren ( *resourceList, clipPath, regExpVec, false, true, true ); + // Add Sub folder if no file inside this, was added. + if ( resourceList->size() <= oldCount ) + { + PackageFormat_Support::AddResourceIfExists(resourceList, clipPath); + } + + // Get the files Under Local folder if it is XDStyle + if ( isXDStyle ) + { + clipPath = rootPath + kDirChar + "Local" + kDirChar ; + regExpVec.clear(); + // ClipInfo file + regExp = "^" + clipName + "C\\d\\d.SMI$"; + regExpVec.push_back ( regExp ); + // Picture pointer file + regExp = "^" + clipName + "I\\d\\d.PPN$"; + regExpVec.push_back ( regExp ); + oldCount = resourceList->size(); + IOUtils::GetMatchingChildren ( *resourceList, clipPath, regExpVec, false, true, true ); + // Add Local folder if no file inside this, was added. + if ( resourceList->size() <= oldCount ) + { + PackageFormat_Support::AddResourceIfExists(resourceList, clipPath); + } + } + + // Add the Edit lists associated to this clip + XMP_StringVector editInfoList; + bool atLeastOneFileAdded = false; + clipPath = rootPath + kDirChar + "Edit" + kDirChar ; + if ( GetInfoFiles ( editInfoList , clipPath ) ) + { + size_t noOfEditInfoFiles = editInfoList.size() ; + for( size_t count = 0; count < noOfEditInfoFiles; count++ ) + { + atLeastOneFileAdded = PackageFormat_Support::AddResourceIfExists(resourceList, editInfoList[count]) ? true : atLeastOneFileAdded; + + XMP_VarString editNRTFile = editInfoList[count] ; + size_t filenamelen = editNRTFile.length() ; + if ( editNRTFile[ filenamelen - 7 ] == 'E' + && IsDigit( editNRTFile[ filenamelen - 6 ] ) + && IsDigit( editNRTFile[ filenamelen - 5 ] ) ) + { + editNRTFile.erase( editNRTFile.begin() + filenamelen - 7, editNRTFile.end() ) ; + } + else + { + editNRTFile.erase( editNRTFile.begin() + filenamelen - 4, editNRTFile.end() ) ; + } + + XMP_VarString fileName; + size_t pos = editNRTFile.find_last_of ( kDirChar ); + fileName = editNRTFile.substr ( pos + 1 ); + XMP_VarString regExp = "^" + fileName + "M\\d\\d.XML$"; + oldCount = resourceList->size(); + IOUtils::GetMatchingChildren ( *resourceList, clipPath, regExp, false, true, true ); + atLeastOneFileAdded = resourceList->size() > oldCount; + + } + } + // Add Edit folder if no file inside this, was added. + if ( !atLeastOneFileAdded ) + { + PackageFormat_Support::AddResourceIfExists(resourceList, clipPath); + } + + atLeastOneFileAdded = false; + + // Add the Takes associated to this clip, + // Take folder exists only for XDStyle + if( this->isXDStyle ) + { + XMP_StringVector takeList; + clipPath = rootPath + kDirChar + "Take" + kDirChar ; + if( GetInfoFiles ( takeList , clipPath ) ) + { + size_t noOfTakes = takeList.size() ; + for( size_t count = 0; count < noOfTakes; count++ ) + { + atLeastOneFileAdded = PackageFormat_Support::AddResourceIfExists(resourceList, takeList[count]) ? true : atLeastOneFileAdded; + XMP_VarString takeNRTFile = takeList[count] ; + size_t filenamelen = takeList[count].length() ; + if ( takeNRTFile[ filenamelen - 7 ] == 'U' + && IsDigit( takeNRTFile[ filenamelen - 6 ] ) + && IsDigit( takeNRTFile[ filenamelen - 5 ] ) ) + { + takeNRTFile.erase( takeNRTFile.begin() + filenamelen - 7, takeNRTFile.end() ) ; + } + else + { + takeNRTFile.erase( takeNRTFile.begin() + filenamelen - 4, takeNRTFile.end() ) ; + } + + XMP_VarString fileName; + size_t pos = takeNRTFile.find_last_of ( kDirChar ); + fileName = takeNRTFile.substr ( pos + 1 ); + XMP_VarString regExp = "^" + fileName + "M\\d\\d.XML$"; + oldCount = resourceList->size(); + IOUtils::GetMatchingChildren ( *resourceList, clipPath, regExp, false, true, true ); + atLeastOneFileAdded = resourceList->size() > oldCount; + } + } + // Add Take folder if no file inside this, was added. + if(!atLeastOneFileAdded) + { + filePath = rootPath + kDirChar + "Take" + kDirChar; + PackageFormat_Support::AddResourceIfExists(resourceList, filePath); + } + } + + // Add the Planning Metadata Files associated to this clip + // Planning Metadata exist for both SxS and Professional Disk + XMP_StringVector planList; + clipPath = rootPath + kDirChar + "General" + kDirChar + "Sony" + kDirChar+ "Planning" + kDirChar; + if( GetPlanningFiles ( planList , clipPath ) ) + { + size_t noOfPlans = planList.size() ; + for( size_t count = 0; count < noOfPlans; count++ ) + { + resourceList->push_back( planList[count] ); + } + } +} // XDCAMFAM_MetaHandler::FillAssociatedResources + +// ================================================================================================= +// XDCAMFAM_MetaHandler::MakeClipFilePath +// ==================================== +bool XDCAMFAM_MetaHandler::MakeClipFilePath ( std::string * path, XMP_StringPtr suffix, bool checkFile /* = false */ ) +{ + + *path = this->rootPath; + *path += kDirChar; + + *path += "Clip"; // ! Yes, mixed case. + + *path += kDirChar; + *path += this->clipName; + *path += suffix; + + if ( ! checkFile ) return true; + return Host_IO::Exists ( path->c_str() ); + +} // XDCAMFAM_MetaHandler::MakeClipFilePath + +// ================================================================================================= +// XDCAMFAM_MetaHandler::MakeLocalFilePath +// ==================================== +bool XDCAMFAM_MetaHandler::MakeLocalFilePath ( std::string * path, XMP_Uns8 fileType, bool checkFile /* = false */ ) +{ + + *path = this->rootPath; + *path += kDirChar; + + *path += "Local"; // ! Yes, mixed case. + + *path += kDirChar; + *path += this->clipName; + + if( fileType == k_LocalPPNFile ) + *path += "I01.PPN"; + else if ( fileType == k_LocalClipInfoFile ) + *path += "S01.SMI"; + else + return false; + + if ( ! checkFile ) return true; + return Host_IO::Exists ( path->c_str() ); + +} // XDCAMFAM_MetaHandler::MakeLocalFilePath + + +// ================================================================================================= +// XDCAMFAM_MetaHandler::GetMediaProMetadata +// ==================================== +bool XDCAMFAM_MetaHandler::GetMediaProMetadata ( SXMPMeta * xmpObjPtr, + const std::string& clipUMID, + bool digestFound ) +{ + // Build a directory string to the MEDIAPRO file. + std::string mediaproPath; + MakeMediaproPath ( &mediaproPath ); + return XDCAM_Support::GetMediaProLegacyMetadata ( xmpObjPtr, clipUMID, mediaproPath, digestFound ); +} // XDCAMFAM_MetaHandler::GetMediaProMetadata + +// ================================================================================================= +// XDCAMFAM_MetaHandler::GetPlanningFiles +// ==================================== +bool XDCAMFAM_MetaHandler::GetPlanningFiles ( std::vector &planInfoList, std::string pathToFolder) +{ + std::string clipUmid; + bool found = false; + + if( GetClipUmid ( clipUmid ) ) + { + if ( Host_IO::Exists( pathToFolder.c_str() ) && + Host_IO::GetFileMode( pathToFolder.c_str() ) == Host_IO::kFMode_IsFolder + ) + { + Host_IO::AutoFolder planFolder; + std::string listChild; + + planFolder.folder = Host_IO::OpenFolder ( pathToFolder.c_str() ); + while ( Host_IO::GetNextChild ( planFolder.folder, &listChild ) ) { + size_t filenamelen = listChild.size(); + std::string listFilePath = pathToFolder + listChild ; + if ( ! ( filenamelen > 4 && + ( listChild.compare ( filenamelen - 4, 4 , ".XML" ) == 0 + || + listChild.compare ( filenamelen - 4, 4 , ".xml" ) == 0 + ) + && + Host_IO::GetFileMode( listFilePath.c_str() ) == Host_IO::kFMode_IsFile + ) ) continue; + if( IsClipsPlanning ( clipUmid , listFilePath.c_str() ) ) + { + found = true ; + planInfoList.push_back( listFilePath ); + } + } + } + } + return found; +} // XDCAMFAM_MetaHandler::GetPlanningFiles + +// ================================================================================================= +// XDCAMFAM_MetaHandler::IsClipsPlanning +// ==================================== +bool XDCAMFAM_MetaHandler::IsClipsPlanning ( std::string clipUmid , XMP_StringPtr planPath ) +{ + ExpatAdapter* planniingExpat = 0 ; + XMP_StringPtr nameSpace = 0 ; + try { + readXMLFile( planPath, planniingExpat ); + if ( planniingExpat != 0 ) + { + XML_Node & xmlTree = planniingExpat->tree; + XML_NodePtr rootElem = 0; + + for ( size_t i = 0, limit = xmlTree.content.size(); i < limit; ++i ) { + if ( xmlTree.content[i]->kind == kElemNode ) { + rootElem = xmlTree.content[i]; + } + } + if ( rootElem != 0 ) + { + XMP_StringPtr rootLocalName = rootElem->name.c_str() + rootElem->nsPrefixLen; + + if ( XMP_LitMatch ( rootLocalName, "PlanningMetadata" ) ) + { + nameSpace = rootElem->ns.c_str() ; + size_t noOfMaterialGroups = rootElem->CountNamedElements ( nameSpace, "MaterialGroup" ) ; + while( noOfMaterialGroups-- ) + { + XML_NodePtr mgNode = rootElem->GetNamedElement( nameSpace, "MaterialGroup" ); + size_t noOfMaterialElements = mgNode->CountNamedElements ( nameSpace, "Material" ) ; + while( noOfMaterialElements-- ) + { + XML_NodePtr materialNode = mgNode->GetNamedElement( nameSpace, "Material" ); + XMP_StringPtr materialType = materialNode->GetAttrValue ( "type" ); + if ( materialType && XMP_LitMatch( materialType , "clip" ) ) + { + XMP_StringPtr umidValue = materialNode->GetAttrValue ( "umidRef" ); + if ( umidValue != 0 && XMP_LitMatch( umidValue , clipUmid.c_str() ) ) + { + delete ( planniingExpat ) ; + return true; + } + } + + } + } + } + } + } + + } catch ( ... ) { + } + delete ( planniingExpat ) ; + return false; +} // XDCAMFAM_MetaHandler::IsClipsPlanning + +// ================================================================================================= +// XDCAMFAM_MetaHandler::GetInfoFiles +// ==================================== +bool XDCAMFAM_MetaHandler::GetInfoFiles ( std::vector &infoList, std::string pathToFolder) +{ + std::string clipUmid; + bool found = false; + + if( GetClipUmid ( clipUmid ) ) + { + if ( Host_IO::Exists( pathToFolder.c_str() ) && + Host_IO::GetFileMode( pathToFolder.c_str() ) == Host_IO::kFMode_IsFolder + ) + { + Host_IO::AutoFolder infoFolder; + std::string listChild; + + infoFolder.folder = Host_IO::OpenFolder ( pathToFolder.c_str() ); + while ( Host_IO::GetNextChild ( infoFolder.folder, &listChild ) ) { + size_t filenamelen = listChild.size(); + std::string listFilePath = pathToFolder + listChild ; + if ( ! ( filenamelen > 7 && + listChild.compare ( filenamelen - 4, 4 , ".SMI" ) == 0 && + Host_IO::GetFileMode( listFilePath.c_str() ) == Host_IO::kFMode_IsFile + ) ) continue; + if( RefersClipUmid ( clipUmid , listFilePath.c_str() ) ) + { + found = true ; + infoList.push_back( listFilePath ); + } + } + } + } + return found; +} // XDCAMFAM_MetaHandler::GetInfoFiles + +// ================================================================================================= +// XDCAMFAM_MetaHandler::GetClipUmid +// ============================== +bool XDCAMFAM_MetaHandler::GetClipUmid ( std::string &clipUmid ) +{ + std::string clipInfoPath; + ExpatAdapter* clipInfoExpat = 0 ; + bool umidFound = false; + XMP_StringPtr nameSpace = 0; + try { + if ( this->MakeLocalFilePath ( &clipInfoPath, k_LocalClipInfoFile, true ) ) + { + readXMLFile( clipInfoPath.c_str(), clipInfoExpat ); + if ( clipInfoExpat != 0 ) + { + XML_Node & xmlTree = clipInfoExpat->tree; + XML_NodePtr rootElem = 0; + + for ( size_t i = 0, limit = xmlTree.content.size(); i < limit; ++i ) { + if ( xmlTree.content[i]->kind == kElemNode ) { + rootElem = xmlTree.content[i]; + } + } + if ( rootElem != 0 ) + { + XMP_StringPtr rootLocalName = rootElem->name.c_str() + rootElem->nsPrefixLen; + + if ( XMP_LitMatch ( rootLocalName, "smil" ) ) + { + XMP_StringPtr umidValue = rootElem->GetAttrValue ( "umid" ); + if ( umidValue != 0 ) { + clipUmid = umidValue; + umidFound = true; + } + } + } + } + } + if( ! umidFound ) + { //try to get the umid from the NRT metadata + delete ( clipInfoExpat ) ; clipInfoExpat = 0; + this->MakeClipFilePath ( &clipInfoPath, "M01.XML" ) ; + readXMLFile( clipInfoPath.c_str(), clipInfoExpat ) ; + if ( clipInfoExpat != 0 ) + { + XML_Node & xmlTree = clipInfoExpat->tree; + XML_NodePtr rootElem = 0; + for ( size_t i = 0, limit = xmlTree.content.size(); i < limit; ++i ) { + if ( xmlTree.content[i]->kind == kElemNode ) { + rootElem = xmlTree.content[i]; + } + } + if ( rootElem != 0 ) + { + XMP_StringPtr rootLocalName = rootElem->name.c_str() + rootElem->nsPrefixLen; + + if ( XMP_LitMatch ( rootLocalName, "NonRealTimeMeta" ) ) + { + nameSpace = rootElem->ns.c_str() ; + XML_NodePtr targetProp = rootElem->GetNamedElement ( nameSpace, "TargetMaterial" ); + if ( (targetProp != 0) && targetProp->IsEmptyLeafNode() ) { + XMP_StringPtr umidValue = targetProp->GetAttrValue ( "umidRef" ); + if ( umidValue != 0 ) { + clipUmid = umidValue; + umidFound = true; + } + } + } + } + } + } + } catch ( ... ) { + } + delete ( clipInfoExpat ) ; + return umidFound; +} // XDCAMFAM_MetaHandler::GetClipUmid + +// ================================================================================================= diff --git a/core/libs/dngwriter/extra/xmp_sdk/XMPFiles/source/FileHandlers/XDCAMFAM_Handler.hpp b/core/libs/dngwriter/extra/xmp_sdk/XMPFiles/source/FileHandlers/XDCAMFAM_Handler.hpp new file mode 100644 index 0000000000..b53e55bc2d --- /dev/null +++ b/core/libs/dngwriter/extra/xmp_sdk/XMPFiles/source/FileHandlers/XDCAMFAM_Handler.hpp @@ -0,0 +1,71 @@ +#ifndef __XDCAMFAM_Handler_hpp__ +#define __XDCAMFAM_Handler_hpp__ 1 + +// ================================================================================================= +// ADOBE SYSTEMS INCORPORATED +// Copyright 2015 Adobe Systems Incorporated +// All Rights Reserved +// +// NOTICE: Adobe permits you to use, modify, and distribute this file in accordance with the terms +// of the Adobe license agreement accompanying it. +// ================================================================================================= + +#include "public/include/XMP_Environment.h" // ! This must be the first include. +#include "XMPFiles/source/FileHandlers/XDCAM_Handler.hpp" + + +extern XMPFileHandler * XDCAMFAM_MetaHandlerCTor ( XMPFiles * parent ); + +extern bool XDCAMFAM_CheckFormat ( XMP_FileFormat format, + const std::string & rootPath, + const std::string & gpName, + const std::string & parentName, + const std::string & leafName, + XMPFiles * parent ); + +static const XMP_OptionBits kXDCAMFAM_HandlerFlags = (kXMPFiles_CanInjectXMP | + kXMPFiles_CanExpand | + kXMPFiles_CanRewrite | + kXMPFiles_PrefersInPlace | + kXMPFiles_CanReconcile | + kXMPFiles_AllowsOnlyXMP | + kXMPFiles_ReturnsRawPacket | + kXMPFiles_HandlerOwnsFile | + kXMPFiles_AllowsSafeUpdate | + kXMPFiles_FolderBasedFormat); + + +class XDCAMFAM_MetaHandler : public XDCAM_MetaHandler +{ + +public: + + void FillAssociatedResources ( std::vector * resourceList ); + XDCAMFAM_MetaHandler ( XMPFiles * _parent ); + virtual ~XDCAMFAM_MetaHandler() { }; + +private: + + bool isXDStyle; + + bool MakeClipFilePath ( std::string * path, XMP_StringPtr suffix, bool checkFile = false ); + void SetPathVariables ( const std::string & clientPath ); + bool GetMediaProMetadata ( SXMPMeta * xmpObjPtr, const std::string& clipUMID, bool digestFound ); + bool GetInfoFiles ( std::vector &infoList, std::string pathToFolder) ; + bool GetPlanningFiles ( std::vector &planInfoList, std::string pathToFolder) ; + bool IsClipsPlanning ( std::string clipUmid , XMP_StringPtr planPath ) ; + bool GetClipUmid ( std::string &clipUmid ); + bool MakeLocalFilePath ( std::string * path, XMP_Uns8 fileType, bool checkFile = false ); + + XDCAMFAM_MetaHandler() : XDCAM_MetaHandler() {}; // Hidden on purpose. + + enum + { + k_LocalPPNFile, + k_LocalClipInfoFile + }; + +}; // XDCAMFAM_MetaHandler + +// ================================================================================================= +#endif /* __XDCAMFAM_Handler_hpp__ */ diff --git a/core/libs/dngwriter/extra/xmp_sdk/XMPFiles/source/FileHandlers/XDCAMSAM_Handler.cpp b/core/libs/dngwriter/extra/xmp_sdk/XMPFiles/source/FileHandlers/XDCAMSAM_Handler.cpp new file mode 100644 index 0000000000..f0303dfdf1 --- /dev/null +++ b/core/libs/dngwriter/extra/xmp_sdk/XMPFiles/source/FileHandlers/XDCAMSAM_Handler.cpp @@ -0,0 +1,429 @@ +// ================================================================================================= +// ADOBE SYSTEMS INCORPORATED +// Copyright 2015 Adobe Systems Incorporated +// All Rights Reserved +// +// NOTICE: Adobe permits you to use, modify, and distribute this file in accordance with the terms +// of the Adobe license agreement accompanying it. +// ================================================================================================= + +#include "public/include/XMP_Environment.h" // ! XMP_Environment.h must be the first included header. + +#include "public/include/XMP_Const.h" +#include "public/include/XMP_IO.hpp" + +#include "XMPFiles/source/XMPFiles_Impl.hpp" +#include "source/XMPFiles_IO.hpp" +#include "source/XIO.hpp" +#include "source/IOUtils.hpp" + +#include "XMPFiles/source/FileHandlers/XDCAMSAM_Handler.hpp" +#include "XMPFiles/source/FormatSupport/XDCAM_Support.hpp" +#include "XMPFiles/source/FormatSupport/PackageFormat_Support.hpp" + +bool XDCAMSAM_CheckFormat ( XMP_FileFormat format, + const std::string & rootPath, + const std::string & groupName, + const std::string & parentName, + const std::string & leafName, + XMPFiles * parent ) +{ + // We only support file in CLPR folder not in other folders + + if ( ( format != kXMP_XDCAM_SAMFile ) && ( format != kXMP_UnknownFile ) ) return false; + + // parentName or groupName empty means Logical path exists + if ( groupName.empty() != parentName.empty() ) return false; + + std::string tempPath = rootPath; + std::string clipName = leafName; + + if ( groupName.empty() ) + { + // Logical clip exists + tempPath += kDirChar; + tempPath += "PROAV"; + + // Simple checks to ensure presence or absence of Management files or CLPR folder + if ( Host_IO::GetChildMode ( tempPath.c_str(), "INDEX.XML" ) != Host_IO::kFMode_IsFile ) return false; + if ( Host_IO::GetChildMode ( tempPath.c_str(), "DISCMETA.XML" ) != Host_IO::kFMode_IsFile ) return false; + if ( Host_IO::GetChildMode ( tempPath.c_str(), "DISCINFO.XML" ) != Host_IO::kFMode_IsFile ) return false; + if ( Host_IO::GetChildMode ( tempPath.c_str(), "CLPR" ) != Host_IO::kFMode_IsFolder ) return false; + if ( Host_IO::GetChildMode( tempPath.c_str(), "MEDIAPRO.XML" ) == Host_IO::kFMode_IsFile ) return false; + tempPath += kDirChar; + tempPath += "CLPR"; + tempPath += kDirChar + leafName; + } + else if ( groupName == "CLPR" ) + { + // XMP provides support only to files inside CLPR + // Simple checks to ensure presence or absence of Management files + if ( Host_IO::GetChildMode ( tempPath.c_str(), "INDEX.XML" ) != Host_IO::kFMode_IsFile ) return false; + if ( Host_IO::GetChildMode ( tempPath.c_str(), "DISCMETA.XML" ) != Host_IO::kFMode_IsFile ) return false; + if ( Host_IO::GetChildMode ( tempPath.c_str(), "DISCINFO.XML" ) != Host_IO::kFMode_IsFile ) return false; + if ( ( Host_IO::GetChildMode( tempPath.c_str(), "MEDIAPRO.XML" ) == Host_IO::kFMode_IsFile ) ) return false; + + tempPath += kDirChar + groupName; + tempPath += kDirChar + parentName; + size_t length = clipName.length(); + const char fileType = clipName.at ( length - 3 ); + if ( IsDigit( clipName.at( length - 1 ) ) && IsDigit( clipName.at( length - 2 ) ) ) + { + // Last 3rd characater shows what is the file type + switch ( fileType ) + { + case 'A' : // Audio + case 'C' : // ClipInfo + case 'I' : // Picture pointer + case 'M' : // NRT + case 'R' : // Real Time + case 'S' : // Sub (Proxy) + case 'V' : // Video + break; + default: // Unknown + return false; + } + clipName.erase ( clipName.begin() + length - 3, clipName.end() ); + } + } + else + return false; + tempPath += kDirChar + clipName; + tempPath += "M01.XML"; + + // Checking for NRT file + if ( Host_IO::GetFileMode ( tempPath.c_str() ) != Host_IO::kFMode_IsFile ) + return false; + + return true; + +} // XDCAMSAM_CheckFormat + +XMPFileHandler * XDCAMSAM_MetaHandlerCTor ( XMPFiles * parent ) +{ + return new XDCAMSAM_MetaHandler ( parent ); +} // XDCAMSAM_MetaHandlerCTor + + +// ================================================================================================= +// XDCAMSAM_MetaHandler::XDCAMSAM_MetaHandler +// ==================================== +XDCAMSAM_MetaHandler::XDCAMSAM_MetaHandler ( XMPFiles * _parent ) : XDCAM_MetaHandler(_parent) +{ + this->handlerFlags = kXDCAMSAM_HandlerFlags; + // Setting the various path variables + this->SetPathVariables ( this->parent->GetFilePath() ); + +} // XDCAMSAM_MetaHandler::XDCAMSAM_MetaHandler + +// ================================================================================================= +// XDCAMSAM_MetaHandler::SetPathVariables +// ==================================== +void XDCAMSAM_MetaHandler::SetPathVariables ( const std::string & clientPath ) +{ + // No need to check for existing or non existing as would have been done at check file format if ForceGivenHandler flag is not provided + std::string tempPath = clientPath; + std::string parentName, groupName; + std::string ignored; + + std::string leafName; + XIO::SplitLeafName ( &tempPath, &leafName ); + + if ( ! Host_IO::Exists( clientPath.c_str() ) ) + { + // logical path exists + // No need to extract extension as clipname is given without extension + this->rootPath = tempPath; + tempPath += kDirChar; + tempPath += "PROAV"; + + XMP_Assert ( Host_IO::GetChildMode( tempPath.c_str(), "MEDIAPRO.XML" ) != Host_IO::kFMode_IsFile ); + + tempPath += kDirChar; + tempPath += "CLPR"; + tempPath += kDirChar + leafName; + } + else + { + // Real Absolute Path exists + XIO::SplitFileExtension ( &leafName, &ignored ); + + XIO::SplitLeafName ( &tempPath, &parentName ); + XIO::SplitLeafName ( &tempPath, &groupName ); + + std::string proav; + XIO::SplitLeafName ( &tempPath, &proav ); + XMP_Assert ( proav == "PROAV" ); + + this->rootPath = tempPath; + + + XMP_Assert ( groupName == "CLPR" ); + + // Real Path may be ..PROAV//CLPR//clipName//clipname.MXF + // So XMP check for ..PROAV//CLPR//clipName//clipNameM01.xml + // XNP sidecar file will be ..PROAV//CLPR//clipName//clipNameM01.XMP + size_t length = leafName.length(); + + XMP_Assert ( IsDigit( leafName.at( length - 2 ) ) && IsDigit( leafName.at( length - 1 ) ) ); + // Last 3rd character of file will inform us about its type + const char fileType = leafName.at( length - 3 ); + + // A = Audio, C = ClipInfo, I = Picture Pointer, M = Non-Realtime, R = Realtime, S = Sub (Proxy), V = Video + XMP_Assert ( fileType == 'A' || fileType == 'C' || fileType == 'I' || fileType == 'M' || fileType == 'R' || fileType == 'S' || fileType == 'V' ); + + leafName.erase ( leafName.begin() + length - 3, leafName.end() ); + + tempPath += kDirChar + proav; + tempPath += kDirChar + groupName; + tempPath += kDirChar + parentName; + } + + this->clipName = leafName; + + + tempPath += kDirChar; + tempPath += leafName; + + // NRT file Check as already cover in checkformat + if ( ! MakeClipFilePath( &this->mNRTFilePath, "M01.XML", true ) ) + { + XMP_Error error( kXMPErr_FilePathNotAFile, "Clip NRT XML file must be exist" ); + NotifyClient( &this->parent->errorCallback, kXMPErrSev_FileFatal, error ); + } + + // Setting correct sidecar path covering both .XMP and .xmp + if ( ! ( MakeClipFilePath( &this->sidecarPath, "M01.XMP", true ) || MakeClipFilePath( &this->sidecarPath, "M01.xmp", true ) ) ) + this->sidecarPath = tempPath + "M01.XMP"; + +} // XDCAMSAM_MetaHandler::SetPathVariables + +// ================================================================================================= +// XDCAMSAM_MetaHandler::FillAssociatedResources +// ==================================== +void XDCAMSAM_MetaHandler::FillAssociatedResources( std::vector * resourceList ) +{ + + std::string proavPath = rootPath + kDirChar + "PROAV" + kDirChar; + std::string filePath; + + //Add RootPath + filePath = rootPath + kDirChar; + PackageFormat_Support::AddResourceIfExists( resourceList, filePath ); + + // Get the files present directly inside PROAV folder. + filePath = proavPath + "INDEX.XML"; + PackageFormat_Support::AddResourceIfExists(resourceList, filePath); + filePath = proavPath + "INDEX.BUP"; + PackageFormat_Support::AddResourceIfExists(resourceList, filePath); + + filePath = proavPath + "DISCINFO.XML"; + PackageFormat_Support::AddResourceIfExists(resourceList, filePath); + filePath = proavPath + "DISCINFO.BUP"; + PackageFormat_Support::AddResourceIfExists(resourceList, filePath); + + filePath = proavPath + "DISCMETA.XML"; + PackageFormat_Support::AddResourceIfExists(resourceList, filePath); + + // Covering files in clipname folder in CLPR folder + XMP_VarString clipPath = proavPath + "CLPR" + kDirChar + clipName + kDirChar; + XMP_VarString regExp; + XMP_StringVector regExpVec; + + // ClipInfo file + regExp = "^" + clipName + "C\\d\\d.SMI$"; + regExpVec.push_back ( regExp ); + // Non-Real time metadata file + regExp = "^" + clipName + "M\\d\\d.XML$"; + regExpVec.push_back ( regExp ); + // Video file + regExp = "^" + clipName + "V\\d\\d.MXF$"; + regExpVec.push_back ( regExp ); + // Audio File + regExp = "^" + clipName + "A\\d\\d.MXF$"; + regExpVec.push_back ( regExp ); + // Real time metadata file + regExp = "^" + clipName + "R\\d\\d.BIM$"; + regExpVec.push_back ( regExp ); + // Picture pointer file + regExp = "^" + clipName + "I\\d\\d.PPN$"; + regExpVec.push_back ( regExp ); + // Sub(Proxy) files + regExp = "^" + clipName + "S\\d\\d.MXF$"; + regExpVec.push_back ( regExp ); + IOUtils::GetMatchingChildren ( *resourceList, clipPath, regExpVec, false, true, true ); + + // Adding sidecar file if exixts + PackageFormat_Support::AddResourceIfExists(resourceList, this->sidecarPath); + + // Add the Edit lists that refer this clip + std::vector editInfoList; + if( GetEditInfoFiles ( editInfoList ) ) + { + size_t noOfEditInfoFiles = editInfoList.size() ; + for( size_t count = 0; count < noOfEditInfoFiles; count++ ) + { + PackageFormat_Support::AddResourceIfExists(resourceList, editInfoList[count]); + std::string editNRTFile = editInfoList[count].c_str() ; + size_t filenamelen = editInfoList[count].length() ; + editNRTFile[ filenamelen - 7 ] = 'M'; + editNRTFile[ filenamelen - 3 ] = 'X'; + editNRTFile[ filenamelen - 2 ] = 'M'; + editNRTFile[ filenamelen - 1 ] = 'L'; + PackageFormat_Support::AddResourceIfExists(resourceList, editNRTFile ); + } + } +} // XDCAMSAM_MetaHandler::FillAssociatedResources + +// ================================================================================================= +// XDCAMSAM_MetaHandler::MakeClipFilePath +// ==================================== +bool XDCAMSAM_MetaHandler::MakeClipFilePath ( std::string * path, XMP_StringPtr suffix, bool checkFile /* = false */ ) +{ + *path = this->rootPath; + *path += kDirChar; + *path += "PROAV"; + *path += kDirChar; + + *path += "CLPR"; // ! Yes, mixed case. + + *path += kDirChar; + *path += this->clipName; + *path += kDirChar; + *path += this->clipName; + *path += suffix; + + if ( ! checkFile ) return true; + return Host_IO::Exists ( path->c_str() ); + +} // XDCAMSAM_MetaHandler::MakeClipFilePath + +// ================================================================================================= +// XDCAMSAM_MetaHandler::GetEditInfoFiles +// ==================================== +bool XDCAMSAM_MetaHandler::GetEditInfoFiles ( std::vector &editInfoList ) +{ + std::string clipUmid; + bool found = false; + + if( GetClipUmid ( clipUmid ) ) + { + std::string editFolderPath = this->rootPath + kDirChar + "PROAV" + kDirChar + "EDTR" + kDirChar ; + if ( Host_IO::Exists( editFolderPath.c_str() ) && + Host_IO::GetFileMode( editFolderPath.c_str() ) == Host_IO::kFMode_IsFolder + ) + { + Host_IO::AutoFolder edtrFolder, editFolder; + std::string edtrChildName, edlistChild; + + edtrFolder.folder = Host_IO::OpenFolder ( editFolderPath.c_str() ); + while ( Host_IO::GetNextChild ( edtrFolder.folder, &edtrChildName ) ) { + size_t childLen = edtrChildName.size(); + std::string editListFolderPath = editFolderPath + edtrChildName + kDirChar ; + if ( ! ( childLen == 5 && + edtrChildName[0] == 'E' && + IsDigit( edtrChildName[1] ) && + IsDigit( edtrChildName[2] ) && + IsDigit( edtrChildName[3] ) && + IsDigit( edtrChildName[4] ) && + Host_IO::GetFileMode( editListFolderPath.c_str() ) == Host_IO::kFMode_IsFolder + ) ) continue; + + editFolder.folder = Host_IO::OpenFolder ( editListFolderPath.c_str() ); + while ( Host_IO::GetNextChild ( editFolder.folder, &edlistChild ) ) { + size_t filenamelen = edlistChild.size(); + std::string editListFilePath = editListFolderPath + edlistChild ; + if ( ! ( filenamelen == 12 && + edlistChild.compare ( filenamelen - 4, 4 , ".SMI" ) == 0 && + edlistChild.compare ( 0, edtrChildName.size(), edtrChildName ) == 0 && + Host_IO::GetFileMode( editListFilePath.c_str() ) == Host_IO::kFMode_IsFile + ) ) continue; + if( RefersClipUmid ( clipUmid , editListFilePath.c_str() ) ) + { + found = true ; + editInfoList.push_back( editListFilePath ); + } + } + } + } + } + return found; +} // XDCAMSAM_MetaHandler::GetEditInfoFiles + +// ================================================================================================= +// XDCAMSAM_MetaHandler::GetClipUmid +// ============================== +bool XDCAMSAM_MetaHandler::GetClipUmid ( std::string &clipUmid ) +{ + std::string clipInfoPath; + ExpatAdapter* clipInfoExpat = 0 ; + bool umidFound = false; + XMP_StringPtr nameSpace = 0; + try { + this->MakeClipFilePath ( &clipInfoPath, "C01.SMI" ) ; + readXMLFile( clipInfoPath.c_str(), clipInfoExpat ); + if ( clipInfoExpat != 0 ) + { + XML_Node & xmlTree = clipInfoExpat->tree; + XML_NodePtr rootElem = 0; + + for ( size_t i = 0, limit = xmlTree.content.size(); i < limit; ++i ) { + if ( xmlTree.content[i]->kind == kElemNode ) { + rootElem = xmlTree.content[i]; + } + } + if ( rootElem != 0 ) + { + XMP_StringPtr rootLocalName = rootElem->name.c_str() + rootElem->nsPrefixLen; + + if ( XMP_LitMatch ( rootLocalName, "smil" ) ) + { + XMP_StringPtr umidValue = rootElem->GetAttrValue ( "umid" ); + if ( umidValue != 0 ) { + clipUmid = umidValue; + umidFound = true; + } + } + } + } + if( ! umidFound ) + { //try to get the umid from the NRT metadata + delete ( clipInfoExpat ) ; clipInfoExpat = 0; + this->MakeClipFilePath ( &clipInfoPath, "M01.XML" ) ; + readXMLFile( clipInfoPath.c_str(), clipInfoExpat ) ; + if ( clipInfoExpat != 0 ) + { + XML_Node & xmlTree = clipInfoExpat->tree; + XML_NodePtr rootElem = 0; + for ( size_t i = 0, limit = xmlTree.content.size(); i < limit; ++i ) { + if ( xmlTree.content[i]->kind == kElemNode ) { + rootElem = xmlTree.content[i]; + } + } + if ( rootElem != 0 ) + { + XMP_StringPtr rootLocalName = rootElem->name.c_str() + rootElem->nsPrefixLen; + + if ( XMP_LitMatch ( rootLocalName, "NonRealTimeMeta" ) ) + { + nameSpace = rootElem->ns.c_str() ; + XML_NodePtr targetProp = rootElem->GetNamedElement ( nameSpace, "TargetMaterial" ); + if ( (targetProp != 0) && targetProp->IsEmptyLeafNode() ) { + XMP_StringPtr umidValue = targetProp->GetAttrValue ( "umidRef" ); + if ( umidValue != 0 ) { + clipUmid = umidValue; + umidFound = true; + } + } + } + } + } + } + } catch ( ... ) { + } + delete ( clipInfoExpat ) ; + return umidFound; +} // XDCAMSAM_MetaHandler::GetClipUmid + +// ================================================================================================= + diff --git a/core/libs/dngwriter/extra/xmp_sdk/XMPFiles/source/FileHandlers/XDCAMSAM_Handler.hpp b/core/libs/dngwriter/extra/xmp_sdk/XMPFiles/source/FileHandlers/XDCAMSAM_Handler.hpp new file mode 100644 index 0000000000..b68334bf25 --- /dev/null +++ b/core/libs/dngwriter/extra/xmp_sdk/XMPFiles/source/FileHandlers/XDCAMSAM_Handler.hpp @@ -0,0 +1,58 @@ +#ifndef __XDCAMSAM_Handler_hpp__ +#define __XDCAMSAM_Handler_hpp__ 1 + +// ================================================================================================= +// ADOBE SYSTEMS INCORPORATED +// Copyright 2015 Adobe Systems Incorporated +// All Rights Reserved +// +// NOTICE: Adobe permits you to use, modify, and distribute this file in accordance with the terms +// of the Adobe license agreement accompanying it. +// ================================================================================================= + +#include "public/include/XMP_Environment.h" // ! This must be the first include. +#include "XMPFiles/source/FileHandlers/XDCAM_Handler.hpp" + + +extern XMPFileHandler * XDCAMSAM_MetaHandlerCTor ( XMPFiles * parent ); + +extern bool XDCAMSAM_CheckFormat ( XMP_FileFormat format, + const std::string & rootPath, + const std::string & groupName, + const std::string & parentName, + const std::string & leafName, + XMPFiles * parent ); + +static const XMP_OptionBits kXDCAMSAM_HandlerFlags = (kXMPFiles_CanInjectXMP | + kXMPFiles_CanExpand | + kXMPFiles_CanRewrite | + kXMPFiles_PrefersInPlace | + kXMPFiles_CanReconcile | + kXMPFiles_AllowsOnlyXMP | + kXMPFiles_ReturnsRawPacket | + kXMPFiles_HandlerOwnsFile | + kXMPFiles_AllowsSafeUpdate | + kXMPFiles_FolderBasedFormat); + +class XDCAMSAM_MetaHandler : public XDCAM_MetaHandler +{ + +public: + + void FillAssociatedResources ( std::vector * resourceList ); + XDCAMSAM_MetaHandler ( XMPFiles * _parent ); + virtual ~XDCAMSAM_MetaHandler() { }; + +private: + + bool MakeClipFilePath ( std::string * path, XMP_StringPtr suffix, bool checkFile = false ); + void SetPathVariables ( const std::string & clientPath ); + bool GetEditInfoFiles ( std::vector &editInfoList ); + bool GetClipUmid ( std::string &clipUmid ); + + XDCAMSAM_MetaHandler() : XDCAM_MetaHandler() {}; // Hidden on purpose. + +}; // XDCAMSAM_MetaHandler + +// ================================================================================================= +#endif /* __XDCAMSAM_Handler_hpp__ */ diff --git a/core/libs/dngwriter/extra/xmp_sdk/XMPFiles/source/FileHandlers/XDCAM_Handler.cpp b/core/libs/dngwriter/extra/xmp_sdk/XMPFiles/source/FileHandlers/XDCAM_Handler.cpp new file mode 100644 index 0000000000..af211ade65 --- /dev/null +++ b/core/libs/dngwriter/extra/xmp_sdk/XMPFiles/source/FileHandlers/XDCAM_Handler.cpp @@ -0,0 +1,677 @@ +// ================================================================================================= +// ADOBE SYSTEMS INCORPORATED +// Copyright 2007 Adobe Systems Incorporated +// All Rights Reserved +// +// NOTICE: Adobe permits you to use, modify, and distribute this file in accordance with the terms +// of the Adobe license agreement accompanying it. +// ================================================================================================= + +#include "public/include/XMP_Environment.h" // ! XMP_Environment.h must be the first included header. + +#include "public/include/XMP_Const.h" +#include "public/include/XMP_IO.hpp" + +#include "XMPFiles/source/XMPFiles_Impl.hpp" +#include "source/XMPFiles_IO.hpp" +#include "source/XIO.hpp" +#include "source/IOUtils.hpp" + +#include "XMPFiles/source/FileHandlers/XDCAM_Handler.hpp" +#include "XMPFiles/source/FormatSupport/XDCAM_Support.hpp" +#include "XMPFiles/source/FormatSupport/PackageFormat_Support.hpp" +#include "XMP_MD5.h" + +using namespace std; + +// ================================================================================================= +/// \file XDCAM_Handler.cpp +/// \brief Folder format handler for XDCAM. +/// +/// This handler is for the XDCAM video format. This is a pseudo-package, visible files but with a very +/// well-defined layout and naming rules. There are 2 different layouts for XDCAM, called FAM and SAM. +/// The FAM layout is used by "normal" XDCAM devices. The SAM layout is used by XDCAM-EX devices. +/// +/// A typical FAM layout looks like (note mixed case for the nested folders): +/// +/// .../MyMovie/ +/// INDEX.XML +/// DISCMETA.XML +/// MEDIAPRO.XML +/// General/ +/// unknown files +/// Clip/ +/// C0001.MXF +/// C0001M01.XML +/// C0001M01.XMP +/// C0002.MXF +/// C0002M01.XML +/// C0002M01.XMP +/// Sub/ +/// C0001S01.MXF +/// C0002S01.MXF +/// Edit/ +/// E0001E01.SMI +/// E0001M01.XML +/// E0002E01.SMI +/// E0002M01.XML +/// +/// A typical FAM XMPilot layout looks like (note mixed case for the nested folders): +/// +/// .../MyMovie/ +/// DISCMETA.XML +/// MEDIAPRO.XML +/// General/ +/// Clip/ +/// Office_0001.MXF +/// Office_0001M01.XML +/// Office_0001M01.XMP +/// Office_0002.MXF +/// Office_0002M01.XML +/// Office_0002M01.XMP +/// Sub/ +/// Office_0001S01.MXF +/// Office_0002S01.MXF +/// Edit/ +/// UserData/ +/// unknown files +/// +/// A typical FAM XDCAM Memory SxS layout looks like (note mixed case for the nested folders): +/// +/// .../MyMovie/ +/// DISCMETA.XML +/// MEDIAPRO.XML +/// CUEUP.XML +/// General/ +/// Clip/ +/// C0001.MXF +/// C0001M01.XML +/// C0001M01.XMP +/// C0001R01.BIM +/// C0002.MXF +/// C0002M01.XML +/// C0002M01.XMP +/// C0001R01.BIM +/// Sub/ +/// C0001S01.MXF +/// C0002S01.MXF +/// Edit/ +/// Take/ +/// T0001.SMI +/// T0001M01.XML +/// UserData/ +/// +/// A typical SAM layout looks like: +/// +/// .../MyMovie/ +/// GENERAL/ +/// unknown files +/// PROAV/ +/// INDEX.XML +/// INDEX.BUP +/// DISCMETA.XML +/// DISCINFO.XML +/// DISCINFO.BUP +/// CLPR/ +/// C0001/ +/// C0001C01.SMI +/// C0001V01.MXF +/// C0001A01.MXF +/// C0001A02.MXF +/// C0001R01.BIM +/// C0001I01.PPN +/// C0001M01.XML +/// C0001M01.XMP +/// C0001S01.MXF +/// C0002/ +/// ... +/// EDTR/ +/// E0001/ +/// E0001E01.SMI +/// E0001M01.XML +/// E0002/ +/// ... +/// +/// Note that the Sony documentation uses the folder names "General", "Clip", "Sub", and "Edit". We +/// use all caps here. Common code has already shifted the names, we want to be case insensitive. +/// +/// From the user's point of view, .../MyMovie contains XDCAM stuff, in this case 2 clips whose raw +/// names are C0001 and C0002. There may be mapping information for nicer clip names to the raw +/// names, but that can be ignored for now. Each clip is stored as a collection of files, each file +/// holding some specific aspect of the clip's data. +/// +/// The XDCAM handler operates on clips. The path from the client of XMPFiles can be either a logical +/// clip path, like ".../MyMovie/C0001", or a full path to one of the files. In the latter case the +/// handler must figure out the intended clip, it must not blindly use the named file. +/// +/// Once the XDCAM structure and intended clip are identified, the handler only deals with the .XMP +/// and .XML files in the CLIP or CLPR/ folders. The .XMP file, if present, contains the XMP +/// for the clip. The .XML file must be present to define the existance of the clip. It contains a +/// variety of information about the clip, including some legacy metadata. +/// +// ================================================================================================= + +// ================================================================================================= +// XDCAM_CheckFormat +// ================= +// +// This version does fairly simple checks. The top level folder (.../MyMovie) must have exactly 1 +// child, a folder called CONTENTS. This must have a subfolder called CLIP. It may also have +// subfolders called VIDEO, AUDIO, ICON, VOICE, and PROXY. Any mixture of these additional folders +// is allowed, but no other children are allowed in CONTENTS. The CLIP folder must contain a .XML +// file for the desired clip. The name checks are case insensitive. +// +// The state of the string parameters depends on the form of the path passed by the client. If the +// client passed a logical clip path, like ".../MyMovie/C0001", the parameters are: +// rootPath - ".../MyMovie" +// gpName - empty +// parentName - empty +// leafName - "C0001" +// +// If the client passed a FAM file path, like ".../MyMovie/Edit/E0001E01.SMI", they are: +// rootPath - "..." +// gpName - "MyMovie" +// parentName - "EDIT" (common code has shifted the case) +// leafName - "E0001E01" +// +// If the client passed a SAM file path, like ".../MyMovie/PROAV/CLPR/C0001/C0001A02.MXF", they are: +// rootPath - ".../MyMovie/PROAV" +// gpName - "CLPR" +// parentName - "C0001" +// leafName - "C0001A02" +// +// For both FAM and SAM the leading character of the leafName for an existing file might be coerced +// to 'C' to form the logical clip name. And suffix such as "M01" must be removed for FAM. We don't +// need to worry about that for SAM, that uses the folder name. + +// ! The FAM format supports general clip file names through an ALIAS.XML mapping file. The simple +// ! existence check has an edge case bug, left to be fixed later. If the ALIAS.XML file exists, but +// ! some of the clips still have "raw" names, and we're passed an existing file path in the EDIT +// ! folder, we will fail to do the leading 'E' to 'C' coercion. We might also erroneously remove a +// ! suffix from a mapped essence file with a name like ClipX01.MXF. + +// ! The common code has shifted the gpName, parentName, and leafName strings to uppercase. It has +// ! also made sure that for a logical clip path the rootPath is an existing folder, and that the +// ! file exists for a full file path. + +// ================================================================================================= +// XDCAM_MetaHandler::XDCAM_MetaHandler +// ==================================== +XDCAM_MetaHandler::XDCAM_MetaHandler ( XMPFiles * _parent ) : expat(0), clipMetadata(NULL) +{ + + this->parent = _parent; // Inherited, can't set in the prefix. + this->stdCharForm = kXMP_Char8Bit; + +} // XDCAM_MetaHandler::XDCAM_MetaHandler + +// ================================================================================================= +// XDCAM_MetaHandler::~XDCAM_MetaHandler +// ===================================== + +XDCAM_MetaHandler::~XDCAM_MetaHandler() +{ + + this->CleanupLegacyXML(); + if ( this->parent->tempPtr != 0 ) { + free ( this->parent->tempPtr ); + this->parent->tempPtr = 0; + } + +} // XDCAM_MetaHandler::~XDCAM_MetaHandler + +// ================================================================================================= +// XDCAM_MetaHandler::MakeMediaproPath +// =================================== + +bool XDCAM_MetaHandler::MakeMediaproPath ( std::string * path, bool checkFile /* = false */ ) +{ + + *path = this->rootPath; + *path += kDirChar; + *path += "MEDIAPRO.XML"; + + if ( ! checkFile ) return true; + return Host_IO::Exists ( path->c_str() ); + +} // XDCAM_MetaHandler::MakeMediaproPath + +// ================================================================================================= +// XDCAM_MetaHandler::MakeLegacyDigest +// =================================== + +// *** Early hack version. + +#define kHexDigits "0123456789ABCDEF" + +// ================================================================================================= +// XDCAM_MetaHandler::MakeLegacyDigest +// ================================ + +void XDCAM_MetaHandler::MakeLegacyDigest ( std::string * digestStr ) +{ + digestStr->erase(); + if ( this->clipMetadata == 0 ) return; // Bail if we don't have any legacy XML. + XMP_Assert ( this->expat != 0 ); + + XMP_StringPtr xdcNS = this->xdcNS.c_str(); + XML_NodePtr legacyContext, legacyProp; + + legacyContext = this->clipMetadata->GetNamedElement ( xdcNS, "Access" ); + if ( legacyContext == 0 ) return; + + MD5_CTX context; + unsigned char digestBin [16]; + MD5Init ( &context ); + + legacyProp = legacyContext->GetNamedElement ( xdcNS, "Creator" ); + if ( (legacyProp != 0) && legacyProp->IsLeafContentNode() && (! legacyProp->content.empty()) ) { + const XML_Node * xmlValue = legacyProp->content[0]; + MD5Update ( &context, (XMP_Uns8*)xmlValue->value.c_str(), (unsigned int)xmlValue->value.size() ); + } + + legacyProp = legacyContext->GetNamedElement ( xdcNS, "CreationDate" ); + if ( (legacyProp != 0) && legacyProp->IsLeafContentNode() && (! legacyProp->content.empty()) ) { + const XML_Node * xmlValue = legacyProp->content[0]; + MD5Update ( &context, (XMP_Uns8*)xmlValue->value.c_str(), (unsigned int)xmlValue->value.size() ); + } + + legacyProp = legacyContext->GetNamedElement ( xdcNS, "LastUpdateDate" ); + if ( (legacyProp != 0) && legacyProp->IsLeafContentNode() && (! legacyProp->content.empty()) ) { + const XML_Node * xmlValue = legacyProp->content[0]; + MD5Update ( &context, (XMP_Uns8*)xmlValue->value.c_str(), (unsigned int)xmlValue->value.size() ); + } + + MD5Final ( digestBin, &context ); + + char buffer [40]; + for ( int in = 0, out = 0; in < 16; in += 1, out += 2 ) { + XMP_Uns8 byte = digestBin[in]; + buffer[out] = kHexDigits [ byte >> 4 ]; + buffer[out+1] = kHexDigits [ byte & 0xF ]; + } + buffer[32] = 0; + digestStr->append ( buffer ); + +} // XDCAM_MetaHandler::MakeLegacyDigest + +// ================================================================================================= +// XDCAM_MetaHandler::CleanupLegacyXML +// ================================ + +void XDCAM_MetaHandler::CleanupLegacyXML() +{ + + if ( this->expat != 0 ) { delete ( this->expat ); this->expat = 0; } + + clipMetadata = 0; // ! Was a pointer into the expat tree. + +} // XDCAM_MetaHandler::CleanupLegacyXML + +// ================================================================================================= +// XDCAM_MetaHandler::readXMLFile +// ================================ + +void XDCAM_MetaHandler::readXMLFile( XMP_StringPtr filePath, ExpatAdapter* &expat ) +{ + Host_IO::FileRef hostRef = Host_IO::Open ( filePath, Host_IO::openReadOnly ); + if ( hostRef == Host_IO::noFileRef ) return; // The open failed. + XMPFiles_IO xmlFile ( hostRef, filePath, Host_IO::openReadOnly ); + + expat = XMP_NewExpatAdapter ( ExpatAdapter::kUseLocalNamespaces ); + if ( expat == 0 ) XMP_Throw ( "XDCAM_MetaHandler: Can't create Expat adapter", kXMPErr_NoMemory ); + + XMP_Uns8 buffer [64*1024]; + while ( true ) { + XMP_Int32 ioCount = xmlFile.Read ( buffer, sizeof(buffer) ); + if ( ioCount == 0 ) break; + expat->ParseBuffer ( buffer, ioCount, false /* not the end */ ); + } + expat->ParseBuffer ( 0, 0, true ); // End the parse. + + xmlFile.Close(); +} + +// ================================================================================================= +// XDCAM_MetaHandler::GetFileModDate +// ================================= + +static inline bool operator< ( const XMP_DateTime & left, const XMP_DateTime & right ) { + int compare = SXMPUtils::CompareDateTime ( left, right ); + return (compare < 0); +} + +// ================================================================================================= +// XDCAM_MetaHandler::GetFileModDate +// ================================ + +bool XDCAM_MetaHandler::GetFileModDate ( XMP_DateTime * modDate ) +{ + + // Modify date is found in the increasing priority order + // + // MEDIAPRO.XML + // Non-Real time metadata file + // XMP file + + bool ok, haveDate = false; + std::string fullPath; + XMP_DateTime oneDate, junkDate; + if ( modDate == 0 ) modDate = &junkDate; + + // MEDIAPRO.XML + std::string mediaproPath; + ok = MakeMediaproPath ( &mediaproPath, true /* checkFile */ ); + if ( ok ) ok = Host_IO::GetModifyDate ( mediaproPath.c_str(), &oneDate ); + if ( ok ) { + if ( (! haveDate) ) *modDate = oneDate; + haveDate = true; + } + + // Non-Real time metadata file + ok = Host_IO::Exists( this->mNRTFilePath.c_str() ); + //ok = this->MakeClipFilePath ( &fullPath, "M01.XML", true /* checkFile */ ); + if ( ok ) ok = Host_IO::GetModifyDate ( this->mNRTFilePath.c_str(), &oneDate ); + if ( ok ) { + if ( (! haveDate) || (*modDate < oneDate) ) *modDate = oneDate; + haveDate = true; + } + + // XMP file + ok = Host_IO::Exists( this->sidecarPath.c_str() ); + //ok = this->MakeClipFilePath ( &fullPath, "M01.XMP", true /* checkFile */ ); + if ( ok ) ok = Host_IO::GetModifyDate ( this->sidecarPath.c_str(), &oneDate ); + if ( ok ) { + if ( (! haveDate) || (*modDate < oneDate) ) *modDate = oneDate; + haveDate = true; + } + + return haveDate; + +} // XDCAM_MetaHandler::GetFileModDate + +// ================================================================================================= +// XDCAM_MetaHandler::RefersClipUmid +// ================================== +bool XDCAM_MetaHandler::RefersClipUmid ( std::string clipUmid , XMP_StringPtr editInfoPath ) +{ + ExpatAdapter* editInfoExpat = 0 ; + XMP_StringPtr nameSpace = 0 ; + try { + readXMLFile( editInfoPath, editInfoExpat ); + if ( editInfoExpat != 0 ) + { + XML_Node & xmlTree = editInfoExpat->tree; + XML_NodePtr rootElem = 0; + + for ( size_t i = 0, limit = xmlTree.content.size(); i < limit; ++i ) { + if ( xmlTree.content[i]->kind == kElemNode ) { + rootElem = xmlTree.content[i]; + } + } + if ( rootElem != 0 ) + { + XMP_StringPtr rootLocalName = rootElem->name.c_str() + rootElem->nsPrefixLen; + + if ( XMP_LitMatch ( rootLocalName, "smil" ) ) + { + nameSpace = rootElem->ns.c_str() ; + size_t noOfBodyElements = rootElem->CountNamedElements ( nameSpace, "body" ) ; + while( noOfBodyElements-- ) + { + XML_NodePtr bodyNode = rootElem->GetNamedElement( nameSpace, "body" ); + size_t noOfParElements = bodyNode->CountNamedElements ( nameSpace, "par" ) ; + while( noOfParElements-- ) + { + XML_NodePtr parNode = bodyNode->GetNamedElement( nameSpace, "par" ); + size_t noOfRefElements = parNode->CountNamedElements ( nameSpace, "ref" ) ; + size_t whichElem = 0; + while( noOfRefElements-- ) + { + XML_NodePtr refNode = parNode->GetNamedElement( nameSpace, "ref" ,whichElem++ ); + XMP_StringPtr umidValue = refNode->GetAttrValue ( "src" ); + if ( umidValue != 0 && + ( XMP_LitMatch( umidValue , clipUmid.c_str() ) || + ( strlen(umidValue) > 15 && XMP_LitMatch( &umidValue[15] , clipUmid.c_str() ) ) + ) + ) + { + delete ( editInfoExpat ) ; + return true; + } + } + } + } + } + } + } + + } catch ( ... ) { + } + delete ( editInfoExpat ) ; + return false; +} // XDCAM_MetaHandler::RefersClipUmid + +// ================================================================================================= +// XDCAM_MetaHandler::IsMetadataWritable +// ======================================= +bool XDCAM_MetaHandler::IsMetadataWritable ( ) +{ + std::vector metadataFiles; + FillMetadataFiles(&metadataFiles); + std::vector::iterator itr = metadataFiles.begin(); + // Check whether sidecar is writable, if not then check if it can be created. + bool xmpWritable = Host_IO::Writable( itr->c_str(), true ); + // Check for legacy metadata file. + bool xmlWritable = Host_IO::Writable( (++itr)->c_str(), false ); + return ( xmlWritable && xmpWritable ); +}// XDCAM_MetaHandler::IsMetadataWritable + +// ================================================================================================= +// XDCAM_MetaHandler::FillMetadataFiles +// ==================================== +void XDCAM_MetaHandler::FillMetadataFiles ( std::vector * metadataFiles ) +{ + metadataFiles->push_back( this->sidecarPath ); + metadataFiles->push_back( this->mNRTFilePath ); + +} // XDCAM_MetaHandler::FillMetadataFiles + +// ================================================================================================= +// XDCAM_MetaHandler::CacheFileData +// ================================ + +void XDCAM_MetaHandler::CacheFileData() +{ + XMP_Assert ( ! this->containsXMP ); + + if ( this->parent->UsesClientIO() ) { + XMP_Throw ( "XDCAM cannot be used with client-managed I/O", kXMPErr_InternalFailure ); + } + + // See if the clip's .XMP file exists. + if ( ! Host_IO::Exists ( this->sidecarPath.c_str() ) ) return; // No XMP. + + // Read the entire .XMP file. We know the XMP exists, New_XMPFiles_IO is supposed to return 0 + // only if the file does not exist. + + bool readOnly = XMP_OptionIsClear ( this->parent->openFlags, kXMPFiles_OpenForUpdate ); + + XMP_Assert ( this->parent->ioRef == 0 ); + + XMPFiles_IO* xmpFile = XMPFiles_IO::New_XMPFiles_IO ( this->sidecarPath.c_str(), readOnly ); + if ( xmpFile == 0 ) XMP_Throw ( "XDCAM XMP file open failure", kXMPErr_InternalFailure ); + this->parent->ioRef = xmpFile; + + XMP_Int64 xmpLen = xmpFile->Length(); + if ( xmpLen > 100*1024*1024 ) { + XMP_Throw ( "XDCAM XMP is outrageously large", kXMPErr_InternalFailure ); // Sanity check. + } + + this->xmpPacket.erase(); + this->xmpPacket.append ( (size_t)xmpLen, ' ' ); + + XMP_Int32 ioCount = xmpFile->ReadAll ( (void*)this->xmpPacket.data(), (XMP_Int32)xmpLen ); + + this->packetInfo.offset = 0; + this->packetInfo.length = (XMP_Int32)xmpLen; + FillPacketInfo ( this->xmpPacket, &this->packetInfo ); + + this->containsXMP = true; + +} // XDCAM_MetaHandler::CacheFileData + +// ================================================================================================= +// XDCAM_MetaHandler::GetMediaProMetadata +// ====================================== + +// ================================================================================================= +// XDCAM_MetaHandler::ProcessXMP +// ============================= + +void XDCAM_MetaHandler::ProcessXMP() +{ + + // Some versions of gcc can't tolerate goto's across declarations. + // *** Better yet, avoid this cruft with self-cleaning objects. + #define CleanupAndExit \ + { \ + bool openForUpdate = XMP_OptionIsSet ( this->parent->openFlags, kXMPFiles_OpenForUpdate ); \ + if ( ! openForUpdate ) this->CleanupLegacyXML(); \ + return; \ + } + + if ( this->processedXMP ) return; + this->processedXMP = true; // Make sure only called once. + + if ( this->containsXMP ) { + this->xmpObj.ParseFromBuffer ( this->xmpPacket.c_str(), (XMP_StringLen)this->xmpPacket.size() ); + } + + // NonRealTimeMeta -> XMP by schema + std::string xmlPath = this->mNRTFilePath; + std::string umid; + + readXMLFile( xmlPath.c_str(), this->expat ); + if ( this->expat == 0 ) return; + + // The root element should be NonRealTimeMeta in some namespace. Take whatever this file uses. + + XML_Node & xmlTree = this->expat->tree; + XML_NodePtr rootElem = 0; + + for ( size_t i = 0, limit = xmlTree.content.size(); i < limit; ++i ) { + if ( xmlTree.content[i]->kind == kElemNode ) { + rootElem = xmlTree.content[i]; + } + } + + if ( rootElem == 0 ) CleanupAndExit + XMP_StringPtr rootLocalName = rootElem->name.c_str() + rootElem->nsPrefixLen; + if ( ! XMP_LitMatch ( rootLocalName, "NonRealTimeMeta" ) ) CleanupAndExit + + this->legacyNS = rootElem->ns; + + // Check the legacy digest. + + XMP_StringPtr legacyNS = this->legacyNS.c_str(); + + this->clipMetadata = rootElem; // ! Save the NonRealTimeMeta pointer for other use. + + std::string oldDigest, newDigest; + bool digestFound = this->xmpObj.GetStructField ( kXMP_NS_XMP, "NativeDigests", kXMP_NS_XMP, "XDCAM", &oldDigest, 0 ); + if ( digestFound ) { + this->MakeLegacyDigest ( &newDigest ); + if ( oldDigest == newDigest ) CleanupAndExit + } + + // If we get here we need find and import the actual legacy elements using the current namespace. + // Either there is no old digest in the XMP, or the digests differ. In the former case keep any + // existing XMP, in the latter case take new legacy values. + + this->containsXMP = XDCAM_Support::GetLegacyMetadata ( &this->xmpObj, rootElem, legacyNS, digestFound, umid ); + this->containsXMP |= GetMediaProMetadata ( &this->xmpObj, umid, digestFound ); + + CleanupAndExit + #undef CleanupAndExit + +} // XDCAM_MetaHandler::ProcessXMP + +// ================================================================================================= +// XDCAM_MetaHandler::UpdateFile +// ============================= +// +// Note that UpdateFile is only called from XMPFiles::CloseFile, so it is OK to close the file here. + +void XDCAM_MetaHandler::UpdateFile ( bool doSafeUpdate ) +{ + if ( ! this->needsUpdate ) return; + this->needsUpdate = false; // Make sure only called once. + + XMP_Assert ( this->parent->UsesLocalIO() ); + + // Update the internal legacy XML tree if we have one, and set the digest in the XMP. + + bool updateLegacyXML = false; + + if ( this->clipMetadata != 0 ) { + updateLegacyXML = XDCAM_Support::SetLegacyMetadata ( this->clipMetadata, &this->xmpObj, this->legacyNS.c_str()); + } + + std::string newDigest; + this->MakeLegacyDigest ( &newDigest ); + this->xmpObj.SetStructField ( kXMP_NS_XMP, "NativeDigests", kXMP_NS_XMP, "XDCAM", newDigest.c_str(), kXMP_DeleteExisting ); + this->xmpObj.SerializeToBuffer ( &this->xmpPacket, this->GetSerializeOptions() ); + + // ----------------------------------------------------------------------- + // Update the XMP file first, don't let legacy XML failures block the XMP. + + + + bool haveXMP = Host_IO::Exists ( this->sidecarPath.c_str() ); + if ( ! haveXMP ) { + XMP_Assert ( this->parent->ioRef == 0 ); + Host_IO::Create ( this->sidecarPath.c_str() ); + this->parent->ioRef = XMPFiles_IO::New_XMPFiles_IO ( this->sidecarPath.c_str(), Host_IO::openReadWrite ); + if ( this->parent->ioRef == 0 ) XMP_Throw ( "Failure opening XDCAM XMP file", kXMPErr_ExternalFailure ); + } + + XMP_IO* xmpFile = this->parent->ioRef; + XMP_Assert ( xmpFile != 0 ); + XIO::ReplaceTextFile ( xmpFile, this->xmpPacket, (haveXMP & doSafeUpdate) ); + + // -------------------------------------------- + // Now update the legacy XML file if necessary. + + if ( updateLegacyXML ) { + + std::string legacyXML, xmlPath; + this->expat->tree.Serialize ( &legacyXML ); + xmlPath = this->mNRTFilePath; + + bool haveXML = Host_IO::Exists ( xmlPath.c_str() ); + if ( ! haveXML ) Host_IO::Create ( xmlPath.c_str() ); + + Host_IO::FileRef hostRef = Host_IO::Open ( xmlPath.c_str(), Host_IO::openReadWrite ); + if ( hostRef == Host_IO::noFileRef ) XMP_Throw ( "Failure opening XDCAM XML file", kXMPErr_ExternalFailure ); + XMPFiles_IO origXML ( hostRef, xmlPath.c_str(), Host_IO::openReadWrite ); + XIO::ReplaceTextFile ( &origXML, legacyXML, (haveXML & doSafeUpdate) ); + origXML.Close(); + + } + +} // XDCAM_MetaHandler::UpdateFile + +// ================================================================================================= +// XDCAM_MetaHandler::WriteTempFile +// ================================ + +void XDCAM_MetaHandler::WriteTempFile ( XMP_IO* tempRef ) +{ + + // ! WriteTempFile is not supposed to be called for handlers that own the file. + XMP_Throw ( "XDCAM_MetaHandler::WriteTempFile should not be called", kXMPErr_InternalFailure ); + +} // XDCAM_MetaHandler::WriteTempFile + +// ================================================================================================= diff --git a/core/libs/dngwriter/extra/xmp_sdk/XMPFiles/source/FileHandlers/XDCAM_Handler.hpp b/core/libs/dngwriter/extra/xmp_sdk/XMPFiles/source/FileHandlers/XDCAM_Handler.hpp new file mode 100644 index 0000000000..e70621a8d7 --- /dev/null +++ b/core/libs/dngwriter/extra/xmp_sdk/XMPFiles/source/FileHandlers/XDCAM_Handler.hpp @@ -0,0 +1,86 @@ +#ifndef __XDCAM_Handler_hpp__ +#define __XDCAM_Handler_hpp__ 1 + +// ================================================================================================= +// ADOBE SYSTEMS INCORPORATED +// Copyright 2007 Adobe Systems Incorporated +// All Rights Reserved +// +// NOTICE: Adobe permits you to use, modify, and distribute this file in accordance with the terms +// of the Adobe license agreement accompanying it. +// ================================================================================================= + +#include "public/include/XMP_Environment.h" // ! This must be the first include. + +#include "XMPFiles/source/XMPFiles_Impl.hpp" + +#include "source/ExpatAdapter.hpp" + +// ================================================================================================= +/// \file XDCAM_Handler.hpp +/// \brief Folder format handler for XDCAM. +/// +/// This header ... +/// +// ================================================================================================= + +inline bool IsDigit( char c ) +{ + return c >= '0' && c <= '9'; +} + +class XDCAM_MetaHandler : public XMPFileHandler +{ +public: + + bool GetFileModDate ( XMP_DateTime * modDate ); + + virtual void FillAssociatedResources ( std::vector * resourceList ) {}; + bool IsMetadataWritable ( ) ; + + void CacheFileData(); + void ProcessXMP(); + + void UpdateFile ( bool doSafeUpdate ); + void WriteTempFile ( XMP_IO* tempRef ); + + XMP_OptionBits GetSerializeOptions() // *** These should be standard for standalone XMP files. + { return (kXMP_UseCompactFormat | kXMP_OmitPacketWrapper); }; + + XDCAM_MetaHandler ( XMPFiles * _parent ); + virtual ~XDCAM_MetaHandler(); + +protected: + + XDCAM_MetaHandler() : expat(0), clipMetadata(0) {}; // Hidden on purpose. + + virtual bool MakeClipFilePath ( std::string * path, XMP_StringPtr suffix, bool checkFile = false ) { return false; } + virtual void SetPathVariables ( const std::string & clientPath ) { } + virtual bool GetMediaProMetadata ( SXMPMeta * xmpObjPtr, const std::string& clipUMID, bool digestFound ) { + return false; + } + bool MakeMediaproPath ( std::string * path, bool checkFile = false ); + virtual bool GetClipUmid ( std::string &clipUmid ) { return false; } + void readXMLFile( XMP_StringPtr filePath,ExpatAdapter* &expat ); + bool RefersClipUmid ( std::string clipUmid , XMP_StringPtr editInfoPath ) ; + std::string rootPath, clipName, sidecarPath; + + std::string mNRTFilePath; + std::string oldSidecarPath; + +private: + + void FillMetadataFiles ( std::vector * metadataFiles ); + void MakeLegacyDigest ( std::string * digestStr ); + void CleanupLegacyXML(); + + std::string xdcNS, legacyNS; + + ExpatAdapter * expat; + XML_Node * clipMetadata; // ! Don't delete, points into the Expat tree. + +}; // XDCAM_MetaHandler + +// ================================================================================================= + +#endif /* __XDCAM_Handler_hpp__ */ diff --git a/core/libs/dngwriter/extra/xmp_sdk/XMPFiles/source/FormatSupport/AIFF/AIFFBehavior.cpp b/core/libs/dngwriter/extra/xmp_sdk/XMPFiles/source/FormatSupport/AIFF/AIFFBehavior.cpp new file mode 100644 index 0000000000..a97c2b0725 --- /dev/null +++ b/core/libs/dngwriter/extra/xmp_sdk/XMPFiles/source/FormatSupport/AIFF/AIFFBehavior.cpp @@ -0,0 +1,302 @@ +// ================================================================================================= +// ADOBE SYSTEMS INCORPORATED +// Copyright 2010 Adobe Systems Incorporated +// All Rights Reserved +// +// NOTICE: Adobe permits you to use, modify, and distribute this file in accordance with the terms +// of the Adobe license agreement accompanying it. +// ================================================================================================= + +#include "public/include/XMP_Environment.h" // ! XMP_Environment.h must be the first included header. +#include "public/include/XMP_Const.h" + +#include "XMPFiles/source/FormatSupport/AIFF/AIFFBehavior.h" +#include "XMPFiles/source/FormatSupport/IFF/Chunk.h" +#include "source/XMP_LibUtils.hpp" +#include "source/XIO.hpp" + +#include + +using namespace IFF_RIFF; + +// +// Static init +// +const BigEndian& AIFFBehavior::mEndian = BigEndian::getInstance(); + +//----------------------------------------------------------------------------- +// +// AIFFBehavior::getRealSize(...) +// +// Purpose: Validate the passed in size value, identify the valid size if the +// passed in isn't valid and return the valid size. +// Throw an exception if the passed in size isn't valid and there's +// no way to identify a valid size. +// +//----------------------------------------------------------------------------- + +XMP_Uns64 AIFFBehavior::getRealSize( const XMP_Uns64 size, const ChunkIdentifier& id, IChunkContainer& tree, XMP_IO* stream ) +{ + if( (size & 0x80000000) > 0 ) + { + XMP_Throw( "Unknown size value", kXMPErr_BadFileFormat ); + } + + return size; +} + +//----------------------------------------------------------------------------- +// +// AIFFBehavior::isValidTopLevelChunk(...) +// +// Purpose: Return true if the passed identifier is valid for top-level chunks +// of a certain format. +// +//----------------------------------------------------------------------------- + +bool AIFFBehavior::isValidTopLevelChunk( const ChunkIdentifier& id, XMP_Uns32 chunkNo ) +{ + return (chunkNo == 0) && (id.id == kChunk_FORM) && ((id.type == kType_AIFF) || (id.type == kType_AIFC)); +} + +//----------------------------------------------------------------------------- +// +// AIFFBehavior::getMaxChunkSize(...) +// +// Purpose: Return the maximum size of a single chunk, i.e. the maximum size +// of a top-level chunk. +// +//----------------------------------------------------------------------------- + +XMP_Uns64 AIFFBehavior::getMaxChunkSize() const +{ + return 0x80000000LL; // 2 GByte +} + +//----------------------------------------------------------------------------- +// +// AIFFBehavior::fixHierarchy(...) +// +// Purpose: Fix the hierarchy of chunks depending ones based on size changes of +// one or more chunks and second based on format specific rules. +// Throw an exception if the hierarchy can't be fixed. +// +//----------------------------------------------------------------------------- + +void AIFFBehavior::fixHierarchy( IChunkContainer& tree ) +{ + XMP_Validate( tree.numChildren() == 1, "AIFF files should only have one top level chunk (FORM)", kXMPErr_BadFileFormat); + Chunk* formChunk = tree.getChildAt(0); + + XMP_Validate( (formChunk->getType() == kType_AIFF) || (formChunk->getType() == kType_AIFC), "Invalid type for AIFF/AIFC top level chunk (FORM)", kXMPErr_BadFileFormat); + + if( formChunk->hasChanged() ) + { + // + // none of the modified chunks should be smaller than 12Byte + // + for( XMP_Uns32 i=0; inumChildren(); i++ ) + { + Chunk* chunk = formChunk->getChildAt(i); + + if( chunk->hasChanged() && chunk->getSize() != chunk->getOriginalSize() ) + { + XMP_Validate( chunk->getSize() >= Chunk::TYPE_SIZE, "Modified chunk smaller than 12bytes", kXMPErr_InternalFailure ); + } + } + + // + // move new added chunks to temporary container + // + Chunk* tmpContainer = Chunk::createChunk( mEndian ); + this->moveChunks( *formChunk, *tmpContainer, formChunk->numChildren() - mChunksAdded ); + + // + // for all children chunks until the last child of the initial list is reached + // try to arrange the chunks at the current location using exisiting free space + // or FREE chunks around, otherwise move the chunk to the end + // + this->arrangeChunksInPlace( *formChunk, *tmpContainer ); + + // + // for all chunks that were moved to the end try to find a FREE chunk for them + // + this->arrangeChunksInTree( *tmpContainer, *formChunk ); + + // + // append all remaining new added chunks to the end of the tree + // + this->moveChunks( *tmpContainer, *formChunk, 0 ); + delete tmpContainer; + + // + // check for FREE chunks at the end + // + Chunk* endFREE = this->mergeFreeChunks( *formChunk, formChunk->numChildren() - 1 ); + + if( endFREE != NULL ) + { + formChunk->removeChildAt( formChunk->numChildren() - 1 ); + delete endFREE; + } + + // + // Fix the offset values of all chunks. Throw an exception in the case that + // the offset of a non-modified chunk needs to be reset. + // + XMP_Validate( formChunk->getOffset() == 0, "Invalid offset for AIFF/AIFC top level chunk (FORM)", kXMPErr_InternalFailure ); + + this->validateOffsets( tree ); + } +} + +void AIFFBehavior::insertChunk( IChunkContainer& tree, Chunk& chunk ) +{ + XMP_Validate( tree.numChildren() == 1, "AIFF files should only have one top level chunk (FORM)", kXMPErr_BadFileFormat); + Chunk* formChunk = tree.getChildAt(0); + + XMP_Validate( (formChunk->getType() == kType_AIFF) || (formChunk->getType() == kType_AIFC), "Invalid type for AIFF/AIFC top level chunk (FORM)", kXMPErr_BadFileFormat); + + // add new chunk to the end of the AIFF:FORM + formChunk->appendChild(&chunk); + + mChunksAdded++; +} + +bool AIFFBehavior::removeChunk( IChunkContainer& tree, Chunk& chunk ) +{ + XMP_Validate( chunk.getID() != kChunk_FORM, "Can't remove FORM chunk!", kXMPErr_InternalFailure ); + XMP_Validate( chunk.getChunkMode() != CHUNK_UNKNOWN, "Cant' remove UNKNOWN Chunk", kXMPErr_InternalFailure ); + + XMP_Validate( tree.numChildren() == 1, "AIFF files should only have one top level chunk (FORM)", kXMPErr_BadFileFormat); + + Chunk* formChunk = tree.getChildAt(0); + + XMP_Validate( (formChunk->getType() == kType_AIFF) || (formChunk->getType() == kType_AIFC), "Invalid type for AIFF/AIFC top level chunk (FORM)", kXMPErr_BadFileFormat); + + XMP_Uns32 i = std::find( formChunk->firstChild(), formChunk->lastChild(), &chunk ) - formChunk->firstChild(); + + XMP_Validate( i < formChunk->numChildren(), "Invalid chunk in tree", kXMPErr_InternalFailure ); + + // + // adjust new chunks counter + // + if( i > formChunk->numChildren() - mChunksAdded - 1 ) + { + mChunksAdded--; + } + + if( i < formChunk->numChildren()-1 ) + { + // + // fill gap with free chunk + // + Chunk* free = this->createFREE( chunk.getPadSize( true ) ); + formChunk->replaceChildAt( i, free ); + free->setAsNew(); + + // + // merge JUNK chunks + // + this->mergeFreeChunks( *formChunk, i ); + } + else + { + // + // remove chunk from tree + // + formChunk->removeChildAt( i ); + } + + return true; +} + +XMP_Bool AIFFBehavior::isFREEChunk( const Chunk& chunk ) const +{ + XMP_Bool ret = ( chunk.getID() == kChunk_APPL && chunk.getType() == kType_FREE ); + + // + // if the signature is not 'APPL':'FREE' the it could be an annotation chunk + // (ID: 'ANNO') which data area is smaller than 4bytes and the data is zero + // + if( !ret && chunk.getID() == kChunk_ANNO && chunk.getSize() < Chunk::TYPE_SIZE ) + { + ret = chunk.getSize() == 0; + + if( !ret ) + { + const XMP_Uns8* buffer; + chunk.getData( &buffer ); + + XMP_Uns8* data = new XMP_Uns8[static_cast( chunk.getSize() )]; + memset( data, 0, static_cast( chunk.getSize() ) ); + + ret = ( memcmp( data, buffer, static_cast( chunk.getSize() ) ) == 0 ); + + delete[] data; + } + } + + return ret; +} + +Chunk* AIFFBehavior::createFREE( XMP_Uns64 chunkSize ) +{ + XMP_Int64 alloc = chunkSize - Chunk::HEADER_SIZE; + + Chunk* chunk = NULL; + XMP_Uns8* data = NULL; + + if( alloc > 0 ) + { + data = new XMP_Uns8[static_cast( alloc )]; + memset( data, 0, static_cast( alloc ) ); + } + + if( alloc < Chunk::TYPE_SIZE ) + { + // + // if the required size is smaller than the minimum size of a 'APPL':'FREE' chunk + // then create an annotation chunk 'ANNO' and zero the data + // + if( alloc > 0 ) + { + chunk = Chunk::createUnknownChunk( mEndian, kChunk_ANNO, 0, alloc ); + chunk->setData( data, alloc ); + } + else + { + chunk = Chunk::createHeaderChunk( mEndian, kChunk_ANNO ); + } + } + else + { + // + // create a 'APPL':'FREE' chunk + // + alloc -= Chunk::TYPE_SIZE; + + if( alloc > 0 ) + { + chunk = Chunk::createUnknownChunk( mEndian, kChunk_APPL, kType_FREE, alloc+Chunk::TYPE_SIZE ); + chunk->setData( data, alloc, true ); + } + else + { + chunk = Chunk::createHeaderChunk( mEndian, kChunk_APPL, kType_FREE ); + } + } + + delete[] data; + + // force set dirty flag + chunk->setChanged(); + + return chunk; +} + +XMP_Uns64 AIFFBehavior::getMinFREESize() const +{ + // avoid creation of chunks with size==0 + return static_cast( Chunk::HEADER_SIZE ) + 2; +} diff --git a/core/libs/dngwriter/extra/xmp_sdk/XMPFiles/source/FormatSupport/AIFF/AIFFBehavior.h b/core/libs/dngwriter/extra/xmp_sdk/XMPFiles/source/FormatSupport/AIFF/AIFFBehavior.h new file mode 100644 index 0000000000..deadac3598 --- /dev/null +++ b/core/libs/dngwriter/extra/xmp_sdk/XMPFiles/source/FormatSupport/AIFF/AIFFBehavior.h @@ -0,0 +1,152 @@ +// ================================================================================================= +// ADOBE SYSTEMS INCORPORATED +// Copyright 2010 Adobe Systems Incorporated +// All Rights Reserved +// +// NOTICE: Adobe permits you to use, modify, and distribute this file in accordance with the terms +// of the Adobe license agreement accompanying it. +// ================================================================================================= + +#ifndef _AIFFBEHAVIOR_h_ +#define _AIFFBEHAVIOR_h_ + +#include "public/include/XMP_Environment.h" // ! XMP_Environment.h must be the first included header. + +#include "public/include/XMP_Const.h" +#include "public/include/XMP_IO.hpp" + +#include "XMPFiles/source/XMPFiles_Impl.hpp" +#include "source/XMPFiles_IO.hpp" + +#include "XMPFiles/source/FormatSupport/IFF/IChunkBehavior.h" +#include "XMPFiles/source/FormatSupport/IFF/ChunkPath.h" +#include "source/Endian.h" + +namespace IFF_RIFF +{ + +/** + AIFF behavior class. + + Implements the IChunkBehavior interface +*/ + + +class AIFFBehavior : public IChunkBehavior +{ +public: + /** + ctor/dtor + */ + AIFFBehavior() : mChunksAdded(0) {} + ~AIFFBehavior() {} + + /** + Validate the passed in size value, identify the valid size if the passed in isn't valid + and return the valid size. + throw an exception if the passed in size isn't valid and there's no way to identify a + valid size. + + @param size Size value + @param id Identifier of chunk + @param tree Chunk tree + @param stream Stream handle + + @return Valid size value. + */ + XMP_Uns64 getRealSize( const XMP_Uns64 size, const ChunkIdentifier& id, IChunkContainer& tree, XMP_IO* stream ); + + /** + Return the maximum size of a single chunk, i.e. the maximum size of a top-level chunk. + + @return Maximum size + */ + XMP_Uns64 getMaxChunkSize() const; + + /** + Return true if the passed identifier is valid for top-level chunks of a certain format. + + @param id Chunk identifier + @param chunkNo order number of top-level chunk + @return true, if passed id is a valid top-level chunk + */ + bool isValidTopLevelChunk( const ChunkIdentifier& id, XMP_Uns32 chunkNo ); + + /** + Fix the hierarchy of chunks depending ones based on size changes of one or more chunks + and second based on format specific rules. + Throw an exception if the hierarchy can't be fixed. + + @param tree Vector of root chunks. + */ + void fixHierarchy( IChunkContainer& tree ); + + /** + Insert a new chunk into the hierarchy of chunks. The behavior needs to decide the position + of the new chunk and has to do the insertion. + + @param tree Chunk tree + @param chunk New chunk + */ + void insertChunk( IChunkContainer& tree, Chunk& chunk ) ; + + /** + Remove the chunk described by the passed ChunkPath. + + @param tree Chunk tree + @param path Path to the chunk that needs to be removed + + @return true if the chunk was removed and need to be deleted + */ + bool removeChunk( IChunkContainer& tree, Chunk& chunk ) ; + +private: + + + /** + Create a FREE chunk. + If the chunkSize is smaller than the header+type - size then create an annotation chunk. + If the passed size is odd, then add a pad byte. + + @param chunkSize Total size including header + @return New FREE chunk + */ + Chunk* createFREE( XMP_Uns64 chunkSize ); + + /** + Check if the passed chunk is a FREE chunk. + (Could be also a small annotation chunk with zero bytes in its data) + + @param chunk A chunk + + @return true if the passed chunk is a FREE chunk + */ + XMP_Bool isFREEChunk( const Chunk& chunk ) const; + + /** + Retrieve the free space at the passed position in the child list of the parent tree. + If there's a FREE chunk then return it. + + @param outFreeBytes On return it takes the number of free bytes + @param tree Parent tree + @param index Position in the child list of the parent tree + + @return FREE chunk if available + */ + Chunk* getFreeSpace( XMP_Int64& outFreeBytes, const IChunkContainer& tree, XMP_Uns32 index ) const; + + /** + Return the minimum size of a FREE chunk + */ + XMP_Uns64 getMinFREESize( ) const; + +private: + XMP_Uns32 mChunksAdded; + + /** AIFF is always Big Endian */ + static const BigEndian& mEndian; + +}; // IFF_RIFF + +} +#endif diff --git a/core/libs/dngwriter/extra/xmp_sdk/XMPFiles/source/FormatSupport/AIFF/AIFFMetadata.cpp b/core/libs/dngwriter/extra/xmp_sdk/XMPFiles/source/FormatSupport/AIFF/AIFFMetadata.cpp new file mode 100644 index 0000000000..8fadba3c6c --- /dev/null +++ b/core/libs/dngwriter/extra/xmp_sdk/XMPFiles/source/FormatSupport/AIFF/AIFFMetadata.cpp @@ -0,0 +1,46 @@ +// ================================================================================================= +// ADOBE SYSTEMS INCORPORATED +// Copyright 2010 Adobe Systems Incorporated +// All Rights Reserved +// +// NOTICE: Adobe permits you to use, modify, and distribute this file in accordance with the terms +// of the Adobe license agreement accompanying it. +// ================================================================================================= + +#include "public/include/XMP_Environment.h" // ! XMP_Environment.h must be the first included header. +#include "public/include/XMP_Const.h" + +#include "XMPFiles/source/FormatSupport/AIFF/AIFFMetadata.h" + +using namespace IFF_RIFF; + +//----------------------------------------------------------------------------- +// +// AIFFMetadata::AIFFMetadata(...) +// +// Purpose: ctor/dtor +// +//----------------------------------------------------------------------------- + +AIFFMetadata::AIFFMetadata() +{ +} + +AIFFMetadata::~AIFFMetadata() +{ +} + +//----------------------------------------------------------------------------- +// +// AIFFMetadata::isEmptyValue(...) +// +// Purpose: Is the value of the passed ValueObject and its id "empty"? +// +//----------------------------------------------------------------------------- + +bool AIFFMetadata::isEmptyValue( XMP_Uns32 id, ValueObject& valueObj ) +{ + TValueObject* strObj = dynamic_cast*>(&valueObj); + + return ( strObj == NULL || ( strObj != NULL && strObj->getValue().empty() ) ); +} diff --git a/core/libs/dngwriter/extra/xmp_sdk/XMPFiles/source/FormatSupport/AIFF/AIFFMetadata.h b/core/libs/dngwriter/extra/xmp_sdk/XMPFiles/source/FormatSupport/AIFF/AIFFMetadata.h new file mode 100644 index 0000000000..13dbf1c51f --- /dev/null +++ b/core/libs/dngwriter/extra/xmp_sdk/XMPFiles/source/FormatSupport/AIFF/AIFFMetadata.h @@ -0,0 +1,63 @@ +// ================================================================================================= +// ADOBE SYSTEMS INCORPORATED +// Copyright 2010 Adobe Systems Incorporated +// All Rights Reserved +// +// NOTICE: Adobe permits you to use, modify, and distribute this file in accordance with the terms +// of the Adobe license agreement accompanying it. +// ================================================================================================= + +#ifndef _AIFFMetadata_h_ +#define _AIFFMetadata_h_ + +#include "public/include/XMP_Environment.h" // ! XMP_Environment.h must be the first included header. + +#include "public/include/XMP_Const.h" +#include "public/include/XMP_IO.hpp" + +#include "XMPFiles/source/XMPFiles_Impl.hpp" +#include "source/XMPFiles_IO.hpp" + +#include "source/XMP_LibUtils.hpp" + +#include "XMPFiles/source/FormatSupport/IFF/IChunkData.h" +#include "XMPFiles/source/NativeMetadataSupport/IMetadata.h" + + +namespace IFF_RIFF +{ + +/** + * AIFF Metadata model. + * Implements the IMetadata interface + */ +class AIFFMetadata : public IMetadata +{ +public: + enum + { + kName, // std::string + kAuthor, // std::string + kCopyright, // std::string + kAnnotation // std::string + }; + +public: + AIFFMetadata(); + ~AIFFMetadata(); + +protected: + /** + * @see IMetadata::isEmptyValue + */ + virtual bool isEmptyValue( XMP_Uns32 id, ValueObject& valueObj ); + +private: + // Operators hidden on purpose + AIFFMetadata( const AIFFMetadata& ) {}; + AIFFMetadata& operator=( const AIFFMetadata& ) { return *this; }; +}; + +} // namespace + +#endif diff --git a/core/libs/dngwriter/extra/xmp_sdk/XMPFiles/source/FormatSupport/AIFF/AIFFReconcile.cpp b/core/libs/dngwriter/extra/xmp_sdk/XMPFiles/source/FormatSupport/AIFF/AIFFReconcile.cpp new file mode 100644 index 0000000000..af915245cc --- /dev/null +++ b/core/libs/dngwriter/extra/xmp_sdk/XMPFiles/source/FormatSupport/AIFF/AIFFReconcile.cpp @@ -0,0 +1,63 @@ +// ================================================================================================= +// ADOBE SYSTEMS INCORPORATED +// Copyright 2010 Adobe Systems Incorporated +// All Rights Reserved +// +// NOTICE: Adobe permits you to use, modify, and distribute this file in accordance with the terms +// of the Adobe license agreement accompanying it. +// ================================================================================================= + +#include "public/include/XMP_Environment.h" // ! XMP_Environment.h must be the first included header. +#include "public/include/XMP_Const.h" + +#include "XMPFiles/source/FormatSupport/AIFF/AIFFReconcile.h" +#include "XMPFiles/source/FormatSupport/AIFF/AIFFMetadata.h" +#include "XMPFiles/source/NativeMetadataSupport/MetadataSet.h" +#include "source/XMP_LibUtils.hpp" +#include "XMPFiles/source/FormatSupport/Reconcile_Impl.hpp" +#include "source/XIO.hpp" + + +using namespace IFF_RIFF; + +static const MetadataPropertyInfo kAIFFProperties[] = +{ +// XMP NS XMP Property Name Native Metadata Identifier Native Datatype XMP Datatype Delete Priority ExportPolicy + { kXMP_NS_DC, "title", AIFFMetadata::kName, kNativeType_StrUTF8, kXMPType_Localized, true, false, kExport_Always }, // dc:title <-> FORM:AIFF/NAME + { kXMP_NS_DC, "creator", AIFFMetadata::kAuthor, kNativeType_StrUTF8, kXMPType_Array, true, false, kExport_Always }, // dc:creator <-> FORM:AIFF/AUTH + { kXMP_NS_DC, "rights", AIFFMetadata::kCopyright, kNativeType_StrUTF8, kXMPType_Localized, true, false, kExport_Always }, // dc:rights <-> FORM:AIFF/(c) + { kXMP_NS_DM, "logComment", AIFFMetadata::kAnnotation, kNativeType_StrUTF8, kXMPType_Simple, true, false, kExport_Always }, // xmpDM:logComment <-> FORM:AIFF/ANNO + { NULL } +}; + +XMP_Bool AIFFReconcile::importToXMP( SXMPMeta& outXMP, const MetadataSet& inMetaData ) +{ + XMP_Bool changed = false; + + // the reconciliation is based on the existing outXMP packet + AIFFMetadata *aiffMeta = inMetaData.get(); + + if (aiffMeta != NULL) + { + changed = IReconcile::importNativeToXMP( outXMP, *aiffMeta, kAIFFProperties, false ); + } + + return changed; +}//reconcile + + +XMP_Bool AIFFReconcile::exportFromXMP( MetadataSet& outMetaData, SXMPMeta& inXMP ) +{ + XMP_Bool changed = false; + + // Get the appropriate metadata container + AIFFMetadata *aiffMeta = outMetaData.get(); + + // If the metadata container is not available, skip that part of the process + if( aiffMeta != NULL ) + { + changed = IReconcile::exportXMPToNative( *aiffMeta, inXMP, kAIFFProperties ); + }//if AIFF is set + + return changed; +}//dissolve diff --git a/core/libs/dngwriter/extra/xmp_sdk/XMPFiles/source/FormatSupport/AIFF/AIFFReconcile.h b/core/libs/dngwriter/extra/xmp_sdk/XMPFiles/source/FormatSupport/AIFF/AIFFReconcile.h new file mode 100644 index 0000000000..41e5989aca --- /dev/null +++ b/core/libs/dngwriter/extra/xmp_sdk/XMPFiles/source/FormatSupport/AIFF/AIFFReconcile.h @@ -0,0 +1,40 @@ +// ================================================================================================= +// ADOBE SYSTEMS INCORPORATED +// Copyright 2010 Adobe Systems Incorporated +// All Rights Reserved +// +// NOTICE: Adobe permits you to use, modify, and distribute this file in accordance with the terms +// of the Adobe license agreement accompanying it. +// ================================================================================================= + +#ifndef _AIFFReconcile_h_ +#define _AIFFReconcile_h_ + +#include "XMPFiles/source/NativeMetadataSupport/IReconcile.h" + +namespace IFF_RIFF +{ + +class AIFFReconcile : public IReconcile +{ +public: + ~AIFFReconcile() {}; + + /** + * @see IReconcile::importToXMP + * Legacy values are always imported. + * If the values are not UTF-8 they will be converted to UTF-8 except in ServerMode + */ + XMP_Bool importToXMP( SXMPMeta& outXMP, const MetadataSet& inMetaData ); + + /** + * @see IReconcile::exportFromXMP + * XMP values are always exported to Legacy as UTF-8 encoded + */ + XMP_Bool exportFromXMP( MetadataSet& outMetaData, SXMPMeta& inXMP ); + +}; + +} + +#endif diff --git a/core/libs/dngwriter/extra/xmp_sdk/XMPFiles/source/FormatSupport/ASF_Support.cpp b/core/libs/dngwriter/extra/xmp_sdk/XMPFiles/source/FormatSupport/ASF_Support.cpp new file mode 100644 index 0000000000..709aea587a --- /dev/null +++ b/core/libs/dngwriter/extra/xmp_sdk/XMPFiles/source/FormatSupport/ASF_Support.cpp @@ -0,0 +1,1442 @@ +// ================================================================================================= +// ADOBE SYSTEMS INCORPORATED +// Copyright 2006 Adobe Systems Incorporated +// All Rights Reserved +// +// NOTICE: Adobe permits you to use, modify, and distribute this file in accordance with the terms +// of the Adobe license agreement accompanying it. +// ================================================================================================= + +#include "public/include/XMP_Environment.h" // ! XMP_Environment.h must be the first included header. +#include "public/include/XMP_Const.h" + +#include "XMPFiles/source/FormatSupport/ASF_Support.hpp" +#include "source/UnicodeConversions.hpp" +#include "source/XIO.hpp" + +#if XMP_WinBuild + #define snprintf _snprintf + #pragma warning ( disable : 4996 ) // '...' was declared deprecated + #pragma warning ( disable : 4267 ) // *** conversion (from size_t), possible loss of date (many 64 bit related) +#endif + +// ============================================================================================= + +// Platforms other than Win +#if ! XMP_WinBuild +int IsEqualGUID ( const GUID& guid1, const GUID& guid2 ) +{ + return (memcmp ( &guid1, &guid2, sizeof(GUID) ) == 0); +} +#endif + +ASF_Support::ASF_Support() : legacyManager(0),progressTracker(0), posFileSizeInfo(0) {} + +ASF_Support::ASF_Support ( ASF_LegacyManager* _legacyManager,XMP_ProgressTracker* _progressTracker ) + :legacyManager(_legacyManager),progressTracker(_progressTracker), posFileSizeInfo(0) +{ +} + +ASF_Support::~ASF_Support() +{ + legacyManager = 0; +} + +// ============================================================================================= + +long ASF_Support::OpenASF ( XMP_IO* fileRef, ObjectState & inOutObjectState ) +{ + XMP_Uns64 pos = 0; + XMP_Uns64 len; + + try { + pos = fileRef->Rewind(); + } catch ( ... ) {} + + if ( pos != 0 ) return 0; + + // read first and following chunks + while ( ReadObject ( fileRef, inOutObjectState, &len, pos) ) {} + + return inOutObjectState.objects.size(); + +} + +// ============================================================================================= + +bool ASF_Support::ReadObject ( XMP_IO* fileRef, ObjectState & inOutObjectState, XMP_Uns64 * objectLength, XMP_Uns64 & inOutPosition ) +{ + + try { + + XMP_Uns64 startPosition = inOutPosition; + XMP_Uns32 bytesRead; + ASF_ObjectBase objectBase; + + bytesRead = fileRef->ReadAll ( &objectBase, kASF_ObjectBaseLen ); + if ( bytesRead != kASF_ObjectBaseLen ) return false; + + *objectLength = GetUns64LE ( &objectBase.size ); + inOutPosition += *objectLength; + + ObjectData newObject; + + newObject.pos = startPosition; + newObject.len = *objectLength; + newObject.guid = objectBase.guid; + + // xmpIsLastObject indicates, that the XMP-object is the last top-level object + // reset here, if any another object is read + inOutObjectState.xmpIsLastObject = false; + + if ( IsEqualGUID ( ASF_Header_Object, newObject.guid ) ) { + + // header object ? + this->ReadHeaderObject ( fileRef, inOutObjectState, newObject ); + + } else if ( IsEqualGUID ( ASF_XMP_Metadata, newObject.guid ) ) { + + // check object for XMP GUID + inOutObjectState.xmpPos = newObject.pos + kASF_ObjectBaseLen; + inOutObjectState.xmpLen = newObject.len - kASF_ObjectBaseLen; + inOutObjectState.xmpIsLastObject = true; + inOutObjectState.xmpObject = newObject; + newObject.xmp = true; + + } + + inOutObjectState.objects.push_back ( newObject ); + + fileRef->Seek ( inOutPosition, kXMP_SeekFromStart ); + + } catch ( ... ) { + + return false; + + } + + return true; + +} + +// ============================================================================================= + +bool ASF_Support::ReadHeaderObject ( XMP_IO* fileRef, ObjectState& inOutObjectState, const ObjectData& newObject ) +{ + if ( ! IsEqualGUID ( ASF_Header_Object, newObject.guid) || (! legacyManager ) ) return false; + + std::string buffer; + + legacyManager->SetPadding(0); + + try { + + // read header-object structure + XMP_Uns64 pos = newObject.pos; + XMP_Uns32 bufferSize = kASF_ObjectBaseLen + 6; + + buffer.clear(); + buffer.reserve ( bufferSize ); + buffer.assign ( bufferSize, ' ' ); + fileRef->Seek ( pos, kXMP_SeekFromStart ); + fileRef->ReadAll ( const_cast(buffer.data()), bufferSize ); + + XMP_Uns64 read = bufferSize; + pos += bufferSize; + + // read contained header objects + XMP_Uns32 numberOfHeaders = GetUns32LE ( &buffer[24] ); + ASF_ObjectBase objectBase; + + while ( read < newObject.len ) { + + fileRef->Seek ( pos, kXMP_SeekFromStart ); + if ( kASF_ObjectBaseLen != fileRef->Read ( &objectBase, kASF_ObjectBaseLen, true ) ) break; + + fileRef->Seek ( pos, kXMP_SeekFromStart ); + objectBase.size = GetUns64LE ( &objectBase.size ); + + if ( IsEqualGUID ( ASF_File_Properties_Object, objectBase.guid) && (objectBase.size >= 104 ) ) { + + buffer.clear(); + buffer.reserve ( XMP_Uns32( objectBase.size ) ); + buffer.assign ( XMP_Uns32( objectBase.size ), ' ' ); + fileRef->ReadAll ( const_cast(buffer.data()), XMP_Int32(objectBase.size) ); + + // save position of filesize-information + posFileSizeInfo = (pos + 40); + + // creation date + std::string sub ( buffer.substr ( 48, 8 ) ); + legacyManager->SetField ( ASF_LegacyManager::fieldCreationDate, sub ); + + // broadcast flag set ? + XMP_Uns32 flags = GetUns32LE ( &buffer[88] ); + inOutObjectState.broadcast = (flags & 1); + legacyManager->SetBroadcast ( inOutObjectState.broadcast ); + + legacyManager->SetObjectExists ( ASF_LegacyManager::objectFileProperties ); + + } else if ( IsEqualGUID ( ASF_Content_Description_Object, objectBase.guid) && (objectBase.size >= 34 ) ) { + + buffer.clear(); + buffer.reserve ( XMP_Uns32( objectBase.size ) ); + buffer.assign ( XMP_Uns32( objectBase.size ), ' ' ); + fileRef->ReadAll ( const_cast(buffer.data()), XMP_Int32(objectBase.size) ); + + XMP_Uns16 titleLen = GetUns16LE ( &buffer[24] ); + XMP_Uns16 authorLen = GetUns16LE ( &buffer[26] ); + XMP_Uns16 copyrightLen = GetUns16LE ( &buffer[28] ); + XMP_Uns16 descriptionLen = GetUns16LE ( &buffer[30] ); + XMP_Uns16 ratingLen = GetUns16LE ( &buffer[32] ); + + XMP_Uns16 fieldPos = 34; + + std::string titleStr = buffer.substr ( fieldPos, titleLen ); + fieldPos += titleLen; + legacyManager->SetField ( ASF_LegacyManager::fieldTitle, titleStr ); + + std::string authorStr = buffer.substr ( fieldPos, authorLen ); + fieldPos += authorLen; + legacyManager->SetField ( ASF_LegacyManager::fieldAuthor, authorStr ); + + std::string copyrightStr = buffer.substr ( fieldPos, copyrightLen ); + fieldPos += copyrightLen; + legacyManager->SetField ( ASF_LegacyManager::fieldCopyright, copyrightStr ); + + std::string descriptionStr = buffer.substr ( fieldPos, descriptionLen ); + fieldPos += descriptionLen; + legacyManager->SetField ( ASF_LegacyManager::fieldDescription, descriptionStr ); + + /* rating is currently not part of reconciliation + std::string ratingStr = buffer.substr ( fieldPos, ratingLen ); + fieldPos += ratingLen; + legacyData.append ( titleStr ); + */ + + legacyManager->SetObjectExists ( ASF_LegacyManager::objectContentDescription ); + + } else if ( IsEqualGUID ( ASF_Content_Branding_Object, objectBase.guid ) ) { + + buffer.clear(); + buffer.reserve ( XMP_Uns32( objectBase.size ) ); + buffer.assign ( XMP_Uns32( objectBase.size ), ' ' ); + fileRef->ReadAll ( const_cast(buffer.data()), XMP_Int32(objectBase.size) ); + + XMP_Uns32 fieldPos = 28; + + // copyright URL is 3. element with variable size + for ( int i = 1; i <= 3 ; ++i ) { + XMP_Uns32 len = GetUns32LE ( &buffer[fieldPos] ); + if ( i == 3 ) { + std::string copyrightURLStr = buffer.substr ( fieldPos + 4, len ); + legacyManager->SetField ( ASF_LegacyManager::fieldCopyrightURL, copyrightURLStr ); + } + fieldPos += (len + 4); + } + + legacyManager->SetObjectExists ( ASF_LegacyManager::objectContentBranding ); + +#if ! Exclude_LicenseURL_Recon + + } else if ( IsEqualGUID ( ASF_Content_Encryption_Object, objectBase.guid ) ) { + + buffer.clear(); + buffer.reserve ( XMP_Uns32( objectBase.size ) ); + buffer.assign ( XMP_Uns32( objectBase.size ), ' ' ); + fileRef->ReadAll ( const_cast(buffer.data()), XMP_Int32(objectBase.size) ); + + XMP_Uns32 fieldPos = 24; + + // license URL is 4. element with variable size + for ( int i = 1; i <= 4 ; ++i ) { + XMP_Uns32 len = GetUns32LE ( &buffer[fieldPos] ); + if ( i == 4 ) { + std::string licenseURLStr = buffer.substr ( fieldPos + 4, len ); + legacyManager->SetField ( ASF_LegacyManager::fieldLicenseURL, licenseURLStr ); + } + fieldPos += (len + 4); + } + + legacyManager->SetObjectExists ( objectContentEncryption ); + +#endif + + } else if ( IsEqualGUID ( ASF_Padding_Object, objectBase.guid ) ) { + + legacyManager->SetPadding ( legacyManager->GetPadding() + (objectBase.size - 24) ); + + } else if ( IsEqualGUID ( ASF_Header_Extension_Object, objectBase.guid ) ) { + + this->ReadHeaderExtensionObject ( fileRef, inOutObjectState, pos, objectBase ); + + } + + pos += objectBase.size; + read += objectBase.size; + } + + } catch ( ... ) { + + return false; + + } + + legacyManager->ComputeDigest(); + + return true; +} + +// ============================================================================================= + +bool ASF_Support::WriteHeaderObject ( XMP_IO* sourceRef, XMP_IO* destRef, const ObjectData& object, ASF_LegacyManager& _legacyManager, bool usePadding ) +{ + if ( ! IsEqualGUID ( ASF_Header_Object, object.guid ) ) return false; + + std::string buffer; + XMP_Uns16 valueUns16LE; + XMP_Uns32 valueUns32LE; + XMP_Uns64 valueUns64LE; + + try { + + // read header-object structure + XMP_Uns64 pos = object.pos; + XMP_Uns32 bufferSize = kASF_ObjectBaseLen + 6; + + buffer.clear(); + buffer.reserve ( bufferSize ); + buffer.assign ( bufferSize, ' ' ); + sourceRef->Seek ( pos, kXMP_SeekFromStart ); + sourceRef->ReadAll ( const_cast(buffer.data()), bufferSize ); + + XMP_Uns64 read = bufferSize; + pos += bufferSize; + + // read contained header objects + XMP_Uns32 numberOfHeaders = GetUns32LE ( &buffer[24] ); + ASF_ObjectBase objectBase; + + // prepare new header in memory + std::string header; + + int changedObjects = _legacyManager.changedObjects(); + int exportedObjects = 0; + int writtenObjects = 0; + + header.append ( buffer.c_str(), bufferSize ); + + while ( read < object.len ) { + + sourceRef->Seek ( pos, kXMP_SeekFromStart ); + if ( kASF_ObjectBaseLen != sourceRef->Read ( &objectBase, kASF_ObjectBaseLen, true ) ) break; + + sourceRef->Seek ( pos, kXMP_SeekFromStart ); + objectBase.size = GetUns64LE ( &objectBase.size ); + + int headerStartPos = header.size(); + + // save position of filesize-information + if ( IsEqualGUID ( ASF_File_Properties_Object, objectBase.guid ) ) { + posFileSizeInfo = (headerStartPos + 40); + } + + // write objects + if ( IsEqualGUID ( ASF_File_Properties_Object, objectBase.guid ) && + (objectBase.size >= 104) && (changedObjects & ASF_LegacyManager::objectFileProperties) ) { + + // copy object and replace creation-date + buffer.reserve ( XMP_Uns32 ( objectBase.size ) ); + buffer.assign ( XMP_Uns32 ( objectBase.size ), ' ' ); + sourceRef->ReadAll ( const_cast(buffer.data()), XMP_Int32(objectBase.size) ); + header.append ( buffer, 0, XMP_Uns32( objectBase.size ) ); + + if ( ! _legacyManager.GetBroadcast() ) { + buffer = _legacyManager.GetField ( ASF_LegacyManager::fieldCreationDate ); + ReplaceString ( header, buffer, (headerStartPos + 48), 8 ); + } + + exportedObjects |= ASF_LegacyManager::objectFileProperties; + + } else if ( IsEqualGUID ( ASF_Content_Description_Object, objectBase.guid ) && + (objectBase.size >= 34) && (changedObjects & ASF_LegacyManager::objectContentDescription) ) { + + // re-create object with xmp-data + buffer.reserve ( XMP_Uns32( objectBase.size ) ); + buffer.assign ( XMP_Uns32( objectBase.size ), ' ' ); + sourceRef->ReadAll ( const_cast(buffer.data()), XMP_Int32(objectBase.size) ); + // write header only + header.append ( buffer, 0, XMP_Uns32( kASF_ObjectBaseLen ) ); + + // write length fields + + XMP_Uns16 titleLen = _legacyManager.GetField ( ASF_LegacyManager::fieldTitle).size( ); + valueUns16LE = MakeUns16LE ( titleLen ); + header.append ( (const char*)&valueUns16LE, 2 ); + + XMP_Uns16 authorLen = _legacyManager.GetField ( ASF_LegacyManager::fieldAuthor).size( ); + valueUns16LE = MakeUns16LE ( authorLen ); + header.append ( (const char*)&valueUns16LE, 2 ); + + XMP_Uns16 copyrightLen = _legacyManager.GetField ( ASF_LegacyManager::fieldCopyright).size( ); + valueUns16LE = MakeUns16LE ( copyrightLen ); + header.append ( (const char*)&valueUns16LE, 2 ); + + XMP_Uns16 descriptionLen = _legacyManager.GetField ( ASF_LegacyManager::fieldDescription).size( ); + valueUns16LE = MakeUns16LE ( descriptionLen ); + header.append ( (const char*)&valueUns16LE, 2 ); + + // retrieve existing overall length of preceding fields + XMP_Uns16 precedingLen = 0; + precedingLen += GetUns16LE ( &buffer[24] ); // Title + precedingLen += GetUns16LE ( &buffer[26] ); // Author + precedingLen += GetUns16LE ( &buffer[28] ); // Copyright + precedingLen += GetUns16LE ( &buffer[30] ); // Description + // retrieve existing 'Rating' length + XMP_Uns16 ratingLen = GetUns16LE ( &buffer[32] ); // Rating + valueUns16LE = MakeUns16LE ( ratingLen ); + header.append ( (const char*)&valueUns16LE, 2 ); + + // write field contents + + header.append ( _legacyManager.GetField ( ASF_LegacyManager::fieldTitle ) ); + header.append ( _legacyManager.GetField ( ASF_LegacyManager::fieldAuthor ) ); + header.append ( _legacyManager.GetField ( ASF_LegacyManager::fieldCopyright ) ); + header.append ( _legacyManager.GetField ( ASF_LegacyManager::fieldDescription ) ); + header.append ( buffer, (34 + precedingLen), ratingLen ); + + // update new object size + valueUns64LE = MakeUns64LE ( header.size() - headerStartPos ); + std::string newSize ( (const char*)&valueUns64LE, 8 ); + ReplaceString ( header, newSize, (headerStartPos + 16), 8 ); + + exportedObjects |= ASF_LegacyManager::objectContentDescription; + + } else if ( IsEqualGUID ( ASF_Content_Branding_Object, objectBase.guid ) && + (changedObjects & ASF_LegacyManager::objectContentBranding) ) { + + // re-create object with xmp-data + buffer.reserve ( XMP_Uns32( objectBase.size ) ); + buffer.assign ( XMP_Uns32( objectBase.size ), ' ' ); + sourceRef->ReadAll ( const_cast(buffer.data()), XMP_Int32(objectBase.size) ); + + // calculate size of fields coming before 'Copyright URL' + XMP_Uns32 length = 28; + length += (GetUns32LE ( &buffer[length] ) + 4); // Banner Image Data + length += (GetUns32LE ( &buffer[length] ) + 4); // Banner Image URL + + // write first part of header + header.append ( buffer, 0, length ); + + // copyright URL + length = _legacyManager.GetField ( ASF_LegacyManager::fieldCopyrightURL).size( ); + valueUns32LE = MakeUns32LE ( length ); + header.append ( (const char*)&valueUns32LE, 4 ); + header.append ( _legacyManager.GetField ( ASF_LegacyManager::fieldCopyrightURL ) ); + + // update new object size + valueUns64LE = MakeUns64LE ( header.size() - headerStartPos ); + std::string newSize ( (const char*)&valueUns64LE, 8 ); + ReplaceString ( header, newSize, (headerStartPos + 16), 8 ); + + exportedObjects |= ASF_LegacyManager::objectContentBranding; + +#if ! Exclude_LicenseURL_Recon + + } else if ( IsEqualGUID ( ASF_Content_Encryption_Object, objectBase.guid ) && + (changedObjects & ASF_LegacyManager::objectContentEncryption) ) { + + // re-create object with xmp-data + buffer.reserve ( XMP_Uns32( objectBase.size ) ); + buffer.assign ( XMP_Uns32( objectBase.size ), ' ' ); + sourceRef->ReadAll ( const_cast(buffer.data()), XMP_Int32(objectBase.size) ); + + // calculate size of fields coming before 'License URL' + XMP_Uns32 length = 24; + length += (GetUns32LE ( &buffer[length] ) + 4); // Secret Data + length += (GetUns32LE ( &buffer[length] ) + 4); // Protection Type + length += (GetUns32LE ( &buffer[length] ) + 4); // Key ID + + // write first part of header + header.append ( buffer, 0, length ); + + // License URL + length = _legacyManager.GetField ( ASF_LegacyManager::fieldLicenseURL).size( ); + valueUns32LE = MakeUns32LE ( length ); + header.append ( (const char*)&valueUns32LE, 4 ); + header.append ( _legacyManager.GetField ( ASF_LegacyManager::fieldLicenseURL ) ); + + // update new object size + valueUns64LE = MakeUns64LE ( header.size() - headerStartPos ); + std::string newSize ( (const char*)&valueUns64LE, 8 ); + ReplaceString ( header, newSize, (headerStartPos + 16), 8 ); + + exportedObjects |= ASF_LegacyManager::objectContentEncryption; + +#endif + + } else if ( IsEqualGUID ( ASF_Header_Extension_Object, objectBase.guid ) && usePadding ) { + + // re-create object if padding needs to be used + buffer.reserve ( XMP_Uns32( objectBase.size ) ); + buffer.assign ( XMP_Uns32( objectBase.size ), ' ' ); + sourceRef->ReadAll ( const_cast(buffer.data()), XMP_Int32(objectBase.size) ); + + ASF_Support::WriteHeaderExtensionObject ( buffer, &header, objectBase, 0 ); + + } else if ( IsEqualGUID ( ASF_Padding_Object, objectBase.guid ) && usePadding ) { + + // eliminate padding (will be created as last object) + + } else { + + // simply copy all other objects + buffer.reserve ( XMP_Uns32( objectBase.size ) ); + buffer.assign ( XMP_Uns32( objectBase.size ), ' ' ); + sourceRef->ReadAll ( const_cast(buffer.data()), XMP_Int32(objectBase.size) ); + + header.append ( buffer, 0, XMP_Uns32( objectBase.size ) ); + + } + + pos += objectBase.size; + read += objectBase.size; + + writtenObjects ++; + + } + + // any objects to create ? + int newObjects = (changedObjects ^ exportedObjects); + + if ( newObjects ) { + + // create new objects with xmp-data + int headerStartPos; + ASF_ObjectBase newObjectBase; + XMP_Uns32 length; + + if ( newObjects & ASF_LegacyManager::objectContentDescription ) { + + headerStartPos = header.size(); + newObjectBase.guid = ASF_Content_Description_Object; + newObjectBase.size = 0; + + // write object header + header.append ( (const char*)&newObjectBase, kASF_ObjectBaseLen ); + + XMP_Uns16 titleLen = _legacyManager.GetField ( ASF_LegacyManager::fieldTitle).size( ); + valueUns16LE = MakeUns16LE ( titleLen ); + header.append ( (const char*)&valueUns16LE, 2 ); + + XMP_Uns16 authorLen = _legacyManager.GetField ( ASF_LegacyManager::fieldAuthor).size( ); + valueUns16LE = MakeUns16LE ( authorLen ); + header.append ( (const char*)&valueUns16LE, 2 ); + + XMP_Uns16 copyrightLen = _legacyManager.GetField ( ASF_LegacyManager::fieldCopyright).size( ); + valueUns16LE = MakeUns16LE ( copyrightLen ); + header.append ( (const char*)&valueUns16LE, 2 ); + + XMP_Uns16 descriptionLen = _legacyManager.GetField ( ASF_LegacyManager::fieldDescription).size( ); + valueUns16LE = MakeUns16LE ( descriptionLen ); + header.append ( (const char*)&valueUns16LE, 2 ); + + XMP_Uns16 ratingLen = 0; + valueUns16LE = MakeUns16LE ( ratingLen ); + header.append ( (const char*)&valueUns16LE, 2 ); + + // write field contents + + header.append ( _legacyManager.GetField ( ASF_LegacyManager::fieldTitle ) ); + header.append ( _legacyManager.GetField ( ASF_LegacyManager::fieldAuthor ) ); + header.append ( _legacyManager.GetField ( ASF_LegacyManager::fieldCopyright ) ); + header.append ( _legacyManager.GetField ( ASF_LegacyManager::fieldDescription ) ); + + // update new object size + valueUns64LE = MakeUns64LE ( header.size() - headerStartPos ); + std::string newSize ( (const char*)&valueUns64LE, 8 ); + ReplaceString ( header, newSize, (headerStartPos + 16), 8 ); + + newObjects &= ~ASF_LegacyManager::objectContentDescription; + + writtenObjects ++; + + } + + if ( newObjects & ASF_LegacyManager::objectContentBranding ) { + + headerStartPos = header.size(); + newObjectBase.guid = ASF_Content_Branding_Object; + newObjectBase.size = 0; + + // write object header + header.append ( (const char*)&newObjectBase, kASF_ObjectBaseLen ); + + // write 'empty' fields + header.append ( 12, '\0' ); + + // copyright URL + length = _legacyManager.GetField ( ASF_LegacyManager::fieldCopyrightURL).size( ); + valueUns32LE = MakeUns32LE ( length ); + header.append ( (const char*)&valueUns32LE, 4 ); + header.append ( _legacyManager.GetField ( ASF_LegacyManager::fieldCopyrightURL ) ); + + // update new object size + valueUns64LE = MakeUns64LE ( header.size() - headerStartPos ); + std::string newSize ( (const char*)&valueUns64LE, 8 ); + ReplaceString ( header, newSize, (headerStartPos + 16), 8 ); + + newObjects &= ~ASF_LegacyManager::objectContentBranding; + + writtenObjects ++; + + } + +#if ! Exclude_LicenseURL_Recon + + if ( newObjects & ASF_LegacyManager::objectContentEncryption ) { + + headerStartPos = header.size(); + newObjectBase.guid = ASF_Content_Encryption_Object; + newObjectBase.size = 0; + + // write object header + header.append ( (const char*)&newObjectBase, kASF_ObjectBaseLen ); + + // write 'empty' fields + header.append ( 12, '\0' ); + + // License URL + length = _legacyManager.GetField ( ASF_LegacyManager::fieldLicenseURL).size( ); + valueUns32LE = MakeUns32LE ( length ); + header.append ( (const char*)&valueUns32LE, 4 ); + header.append ( _legacyManager.GetField ( ASF_LegacyManager::fieldLicenseURL ) ); + + // update new object size + valueUns64LE = MakeUns64LE ( header.size() - headerStartPos ); + std::string newSize ( (const char*)&valueUns64LE, 8 ); + ReplaceString ( header, newSize, (headerStartPos + 16), 8 ); + + newObjects &= ~ASF_LegacyManager::objectContentEncryption; + + writtenObjects ++; + + } + +#endif + + } + + // create padding object ? + if ( usePadding && (header.size ( ) < object.len ) ) { + ASF_Support::CreatePaddingObject ( &header, (object.len - header.size()) ); + writtenObjects ++; + } + + // update new header-object size + valueUns64LE = MakeUns64LE ( header.size() ); + std::string newValue ( (const char*)&valueUns64LE, 8 ); + ReplaceString ( header, newValue, 16, 8 ); + + // update new number of Header objects + valueUns32LE = MakeUns32LE ( writtenObjects ); + newValue = std::string ( (const char*)&valueUns32LE, 4 ); + ReplaceString ( header, newValue, 24, 4 ); + + // if we are operating on the same file (in-place update), place pointer before writing + if ( sourceRef == destRef ) destRef->Seek ( object.pos, kXMP_SeekFromStart ); + if ( this->progressTracker != 0 ) + { + XMP_Assert ( this->progressTracker->WorkInProgress() ); + this->progressTracker->AddTotalWork ( (float)header.size() ); + } + // write header + destRef->Write ( header.c_str(), header.size() ); + + } catch ( ... ) { + + return false; + + } + + return true; + +} + +// ============================================================================================= + +bool ASF_Support::UpdateHeaderObject ( XMP_IO* fileRef, const ObjectData& object, ASF_LegacyManager& _legacyManager ) +{ + return ASF_Support::WriteHeaderObject ( fileRef, fileRef, object, _legacyManager, true ); +} + +// ============================================================================================= + +bool ASF_Support::UpdateFileSize ( XMP_IO* fileRef ) +{ + if ( fileRef == 0 ) return false; + + XMP_Uns64 posCurrent = fileRef->Seek ( 0, kXMP_SeekFromCurrent ); + XMP_Uns64 newSizeLE = MakeUns64LE ( fileRef->Length() ); + + if ( this->posFileSizeInfo != 0 ) { + + fileRef->Seek ( this->posFileSizeInfo, kXMP_SeekFromStart ); + + } else { + + // The position of the file size field is not known, find it. + + ASF_ObjectBase objHeader; + + // Read the Header object at the start of the file. + + fileRef->Rewind(); + fileRef->ReadAll ( &objHeader, kASF_ObjectBaseLen ); + if ( ! IsEqualGUID ( ASF_Header_Object, objHeader.guid ) ) return false; + + XMP_Uns32 childCount; + fileRef->ReadAll ( &childCount, 4 ); + childCount = GetUns32LE ( &childCount ); + + fileRef->Seek ( 2, kXMP_SeekFromCurrent ); // Skip the 2 reserved bytes. + + // Look for the File Properties object in the Header's children. + + for ( ; childCount > 0; --childCount ) { + fileRef->ReadAll ( &objHeader, kASF_ObjectBaseLen ); + if ( IsEqualGUID ( ASF_File_Properties_Object, objHeader.guid ) ) break; + XMP_Uns64 dataLen = GetUns64LE ( &objHeader.size ) - 24; + fileRef->Seek ( dataLen, kXMP_SeekFromCurrent ); // Skip this object's data. + } + if ( childCount == 0 ) return false; + + // Seek to the file size field. + + XMP_Uns64 fpoSize = GetUns64LE ( &objHeader.size ); + if ( fpoSize < (16+8+16+8) ) return false; + fileRef->Seek ( 16, kXMP_SeekFromCurrent ); // Skip to the file size field. + + } + + fileRef->Write ( &newSizeLE, 8 ); // Write the new file size. + + fileRef->Seek ( posCurrent, kXMP_SeekFromStart ); + return true; + +} + +// ============================================================================================= + +bool ASF_Support::ReadHeaderExtensionObject ( XMP_IO* fileRef, ObjectState& inOutObjectState, const XMP_Uns64& _pos, const ASF_ObjectBase& _objectBase ) +{ + if ( ! IsEqualGUID ( ASF_Header_Extension_Object, _objectBase.guid) || (! legacyManager ) ) return false; + + try { + + // read extended header-object structure beginning at the data part (offset = 46) + const XMP_Uns64 offset = 46; + XMP_Uns64 read = 0; + XMP_Uns64 data = (_objectBase.size - offset); + XMP_Uns64 pos = (_pos + offset); + + ASF_ObjectBase objectBase; + + while ( read < data ) { + + fileRef->Seek ( pos, kXMP_SeekFromStart ); + if ( kASF_ObjectBaseLen != fileRef->Read ( &objectBase, kASF_ObjectBaseLen, true ) ) break; + + objectBase.size = GetUns64LE ( &objectBase.size ); + + if ( IsEqualGUID ( ASF_Padding_Object, objectBase.guid ) ) { + legacyManager->SetPadding ( legacyManager->GetPadding() + (objectBase.size - 24) ); + } + + pos += objectBase.size; + read += objectBase.size; + + } + + } catch ( ... ) { + + return false; + + } + + return true; + +} + +// ============================================================================================= + +bool ASF_Support::WriteHeaderExtensionObject ( const std::string& buffer, std::string* header, const ASF_ObjectBase& _objectBase, const int /*reservePadding*/ ) +{ + if ( ! IsEqualGUID ( ASF_Header_Extension_Object, _objectBase.guid ) || (! header) || (buffer.size() < 46) ) return false; + + const XMP_Uns64 offset = 46; + int startPos = header->size(); + + // copy header base + header->append ( buffer, 0, offset ); + + // read extended header-object structure beginning at the data part (offset = 46) + XMP_Uns64 read = 0; + XMP_Uns64 data = (_objectBase.size - offset); + XMP_Uns64 pos = offset; + + ASF_ObjectBase objectBase; + + while ( read < data ) { + + memcpy ( &objectBase, &buffer[int(pos)], kASF_ObjectBaseLen ); + objectBase.size = GetUns64LE ( &objectBase.size ); + + if ( IsEqualGUID ( ASF_Padding_Object, objectBase.guid ) ) { + // eliminate + } else { + // copy other objects + header->append ( buffer, XMP_Uns32(pos), XMP_Uns32(objectBase.size) ); + } + + pos += objectBase.size; + read += objectBase.size; + + } + + // update header extension data size + XMP_Uns32 valueUns32LE = MakeUns32LE ( header->size() - startPos - offset ); + std::string newDataSize ( (const char*)&valueUns32LE, 4 ); + ReplaceString ( *header, newDataSize, (startPos + 42), 4 ); + + // update new object size + XMP_Uns64 valueUns64LE = MakeUns64LE ( header->size() - startPos ); + std::string newObjectSize ( (const char*)&valueUns64LE, 8 ); + ReplaceString ( *header, newObjectSize, (startPos + 16), 8 ); + + return true; + +} + +// ============================================================================================= + +bool ASF_Support::CreatePaddingObject ( std::string* header, const XMP_Uns64 size ) +{ + if ( ( ! header) || (size < 24) ) return false; + + ASF_ObjectBase newObjectBase; + + newObjectBase.guid = ASF_Padding_Object; + newObjectBase.size = MakeUns64LE ( size ); + + // write object header + header->append ( (const char*)&newObjectBase, kASF_ObjectBaseLen ); + + // write 'empty' padding + header->append ( XMP_Uns32 ( size - 24 ), '\0' ); + + return true; + +} + +// ============================================================================================= + +bool ASF_Support::WriteXMPObject ( XMP_IO* fileRef, XMP_Uns32 len, const char* inBuffer ) +{ + bool ret = false; + + ASF_ObjectBase objectBase = { ASF_XMP_Metadata, 0 }; + objectBase.size = MakeUns64LE ( len + kASF_ObjectBaseLen ); + + try { + fileRef->Write ( &objectBase, kASF_ObjectBaseLen ); + fileRef->Write ( inBuffer, len ); + ret = true; + } catch ( ... ) {} + + return ret; + +} + +// ============================================================================================= + +bool ASF_Support::UpdateXMPObject ( XMP_IO* fileRef, const ObjectData& object, XMP_Uns32 len, const char * inBuffer ) +{ + bool ret = false; + + ASF_ObjectBase objectBase = { ASF_XMP_Metadata, 0 }; + objectBase.size = MakeUns64LE ( len + kASF_ObjectBaseLen ); + + try { + fileRef->Seek ( object.pos, kXMP_SeekFromStart ); + fileRef->Write ( &objectBase, kASF_ObjectBaseLen ); + fileRef->Write ( inBuffer, len ); + ret = true; + } catch ( ... ) {} + + return ret; + +} + +// ============================================================================================= + +bool ASF_Support::CopyObject ( XMP_IO* sourceRef, XMP_IO* destRef, const ObjectData& object ) +{ + try { + sourceRef->Seek ( object.pos, kXMP_SeekFromStart ); + XIO::Copy ( sourceRef, destRef, object.len ); + } catch ( ... ) { + return false; + } + + return true; + +} + +// ============================================================================================= + +bool ASF_Support::ReadBuffer ( XMP_IO* fileRef, XMP_Uns64 & pos, XMP_Uns64 len, char * outBuffer ) +{ + try { + + if ( (fileRef == 0) || (outBuffer == 0) ) return false; + + fileRef->Seek ( pos, kXMP_SeekFromStart ); + long bytesRead = fileRef->ReadAll ( outBuffer, XMP_Int32(len) ); + if ( XMP_Uns32 ( bytesRead ) != len ) return false; + + return true; + + } catch ( ... ) {} + + return false; + +} + +// ============================================================================================= + +bool ASF_Support::WriteBuffer ( XMP_IO* fileRef, XMP_Uns64 & pos, XMP_Uns32 len, const char * inBuffer ) +{ + try { + + if ( (fileRef == 0) || (inBuffer == 0) ) return false; + + fileRef->Seek ( pos, kXMP_SeekFromStart ); + fileRef->Write ( inBuffer, len ); + + return true; + + } catch ( ... ) {} + + return false; + +} + +// ================================================================================================= + +std::string ASF_Support::ReplaceString ( std::string& operand, std::string& str, int offset, int count ) +{ + std::basic_string::iterator iterF1, iterL1, iterF2, iterL2; + + iterF1 = operand.begin() + offset; + iterL1 = operand.begin() + offset + count; + iterF2 = str.begin(); + iterL2 = str.begin() + count; + + return operand.replace ( iterF1, iterL1, iterF2, iterL2 ); + +} + +// ================================================================================================= + +ASF_LegacyManager::ASF_LegacyManager() : fields(fieldLast), broadcastSet(false), digestComputed(false), + imported(false), objectsExisting(0), objectsToExport(0), legacyDiff(0), padding(0) +{ + // Nothing more to do. +} + +// ================================================================================================= + +ASF_LegacyManager::~ASF_LegacyManager() +{ + // Nothing to do. +} + +// ================================================================================================= + +bool ASF_LegacyManager::SetField ( fieldType field, const std::string& value ) +{ + if ( field >= fieldLast ) return false; + + unsigned int maxSize = this->GetFieldMaxSize ( field ); + + if (value.size ( ) <= maxSize ) { + fields[field] = value; + } else { + fields[field] = value.substr ( 0, maxSize ); + } + + if ( field == fieldCopyrightURL ) NormalizeStringDisplayASCII ( fields[field] ); + + #if ! Exclude_LicenseURL_Recon + if ( field == fieldLicenseURL ) NormalizeStringDisplayASCII ( fields[field] ); + #endif + + return true; + +} + +// ================================================================================================= + +std::string ASF_LegacyManager::GetField ( fieldType field ) +{ + if ( field >= fieldLast ) return std::string(); + return fields[field]; +} + +// ================================================================================================= + +unsigned int ASF_LegacyManager::GetFieldMaxSize ( fieldType field ) +{ + unsigned int maxSize = 0; + + switch ( field ) { + + case fieldCreationDate : + maxSize = 8; + break; + + case fieldTitle : + case fieldAuthor : + case fieldCopyright : + case fieldDescription : + maxSize = 0xFFFF; + break; + + case fieldCopyrightURL : +#if ! Exclude_LicenseURL_Recon + case fieldLicenseURL : +#endif + maxSize = 0xFFFFFFFF; + break; + + default: + break; + + } + + return maxSize; + +} + +// ================================================================================================= + +void ASF_LegacyManager::SetObjectExists ( objectType object ) +{ + objectsExisting |= object; +} + +// ================================================================================================= + +void ASF_LegacyManager::SetBroadcast ( const bool broadcast ) +{ + broadcastSet = broadcast; +} + +// ================================================================================================= + +bool ASF_LegacyManager::GetBroadcast() +{ + return broadcastSet; +} + +// ================================================================================================= + +void ASF_LegacyManager::ComputeDigest() +{ + MD5_CTX context; + MD5_Digest digest; + char buffer[40]; + + MD5Init ( &context ); + digestStr.clear(); + digestStr.reserve ( 160 ); + + for ( int type=0; type < fieldLast; ++type ) { + + if (fields[type].size ( ) > 0 ) { + snprintf ( buffer, sizeof(buffer), "%d,", type ); + digestStr.append ( buffer ); + MD5Update ( &context, (XMP_Uns8*)fields[type].data(), fields[type].size() ); + } + + } + + if( digestStr.size() > 0 ) digestStr[digestStr.size()-1] = ';'; + + MD5Final ( digest, &context ); + + size_t in, out; + for ( in = 0, out = 0; in < 16; in += 1, out += 2 ) { + XMP_Uns8 byte = digest[in]; + buffer[out] = ReconcileUtils::kHexDigits [ byte >> 4 ]; + buffer[out+1] = ReconcileUtils::kHexDigits [ byte & 0xF ]; + } + buffer[32] = 0; + + digestStr.append ( buffer ); + + digestComputed = true; + +} + +// ================================================================================================= + +bool ASF_LegacyManager::CheckDigest ( const SXMPMeta& xmp ) +{ + bool ret = false; + + if ( ! digestComputed ) this->ComputeDigest(); + + std::string oldDigest; + + if ( xmp.GetProperty ( kXMP_NS_ASF, "NativeDigest", &oldDigest, 0 ) ) { + ret = (digestStr == oldDigest); + } + + return ret; + +} + +// ================================================================================================= + +void ASF_LegacyManager::SetDigest ( SXMPMeta* xmp ) +{ + if ( ! digestComputed ) this->ComputeDigest(); + + xmp->SetProperty ( kXMP_NS_ASF, "NativeDigest", digestStr.c_str() ); + +} + +// ================================================================================================= + +void ASF_LegacyManager::ImportLegacy ( SXMPMeta* xmp ) +{ + std::string utf8; + + if ( ! broadcastSet ) { + ConvertMSDateToISODate ( fields[fieldCreationDate], &utf8 ); + if ( ! utf8.empty() ) xmp->SetProperty ( kXMP_NS_XMP, "CreateDate", utf8.c_str(), kXMP_DeleteExisting ); + } + + FromUTF16 ( (UTF16Unit*)fields[fieldTitle].c_str(), (fields[fieldTitle].size() / 2), &utf8, false ); + if ( ! utf8.empty() ) xmp->SetLocalizedText ( kXMP_NS_DC, "title", "", "x-default", utf8.c_str(), kXMP_DeleteExisting ); + + xmp->DeleteProperty ( kXMP_NS_DC, "creator" ); + FromUTF16 ( (UTF16Unit*)fields[fieldAuthor].c_str(), (fields[fieldAuthor].size() / 2), &utf8, false ); + if ( ! utf8.empty() ) SXMPUtils::SeparateArrayItems ( xmp, kXMP_NS_DC, "creator", + (kXMP_PropArrayIsOrdered | kXMPUtil_AllowCommas), utf8.c_str() ); + + FromUTF16 ( (UTF16Unit*)fields[fieldCopyright].c_str(), (fields[fieldCopyright].size() / 2), &utf8, false ); + if ( ! utf8.empty() ) xmp->SetLocalizedText ( kXMP_NS_DC, "rights", "", "x-default", utf8.c_str(), kXMP_DeleteExisting ); + + FromUTF16 ( (UTF16Unit*)fields[fieldDescription].c_str(), (fields[fieldDescription].size() / 2), &utf8, false ); + if ( ! utf8.empty() ) xmp->SetLocalizedText ( kXMP_NS_DC, "description", "", "x-default", utf8.c_str(), kXMP_DeleteExisting ); + + if ( ! fields[fieldCopyrightURL].empty() ) xmp->SetProperty ( kXMP_NS_XMP_Rights, "WebStatement", fields[fieldCopyrightURL].c_str(), kXMP_DeleteExisting ); + +#if ! Exclude_LicenseURL_Recon + if ( ! fields[fieldLicenseURL].empty() ) xmp->SetProperty ( kXMP_NS_XMP_Rights, "Certificate", fields[fieldLicenseURL].c_str(), kXMP_DeleteExisting ); +#endif + + imported = true; + +} + +// ================================================================================================= + +int ASF_LegacyManager::ExportLegacy ( const SXMPMeta& xmp ) +{ + int changed = 0; + objectsToExport = 0; + legacyDiff = 0; + + std::string utf8; + std::string utf16; + XMP_OptionBits flags; + + if ( ! broadcastSet ) { + if ( xmp.GetProperty ( kXMP_NS_XMP, "CreateDate", &utf8, &flags ) ) { + std::string date; + ConvertISODateToMSDate ( utf8, &date ); + if ( fields[fieldCreationDate] != date ) { + legacyDiff += date.size(); + legacyDiff -= fields[fieldCreationDate].size(); + this->SetField ( fieldCreationDate, date ); + objectsToExport |= objectFileProperties; + changed ++; + } + } + } + + if ( xmp.GetLocalizedText ( kXMP_NS_DC, "title", "", "x-default", 0, &utf8, &flags ) ) { + NormalizeStringTrailingNull ( utf8 ); + ToUTF16 ( (const UTF8Unit*)utf8.data(), utf8.size(), &utf16, false ); + if ( fields[fieldTitle] != utf16 ) { + legacyDiff += utf16.size(); + legacyDiff -= fields[fieldTitle].size(); + this->SetField ( fieldTitle, utf16 ); + objectsToExport |= objectContentDescription; + changed ++; + } + } + + utf8.clear(); + SXMPUtils::CatenateArrayItems ( xmp, kXMP_NS_DC, "creator", 0, 0, kXMPUtil_AllowCommas, &utf8 ); + if ( ! utf8.empty() ) { + NormalizeStringTrailingNull ( utf8 ); + ToUTF16 ( (const UTF8Unit*)utf8.data(), utf8.size(), &utf16, false ); + if ( fields[fieldAuthor] != utf16 ) { + legacyDiff += utf16.size(); + legacyDiff -= fields[fieldAuthor].size(); + this->SetField ( fieldAuthor, utf16 ); + objectsToExport |= objectContentDescription; + changed ++; + } + } + + if ( xmp.GetLocalizedText ( kXMP_NS_DC, "rights", "", "x-default", 0, &utf8, &flags ) ) { + NormalizeStringTrailingNull ( utf8 ); + ToUTF16 ( (const UTF8Unit*)utf8.data(), utf8.size(), &utf16, false ); + if ( fields[fieldCopyright] != utf16 ) { + legacyDiff += utf16.size(); + legacyDiff -= fields[fieldCopyright].size(); + this->SetField ( fieldCopyright, utf16 ); + objectsToExport |= objectContentDescription; + changed ++; + } + } + + if ( xmp.GetLocalizedText ( kXMP_NS_DC, "description", "", "x-default", 0, &utf8, &flags ) ) { + NormalizeStringTrailingNull ( utf8 ); + ToUTF16 ( (const UTF8Unit*)utf8.data(), utf8.size(), &utf16, false ); + if ( fields[fieldDescription] != utf16 ) { + legacyDiff += utf16.size(); + legacyDiff -= fields[fieldDescription].size(); + this->SetField ( fieldDescription, utf16 ); + objectsToExport |= objectContentDescription; + changed ++; + } + } + + if ( xmp.GetProperty ( kXMP_NS_XMP_Rights, "WebStatement", &utf8, &flags ) ) { + NormalizeStringTrailingNull ( utf8 ); + if ( fields[fieldCopyrightURL] != utf8 ) { + legacyDiff += utf8.size(); + legacyDiff -= fields[fieldCopyrightURL].size(); + this->SetField ( fieldCopyrightURL, utf8 ); + objectsToExport |= objectContentBranding; + changed ++; + } + } + +#if ! Exclude_LicenseURL_Recon + if ( xmp.GetProperty ( kXMP_NS_XMP_Rights, "Certificate", &utf8, &flags ) ) { + NormalizeStringTrailingNull ( utf8 ); + if ( fields[fieldLicenseURL] != utf8 ) { + legacyDiff += utf8.size(); + legacyDiff -= fields[fieldLicenseURL].size(); + this->SetField ( fieldLicenseURL, utf8 ); + objectsToExport |= objectContentEncryption; + changed ++; + } + } +#endif + + // find objects, that would need to be created on legacy export + int newObjects = (objectsToExport & !objectsExisting); + + // calculate minimum storage for new objects, that might be created on export + if ( newObjects & objectContentDescription ) + legacyDiff += sizeContentDescription; + if ( newObjects & objectContentBranding ) + legacyDiff += sizeContentBranding; + if ( newObjects & objectContentEncryption ) + legacyDiff += sizeContentEncryption; + + ComputeDigest(); + + return changed; + +} + +// ================================================================================================= + +bool ASF_LegacyManager::hasLegacyChanged() +{ + return (objectsToExport != 0); +} + +// ================================================================================================= + +XMP_Int64 ASF_LegacyManager::getLegacyDiff() +{ + return (this->hasLegacyChanged() ? legacyDiff : 0); +} + +// ================================================================================================= + +int ASF_LegacyManager::changedObjects() +{ + return objectsToExport; +} + +// ================================================================================================= + +void ASF_LegacyManager::SetPadding ( XMP_Int64 _padding ) +{ + padding = _padding; +} + +// ================================================================================================= + +XMP_Int64 ASF_LegacyManager::GetPadding() +{ + return padding; +} + +// ================================================================================================= + +std::string ASF_LegacyManager::NormalizeStringDisplayASCII ( std::string& operand ) +{ + std::basic_string::iterator current = operand.begin(); + std::basic_string::iterator end = operand.end();; + + for ( ; (current != end); ++current ) { + char element = *current; + if ( ( (element < 0x21) && (element != 0x00)) || (element > 0x7e) ) { + *current = '?'; + } + } + + return operand; + +} + +// ================================================================================================= + +std::string ASF_LegacyManager::NormalizeStringTrailingNull ( std::string& operand ) +{ + if ( ( operand.size() > 0) && (operand[operand.size() - 1] != '\0') ) { + operand.append ( 1, '\0' ); + } + + return operand; + +} + +// ================================================================================================= + +int ASF_LegacyManager::DaysInMonth ( XMP_Int32 year, XMP_Int32 month ) +{ + + static short daysInMonth[13] = { 0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 }; + // Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec + + int days = daysInMonth [ month ]; + if ( (month == 2) && IsLeapYear ( year ) ) days += 1; + + return days; + +} + +// ================================================================================================= + +bool ASF_LegacyManager::IsLeapYear ( long year ) +{ + + if ( year < 0 ) year = -year + 1; // Fold the negative years, assuming there is a year 0. + + if ( (year % 4) != 0 ) return false; // Not a multiple of 4. + if ( (year % 100) != 0 ) return true; // A multiple of 4 but not a multiple of 100. + if ( (year % 400) == 0 ) return true; // A multiple of 400. + + return false; // A multiple of 100 but not a multiple of 400. + +} + +// ================================================================================================= + +void ASF_LegacyManager::ConvertMSDateToISODate ( std::string& source, std::string* dest ) +{ + + XMP_Int64 creationDate = GetUns64LE ( source.c_str() ); + XMP_Int64 totalSecs = creationDate / (10*1000*1000); + XMP_Int32 nanoSec = ( ( XMP_Int32) (creationDate - (totalSecs * 10*1000*1000)) ) * 100; + + XMP_Int32 days = (XMP_Int32) (totalSecs / 86400); + totalSecs -= ( ( XMP_Int64)days * 86400 ); + + XMP_Int32 hour = (XMP_Int32) (totalSecs / 3600); + totalSecs -= ( ( XMP_Int64)hour * 3600 ); + + XMP_Int32 minute = (XMP_Int32) (totalSecs / 60); + totalSecs -= ( ( XMP_Int64)minute * 60 ); + + XMP_Int32 second = (XMP_Int32)totalSecs; + + // A little more simple code converts this to an XMP date string: + + XMP_DateTime date; + memset ( &date, 0, sizeof ( date ) ); + + date.year = 1601; // The MS date origin. + date.month = 1; + date.day = 1; + + date.day += days; // Add in the delta. + date.hour = hour; + date.minute = minute; + date.second = second; + date.nanoSecond = nanoSec; + + date.hasTimeZone = true; // ! Needed for ConvertToUTCTime to do anything. + SXMPUtils::ConvertToUTCTime ( &date ); // Normalize the date/time. + SXMPUtils::ConvertFromDate ( date, dest ); // Convert to an ISO 8601 string. + +} + +// ================================================================================================= + +void ASF_LegacyManager::ConvertISODateToMSDate ( std::string& source, std::string* dest ) +{ + XMP_DateTime date; + SXMPUtils::ConvertToDate ( source, &date ); + SXMPUtils::ConvertToUTCTime ( &date ); + + XMP_Int64 creationDate; + creationDate = date.nanoSecond / 100; + creationDate += (XMP_Int64 ( date.second) * (10*1000*1000) ); + creationDate += (XMP_Int64 ( date.minute) * 60 * (10*1000*1000) ); + creationDate += (XMP_Int64 ( date.hour) * 3600 * (10*1000*1000) ); + + XMP_Int32 days = (date.day - 1); + + --date.month; + while ( date.month >= 1 ) { + days += DaysInMonth ( date.year, date.month ); + --date.month; + } + + --date.year; + while ( date.year >= 1601 ) { + days += (IsLeapYear ( date.year) ? 366 : 365 ); + --date.year; + } + + creationDate += (XMP_Int64 ( days) * 86400 * (10*1000*1000) ); + + creationDate = GetUns64LE ( &creationDate ); + dest->assign ( (const char*)&creationDate, 8 ); + +} diff --git a/core/libs/dngwriter/extra/xmp_sdk/XMPFiles/source/FormatSupport/ASF_Support.hpp b/core/libs/dngwriter/extra/xmp_sdk/XMPFiles/source/FormatSupport/ASF_Support.hpp new file mode 100644 index 0000000000..3e170e3a0a --- /dev/null +++ b/core/libs/dngwriter/extra/xmp_sdk/XMPFiles/source/FormatSupport/ASF_Support.hpp @@ -0,0 +1,228 @@ +#ifndef __ASF_Support_hpp__ +#define __ASF_Support_hpp__ 1 + +// ================================================================================================= +// ADOBE SYSTEMS INCORPORATED +// Copyright 2006 Adobe Systems Incorporated +// All Rights Reserved +// +// NOTICE: Adobe permits you to use, modify, and distribute this file in accordance with the terms +// of the Adobe license agreement accompanying it. +// ================================================================================================= + +#include "public/include/XMP_Environment.h" // ! XMP_Environment.h must be the first included header. +#include "public/include/XMP_Const.h" + +#include "XMPFiles/source/XMPFiles_Impl.hpp" +#include "XMPFiles/source/FormatSupport/Reconcile_Impl.hpp" +#include "source/XIO.hpp" +#include "source/XMP_ProgressTracker.hpp" + +// currently exclude LicenseURL from reconciliation +#define Exclude_LicenseURL_Recon 1 +#define EXCLUDE_LICENSEURL_RECON 1 + +// Defines for platforms other than Win +#if ! XMP_WinBuild + + typedef struct _GUID + { + XMP_Uns32 Data1; + XMP_Uns16 Data2; + XMP_Uns16 Data3; + XMP_Uns8 Data4[8]; + } GUID; + + int IsEqualGUID ( const GUID& guid1, const GUID& guid2 ); + + static const GUID GUID_NULL = { 0x0, 0x0, 0x0, { 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0 } }; + +#endif + +// header object +static const GUID ASF_Header_Object = { MakeUns32LE(0x75b22630), MakeUns16LE(0x668e), MakeUns16LE(0x11cf), { 0xa6, 0xd9, 0x00, 0xaa, 0x00, 0x62, 0xce, 0x6c } }; +// contains ... +static const GUID ASF_File_Properties_Object = { MakeUns32LE(0x8cabdca1), MakeUns16LE(0xa947), MakeUns16LE(0x11cf), { 0x8e, 0xe4, 0x00, 0xc0, 0x0c, 0x20, 0x53, 0x65 } }; +static const GUID ASF_Content_Description_Object = { MakeUns32LE(0x75b22633), MakeUns16LE(0x668e), MakeUns16LE(0x11cf), { 0xa6, 0xd9, 0x00, 0xaa, 0x00, 0x62, 0xce, 0x6c } }; +static const GUID ASF_Content_Branding_Object = { MakeUns32LE(0x2211b3fa), MakeUns16LE(0xbd23), MakeUns16LE(0x11d2), { 0xb4, 0xb7, 0x00, 0xa0, 0xc9, 0x55, 0xfc, 0x6e } }; +static const GUID ASF_Content_Encryption_Object = { MakeUns32LE(0x2211b3fb), MakeUns16LE(0xbd23), MakeUns16LE(0x11d2), { 0xb4, 0xb7, 0x00, 0xa0, 0xc9, 0x55, 0xfc, 0x6e } }; +// padding +// Remark: regarding to Microsofts spec only the ASF_Header_Object contains a ASF_Padding_Object +// Real world files show, that the ASF_Header_Extension_Object contains a ASF_Padding_Object +static const GUID ASF_Header_Extension_Object = { MakeUns32LE(0x5fbf03b5), MakeUns16LE(0xa92e), MakeUns16LE(0x11cf), { 0x8e, 0xe3, 0x00, 0xc0, 0x0c, 0x20, 0x53, 0x65 } }; +static const GUID ASF_Padding_Object = { MakeUns32LE(0x1806d474), MakeUns16LE(0xcadf), MakeUns16LE(0x4509), { 0xa4, 0xba, 0x9a, 0xab, 0xcb, 0x96, 0xaa, 0xe8 } }; + +// data object +static const GUID ASF_Data_Object = { MakeUns32LE(0x75b22636), MakeUns16LE(0x668e), MakeUns16LE(0x11cf), { 0xa6, 0xd9, 0x00, 0xaa, 0x00, 0x62, 0xce, 0x6c } }; + +// XMP object +static const GUID ASF_XMP_Metadata = { MakeUns32LE(0xbe7acfcb), MakeUns16LE(0x97a9), MakeUns16LE(0x42e8), { 0x9c, 0x71, 0x99, 0x94, 0x91, 0xe3, 0xaf, 0xac } }; + +static const int guidLen = sizeof(GUID); + +typedef struct _ASF_ObjectBase +{ + GUID guid; + XMP_Uns64 size; + +} ASF_ObjectBase; + +static const XMP_Uns32 kASF_ObjectBaseLen = (XMP_Uns32) sizeof(ASF_ObjectBase); + +// ================================================================================================= + +class ASF_LegacyManager { +public: + + enum objectType { + objectFileProperties = 1 << 0, + objectContentDescription = 1 << 1, + objectContentBranding = 1 << 2, + objectContentEncryption = 1 << 3 + }; + + enum minObjectSize { + sizeContentDescription = 34, + sizeContentBranding = 40, + sizeContentEncryption = 40 + }; + + enum fieldType { + // File_Properties_Object + fieldCreationDate = 0, + // Content_Description_Object + fieldTitle, + fieldAuthor, + fieldCopyright, + fieldDescription, + // Content_Branding_Object + fieldCopyrightURL, + #if ! Exclude_LicenseURL_Recon + // Content_Encryption_Object + fieldLicenseURL, + #endif + // last + fieldLast + }; + + ASF_LegacyManager(); + virtual ~ASF_LegacyManager(); + + bool SetField ( fieldType field, const std::string& value ); + std::string GetField ( fieldType field ); + unsigned int GetFieldMaxSize ( fieldType field ); + + void SetObjectExists ( objectType object ); + + void SetBroadcast ( const bool broadcast ); + bool GetBroadcast(); + + void ComputeDigest(); + bool CheckDigest ( const SXMPMeta& xmp ); + void SetDigest ( SXMPMeta* xmp ); + + void ImportLegacy ( SXMPMeta* xmp ); + int ExportLegacy ( const SXMPMeta& xmp ); + bool hasLegacyChanged(); + XMP_Int64 getLegacyDiff(); + int changedObjects(); + + void SetPadding ( XMP_Int64 padding ); + XMP_Int64 GetPadding(); + +private: + + typedef std::vector TFields; + TFields fields; + bool broadcastSet; + + std::string digestStr; + bool digestComputed; + + bool imported; + int objectsExisting; + int objectsToExport; + XMP_Int64 legacyDiff; + XMP_Int64 padding; + + static std::string NormalizeStringDisplayASCII ( std::string& operand ); + static std::string NormalizeStringTrailingNull ( std::string& operand ); + + static void ConvertMSDateToISODate ( std::string& source, std::string* dest ); + static void ConvertISODateToMSDate ( std::string& source, std::string* dest ); + + static int DaysInMonth ( XMP_Int32 year, XMP_Int32 month ); + static bool IsLeapYear ( long year ); + +}; // class ASF_LegacyManager + +// ================================================================================================= + +class ASF_Support { +public: + + class ObjectData { + public: + ObjectData() : pos(0), len(0), guid(GUID_NULL), xmp(false) {} + virtual ~ObjectData() {} + XMP_Uns64 pos; // file offset of object + XMP_Uns64 len; // length of object data + GUID guid; // object GUID + bool xmp; // object with XMP ? + }; + + typedef std::vector ObjectVector; + typedef ObjectVector::iterator ObjectIterator; + + class ObjectState { + + public: + ObjectState() : xmpPos(0), xmpLen(0), xmpIsLastObject(false), broadcast(false) {} + virtual ~ObjectState() {} + XMP_Uns64 xmpPos; + XMP_Uns64 xmpLen; + bool xmpIsLastObject; + bool broadcast; + ObjectData xmpObject; + ObjectVector objects; + }; + + ASF_Support(); + ASF_Support ( ASF_LegacyManager* legacyManager, XMP_ProgressTracker* _progressTracker); + virtual ~ASF_Support(); + + long OpenASF ( XMP_IO* fileRef, ObjectState & inOutObjectState ); + + bool ReadObject ( XMP_IO* fileRef, ObjectState & inOutObjectState, XMP_Uns64 * objectLength, XMP_Uns64 & inOutPosition ); + + bool ReadHeaderObject ( XMP_IO* fileRef, ObjectState& inOutObjectState, const ObjectData& newObject ); + bool WriteHeaderObject ( XMP_IO* sourceRef, XMP_IO* destRef, const ObjectData& object, ASF_LegacyManager& legacyManager, bool usePadding ); + bool UpdateHeaderObject ( XMP_IO* fileRef, const ObjectData& object, ASF_LegacyManager& legacyManager ); + + bool UpdateFileSize ( XMP_IO* fileRef ); + + bool ReadHeaderExtensionObject ( XMP_IO* fileRef, ObjectState& inOutObjectState, const XMP_Uns64& pos, const ASF_ObjectBase& objectBase ); + static bool WriteHeaderExtensionObject ( const std::string& buffer, std::string* header, const ASF_ObjectBase& objectBase, const int reservePadding ); + + static bool CreatePaddingObject ( std::string* header, const XMP_Uns64 size ); + + static bool WriteXMPObject ( XMP_IO* fileRef, XMP_Uns32 len, const char* inBuffer ); + static bool UpdateXMPObject ( XMP_IO* fileRef, const ObjectData& object, XMP_Uns32 len, const char * inBuffer ); + static bool CopyObject ( XMP_IO* sourceRef, XMP_IO* destRef, const ObjectData& object ); + + static bool ReadBuffer ( XMP_IO* fileRef, XMP_Uns64 & pos, XMP_Uns64 len, char * outBuffer ); + static bool WriteBuffer ( XMP_IO* fileRef, XMP_Uns64 & pos, XMP_Uns32 len, const char * inBuffer ); + +private: + + ASF_LegacyManager* legacyManager; + XMP_ProgressTracker* progressTracker;//not owned by ASF_Support + XMP_Uns64 posFileSizeInfo; + + static std::string ReplaceString ( std::string& operand, std::string& str, int offset, int count ); + +}; // class ASF_Support + +// ================================================================================================= + +#endif // __ASF_Support_hpp__ diff --git a/core/libs/dngwriter/extra/xmp_sdk/XMPFiles/source/FormatSupport/ID3_Support.cpp b/core/libs/dngwriter/extra/xmp_sdk/XMPFiles/source/FormatSupport/ID3_Support.cpp new file mode 100644 index 0000000000..abe0923551 --- /dev/null +++ b/core/libs/dngwriter/extra/xmp_sdk/XMPFiles/source/FormatSupport/ID3_Support.cpp @@ -0,0 +1,883 @@ +// ================================================================================================= +// ADOBE SYSTEMS INCORPORATED +// Copyright 2008 Adobe Systems Incorporated +// All Rights Reserved +// +// NOTICE: Adobe permits you to use, modify, and distribute this file in accordance with the terms +// of the Adobe license agreement accompanying it. +// ================================================================================================= + +#include "public/include/XMP_Environment.h" // ! This must be the first include. + +#include "XMPFiles/source/XMPFiles_Impl.hpp" +#include "XMPFiles/source/FormatSupport/ID3_Support.hpp" +#include "XMPFiles/source/FormatSupport/Reconcile_Impl.hpp" + +#include "source/UnicodeConversions.hpp" +#include "source/XIO.hpp" + +#include + +#define MIN(a,b) ((a) < (b) ? (a) : (b)) + +namespace ID3_Support { + +// ================================================================================================= + +ID3GenreMap* kMapID3GenreCodeToName = 0; // Map from a code like "21" or "RX" to the full name. +ID3GenreMap* kMapID3GenreNameToCode = 0; // Map from the full name to a code like "21" or "RX". + +static size_t numberedGenreCount = 0; // Set in InitializeGlobals, used in ID3v1Tag::read and write. + +struct GenreInfo { const char * code; const char * name; }; + +static const GenreInfo kAbbreviatedGenres[] = { // ID3 v3 or v4 genre abbreviations. + { "RX", "Remix" }, + { "CR", "Cover" }, + { 0, 0 } +}; + +static const GenreInfo kNumberedGenres[] = { // Numeric genre codes from ID3 v1, complete range of 0..125. + { "0", "Blues" }, + { "1", "Classic Rock" }, + { "2", "Country" }, + { "3", "Dance" }, + { "4", "Disco" }, + { "5", "Funk" }, + { "6", "Grunge" }, + { "7", "Hip-Hop" }, + { "8", "Jazz" }, + { "9", "Metal" }, + { "10", "New Age" }, + { "11", "Oldies" }, + { "12", "Other" }, + { "13", "Pop" }, + { "14", "R&B" }, + { "15", "Rap" }, + { "16", "Reggae" }, + { "17", "Rock" }, + { "18", "Techno" }, + { "19", "Industrial" }, + { "20", "Alternative" }, + { "21", "Ska" }, + { "22", "Death Metal" }, + { "23", "Pranks" }, + { "24", "Soundtrack" }, + { "25", "Euro-Techno" }, + { "26", "Ambient" }, + { "27", "Trip-Hop" }, + { "28", "Vocal" }, + { "29", "Jazz+Funk" }, + { "30", "Fusion" }, + { "31", "Trance" }, + { "32", "Classical" }, + { "33", "Instrumental" }, + { "34", "Acid" }, + { "35", "House" }, + { "36", "Game" }, + { "37", "Sound Clip" }, + { "38", "Gospel" }, + { "39", "Noise" }, + { "40", "AlternRock" }, + { "41", "Bass" }, + { "42", "Soul" }, + { "43", "Punk" }, + { "44", "Space" }, + { "45", "Meditative" }, + { "46", "Instrumental Pop" }, + { "47", "Instrumental Rock" }, + { "48", "Ethnic" }, + { "49", "Gothic" }, + { "50", "Darkwave" }, + { "51", "Techno-Industrial" }, + { "52", "Electronic" }, + { "53", "Pop-Folk" }, + { "54", "Eurodance" }, + { "55", "Dream" }, + { "56", "Southern Rock" }, + { "57", "Comedy" }, + { "58", "Cult" }, + { "59", "Gangsta" }, + { "60", "Top 40" }, + { "61", "Christian Rap" }, + { "62", "Pop/Funk" }, + { "63", "Jungle" }, + { "64", "Native American" }, + { "65", "Cabaret" }, + { "66", "New Wave" }, + { "67", "Psychadelic" }, + { "68", "Rave" }, + { "69", "Showtunes" }, + { "70", "Trailer" }, + { "71", "Lo-Fi" }, + { "72", "Tribal" }, + { "73", "Acid Punk" }, + { "74", "Acid Jazz" }, + { "75", "Polka" }, + { "76", "Retro" }, + { "77", "Musical" }, + { "78", "Rock & Roll" }, + { "79", "Hard Rock" }, + { "80", "Folk" }, + { "81", "Folk-Rock" }, + { "82", "National Folk" }, + { "83", "Swing" }, + { "84", "Fast Fusion" }, + { "85", "Bebob" }, + { "86", "Latin" }, + { "87", "Revival" }, + { "88", "Celtic" }, + { "89", "Bluegrass" }, + { "90", "Avantgarde" }, + { "91", "Gothic Rock" }, + { "92", "Progressive Rock" }, + { "93", "Psychedelic Rock" }, + { "94", "Symphonic Rock" }, + { "95", "Slow Rock" }, + { "96", "Big Band" }, + { "97", "Chorus" }, + { "98", "Easy Listening" }, + { "99", "Acoustic" }, + { "100", "Humour" }, + { "101", "Speech" }, + { "102", "Chanson" }, + { "103", "Opera" }, + { "104", "Chamber Music" }, + { "105", "Sonata" }, + { "106", "Symphony" }, + { "107", "Booty Bass" }, + { "108", "Primus" }, + { "109", "Porn Groove" }, + { "110", "Satire" }, + { "111", "Slow Jam" }, + { "112", "Club" }, + { "113", "Tango" }, + { "114", "Samba" }, + { "115", "Folklore" }, + { "116", "Ballad" }, + { "117", "Power Ballad" }, + { "118", "Rhythmic Soul" }, + { "119", "Freestyle" }, + { "120", "Duet" }, + { "121", "Punk Rock" }, + { "122", "Drum Solo" }, + { "123", "A capella" }, // ! Should be Acapella, keep space for compatibility with old code. + { "124", "Euro-House" }, + { "125", "Dance Hall" }, + { 0, 0 } +}; + +// ================================================================================================= + +bool InitializeGlobals() +{ + + kMapID3GenreCodeToName = new ID3GenreMap; + if ( kMapID3GenreCodeToName == 0 ) return false; + kMapID3GenreNameToCode = new ID3GenreMap; + if ( kMapID3GenreNameToCode == 0 ) return false; + + ID3GenreMap::value_type newValue; + + size_t i; + + for ( i = 0; kNumberedGenres[i].code != 0; ++i ) { + XMP_Assert ( (long)i == strtol ( kNumberedGenres[i].code, 0, 10 ) ); + ID3GenreMap::value_type code2Name ( kNumberedGenres[i].code, kNumberedGenres[i].name ); + kMapID3GenreCodeToName->insert ( kMapID3GenreCodeToName->end(), code2Name ); + ID3GenreMap::value_type name2Code ( kNumberedGenres[i].name, kNumberedGenres[i].code ); + kMapID3GenreNameToCode->insert ( kMapID3GenreNameToCode->end(), name2Code ); + } + + numberedGenreCount = i; // Used in ID3v1Tag::read and write. + + for ( i = 0; kAbbreviatedGenres[i].code != 0; ++i ) { + ID3GenreMap::value_type code2Name ( kAbbreviatedGenres[i].code, kAbbreviatedGenres[i].name ); + kMapID3GenreCodeToName->insert ( kMapID3GenreCodeToName->end(), code2Name ); + ID3GenreMap::value_type name2Code ( kAbbreviatedGenres[i].name, kAbbreviatedGenres[i].code ); + kMapID3GenreNameToCode->insert ( kMapID3GenreNameToCode->end(), name2Code ); + } + + return true; + +} // InitializeGlobals + +// ================================================================================================= + +void TerminateGlobals() +{ + delete kMapID3GenreCodeToName; + delete kMapID3GenreNameToCode; + kMapID3GenreCodeToName = kMapID3GenreNameToCode = 0; +} + +// ================================================================================================= +// GenreUtils +// ================================================================================================= + +const char * GenreUtils::FindGenreName ( const std::string & code ) +{ + // Lookup a genre code and return its name if known, otherwise 0. + + const char * name = 0; + ID3GenreMap::iterator mapPos = kMapID3GenreCodeToName->find ( code.c_str() ); + if ( mapPos != kMapID3GenreCodeToName->end() ) name = mapPos->second; + return name; + +} + +// ================================================================================================= + +const char * GenreUtils::FindGenreCode ( const std::string & name ) +{ + // Lookup a genre name and return its code if known, otherwise 0. + + const char * code = 0; + ID3GenreMap::iterator mapPos = kMapID3GenreNameToCode->find ( name.c_str() ); + if ( mapPos != kMapID3GenreNameToCode->end() ) code = mapPos->second; + return code; + +} + +// ================================================================================================= + +static void StripOutsideSpaces ( std::string * value ) +{ + size_t length = value->size(); + size_t first, last; + + for ( first = 0; ((first < length) && ((*value)[first] == ' ')); ++first ) {} + if ( first == length ) { value->erase(); return; } + XMP_Assert ( (first < length) && ((*value)[first] != ' ') ); + + for ( last = length-1; ((last > first) && ((*value)[last] == ' ')); --last ) {} + if ( (first == 0) && (last == length-1) ) return; + + size_t newLen = last - first + 1; + if ( newLen < length ) *value = value->substr ( first, newLen ); + +} + +// ================================================================================================= + +void GenreUtils::ConvertGenreToXMP ( const char * id3Genre, std::string * xmpGenre ) +{ + // If the first character of TCON is not '(' then the entire TCON value is taken as the genre + // name and the suffix is empty. + // + // If the first character of TCON is '(' then the string up to ')' (or the end) is taken as the + // coded genre name. The rest of the TCON value after ')' is taken as the suffix. + // + // If the coded name is known then the corresponsing full name is used as the genre name, with + // no parens. + // + // If the coded name is not known then the coded name with parens is used as the genre name. + // + // The value of xmpDM:genre begins with the genre name. If the suffix is not empty we append + // "; " and the suffix. The known coded genre names currently do not use semicolon. + // + // Keeping the parens when importing unknown coded names might seem odd. But it preserves the + // ID3 syntax when exporting. Otherwise we would import "(XX)" and export "XX". We don't add + // parens all the time on export, that would import "Blues/R&B" and export "(Blues/R&B)". + + xmpGenre->erase(); + size_t id3Length = strlen ( id3Genre ); + if ( id3Length == 0 ) return; + + if ( id3Genre[0] != '(' ) { + // No left paren, take the whole TCON value as the XMP value. + xmpGenre->assign ( id3Genre, id3Length ); + StripOutsideSpaces ( xmpGenre ); + return; + } + + // The first character of TCON is '(', process the coded part and the suffix. + + size_t codeEnd; + std::string genreCode, suffix; + + for ( codeEnd = 1; ((codeEnd < id3Length) && (id3Genre[codeEnd] != ')')); ++codeEnd ) {} + genreCode.assign ( &id3Genre[1], codeEnd-1 ); + if ( codeEnd < id3Length ) suffix.assign ( &id3Genre[codeEnd+1], id3Length-codeEnd-1 ); + + StripOutsideSpaces ( &genreCode ); + StripOutsideSpaces ( &suffix ); + + if ( genreCode.empty() ) { + + (*xmpGenre) = suffix; // Degenerate case of "()suffix", treat as if "suffix". + + } else { + + const char * fullName = FindGenreName ( genreCode ); + + if ( fullName != 0 ) { + (*xmpGenre) = fullName; + } else { + (*xmpGenre) = '('; + (*xmpGenre) += genreCode; + (*xmpGenre) += ')'; + } + + if ( ! suffix.empty() ) { + (*xmpGenre) += "; "; + (*xmpGenre) += suffix; + } + + } + +} + +// ================================================================================================= + +void GenreUtils::ConvertGenreToID3 ( const char * xmpGenre, std::string * id3Genre ) +{ + // The genre name is the xmpDM:genre value up to ';', with spaces at the front or back removed. + // The suffix is everything after ';', also with spaces at the front or back removed. + // + // If the genre name is known, it is replaced by the coded name in parens. + // + // The TCON value is the genre name plus the suffix. If the genre name does not end in ')' then + // a space is inserted. + + id3Genre->erase(); + size_t xmpLength = strlen ( xmpGenre ); + if ( xmpLength == 0 ) return; + + size_t nameEnd; + std::string genreName, suffix; + + for ( nameEnd = 0; ((nameEnd < xmpLength) && (xmpGenre[nameEnd] != ';')); ++nameEnd ) {} + genreName.assign ( xmpGenre, nameEnd ); + if ( nameEnd < xmpLength ) suffix.assign ( &xmpGenre[nameEnd+1], xmpLength-nameEnd-1 ); + + StripOutsideSpaces ( &genreName ); + StripOutsideSpaces ( &suffix ); + + if ( genreName.empty() ) { + + (*id3Genre) = suffix; // Degenerate case of "; suffix", treat as if "suffix". + + } else { + + const char * codedName = FindGenreCode ( genreName ); + if ( codedName != 0 ) { + genreName = '('; + genreName += codedName; + genreName += ')'; + } + + (*id3Genre) = genreName; + if ( ! suffix.empty() ) { + if ( genreName[genreName.size()-1] != ')' ) (*id3Genre) += ' '; + (*id3Genre) += suffix; + } + + } + +} + +// ================================================================================================= +// ID3Header +// ================================================================================================= + +bool ID3Header::read ( XMP_IO* file ) +{ + + XMP_Assert ( sizeof(fields) == kID3_TagHeaderSize ); + file->ReadAll ( this->fields, kID3_TagHeaderSize ); + + if ( ! CheckBytes ( &this->fields[ID3Header::o_id], "ID3", 3 ) ) { + // chuck in default contents: + const static char defaultHeader[kID3_TagHeaderSize] = { 'I', 'D', '3', 3, 0, 0, 0, 0, 0, 0 }; + memcpy ( this->fields, defaultHeader, kID3_TagHeaderSize ); + return false; // no header found (o.k.) thus stick with new, default header constructed above + } + + XMP_Uns8 major = this->fields[o_vMajor]; + XMP_Uns8 minor = this->fields[o_vMinor]; + XMP_Validate ( ((2 <= major) && (major <= 4)), "Invalid ID3 major version", kXMPErr_BadFileFormat ); + + return true; + +} + +// ================================================================================================= + +void ID3Header::write ( XMP_IO* file, XMP_Int64 tagSize ) +{ + + XMP_Assert ( ((XMP_Int64)kID3_TagHeaderSize <= tagSize) && (tagSize < 256*1024*1024) ); // 256 MB limit due to synching. + + XMP_Uns32 synchSize = int32ToSynch ( (XMP_Uns32)tagSize - kID3_TagHeaderSize ); + PutUns32BE ( synchSize, &this->fields[ID3Header::o_size] ); + file->Write ( this->fields, kID3_TagHeaderSize ); + +} + +// ================================================================================================= +// ID3v2Frame +// ================================================================================================= + +#define frameDefaults id(0), flags(0), content(0), contentSize(0), active(true), changed(false) + +ID3v2Frame::ID3v2Frame() : frameDefaults +{ + XMP_Assert ( sizeof(fields) == kV23_FrameHeaderSize ); // Only need to do this in one place. + memset ( this->fields, 0, kV23_FrameHeaderSize ); +} + +// ================================================================================================= + +ID3v2Frame::ID3v2Frame ( XMP_Uns32 id ) : frameDefaults +{ + memset ( this->fields, 0, kV23_FrameHeaderSize ); + this->id = id; + PutUns32BE ( id, &this->fields[o_id] ); +} + +// ================================================================================================= + +void ID3v2Frame::release() +{ + if ( this->content != 0 ) delete this->content; + this->content = 0; + this->contentSize = 0; +} + +// ================================================================================================= + +void ID3v2Frame::setFrameValue( const std::string& rawvalue, bool needDescriptor, + bool utf16, bool isXMPPRIVFrame, bool needEncodingByte, bool isAlreadyEncoded /* = false */ ) +{ + + std::string value; + + if ( isXMPPRIVFrame ) { + + XMP_Assert( ( !needDescriptor ) && ( !utf16 ) ); + + value.append( "XMP\0", 4 ); + value.append( rawvalue ); + value.append( "\0", 1 ); // final zero byte + + } + else if ( !isAlreadyEncoded ) { + + if ( needEncodingByte ) { + if ( utf16 ) { + value.append( "\x1", 1 ); + } + else { + value.append( "\x0", 1 ); + } + } + + if ( needDescriptor ) value.append( "eng", 3 ); + + if ( utf16 ) { + + if ( needDescriptor ) value.append( "\xFF\xFE\0\0", 4 ); + + value.append( "\xFF\xFE", 2 ); + std::string utf16str; + ToUTF16( ( XMP_Uns8* ) rawvalue.c_str(), rawvalue.size(), &utf16str, false ); + value.append( utf16str ); + value.append( "\0\0", 2 ); + + } + else { + + std::string convertedValue; + ReconcileUtils::UTF8ToLatin1( rawvalue.c_str(), rawvalue.size(), &convertedValue ); + + if ( needDescriptor ) value.append( "\0", 1 ); + value.append( convertedValue ); + value.append( "\0", 1 ); + + } + + } + + this->changed = true; + this->release(); + + if ( isAlreadyEncoded ) + { + XMP_Assert( ( !needDescriptor ) && ( !utf16 ) && value.empty() ); + this->contentSize = ( XMP_Int32 ) rawvalue.size(); + } + else + this->contentSize = ( XMP_Int32 ) value.size(); + XMP_Validate( ( this->contentSize < 20 * 1024 * 1024 ), "XMP Property exceeds 20MB in size", kXMPErr_InternalFailure ); + this->content = new char[ this->contentSize ]; + if ( isAlreadyEncoded ) + memcpy( this->content, rawvalue.c_str(), this->contentSize ); + else + memcpy( this->content, value.c_str(), this->contentSize ); + +} // ID3v2Frame::setFrameValue + +// ================================================================================================= + +XMP_Int64 ID3v2Frame::read ( XMP_IO* file, XMP_Uns8 majorVersion ) +{ + XMP_Assert ( (2 <= majorVersion) && (majorVersion <= 4) ); + + this->release(); // ensures/allows reuse of 'curFrame' + XMP_Int64 start = file->Offset(); + + if ( majorVersion > 2 ) { + file->ReadAll ( this->fields, kV23_FrameHeaderSize ); + } else { + // Read the 6 byte v2.2 header into the 10 byte form. + memset ( this->fields, 0, kV23_FrameHeaderSize ); // Clear all of the bytes. + file->ReadAll ( &this->fields[o_id], 3 ); // Leave the low order byte as zero. + file->ReadAll ( &this->fields[o_size+1], 3 ); // Read big endian UInt24. + } + + this->id = GetUns32BE ( &this->fields[o_id] ); + + if ( this->id == 0 ) { + file->Seek ( start, kXMP_SeekFromStart ); // Zero ID must mean nothing but padding. + return 0; + } + + this->flags = GetUns16BE ( &this->fields[o_flags] ); + XMP_Validate ( (0 == (this->flags & 0xEE)), "invalid lower bits in frame flags", kXMPErr_BadFileFormat ); + + //*** flag handling, spec :429ff aka line 431ff (i.e. Frame should be discarded) + // compression and all of that..., unsynchronisation + this->contentSize = GetUns32BE ( &this->fields[o_size] ); + if ( majorVersion == 4 ) this->contentSize = synchToInt32 ( this->contentSize ); + + XMP_Validate ( (this->contentSize >= 0), "negative frame size", kXMPErr_BadFileFormat ); + XMP_Validate ( (this->contentSize < 20*1024*1024), "single frame exceeds 20MB", kXMPErr_BadFileFormat ); + + this->content = new char [ this->contentSize ]; + + file->ReadAll ( this->content, this->contentSize ); + return file->Offset() - start; + +} // ID3v2Frame::read + +// ================================================================================================= + +void ID3v2Frame::write ( XMP_IO* file, XMP_Uns8 majorVersion ) +{ + XMP_Assert ( (2 <= majorVersion) && (majorVersion <= 4) ); + + if ( majorVersion < 4 ) { + PutUns32BE ( this->contentSize, &this->fields[o_size] ); + } else { + PutUns32BE ( int32ToSynch ( this->contentSize ), &this->fields[o_size] ); + } + + if ( majorVersion > 2 ) { + file->Write ( this->fields, kV23_FrameHeaderSize ); + } else { + file->Write ( &this->fields[o_id], 3 ); + file->Write ( &this->fields[o_size+1], 3 ); + } + + file->Write ( this->content, this->contentSize ); + +} // ID3v2Frame::write + +// ================================================================================================= + +bool ID3v2Frame::advancePastCOMMDescriptor ( XMP_Int32& pos ) +{ + + if ( (this->contentSize - pos) <= 3 ) return false; // silent error, no room left behing language tag + if ( ! CheckBytes ( &this->content[pos], "eng", 3 ) ) return false; // not an error, but leave all non-eng tags alone... + + pos += 3; // skip lang tag + if ( pos >= this->contentSize ) return false; // silent error + + while ( pos < this->contentSize ) { + if ( this->content[pos++] == 0x00 ) break; + } + if ( (pos < this->contentSize) && (this->content[pos] == 0x00) ) pos++; + + if ( (pos == 5) && (this->contentSize == 6) && (GetUns16BE(&this->content[4]) == 0x0031) ) { + return false; + } + + if ( pos > 4 ) { + std::string descriptor = std::string ( &this->content[4], pos-1 ); + if ( 0 == descriptor.substr(0,4).compare( "iTun" ) ) { // begins with engiTun ? + return false; // leave alone, then + } + } + + return true; //o.k., descriptor skipped, time for the real thing. + +} // ID3v2Frame::advancePastCOMMDescriptor + +// ================================================================================================= + +bool ID3v2Frame::getFrameValue ( XMP_Uns8 majorVersion, XMP_Uns32 logicalID, std::string* utf8string ) +{ + + XMP_Assert ( (this->content != 0) && (this->contentSize >= 0) && (this->contentSize < 20*1024*1024) ); + + if ( this->contentSize == 0 ) { + utf8string->erase(); + return true; // ...it is "of interest", even if empty contents. + } + + XMP_Int32 pos = 0; + XMP_Uns8 encByte = 0; + // WCOP does not have an encoding byte, for all others: use [0] as EncByte, advance pos + if ( logicalID != 0x57434F50 ) { + encByte = this->content[0]; + pos++; + } + + // mode specific forks, COMM or USLT + bool commMode = ( (logicalID == 0x434F4D4D) || (logicalID == 0x55534C54) ); + + switch ( encByte ) { + + case 0: //ISO-8859-1, 0-terminated + { + if ( commMode && (! advancePastCOMMDescriptor ( pos )) ) return false; // not a frame of interest! + + char* localPtr = &this->content[pos]; + size_t localLen = this->contentSize - pos; + ReconcileUtils::Latin1ToUTF8 ( localPtr, localLen, utf8string ); + break; + + } + + case 1: // Unicode, v2.4: UTF-16 (undetermined Endianess), with BOM, terminated 0x00 00 + case 2: // UTF-16BE without BOM, terminated 0x00 00 + { + + if ( commMode && (! advancePastCOMMDescriptor ( pos )) ) return false; // not a frame of interest! + + std::string tmp ( this->content, this->contentSize ); + bool bigEndian = true; // assume for now (if no BOM follows) + + if ( GetUns16BE ( &this->content[pos] ) == 0xFEFF ) { + pos += 2; + bigEndian = true; + } else if ( GetUns16BE ( &this->content[pos] ) == 0xFFFE ) { + pos += 2; + bigEndian = false; + } + + FromUTF16 ( (UTF16Unit*)&this->content[pos], ((this->contentSize - pos)) / 2, utf8string, bigEndian ); + break; + + } + + case 3: // UTF-8 unicode, terminated \0 + { + if ( commMode && (! advancePastCOMMDescriptor ( pos )) ) return false; // not a frame of interest! + + if ( (GetUns32BE ( &this->content[pos]) & 0xFFFFFF00 ) == 0xEFBBBF00 ) { + pos += 3; // swallow any BOM, just in case + } + + utf8string->assign ( &this->content[pos], (this->contentSize - pos) ); + break; + } + + default: + XMP_Throw ( "unknown text encoding", kXMPErr_BadFileFormat ); //COULDDO assume latin-1 or utf-8 as best-effort + break; + + } + + return true; + +} // ID3v2Frame::getFrameValue + +// ================================================================================================= +// ID3v1Tag +// ================================================================================================= + +bool ID3v1Tag::read ( XMP_IO* file, SXMPMeta* meta ) +{ + // Returns true if ID3v1 (or v1.1) exists, otherwise false, sets XMP properties en route. + + if ( file->Length() <= 128 ) return false; // ensure sufficient room + file->Seek ( -128, kXMP_SeekFromEnd ); + + XMP_Uns32 tagID = XIO::ReadInt32_BE ( file ); + tagID = tagID & 0xFFFFFF00; // wipe 4th byte + if ( tagID != 0x54414700 ) return false; // must be "TAG" + file->Seek ( -1, kXMP_SeekFromCurrent ); //rewind 1 + + XMP_Uns8 buffer[31]; // nothing is bigger here, than 30 bytes (offsets [0]-[29]) + buffer[30] = 0; // wipe last byte + std::string utf8string; + + file->ReadAll ( buffer, 30 ); + std::string title ( (char*) buffer ); //security: guaranteed to 0-terminate after 30 bytes + if ( ! title.empty() ) { + ReconcileUtils::Latin1ToUTF8 ( title.c_str(), title.size(), &utf8string ); + meta->SetLocalizedText ( kXMP_NS_DC, "title", "", "x-default", utf8string.c_str() ); + } + + file->ReadAll ( buffer, 30 ); + std::string artist( (char*) buffer ); + if ( ! artist.empty() ) { + ReconcileUtils::Latin1ToUTF8 ( artist.c_str(), artist.size(), &utf8string ); + meta->SetProperty ( kXMP_NS_DM, "artist", utf8string.c_str() ); + } + + file->ReadAll ( buffer, 30 ); + std::string album( (char*) buffer ); + if ( ! album.empty() ) { + ReconcileUtils::Latin1ToUTF8 ( album.c_str(), album.size(), &utf8string ); + meta->SetProperty ( kXMP_NS_DM, "album", utf8string.c_str() ); + } + + file->ReadAll ( buffer, 4 ); + buffer[4]=0; // ensure 0-term + std::string year( (char*) buffer ); + if ( ! year.empty() ) { // should be moot for a year, but let's be safe: + ReconcileUtils::Latin1ToUTF8 ( year.c_str(), year.size(), &utf8string ); + meta->SetProperty ( kXMP_NS_XMP, "CreateDate", utf8string.c_str() ); + } + + file->ReadAll ( buffer, 30 ); + std::string comment( (char*) buffer ); + if ( ! comment.empty() ) { + ReconcileUtils::Latin1ToUTF8 ( comment.c_str(), comment.size(), &utf8string ); + meta->SetProperty ( kXMP_NS_DM, "logComment", utf8string.c_str() ); + } + + if ( buffer[28] == 0 ) { + XMP_Uns8 trackNo = buffer[29]; + if ( trackNo > 0 ) { + std::string trackStr; + meta->SetProperty_Int ( kXMP_NS_DM, "trackNumber", trackNo ); + } + } + + XMP_Uns8 genreNo = XIO::ReadUns8 ( file ); + if ( genreNo < numberedGenreCount ) { + meta->SetProperty ( kXMP_NS_DM, "genre", kNumberedGenres[genreNo].name ); + } else { + char buffer[4]; // AUDIT: Big enough for UInt8. + snprintf ( buffer, 4, "%d", genreNo ); + XMP_Assert ( strlen(buffer) == 3 ); // Should be in the range 126..255. + meta->SetProperty ( kXMP_NS_DM, "genre", buffer ); + } + + return true; // ID3Tag found + +} // ID3v1Tag::read + +// ================================================================================================= + +static inline bool GetDecimalUns32 ( const char * str, XMP_Uns32 * bin ) +{ + XMP_Assert ( bin != 0 ); + if ( (str == 0) || (str[0] == 0) ) return false; + + *bin = 0; + for ( size_t i = 0; str[i] != 0; ++i ) { + char ch = str[i]; + if ( (ch < '0') || (ch > '9') ) return false; + *bin = (*bin * 10) + (ch - '0'); + } + + return true; + +} + +// ================================================================================================= + +void ID3v1Tag::write ( XMP_IO* file, SXMPMeta* meta ) +{ + + std::string zeros ( 128, '\0' ); + std::string utf8, latin1; + + file->Seek ( -128, kXMP_SeekFromEnd ); + file->Write ( zeros.data(), 128 ); + + file->Seek ( -128, kXMP_SeekFromEnd ); + XIO::WriteUns8 ( file, 'T' ); + XIO::WriteUns8 ( file, 'A' ); + XIO::WriteUns8 ( file, 'G' ); + + if ( meta->GetLocalizedText ( kXMP_NS_DC, "title", "", "x-default", 0, &utf8, 0 ) ) { + file->Seek ( (-128 + 3), kXMP_SeekFromEnd ); + ReconcileUtils::UTF8ToLatin1 ( utf8.c_str(), utf8.size(), &latin1 ); + file->Write ( latin1.c_str(), MIN ( 30, (XMP_Int32)latin1.size() ) ); + } + + if ( meta->GetProperty ( kXMP_NS_DM, "artist", &utf8, 0 ) ) { + file->Seek ( (-128 + 33), kXMP_SeekFromEnd ); + ReconcileUtils::UTF8ToLatin1 ( utf8.c_str(), utf8.size(), &latin1 ); + file->Write ( latin1.c_str(), MIN ( 30, (XMP_Int32)latin1.size() ) ); + } + + if ( meta->GetProperty ( kXMP_NS_DM, "album", &utf8, 0 ) ) { + file->Seek ( (-128 + 63), kXMP_SeekFromEnd ); + ReconcileUtils::UTF8ToLatin1 ( utf8.c_str(), utf8.size(), &latin1 ); + file->Write ( latin1.c_str(), MIN ( 30, (XMP_Int32)latin1.size() ) ); + } + + if ( meta->GetProperty ( kXMP_NS_XMP, "CreateDate", &utf8, 0 ) ) { + XMP_DateTime dateTime; + SXMPUtils::ConvertToDate( utf8, &dateTime ); + if ( dateTime.hasDate ) { + SXMPUtils::ConvertFromInt ( dateTime.year, "", &latin1 ); + file->Seek ( (-128 + 93), kXMP_SeekFromEnd ); + file->Write ( latin1.c_str(), MIN ( 4, (XMP_Int32)latin1.size() ) ); + } + } + + if ( meta->GetProperty ( kXMP_NS_DM, "logComment", &utf8, 0 ) ) { + file->Seek ( (-128 + 97), kXMP_SeekFromEnd ); + ReconcileUtils::UTF8ToLatin1 ( utf8.c_str(), utf8.size(), &latin1 ); + file->Write ( latin1.c_str(), MIN ( 30, (XMP_Int32)latin1.size() ) ); + } + + if ( meta->GetProperty ( kXMP_NS_DM, "genre", &utf8, 0 ) ) { + + // Write the first genre code as a UInt8. + size_t nameEnd; + std::string name; + + for ( nameEnd = 0; ((nameEnd < utf8.size()) && (utf8[nameEnd] != ';')); ++nameEnd ) {} + name.assign ( utf8.c_str(), nameEnd ); + const char * code = GenreUtils::FindGenreCode ( name ); + + if ( code != 0 ) { + XMP_Uns32 value; + bool ok = GetDecimalUns32 ( code, &value ); + if ( ok && (value <= 255) ) { + file->Seek ( (-128 + 127), kXMP_SeekFromEnd ); + XIO::WriteUns8 ( file, (XMP_Uns8)value ); + } + } + + } + + if ( meta->GetProperty ( kXMP_NS_DM, "trackNumber", &utf8, (XMP_OptionBits *) kXMP_NoOptions ) ) { + + XMP_Uns8 trackNo = 0; + try { + trackNo = (XMP_Uns8) SXMPUtils::ConvertToInt ( utf8.c_str() ); + file->Seek ( (-128 + 125), kXMP_SeekFromEnd ); + XIO::WriteUns8 ( file, 0 ); // ID3v1.1 extension + XIO::WriteUns8 ( file, trackNo ); + } catch ( ... ) { + // forgive, just don't set this one. + } + + } + +} // ID3v1Tag::write + +// ================================================================================================= + +}; // namespace ID3_Support diff --git a/core/libs/dngwriter/extra/xmp_sdk/XMPFiles/source/FormatSupport/ID3_Support.hpp b/core/libs/dngwriter/extra/xmp_sdk/XMPFiles/source/FormatSupport/ID3_Support.hpp new file mode 100644 index 0000000000..299c27c91d --- /dev/null +++ b/core/libs/dngwriter/extra/xmp_sdk/XMPFiles/source/FormatSupport/ID3_Support.hpp @@ -0,0 +1,191 @@ +#ifndef __ID3_Support_hpp__ +#define __ID3_Support_hpp__ 1 + +// ================================================================================================= +// ADOBE SYSTEMS INCORPORATED +// Copyright 2008 Adobe Systems Incorporated +// All Rights Reserved +// +// NOTICE: Adobe permits you to use, modify, and distribute this file in accordance with the terms +// of the Adobe license agreement accompanying it. +// ================================================================================================= + +#include "public/include/XMP_Environment.h" // ! This must be the first include. + +#include "public/include/XMP_Const.h" +#include "public/include/XMP_IO.hpp" + +#if XMP_WinBuild + #define stricmp _stricmp +#else + static int stricmp ( const char * left, const char * right ) // Case insensitive ASCII compare. + { + char chL = *left; // ! Allow for 0 passes in the loop (one string is empty). + char chR = *right; // ! Return -1 for stricmp ( "a", "Z" ). + + for ( ; (*left != 0) && (*right != 0); ++left, ++right ) { + chL = *left; + chR = *right; + if ( chL == chR ) continue; + if ( ('A' <= chL) && (chL <= 'Z') ) chL |= 0x20; + if ( ('A' <= chR) && (chR <= 'Z') ) chR |= 0x20; + if ( chL != chR ) break; + } + + if ( chL == chR ) return 0; + if ( chL < chR ) return -1; + return 1; + } +#endif + +// ================================================================================================= + +namespace ID3_Support { + + // ============================================================================================= + + inline XMP_Int32 synchToInt32 ( XMP_Uns32 rawDataBE ) { + XMP_Validate ( (0 == (rawDataBE & 0x80808080)), "input not synchsafe", kXMPErr_InternalFailure ); + XMP_Int32 r = (rawDataBE & 0x0000007F) + ((rawDataBE >> 1) & 0x00003F80) + + ((rawDataBE >> 2) & 0x001FC000) + ((rawDataBE >> 3) & 0x0FE00000); + return r; + } + + inline XMP_Uns32 int32ToSynch ( XMP_Int32 value ) { + XMP_Validate ( (0 <= 0x0FFFFFFF), "value too big", kXMPErr_InternalFailure ); + XMP_Uns32 r = (value & 0x0000007F) + ((value & 0x00003F80) << 1) + + ((value & 0x001FC000) << 2) + ((value & 0x0FE00000) << 3); + return r; + } + + // ============================================================================================= + + bool InitializeGlobals(); // Initialize and terminate the known genre maps. + void TerminateGlobals(); + + // ============================================================================================= + + namespace GenreUtils { + + void ConvertGenreToXMP ( const char * id3Genre, std::string * xmpGenre ); + void ConvertGenreToID3 ( const char * xmpGenre, std::string * id3Genre ); + + // Internal utilities, exposed for unit testing: + const char * FindGenreName ( const std::string & code ); + const char * FindGenreCode ( const std::string & name ); + + }; + + // ============================================================================================= + + class ID3Header { // Minimal support to read and write the ID3 header. + public: + + const static size_t o_id = 0; + const static size_t o_vMajor = 3; + const static size_t o_vMinor = 4; + const static size_t o_flags = 5; + const static size_t o_size = 6; + + const static size_t kID3_TagHeaderSize = 10; // This is the same in v2.2, v2.3, and v2.4. + char fields [kID3_TagHeaderSize]; + + ~ID3Header() {}; + + // Read the v2 header into the fields buffer and check the version. + bool read ( XMP_IO* file ); + + // Set the size and write the the v2 header from the fields buffer. + void write ( XMP_IO* file, XMP_Int64 tagSize ); + + }; + + // ============================================================================================= + + class ID3v2Frame { + public: + + // Applies to ID3 v2.2, v2.3, and v2.4. The metadata values are mostly the same, v2.2 has + // smaller frame headers and only supports UTF-16 Unicode. + + const static XMP_Uns16 o_id = 0; + const static XMP_Uns16 o_size = 4; // size after unsync, excludes frame header. + const static XMP_Uns16 o_flags = 8; + + const static int kV23_FrameHeaderSize = 10; // The header for v2.3 and v2.4. + const static int kV22_FrameHeaderSize = 6; // The header for v2.2. + char fields [kV23_FrameHeaderSize]; + + XMP_Uns32 id; + XMP_Uns16 flags; + + char* content; + XMP_Int32 contentSize; // size of variable content, right as its stored in o_size + + bool active; //default: true. flag is lowered, if another frame with replaces this one as "last meaningful frame of its kind" + bool changed; //default: false. flag is raised, if setString() is used + + ID3v2Frame(); + ID3v2Frame ( XMP_Uns32 id ); + + ID3v2Frame ( const ID3v2Frame& orig ) { + XMP_Throw ( "ID3v2Frame copy constructor not implemented", kXMPErr_InternalFailure ); + } + + ~ID3v2Frame() { this->release(); } + + void release(); + + void setFrameValue ( const std::string& rawvalue, bool needDescriptor = false, + bool utf16 = false, bool isXMPPRIVFrame = false, bool needEncodingByte = true, bool isAlreadyEncoded = false ); + + XMP_Int64 read ( XMP_IO* file, XMP_Uns8 majorVersion ); + void write ( XMP_IO* file, XMP_Uns8 majorVersion ); + + // two types of COMM frames should be preserved but otherwise ignored + // * a 6-field long header just having + // encoding(1 byte),lang(3 bytes) and 0x00 31 (no descriptor, "1" as content") + // perhaps only used to indicate client language + // * COMM frames whose description begins with engiTun, these are iTunes flags + // + // returns true: job done as expted + // false: do not use this frame, but preserve (i.e. iTunes marker COMM frame) + bool advancePastCOMMDescriptor ( XMP_Int32& pos ); + + // returns the frame content as a proper UTF8 string + // * w/o the initial encoding byte + // * dealing with BOM's + // + // @returns: by value: character string with the value + // as return value: false if frame is "not of intereset" despite a generally + // "interesting" frame ID, these are + // * iTunes-'marker' COMM frame + bool getFrameValue ( XMP_Uns8 majorVersion, XMP_Uns32 logicalID, std::string* utf8string ); + + }; + + // ============================================================================================= + + class ID3v1Tag { // Support for the fixed length v1 tag found at the end of the file. + public: + + const static XMP_Uns16 o_tag = 0; // must be "TAG" + const static XMP_Uns16 o_title = 3; + const static XMP_Uns16 o_artist = 33; + const static XMP_Uns16 o_album = 63; + const static XMP_Uns16 o_year = 93; + const static XMP_Uns16 o_comment = 97; + const static XMP_Uns16 o_zero = 125; // must be zero for trackNo to be valid + const static XMP_Uns16 o_trackNo = 126; // trackNo + const static XMP_Uns16 o_genre = 127; // last byte: index, or 255 + + const static int kV1_TagSize = 128; + + bool read ( XMP_IO* file, SXMPMeta* meta ); + void write ( XMP_IO* file, SXMPMeta* meta ); + + }; + +} + +#endif // __ID3_Support_hpp__ diff --git a/core/libs/dngwriter/extra/xmp_sdk/XMPFiles/source/FormatSupport/IFF/Chunk.cpp b/core/libs/dngwriter/extra/xmp_sdk/XMPFiles/source/FormatSupport/IFF/Chunk.cpp new file mode 100644 index 0000000000..9e1407fbd4 --- /dev/null +++ b/core/libs/dngwriter/extra/xmp_sdk/XMPFiles/source/FormatSupport/IFF/Chunk.cpp @@ -0,0 +1,1228 @@ +// ================================================================================================= +// ADOBE SYSTEMS INCORPORATED +// Copyright 2010 Adobe Systems Incorporated +// All Rights Reserved +// +// NOTICE: Adobe permits you to use, modify, and distribute this file in accordance with the terms +// of the Adobe license agreement accompanying it. +// ================================================================================================= + +#include "public/include/XMP_Environment.h" // ! XMP_Environment.h must be the first included header. +#include "public/include/XMP_Const.h" + +#include "XMPFiles/source/FormatSupport/IFF/Chunk.h" +#include "source/XMP_LibUtils.hpp" +#include "source/XIO.hpp" + +#include +#include + +using namespace IFF_RIFF; + +//----------------------------------------------------------------------------- +// +// Chunk::createChunk(...) +// +// Purpose: [static] Static factory to create an unknown chunk +// +//----------------------------------------------------------------------------- + +Chunk* Chunk::createChunk( const IEndian& endian ) +{ + return new Chunk( endian ); +} + + +//----------------------------------------------------------------------------- +// +// Chunk::createUnknownChunk(...) +// +// Purpose: [static] Static factory to create an unknown chunk with initial id, +// sizes and offsets. +// +//----------------------------------------------------------------------------- + +Chunk* Chunk::createUnknownChunk( + const IEndian& endian, + const XMP_Uns32 id, + const XMP_Uns32 type, + const XMP_Uns64 size, + const XMP_Uns64 originalOffset, + const XMP_Uns64 offset +) +{ + Chunk *chunk = new Chunk( endian ); + chunk->setID( id ); + chunk->mOriginalOffset = originalOffset; + chunk->mOffset = offset; + + if (type != 0) + { + chunk->setType(type); + } + + // sizes have to be set after type, otherwise the setType sets the size to 4. + chunk->mSize = chunk->mOriginalSize = size; + chunk->mChunkMode = CHUNK_UNKNOWN; + chunk->mDirty = false; + return chunk; +} + +//----------------------------------------------------------------------------- +// +// Chunk::createHeaderChunk(...) +// +// Purpose: [static] Static factory to create a leaf chunk with no data area or +// only the type in the data area +// +//----------------------------------------------------------------------------- + +Chunk* Chunk::createHeaderChunk( const IEndian& endian, const XMP_Uns32 id, const XMP_Uns32 type /*= kType_NONE*/) +{ + Chunk *chunk = new Chunk( endian ); + chunk->setID( id ); + + XMP_Uns64 size = 0; + + if( type != kType_NONE ) + { + chunk->setType( type ); + size += Chunk::TYPE_SIZE; + } + + chunk->mSize = size; + chunk->mOriginalSize = size; + chunk->mChunkMode = CHUNK_LEAF; + chunk->mDirty = false; + + return chunk; +} + + +//----------------------------------------------------------------------------- +// +// Chunk::Chunk(...) +// +// Purpose: ctor/dtor +// +//----------------------------------------------------------------------------- + +Chunk::Chunk( const IEndian& endian ) +: mEndian( endian ) +{ + // initialize private instance variables + mChunkId.id = kChunk_NONE; + mChunkId.type = kType_NONE; + mSize = 0; + mOriginalSize = 0; + mBufferSize = 0; + mData = NULL; + mParent = NULL; + mOriginalOffset = 0; + mOffset = 0; + mDirty = false; + mChunkMode = CHUNK_UNKNOWN; +} + + +Chunk::~Chunk() +{ + for( ChunkIterator iter = mChildren.begin(); iter != mChildren.end(); iter++ ) + { + delete *iter; + } + + // Free allocated data buffer + if( mData != NULL ) + { + delete [] mData; + } +} + + +/************************ IChunk interface implementation ************************/ + +//----------------------------------------------------------------------------- +// +// Chunk::getData(...) +// +// Purpose: access data area of Chunk +// +//----------------------------------------------------------------------------- + +XMP_Uns64 Chunk::getData( const XMP_Uns8** data ) const +{ + if( data == NULL ) + { + XMP_Throw ( "Invalid data pointer.", kXMPErr_BadParam ); + } + + *data = mData; + + return mBufferSize; +} + + +//----------------------------------------------------------------------------- +// +// Chunk::setData(...) +// +// Purpose: Set new data for the chunk. +// Will delete an existing internal buffer and recreate a new one +// and copy the given data into that new buffer. +// +//----------------------------------------------------------------------------- + +void Chunk::setData( const XMP_Uns8* const data, XMP_Uns64 size, XMP_Bool writeType /*=false*/ ) +{ + // chunk nodes cannot contain data + if ( mChunkMode == CHUNK_NODE ) + { + XMP_Throw ( "A chunk node cannot contain data.", kXMPErr_BadParam ); + } + else if ( data == NULL || size == 0 ) + { + XMP_Throw ( "Invalid data pointer.", kXMPErr_BadParam ); + } + + if( mData != NULL ) + { + delete [] mData; + } + + if( writeType ) + { + mBufferSize = size + TYPE_SIZE; + mData = new XMP_Uns8[static_cast(mBufferSize)]; // Throws bad_alloc exception in case of being out of memory + setType( mChunkId.type ); + memcpy( &mData[TYPE_SIZE], data, static_cast(size) ); + } + else + { + mBufferSize = size; + mData = new XMP_Uns8[static_cast(mBufferSize)]; // Throws bad_alloc exception in case of being out of memory + // ! We assume that size IS the actual size of that input buffer, otherwise behavior is undefined + memcpy( mData, data, static_cast(size) ); + + // set the type variable + if( mBufferSize >= TYPE_SIZE ) + { + //Chunk type is always BE + //The first four bytes could be the type + mChunkId.type = BigEndian::getInstance().getUns32( mData ); + } + } + + mChunkMode = CHUNK_LEAF; + setChanged(); + adjustSize(); +} + +//----------------------------------------------------------------------------- +// +// Chunk::getUns32(...) +// +// Purpose: The following methods are getter/setter for certain data types. +// They always take care of little-endian/big-endian issues. +// The offset starts at the data area of the Chunk. +// +//----------------------------------------------------------------------------- + +XMP_Uns32 Chunk::getUns32( XMP_Uns64 offset ) const +{ + if( offset + sizeof(XMP_Uns32) > mBufferSize ) + { + XMP_Throw ( "Data access out of bounds", kXMPErr_BadIndex ); + } + return mEndian.getUns32( &mData[offset] ); +} + + +void Chunk::setUns32( XMP_Uns32 value, XMP_Uns64 offset ) +{ + // chunk nodes cannot contain data + if ( mChunkMode == CHUNK_NODE ) + { + XMP_Throw ( "A chunk node cannot contain data.", kXMPErr_BadParam ); + } + + // If the new value exceeds the size of the buffer, recreate the buffer + adjustInternalBuffer( offset + sizeof(XMP_Uns32) ); + // Write the new value + mEndian.putUns32( value, &mData[offset] ); + // Chunk becomes leaf chunk when adding data + mChunkMode = CHUNK_LEAF; + // Flag the chunk as dirty + setChanged(); + // If the buffer is bigger than the Chunk size, adjust the Chunk size + adjustSize(); + +} + + +XMP_Uns64 Chunk::getUns64( XMP_Uns64 offset ) const +{ + if( offset + sizeof(XMP_Uns64) > mBufferSize ) + { + XMP_Throw ( "Data access out of bounds", kXMPErr_BadIndex ); + } + return mEndian.getUns64( &mData[offset] ); +} + + +void Chunk::setUns64( XMP_Uns64 value, XMP_Uns64 offset ) +{ + // chunk nodes cannot contain data + if ( mChunkMode == CHUNK_NODE ) + { + XMP_Throw ( "A chunk node cannot contain data.", kXMPErr_BadParam ); + } + + // If the new value exceeds the size of the buffer, recreate the buffer + adjustInternalBuffer( offset + sizeof(XMP_Uns64) ); + // Write the new value + mEndian.putUns64( value, &mData[offset] ); + // Chunk becomes leaf chunk when adding data + mChunkMode = CHUNK_LEAF; + // Flag the chunk as dirty + setChanged(); + // If the buffer is bigger than the Chunk size, adjust the Chunk size + adjustSize(); +} + + +XMP_Int32 Chunk::getInt32( XMP_Uns64 offset ) const +{ + if( offset + sizeof(XMP_Int32) > mBufferSize ) + { + XMP_Throw ( "Data access out of bounds", kXMPErr_BadIndex ); + } + return mEndian.getUns32( &mData[offset] ); +} + + +void Chunk::setInt32( XMP_Int32 value, XMP_Uns64 offset ) +{ + // chunk nodes cannot contain data + if ( mChunkMode == CHUNK_NODE ) + { + XMP_Throw ( "A chunk node cannot contain data.", kXMPErr_BadParam ); + } + + // If the new value exceeds the size of the buffer, recreate the buffer + adjustInternalBuffer( offset + sizeof(XMP_Int32) ); + // Write the new value + mEndian.putUns32( value, &mData[offset] ); + // Chunk becomes leaf chunk when adding data + mChunkMode = CHUNK_LEAF; + // Flag the chunk as dirty + setChanged(); + // If the buffer is bigger than the Chunk size, adjust the Chunk size + adjustSize(); +} + + +XMP_Int64 Chunk::getInt64( XMP_Uns64 offset ) const +{ + if( offset + sizeof(XMP_Int64) > mBufferSize ) + { + XMP_Throw ( "Data access out of bounds", kXMPErr_BadIndex ); + } + return mEndian.getUns64( &mData[offset] ); +} + + +void Chunk::setInt64( XMP_Int64 value, XMP_Uns64 offset ) +{ + // chunk nodes cannot contain data + if ( mChunkMode == CHUNK_NODE ) + { + XMP_Throw ( "A chunk node cannot contain data.", kXMPErr_BadParam ); + } + + // If the new value exceeds the size of the buffer, recreate the buffer + adjustInternalBuffer( offset + sizeof(XMP_Int64) ); + // Write the new value + mEndian.putUns64( value, &mData[offset] ); + // Chunk becomes leaf chunk when adding data + mChunkMode = CHUNK_LEAF; + // Flag the chunk as dirty + setChanged(); + // If the buffer is bigger than the Chunk size, adjust the Chunk size + adjustSize(); +} + + +std::string Chunk::getString( XMP_Uns64 size /*=0*/, XMP_Uns64 offset /*=0*/ ) const +{ + if( offset + size > mBufferSize ) + { + XMP_Throw ( "Data access out of bounds", kXMPErr_BadIndex ); + } + + XMP_Uns64 requestedSize = size != 0 ? size : mBufferSize - offset; + + std::string str((char *)&mData[offset],static_cast(requestedSize)); + + return str; +} + + +void Chunk::setString( std::string value, XMP_Uns64 offset ) +{ + if ( mChunkMode == CHUNK_NODE ) + { + XMP_Throw ( "A chunk node cannot contain data.", kXMPErr_BadParam ); + } + + // If the new value exceeds the size of the buffer, recreate the buffer + adjustInternalBuffer( offset + value.length() ); + // Write the new value + memcpy( &mData[offset], value.data(), value.length() ); + // Chunk becomes leaf chunk when adding data + mChunkMode = CHUNK_LEAF; + // Flag the chunk as dirty + setChanged(); + // If the buffer is bigger than the Chunk size, adjust the Chunk size + adjustSize(); +} + + +/************************ Chunk public methods ************************/ + +//----------------------------------------------------------------------------- +// +// Chunk::setID(...) +// +// Purpose: Sets the chunk id. +// +//----------------------------------------------------------------------------- + +void Chunk::setID( XMP_Uns32 id ) +{ + mChunkId.id = id; + setChanged(); +} + + +//----------------------------------------------------------------------------- +// +// Chunk::setType(...) +// +// Purpose: Sets the chunk type +// +//----------------------------------------------------------------------------- + +void Chunk::setType( XMP_Uns32 type ) +{ + mChunkId.type = type; + + // reserve space for type + // setChanged() and adjustSize() implicitly called + // make sure that no exception is thrown + ChunkMode existing = mChunkMode; + mChunkMode = CHUNK_UNKNOWN; + setUns32(0, 0); + mChunkMode = existing; + + BigEndian::getInstance().putUns32( type, mData ); +} + +//----------------------------------------------------------------------------- +// +// Chunk::getPadSize(...) +// +// Purpose: Returns the original size of the Chunk including a pad byte if +// the size isn't a even number +// +//----------------------------------------------------------------------------- + +XMP_Uns64 Chunk::getOriginalPadSize( bool includeHeader /*= false*/ ) const +{ + XMP_Uns64 ret = this->getOriginalSize( includeHeader ); + + if( ret & 1 ) + { + ret++; + } + + return ret; +} + +//----------------------------------------------------------------------------- +// +// Chunk::getPadSize(...) +// +// Purpose: Returns the current size of the Chunk including a pad byte if the +// size isn't a even number +// +//----------------------------------------------------------------------------- + +XMP_Uns64 Chunk::getPadSize( bool includeHeader /*= false*/ ) const +{ + XMP_Uns64 ret = this->getSize( includeHeader ); + + if( ret & 1 ) + { + ret++; + } + + return ret; +} + +//----------------------------------------------------------------------------- +// +// Chunk::calculateSize(...) +// +// Purpose: Calculate the size of this chunks based on its children sizes. +// If this chunk has no children then no new size will be calculated. +// +//----------------------------------------------------------------------------- + +XMP_Uns64 Chunk::calculateSize( bool setOriginal /*= false*/ ) +{ + XMP_Uns64 size = 0LL; + + // + // calculate only foe nodes + // + if( this->getChunkMode() == CHUNK_NODE ) + { + // + // calculate size of all children + // + for( ChunkIterator iter = mChildren.begin(); iter != mChildren.end(); iter++ ) + { + XMP_Uns64 childSize = (*iter)->getSize(true); + + size += childSize; + + // + // take account of pad byte + // + if( childSize & 1 ) + { + size++; + } + } + + // + // assume that we have a type + // + size += Chunk::TYPE_SIZE; + + // + // set dirty flag only if something has changed + // + if( size != mSize || ( setOriginal && size != mOriginalSize ) ) + { + this->setChanged(); + } + + // + // set new size(s) + // + if( setOriginal ) + { + mOriginalSize = size; + } + + mSize = size; + } + else + size = mSize; + + return size; +} + + +//----------------------------------------------------------------------------- +// +// Chunk::calculateWriteSize(...) +// +// Purpose: Calculate the size of the chunks that are dirty including the size +// of its children +// +//----------------------------------------------------------------------------- + +XMP_Int64 Chunk::calculateWriteSize( ) const +{ + XMP_Int64 size=0; + if (hasChanged()) + { + size+=(sizeof(XMP_Uns32)*2); + if (mChunkMode == CHUNK_LEAF) + { + if ( mSize % 2 == 1 ) + { + // for odd file sizes, a pad byte is written + size+=(mSize+1); + } + else + { + size+=mSize; + } + } + else // mChunkMode == CHUNK_NODE + { + // writes type if defined + if (mChunkId.type != kType_NONE) + { + size+=sizeof(XMP_Uns32); + } + + // calls calculateWriteSize recursively on it's children + for( ConstChunkIterator iter = mChildren.begin(); iter != mChildren.end(); iter++ ) + { + size+=(*iter)->calculateWriteSize( ); + } + } + } + + return size; +} + +//----------------------------------------------------------------------------- +// +// Chunk::setOffset(...) +// +// Purpose: Adjust the offset that this chunk has within the file +// +//----------------------------------------------------------------------------- + +void Chunk::setOffset (XMP_Uns64 newOffset) // changes during rearranging +{ + XMP_Uns64 oldOffset = mOffset; + mOffset = newOffset; + + if( mOffset != oldOffset ) + { + setChanged(); + } +} + + +//----------------------------------------------------------------------------- +// +// Chunk::resetChanges(...) +// +// Purpose: Resets the dirty status for this chunk and its children to false +// +//----------------------------------------------------------------------------- + +void Chunk::resetChanges() +{ + mDirty = false; + + for( ChunkIterator iter = mChildren.begin(); iter != mChildren.end(); iter++ ) + { + (*iter)->resetChanges(); + } +} //resetChanges + + +//----------------------------------------------------------------------------- +// +// Chunk::setAsNew(...) +// +// Purpose: Sets all necessary member variables to flag this chunk as a new one +// being inserted into the tree +// +//----------------------------------------------------------------------------- + +void Chunk::setAsNew() +{ + mOriginalSize = mSize; + mOriginalOffset = mOffset; +} + +//----------------------------------------------------------------------------- +// +// Chunk::toString(...) +// +// Purpose: Creates a string representation of the chunk (debug method) +// +//----------------------------------------------------------------------------- + +std::string Chunk::toString( std::string tabs, XMP_Bool showOriginal ) +{ + const BigEndian &BE = BigEndian::getInstance(); + char buffer[256]; + XMP_Uns32 id = BE.getUns32(&this->mChunkId.id); + XMP_Uns32 type = BE.getUns32(&this->mChunkId.type); + + XMP_Uns64 size, offset; + + if ( showOriginal ) + { + size = mEndian.getUns64(&this->mOriginalSize); + offset = mEndian.getUns64(&this->mOriginalOffset); + } + else + { + size = mEndian.getUns64(&this->mSize); + offset = mEndian.getUns64(&this->mOffset); + } + + snprintf( buffer, 255, "%.4s -- " + "size: 0x%.8llX, " + "type: %.4s, " + "offset: 0x%.8llX", + (char*)(&id), + size, + (char*)(&type), + offset ); + std::string str(buffer); + + // Dump children + if ( mChildren.size() > 0) + { + tabs.append("\t"); + } + + for( ChunkIterator iter = mChildren.begin(); iter != mChildren.end(); iter++ ) + { + str += "\n"; + str += tabs; + str += (*iter)->toString(tabs , showOriginal); + } + + return str; +} + + +/************************ file access ************************/ + +//----------------------------------------------------------------------------- +// +// Chunk::readChunk(...) +// +// Purpose: Read id, size and offset and create a chunk with mode CHUNK_UNKNOWN. +// The file is expected to be open and is not closed! +// +//----------------------------------------------------------------------------- + +void Chunk::readChunk( XMP_IO* file ) +{ + if( file == NULL ) + { + XMP_Throw( "Chunk::readChunk: Must pass a valid file pointer", kXMPErr_BadParam ); + } + + if( mChunkId.id != kChunk_NONE ) + { + XMP_Throw ( "readChunk must not be called more than once", kXMPErr_InternalFailure ); + } + // error handling is done in the controller + // determine offset in the file + mOriginalOffset = mOffset = file->Offset(); + //ID is always BE + mChunkId.id = XIO::ReadUns32_BE( file ); + // Size can be both + if (typeid(mEndian) == typeid(LittleEndian)) + { + mOriginalSize = mSize = XIO::ReadUns32_LE( file ); + + } + else + { + mOriginalSize = mSize = XIO::ReadUns32_BE( file ); + } + + // For Type do not assume any format as it could be data, read it as bytes + if (mSize >= TYPE_SIZE) + { + mData = new XMP_Uns8[TYPE_SIZE]; + + for ( XMP_Uns32 i = 0; i < TYPE_SIZE ; i++ ) + { + mData[i] = XIO::ReadUns8( file ); + } + //Chunk type is always BE + //The first four bytes could be the type + mChunkId.type = BigEndian::getInstance().getUns32( mData ); + } + + mDirty = false; +}//readChunk + + +//----------------------------------------------------------------------------- +// +// Chunk::cacheChunkData(...) +// +// Purpose: Stores the data in the class (only called if required). +// The file is expected to be open and is not closed! +// +//----------------------------------------------------------------------------- + +void Chunk::cacheChunkData( XMP_IO* file ) +{ + XMP_Enforce( file != NULL ); + + if( mChunkMode != CHUNK_UNKNOWN ) + { + XMP_Throw ( "chunk already has either data or children.", kXMPErr_BadParam ); + } + + // error handling is done in the controller + + // continue only when the chunk contains data + if (mSize != 0) + { + mBufferSize = mSize; + XMP_Uns8* tmp = new XMP_Uns8[XMP_Uns32(mSize)]; + + // Do we have a type? + if (mSize >= TYPE_SIZE) + { + // add type in front of new buffer + for ( XMP_Uns32 i = 0; i < TYPE_SIZE ; i++ ) + { + tmp[i] = mData[i]; + } + // Read rest of data from file + if( mSize != TYPE_SIZE ) + { + // Chunks that are cached are very probably not bigger than 2GB, so cast is safe + file->ReadAll ( &tmp[TYPE_SIZE], static_cast(mSize - TYPE_SIZE) ); + } + } + else + { + // Chunks that are cached are very probably not bigger than 2GB, so cast is safe + file->ReadAll ( tmp, static_cast(mSize) ); + } + // deletes the existing array + delete [] mData; + //assign the new buffer + mData = tmp; + } + + // Remember that this method has been called + mDirty = false; + mChunkMode = CHUNK_LEAF; +} + + +//----------------------------------------------------------------------------- +// +// Chunk::writeChunk(...) +// +// Purpose: Write or updates chunk (new data, new size, new position). +// The file is expected to be open and is not closed! +// +//----------------------------------------------------------------------------- + +void Chunk::writeChunk( XMP_IO* file ) +{ + if( file == NULL ) + { + XMP_Throw( "Chunk::writeChunk: Must pass a valid file pointer", kXMPErr_BadParam ); + } + + if (mChunkMode == CHUNK_UNKNOWN) + { + if (hasChanged()) + { + XMP_Throw ( "A chunk with mode unknown must not be changed & written.", kXMPErr_BadParam ); + } + + // do nothing + } + else if (hasChanged()) + { + // positions the file pointer + file->Seek ( mOffset, kXMP_SeekFromStart ); + + + // ============ This part is identical for CHUNK_LEAF and CHUNK_TYPE ============ + + // writes ID (starting with offset) + XIO::WriteInt32_BE( file, mChunkId.id ); + + // writes size, which is always 32bit + XMP_Uns32 outSize = ( mSize >= 0x00000000FFFFFFFF ? 0xFFFFFFFF : static_cast( mSize & 0x00000000FFFFFFFF ) ); + + if (typeid(mEndian) == typeid(LittleEndian)) + { + XIO::WriteUns32_LE( file, static_cast(mSize) ); + } + else + { + XIO::WriteUns32_BE( file, static_cast(mSize) ); + } + + + // ============ This part is different for CHUNK_LEAF and CHUNK_TYPE ============ + if (mChunkMode == CHUNK_LEAF) + { + // writes buffer (including the optional type at the beginning) + // Cached chunks will very probably not be bigger than 2GB, so cast is safe + file->Write ( mData, static_cast(mSize) ); + if ( mSize % 2 == 1 ) + { + // for odd file sizes, a pad byte is written + XIO::WriteUns8 ( file, 0 ); + } + } + else // mChunkMode == CHUNK_NODE + { + // writes type if defined + if (mChunkId.type != kType_NONE) + { + XIO::WriteInt32_BE( file, mChunkId.type ); + } + + // calls writeChunk on it's children + for( ChunkIterator iter = mChildren.begin(); iter != mChildren.end(); iter++ ) + { + (*iter)->writeChunk( file ); + } + } + } + + // set back dirty state + mDirty = false; +} + + +/************************ children access ************************/ + +//----------------------------------------------------------------------------- +// +// Chunk::numChildren(...) +// +// Purpose: Returns the number children chunks +// +//----------------------------------------------------------------------------- + +XMP_Uns32 Chunk::numChildren() const +{ + return static_cast( mChildren.size() ); +} + + +//----------------------------------------------------------------------------- +// +// Chunk::getChildAt(...) +// +// Purpose: Returns a child node +// +//----------------------------------------------------------------------------- + +Chunk* Chunk::getChildAt( XMP_Uns32 pos ) const +{ + try + { + return mChildren.at(pos); + } + catch( ... ) + { + XMP_Throw ( "Non-existing child requested.", kXMPErr_BadIndex ); + } +} + + +//----------------------------------------------------------------------------- +// +// Chunk::appendChild(...) +// +// Purpose: Appends a child node at the end of the children list +// +//----------------------------------------------------------------------------- + +void Chunk::appendChild( Chunk* child, XMP_Bool adjustSizes ) +{ + if (mChunkMode == CHUNK_LEAF) + { + XMP_Throw ( "A chunk leaf cannot contain children.", kXMPErr_BadParam ); + } + + try + { + mChildren.push_back( child ); + // make this the parent of the new node + child->mParent = this; + mChunkMode = CHUNK_NODE; + + // set offset of new child + XMP_Uns64 childOffset = 0; + + if( this->numChildren() == 1 ) + { + // first added child + if( this->getID() != kChunk_NONE ) + { + childOffset = this->getOffset() + Chunk::HEADER_SIZE + ( this->getType() == kType_NONE ? 0 : Chunk::TYPE_SIZE ); + } + } + else + { + Chunk* predecessor = this->getChildAt( this->numChildren() - 2 ); + childOffset = predecessor->getOffset() + predecessor->getPadSize( true ); + } + + child->setOffset( childOffset ); + + setChanged(); + + if ( adjustSizes ) + { + // to fix the sizes of this node and parents + adjustSize( child->getSize(true) ); + } + } + catch (...) + { + XMP_Throw ( "Vector error in appendChild", kXMPErr_InternalFailure ); + } +} + + +//----------------------------------------------------------------------------- +// +// Chunk::insertChildAt(...) +// +// Purpose: Inserts a child node at a certain position +// +//----------------------------------------------------------------------------- + +void Chunk::insertChildAt( XMP_Uns32 pos, Chunk* child ) +{ + if (mChunkMode == CHUNK_LEAF) + { + XMP_Throw ( "A chunk leaf cannot contain children.", kXMPErr_BadParam ); + } + + try + { + if (pos <= mChildren.size()) + { + mChildren.insert(mChildren.begin() + pos, child); + // make this the parent of the new node + child->mParent = this; + mChunkMode = CHUNK_NODE; + + // set offset of new child + XMP_Uns64 childOffset = 0; + + if( pos == 0 ) + { + if( this->getID() != kChunk_NONE ) + { + childOffset = this->getOffset() + Chunk::HEADER_SIZE + ( this->getType() == kType_NONE ? 0 : Chunk::TYPE_SIZE ); + } + } + else + { + Chunk* predecessor = this->getChildAt( pos-1 ); + childOffset = predecessor->getOffset() + predecessor->getPadSize( true ); + } + + child->setOffset( childOffset ); + + setChanged(); + + // to fix the sizes of this node and parents + adjustSize( child->getSize(true) ); + } + else + { + XMP_Throw ( "Index not valid.", kXMPErr_BadIndex ); + } + } + catch (...) + { + XMP_Throw ( "Index not valid.", kXMPErr_BadIndex ); + } +} + + +//----------------------------------------------------------------------------- +// +// Chunk::removeChildAt(...) +// +// Purpose: Removes a child node at a given position +// +//----------------------------------------------------------------------------- + +Chunk* Chunk::removeChildAt( XMP_Uns32 pos ) +{ + Chunk* toDelete = NULL; + + try + { + toDelete = mChildren.at(pos); + // to fix the size of this node + XMP_Int64 sizeDeleted = static_cast(toDelete->getSize(true)); + mChildren.erase(mChildren.begin() + pos); + + setChanged(); + + // to fix the sizes of this node and parents + adjustSize(-sizeDeleted); + } + catch (...) + { + XMP_Throw ( "Index not valid.", kXMPErr_BadIndex ); + } + + return toDelete; +} + +//----------------------------------------------------------------------------- +// +// replaceChildAt(...) +// +// Purpose: Remove child at the passed position and insert the new chunk +// +//----------------------------------------------------------------------------- + +Chunk* Chunk::replaceChildAt( XMP_Uns32 pos, Chunk* child ) +{ + Chunk* toDelete = NULL; + + try + { + // + // removed old chunk + // + toDelete = mChildren.at(pos); + mChildren.erase(mChildren.begin() + pos); + + // + // insert new chunk + // + mChildren.insert(mChildren.begin() + pos, child); + // make this the parent of the new node + child->mParent = this; + mChunkMode = CHUNK_NODE; + + // set offset + child->setOffset( toDelete->getOffset() ); + + setChanged(); + + // to fix the sizes of this node and parents + adjustSize( child->getPadSize() - toDelete->getPadSize() ); + } + catch (...) + { + XMP_Throw ( "Index not valid.", kXMPErr_BadIndex ); + } + + return toDelete; +} + +//----------------------------------------------------------------------------- +// +// Chunk::firstChild(...) +// +// Purpose: iterators +// +//----------------------------------------------------------------------------- + +Chunk::ConstChunkIterator Chunk::firstChild() const +{ + return mChildren.begin(); +} + + +Chunk::ConstChunkIterator Chunk::lastChild() const +{ + return mChildren.end(); +} + + +/******************* Private Methods ***************************/ + +//----------------------------------------------------------------------------- +// +// Chunk::setChanged(...) +// +// Purpose: Sets this node and all of its parents up to the tree root dirty +// +//----------------------------------------------------------------------------- + +void Chunk::setChanged() +{ + mDirty = true; + + if (mParent != NULL) + { + mParent->setChanged(); + } +} + + +//----------------------------------------------------------------------------- +// +// Chunk::adjustInternalBuffer(...) +// +// Purpose: Resizes the internal byte buffer to the given size if the new size +// is bigger than the current one. +// If the new size is smaller, the buffer is not adjusted +// +//----------------------------------------------------------------------------- + +void Chunk::adjustInternalBuffer( XMP_Uns64 newSize ) +{ + // only adjust if the new size is bigger than the old one. + // If it is smaller, leave the buffer alone + if( newSize > mBufferSize ) + { + XMP_Uns8 *tmp = new XMP_Uns8[static_cast(newSize)]; // Might throw bad_alloc exception + + // Do we have an old buffer? + if( mData != NULL ) + { + // Copy it to the new one and delete the old one + memcpy( tmp, mData, static_cast(mBufferSize) ); + + delete [] mData; + } + mData = tmp; + mBufferSize = newSize; + } +}//adjustInternalBuffer + + +//----------------------------------------------------------------------------- +// +// Chunk::adjustSize(...) +// +// Purpose: Adjusts the chunk size and the parents chunk sizes +// +//----------------------------------------------------------------------------- + +void Chunk::adjustSize( XMP_Int64 sizeChange ) +{ + // Calculate leaf sizeChange + if (mChunkMode == CHUNK_LEAF) + { + // Note: The leave nodes size is equal to the buffer size can have odd and even sizes. + XMP_Uns64 sizeInclPad = mSize + (mSize % 2); + sizeChange = mBufferSize - sizeInclPad; + mSize = mBufferSize; + + // if the difference is odd, the corrected even size has be incremented by 1 + sizeChange += abs(sizeChange % 2); + } + else // mChunkMode == CHUNK_NODE/CHUNK_UNKNOWN + { + // if the difference is odd, the corrected even size has be incremented by 1 + // (or decremented by 1 when < 0). + sizeChange += sizeChange % 2; + + // the chunk node gets the corrected (odd->even) size + mSize += sizeChange; + } + + + if (mParent != NULL) + { + // adjusts the parents size with the corrected (odd->even) size difference of this node + mParent->adjustSize(sizeChange); + } +}//adjustSize diff --git a/core/libs/dngwriter/extra/xmp_sdk/XMPFiles/source/FormatSupport/IFF/Chunk.h b/core/libs/dngwriter/extra/xmp_sdk/XMPFiles/source/FormatSupport/IFF/Chunk.h new file mode 100644 index 0000000000..ef2ba474af --- /dev/null +++ b/core/libs/dngwriter/extra/xmp_sdk/XMPFiles/source/FormatSupport/IFF/Chunk.h @@ -0,0 +1,468 @@ +// ================================================================================================= +// ADOBE SYSTEMS INCORPORATED +// Copyright 2010 Adobe Systems Incorporated +// All Rights Reserved +// +// NOTICE: Adobe permits you to use, modify, and distribute this file in accordance with the terms +// of the Adobe license agreement accompanying it. +// ================================================================================================= + +#ifndef _Chunk_h_ +#define _Chunk_h_ + +#include "public/include/XMP_Environment.h" // ! XMP_Environment.h must be the first included header. + +#include "public/include/XMP_Const.h" +#include "public/include/XMP_IO.hpp" + +#include "XMPFiles/source/XMPFiles_Impl.hpp" +#include "source/XMPFiles_IO.hpp" + +#include "source/Endian.h" +#include "XMPFiles/source/FormatSupport/IFF/ChunkPath.h" +#include "XMPFiles/source/FormatSupport/IFF/IChunkData.h" +#include "XMPFiles/source/FormatSupport/IFF/IChunkContainer.h" + +namespace IFF_RIFF +{ + /** + * CHUNK_UNKNOWN = Either new chunk or a chunk that was read, but not cached + * (it is not decided yet whether it becones a node or leaf or is not cached at all) + * CHUNK_NODE = Node chunk that contains children, but no own data (except the optional type) + * CHUNK_LEAF = Leaf chunk that contains data but no children + */ + enum ChunkMode { CHUNK_UNKNOWN = 0, CHUNK_NODE = 1, CHUNK_LEAF = 2 }; + +/** + * Each Chunk of the IFF/RIFF based file formats (e.g. WAVE, AVI, AIFF) are represented by + * instances of the class Chunk. + * A chunk can be a node chunk containing children, a leaf chunk containing data or an "unknown" chunk, + * which means that its content has not cached/loaded yet (or will never be during the file handling); + * see ChunkMode for more details. + * + * Note: A Chunk can either have a chunk OR a list of child chunks, but never both. + * + * This class provides access to the children or the data of a chunk, depending of the type. + * It keeps track of its size. When the size is changed (by adding or removing data/ or children), + * the size is also fixed for the parent hierarchy. + * + * The dirty flag (hasChanged()) that is set for each change of a chunk is also promoted to the parents. + * The chunk stores its original and new offset within the host file, but its *not* automatically correctin the offset; + * this is done by the IChunkBehavior class. + * The Chunk class provides an interface to iterate through the tree structure of Chunks. + * There are methods to insert, remove and move Chunk's in its children tree structure. + * + * The chunk can read itself from a host file (readChunk()), but it does not automatically read its children, + * because they are not necessarily used by the file handler. + * The method writeChunk() recurses through the complete chunk tree and writes the *changed* chunks back to the host file. + * It is important that the offsets have been fixed before. + * + * Table about endianess in the different RIFF file formats: + * + * | ID size type data + * ----------------------------------------- + * AVI | BE LE BE LE + * WAV | BE LE BE LE + * AIFF | BE BE BE BE + */ +class Chunk : public IChunkData, + public IChunkContainer +{ + public: + /** Factory to create an empty chunk */ + static Chunk* createChunk( const IEndian& endian ); + + /** Factory to create an empty chunk */ + static Chunk* createUnknownChunk( + const IEndian& endian, + const XMP_Uns32 id, + const XMP_Uns32 type, + const XMP_Uns64 size, + const XMP_Uns64 originalOffset = 0, + const XMP_Uns64 offset = 0 + ); + + /** Static factory to create a leaf chunk with no data area or only the type in the data area */ + static Chunk* createHeaderChunk( const IEndian& endian, const XMP_Uns32 id, const XMP_Uns32 type = kType_NONE ); + + /** + * dtor + */ + ~Chunk(); + + + //===================== IChunkData interface implementation ================ + + /** + * Get the chunk ID + * + * @return Return the ID, 0 if the chunk does not have an ID. + */ + inline XMP_Uns32 getID() const { return mChunkId.id; } + + /** + * Get the chunk type (if available) + * (the first four data bytes of the chunk could be a chunk type) + * + * @return Return the type, kType_NONE if the chunk does not contain data. + */ + inline XMP_Uns32 getType() const { return mChunkId.type; } + + /** + * Get the chunk identifier [id and type] + * + * @return Return the identifier + */ + inline const ChunkIdentifier& getIdentifier() const { return mChunkId; } + + /** + * Access the data of the chunk. + * + * @param data OUT pointer to the byte array + * @return size of the data block, 0 if no data is available + */ + XMP_Uns64 getData( const XMP_Uns8** data ) const; + + /** + * Set new data for the chunk. + * Will delete an existing internal buffer and recreate a new one + * and copy the given data into that new buffer. + * + * @param data pointer to the data to put into the chunk + * @param size Size of the data block + * @param writeType if true, the type of the chunk (getType()) is written in front of the data block. + */ + void setData( const XMP_Uns8* const data, XMP_Uns64 size, XMP_Bool writeType = false ); + + /** + * Returns the current size of the Chunk. + * + * @param includeHeader if set, the returned size will be the whole chunk size including the header + * @return Returns either the size of the data block of the chunk or size of the whole chunk, including the eight byte header. + */ + XMP_Uns64 getSize( bool includeHeader = false ) const { return includeHeader ? mSize + HEADER_SIZE : mSize; } + + /** + * Returns the current size of the Chunk including a pad byte if the size isn't a even number + * + * @param includeHeader if set, the returned size will be the whole chunk size including the header + * @return Returns either the size of the data block of the chunk or size of the whole chunk, including the eight byte header. + */ + XMP_Uns64 getPadSize( bool includeHeader = false ) const; + /** + * @return Returns the mode of the chunk (see ChunkMode definition). + */ + ChunkMode getChunkMode() const { return mChunkMode; } + + /* The following methods are getter/setter for certain data types. + * They always take care of little-endian/big-endian issues. + * The offset starts at the data area of the Chunk. */ + + XMP_Uns32 getUns32( XMP_Uns64 offset=0 ) const; + void setUns32( XMP_Uns32 value, XMP_Uns64 offset=0 ); + + XMP_Uns64 getUns64( XMP_Uns64 offset=0 ) const; + void setUns64( XMP_Uns64 value, XMP_Uns64 offset=0 ); + + XMP_Int32 getInt32( XMP_Uns64 offset=0 ) const; + void setInt32( XMP_Int32 value, XMP_Uns64 offset=0 ); + + XMP_Int64 getInt64( XMP_Uns64 offset=0 ) const; + void setInt64( XMP_Int64 value, XMP_Uns64 offset=0 ); + + std::string getString( XMP_Uns64 size = 0, XMP_Uns64 offset=0 ) const; + void setString( std::string value, XMP_Uns64 offset=0 ); + + + //===================== IChunk interface implementation ================ + + //FIXME XMP exception if size cast from 64 to 32 looses data + + /** + * Sets the chunk id. + */ + void setID( XMP_Uns32 id ); + + /** + * Sets the chunk type. + */ + void setType( XMP_Uns32 type ); + + /** + * Sets the chunk size. + * NOTE: Should only be used for repairing wrong sizes in files (repair flag). + * Normally Size is changed by changing the data automatically! + */ + inline void setSize( XMP_Uns64 newSize, bool setOriginal = false ) { mDirty = mSize != newSize; mSize = newSize; mOriginalSize = setOriginal ? newSize : mOriginalSize; } + + /** + * Calculate the size of the chunks that are dirty including the size + * of its children + */ + XMP_Int64 calculateWriteSize( ) const; + + /** + * Calculate the size of this chunks based on its children sizes. + * If this chunk has no children then no new size will be calculated. + */ + XMP_Uns64 calculateSize( bool setOriginal = false ); + + /** + * @return Returns the offset of the chunk within the stream. + */ + inline XMP_Uns64 getOffset () const { return mOffset; } + + /** + * @return Returns the original offset of the chunk within the stream. + */ + inline XMP_Uns64 getOriginalOffset () const { return mOriginalOffset; } + + /** + * Returns the original size of the Chunk + * + * @param includeHeader if set, the returned original size will be the whole chunk size including the header + * @return Returns the original size of the chunk within the stream (inluding/excluding headerSize). + */ + inline XMP_Uns64 getOriginalSize( bool includeHeader = false ) const { return includeHeader ? mOriginalSize + HEADER_SIZE : mOriginalSize; } + + /** + * Returns the original size of the Chunk including a pad byte if the size isn't a even number + * + * @param includeHeader if set, the returned size will be the whole chunk size including the header + * @return Returns either the size of the data block of the chunk or size of the whole chunk, including the eight byte header. + */ + XMP_Uns64 getOriginalPadSize( bool includeHeader = false ) const; + + /** + * Adjust the offset that this chunk has within the file. + * + * @param newOffset the new offset within the file stream + */ + void setOffset (XMP_Uns64 newOffset); // changes during rearranging + + /** + * Has the Chunk class changes, or has the position within the file been changed? + * If the result is true the chunk has to be written back to the file + * (all parent chunks are also set to dirty in that case). + * + * @return Returns true if the chunk node has been modified. + */ + XMP_Bool hasChanged() const { return mDirty; } + + /** + * Sets this node and all of its parents up to the tree root dirty. + */ + void setChanged(); + + /** + * Resets the dirty status for this chunk and its children to false + */ + void resetChanges(); + + /** + *Sets all necessary member variables to flag this chunk as a new one being inserted into the tree + */ + void setAsNew(); + + /** + * @return Returns the parent chunk (can be NULL if this is the root of the tree). + */ + inline Chunk* getParent() const { return mParent; } + + /** + * Creates a string representation of the chunk (debug method). + */ + std::string toString( std::string tabs = std::string() , XMP_Bool showOriginal = false ); + + + //------------------- + // file access + //------------------- + + /** + * Read id, size and offset and create a chunk with mode CHUNK_UNKNOWN. + * The file is expected to be open and is not closed! + * + * @param file File reference to read the chunk from + */ + void readChunk( XMP_IO* file ); + + /** + * Stores the data in the class (only called if required). + * The file is expected to be open and is not closed! + * + * @param file File reference to cache the chunk data + */ + void cacheChunkData( XMP_IO* file ); + + /** + * Write or updates chunk (new data, new size, new position). + * The file is expected to be open and is not closed! + * + * Behavior for the different chunk types: + * + * CHUNK_UNKNOWN: + * - does not write anything back + * - throws exception if hasChanged == true + * + * CHUNK_LEAF: + * - writes ID (starting with offset) + * - writes size + * - writes buffer (including the optional type at the beginning) + * + * CHUNK_NODE: + * - writes ID (starting with offset) + * - writes size + * - writes type if defined + * - calls writeChunk on it's children + * + * Note: readChunk() and optionally cacheChunkData() has to be called before! + * + * @param file File reference to write the chunk to + */ + void writeChunk( XMP_IO* file ); + + + //------------------- + // children access + //------------------- + + /** + * @return Returns the number children chunks. + */ + XMP_Uns32 numChildren() const; + + /** + * Returns a child node. + * + * @param pos position of the child node to return + * @return Returns the child node at the given position. + */ + Chunk* getChildAt( XMP_Uns32 pos ) const; + + /** + * Appends a child node at the end of the children list. + * + * @param node the new node + * @param adjustSizes adjust size of chunk and parents + * @return Returns the added node. + */ + void appendChild( Chunk* node, XMP_Bool adjustSizes = true ); + + /** + * Inserts a child node at a certain position. + * + * @param pos position in the children list to add the new node + * @param node the new node + * @return Returns the added node. + */ + void insertChildAt( XMP_Uns32 pos, Chunk* node ); + + /** + * Removes a child node at a given position. + * + * @param pos position of the node to delete in the children list + * + * @return The removed chunk + */ + Chunk* removeChildAt( XMP_Uns32 pos ); + + /** + * Remove child at the passed position and insert the new chunk + * + * @param pos Position of chunk that will be replaced + * @param chunk New chunk + * + * @return Replaced chunk + */ + Chunk* replaceChildAt( XMP_Uns32 pos, Chunk* node ); + + //-------------------- + // children iteration + //-------------------- + + typedef std::vector::iterator ChunkIterator; + typedef std::vector::const_iterator ConstChunkIterator; + + ConstChunkIterator firstChild() const; + + ConstChunkIterator lastChild() const; + + /** The size of the header (id+size) */ + static const XMP_Uns8 HEADER_SIZE = 8; + /** The size of the type */ + static const XMP_Uns8 TYPE_SIZE = 4; + + + private: + /** stores the chunk header */ + ChunkIdentifier mChunkId; + /** Original size of chunk without the header */ + XMP_Uns64 mOriginalSize; + /** size of chunk without the header */ + XMP_Uns64 mSize; + /** size of the internal buffer */ + XMP_Uns64 mBufferSize; + /** buffer for the chunk data without the header, but including the type (first 4 bytes). */ + XMP_Uns8* mData; + /** Buffer to hold the first 4 bytes that are used for either the type or as data. + * Only used for ReadChunk and CacheChunk */ + ChunkMode mChunkMode; + + /** + * Current position in stream (file). Can only be changed by moving the chunks around + * (by using an IChunkBehavior class). + * Note: Sizes are stored in chunk because it can be changed by the handler (i.e. by changing the data) + */ + XMP_Uns64 mOriginalOffset; + /** + * New position of the chunk in the stream. + * It is initialized to MAXINT64 when there is no new offset. + * If the offset has been changed the dirty flag has to be set. + */ + XMP_Uns64 mOffset; + + /** + * The dirty flag indicates that the chunk (and all parent chunks) has been modified or moved and + * that it therefore needs to be written to file. + */ + XMP_Bool mDirty; // has Chunk data changed? has Chunk position changed? + + /** The parent of this node; only the root node does not have a parent. */ + Chunk* mParent; + + /** Stores the byte order for this node. + * Note: The endianess does not change within one file */ + const IEndian& mEndian; + + /** The list of child nodes. */ + std::vector mChildren; + + /** + * private ctor, prevents direct invokation. + * + * @param endian Endian util + */ + Chunk( const IEndian& endian ); + + /** + * Resizes the internal byte buffer to the given size if the new size is bigger than the current one. + * If the new size is smaller, the buffer is not adjusted + */ + void adjustInternalBuffer( XMP_Uns64 newSize ); + + /** + * Adjusts the chunk size and the parents chunk sizes. + * - Leaf chunks always have the size of their data, inluding the 4-byte type and excluding the header. + * Leaf chunks can have an ODD size! + * - Node chunks have the added size of all of their children, including the childrens header, but excluding it's own header. + * IMPORTANT: When a leaf child node has an ODD size of data, + * a pad byte is added during the writing process and the parent's size INCLUDES the pad byte. + */ + void adjustSize( XMP_Int64 sizeChange = 0 ); + +}; // Chunk + +} // namespace + +#endif diff --git a/core/libs/dngwriter/extra/xmp_sdk/XMPFiles/source/FormatSupport/IFF/ChunkController.cpp b/core/libs/dngwriter/extra/xmp_sdk/XMPFiles/source/FormatSupport/IFF/ChunkController.cpp new file mode 100644 index 0000000000..fc7c2c6d0a --- /dev/null +++ b/core/libs/dngwriter/extra/xmp_sdk/XMPFiles/source/FormatSupport/IFF/ChunkController.cpp @@ -0,0 +1,733 @@ +// ================================================================================================= +// ADOBE SYSTEMS INCORPORATED +// Copyright 2010 Adobe Systems Incorporated +// All Rights Reserved +// +// NOTICE: Adobe permits you to use, modify, and distribute this file in accordance with the terms +// of the Adobe license agreement accompanying it. +// ================================================================================================= + +#include "public/include/XMP_Environment.h" // ! XMP_Environment.h must be the first included header. +#include "public/include/XMP_Const.h" +#include "source/XIO.hpp" + +#include "XMPFiles/source/FormatSupport/IFF/ChunkController.h" +#include "XMPFiles/source/FormatSupport/IFF/Chunk.h" + +#include + +using namespace IFF_RIFF; + +//----------------------------------------------------------------------------- +// +// ChunkController::ChunkController(...) +// +// Purpose: ctor/dtor +// +//----------------------------------------------------------------------------- + +ChunkController::ChunkController( IChunkBehavior* chunkBehavior, XMP_Bool bigEndian ) +: mEndian (NULL), + mChunkBehavior (chunkBehavior), + mFileSize (0), + mRoot (NULL), + mTrailingGarbageSize (0), + mTrailingGarbageOffset (0) +{ + if (bigEndian) + { + mEndian = &BigEndian::getInstance(); + } else { + mEndian = &LittleEndian::getInstance(); + } + + // create virtual root chunk + mRoot = Chunk::createChunk(*mEndian); + + // share chunk paths with behavior + mChunkBehavior->setMovablePaths( &mChunkPaths ); +} + +ChunkController::~ChunkController() +{ + XMP_Validate( mRoot != NULL, "ERROR inserting Chunk. mRoot is NULL.", kXMPErr_InternalFailure ); + XMP_Assert(dynamic_cast(mRoot) == static_cast(mRoot)); + delete dynamic_cast(mRoot); +} + +//----------------------------------------------------------------------------- +// +// ChunkController::addChunkPath(...) +// +// Purpose: Adds the given path to the array of "Chunk's of interest" +// +//----------------------------------------------------------------------------- + +void ChunkController::addChunkPath( const ChunkPath& path ) +{ + mChunkPaths.push_back(path); +} + +//----------------------------------------------------------------------------- +// +// ChunkController::compareChunkPaths(...) +// +// Purpose: The function parses all the sibling chunks. For every chunk it +// either caches the chunk, skips it, or calls the function recusivly +// for the children chunks +// +//----------------------------------------------------------------------------- + +ChunkPath::MatchResult ChunkController::compareChunkPaths(const ChunkPath& currentPath) +{ + ChunkPath::MatchResult result = ChunkPath::kNoMatch; + + for( PathIterator iter = mChunkPaths.begin(); ( result == ChunkPath::kNoMatch ) && ( iter != mChunkPaths.end() ); iter++ ) + { + result = iter->match(currentPath); + } + + return result; +} + +//----------------------------------------------------------------------------- +// +// ChunkController::parseChunks(...) +// +// Purpose: The function Parses all the sibling chunks. For every chunk it +// either caches the chunk, skips it, or calls the function recusivly +// for the children chunks +// +//----------------------------------------------------------------------------- + +void ChunkController::parseChunks( XMP_IO* stream, ChunkPath& currentPath, XMP_OptionBits* options /* = NULL */, Chunk* parent /* = NULL */) +{ + XMP_Uns64 filePos = stream->Offset(); + XMP_Bool isRoot = (parent == mRoot); + XMP_Uns64 parseLimit = mFileSize; + XMP_Uns32 chunkCnt = 0; + + XMP_Validate( mRoot != NULL, "ERROR inserting Chunk. mRoot is NULL.", kXMPErr_InternalFailure ); + XMP_Assert(dynamic_cast(mRoot) == static_cast(mRoot)); + parent = ( parent == NULL ? dynamic_cast(mRoot) : parent ); + + // + // calculate the parse limit + // + if ( !isRoot ) + { + parseLimit = parent->getOriginalOffset() + parent->getSize( true ); + + if( parseLimit > mFileSize ) + { + parseLimit = mFileSize; + } + } + + while ( filePos < parseLimit ) + { + XMP_Uns64 fileTail = mFileSize - filePos; + + // + // check if there is enough space (at least for id and size) + // + if ( fileTail < Chunk::HEADER_SIZE ) + { + //preserve rest of bytes (fileTail) + mTrailingGarbageOffset = filePos; + mTrailingGarbageSize = fileTail; + break; // stop parsing + } + else + { + bool chunkJump = false; + + // + // create a new Chunk + // + Chunk* chunk = Chunk::createChunk(* mEndian ); + + bool readFailure = false; + // + // read the Chunk (id, size, [type]) without caching the data + // + try + { + chunk->readChunk( stream ); + } + catch( ... ) + { + // remember exception during reading the chunk + readFailure = true; + } + + // + // validate chunk ID for top-level chunks + // + if( isRoot && ! mChunkBehavior->isValidTopLevelChunk( chunk->getIdentifier(), chunkCnt ) ) + { + // notValid: preserve rest of bytes (fileTail) + mTrailingGarbageOffset = filePos; + mTrailingGarbageSize = fileTail; + //delete unused chunk (because these are undefined trailing bytes) + delete chunk; + break; // stop parsing + } + else if ( readFailure ) + { + delete chunk; + XMP_Throw ( "Bad RIFF chunk", kXMPErr_BadFileFormat ); + } + + // + // parenting + // (as early as possible in order to be able to clean up + // the tree correctly in the case of an exception) + // + parent->appendChild(chunk, false); + + // count top-level chunks + if( isRoot ) + { + chunkCnt++; + } + + // + // check size if value exceeds 4GB border + // + if( chunk->getSize() >= 0x00000000FFFFFFFFLL ) + { + // remember file position + XMP_Int64 currentFilePos = stream->Offset(); + + // ask for the "real" size value + XMP_Uns64 realSize = mChunkBehavior->getRealSize( chunk->getSize(), + chunk->getIdentifier(), + *mRoot, + stream ); + + // set new size at chunk + chunk->setSize( realSize, true ); + + // set flag if the file position changed + chunkJump = currentFilePos < stream->Offset(); + } + + // + // Repair if needed + // + if ( filePos + chunk->getSize(true) > mFileSize ) + { + bool isUpdate = ( options != NULL ? XMP_OptionIsSet ( *options, kXMPFiles_OpenForUpdate ) : false ); + bool repairFile = ( options != NULL ? XMP_OptionIsSet ( *options, kXMPFiles_OpenRepairFile ) : false ); + + if ( ( ! isUpdate ) || ( repairFile && isRoot ) ) + { + chunk->setSize( mFileSize-filePos-Chunk::HEADER_SIZE, true ); + } + else + { + XMP_Throw ( "Bad RIFF chunk size", kXMPErr_BadFileFormat ); + } + } + + // extend search path + currentPath.append( chunk->getIdentifier() ); + + // first 4 bytes might be already read by the chunk->readChunk function + XMP_Uns64 offsetOfChunkRead = stream->Offset() - filePos - Chunk::HEADER_SIZE; + + switch ( compareChunkPaths(currentPath) ) + { + case ChunkPath::kFullMatch : + { + chunk->cacheChunkData( stream ); + } + break; + + case ChunkPath::kPartMatch : + { + parseChunks( stream, currentPath, options, chunk); + // recalculate the size based on the sizes of its children + chunk->calculateSize( true ); + } + break; + + case ChunkPath::kNoMatch : + { + // Not a chunk we are interested in, so mark it as not changed + // It will then be ignored by any further logic + chunk->resetChanges(); + + if ( !chunkJump && chunk->getSize() > 0) // if chunk not empty + { + XMP_Validate( stream->Offset() + chunk->getSize() - offsetOfChunkRead <= mFileSize , "ERROR: want's to skip beyond EOF", kXMPErr_InternalFailure); + stream->Seek ( chunk->getSize() - offsetOfChunkRead , kXMP_SeekFromCurrent ); + } + } + break; + } + + // remove last identifier from current path + currentPath.remove(); + + // update current file position + filePos = stream->Offset(); + + // skip pad byte if there is one (if size odd) + if( filePos < mFileSize && + ( ( chunkJump && ( stream->Offset() & 1 ) > 0 ) || + ( !chunkJump && ( chunk->getSize() & 1 ) > 0 ) ) ) + { + stream->Seek ( 1 , kXMP_SeekFromCurrent ); + filePos++; + } + } + } +} + + +//----------------------------------------------------------------------------- +// +// ChunkController::parseFile(...) +// +// Purpose: construct the tree, parse children for list of interesting Chunks +// All requested leaf chunks are cached, the parent chunks are created +// but not cached and the rest is skipped +// +//----------------------------------------------------------------------------- + +void ChunkController::parseFile( XMP_IO* stream, XMP_OptionBits* options /* = NULL */ ) +{ + // store file information in root node + mFileSize = stream ->Length(); + ChunkPath currentPath; + + // Make sure the tree is clean before parsing + cleanupTree(); + + try + { + parseChunks( stream, currentPath, options, dynamic_cast(mRoot) ); + } + catch( ... ) + { + this->cleanupTree(); + throw; + } +} + + +//----------------------------------------------------------------------------- +// +// ChunkController::writeFile(...) +// +// Purpose: Called by the handler to write back the changes to the file. +// +//----------------------------------------------------------------------------- +void ChunkController::writeFile( XMP_IO* stream ,XMP_ProgressTracker * progressTracker ) + +{ + // + // if any of the top-level chunks exceeds their maximum size then skip writing and throw an exception + // + for( XMP_Uns32 i=0; inumChildren(); i++ ) + { + Chunk* toplevel = mRoot->getChildAt(i); + XMP_Validate( toplevel->getSize() < mChunkBehavior->getMaxChunkSize(), "Exceeded maximum chunk size.", kXMPErr_AssertFailure ); + } + + // + // if exception is thrown write chunk is skipped + // + mChunkBehavior->fixHierarchy(*mRoot); + + if (mRoot->numChildren() > 0) + { + // The new file size (without trailing garbage) is the offset of the last top-level chunk + its size. + // NOTE: the padding bytes can be ignored, as the top-level chunk is always a node, not a leaf. + Chunk* lastChild = mRoot->getChildAt(mRoot->numChildren() - 1); + XMP_Uns64 newFileSize = lastChild->getOffset() + lastChild->getSize(true); + if ( progressTracker != 0 ) + { + float fileWriteSize=0.0f; + for( XMP_Uns32 i = 0; i < mRoot->numChildren(); i++ ) + { + Chunk* child = mRoot->getChildAt(i); + fileWriteSize+=child->calculateWriteSize( ); + } + XMP_Assert ( progressTracker->WorkInProgress() ); + progressTracker->AddTotalWork ( fileWriteSize ); + } + + // Move garbage tail after last top-level chunk, + // BEFORE the chunks are written -- in case the file shrinks + if (mTrailingGarbageSize > 0 && newFileSize != mTrailingGarbageOffset) + { + if ( progressTracker != 0 ) + { + XMP_Assert ( progressTracker->WorkInProgress() ); + progressTracker->AddTotalWork ( (float)mTrailingGarbageSize ); + } + XIO::Move( stream, mTrailingGarbageOffset, stream, newFileSize, mTrailingGarbageSize ); + newFileSize += mTrailingGarbageSize; + } + + // Write changed and new chunks to the file + for( XMP_Uns32 i = 0; i < mRoot->numChildren(); i++ ) + { + Chunk* child = mRoot->getChildAt(i); + child->writeChunk( stream ); + } + + // file has been completely written, + // truncate the file it has been bigger before + if (newFileSize < mFileSize) + { + stream->Truncate ( newFileSize ); + } + } +} + +//----------------------------------------------------------------------------- +// +// ChunkController::getChunk(...) +// +// Purpose: returns a certain Chunk +// +//----------------------------------------------------------------------------- + +IChunkData* ChunkController::getChunk( const ChunkPath& path, XMP_Bool last ) const +{ + IChunkData* ret = NULL; + + if( path.length() > 0 ) + { + ChunkPath current; + ret = this->findChunk( path, current, *(dynamic_cast(mRoot)), last ); + } + + return ret; +} + + +//----------------------------------------------------------------------------- +// +// ChunkController::findChunk(...) +// +// Purpose: Find a chunk described by path in the hierarchy of chunks starting +// at the passed chunk. +// The position of chunk in the hierarchy is described by the parameter +// currentPath. +// This method is supposed to be recursively. +// +//----------------------------------------------------------------------------- + +Chunk* ChunkController::findChunk( const ChunkPath& path, ChunkPath& currentPath, const Chunk& chunk, XMP_Bool last ) const +{ + Chunk* ret = NULL; + XMP_Uns32 cnt = 0; + + if( path.length() > currentPath.length() ) + { + for( XMP_Uns32 i=0; igetIdentifier() ); + + switch( path.match( currentPath ) ) + { + case ChunkPath::kFullMatch: + { + ret = child; + } + break; + + case ChunkPath::kPartMatch: + { + ret = this->findChunk( path, currentPath, *child, last ); + } + break; + + case ChunkPath::kNoMatch: + { + // Nothing to do + } + break; + } + + currentPath.remove(); + } + } + } + + return ret; +} + + +//----------------------------------------------------------------------------- +// +// ChunkController::getChunks(...) +// +// Purpose: Returns all chunks that match completely to the passed path. +// +//----------------------------------------------------------------------------- + +const std::vector& ChunkController::getChunks( const ChunkPath& path ) +{ + mSearchResults.clear(); + + if( path.length() > 0 ) + { + ChunkPath current; + this->findChunks( path, current, *(dynamic_cast(mRoot)) ); + } + + return mSearchResults; +}//getChunks + + +//----------------------------------------------------------------------------- +// +// ChunkController::getTopLevelTypes(...) +// +// Purpose: Return an array containing the types of the top level nodes +// Top level nodes are the ones beneath ROOT +// +//----------------------------------------------------------------------------- + +const std::vector ChunkController::getTopLevelTypes() +{ + std::vector typeList; + + for( XMP_Uns32 i = 0; i < mRoot->numChildren(); i++ ) + { + typeList.push_back( mRoot->getChildAt( i )->getType() ); + } + + return typeList; +}// getTopLevelTypes + + +//----------------------------------------------------------------------------- +// +// ChunkController::findChunks(...) +// +// Purpose: Find all chunks described by path in the hierarchy of chunks starting +// at the passed chunk. +// The position of chunks in the hierarchy is described by the parameter +// currentPath. Found chunks that match to the path are stored in the +// member mSearchResults. +// This method is supposed to be recursively. +// +//----------------------------------------------------------------------------- + +void ChunkController::findChunks( const ChunkPath& path, ChunkPath& currentPath, const Chunk& chunk ) +{ + if( path.length() > currentPath.length() ) + { + for( XMP_Uns32 i=0; igetIdentifier() ); + + switch( path.match( currentPath ) ) + { + case ChunkPath::kFullMatch: + { + mSearchResults.push_back( child ); + } + break; + + case ChunkPath::kPartMatch: + { + this->findChunks( path, currentPath, *child ); + } + break; + + case ChunkPath::kNoMatch: + { + // Nothing to do + } + break; + } + + currentPath.remove(); + } + } + } +}//findChunks + + +//----------------------------------------------------------------------------- +// +// ChunkController::cleanupTree(...) +// +// Purpose: Cleanup function called from destructor and in case of an exception +// +//----------------------------------------------------------------------------- + +void ChunkController::cleanupTree() +{ + XMP_Validate( mRoot != NULL, "ERROR inserting Chunk. mRoot is NULL.", kXMPErr_InternalFailure ); + XMP_Assert(dynamic_cast(mRoot) == static_cast(mRoot)); + delete dynamic_cast(mRoot); + mRoot = Chunk::createChunk(*mEndian); +} + + +//----------------------------------------------------------------------------- +// +// ChunkController::dumpTree(...) +// +// Purpose: dumps the tree structure +// +//----------------------------------------------------------------------------- + +std::string ChunkController::dumpTree( ) +{ + std::string ret; + char buffer[256]; + + if ( mRoot != NULL ) + { + ret = mRoot->toString(); + } + + if ( mTrailingGarbageSize != 0 ) + { + snprintf( buffer, 255, "\n Trailing Bytes: %llu", mTrailingGarbageSize ); + + std::string str(buffer); + ret.append(str); + } + return ret; +} + +//----------------------------------------------------------------------------- +// +// ChunkController::createChunk(...) +// +// Purpose: Create a new empty chunk +// +//----------------------------------------------------------------------------- + +IChunkData* ChunkController::createChunk( XMP_Uns32 id, XMP_Uns32 type /*= kType_NONE*/ ) +{ + Chunk* chunk = Chunk::createChunk(* mEndian ); + + chunk->setID( id ); + if( type != kType_NONE ) + { + chunk->setType( type ); + } + + return chunk; +} + +//----------------------------------------------------------------------------- +// +// ChunkController::insertChunk(...) +// +// Purpose: Insert a new chunk. The position of this new chunk within the +// hierarchy is determined internally by the behavior. +// Throws an exception if a chunk cannot be inserted into the tree +// +//----------------------------------------------------------------------------- + +void ChunkController::insertChunk( IChunkData* chunk ) +{ + XMP_Validate( chunk != NULL, "ERROR inserting Chunk. Chunk is NULL.", kXMPErr_InternalFailure ); + XMP_Assert(dynamic_cast(chunk) == static_cast(chunk)); + + Chunk* ch = dynamic_cast(chunk); + mChunkBehavior->insertChunk( *mRoot, *ch ); + // sets OriginalSize = Size / OriginalOffset = Offset + ch->setAsNew(); + // force set dirty flag + ch->setChanged(); +} + +//----------------------------------------------------------------------------- +// +// ChunkController::removeChunk(...) +// +// Purpose: Delete a chunk or remove/delete it from the tree. +// If the chunk exists within the chunk hierarchy the chunk gets removed +// from the tree and deleted. +// If it is not in the tree, then it is only destroyed. +// +//----------------------------------------------------------------------------- + +void ChunkController::removeChunk( IChunkData* chunk ) +{ + if( chunk != NULL ) + { + Chunk* chk = dynamic_cast(chunk); + + if( this->isInTree( chk ) ) + { + if( mChunkBehavior->removeChunk( *mRoot, *chk ) ) + { + delete chk; + } + } + else + { + delete chk; + } + } +} + +//----------------------------------------------------------------------------- +// +// ChunkController::isInTree(...) +// +// Purpose: return true if the passed in Chunk is part of the Chunk tree +// +//----------------------------------------------------------------------------- + +bool ChunkController::isInTree( Chunk* chunk ) +{ + bool ret = ( mRoot == chunk ); + + if( !ret && chunk != NULL ) + { + Chunk* parent = chunk->getParent(); + + while( !ret && parent != NULL ) + { + ret = ( mRoot == parent ); + parent = parent->getParent(); + } + } + + return ret; +} diff --git a/core/libs/dngwriter/extra/xmp_sdk/XMPFiles/source/FormatSupport/IFF/ChunkController.h b/core/libs/dngwriter/extra/xmp_sdk/XMPFiles/source/FormatSupport/IFF/ChunkController.h new file mode 100644 index 0000000000..52dea4221e --- /dev/null +++ b/core/libs/dngwriter/extra/xmp_sdk/XMPFiles/source/FormatSupport/IFF/ChunkController.h @@ -0,0 +1,248 @@ +// ================================================================================================= +// ADOBE SYSTEMS INCORPORATED +// Copyright 2010 Adobe Systems Incorporated +// All Rights Reserved +// +// NOTICE: Adobe permits you to use, modify, and distribute this file in accordance with the terms +// of the Adobe license agreement accompanying it. +// ================================================================================================= + +#ifndef _ChunkController_h_ +#define _ChunkController_h_ + +#include "public/include/XMP_Environment.h" // ! XMP_Environment.h must be the first included header. + +#include "public/include/XMP_Const.h" +#include "public/include/XMP_IO.hpp" + +#include "source/XMP_LibUtils.hpp" +#include "source/XMP_ProgressTracker.hpp" + +#include "XMPFiles/source/FormatSupport/IFF/ChunkPath.h" +#include "XMPFiles/source/FormatSupport/IFF/IChunkBehavior.h" + +class IEndian; + +namespace IFF_RIFF +{ +/** + The class ChunkController is supposed to act as an controller between the IRIFFHandler and the actual chunks (Chunk instances). + It controls the parsing and writing of the passed stream. +*/ + +class IChunkData; +class IChunkContainer; +class Chunk; + +class ChunkController +{ + public: + /** + * Constructor: + * Creates an IEndian based instance for further usage. + * + * @param IChunkBehavior* chunkBehavior : for AVI the IChunkBehavior instance would be an instance of a IChunkBehavior class, that knows + * about the 1,2,4 GB border, padding byte special cases, AVIX stuff and so on. That knowledge would + * be used during writeFile() + * In the case of WAVE it would be an instance of WAVEBehavior that would know how to get the 64bit + * size values for RF64 if required. That knowledge would be used during parseFile() + * @param XMP_Bool bigEndian set True if file chunk data is big endian (e.g. AIFF). + * Must explicitely be set, so that handlers do not accidentaly use the wrong endianess + */ + ChunkController( IChunkBehavior* chunkBehavior, XMP_Bool bigEndian ); + + ~ChunkController(); + + /** + * Adds the given path to the array of "Chunk's of interest", + * + * @param path List of Paths that should be parsed + * example AVI: [ RIFF:AVI/LIST:INFO , RIFF:AVIX/LIST:INFO, RIFF:AVI/LIST:TDAT ] + */ + void addChunkPath( const ChunkPath& path ); + + /** + * construct the tree, parse children for list of interesting Chunks + * All requested leaf chunks are cached, the parent chunks are created but not cached + * and the rest is skipped. + * + * @param stream the open [file] stream with file pointer at the beginning of the file + * + */ + void parseFile( XMP_IO* stream, XMP_OptionBits* options = NULL ); + + /** + * Create a new empty chunk + * + * @param id Chunk identifier + * @param type Chunk type [optional] + * @return New IChunkData with passed id/type + */ + IChunkData* createChunk( XMP_Uns32 id, XMP_Uns32 type = kType_NONE ); + + /** + * Insert a new chunk. The position of this new chunk within the hierarchy + * is determined internally by the behavior. + * Throws an exception if a chunk cannot be inserted into the tree + * + * @param chunk The chunk to insert into the tree + */ + void insertChunk( IChunkData* chunk ); + + /** + * Delete a chunk or remove/delete it from the tree. + * If the chunk exists within the chunk hierarchy the chunk gets removed from the tree and deleted. + * If it is not in the tree, then it is only destroyed. + * + * @param chunk Chunk to remove/delete + */ + void removeChunk( IChunkData* chunk ); + + /** + * Called by the handler to write back the changes to the file. + * 1. fix the file tree (ChunkBehavior#fixHierarchy), + * offsets are corrected, no overlapping chunks; + * if rearranging fails, the file is not touched + * 2. write the changed chunks to the file + * + * @param stream the open [file] stream for writing, the file pointer must be at the beginning + * @param progressTracker Progress tracker to track the file write progress and reporting it to client + */ + void writeFile( XMP_IO* stream,XMP_ProgressTracker * progressTracker ); + + /** + * Returns the first (or last) Chunk that matches the passed path. + * + * @param path the path of the Chunk to return + * @param last in case of duplicates return the last one + * @return Returns Chunk or NULL + */ + IChunkData* getChunk( const ChunkPath& path, XMP_Bool last = false ) const; + + /** + * Returns all chunks that match completely to the passed path. + * E.g. if FORM:AIFF/LIST is given, it would return all LIST chunks in FORM:AIFF + * + * @param path the path of the Chunk to return + * @return list of found chunks or empty list + */ + const std::vector& getChunks( const ChunkPath& path ); + + /** + * returns the number of the bytes after the last valid IFF chunk + */ + inline XMP_Int64 getTrailingGarbageSize() { return mTrailingGarbageSize; }; + + /** + * returns the file size + */ + inline XMP_Int64 getFileSize() { return mFileSize; }; + + /** + * Return an array containing the types of the top level nodes + * Top level nodes are the ones beneath ROOT + */ + const std::vector getTopLevelTypes(); + + /** + * dumps the tree structure + * + */ + std::string dumpTree( ); + + protected: + /** + * Standard Constructor: + * Hidden on purpose. Must not be used! + * A Controller must have a behavior! + */ + ChunkController() { XMP_Throw("Ctor hidden", kXMPErr_InternalFailure); } + + /** + * The function Parses all the sibling chunks. For every chunk it either caches the chunk, + * skips it, or calls the function recusivly for the children chunks + * + * @param stream the file stream + * @param currentPath the path/id of the Chunk to return + * @param options handler options + * @param parent pointer to the parent chunk + */ + void parseChunks( XMP_IO* stream, ChunkPath& currentPath, XMP_OptionBits* options = NULL, Chunk* parent = NULL ); + + /** + * The function parses all the sibling chunks. For every chunk it either caches the chunk, + * skips it, or calls the function recusivly for the children chunks + * + * @param ChunkPath& currentPath: the path/id of the Chunk to return + */ + ChunkPath::MatchResult compareChunkPaths( const ChunkPath& currentPath ); + + /** + * Find a chunk described by path in the hierarchy of chunks starting at the passed chunk. + * The position of chunk in the hierarchy is described by the parameter currentPath. + * This method is supposed to be recursively. + */ + Chunk* findChunk( const ChunkPath& path, ChunkPath& currentPath, const Chunk& chunk, XMP_Bool last = false ) const; + + /** + * Find all chunks described by path in the hierarchy of chunks starting at the passed chunk. + * The position of chunks in the hierarchy is described by the parameter currentPath. + * Found chunks that match to the path are stored in the member mSearchResults. + * This method is supposed to be recursively. + */ + void findChunks( const ChunkPath& path, ChunkPath& currentPath, const Chunk& chunk ); + + /** + * Cleanup function called from destructor and in case of an exception + */ + void cleanupTree(); + + /** + * return true if the passed in Chunk is part of the Chunk tree + * + * @param chunk the chunk that shall be checked. + */ + bool isInTree( Chunk* chunk ); + + + // Members + + /** + * Endian class. Either BigEndian oder LittleEndian. Based on the file format. + */ + const IEndian* mEndian; + + /** + * Chunk behaviour class. Has file format specific function for getting the size and + * rearranging the chunk tree. + */ + IChunkBehavior* mChunkBehavior; + + /** The list of chunks wich should be cached. */ + std::vector mChunkPaths; + + /** Iterator for the list of chunk paths */ + typedef std::vector::iterator PathIterator; + + /** The overall filesize after parsing the file stream */ + XMP_Uns64 mFileSize; + + /** The root of the Chunk Tree (top level list) */ + IChunkContainer* mRoot; + + /** Offset of trailing garbage characters */ + XMP_Uns64 mTrailingGarbageOffset; + + /** Size of trailing garbage characters */ + XMP_Uns64 mTrailingGarbageSize; + + /** search results of method getChunks(...) */ + ChunkPath mSearchPath; + + /** Cached search results */ + std::vector mSearchResults; +}; // ChunkController + +} // namespace + +#endif diff --git a/core/libs/dngwriter/extra/xmp_sdk/XMPFiles/source/FormatSupport/IFF/ChunkPath.cpp b/core/libs/dngwriter/extra/xmp_sdk/XMPFiles/source/FormatSupport/IFF/ChunkPath.cpp new file mode 100644 index 0000000000..54680f847c --- /dev/null +++ b/core/libs/dngwriter/extra/xmp_sdk/XMPFiles/source/FormatSupport/IFF/ChunkPath.cpp @@ -0,0 +1,239 @@ +// ================================================================================================= +// ADOBE SYSTEMS INCORPORATED +// Copyright 2010 Adobe Systems Incorporated +// All Rights Reserved +// +// NOTICE: Adobe permits you to use, modify, and distribute this file in accordance with the terms +// of the Adobe license agreement accompanying it. +// ================================================================================================= + +#include "XMPFiles/source/FormatSupport/IFF/ChunkPath.h" +#include "source/XMP_LibUtils.hpp" + +#include + +using namespace IFF_RIFF; + +typedef std::vector::size_type ChunkSizeType; + +//----------------------------------------------------------------------------- +// +// ChunkPath::ChunkPath(...) +// +// Purpose: ctor/dtor +// +//----------------------------------------------------------------------------- + +ChunkPath::ChunkPath( const ChunkIdentifier* path /*= NULL*/, XMP_Uns32 size /*=0*/ ) +{ + if( path != NULL ) + { + for( XMP_Uns32 i=0; iappend( path[i] ); + } + } +} + +ChunkPath::ChunkPath( const ChunkPath& path ) +{ + for( XMP_Int32 i=0; iappend( path.identifier(i) ); + } +} + +ChunkPath::ChunkPath( const ChunkIdentifier& identifier ) +{ + this->append( identifier ); +} + +ChunkPath::~ChunkPath() +{ + this->clear(); +} + + +ChunkPath & ChunkPath::operator=( const ChunkPath &rhs ) +{ + for( XMP_Int32 i = 0; i < rhs.length(); i++ ) + { + this->append( rhs.identifier(i) ); + } + + return *this; +} + +//----------------------------------------------------------------------------- +// +// ChunkPath::clear(...) +// +// Purpose: Remove all ChunkIdentifier's from the path +// +//----------------------------------------------------------------------------- + +void ChunkPath::clear() +{ + mPath.clear(); +} + +//----------------------------------------------------------------------------- +// +// ChunkPath::append(...) +// +// Purpose: Append a ChunkIdentifier to the end of the path +// +//----------------------------------------------------------------------------- + +void ChunkPath::append( XMP_Uns32 id, XMP_Uns32 type /*= kType_NONE*/ ) +{ + ChunkIdentifier ci; + + ci.id = id; + ci.type = type; + + mPath.push_back(ci); +} + + +void ChunkPath::append( const ChunkIdentifier& identifier ) +{ + mPath.push_back(identifier); +} + + +void ChunkPath::append( const ChunkIdentifier* path, XMP_Uns32 size ) +{ + if( path != NULL ) + { + for( XMP_Uns32 i=0; i < size; i++ ) + { + this->append( path[i] ); + } + } +} + +//----------------------------------------------------------------------------- +// +// ChunkPath::insert(...) +// +// Purpose: Insert an identifier +// +//----------------------------------------------------------------------------- + +void ChunkPath::insert( const ChunkIdentifier& identifier, XMP_Uns32 pos /*= 0*/ ) +{ + if( pos >= mPath.size() ) + { + this->append( identifier ); + } + else + { + mPath.insert( mPath.begin() + pos, identifier ); + } +} + +//----------------------------------------------------------------------------- +// +// ChunkPath::remove(...) +// +// Purpose: Remove the endmost ChunkIdentifier from the path +// +//----------------------------------------------------------------------------- + +void ChunkPath::remove() +{ + mPath.pop_back(); +} + +//----------------------------------------------------------------------------- +// +// ChunkPath::removeAt(...) +// +// Purpose: Remove the ChunkIdentifier at the passed position in the path +// +//----------------------------------------------------------------------------- + +void ChunkPath::removeAt( XMP_Int32 pos ) +{ + if( ! mPath.empty() && pos >= 0 && (ChunkSizeType)pos < mPath.size() ) + { + mPath.erase( mPath.begin() + pos ); + } + else + { + XMP_Throw( "Index out of range.", kXMPErr_BadIndex ); + } +} + +//----------------------------------------------------------------------------- +// +// ChunkPath::identifier(...) +// +// Purpose: Return ChunkIdentifier at the passed position +// +//----------------------------------------------------------------------------- + +const ChunkIdentifier& ChunkPath::identifier( XMP_Int32 pos ) const +{ + return mPath.at(pos); +} + +//----------------------------------------------------------------------------- +// +// ChunkPath::length(...) +// +// Purpose: Return the number of ChunkIdentifier's in the path +// +//----------------------------------------------------------------------------- + +XMP_Int32 ChunkPath::length() const +{ + return (XMP_Int32)mPath.size(); +} + +//----------------------------------------------------------------------------- +// +// ChunkPath::match(...) +// +// Purpose: Compare the passed ChunkPath with this path. +// +//----------------------------------------------------------------------------- + +ChunkPath::MatchResult ChunkPath::match( const ChunkPath& path ) const +{ + MatchResult ret = kNoMatch; + + if( path.length() > 0 ) + { + XMP_Int32 depth = ( this->length() > path.length() ? path.length() : this->length() ); + XMP_Int32 matchCount = 0; + + for( XMP_Int32 i=0; iidentifier(i); + const ChunkIdentifier& id2 = path.identifier(i); + + if( id1.id == id2.id ) + { + if( i == this->length() - 1 && id1.type == kType_NONE ) + { + matchCount++; + } + else if( id1.type == id2.type ) + { + matchCount++; + } + } + else + break; + } + + if( matchCount == depth ) + { + ret = ( path.length() >= this->length() ? kFullMatch : kPartMatch ); + } + } + + return ret; +} diff --git a/core/libs/dngwriter/extra/xmp_sdk/XMPFiles/source/FormatSupport/IFF/ChunkPath.h b/core/libs/dngwriter/extra/xmp_sdk/XMPFiles/source/FormatSupport/IFF/ChunkPath.h new file mode 100644 index 0000000000..30ed3c030a --- /dev/null +++ b/core/libs/dngwriter/extra/xmp_sdk/XMPFiles/source/FormatSupport/IFF/ChunkPath.h @@ -0,0 +1,191 @@ +// ================================================================================================= +// ADOBE SYSTEMS INCORPORATED +// Copyright 2010 Adobe Systems Incorporated +// All Rights Reserved +// +// NOTICE: Adobe permits you to use, modify, and distribute this file in accordance with the terms +// of the Adobe license agreement accompanying it. +// ================================================================================================= + +#ifndef _ChunkPath_h_ +#define _ChunkPath_h_ + +#include "public/include/XMP_Environment.h" // ! This must be the first include. +#include "public/include/XMP_Const.h" + +#include // For UINT_MAX. +#include + +namespace IFF_RIFF +{ + +/** + A ChunkPath describes one certain chunk in the hierarchy of chunks + of the IFF/RIFF file format. + Each chunks gets identified by a structure of the type ChunkIdentifier. + Which consists of the 4byte ID of the chunk and, if applicable, the 4byte + type of the chunk. +*/ + +// IFF/RIFF ids +enum { + // invalid ID + kChunk_NONE = UINT_MAX, + + // format chunks + kChunk_RIFF = 0x52494646, + kChunk_RF64 = 0x52463634, + kChunk_FORM = 0x464F524D, + kChunk_JUNK = 0x4A554E4B, + kChunk_JUNQ = 0x4A554E51, + + + // other container chunks + kChunk_LIST = 0x4C495354, + + // other relevant chunks + kChunk_XMP = 0x5F504D58, // "_PMX" + + kChunk_data = 0x64617461, + + //should occur only in AVI + kChunk_Cr8r = 0x43723872, + kChunk_PrmL = 0x50726D4C, + + //should occur only in WAV + kChunk_DISP = 0x44495350, + kChunk_bext = 0x62657874, + kChunk_cart = 0x63617274, + kChunk_ds64 = 0x64733634, + kChunk_iXML = 0x69584D4C, + + // AIFF + kChunk_APPL = 0x4150504C, + kChunk_NAME = 0x4E414D45, + kChunk_AUTH = 0x41555448, + kChunk_CPR = 0x28632920, + kChunk_ANNO = 0x414E4E4F +}; + +// IFF/RIFF types +enum { + kType_AVI_ = 0x41564920, + kType_AVIX = 0x41564958, + kType_WAVE = 0x57415645, + kType_AIFF = 0x41494646, + kType_AIFC = 0x41494643, + kType_INFO = 0x494E464F, + kType_Tdat = 0x54646174, + + // AIFF + kType_XMP = 0x584D5020, + kType_FREE = 0x46524545, + + kType_NONE = UINT_MAX +}; + + +struct ChunkIdentifier +{ + XMP_Uns32 id; + XMP_Uns32 type; +}; + +/** +* calculates the size of a ChunkIdentifier array. +* Has to be a macro as the sizeof operator does nto work for pointer function parameters +*/ +#define SizeOfCIArray(ciArray) ( sizeof(ciArray) / sizeof(ChunkIdentifier) ) + + +class ChunkPath +{ +public: + /** + ctor/dtor + */ + ChunkPath( const ChunkIdentifier* path = NULL, XMP_Uns32 size = 0 ); + ChunkPath( const ChunkPath& path ); + ChunkPath( const ChunkIdentifier& identifier ); + ~ChunkPath(); + + ChunkPath & operator=( const ChunkPath &rhs ); + + /** + Append a ChunkIdentifier to the end of the path + + @param id 4byte id of chunk + @param type 4byte type of chunk + */ + void append( XMP_Uns32 id, XMP_Uns32 type = kType_NONE ); + void append( const ChunkIdentifier& identifier ); + + /** + Append a whole path + + @param path Array of ChunkIdentifiert objects + @param size number of elements in the given array + */ + void append( const ChunkIdentifier* path = NULL, XMP_Uns32 size = 0 ); + + /** + Insert an identifier + + @param identifier id and type + @param pos position within the path + */ + void insert( const ChunkIdentifier& identifier, XMP_Uns32 pos = 0 ); + + /** + Remove the endmost ChunkIdentifier from the path + */ + void remove(); + /** + Remove the ChunkIdentifier at the passed position in the path. + Throw exception if the position is out of range. + + @param pos Position of ChunkIdentifier in the path + */ + void removeAt( XMP_Int32 pos ); + + /** + Return ChunkIdentifier at the passed position + + @param pos Position of ChunkIdentifier in the path + @return ChunkIdentifier at passed position (throw exception if + the position is out of range) + */ + const ChunkIdentifier& identifier( XMP_Int32 pos ) const; + + /** + Return the number of ChunkIdentifier's in the path + */ + XMP_Int32 length() const; + + /** + Remove all ChunkIdentifier's from the path + */ + void clear(); + + /** + Compare the passed ChunkPath with this path. + + @param path Path to compare with this path + @return Match result + */ + enum MatchResult + { + kNoMatch = 0, + kPartMatch = 1, + kFullMatch = 2 + }; + + MatchResult match( const ChunkPath& path ) const; + +private: + std::vector mPath; +}; + +} + +#endif diff --git a/core/libs/dngwriter/extra/xmp_sdk/XMPFiles/source/FormatSupport/IFF/IChunkBehavior.cpp b/core/libs/dngwriter/extra/xmp_sdk/XMPFiles/source/FormatSupport/IFF/IChunkBehavior.cpp new file mode 100644 index 0000000000..44159c270a --- /dev/null +++ b/core/libs/dngwriter/extra/xmp_sdk/XMPFiles/source/FormatSupport/IFF/IChunkBehavior.cpp @@ -0,0 +1,595 @@ +// ================================================================================================= +// ADOBE SYSTEMS INCORPORATED +// Copyright 2010 Adobe Systems Incorporated +// All Rights Reserved +// +// NOTICE: Adobe permits you to use, modify, and distribute this file in accordance with the terms +// of the Adobe license agreement accompanying it. +// ================================================================================================= + +#include "public/include/XMP_Environment.h" // ! XMP_Environment.h must be the first included header. +#include "public/include/XMP_Const.h" + +#include "XMPFiles/source/FormatSupport/IFF/IChunkBehavior.h" +#include "XMPFiles/source/FormatSupport/IFF/Chunk.h" + +#include + +using namespace IFF_RIFF; + +//----------------------------------------------------------------------------- +// +// getIndex(...) +// +// Purpose: [static] Calculate index of chunk in tree +// +//----------------------------------------------------------------------------- + +static XMP_Uns32 getIndex( const IChunkContainer& tree, const Chunk& chunk ) +{ + const Chunk& parent = dynamic_cast( tree ); + + return std::find( parent.firstChild(), parent.lastChild(), &chunk ) - parent.firstChild(); +} + +//----------------------------------------------------------------------------- +// +// IChunkBehavior::arrangeChunksInPlace(...) +// +// Purpose: Try to arrange all chunks of the source tree at their current location. +// In a loop the method takes each chunk of the srcTree. +// * If a chunk is a FREE chunk then it is removed. +// * If a chunk is a known movable chunk then adjust its offset so that there's +// no gap to its previous chunk. If the chunk offset was adjusted and/or the +// chunk grew/shrank in its size then remember the offset difference for +// further processing +// * If the chunk is neither movable nor a FREE chunk and there is a offset +// difference then fill possible gaps with FREE chunk or move chunks to +// the destTree. +// +//----------------------------------------------------------------------------- + +void IChunkBehavior::arrangeChunksInPlace( IChunkContainer& srcTree, IChunkContainer& destTree ) +{ + XMP_Validate( &srcTree != &destTree, "Source and destination tree mustn't be the same", kXMPErr_InternalFailure ); + + XMP_Int64 offsetAdjust = 0; + + for( XMP_Int32 index=0; index( srcTree.numChildren() ); index++ ) + { + Chunk* chunk = srcTree.getChildAt( index ); + + // + // Is chunk one that might be moved in the tree + // (and so it's a chunk that might be modified) + // + if( this->isMovable( *chunk ) ) + { + // + // Are there FREE chunks above this chunk? + // Then remove it. + // + Chunk* freeChunk = NULL; + + if( index > 0 ) + { + // find FREE chunk and merge possible multiple FREE chunks to one chunk + freeChunk = this->mergeFreeChunks( srcTree, index-1 ); + } + + if( freeChunk != NULL ) + { + // update running index + index = ::getIndex( srcTree, *chunk ); + + // subtract size of FREE chunk from offset adjust value + offsetAdjust -= static_cast( freeChunk->getPadSize(true) ); + + // remove FREE chunk from the tree + srcTree.removeChildAt( index-1 ); + delete freeChunk; + + // update running index because one chunk was removed from the tree + index--; + } + + // + // offset needs to be adjusted + // + if( offsetAdjust != 0 ) + { + chunk->setOffset( chunk->getOffset() + offsetAdjust ); + } + + // + // update adjust value if the size of the chunk has changed + // (and so the offsets of following chunks needs to be adjusted) + // + offsetAdjust += chunk->getPadSize() - chunk->getOriginalPadSize(); + } + else if( this->isFREEChunk( *chunk ) && offsetAdjust != 0 ) + { + // + // chunk is a FREE chunk, just remove it + // + + // merge FREE chunks + chunk = this->mergeFreeChunks( srcTree, index ); + + // update running index (in case multiple FREE chunk were merged) + index = ::getIndex( srcTree, *chunk ); + + // update adjust value about the total size of the FREE chunk + offsetAdjust -= static_cast( chunk->getPadSize(true) ); + + // remove FREE chunk from tree + srcTree.removeChildAt( index ); + delete chunk; + + // update running index + index--; + } + else if( offsetAdjust != 0 ) + { + // + // the current chunk can't be moved, + // so we can't adjust the offset of this chunk + // + XMP_Uns64 gap = 0; + + if( offsetAdjust > 0 ) + { + // + // One or more foregoing chunks grew in their seize and so + // the offset of following chunks needs to be adjusted. + // But since the current chunk can't be moved one or more previous + // chunks are now overlapping over the current chunk. + // + // So now one or more of the previous chunks needs to be removed + // (moved to the destTree) so that the offset value of the current + // chunk can stay where it is. + // A possible gap will be filled with a FREE chunk. + // + + Chunk* preChunk = NULL; + + // + // count Chunks that needs to be moved + // + XMP_Validate( index-1 >= 0, "There shouldn't be an offset adjust value for the first chunk", kXMPErr_InternalFailure ); + + XMP_Int32 preIndex = index; + XMP_Uns64 preSize = 0; + + do + { + preIndex--; + preChunk = srcTree.getChildAt( preIndex ); + + XMP_Validate( this->isMovable( *preChunk ) || this->isFREEChunk( *preChunk ), "Movable or FREE chunk expected", kXMPErr_InternalFailure ); + + preSize += preChunk->getPadSize( true ); + + } while( static_cast( preSize ) < offsetAdjust && preIndex > 0 ); + + // + // move chunks + // + for( XMP_Uns32 rem=preIndex; rem( index ); rem++ ) + { + // always fetch chunk at the first index of the range because + // these chunks are removed from the tree + preChunk = srcTree.removeChildAt( preIndex ); + + if( this->isFREEChunk( *preChunk ) ) + { + delete preChunk; + } + else + { + destTree.appendChild( preChunk, false ); + } + } + + // update current index + index = ::getIndex( srcTree, *chunk ); + + // + // calculate size of gap + // + XMP_Uns64 curOffset = chunk->getOffset(); + XMP_Uns64 preOffset = Chunk::HEADER_SIZE + Chunk::TYPE_SIZE; + + if( index > 0 ) + { + preChunk = srcTree.getChildAt( index-1 ); + preOffset = preChunk->getOffset() + preChunk->getPadSize( true ); + } + + gap = curOffset - preOffset; + } + else if( offsetAdjust < 0 ) + { + // + // There is a gap between the previous chunk and the current one. + // Fill the gap with a FREE chunk. + // + gap = offsetAdjust * (-1); + } + + // + // if there is a gap we need to fill it with a FREE chunk + // + if( gap > 0 ) + { + // + // The gap must be at least as big as the minimum size of FREE chunks. + // If that's not the case we need to move more chunks to expand + // the gap. + // + while( gap < this->getMinFREESize() ) + { + XMP_Validate( index > 0, "Not enough space to insert FREE chunk", kXMPErr_Unimplemented ); + + Chunk* preChunk = srcTree.removeChildAt( index-1 ); + gap += preChunk->getPadSize(true); + destTree.appendChild( preChunk, false ); + + // update running index + index--; + } + + // + // Fill the gap with a FREE chunk. + // + Chunk* freeChunk = this->createFREE( gap ); + srcTree.insertChildAt( index, freeChunk ); + freeChunk->setAsNew(); + + // update running index + index++; + } + + // reset adjust value + offsetAdjust = 0; + } + } +} + +//----------------------------------------------------------------------------- +// +// IChunkBehavior::arrangeChunksInTree(...) +// +// Purpose: This method proceeds the list of Chunks of the source tree in the +// passed range and looks for FREE chunks in the destination tree to +// move the source chunks to. +// Source tree and destination tree could be one and the same but it's +// not required. If both trees are the same then it's not allowed to +// cross source and destination ranges. +// +//----------------------------------------------------------------------------- + +void IChunkBehavior::arrangeChunksInTree( IChunkContainer& srcTree, IChunkContainer& destTree ) +{ + XMP_Validate( &srcTree != &destTree, "Source and destination tree mustn't be the same", kXMPErr_InternalFailure ); + + if( srcTree.numChildren() > 0 ) + { + // + // for all chunks that were moved to the end try to find a FREE chunk for them + // + for( XMP_Int32 index=srcTree.numChildren()-1; index>=0; index-- ) + { + Chunk* chunk = srcTree.getChildAt(index); + + // + // find a FREE chunk where the chunk would fit in + // + XMP_Int32 freeIndex = this->findFREEChunk( destTree, chunk->getSize(true) ); + + if( freeIndex >= 0 ) + { + Chunk* freeChunk = destTree.getChildAt( freeIndex ); + + // remove chunk from source tree + srcTree.removeChildAt( index ); + + // insert chunk at new location + destTree.insertChildAt( freeIndex, chunk ); + + // remove the FREE chunk + destTree.removeChildAt( freeIndex+1 ); + + // + // if the size of the FREE chunk is larger than the size of the chunk then fill + // the gap with a new FREE chunk (the method findFREEChunk takes care that the + // remaining space is large enough for a new FREE chunk, but findFREEChunk also + // takes account of possible pad bytes in its calculations! Therefore following + // calculations have to take account of a possible pad byte as well!) + // + if( freeChunk->getPadSize( true ) > chunk->getPadSize( true ) ) + { + Chunk* remainFreeChunk = this->createFREE( freeChunk->getPadSize( true ) - chunk->getPadSize( true ) ); + destTree.insertChildAt( freeIndex+1, remainFreeChunk ); + remainFreeChunk->setAsNew(); + } + + delete freeChunk; + } + } + } +} + +//----------------------------------------------------------------------------- +// +// IChunkBehavior::validateOffsets(...) +// +// Purpose: Fix recursively the offset values of all modified chunks. +// At the same time the method checks the offset value of all not +// modified chunks and throws an exception if there is any discrepance +// with the calculated offset. +// +//----------------------------------------------------------------------------- + +void IChunkBehavior::validateOffsets( IChunkContainer& tree, XMP_Uns64 startOffset /*= 0*/ ) +{ + XMP_Uns64 offset = startOffset; + + // + // for all children of the tree + // + for( XMP_Uns32 i=0; igetOffset() == offset, "Invalid offset", kXMPErr_InternalFailure ); + + if( !this->isMovable( *chunk ) ) + { + XMP_Validate( chunk->getOffset() == chunk->getOriginalOffset(), "Invalid offset non-modified chunk", kXMPErr_InternalFailure ); + } + + // go through children + if( chunk->getChunkMode() == CHUNK_NODE ) + { + this->validateOffsets( *chunk, offset + Chunk::HEADER_SIZE + Chunk::TYPE_SIZE ); + } + + // calculate next offset + offset += chunk->getPadSize(true); + } +} + +//----------------------------------------------------------------------------- +// +// IChunkBehavior::getFreeSpace(...) +// +// Purpose: Retrieve the free space at the passed position in the child list of +// the parent tree. If there's a FREE chunk then return it. +// +//----------------------------------------------------------------------------- + +Chunk* IChunkBehavior::getFreeSpace( XMP_Int64& outFreeBytes, const IChunkContainer& tree, XMP_Uns32 index ) const +{ + // validate index + XMP_Validate( index < tree.numChildren(), "Invalid index", kXMPErr_InternalFailure ); + + Chunk* ret = NULL; + + Chunk* chunk = tree.getChildAt( index ); + + if( this->isFREEChunk( *chunk ) ) + { + // + // chunk is a FREE chunk + // + outFreeBytes = chunk->getSize( true ); + ret = chunk; + } + else if( chunk->getChunkMode() != CHUNK_UNKNOWN && chunk->hasChanged() ) + { + // + // chunk is NOT a FREE chunk but the size of this chunk has changed + // + outFreeBytes = chunk->getOriginalSize() - chunk->getSize(); + } + + return ret; +} + +//----------------------------------------------------------------------------- +// +// IChunkBehavior::mergeFreeChunks(...) +// +// Purpose: Try to merge existing FREE chunks at the passed position in the +// child list of the passed parent tree. The algorithm looks at the +// position, before the position and after the position. +// +//----------------------------------------------------------------------------- + +Chunk* IChunkBehavior::mergeFreeChunks( IChunkContainer& tree, XMP_Uns32 index ) +{ + // validate index + XMP_Validate( index < tree.numChildren(), "Invalid index", kXMPErr_InternalFailure ); + + Chunk* ret = NULL; + + Chunk* chunk = tree.getChildAt( index ); + + // + // is chunk a FREE chunk + // + if( this->isFREEChunk( *chunk ) ) + { + XMP_Uns32 indexStart = index; + XMP_Uns32 indexEnd = index; + + XMP_Uns64 size = chunk->getPadSize( true ); + + // + // find FREE chunks before start chunk + // + if( index > 0 ) + { + XMP_Int32 i = XMP_Int32( index-1 ); + Chunk* c = NULL; + + do + { + c = tree.getChildAt(i); + + if( this->isFREEChunk( *c ) ) + { + size += c->getPadSize( true ); + indexStart = XMP_Uns32(i); + i--; + } + else + { + c = NULL; + } + + } while( i >= 0 && c != NULL ); + } + + // + // find FREE chunks after start chunk + // + if( index+1 < tree.numChildren() ) + { + XMP_Uns32 i = index+1; + Chunk* c = NULL; + + do + { + c = tree.getChildAt(i); + + if( this->isFREEChunk( *c ) ) + { + size += c->getPadSize( true ); + indexEnd = i; + i++; + } + else + { + c = NULL; + } + + } while( i < tree.numChildren() && c != NULL ); + } + + if( indexStart < indexEnd ) + { + // + // more than one FREE chunks, so merge them + // + for( XMP_Uns32 i=indexStart; i<=indexEnd; i++ ) + { + Chunk* f = tree.getChildAt( indexStart ); + tree.removeChildAt( indexStart ); + delete f; + } + + ret = this->createFREE( size ); + tree.insertChildAt( indexStart, ret ); + ret->setAsNew(); + } + else + { + // + // one single FREE chunk + // + ret = chunk; + } + } + + return ret; +} + +//----------------------------------------------------------------------------- +// +// IChunkBehavior::findFREEChunk(...) +// +// Purpose: Find a FREE chunk with the passed total size (including header). +// If the FREE chunk is found then take care of the fact that is has +// to be that large (or larger) then the minimum size of a FREE chunk. +// The method takes also into account that the passed size probably +// not includes a pad byte +// +//----------------------------------------------------------------------------- + +XMP_Int32 IChunkBehavior::findFREEChunk( const IChunkContainer& tree, XMP_Uns64 requiredSize /*including header*/ ) +{ + XMP_Int32 ret = -1; + + for( XMP_Uns32 i=0; iisFREEChunk( *chunk ) && + ( chunk->getPadSize( true ) == requiredSizePad || + chunk->getPadSize( true ) >= requiredSizePad + getMinFREESize() ) ) + { + ret = i; + break; + } + } + + return ret; +} + +//----------------------------------------------------------------------------- +// +// IChunkBehavior::moveChunks(...) +// +// Purpose: Move a range of chunks from one container to another. +// +//----------------------------------------------------------------------------- + +void IChunkBehavior::moveChunks( IChunkContainer& srcTree, IChunkContainer& destTree, XMP_Uns32 start ) +{ + XMP_Validate( &srcTree != &destTree, "Source tree and destination tree shouldn't be the same", kXMPErr_InternalFailure ); + + XMP_Uns32 end = srcTree.numChildren(); + + for( XMP_Uns32 index=start; indexisFREEChunk( chunk ) && mMovablePaths != NULL ) + { + ChunkPath path( chunk.getIdentifier() ); + Chunk* parent = chunk.getParent(); + + while( parent != NULL && parent->getID() != kChunk_NONE ) + { + path.insert( parent->getIdentifier() ); + parent = parent->getParent(); + } + + for( std::vector::iterator iter=mMovablePaths->begin(); iter!=mMovablePaths->end() && !ret; iter++ ) + { + ret = ( iter->match( path ) == ChunkPath::kFullMatch ); + } + } + + return ret; +} diff --git a/core/libs/dngwriter/extra/xmp_sdk/XMPFiles/source/FormatSupport/IFF/IChunkBehavior.h b/core/libs/dngwriter/extra/xmp_sdk/XMPFiles/source/FormatSupport/IFF/IChunkBehavior.h new file mode 100644 index 0000000000..418afb3cd4 --- /dev/null +++ b/core/libs/dngwriter/extra/xmp_sdk/XMPFiles/source/FormatSupport/IFF/IChunkBehavior.h @@ -0,0 +1,239 @@ +// ================================================================================================= +// ADOBE SYSTEMS INCORPORATED +// Copyright 2010 Adobe Systems Incorporated +// All Rights Reserved +// +// NOTICE: Adobe permits you to use, modify, and distribute this file in accordance with the terms +// of the Adobe license agreement accompanying it. +// ================================================================================================= + +#ifndef _IChunkBehavior_h_ +#define _IChunkBehavior_h_ + +#include "public/include/XMP_Environment.h" // ! This must be the first include. +#include "public/include/XMP_Const.h" +#include "public/include/XMP_IO.hpp" +#include + +namespace IFF_RIFF +{ + +/** + The IChunkBehavior is an interface that provides access to algorithm + for the read and write process of IFF/RIFF formated streams. + A file format specific instance based on this interface gets injected into + the class ChunkController and offers format specific algorithm wherever + the processing of a certain file format differs from the general specification + of RIFF/IFF. + That is e.g. the RF64 format where it is possible that the size value of the + top level chunk doesn't represent the real size. Or AVI, where are special rules + if the size of a chunk exceed the 4GB border. +*/ + +class IChunkContainer; +class Chunk; +struct ChunkIdentifier; +class ChunkPath; + +class IChunkBehavior +{ +public: + IChunkBehavior() : mMovablePaths(NULL) {} + virtual ~IChunkBehavior() {}; + + /** + * Set list of chunk paths of chunks that might be moved within the hierarchy + */ + inline void setMovablePaths( std::vector* paths ) { mMovablePaths = paths; } + + /** + Validate the passed in size value, identify the valid size if the passed in isn't valid + and return the valid size. + throw an exception if the passed in size isn't valid and there's no way to identify a + valid size. + + @param size Size value + @param id Identifier of chunk + @param tree Chunk tree + @param stream Stream handle + + @return Valid size value. + */ + virtual XMP_Uns64 getRealSize( const XMP_Uns64 size, const ChunkIdentifier& id, IChunkContainer& tree, XMP_IO* stream ) = 0; + + /** + Return the maximum size of a single chunk, i.e. the maximum size of a top-level chunk. + + @return Maximum size + */ + virtual XMP_Uns64 getMaxChunkSize() const = 0; + + /** + Return true if the passed identifier is valid for top-level chunks of a certain format. + + @param id Chunk identifier + @param chunkNo order number of top-level chunk + @return true, if passed id is a valid top-level chunk + */ + virtual bool isValidTopLevelChunk( const ChunkIdentifier& id, XMP_Uns32 chunkNo ) = 0; + + /** + Fix the hierarchy of chunks depending ones based on size changes of one or more chunks + and second based on format specific rules. + Throw an exception if the hierarchy can't be fixed. + + @param tree Vector of root chunks. + */ + virtual void fixHierarchy( IChunkContainer& tree ) = 0; + + /** + Insert a new chunk into the hierarchy of chunks. The behavior needs to decide the position + of the new chunk and has to do the insertion. + + @param tree Chunk tree + @param chunk New chunk + */ + virtual void insertChunk( IChunkContainer& tree, Chunk& chunk ) = 0; + + /** + Remove the chunk described by the passed ChunkPath. + + @param tree Chunk tree + @param path Path to the chunk that needs to be removed + + @return true if the chunk was removed and need to be deleted + */ + virtual bool removeChunk( IChunkContainer& tree, Chunk& chunk ) = 0; + +protected: + /** + Create a FREE chunk. + If the chunkSize is smaller than the header+type - size then create an annotation chunk. + If the passed size is odd, then add a pad byte. + + @param chunkSize Total size including header + @return New FREE chunk + */ + virtual Chunk* createFREE( XMP_Uns64 chunkSize ) = 0; + + /** + Check if the passed chunk is a FREE chunk. + (Could be also a small annotation chunk with zero bytes in its data) + + @param chunk A chunk + + @return true if the passed chunk is a FREE chunk + */ + virtual XMP_Bool isFREEChunk( const Chunk& chunk ) const = 0; + + /** + Return the minimum size of a FREE chunk + */ + virtual XMP_Uns64 getMinFREESize( ) const + = 0; +protected: + /************************************************************************/ + /* END of Interface. The following are helper functions for all derived */ + /* Behavior Classes */ + /************************************************************************/ + + /** + Find a FREE chunk with the passed total size (including header). If the FREE chunk is found then + take care of the fact that is has to be that large (or larger) then the minimum size of a FREE chunk. + The method takes also into account that the passed size probably not includes a pad byte + + @param tree Parent tree + @param requiredSize Required total size (including header) + + @return Index of found FREE chunk + */ + XMP_Int32 findFREEChunk( const IChunkContainer& tree, XMP_Uns64 requiredSize ); + + /** + May we move a chunk of passed id/type + + @param identifier id and type of chunk + @return true if such a chunk might be moved within the tree + */ + bool isMovable( const Chunk& chunk ) const; + + /** + Validate recursively the offset values of all chunks. + Throws an exception if there is any discrepancy with the calculated offset. + + @param tree (Sub-)tree of chunks + @param startOffset First offset in the (sub-)tree + */ + void validateOffsets( IChunkContainer& tree, XMP_Uns64 startOffset = 0 ); + + /** + Retrieve the free space at the passed position in the child list of the parent tree. + If there's a FREE chunk then return it. + + @param outFreeBytes On return it takes the number of free bytes + @param tree Parent tree + @param index Position in the child list of the parent tree + + @return FREE chunk if available + */ + Chunk* getFreeSpace( XMP_Int64& outFreeBytes, const IChunkContainer& tree, XMP_Uns32 index ) const ; + + /** + Try to arrange all chunks of the source tree at their current location. + The method looks for FREE chunk around or for size changes of the chunks around and try that space. + If a chunk can't be arrange at its location it is moved to the end of the destination tree. + + @param srcTree Tree that consists of the chunks that needs to be arranged + @param destTree Tree where chunks are added to if they can't be arranged + + @return Index of last proceeded chunk + */ + void arrangeChunksInPlace( IChunkContainer& srcTree, IChunkContainer& destTree ); + + /** + This method proceeds the list of Chunks of the source tree in the passed range and looks for FREE chunks + in the destination tree to move the source chunks to. + Source tree and destination tree could be one and the same but it's not required. If both trees are the + same then it's not allowed to cross source and destination ranges. + + @param srcTree Tree that consists of the chunks that needs to be arranged + @param destTree Tree where the method looks for FREE chunks + @param srcStart Start index within the source tree + @param srcEnd End index within the source tree (if booth, srcStart and srcEnd are zero then the complete list + of the source tree is proceeded) + @param destStart Start index within the destination tree + @param destEnd End index within the destination tree (if booth, destStart and destEnd are zero then the complete list + of the destination tree is proceeded) + */ + void arrangeChunksInTree( IChunkContainer& srcTree, IChunkContainer& destTree ); + + /** + Try to merge existing FREE chunks at the passed position in the child list + of the passed parent tree. + The algorithm looks at the position, before the position and after the position. + + @param tree Parent tree + @param index Position in the child list of the parent tree + + @return FREE chunk if available at the passed position (in case of a merge + the merged FREE chunk) + */ + Chunk* mergeFreeChunks( IChunkContainer& tree, XMP_Uns32 index ); + + /** + Move a range of chunks from one container to another starting at the start index up to the + end of the srcTree. + + @param srcTree Source container + @param destTree Destination container + @param start Start index of source container + */ + void moveChunks( IChunkContainer& srcTree, IChunkContainer& destTree, XMP_Uns32 start ); + +private: + std::vector* mMovablePaths; +}; + +} // IChunkBehavior + +#endif diff --git a/core/libs/dngwriter/extra/xmp_sdk/XMPFiles/source/FormatSupport/IFF/IChunkContainer.h b/core/libs/dngwriter/extra/xmp_sdk/XMPFiles/source/FormatSupport/IFF/IChunkContainer.h new file mode 100644 index 0000000000..04ad425b56 --- /dev/null +++ b/core/libs/dngwriter/extra/xmp_sdk/XMPFiles/source/FormatSupport/IFF/IChunkContainer.h @@ -0,0 +1,87 @@ +// ================================================================================================= +// ADOBE SYSTEMS INCORPORATED +// Copyright 2010 Adobe Systems Incorporated +// All Rights Reserved +// +// NOTICE: Adobe permits you to use, modify, and distribute this file in accordance with the terms +// of the Adobe license agreement accompanying it. +// ================================================================================================= + +#ifndef _IChunkContainer_h_ +#define _IChunkContainer_h_ + +#include "public/include/XMP_Environment.h" // ! This must be the first include. + +namespace IFF_RIFF +{ + +/** + The interface IChunkContainer defines the access to child chunks of + an existing chunk. +*/ + +class Chunk; + +class IChunkContainer +{ +public: + virtual ~IChunkContainer() {}; + + /** + * @return Returns the number children chunks. + */ + virtual XMP_Uns32 numChildren() const = 0; + + /** + * Returns a child node. + * + * @param pos position of the child node to return + * @return Returns the child node at the given position. + */ + virtual Chunk* getChildAt( XMP_Uns32 pos ) const = 0; + + /** + * Appends a child node at the end of the children list. + * + * @param node the new node + * @param adjustSizes adjust size&offset of chunk and parents + * @return Returns the added node. + */ + virtual void appendChild( Chunk* node, XMP_Bool adjustSizes = true ) = 0; + + /** + * Inserts a child node at a certain position. + * + * @param pos position in the children list to add the new node + * @param node the new node + * @return Returns the added node. + */ + virtual void insertChildAt( XMP_Uns32 pos, Chunk* node ) = 0; + + /** + * Removes a child node at a given position. + * + * @param pos position of the node to delete in the children list + * + * @return The removed chunk + */ + virtual Chunk* removeChildAt( XMP_Uns32 pos ) = 0; + + /** + * Remove child at the passed position and insert the new chunk + * + * @param pos Position of chunk that will be replaced + * @param chunk New chunk + * + * @return Replaced chunk + */ + virtual Chunk* replaceChildAt( XMP_Uns32 pos, Chunk* node ) = 0; + + /** creates a string representation of the chunk and its children. + */ + virtual std::string toString( std::string tab = std::string() , XMP_Bool showOriginal = false ) = 0; +}; + +} + +#endif diff --git a/core/libs/dngwriter/extra/xmp_sdk/XMPFiles/source/FormatSupport/IFF/IChunkData.h b/core/libs/dngwriter/extra/xmp_sdk/XMPFiles/source/FormatSupport/IFF/IChunkData.h new file mode 100644 index 0000000000..62674e62e2 --- /dev/null +++ b/core/libs/dngwriter/extra/xmp_sdk/XMPFiles/source/FormatSupport/IFF/IChunkData.h @@ -0,0 +1,108 @@ +// ================================================================================================= +// ADOBE SYSTEMS INCORPORATED +// Copyright 2010 Adobe Systems Incorporated +// All Rights Reserved +// +// NOTICE: Adobe permits you to use, modify, and distribute this file in accordance with the terms +// of the Adobe license agreement accompanying it. +// ================================================================================================= + +#ifndef _IChunkData_h_ +#define _IChunkData_h_ + +#include "public/include/XMP_Environment.h" // ! This must be the first include. +#include "public/include/XMP_Const.h" +#include "source/Endian.h" +#include "XMPFiles/source/FormatSupport/IFF/ChunkPath.h" +#include + +namespace IFF_RIFF +{ + +/** + * This interface allow access to only the data part of a chunk. + */ +class IChunkData +{ + public: + + virtual ~IChunkData() {}; + + /** + * Get the chunk ID + * + * @return Return the ID, 0 if the chunk does not have an ID. + */ + virtual XMP_Uns32 getID() const = 0; + + /** + * Get the chunk type (if available) + * (the first four data bytes of the chunk could be a chunk type) + * + * @return Return the type, kType_NONE if the chunk does not contain data. + */ + virtual XMP_Uns32 getType() const = 0; + + /** + * Get the chunk identifier [id and type] + * + * @return Return the identifier + */ + virtual const ChunkIdentifier& getIdentifier() const = 0; + + /** + * Access the data of the chunk. + * + * @param data OUT pointer to the byte array + * @return size of the data block, 0 if no data is available + */ + virtual XMP_Uns64 getData( const XMP_Uns8** data ) const = 0; + + /** + * Set new data for the chunk. + * Will delete an existing internal buffer and recreate a new one + * and copy the given data into that new buffer. + * + * @param data pointer to the data to put into the chunk + * @param size Size of the data block + */ + virtual void setData( const XMP_Uns8* const data, XMP_Uns64 size, XMP_Bool writeType = false ) = 0; + + /** + * Returns the current size of the Chunk without pad byte. + * + * @param includeHeader if set, the returned size will be the whole chunk size including the header + * @return either size of the data block of the chunk or size of the whole chunk + */ + virtual XMP_Uns64 getSize( bool includeHeader = false ) const = 0; + + + /* The following methods are getter/setter for certain data types. + They always take care of little-endian/big-endian issues. + The offset starts at the data area of the Chunk. */ + + virtual XMP_Uns32 getUns32( XMP_Uns64 offset=0 ) const = 0; + virtual void setUns32( XMP_Uns32 value, XMP_Uns64 offset=0 ) = 0; + + virtual XMP_Uns64 getUns64( XMP_Uns64 offset=0 ) const = 0; + virtual void setUns64( XMP_Uns64 value, XMP_Uns64 offset=0 ) = 0; + + virtual XMP_Int32 getInt32( XMP_Uns64 offset=0 ) const = 0; + virtual void setInt32( XMP_Int32 value, XMP_Uns64 offset=0 ) = 0; + + virtual XMP_Int64 getInt64( XMP_Uns64 offset=0 ) const = 0; + virtual void setInt64( XMP_Int64 value, XMP_Uns64 offset=0 ) = 0; + + virtual std::string getString( XMP_Uns64 size = 0, XMP_Uns64 offset=0 ) const = 0; + virtual void setString( std::string value, XMP_Uns64 offset=0 ) = 0; + + /** + * Creates a string representation of the chunk and its children. + */ + virtual std::string toString( std::string tabs = std::string() , XMP_Bool showOriginal = false ) = 0; + +}; // IChunkData + +} // namespace + +#endif diff --git a/core/libs/dngwriter/extra/xmp_sdk/XMPFiles/source/FormatSupport/IPTC_Support.cpp b/core/libs/dngwriter/extra/xmp_sdk/XMPFiles/source/FormatSupport/IPTC_Support.cpp new file mode 100644 index 0000000000..3a3421ca0c --- /dev/null +++ b/core/libs/dngwriter/extra/xmp_sdk/XMPFiles/source/FormatSupport/IPTC_Support.cpp @@ -0,0 +1,772 @@ +// ================================================================================================= +// ADOBE SYSTEMS INCORPORATED +// Copyright 2006 Adobe Systems Incorporated +// All Rights Reserved +// +// NOTICE: Adobe permits you to use, modify, and distribute this file in accordance with the terms +// of the Adobe license agreement accompanying it. +// ================================================================================================= + +#include "public/include/XMP_Environment.h" // ! XMP_Environment.h must be the first included header. +#include "public/include/XMP_Const.h" + +#include "XMPFiles/source/FormatSupport/IPTC_Support.hpp" +#include "source/EndianUtils.hpp" +#include "XMPFiles/source/FormatSupport/Reconcile_Impl.hpp" +#include "source/XIO.hpp" + +// ================================================================================================= +/// \file IPTC_Support.cpp +/// \brief XMPFiles support for IPTC (IIM) DataSets. +/// +// ================================================================================================= + +enum { kUTF8_IncomingMode = 0, kUTF8_LosslessMode = 1, kUTF8_AlwaysMode = 2 }; +#ifndef kUTF8_Mode + #define kUTF8_Mode kUTF8_AlwaysMode +#endif + +const DataSetCharacteristics kKnownDataSets[] = + { { kIPTC_ObjectType, kIPTC_UnmappedText, 67, "", "" }, // Not mapped to XMP. + { kIPTC_IntellectualGenre, kIPTC_MapSpecial, 68, kXMP_NS_IPTCCore, "IntellectualGenre" }, // Only the name part is in the XMP. + { kIPTC_Title, kIPTC_MapLangAlt, 64, kXMP_NS_DC, "title" }, + { kIPTC_EditStatus, kIPTC_UnmappedText, 64, "", "" }, // Not mapped to XMP. + { kIPTC_EditorialUpdate, kIPTC_UnmappedText, 2, "", "" }, // Not mapped to XMP. + { kIPTC_Urgency, kIPTC_MapSimple, 1, kXMP_NS_Photoshop, "Urgency" }, + { kIPTC_SubjectCode, kIPTC_MapSpecial, 236, kXMP_NS_IPTCCore, "SubjectCode" }, // Only the reference number is in the XMP. + { kIPTC_Category, kIPTC_MapSimple, 3, kXMP_NS_Photoshop, "Category" }, + { kIPTC_SuppCategory, kIPTC_MapArray, 32, kXMP_NS_Photoshop, "SupplementalCategories" }, + { kIPTC_FixtureIdentifier, kIPTC_UnmappedText, 32, "", "" }, // Not mapped to XMP. + { kIPTC_Keyword, kIPTC_MapArray, 64, kXMP_NS_DC, "subject" }, + { kIPTC_ContentLocCode, kIPTC_UnmappedText, 3, "", "" }, // Not mapped to XMP. + { kIPTC_ContentLocName, kIPTC_UnmappedText, 64, "", "" }, // Not mapped to XMP. + { kIPTC_ReleaseDate, kIPTC_UnmappedText, 8, "", "" }, // Not mapped to XMP. + { kIPTC_ReleaseTime, kIPTC_UnmappedText, 11, "", "" }, // Not mapped to XMP. + { kIPTC_ExpDate, kIPTC_UnmappedText, 8, "", "" }, // Not mapped to XMP. + { kIPTC_ExpTime, kIPTC_UnmappedText, 11, "", "" }, // Not mapped to XMP. + { kIPTC_Instructions, kIPTC_MapSimple, 256, kXMP_NS_Photoshop, "Instructions" }, + { kIPTC_ActionAdvised, kIPTC_UnmappedText, 2, "", "" }, // Not mapped to XMP. + { kIPTC_RefService, kIPTC_UnmappedText, 10, "", "" }, // Not mapped to XMP. ! Interleave 2:45, 2:47, 2:50! + { kIPTC_RefDate, kIPTC_UnmappedText, 8, "", "" }, // Not mapped to XMP. ! Interleave 2:45, 2:47, 2:50! + { kIPTC_RefNumber, kIPTC_UnmappedText, 8, "", "" }, // Not mapped to XMP. ! Interleave 2:45, 2:47, 2:50! + { kIPTC_DateCreated, kIPTC_MapSpecial, 8, kXMP_NS_Photoshop, "DateCreated" }, // ! Reformatted date. Combined with 2:60, TimeCreated. + { kIPTC_TimeCreated, kIPTC_UnmappedText, 11, "", "" }, // ! Combined with 2:55, DateCreated. + { kIPTC_DigitalCreateDate, kIPTC_Map3Way, 8, "", "" }, // ! 3 way Exif-IPTC-XMP date/time set. Combined with 2:63, DigitalCreateTime. + { kIPTC_DigitalCreateTime, kIPTC_UnmappedText, 11, "", "" }, // ! Combined with 2:62, DigitalCreateDate. + { kIPTC_OriginProgram, kIPTC_UnmappedText, 32, "", "" }, // Not mapped to XMP. + { kIPTC_ProgramVersion, kIPTC_UnmappedText, 10, "", "" }, // Not mapped to XMP. + { kIPTC_ObjectCycle, kIPTC_UnmappedText, 1, "", "" }, // Not mapped to XMP. + { kIPTC_Creator, kIPTC_Map3Way, 32, "", "" }, // ! In the 3 way Exif-IPTC-XMP set. + { kIPTC_CreatorJobtitle, kIPTC_MapSimple, 32, kXMP_NS_Photoshop, "AuthorsPosition" }, + { kIPTC_City, kIPTC_MapSimple, 32, kXMP_NS_Photoshop, "City" }, + { kIPTC_Location, kIPTC_MapSimple, 32, kXMP_NS_IPTCCore, "Location" }, + { kIPTC_State, kIPTC_MapSimple, 32, kXMP_NS_Photoshop, "State" }, + { kIPTC_CountryCode, kIPTC_MapSimple, 3, kXMP_NS_IPTCCore, "CountryCode" }, + { kIPTC_Country, kIPTC_MapSimple, 64, kXMP_NS_Photoshop, "Country" }, + { kIPTC_JobID, kIPTC_MapSimple, 32, kXMP_NS_Photoshop, "TransmissionReference" }, + { kIPTC_Headline, kIPTC_MapSimple, 256, kXMP_NS_Photoshop, "Headline" }, + { kIPTC_Provider, kIPTC_MapSimple, 32, kXMP_NS_Photoshop, "Credit" }, + { kIPTC_Source, kIPTC_MapSimple, 32, kXMP_NS_Photoshop, "Source" }, + { kIPTC_CopyrightNotice, kIPTC_Map3Way, 128, "", "" }, // ! In the 3 way Exif-IPTC-XMP set. + { kIPTC_Contact, kIPTC_UnmappedText, 128, "", "" }, // Not mapped to XMP. + { kIPTC_Description, kIPTC_Map3Way, 2000, "", "" }, // ! In the 3 way Exif-IPTC-XMP set. + { kIPTC_DescriptionWriter, kIPTC_MapSimple, 32, kXMP_NS_Photoshop, "CaptionWriter" }, + { kIPTC_RasterizedCaption, kIPTC_UnmappedBin, 7360, "", "" }, // Not mapped to XMP. ! Binary data! + { kIPTC_ImageType, kIPTC_UnmappedText, 2, "", "" }, // Not mapped to XMP. + { kIPTC_ImageOrientation, kIPTC_UnmappedText, 1, "", "" }, // Not mapped to XMP. + { kIPTC_LanguageID, kIPTC_UnmappedText, 3, "", "" }, // Not mapped to XMP. + { kIPTC_AudioType, kIPTC_UnmappedText, 2, "", "" }, // Not mapped to XMP. + { kIPTC_AudioSampleRate, kIPTC_UnmappedText, 6, "", "" }, // Not mapped to XMP. + { kIPTC_AudioSampleRes, kIPTC_UnmappedText, 2, "", "" }, // Not mapped to XMP. + { kIPTC_AudioDuration, kIPTC_UnmappedText, 6, "", "" }, // Not mapped to XMP. + { kIPTC_AudioOutcue, kIPTC_UnmappedText, 64, "", "" }, // Not mapped to XMP. + { kIPTC_PreviewFormat, kIPTC_UnmappedBin, 2, "", "" }, // Not mapped to XMP. ! Binary data! + { kIPTC_PreviewFormatVers, kIPTC_UnmappedBin, 2, "", "" }, // Not mapped to XMP. ! Binary data! + { kIPTC_PreviewData, kIPTC_UnmappedBin, 256000, "", "" }, // Not mapped to XMP. ! Binary data! + { 255, kIPTC_MapSpecial, 0, 0, 0 } }; // ! Must be last as a sentinel. + +// A combination of the IPTC "Subject Reference System Guidelines" and IIMv4.1 Appendix G. +const IntellectualGenreMapping kIntellectualGenreMappings[] = +{ { "001", "Current" }, + { "002", "Analysis" }, + { "003", "Archive material" }, + { "004", "Background" }, + { "005", "Feature" }, + { "006", "Forecast" }, + { "007", "History" }, + { "008", "Obituary" }, + { "009", "Opinion" }, + { "010", "Polls and surveys" }, + { "010", "Polls & Surveys" }, + { "011", "Profile" }, + { "012", "Results listings and statistics" }, + { "012", "Results Listings & Tables" }, + { "013", "Side bar and supporting information" }, + { "013", "Side bar & Supporting information" }, + { "014", "Summary" }, + { "015", "Transcript and verbatim" }, + { "015", "Transcript & Verbatim" }, + { "016", "Interview" }, + { "017", "From the scene" }, + { "017", "From the Scene" }, + { "018", "Retrospective" }, + { "019", "Synopsis" }, + { "019", "Statistics" }, + { "020", "Update" }, + { "021", "Wrapup" }, + { "021", "Wrap-up" }, + { "022", "Press release" }, + { "022", "Press Release" }, + { "023", "Quote" }, + { "024", "Press-digest" }, + { "025", "Review" }, + { "026", "Curtain raiser" }, + { "027", "Actuality" }, + { "028", "Question and answer" }, + { "029", "Music" }, + { "030", "Response to a question" }, + { "031", "Raw sound" }, + { "032", "Scener" }, + { "033", "Text only" }, + { "034", "Voicer" }, + { "035", "Fixture" }, + { 0, 0 } }; // ! Must be last as a sentinel. + +// ================================================================================================= +// FindKnownDataSet +// ================ + +static const DataSetCharacteristics* FindKnownDataSet ( XMP_Uns8 dsNum ) +{ + size_t i = 0; + + while ( kKnownDataSets[i].dsNum < dsNum ) ++i; // The list is short enough for a linear search. + + if ( kKnownDataSets[i].dsNum != dsNum ) return 0; + return &kKnownDataSets[i]; + +} // FindKnownDataSet + +// ================================================================================================= +// IPTC_Manager::ParseMemoryDataSets +// ================================= +// +// Parse the IIM block. All datasets are put into the map, although we only really care about 1:90 +// and the known 2:xx ones. This approach is tolerant of ill-formed IIM where the datasets are not +// sorted by ascending record number. + +void IPTC_Manager::ParseMemoryDataSets ( const void* data, XMP_Uns32 length, bool copyData /* = true */ ) +{ + // Get rid of any existing data. + + DataSetMap::iterator dsPos = this->dataSets.begin(); + DataSetMap::iterator dsEnd = this->dataSets.end(); + + for ( ; dsPos != dsEnd; ++dsPos ) this->DisposeLooseValue ( dsPos->second ); + + this->dataSets.clear(); + + if ( this->ownedContent ) free ( this->iptcContent ); + this->ownedContent = false; // Set to true later if the content is copied. + this->iptcContent = 0; + this->iptcLength = 0; + + this->changed = false; + + if ( length == 0 ) return; + if ( (data == 0) || (*((XMP_Uns8*)data) != 0x1C) ) XMP_Throw ( "Not valid IPTC, no leading 0x1C", kXMPErr_BadIPTC ); + + // Allocate space for the full in-memory data and copy it. + + if ( length > 10*1024*1024 ) XMP_Throw ( "Outrageous length for memory-based IPTC", kXMPErr_BadIPTC ); + this->iptcLength = length; + + if ( ! copyData ) { + this->iptcContent = (XMP_Uns8*)data; + } else { + this->iptcContent = (XMP_Uns8*) malloc(length); + if ( this->iptcContent == 0 ) XMP_Throw ( "Out of memory", kXMPErr_NoMemory ); + memcpy ( this->iptcContent, data, length ); // AUDIT: Safe, malloc'ed length bytes above. + this->ownedContent = true; + } + + // Build the map of the DataSets. The records should be in ascending order, but we tolerate out + // of order IIM produced by some unknown apps. The DataSets in a record can be in any order. + // There are no record markers, just DataSets, so the ordering is really just clumping of + // DataSets by record. A normal DataSet has a 5 byte header followed by the value. An extended + // DataSet has a special length in the header, a variable sized value length, and the value. + // + // Normal DataSet + // 0 uint8 0x1C + // 1 uint8 record number + // 2 uint8 DataSet number + // 3 uint16 big endian value size, 0..32767, larger means extended DataSet + // + // In an extended DataSet the extended length size is the low 15 bits of the standard size. The + // extended length follows as a big endian unsigned number. The IPTC does not specify, but we + // require the extended length size to be in the range 1..4. It should only be 3 or 4, we allow + // the degenerate cases. + + XMP_Uns8* iptcPtr = this->iptcContent; + XMP_Uns8* iptcEnd = iptcPtr + length; + XMP_Uns8* iptcLimit = iptcEnd - kMinDataSetSize; + XMP_Uns32 dsLen; // ! The large form can have values up to 4GB in length. + + this->utf8Encoding = false; + + for ( ; iptcPtr <= iptcLimit; iptcPtr += dsLen ) { + + // iptcLimit - last possible DataSet, 5 bytes before IIM block end + + // iptcPtr - working pointer to the current byte of interest + // dsPtr - pointer to the current DataSet's header + // dsLen - value length, does not include extended size bytes + + XMP_Uns8* dsPtr = iptcPtr; + XMP_Uns8 oneC = *iptcPtr; + XMP_Uns8 recNum = *(iptcPtr+1); + XMP_Uns8 dsNum = *(iptcPtr+2); + + if ( oneC != 0x1C ) break; // No more DataSets. + + dsLen = GetUns16BE ( iptcPtr+3 ); // ! Compute dsLen before any "continue", needed for loop increment! + iptcPtr += 5; // Advance to the data (or extended length). + + if ( (dsLen & 0x8000) != 0 ) { + XMP_Assert ( dsLen <= 0xFFFF ); + XMP_Uns32 lenLen = dsLen & 0x7FFF; + if ( (lenLen == 0) || (lenLen > 4) ) break; // Bad DataSet, can't find the next so quit. + if ( iptcPtr > (iptcEnd - lenLen) ) break; // Bad final DataSet. Throw instead? + dsLen = 0; + for ( XMP_Uns16 i = 0; i < lenLen; ++i, ++iptcPtr ) { + dsLen = (dsLen << 8) + *iptcPtr; + } + } + + if ( iptcPtr > (iptcEnd - dsLen) ) break; // Bad final DataSet. Throw instead? + + // Make a special check for 1:90 denoting UTF-8 text. + if ( (recNum == 1) && (dsNum == 90) ) { + if ( (dsLen == 3) && (memcmp ( iptcPtr, "\x1B\x25\x47", 3 ) == 0) ) this->utf8Encoding = true; + } + + XMP_Uns16 mapID = recNum*1000 + dsNum; + DataSetInfo dsInfo( recNum, dsNum, dsLen ); + if ( dsLen != 0 ) + dsInfo.dataPtr = iptcPtr; + DataSetMap::iterator dsPos = this->dataSets.find ( mapID ); + + bool repeatable = false; + + const DataSetCharacteristics* knownDS = FindKnownDataSet ( dsNum ); + + if ( (knownDS == 0) || (knownDS->mapForm == kIPTC_MapArray) ) { + repeatable = true; // Allow repeats for unknown DataSets. + } else if ( (dsNum == kIPTC_Creator) || (dsNum == kIPTC_SubjectCode) ) { + repeatable = true; + } + + if ( repeatable || (dsPos == this->dataSets.end()) ) { + DataSetMap::value_type mapValue ( mapID, dsInfo ); + (void) this->dataSets.insert ( this->dataSets.upper_bound ( mapID ), mapValue ); + } else { + this->DisposeLooseValue ( dsPos->second ); + dsPos->second = dsInfo; // Keep the last copy of illegal repeats. + } + + } + +} // IPTC_Manager::ParseMemoryDataSets + +// ================================================================================================= +// IPTC_Manager::GetDataSet +// ======================== + +size_t IPTC_Manager::GetDataSet ( XMP_Uns8 dsNum, DataSetInfo* info, size_t which /* = 0 */ ) const +{ + + XMP_Uns16 mapID = 2000 + dsNum; // ! Only deal with 2:xx datasets. + DataSetMap::const_iterator dsPos = this->dataSets.lower_bound ( mapID ); + if ( (dsPos == this->dataSets.end()) || (dsPos->second.recNum != 2) || (dsNum != dsPos->second.dsNum) ) return 0; + + size_t dsCount = this->dataSets.count ( mapID ); + if ( which >= dsCount ) return 0; // Valid range for which is 0 .. count-1. + + if ( info != 0 ) { + for ( size_t i = 0; i < which; ++i ) ++dsPos; // Can't do "dsPos += which", no iter+int operator. + *info = dsPos->second; + } + + return dsCount; + +} // IPTC_Manager::GetDataSet + +// ================================================================================================= +// IPTC_Manager::GetDataSet_UTF8 +// ============================= + +size_t IPTC_Manager::GetDataSet_UTF8 ( XMP_Uns8 dsNum, std::string * utf8Str, size_t which /* = 0 */ ) const +{ + if ( utf8Str != 0 ) utf8Str->erase(); + + DataSetInfo dsInfo; + size_t dsCount = GetDataSet ( dsNum, &dsInfo, which ); + if ( dsCount == 0 ) return 0; + + if ( utf8Str != 0 ) { + if ( this->utf8Encoding ) { + utf8Str->assign ( (char*)dsInfo.dataPtr, dsInfo.dataLen ); + } else if ( ! ignoreLocalText ) { + ReconcileUtils::LocalToUTF8 ( dsInfo.dataPtr, dsInfo.dataLen, utf8Str ); + } else if ( ReconcileUtils::IsASCII ( dsInfo.dataPtr, dsInfo.dataLen ) ) { + utf8Str->assign ( (char*)dsInfo.dataPtr, dsInfo.dataLen ); + } + } + + return dsCount; + +} // IPTC_Manager::GetDataSet_UTF8 + +// ================================================================================================= +// IPTC_Manager::DisposeLooseValue +// =============================== +// +// Dispose of loose values from SetDataSet calls after the last UpdateMemoryDataSets. + +// ! Don't try to make the DataSetInfo struct be self-cleaning. It is a primary public type, returned +// ! from GetDataSet. Making it self-cleaning would get into nasty assignment and pointer ownership +// ! issues, far worse than doing this explicit cleanup. + +void IPTC_Manager::DisposeLooseValue ( DataSetInfo & dsInfo ) +{ + if ( dsInfo.dataLen == 0 || dsInfo.dataPtr == NULL ) return; + + XMP_Uns8* dataBegin = this->iptcContent; + XMP_Uns8* dataEnd = dataBegin + this->iptcLength; + + if ( ((XMP_Uns8*)dsInfo.dataPtr < dataBegin) || ((XMP_Uns8*)dsInfo.dataPtr >= dataEnd) ) { + free ( (void*) dsInfo.dataPtr ); + dsInfo.dataPtr = NULL; + } + +} // IPTC_Manager::DisposeLooseValue + +// ================================================================================================= +// IPTC_Manager::AppendDataSet +// +// ! Calling instance must make sure that dsPtr is large enough to hold data from dsInfo ! +// =========================== + +XMP_Uns8* IPTC_Manager::AppendDataSet ( XMP_Uns8* dsPtr, const DataSetInfo & dsInfo ) { + + dsPtr[0] = 0x1C; + dsPtr[1] = dsInfo.recNum; + dsPtr[2] = dsInfo.dsNum; + dsPtr += 3; + + XMP_Uns32 dsLen = dsInfo.dataLen; + if ( dsLen <= 0x7FFF ) { + PutUns16BE ( (XMP_Uns16)dsLen, dsPtr ); + dsPtr += 2; + } else { + PutUns16BE ( 0x8004, dsPtr ); + PutUns32BE ( dsLen, dsPtr+2 ); + dsPtr += 6; + } + // AUDIT: Calling instance must make sure that dsPtr is large enough. + // Currently this function is only called from UpdateMemoryDataSets where the size of dsPtr + // is calculated including all data sets in the list, so the dsInfo data should always fit. + memcpy ( dsPtr, dsInfo.dataPtr, dsLen ); + dsPtr += dsLen; + + return dsPtr; + +} // IPTC_Manager::AppendDataSet + +// ================================================================================================= +// ================================================================================================= + +// ================================================================================================= +// IPTC_Writer::~IPTC_Writer +// ========================= +// +// Dispose of loose values from SetDataSet calls after the last UpdateMemoryDataSets. + +IPTC_Writer::~IPTC_Writer() +{ + DataSetMap::iterator dsPos = this->dataSets.begin(); + DataSetMap::iterator dsEnd = this->dataSets.end(); + + for ( ; dsPos != dsEnd; ++dsPos ) this->DisposeLooseValue ( dsPos->second ); + +} // IPTC_Writer::~IPTC_Writer + +// ================================================================================================= +// IPTC_Writer::SetDataSet_UTF8 +// ============================ + +void IPTC_Writer::SetDataSet_UTF8 ( XMP_Uns8 dsNum, const void* utf8Ptr, XMP_Uns32 utf8Len, long which /* = -1 */ ) +{ + // No need to process if data is of zero length + if ( utf8Len == 0 ) + return; + + const DataSetCharacteristics* knownDS = FindKnownDataSet ( dsNum ); + if ( knownDS == 0 ) XMP_Throw ( "Can only set known IPTC DataSets", kXMPErr_InternalFailure ); + + // Decide which character encoding to use and get a temporary pointer to the value. + + XMP_Uns8 * tempPtr; + XMP_Uns32 dataLen; + std::string localStr; +#if XMP_MacBuild +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wunreachable-code" +#endif + if ( kUTF8_Mode == kUTF8_AlwaysMode ) { + + // Always use UTF-8. + if ( ! this->utf8Encoding ) this->ConvertToUTF8(); + tempPtr = (XMP_Uns8*) utf8Ptr; + dataLen = utf8Len; + + } else if ( kUTF8_Mode == kUTF8_IncomingMode ) { + + // Only use UTF-8 if that was what the parsed block used. + if ( this->utf8Encoding ) { + tempPtr = (XMP_Uns8*) utf8Ptr; + dataLen = utf8Len; + } else if ( ! ignoreLocalText ) { + ReconcileUtils::UTF8ToLocal ( utf8Ptr, utf8Len, &localStr ); + tempPtr = (XMP_Uns8*) localStr.data(); + dataLen = (XMP_Uns32) localStr.size(); + } else { + if ( ! ReconcileUtils::IsASCII ( utf8Ptr, utf8Len ) ) return; + tempPtr = (XMP_Uns8*) utf8Ptr; + dataLen = utf8Len; + } + + } else if ( kUTF8_Mode == kUTF8_LosslessMode ) { + + // Convert to UTF-8 if needed to prevent round trip loss. + if ( this->utf8Encoding ) { + tempPtr = (XMP_Uns8*) utf8Ptr; + dataLen = utf8Len; + } else if ( ! ignoreLocalText ) { + std::string rtStr; + ReconcileUtils::UTF8ToLocal ( utf8Ptr, utf8Len, &localStr ); + ReconcileUtils::LocalToUTF8 ( localStr.data(), localStr.size(), &rtStr ); + if ( (rtStr.size() == utf8Len) && (memcmp ( rtStr.data(), utf8Ptr, utf8Len ) == 0) ) { + tempPtr = (XMP_Uns8*) localStr.data(); // No loss, keep local encoding. + dataLen = (XMP_Uns32) localStr.size(); + } else { + this->ConvertToUTF8(); // Had loss, change everything to UTF-8. + XMP_Assert ( this->utf8Encoding ); + tempPtr = (XMP_Uns8*) utf8Ptr; + dataLen = utf8Len; + } + } else { + if ( ! ReconcileUtils::IsASCII ( utf8Ptr, utf8Len ) ) return; + tempPtr = (XMP_Uns8*) utf8Ptr; + dataLen = utf8Len; + } + + } +#if XMP_MacBuild +#pragma clang diagnostic pop +#endif + + // Set the value for this DataSet, making a non-transient copy of the value. Respect UTF-8 character + // boundaries when truncating. This is easy to check. If the first truncated byte has 10 in the + // high order 2 bits then we are in the middle of a UTF-8 multi-byte character. + // Back up to just before a byte with 11 in the high order 2 bits. + + if ( dataLen > knownDS->maxLen ) { + dataLen = (XMP_Uns32)knownDS->maxLen; + if ( this->utf8Encoding && ((tempPtr[dataLen] >> 6) == 2) ) { + for ( ; (dataLen > 0) && ((tempPtr[dataLen] >> 6) != 3); --dataLen ) {} + } + } + + XMP_Uns16 mapID = 2000 + dsNum; // ! Only deal with 2:xx datasets. + DataSetMap::iterator dsPos = this->dataSets.find ( mapID ); + long currCount = (long) this->dataSets.count ( mapID ); + + bool repeatable = false; + + if ( knownDS->mapForm == kIPTC_MapArray ) { + repeatable = true; + } else if ( (dsNum == kIPTC_Creator) || (dsNum == kIPTC_SubjectCode) ) { + repeatable = true; + } + + if ( ! repeatable ) { + + if ( which > 0 ) XMP_Throw ( "Non-repeatable IPTC DataSet", kXMPErr_BadParam ); + + } else { + + if ( which < 0 ) which = currCount; // The default is to append. + + if ( which > currCount ) { + XMP_Throw ( "Invalid index for IPTC DataSet", kXMPErr_BadParam ); + } else if ( which == currCount ) { + dsPos = this->dataSets.end(); // To make later checks do the right thing. + } else { + dsPos = this->dataSets.lower_bound ( mapID ); + for ( ; which > 0; --which ) ++dsPos; + } + + } + + if ( dsPos != this->dataSets.end() ) { + if ( (dsPos->second.dataLen == dataLen) && (memcmp ( dsPos->second.dataPtr, tempPtr, dataLen ) == 0) ) { + return; // ! New value matches the old, don't update. + } + } + + XMP_Uns8 * dataPtr = (XMP_Uns8*) malloc ( dataLen ); + if ( dataPtr == 0 ) XMP_Throw ( "Out of memory", kXMPErr_NoMemory ); + memcpy ( dataPtr, tempPtr, dataLen ); // AUDIT: Safe, malloc'ed dataLen bytes above. + + DataSetInfo dsInfo ( 2, dsNum, dataLen, dataPtr ); + + if ( dsPos != this->dataSets.end() ) { + this->DisposeLooseValue ( dsPos->second ); + dsPos->second = dsInfo; + } else { + DataSetMap::value_type mapValue ( mapID, dsInfo ); + (void) this->dataSets.insert ( this->dataSets.upper_bound ( mapID ), mapValue ); + } + + this->changed = true; + +} // IPTC_Writer::SetDataSet_UTF8 + +// ================================================================================================= +// IPTC_Writer::DeleteDataSet +// ========================== + +void IPTC_Writer::DeleteDataSet ( XMP_Uns8 dsNum, long which /* = -1 */ ) +{ + XMP_Uns16 mapID = 2000 + dsNum; // ! Only deal with 2:xx datasets. + DataSetMap::iterator dsBegin = this->dataSets.lower_bound ( mapID ); // Set for which == -1. + DataSetMap::iterator dsEnd = this->dataSets.upper_bound ( mapID ); + + if ( dsBegin == dsEnd ) return; // Nothing to delete. + + if ( which >= 0 ) { + long currCount = (long) this->dataSets.count ( mapID ); + if ( which >= currCount ) return; // Nothing to delete. + for ( ; which > 0; --which ) ++dsBegin; + dsEnd = dsBegin; ++dsEnd; // ! Can't do "dsEnd = dsBegin+1"! + } + + for ( DataSetMap::iterator dsPos = dsBegin; dsPos != dsEnd; ++dsPos ) { + this->DisposeLooseValue ( dsPos->second ); + } + + this->dataSets.erase ( dsBegin, dsEnd ); + this->changed = true; + +} // IPTC_Writer::DeleteDataSet + +// ================================================================================================= +// IPTC_Writer::UpdateMemoryDataSets +// ================================= +// +// Reconstruct the entire IIM block. This does not include any final alignment padding, that is an +// artifact of some specific wrappers such as Photoshop image resources. + +void IPTC_Writer::UpdateMemoryDataSets() +{ + if ( ! this->changed ) return; + + DataSetMap::iterator dsPos; + DataSetMap::iterator dsEnd = this->dataSets.end(); + + if ( kUTF8_Mode == kUTF8_LosslessMode ) { + if ( this->utf8Encoding ) { + if ( ! this->CheckRoundTripLoss() ) this->ConvertToLocal(); + } else { + if ( this->CheckRoundTripLoss() ) this->ConvertToUTF8(); + } + } + + // Compute the length of the new IIM block. All DataSets other than 1:90 and 2:xx are preserved + // as-is. If local text is used then 1:90 is omitted, if UTF-8 text is used then 1:90 is written + // to say so. The map key of (record*1000 + dataset) provides the desired overall order. + + XMP_Uns32 newLength = (5+2); // We always write 2:00 for the IIM version. + if ( this->utf8Encoding ) newLength += (5+3); // For 1:90, if written. + + for ( dsPos = this->dataSets.begin(); dsPos != dsEnd; ++dsPos ) { // Accumulate the other sizes. + const XMP_Uns16 mapID = dsPos->first; + if ( (mapID == 1090) || (mapID == 2000) ) continue; // Already dealt with 1:90 and 2:00. + XMP_Uns32 dsLen = dsPos->second.dataLen; + newLength += (5 + dsLen); + if ( dsLen > 0x7FFF ) newLength += 4; // We always use a 4 byte extended length for big values. + } + + // Allocate the new IIM block. + + XMP_Uns8* newContent = (XMP_Uns8*) malloc ( newLength ); + if ( newContent == 0 ) XMP_Throw ( "Out of memory", kXMPErr_NoMemory ); + + XMP_Uns8* dsPtr = newContent; + + // Write the record 0 DataSets. There should not be any, but let's be safe. + + for ( dsPos = this->dataSets.begin(); dsPos != dsEnd; ++dsPos ) { + const DataSetInfo & currDS = dsPos->second; + if ( currDS.recNum > 0 ) break; + dsPtr = AppendDataSet ( dsPtr, currDS ); + } + + // Write 1:90 then any other record 1 DataSets. + + if ( this->utf8Encoding ) { // Write 1:90 only if text is UTF-8. + memcpy ( dsPtr, "\x1C\x01\x5A\x00\x03\x1B\x25\x47", (5+3) ); // AUDIT: Within range of allocation. + dsPtr += (5+3); + } + + for ( ; dsPos != dsEnd; ++dsPos ) { + const DataSetInfo & currDS = dsPos->second; + if ( currDS.recNum > 1 ) break; + XMP_Assert ( currDS.recNum == 1 ); + if ( currDS.dsNum == 90 ) continue; + dsPtr = AppendDataSet ( dsPtr, currDS ); + } + + // Write 2:00 then all of the other DataSets from all records. + + if ( this->utf8Encoding ) { + // Start with 2:00 for version 4. + memcpy ( dsPtr, "\x1C\x02\x00\x00\x02\x00\x04", (5+2) ); // AUDIT: Within range of allocation. + dsPtr += (5+2); + } else { + // Start with 2:00 for version 2. + // *** We should probably write version 4 all the time. This is a late CS3 change, don't want + // *** to risk breaking other apps that might be strict about version checking. + memcpy ( dsPtr, "\x1C\x02\x00\x00\x02\x00\x02", (5+2) ); // AUDIT: Within range of allocation. + dsPtr += (5+2); + } + + for ( ; dsPos != dsEnd; ++dsPos ) { + const DataSetInfo & currDS = dsPos->second; + XMP_Assert ( currDS.recNum > 1 ); + if ( dsPos->first == 2000 ) continue; // Check both the record number and DataSet number. + dsPtr = AppendDataSet ( dsPtr, currDS ); + } + + XMP_Assert ( dsPtr == (newContent + newLength) ); + + // Parse the new block, it is the best way to reset internal info and rebuild the map. + + this->ParseMemoryDataSets ( newContent, newLength, false ); // Don't make another copy of the content. + XMP_Assert ( this->iptcLength == newLength ); + this->ownedContent = (newLength > 0); // We really do own the new content, if not empty. + +} // IPTC_Writer::UpdateMemoryDataSets + +// ================================================================================================= +// IPTC_Writer::ConvertToUTF8 +// ========================== +// +// Convert the values of existing text DataSets to UTF-8. For now we only accept text DataSets. + +void IPTC_Writer::ConvertToUTF8() +{ + XMP_Assert ( ! this->utf8Encoding ); + std::string utf8Str; + + DataSetMap::iterator dsPos = this->dataSets.begin(); + DataSetMap::iterator dsEnd = this->dataSets.end(); + + for ( ; dsPos != dsEnd; ++dsPos ) { + + DataSetInfo & dsInfo = dsPos->second; + if ( dsInfo.dataLen != 0 ) + { + ReconcileUtils::LocalToUTF8( dsInfo.dataPtr, dsInfo.dataLen, &utf8Str ); + this->DisposeLooseValue( dsInfo ); + + dsInfo.dataLen = ( XMP_Uns32 ) utf8Str.size(); + dsInfo.dataPtr = ( XMP_Uns8* ) malloc( dsInfo.dataLen ); + if ( dsInfo.dataPtr == 0 ) XMP_Throw( "Out of memory", kXMPErr_NoMemory ); + memcpy( dsInfo.dataPtr, utf8Str.data(), dsInfo.dataLen ); // AUDIT: Safe, malloc'ed dataLen bytes above. + } + } + + this->utf8Encoding = true; + +} // IPTC_Writer::ConvertToUTF8 + +// ================================================================================================= +// IPTC_Writer::ConvertToLocal +// =========================== +// +// Convert the values of existing text DataSets to local. For now we only accept text DataSets. + +void IPTC_Writer::ConvertToLocal() +{ + XMP_Assert ( this->utf8Encoding ); + std::string localStr; + + DataSetMap::iterator dsPos = this->dataSets.begin(); + DataSetMap::iterator dsEnd = this->dataSets.end(); + + for ( ; dsPos != dsEnd; ++dsPos ) { + + DataSetInfo & dsInfo = dsPos->second; + if ( dsInfo.dataLen != 0 ) + { + ReconcileUtils::UTF8ToLocal( dsInfo.dataPtr, dsInfo.dataLen, &localStr ); + this->DisposeLooseValue( dsInfo ); + + dsInfo.dataLen = ( XMP_Uns32 ) localStr.size(); + dsInfo.dataPtr = ( XMP_Uns8* ) malloc( dsInfo.dataLen ); + if ( dsInfo.dataPtr == 0 ) XMP_Throw( "Out of memory", kXMPErr_NoMemory ); + memcpy( dsInfo.dataPtr, localStr.data(), dsInfo.dataLen ); // AUDIT: Safe, malloc'ed dataLen bytes above. + } + } + + this->utf8Encoding = false; + +} // IPTC_Writer::ConvertToLocal + +// ================================================================================================= +// IPTC_Writer::CheckRoundTripLoss +// =============================== +// +// See if we still need UTF-8 because of round-trip loss. Returns true if there is loss. + +bool IPTC_Writer::CheckRoundTripLoss() +{ + XMP_Assert ( this->utf8Encoding ); + std::string localStr, rtStr; + + DataSetMap::iterator dsPos = this->dataSets.begin(); + DataSetMap::iterator dsEnd = this->dataSets.end(); + + for ( ; dsPos != dsEnd; ++dsPos ) { + + DataSetInfo & dsInfo = dsPos->second; + + XMP_StringPtr utf8Ptr = (XMP_StringPtr) dsInfo.dataPtr; + XMP_StringLen utf8Len = dsInfo.dataLen; + + ReconcileUtils::UTF8ToLocal ( utf8Ptr, utf8Len, &localStr ); + ReconcileUtils::LocalToUTF8 ( localStr.data(), localStr.size(), &rtStr ); + + if ( (rtStr.size() != utf8Len) || (memcmp ( rtStr.data(), utf8Ptr, utf8Len ) != 0) ) { + return true; // Had round-trip loss, keep UTF-8. + } + + } + + return false; // No loss. + +} // IPTC_Writer::CheckRoundTripLoss + +// ================================================================================================= diff --git a/core/libs/dngwriter/extra/xmp_sdk/XMPFiles/source/FormatSupport/IPTC_Support.hpp b/core/libs/dngwriter/extra/xmp_sdk/XMPFiles/source/FormatSupport/IPTC_Support.hpp new file mode 100644 index 0000000000..e81f1e029a --- /dev/null +++ b/core/libs/dngwriter/extra/xmp_sdk/XMPFiles/source/FormatSupport/IPTC_Support.hpp @@ -0,0 +1,307 @@ +#ifndef __IPTC_Support_hpp__ +#define __IPTC_Support_hpp__ 1 + +// ================================================================================================= +// ADOBE SYSTEMS INCORPORATED +// Copyright 2006 Adobe Systems Incorporated +// All Rights Reserved +// +// NOTICE: Adobe permits you to use, modify, and distribute this file in accordance with the terms +// of the Adobe license agreement accompanying it. +// ================================================================================================= + +#include "public/include/XMP_Environment.h" // ! This must be the first include. + +#include + +#include "public/include/XMP_Const.h" +#include "XMPFiles/source/XMPFiles_Impl.hpp" +#include "source/EndianUtils.hpp" + +// ================================================================================================= +/// \file IPTC_Support.hpp +/// \brief XMPFiles support for IPTC (IIM) DataSets. +/// +/// This header provides IPTC (IIM) DataSet support specific to the needs of XMPFiles. This is not +/// intended for general purpose IPTC processing. There is a small tree of derived classes, 1 +/// virtual base class and 2 concrete leaf classes: +/// \code +/// IPTC_Manager - The root virtual base class. +/// IPTC_Reader - A derived concrete leaf class for memory-based read-only access. +/// IPTC_Writer - A derived concrete leaf class for memory-based read-write access. +/// \endcode +/// +/// \c IPTC_Manager declares all of the public methods except for specialized constructors in the +/// leaf classes. The read-only classes throw an XMP_Error exception for output methods like +/// \c SetDataSet. They return appropriate values for "safe" methods, \c IsChanged will return false +/// for example. +/// +/// The IPTC DataSet organization differs from TIFF tags and Photoshop image resources in allowing +/// muultiple occurrences for some IDs. The C++ STL multimap is a natural data structure for IPTC. +/// +/// Support is only provided for DataSet 1:90 to decide if local or UTF-8 text encoding is used, and +/// the following text valued DataSets: 2:05, 2:10, 2:15, 2:20, 2:25, 2:40, 2:55, 2:80, 2:85, 2:90, +/// 2:95, 2:101, 2:103, 2:105, 2:110, 2:115, 2:116, 2:120, and 2:122. DataSet 2:00 is ignored when +/// reading but always written. +/// +/// \note Unlike the TIFF_Manager and PSIR_Manager class trees, IPTC_Manager only provides in-memory +/// implementations. The total size of IPTC data is small enough to make this reasonable. +/// +/// \note These classes are for use only when directly compiled and linked. They should not be +/// packaged in a DLL by themselves. They do not provide any form of C++ ABI protection. +// ================================================================================================= + + +// ================================================================================================= +// ================================================================================================= + +enum { // List of recognized 2:* IIM DataSets. The names are from IIMv4 and IPTC4XMP. + kIPTC_ObjectType = 3, + kIPTC_IntellectualGenre = 4, + kIPTC_Title = 5, + kIPTC_EditStatus = 7, + kIPTC_EditorialUpdate = 8, + kIPTC_Urgency = 10, + kIPTC_SubjectCode = 12, + kIPTC_Category = 15, + kIPTC_SuppCategory = 20, + kIPTC_FixtureIdentifier = 22, + kIPTC_Keyword = 25, + kIPTC_ContentLocCode = 26, + kIPTC_ContentLocName = 27, + kIPTC_ReleaseDate = 30, + kIPTC_ReleaseTime = 35, + kIPTC_ExpDate = 37, + kIPTC_ExpTime = 38, + kIPTC_Instructions = 40, + kIPTC_ActionAdvised = 42, + kIPTC_RefService = 45, + kIPTC_RefDate = 47, + kIPTC_RefNumber = 50, + kIPTC_DateCreated = 55, + kIPTC_TimeCreated = 60, + kIPTC_DigitalCreateDate = 62, + kIPTC_DigitalCreateTime = 63, + kIPTC_OriginProgram = 65, + kIPTC_ProgramVersion = 70, + kIPTC_ObjectCycle = 75, + kIPTC_Creator = 80, + kIPTC_CreatorJobtitle = 85, + kIPTC_City = 90, + kIPTC_Location = 92, + kIPTC_State = 95, + kIPTC_CountryCode = 100, + kIPTC_Country = 101, + kIPTC_JobID = 103, + kIPTC_Headline = 105, + kIPTC_Provider = 110, + kIPTC_Source = 115, + kIPTC_CopyrightNotice = 116, + kIPTC_Contact = 118, + kIPTC_Description = 120, + kIPTC_DescriptionWriter = 122, + kIPTC_RasterizedCaption = 125, + kIPTC_ImageType = 130, + kIPTC_ImageOrientation = 131, + kIPTC_LanguageID = 135, + kIPTC_AudioType = 150, + kIPTC_AudioSampleRate = 151, + kIPTC_AudioSampleRes = 152, + kIPTC_AudioDuration = 153, + kIPTC_AudioOutcue = 154, + kIPTC_PreviewFormat = 200, + kIPTC_PreviewFormatVers = 201, + kIPTC_PreviewData = 202 +}; + +enum { // Forms of mapping legacy IPTC to XMP. Order is significant, see PhotoDataUtils::Import2WayIPTC! + kIPTC_MapSimple, // The XMP is simple, the last DataSet occurrence is kept. + kIPTC_MapLangAlt, // The XMP is a LangAlt x-default item, the last DataSet occurrence is kept. + kIPTC_MapArray, // The XMP is an unordered array, all DataSets are kept. + kIPTC_MapSpecial, // The mapping requires DataSet specific code. + kIPTC_Map3Way, // Has a 3 way mapping between Exif, IPTC, and XMP. + kIPTC_UnmappedText, // A text DataSet that is not mapped to XMP. + kIPTC_UnmappedBin // A binary DataSet that is not mapped to XMP. +}; + +struct DataSetCharacteristics { + XMP_Uns8 dsNum; + XMP_Uns8 mapForm; + size_t maxLen; + XMP_StringPtr xmpNS; + XMP_StringPtr xmpProp; +}; + +extern const DataSetCharacteristics kKnownDataSets[]; + +struct IntellectualGenreMapping { + XMP_StringPtr refNum; // The reference number as a 3 digit string. + XMP_StringPtr name; // The intellectual genre name. +}; + +extern const IntellectualGenreMapping kIntellectualGenreMappings[]; + +// ================================================================================================= +// IPTC_Manager +// ============ + +class IPTC_Manager { +public: + + // --------------------------------------------------------------------------------------------- + // Types and constants. + + struct DataSetInfo { + XMP_Uns8 recNum, dsNum; + XMP_Uns32 dataLen; + XMP_Uns8 * dataPtr; // ! The data is read-only. Raw data pointer, beware of character encoding. + DataSetInfo() : recNum(0), dsNum(0), dataLen(0), dataPtr(0) {}; + DataSetInfo( XMP_Uns8 _recNum, XMP_Uns8 _dsNum, XMP_Uns32 _dataLen ) + : recNum( _recNum ), dsNum( _dsNum ), dataLen( _dataLen ), dataPtr( 0 ) {}; + DataSetInfo ( XMP_Uns8 _recNum, XMP_Uns8 _dsNum, XMP_Uns32 _dataLen, XMP_Uns8 * _dataPtr ) + : recNum(_recNum), dsNum(_dsNum), dataLen(_dataLen), dataPtr(_dataPtr) {}; + }; + + // --------------------------------------------------------------------------------------------- + // Parse a binary IPTC (IIM) block. + + void ParseMemoryDataSets ( const void* data, XMP_Uns32 length, bool copyData = true ); + + // --------------------------------------------------------------------------------------------- + // Get the information about a 2:xx DataSet. Returns the number of occurrences. The "which" + // parameter selects the occurrence, they are numbered from 0 to count-1. Returns 0 if which is + // too large. + + size_t GetDataSet ( XMP_Uns8 dsNum, DataSetInfo* info, size_t which = 0 ) const; + + // --------------------------------------------------------------------------------------------- + // Get the value of a text 2:xx DataSet as UTF-8. The returned pointer must be treated as + // read-only. Calls GetDataSet then does a local to UTF-8 conversion if necessary. + + size_t GetDataSet_UTF8 ( XMP_Uns8 dsNum, std::string * utf8Str, size_t which = 0 ) const; + + // --------------------------------------------------------------------------------------------- + // Set the value of a text 2:xx DataSet from a UTF-8 string. Does a UTF-8 to local conversion if + // necessary. If the encoding mode is currently local and this value has round-trip loss, then + // the encoding mode will be changed to UTF-8 and all existing values will be converted. + // Modifies an existing occurrence if "which" is within range. Adds an occurrence if which + // equals the current count, or which is -1 and repeats are allowed. Throws an exception if + // which is too large. The dataPtr provides the raw data, text must be in the right encoding. + + virtual void SetDataSet_UTF8 ( XMP_Uns8 dsNum, const void* utf8Ptr, XMP_Uns32 utf8Len, long which = -1 ) = 0; + + // --------------------------------------------------------------------------------------------- + // Delete an existing 2:xx DataSet. Deletes all occurrences if which is -1. + + virtual void DeleteDataSet ( XMP_Uns8 dsNum, long which = -1 ) = 0; + + // --------------------------------------------------------------------------------------------- + // Determine if any 2:xx DataSets are changed. + + virtual bool IsChanged() = 0; + + // --------------------------------------------------------------------------------------------- + // Determine if UTF-8 or local text encoding is being used. + + bool UsingUTF8() const { return this->utf8Encoding; }; + + // -------------------------------------------------- + // Update all DataSets to reflect the changed values. + + virtual void UpdateMemoryDataSets() = 0; + + // --------------------------------------------------------------------------------------------- + // Get the location and size of the full IPTC block. The client must call UpdateMemoryDataSets + // first if appropriate. The returned dataPtr must be treated as read only. It exists until the + // IPTC_Manager destructor is called. + + XMP_Uns32 GetBlockInfo ( void** dataPtr ) const + { if ( dataPtr != 0 ) *dataPtr = this->iptcContent; return this->iptcLength; }; + + // --------------------------------------------------------------------------------------------- + + virtual ~IPTC_Manager() { if ( this->ownedContent ) free ( this->iptcContent ); }; + +protected: + + enum { kMinDataSetSize = 5 }; // 1+1+1+2 + + typedef std::multimap DataSetMap; + DataSetMap dataSets; // ! All datasets are in the map, key is (record*1000 + dataset). + + XMP_Uns8* iptcContent; + XMP_Uns32 iptcLength; + + bool changed; + bool ownedContent; // True if IPTC_Manager destructor needs to release the content block. + bool utf8Encoding; // True if text values are encoded as UTF-8. + + IPTC_Manager() : iptcContent(0), iptcLength(0), + changed(false), ownedContent(false), utf8Encoding(false) {}; + + void DisposeLooseValue ( DataSetInfo & dsInfo ); + XMP_Uns8* AppendDataSet ( XMP_Uns8* dsPtr, const DataSetInfo & dsInfo ); + +}; // IPTC_Manager + + +// ================================================================================================= +// ================================================================================================= + + +// ================================================================================================= +// IPTC_Reader +// =========== + +class IPTC_Reader : public IPTC_Manager { +public: + + IPTC_Reader() {}; + + void SetDataSet_UTF8 ( XMP_Uns8 dsNum, const void* utf8Ptr, XMP_Uns32 utf8Len, long which = -1 ) { NotAppropriate(); }; + + void DeleteDataSet ( XMP_Uns8 dsNum, long which = -1 ) { NotAppropriate(); }; + + bool IsChanged() { return false; }; + + void UpdateMemoryDataSets() { NotAppropriate(); }; + + virtual ~IPTC_Reader() {}; + +private: + + static inline void NotAppropriate() { XMP_Throw ( "Not appropriate for IPTC_Reader", kXMPErr_InternalFailure ); }; + +}; // IPTC_Reader + +// ================================================================================================= +// IPTC_Writer +// =========== + +class IPTC_Writer : public IPTC_Manager { +public: + + void SetDataSet_UTF8 ( XMP_Uns8 dsNum, const void* utf8Ptr, XMP_Uns32 utf8Len, long which = -1 ); + + void DeleteDataSet ( XMP_Uns8 dsNum, long which = -1 ); + + bool IsChanged() { return changed; }; + + void UpdateMemoryDataSets (); + + IPTC_Writer() {}; + + virtual ~IPTC_Writer(); + +private: + + void ConvertToUTF8(); + void ConvertToLocal(); + + bool CheckRoundTripLoss(); + +}; // IPTC_Writer + +// ================================================================================================= + +#endif // __IPTC_Support_hpp__ diff --git a/core/libs/dngwriter/extra/xmp_sdk/XMPFiles/source/FormatSupport/ISOBaseMedia_Support.cpp b/core/libs/dngwriter/extra/xmp_sdk/XMPFiles/source/FormatSupport/ISOBaseMedia_Support.cpp new file mode 100644 index 0000000000..b1ee77c78a --- /dev/null +++ b/core/libs/dngwriter/extra/xmp_sdk/XMPFiles/source/FormatSupport/ISOBaseMedia_Support.cpp @@ -0,0 +1,202 @@ +// ================================================================================================= +// ADOBE SYSTEMS INCORPORATED +// Copyright 2007 Adobe Systems Incorporated +// All Rights Reserved +// +// NOTICE: Adobe permits you to use, modify, and distribute this file in accordance with the terms +// of the Adobe license agreement accompanying it. +// ================================================================================================= + +#include "public/include/XMP_Environment.h" // ! XMP_Environment.h must be the first included header. +#include "public/include/XMP_Const.h" + +#include "XMPFiles/source/FormatSupport/ISOBaseMedia_Support.hpp" +#include "XMPFiles/source/XMPFiles_Impl.hpp" +#include "source/XIO.hpp" + +// ================================================================================================= +/// \file ISOBaseMedia_Support.cpp +/// \brief Manager for parsing and serializing ISO Base Media files ( MPEG-4 and JPEG-2000). +/// +// ================================================================================================= + +namespace ISOMedia { + +typedef std::set KnownBoxList; +static KnownBoxList boxList; +#define ISOboxType(x,y) boxList.insert(y) +#define SEPARATOR ; + bool IsKnownBoxType(XMP_Uns32 boxType) { + if (boxList.empty()){ + ISOBoxList ISOBoxPrivateList ; + } + if (boxList.find(boxType)!=boxList.end()){ + return true; + } + return false; + } + void TerminateGlobals() + { + boxList.clear(); + } +#undef ISOboxType +#undef SEPARATOR +static BoxInfo voidInfo; + +// ================================================================================================= +// GetBoxInfo - from memory +// ======================== + +const XMP_Uns8 * GetBoxInfo ( const XMP_Uns8 * boxPtr, const XMP_Uns8 * boxLimit, + BoxInfo * info, bool throwErrors /* = false */ ) +{ + XMP_Uns32 u32Size; + + if ( info == 0 ) info = &voidInfo; + info->boxType = info->headerSize = 0; + info->contentSize = 0; + memset( info->idUUID, 0, 16 ); + + if ( boxPtr >= boxLimit ) XMP_Throw ( "Bad offset to GetBoxInfo", kXMPErr_InternalFailure ); + + if ( (boxLimit - boxPtr) < 8 ) { // Is there enough space for a standard box header? + if ( throwErrors ) XMP_Throw ( "No space for ISO box header", kXMPErr_BadFileFormat ); + info->headerSize = (XMP_Uns32) (boxLimit - boxPtr); + return boxLimit; + } + + u32Size = GetUns32BE ( boxPtr ); + info->boxType = GetUns32BE ( boxPtr+4 ); + + if ( u32Size >= 8 ) { + if( info->boxType == ISOMedia::k_uuid ) + { + if ( (boxLimit - boxPtr) < 24 ) + { // Is there enough space for a uuid box header? + if ( throwErrors ) XMP_Throw ( "No space for UUID box header", kXMPErr_BadFileFormat ); + info->headerSize = (XMP_Uns32) (boxLimit - boxPtr); + return boxLimit; + } + info->headerSize = 8 + 16; // 16 for ID in UUID + memcpy( info->idUUID, boxPtr + 8, 16 ); + } + else + { + info->headerSize = 8; // Normal explicit size case. + } + info->contentSize = u32Size - info->headerSize; + } else if ( u32Size == 0 ) { + info->headerSize = 8; // The box goes to EoF - treat it as "to limit". + info->contentSize = (boxLimit - boxPtr) - 8; + } else if ( u32Size != 1 ) { + if ( throwErrors ) XMP_Throw ( "Bad ISO box size, 2..7", kXMPErr_BadFileFormat ); + info->headerSize = 8; // Bad total size in the range of 2..7, treat as 8. + info->contentSize = 0; + } else { + if ( (boxLimit - boxPtr) < 16 ) { // Is there enough space for an extended box header? + if ( throwErrors ) XMP_Throw ( "No space for ISO extended header", kXMPErr_BadFileFormat ); + info->headerSize = (XMP_Uns32) (boxLimit - boxPtr); + return boxLimit; + } + XMP_Uns64 u64Size = GetUns64BE ( boxPtr+8 ); + if ( u64Size < 16 ) { + if ( throwErrors ) XMP_Throw ( "Bad ISO extended box size, < 16", kXMPErr_BadFileFormat ); + u64Size = 16; // Treat bad total size as 16. + } + info->headerSize = 16; + info->contentSize = u64Size - 16; + } + + XMP_Assert ( (XMP_Uns64)(boxLimit - boxPtr) >= (XMP_Uns64)info->headerSize ); + if ( info->contentSize > (XMP_Uns64)((boxLimit - boxPtr) - info->headerSize) ) { + if ( throwErrors ) XMP_Throw ( "Bad ISO box content size", kXMPErr_BadFileFormat ); + info->contentSize = ((boxLimit - boxPtr) - info->headerSize); // Trim a bad content size to the limit. + } + + return (boxPtr + info->headerSize + info->contentSize); + +} // GetBoxInfo + +// ================================================================================================= +// GetBoxInfo - from a file +// ======================== + +XMP_Uns64 GetBoxInfo ( XMP_IO* fileRef, const XMP_Uns64 boxOffset, const XMP_Uns64 boxLimit, + BoxInfo * info, bool doSeek /* = true */, bool throwErrors /* = false */ ) +{ + XMP_Uns8 buffer [8]; + XMP_Uns32 u32Size; + + if ( info == 0 ) info = &voidInfo; + info->boxType = info->headerSize = 0; + info->contentSize = 0; + memset( info->idUUID, 0, 16 ); + + if ( boxOffset >= boxLimit ) XMP_Throw ( "Bad offset to GetBoxInfo", kXMPErr_InternalFailure ); + + if ( (boxLimit - boxOffset) < 8 ) { // Is there enough space for a standard box header? + if ( throwErrors ) XMP_Throw ( "No space for ISO box header", kXMPErr_BadFileFormat ); + info->headerSize = (XMP_Uns32) (boxLimit - boxOffset); + return boxLimit; + } + + if ( doSeek ) fileRef->Seek ( boxOffset, kXMP_SeekFromStart ); + (void) fileRef->ReadAll ( buffer, 8 ); + + u32Size = GetUns32BE ( &buffer[0] ); + info->boxType = GetUns32BE ( &buffer[4] ); + + if ( u32Size >= 8 ) { + if( info->boxType == ISOMedia::k_uuid ) + { + if ( (boxLimit - boxOffset) < 24 ) + { // Is there enough space for a uuid box header? + if ( throwErrors ) XMP_Throw ( "No space for UUID box header", kXMPErr_BadFileFormat ); + info->headerSize = (XMP_Uns32) (boxLimit - boxOffset); + return boxLimit; + } + info->headerSize = 8 + 16; // 16 for ID in UUID + (void) fileRef->ReadAll ( info->idUUID, 16 ); + } + else + { + info->headerSize = 8; // Normal explicit size case. + } + info->contentSize = u32Size - info->headerSize; + } else if ( u32Size == 0 ) { + info->headerSize = 8; // The box goes to EoF. + info->contentSize = fileRef->Length() - (boxOffset + 8); + } else if ( u32Size != 1 ) { + if ( throwErrors ) XMP_Throw ( "Bad ISO box size, 2..7", kXMPErr_BadFileFormat ); + info->headerSize = 8; // Bad total size in the range of 2..7, treat as 8. + info->contentSize = 0; + } else { + if ( (boxLimit - boxOffset) < 16 ) { // Is there enough space for an extended box header? + if ( throwErrors ) XMP_Throw ( "No space for ISO extended header", kXMPErr_BadFileFormat ); + info->headerSize = (XMP_Uns32) (boxLimit - boxOffset); + return boxLimit; + } + (void) fileRef->ReadAll ( buffer, 8 ); + XMP_Uns64 u64Size = GetUns64BE ( &buffer[0] ); + if ( u64Size < 16 ) { + if ( throwErrors ) XMP_Throw ( "Bad ISO extended box size, < 16", kXMPErr_BadFileFormat ); + u64Size = 16; // Treat bad total size as 16. + } + info->headerSize = 16; + info->contentSize = u64Size - 16; + } + + XMP_Assert ( (boxLimit - boxOffset) >= info->headerSize ); + if ( info->contentSize > (boxLimit - boxOffset - info->headerSize) ) { + if ( throwErrors ) XMP_Throw ( "Bad ISO box content size", kXMPErr_BadFileFormat ); + info->contentSize = (boxLimit - boxOffset - info->headerSize); // Trim a bad content size to the limit. + } + + return (boxOffset + info->headerSize + info->contentSize); + +} // GetBoxInfo + +} // namespace ISO_Media + + +// ================================================================================================= diff --git a/core/libs/dngwriter/extra/xmp_sdk/XMPFiles/source/FormatSupport/ISOBaseMedia_Support.hpp b/core/libs/dngwriter/extra/xmp_sdk/XMPFiles/source/FormatSupport/ISOBaseMedia_Support.hpp new file mode 100644 index 0000000000..a7c8feb589 --- /dev/null +++ b/core/libs/dngwriter/extra/xmp_sdk/XMPFiles/source/FormatSupport/ISOBaseMedia_Support.hpp @@ -0,0 +1,129 @@ +#ifndef __ISOBaseMedia_Support_hpp__ +#define __ISOBaseMedia_Support_hpp__ 1 + +// ================================================================================================= +// ADOBE SYSTEMS INCORPORATED +// Copyright 2007 Adobe Systems Incorporated +// All Rights Reserved +// +// NOTICE: Adobe permits you to use, modify, and distribute this file in accordance with the terms +// of the Adobe license agreement accompanying it. +// ================================================================================================= + +#include "public/include/XMP_Environment.h" // ! This must be the first include. + +#include "public/include/XMP_Const.h" +#include "public/include/XMP_IO.hpp" + +#include "XMPFiles/source/XMPFiles_Impl.hpp" + +#include + +// ================================================================================================= +/// \file ISOBaseMedia_Support.hpp +/// \brief XMPFiles support for the ISO Base Media File Format. +/// +/// \note These classes are for use only when directly compiled and linked. They should not be +/// packaged in a DLL by themselves. They do not provide any form of C++ ABI protection. +// ================================================================================================= + +namespace ISOMedia { + +#define ISOBoxList \ + ISOboxType(k_ftyp,0x66747970UL)SEPARATOR /* File header Box, no version/flags.*/ \ + \ + ISOboxType(k_mp41,0x6D703431UL)SEPARATOR /* Compatible brand codes*/ \ + ISOboxType(k_mp42,0x6D703432UL)SEPARATOR \ + ISOboxType(k_f4v ,0x66347620UL)SEPARATOR \ + ISOboxType(k_avc1,0x61766331UL)SEPARATOR \ + ISOboxType(k_qt ,0x71742020UL)SEPARATOR \ + ISOboxType(k_isom,0x69736F6DUL)SEPARATOR \ + ISOboxType(k_3gp4,0x33677034UL)SEPARATOR \ + ISOboxType(k_3g2a,0x33673261UL)SEPARATOR \ + ISOboxType(k_3g2b,0x33673262UL)SEPARATOR \ + ISOboxType(k_3g2c,0x33673263UL)SEPARATOR \ + \ + ISOboxType(k_moov,0x6D6F6F76UL)SEPARATOR /* Container Box, no version/flags. */ \ + ISOboxType(k_mvhd,0x6D766864UL)SEPARATOR /* Data FullBox, has version/flags. */ \ + ISOboxType(k_hdlr,0x68646C72UL)SEPARATOR \ + ISOboxType(k_udta,0x75647461UL)SEPARATOR /* Container Box, no version/flags. */ \ + ISOboxType(k_cprt,0x63707274UL)SEPARATOR /* Data FullBox, has version/flags. */ \ + ISOboxType(k_uuid,0x75756964UL)SEPARATOR /* Data Box, no version/flags. */ \ + ISOboxType(k_free,0x66726565UL)SEPARATOR /* Free space Box, no version/flags.*/ \ + ISOboxType(k_mdat,0x6D646174UL)SEPARATOR /* Media data Box, no version/flags.*/ \ + \ + ISOboxType(k_trak,0x7472616BUL)SEPARATOR /* Types for the QuickTime timecode track.*/ \ + ISOboxType(k_tkhd,0x746B6864UL)SEPARATOR \ + ISOboxType(k_edts,0x65647473UL)SEPARATOR \ + ISOboxType(k_elst,0x656C7374UL)SEPARATOR \ + ISOboxType(k_mdia,0x6D646961UL)SEPARATOR \ + ISOboxType(k_mdhd,0x6D646864UL)SEPARATOR \ + ISOboxType(k_tmcd,0x746D6364UL)SEPARATOR \ + ISOboxType(k_mhlr,0x6D686C72UL)SEPARATOR \ + ISOboxType(k_minf,0x6D696E66UL)SEPARATOR \ + ISOboxType(k_stbl,0x7374626CUL)SEPARATOR \ + ISOboxType(k_stsd,0x73747364UL)SEPARATOR \ + ISOboxType(k_stsc,0x73747363UL)SEPARATOR \ + ISOboxType(k_stco,0x7374636FUL)SEPARATOR \ + ISOboxType(k_co64,0x636F3634UL)SEPARATOR \ + ISOboxType(k_dinf,0x64696E66UL)SEPARATOR \ + ISOboxType(k_dref,0x64726566UL)SEPARATOR \ + ISOboxType(k_alis,0x616C6973UL)SEPARATOR \ + \ + ISOboxType(k_meta,0x6D657461UL)SEPARATOR /* Types for the iTunes metadata boxes.*/ \ + ISOboxType(k_ilst,0x696C7374UL)SEPARATOR \ + ISOboxType(k_mdir,0x6D646972UL)SEPARATOR \ + ISOboxType(k_mean,0x6D65616EUL)SEPARATOR \ + ISOboxType(k_name,0x6E616D65UL)SEPARATOR \ + ISOboxType(k_data,0x64617461UL)SEPARATOR \ + ISOboxType(k_hyphens,0x2D2D2D2DUL)SEPARATOR \ + \ + ISOboxType(k_skip,0x736B6970UL)SEPARATOR /* Additional classic QuickTime top level boxes.*/ \ + ISOboxType(k_wide,0x77696465UL)SEPARATOR \ + ISOboxType(k_pnot,0x706E6F74UL)SEPARATOR \ + \ + ISOboxType(k_XMP_,0x584D505FUL) /* The QuickTime variant XMP box.*/ + +#define ISOBoxPrivateList +#define ISOboxType(x,y) x=y +#define SEPARATOR , + enum { + ISOBoxList + ISOBoxPrivateList + }; +#undef ISOboxType +#undef SEPARATOR + + + bool IsKnownBoxType(XMP_Uns32 boxType) ; + void TerminateGlobals(); + + static XMP_Uns8 k_xmpUUID [16] = { 0xBE, 0x7A, 0xCF, 0xCB, 0x97, 0xA9, 0x42, 0xE8, 0x9C, 0x71, 0x99, 0x94, 0x91, 0xE3, 0xAF, 0xAC }; + + struct BoxInfo { + XMP_Uns32 boxType; // In memory as native endian! + XMP_Uns32 headerSize; // Normally 8 or 16, less than 8 if available space is too small. + XMP_Uns64 contentSize; // Always the real size, never 0 for "to EoF". + XMP_Uns8 idUUID[16]; // ID of the uuid atom if present + BoxInfo() : boxType(0), headerSize(0), contentSize(0) + { + memset( idUUID, 0, 16 ); + }; + }; + + // Get basic info about a box in memory, returning a pointer to the following box. + const XMP_Uns8 * GetBoxInfo ( const XMP_Uns8 * boxPtr, const XMP_Uns8 * boxLimit, + BoxInfo * info, bool throwErrors = false ); + + // Get basic info about a box in a file, returning the offset of the following box. The I/O + // pointer is left at the start of the box's content. Returns the offset of the following box. + XMP_Uns64 GetBoxInfo ( XMP_IO* fileRef, const XMP_Uns64 boxOffset, const XMP_Uns64 boxLimit, + BoxInfo * info, bool doSeek = true, bool throwErrors = false ); + + // XMP_Uns32 CountChildBoxes ( XMP_IO* fileRef, const XMP_Uns64 childOffset, const XMP_Uns64 childLimit ); + +} // namespace ISO_Media + +// ================================================================================================= + +#endif // __ISOBaseMedia_Support_hpp__ diff --git a/core/libs/dngwriter/extra/xmp_sdk/XMPFiles/source/FormatSupport/MOOV_Support.cpp b/core/libs/dngwriter/extra/xmp_sdk/XMPFiles/source/FormatSupport/MOOV_Support.cpp new file mode 100644 index 0000000000..50b02eb466 --- /dev/null +++ b/core/libs/dngwriter/extra/xmp_sdk/XMPFiles/source/FormatSupport/MOOV_Support.cpp @@ -0,0 +1,576 @@ +// ================================================================================================= +// ADOBE SYSTEMS INCORPORATED +// Copyright 2009 Adobe Systems Incorporated +// All Rights Reserved +// +// NOTICE: Adobe permits you to use, modify, and distribute this file in accordance with the terms +// of the Adobe license agreement accompanying it. +// ================================================================================================= + +#include "XMPFiles/source/FormatSupport/MOOV_Support.hpp" + +#include "XMPFiles/source/FormatSupport/ISOBaseMedia_Support.hpp" + +#include + +// ================================================================================================= +/// \file MOOV_Support.cpp +/// \brief XMPFiles support for the 'moov' box in MPEG-4 and QuickTime files. +// ================================================================================================= + +// ================================================================================================= +// ================================================================================================= +// MOOV_Manager - The parsing and reading routines are all commmon +// ================================================================================================= +// ================================================================================================= + +#ifndef TraceParseMoovTree + #define TraceParseMoovTree 0 +#endif + +#ifndef TraceUpdateMoovTree + #define TraceUpdateMoovTree 0 +#endif + +// ================================================================================================= +// MOOV_Manager::PickContentPtr +// ============================ + +XMP_Uns8 * MOOV_Manager::PickContentPtr ( const BoxNode & node ) const +{ + if ( node.contentSize == 0 ) { + return 0; + } else if ( node.changed ) { + return (XMP_Uns8*) &node.changedContent[0]; + } else { + return (XMP_Uns8*) &this->fullSubtree[0] + node.offset + node.headerSize; + } +} // MOOV_Manager::PickContentPtr + +// ================================================================================================= +// MOOV_Manager::FillBoxInfo +// ========================= + +void MOOV_Manager::FillBoxInfo ( const BoxNode & node, BoxInfo * info ) const +{ + if ( info == 0 ) return; + + info->boxType = node.boxType; + info->childCount = (XMP_Uns32)node.children.size(); + info->contentSize = node.contentSize; + info->content = PickContentPtr ( node ); + if( node.boxType == ISOMedia::k_uuid ) + memcpy( info->idUUID, node.idUUID, 16); + +} // MOOV_Manager::FillBoxInfo + +// ================================================================================================= +// MOOV_Manager::GetBoxInfo +// ========================= + +void MOOV_Manager::GetBoxInfo ( const BoxRef ref, BoxInfo * info ) const +{ + + this->FillBoxInfo ( *((BoxNode*)ref), info ); + +} // MOOV_Manager::GetBoxInfo + +// ================================================================================================= +// MOOV_Manager::GetBox +// ==================== +// +// Find a box given the type path. Pick the first child of each type. + +MOOV_Manager::BoxRef MOOV_Manager::GetBox ( const char * boxPath, BoxInfo * info ) const +{ + size_t pathLen = strlen(boxPath); + XMP_Assert ( (pathLen >= 4) && XMP_LitNMatch ( boxPath, "moov", 4 ) ); + if ( info != 0 ) memset ( info, 0, sizeof(BoxInfo) ); + + const char * pathPtr = boxPath + 5; // Skip the "moov/" portion. + const char * pathEnd = boxPath + pathLen; + + BoxRef currRef = &this->moovNode; + + while ( pathPtr < pathEnd ) { + + XMP_Assert ( (pathEnd - pathPtr) >= 4 ); + XMP_Uns32 boxType = GetUns32BE ( pathPtr ); + pathPtr += 5; // ! Don't care that the last step goes 1 too far. + + currRef = this->GetTypeChild ( currRef, boxType, 0 ); + if ( currRef == 0 ) return 0; + + } + + this->FillBoxInfo ( *((BoxNode*)currRef), info ); + return currRef; + +} // MOOV_Manager::GetBox + +// ================================================================================================= +// MOOV_Manager::GetNthChild +// ========================= + +MOOV_Manager::BoxRef MOOV_Manager::GetNthChild ( BoxRef parentRef, size_t childIndex, BoxInfo * info ) const +{ + XMP_Assert ( parentRef != 0 ); + const BoxNode & parent = *((BoxNode*)parentRef); + if ( info != 0 ) memset ( info, 0, sizeof(BoxInfo) ); + + if ( childIndex >= parent.children.size() ) return 0; + + const BoxNode & currNode = parent.children[childIndex]; + + this->FillBoxInfo ( currNode, info ); + return &currNode; + +} // MOOV_Manager::GetNthChild + +// ================================================================================================= +// MOOV_Manager::GetTypeChild +// ========================== + +MOOV_Manager::BoxRef MOOV_Manager::GetTypeChild ( BoxRef parentRef, XMP_Uns32 childType, BoxInfo * info ) const +{ + XMP_Assert ( parentRef != 0 ); + const BoxNode & parent = *((BoxNode*)parentRef); + if ( info != 0 ) memset ( info, 0, sizeof(BoxInfo) ); + if ( parent.children.empty() ) return 0; + + size_t i = 0, limit = parent.children.size(); + for ( ; i < limit; ++i ) { + const BoxNode & currNode = parent.children[i]; + if ( currNode.boxType == childType ) { + this->FillBoxInfo ( currNode, info ); + return &currNode; + } + } + + return 0; + +} // MOOV_Manager::GetTypeChild + +// ================================================================================================= +// MOOV_Manager::GetParsedOffset +// ============================= + +XMP_Uns32 MOOV_Manager::GetParsedOffset ( BoxRef ref ) const +{ + XMP_Assert ( ref != 0 ); + const BoxNode & node = *((BoxNode*)ref); + + if ( node.changed ) return 0; + return node.offset; + +} // MOOV_Manager::GetParsedOffset + +// ================================================================================================= +// MOOV_Manager::GetHeaderSize +// =========================== + +XMP_Uns32 MOOV_Manager::GetHeaderSize ( BoxRef ref ) const +{ + XMP_Assert ( ref != 0 ); + const BoxNode & node = *((BoxNode*)ref); + + if ( node.changed ) return 0; + return node.headerSize; + +} // MOOV_Manager::GetHeaderSize + +// ================================================================================================= +// MOOV_Manager::ParseMemoryTree +// ============================= +// +// Parse the fullSubtree data, building the BoxNode tree for the stuff that we care about. Tolerate +// errors like content ending too soon, make a best effoert to parse what we can. + +void MOOV_Manager::ParseMemoryTree ( XMP_Uns8 fileMode ) +{ + this->fileMode = fileMode; + + this->moovNode.offset = this->moovNode.boxType = 0; + this->moovNode.headerSize = this->moovNode.contentSize = 0; + this->moovNode.children.clear(); + this->moovNode.changedContent.clear(); + this->moovNode.changed = false; + + if ( this->fullSubtree.empty() ) return; + + ISOMedia::BoxInfo moovInfo; + const XMP_Uns8 * moovOrigin = &this->fullSubtree[0]; + const XMP_Uns8 * moovLimit = moovOrigin + this->fullSubtree.size(); + + (void) ISOMedia::GetBoxInfo ( moovOrigin, moovLimit, &moovInfo ); + XMP_Enforce ( moovInfo.boxType == ISOMedia::k_moov ); + + XMP_Uns64 fullMoovSize = moovInfo.headerSize + moovInfo.contentSize; + if ( fullMoovSize > moovBoxSizeLimit ) { // From here on we know 32-bit offsets are safe. + XMP_Throw ( "Oversize 'moov' box", kXMPErr_EnforceFailure ); + } + + this->moovNode.boxType = ISOMedia::k_moov; + this->moovNode.headerSize = moovInfo.headerSize; + this->moovNode.contentSize = (XMP_Uns32)moovInfo.contentSize; + + bool ignoreMetaBoxes = (fileMode == kFileIsTraditionalQT); // ! Don't want, these don't follow ISO spec. + #if TraceParseMoovTree + fprintf ( stderr, "Parsing 'moov' subtree, moovNode @ 0x%X, ignoreMetaBoxes = %d\n", + &this->moovNode, ignoreMetaBoxes ); + #endif + this->ParseNestedBoxes ( &this->moovNode, "moov", ignoreMetaBoxes ); + +} // MOOV_Manager::ParseMemoryTree + +// ================================================================================================= +// MOOV_Manager::ParseNestedBoxes +// ============================== +// +// Add the current level of child boxes to the parent node, recurse as appropriate. + +void MOOV_Manager::ParseNestedBoxes ( BoxNode * parentNode, const std::string & parentPath, bool ignoreMetaBoxes ) +{ + ISOMedia::BoxInfo isoInfo; + const XMP_Uns8 * moovOrigin = &this->fullSubtree[0]; + const XMP_Uns8 * childOrigin = moovOrigin + parentNode->offset + parentNode->headerSize; + const XMP_Uns8 * childLimit = childOrigin + parentNode->contentSize; + const XMP_Uns8 * nextChild; + + parentNode->contentSize = 0; // Exclude nested box size. + if ( parentNode->boxType == ISOMedia::k_meta ) { // ! The 'meta' box is a FullBox. + parentNode->contentSize = 4; + childOrigin += 4; + } + + for ( const XMP_Uns8 * currChild = childOrigin; currChild < childLimit; currChild = nextChild ) { + + nextChild = ISOMedia::GetBoxInfo ( currChild, childLimit, &isoInfo ); + if ( (isoInfo.boxType == 0) && + (isoInfo.headerSize < 8) && + (isoInfo.contentSize == 0) ) continue; // Skip trailing padding that QT sometimes writes. + + XMP_Uns32 childOffset = (XMP_Uns32) (currChild - moovOrigin); + if( isoInfo.boxType == ISOMedia::k_uuid ) + parentNode->children.push_back ( BoxNode ( childOffset, isoInfo.boxType, isoInfo.headerSize, (XMP_Uns8 *)isoInfo.idUUID, (XMP_Uns32)isoInfo.contentSize ) ); + else + parentNode->children.push_back ( BoxNode ( childOffset, isoInfo.boxType, isoInfo.headerSize, (XMP_Uns32)isoInfo.contentSize ) ); + BoxNode * newChild = &parentNode->children.back(); + + #if TraceParseMoovTree + size_t depth = (parentPath.size()+1) / 5; + for ( size_t i = 0; i < depth; ++i ) fprintf ( stderr, " " ); + XMP_Uns32 be32 = MakeUns32BE ( newChild->boxType ); + XMP_Uns32 addr32 = (XMP_Uns32) this->PickContentPtr ( *newChild ); + fprintf ( stderr, " Parsed %s/%.4s, offset 0x%X, size %d, content @ 0x%X, BoxNode @ 0x%X\n", + parentPath.c_str(), &be32, newChild->offset, newChild->contentSize, addr32, newChild ); + #endif + + const char * pathSuffix = 0; // Set to non-zero for boxes of interest. + char buffer[6]; buffer[0] = 0; + + switch ( isoInfo.boxType ) { // Want these boxes regardless of parent. + case ISOMedia::k_udta : pathSuffix = "/udta"; break; + case ISOMedia::k_meta : pathSuffix = "/meta"; break; + case ISOMedia::k_ilst : pathSuffix = "/ilst"; break; + case ISOMedia::k_trak : pathSuffix = "/trak"; break; + case ISOMedia::k_edts : pathSuffix = "/edts"; break; + case ISOMedia::k_mdia : pathSuffix = "/mdia"; break; + case ISOMedia::k_minf : pathSuffix = "/minf"; break; + case ISOMedia::k_dinf : pathSuffix = "/dinf"; break; + case ISOMedia::k_stbl : pathSuffix = "/stbl"; break; + } + if ( pathSuffix != 0 ) { + this->ParseNestedBoxes ( newChild, (parentPath + pathSuffix), ignoreMetaBoxes ); + } + + } + +} // MOOV_Manager::ParseNestedBoxes + +// ================================================================================================= +// MOOV_Manager::NoteChange +// ======================== + +void MOOV_Manager::NoteChange() +{ + + this->moovNode.changed = true; + +} // MOOV_Manager::NoteChange + +// ================================================================================================= +// MOOV_Manager::SetBox +// ==================== +// +// Save the new data, set this box's changed flag, and set the top changed flag. + +void MOOV_Manager::SetBox ( BoxRef theBox, const void* dataPtr, XMP_Uns32 size , const XMP_Uns8 * idUUID ) +{ + XMP_Enforce ( size < moovBoxSizeLimit ); + BoxNode * node = (BoxNode*)theBox; + + if ( node->contentSize == size ) { + if( node->boxType == ISOMedia::k_uuid && idUUID != 0 ) + { + memcpy ( node->idUUID, idUUID, 16 ); + this->moovNode.changed = true; + } + XMP_Uns8 * oldContent = PickContentPtr ( *node ); + if ( memcmp ( oldContent, dataPtr, size ) == 0 ) return; // No change. + memcpy ( oldContent, dataPtr, size ); // Update the old content in-place + this->moovNode.changed = true; + + #if TraceUpdateMoovTree + XMP_Uns32 be32 = MakeUns32BE ( node->boxType ); + fprintf ( stderr, "Updated '%.4s', parse offset 0x%X, same size\n", &be32, node->offset ); + #endif + + } else { + + node->changedContent.assign ( size, 0 ); // Fill with 0's first to get the storage. + memcpy ( &node->changedContent[0], dataPtr, size ); + node->contentSize = size; + node->changed = true; + if( node->boxType == ISOMedia::k_uuid && idUUID != 0) + memcpy ( node->idUUID, idUUID, 16 ); + this->moovNode.changed = true; + + #if TraceUpdateMoovTree + XMP_Uns32 be32 = MakeUns32BE ( node->boxType ); + XMP_Uns32 addr32 = (XMP_Uns32) this->PickContentPtr ( *node ); + fprintf ( stderr, "Updated '%.4s', parse offset 0x%X, new size %d, new content @ 0x%X\n", + &be32, node->offset, node->contentSize, addr32 ); + #endif + + } + +} // MOOV_Manager::SetBox + +// ================================================================================================= +// MOOV_Manager::SetBox +// ==================== +// +// Like above, but create the path to the box if necessary. + +void MOOV_Manager::SetBox ( const char * boxPath, const void* dataPtr, XMP_Uns32 size , const XMP_Uns8 * idUUID ) +{ + XMP_Enforce ( size < moovBoxSizeLimit ); + + size_t pathLen = strlen(boxPath); + XMP_Assert ( (pathLen >= 4) && XMP_LitNMatch ( boxPath, "moov", 4 ) ); + + const char * pathPtr = boxPath + 5; // Skip the "moov/" portion. + const char * pathEnd = boxPath + pathLen; + + BoxRef parentRef = 0; + BoxRef currRef = &this->moovNode; + + while ( pathPtr < pathEnd ) { + + XMP_Assert ( (pathEnd - pathPtr) >= 4 ); + XMP_Uns32 boxType = GetUns32BE ( pathPtr ); + pathPtr += 5; // ! Don't care that the last step goes 1 too far. + + parentRef = currRef; + currRef = this->GetTypeChild ( parentRef, boxType, 0 ); + if ( currRef == 0 ) currRef = this->AddChildBox ( parentRef, boxType, 0, 0 , idUUID ); + + } + + this->SetBox ( currRef, dataPtr, size, idUUID ); + +} // MOOV_Manager::SetBox + +// ================================================================================================= +// MOOV_Manager::AddChildBox +// ========================= + +MOOV_Manager::BoxRef MOOV_Manager::AddChildBox ( BoxRef parentRef, XMP_Uns32 childType, const void* dataPtr, XMP_Uns32 size , const XMP_Uns8 * idUUID ) +{ + BoxNode * parent = (BoxNode*)parentRef; + XMP_Assert ( parent != 0 ); + + if( childType == ISOMedia::k_uuid && idUUID != 0) + parent->children.push_back ( BoxNode ( 0, childType, 0, idUUID, 0 ) ); + else + parent->children.push_back ( BoxNode ( 0, childType, 0, 0 ) ); + BoxNode * newNode = &parent->children.back(); + this->SetBox ( newNode, dataPtr, size ); + + return newNode; + +} // MOOV_Manager::AddChildBox + +// ================================================================================================= +// MOOV_Manager::DeleteNthChild +// ============================ + +bool MOOV_Manager::DeleteNthChild ( BoxRef parentRef, size_t childIndex ) +{ + BoxNode * parent = (BoxNode*)parentRef; + + if ( childIndex >= parent->children.size() ) return false; + + parent->children.erase ( parent->children.begin() + childIndex ); + return true; + +} // MOOV_Manager::DeleteNthChild + +// ================================================================================================= +// MOOV_Manager::DeleteTypeChild +// ============================= + +bool MOOV_Manager::DeleteTypeChild ( BoxRef parentRef, XMP_Uns32 childType ) +{ + BoxNode * parent = (BoxNode*)parentRef; + + BoxListPos child = parent->children.begin(); + BoxListPos limit = parent->children.end(); + + for ( ; child != limit; ++child ) { + if ( child->boxType == childType ) { + parent->children.erase ( child ); + this->moovNode.changed = true; + return true; + } + } + + return false; + +} // MOOV_Manager::DeleteTypeChild + +// ================================================================================================= +// MOOV_Manager::NewSubtreeSize +// ============================ +// +// Determine the new (changed) size of a subtree. Ignore 'free' and 'wide' boxes. + +XMP_Uns32 MOOV_Manager::NewSubtreeSize ( const BoxNode & node, const std::string & parentPath ) +{ + XMP_Uns32 subtreeSize = 8 + node.contentSize; // All boxes will have 8 byte headers. + + if( node.boxType == ISOMedia::k_uuid ) + subtreeSize += 16; // id of uuid is 16 bytes long + if ( (node.boxType == ISOMedia::k_free) || (node.boxType == ISOMedia::k_wide) ) { + } + + for ( size_t i = 0, limit = node.children.size(); i < limit; ++i ) { + + char suffix[6]; + suffix[0] = '/'; + PutUns32BE ( node.boxType, &suffix[1] ); + suffix[5] = 0; + std::string nodePath = parentPath + suffix; + + subtreeSize += this->NewSubtreeSize ( node.children[i], nodePath ); + XMP_Enforce ( subtreeSize < moovBoxSizeLimit ); + + } + + return subtreeSize; + +} // MOOV_Manager::NewSubtreeSize + +// ================================================================================================= +// MOOV_Manager::AppendNewSubtree +// ============================== +// +// Append this node's header, content, and children. Because the 'meta' box is a FullBox with nested +// boxes, there can be both content and children. Ignore 'free' and 'wide' boxes. + +#define IncrNewPtr(count) { newPtr += count; XMP_Enforce ( newPtr <= newEnd ); } + +#if TraceUpdateMoovTree + static XMP_Uns8 * newOrigin; +#endif + +XMP_Uns8 * MOOV_Manager::AppendNewSubtree ( const BoxNode & node, const std::string & parentPath, + XMP_Uns8 * newPtr, XMP_Uns8 * newEnd ) +{ + if ( (node.boxType == ISOMedia::k_free) || (node.boxType == ISOMedia::k_wide) ) { + } + + XMP_Assert ( (node.boxType != ISOMedia::k_meta) ? (node.children.empty() || (node.contentSize == 0)) : + (node.children.empty() || (node.contentSize == 4)) ); + + XMP_Enforce ( (XMP_Uns32)(newEnd - newPtr) >= (8 + node.contentSize) ); + + #if TraceUpdateMoovTree + XMP_Uns32 be32 = MakeUns32BE ( node.boxType ); + XMP_Uns32 newOffset = (XMP_Uns32) (newPtr - newOrigin); + XMP_Uns32 addr32 = (XMP_Uns32) this->PickContentPtr ( node ); + fprintf ( stderr, " Appending %s/%.4s @ 0x%X, size %d, content @ 0x%X\n", + parentPath.c_str(), &be32, newOffset, node.contentSize, addr32 ); + #endif + + // Leave the size as 0 for now, append the type and content. + + XMP_Uns8 * boxOrigin = newPtr; // Save origin to fill in the final size. + PutUns32BE ( node.boxType, (newPtr + 4) ); + IncrNewPtr ( 8 ); + if( node.boxType == ISOMedia::k_uuid ) // For uuid, additional 16 bytes is stored for ID + { + XMP_Enforce ( (XMP_Uns32)(newEnd - newPtr) >= ( 16 + node.contentSize ) ); + memcpy( newPtr, node.idUUID, 16 ); + IncrNewPtr ( 16 ); + } + if ( node.contentSize != 0 ) { + const XMP_Uns8 * content = PickContentPtr( node ); + memcpy ( newPtr, content, node.contentSize ); + IncrNewPtr ( node.contentSize ); + } + + // Append the nested boxes. + + if ( ! node.children.empty() ) { + + char suffix[6]; + suffix[0] = '/'; + PutUns32BE ( node.boxType, &suffix[1] ); + suffix[5] = 0; + std::string nodePath = parentPath + suffix; + + for ( size_t i = 0, limit = node.children.size(); i < limit; ++i ) { + newPtr = this->AppendNewSubtree ( node.children[i], nodePath, newPtr, newEnd ); + } + + } + + // Fill in the final size. + + PutUns32BE ( (XMP_Uns32)(newPtr - boxOrigin), boxOrigin ); + + return newPtr; + +} // MOOV_Manager::AppendNewSubtree + +// ================================================================================================= +// MOOV_Manager::UpdateMemoryTree +// ============================== + +void MOOV_Manager::UpdateMemoryTree() +{ + if ( ! this->IsChanged() ) return; + + XMP_Uns32 newSize = this->NewSubtreeSize ( this->moovNode, "" ); + XMP_Enforce ( newSize < moovBoxSizeLimit ); + + RawDataBlock newData; + newData.assign ( newSize, 0 ); // Prefill with zeroes, can't append multiple items to a vector. + + XMP_Uns8 * newPtr = &newData[0]; + XMP_Uns8 * newEnd = newPtr + newSize; + + #if TraceUpdateMoovTree + fprintf ( stderr, "Starting MOOV_Manager::UpdateMemoryTree\n" ); + newOrigin = newPtr; + #endif + + XMP_Uns8 * trueEnd = this->AppendNewSubtree ( this->moovNode, "", newPtr, newEnd ); + XMP_Enforce ( trueEnd == newEnd ); + + this->fullSubtree.swap ( newData ); + this->ParseMemoryTree ( this->fileMode ); + +} // MOOV_Manager::UpdateMemoryTree diff --git a/core/libs/dngwriter/extra/xmp_sdk/XMPFiles/source/FormatSupport/MOOV_Support.hpp b/core/libs/dngwriter/extra/xmp_sdk/XMPFiles/source/FormatSupport/MOOV_Support.hpp new file mode 100644 index 0000000000..9dc785cdb9 --- /dev/null +++ b/core/libs/dngwriter/extra/xmp_sdk/XMPFiles/source/FormatSupport/MOOV_Support.hpp @@ -0,0 +1,238 @@ +#ifndef __MOOV_Support_hpp__ +#define __MOOV_Support_hpp__ 1 + +// ================================================================================================= +// ADOBE SYSTEMS INCORPORATED +// Copyright 2009 Adobe Systems Incorporated +// All Rights Reserved +// +// NOTICE: Adobe permits you to use, modify, and distribute this file in accordance with the terms +// of the Adobe license agreement accompanying it. +// ================================================================================================= + +#include "public/include/XMP_Environment.h" // ! This must be the first include. +#include "public/include/XMP_Const.h" + +#include "source/EndianUtils.hpp" + +#include "XMPFiles/source/XMPFiles_Impl.hpp" + +#include + +#define moovBoxSizeLimit 100*1024*1024 + +// ================================================================================================= +// MOOV_Manager +// ============ + +class MOOV_Manager { +public: + + // --------------------------------------------------------------------------------------------- + // Types and constants + + enum { // Values for fileMode. + kFileIsNormalISO = 0, // A "normal" MPEG-4 file, no 'qt ' compatible brand. + kFileIsModernQT = 1, // Has an 'ftyp' box and 'qt ' compatible brand. + kFileIsTraditionalQT = 2 // Old QuickTime, no 'ftyp' box. + }; + + typedef const void * BoxRef; // Valid until a sibling or higher box is added or deleted. + + struct BoxInfo { + XMP_Uns32 boxType; // In memory as native endian, compares work with ISOMedia::k_* constants. + XMP_Uns32 childCount; // ! A 'meta' box has both content (version/flags) and children! + XMP_Uns32 contentSize; // Does not include the size of nested boxes. + const XMP_Uns8 * content; // Null if contentSize is zero. + XMP_Uns8 idUUID[16]; // ID of the uuid atom if present + BoxInfo() : boxType(0), childCount(0), contentSize(0), content(0) + { + memset ( idUUID ,0, 16 ); + }; + }; + + // --------------------------------------------------------------------------------------------- + // GetBox - Pick a box given a '/' separated list of box types. Picks the 1st of each type. + // GetBoxInfo - Get the info if we already have the ref. + // GetNthChild - Pick the overall n-th child of the parent, zero based. + // GetTypeChild - Pick the first child of the given type. + // GetParsedOffset - Get the box's offset in the parsed tree, 0 if changed since parsing. + // GetHeaderSize - Get the box's header size in the parsed tree, 0 if changed since parsing. + + BoxRef GetBox ( const char * boxPath, BoxInfo * info ) const; + + void GetBoxInfo ( const BoxRef ref, BoxInfo * info ) const; + + BoxRef GetNthChild ( BoxRef parentRef, size_t childIndex, BoxInfo * info ) const; + BoxRef GetTypeChild ( BoxRef parentRef, XMP_Uns32 childType, BoxInfo * info ) const; + + XMP_Uns32 GetParsedOffset ( BoxRef ref ) const; + XMP_Uns32 GetHeaderSize ( BoxRef ref ) const; + + // --------------------------------------------------------------------------------------------- + // NoteChange - Note overall change, value was directly replaced. + // SetBox(ref) - Replace the content with a copy of the given data. + // SetBox(path) - Like above, but creating path to the box if necessary. + // AddChildBox - Add a child of the given type, using a copy of the given data (may be null) + + void NoteChange(); + + void SetBox ( BoxRef theBox, const void* dataPtr, XMP_Uns32 size , const XMP_Uns8 * idUUID = 0 ); + void SetBox ( const char * boxPath, const void* dataPtr, XMP_Uns32 size , const XMP_Uns8 * idUUID = 0 ); + + BoxRef AddChildBox ( BoxRef parentRef, XMP_Uns32 childType, const void * dataPtr, XMP_Uns32 size , const XMP_Uns8 * idUUID = 0 ); + + // --------------------------------------------------------------------------------------------- + // DeleteNthChild - Delete the overall n-th child, return true if there was one. + // DeleteTypeChild - Delete the first child of the given type, return true if there was one. + + bool DeleteNthChild ( BoxRef parentRef, size_t childIndex ); + bool DeleteTypeChild ( BoxRef parentRef, XMP_Uns32 childType ); + + // --------------------------------------------------------------------------------------------- + + bool IsChanged() const { return this->moovNode.changed; }; + + // --------------------------------------------------------------------------------------------- + // The client is expected to fill in fullSubtree before calling ParseMemoryTree, and directly + // use fullSubtree after calling UpdateMemoryTree. + // + // IMPORTANT: We only support cases where the 'moov' subtree is significantly less than 4 GB, in + // particular with a threshold of probably 100 MB. This has 2 big impacts: we can safely use + // 32-bit offsets and sizes, and comfortably assume everything will fit in available heap space. + + RawDataBlock fullSubtree; // The entire 'moov' box, straight from the file or from UpdateMemoryTree. + + void ParseMemoryTree ( XMP_Uns8 fileMode ); + void UpdateMemoryTree(); + + // --------------------------------------------------------------------------------------------- + + #pragma pack (push, 1) // ! These must match the file layout! + + struct Content_mvhd_0 { + XMP_Uns32 vFlags; // 0 + XMP_Uns32 creationTime; // 4 + XMP_Uns32 modificationTime; // 8 + XMP_Uns32 timescale; // 12 + XMP_Uns32 duration; // 16 + XMP_Int32 rate; // 20 + XMP_Int16 volume; // 24 + XMP_Uns16 pad_1; // 26 + XMP_Uns32 pad_2, pad_3; // 28 + XMP_Int32 matrix [9]; // 36 + XMP_Uns32 preDef [6]; // 72 + XMP_Uns32 nextTrackID; // 96 + }; // 100 + + struct Content_mvhd_1 { + XMP_Uns32 vFlags; // 0 + XMP_Uns64 creationTime; // 4 + XMP_Uns64 modificationTime; // 12 + XMP_Uns32 timescale; // 20 + XMP_Uns64 duration; // 24 + XMP_Int32 rate; // 32 + XMP_Int16 volume; // 36 + XMP_Uns16 pad_1; // 38 + XMP_Uns32 pad_2, pad_3; // 40 + XMP_Int32 matrix [9]; // 48 + XMP_Uns32 preDef [6]; // 84 + XMP_Uns32 nextTrackID; // 108 + }; // 112 + + struct Content_hdlr { // An 'hdlr' box as defined by ISO 14496-12. Maps OK to the QuickTime box. + XMP_Uns32 versionFlags; // 0 + XMP_Uns32 preDef; // 4 + XMP_Uns32 handlerType; // 8 + XMP_Uns32 reserved [3]; // 12 + // Plus optional component name string, null terminated UTF-8. + }; // 24 + + struct Content_stsd_entry { + XMP_Uns32 entrySize; // 0 + XMP_Uns32 format; // 4 + XMP_Uns8 reserved_1 [6]; // 8 + XMP_Uns16 dataRefIndex; // 14 + XMP_Uns32 reserved_2; // 16 + XMP_Uns32 flags; // 20 + XMP_Uns32 timeScale; // 24 + XMP_Uns32 frameDuration; // 28 + XMP_Uns8 frameCount; // 32 + XMP_Uns8 reserved_3; // 33 + // Plus optional trailing ISO boxes. + }; // 34 + + struct Content_stsc_entry { + XMP_Uns32 firstChunkNumber; // 0 + XMP_Uns32 samplesPerChunk; // 4 + XMP_Uns32 sampleDescrID; // 8 + }; // 12 + + #pragma pack( pop ) + +#if SUNOS_SPARC || XMP_IOS_ARM + #pragma pack( ) +#endif //#if SUNOS_SPARC || XMP_IOS_ARM + + // --------------------------------------------------------------------------------------------- + + MOOV_Manager() : fileMode(0) + { + XMP_Assert ( sizeof ( Content_mvhd_0 ) == 100 ); // Make sure the structs really are packed. + XMP_Assert ( sizeof ( Content_mvhd_1 ) == 112 ); + XMP_Assert ( sizeof ( Content_hdlr ) == 24 ); + XMP_Assert ( sizeof ( Content_stsd_entry ) == 34 ); + XMP_Assert ( sizeof ( Content_stsc_entry ) == 12 ); + }; + + virtual ~MOOV_Manager() {}; + +private: + + struct BoxNode; + typedef std::vector BoxList; + typedef BoxList::iterator BoxListPos; + + struct BoxNode { + // ! Don't have a parent link, it will get destroyed by vector growth! + + XMP_Uns32 offset; // The offset in the fullSubtree, 0 if not in the parse. + XMP_Uns32 boxType; + XMP_Uns32 headerSize; // The actual header size in the fullSubtree, 0 if not in the parse. + XMP_Uns32 contentSize; // The current content size, does not include nested boxes or id. + BoxList children; + XMP_Uns8 idUUID[16]; + RawDataBlock changedContent; // Might be empty even if changed is true. + bool changed; // If true, the content is in changedContent, else in fullSubtree. + + BoxNode() : offset(0), boxType(0), headerSize(0), contentSize(0), changed(false) + { + memset ( idUUID, 0, 16 ); + }; + BoxNode ( XMP_Uns32 _offset, XMP_Uns32 _boxType, XMP_Uns32 _headerSize, XMP_Uns32 _contentSize ) + : offset(_offset), boxType(_boxType), headerSize(_headerSize), contentSize(_contentSize), changed(false) + { + memset ( idUUID, 0, 16 ); + }; + BoxNode ( XMP_Uns32 _offset, XMP_Uns32 _boxType, XMP_Uns32 _headerSize, const XMP_Uns8 * _idUUID, XMP_Uns32 _contentSize ) + : offset(_offset), boxType(_boxType), headerSize(_headerSize), contentSize(_contentSize), changed(false) + { + memcpy ( idUUID, _idUUID, 16 ); + }; + }; + + XMP_Uns8 fileMode; + BoxNode moovNode; + + void ParseNestedBoxes ( BoxNode * parentNode, const std::string & parentPath, bool ignoreMetaBoxes ); + + XMP_Uns8 * PickContentPtr ( const BoxNode & node ) const; + void FillBoxInfo ( const BoxNode & node, BoxInfo * info ) const; + + XMP_Uns32 NewSubtreeSize ( const BoxNode & node, const std::string & parentPath ); + XMP_Uns8 * AppendNewSubtree ( const BoxNode & node, const std::string & parentPath, + XMP_Uns8 * newPtr, XMP_Uns8 * newEnd ); + +}; // MOOV_Manager + +#endif // __MOOV_Support_hpp__ diff --git a/core/libs/dngwriter/extra/xmp_sdk/XMPFiles/source/FormatSupport/MacScriptExtracts.h b/core/libs/dngwriter/extra/xmp_sdk/XMPFiles/source/FormatSupport/MacScriptExtracts.h new file mode 100644 index 0000000000..98561831b8 --- /dev/null +++ b/core/libs/dngwriter/extra/xmp_sdk/XMPFiles/source/FormatSupport/MacScriptExtracts.h @@ -0,0 +1,244 @@ +#ifndef __MacScriptExtracts__ +#define __MacScriptExtracts__ + +// Extracts of script (smXyz) and language (langXyz) enums from Apple's old Script.h. +// These are used to support "traditional" QuickTime metadata processing. + +/* + Script codes: + These specify a Mac OS encoding that is related to a FOND ID range. + Some of the encodings have several variants (e.g. for different localized systems) + which all share the same script code. + Not all of these script codes are currently supported by Apple software. + Notes: + - Script code 0 (smRoman) is also used (instead of smGreek) for the Greek encoding + in the Greek localized system. + - Script code 28 (smEthiopic) is also used for the Inuit encoding in the Inuktitut + system. +*/ +enum { + smRoman = 0, + smJapanese = 1, + smTradChinese = 2, /* Traditional Chinese*/ + smKorean = 3, + smArabic = 4, + smHebrew = 5, + smGreek = 6, + smCyrillic = 7, + smRSymbol = 8, /* Right-left symbol*/ + smDevanagari = 9, + smGurmukhi = 10, + smGujarati = 11, + smOriya = 12, + smBengali = 13, + smTamil = 14, + smTelugu = 15, + smKannada = 16, /* Kannada/Kanarese*/ + smMalayalam = 17, + smSinhalese = 18, + smBurmese = 19, + smKhmer = 20, /* Khmer/Cambodian*/ + smThai = 21, + smLao = 22, + smGeorgian = 23, + smArmenian = 24, + smSimpChinese = 25, /* Simplified Chinese*/ + smTibetan = 26, + smMongolian = 27, + smEthiopic = 28, + smGeez = 28, /* Synonym for smEthiopic*/ + smCentralEuroRoman = 29, /* For Czech, Slovak, Polish, Hungarian, Baltic langs*/ + smVietnamese = 30, + smExtArabic = 31, /* extended Arabic*/ + smUninterp = 32 /* uninterpreted symbols, e.g. palette symbols*/ +}; + +/* Extended script code for full Unicode input*/ +enum { + smUnicodeScript = 0x7E +}; + +/* Obsolete script code names (kept for backward compatibility):*/ +enum { + smChinese = 2, /* (Use smTradChinese or smSimpChinese)*/ + smRussian = 7, /* Use smCyrillic*/ + /* smMaldivian = 25: deleted, no code for Maldivian*/ + smLaotian = 22, /* Use smLao */ + smAmharic = 28, /* Use smEthiopic or smGeez*/ + smSlavic = 29, /* Use smCentralEuroRoman*/ + smEastEurRoman = 29, /* Use smCentralEuroRoman*/ + smSindhi = 31, /* Use smExtArabic*/ + smKlingon = 32 +}; + +/* + Language codes: + These specify a language implemented using a particular Mac OS encoding. + Not all of these language codes are currently supported by Apple software. +*/ +enum { + langEnglish = 0, /* smRoman script*/ + langFrench = 1, /* smRoman script*/ + langGerman = 2, /* smRoman script*/ + langItalian = 3, /* smRoman script*/ + langDutch = 4, /* smRoman script*/ + langSwedish = 5, /* smRoman script*/ + langSpanish = 6, /* smRoman script*/ + langDanish = 7, /* smRoman script*/ + langPortuguese = 8, /* smRoman script*/ + langNorwegian = 9, /* (Bokmal) smRoman script*/ + langHebrew = 10, /* smHebrew script*/ + langJapanese = 11, /* smJapanese script*/ + langArabic = 12, /* smArabic script*/ + langFinnish = 13, /* smRoman script*/ + langGreek = 14, /* Greek script (monotonic) using smRoman script code*/ + langIcelandic = 15, /* modified smRoman/Icelandic script*/ + langMaltese = 16, /* Roman script*/ + langTurkish = 17, /* modified smRoman/Turkish script*/ + langCroatian = 18, /* modified smRoman/Croatian script*/ + langTradChinese = 19, /* Chinese (Mandarin) in traditional characters*/ + langUrdu = 20, /* smArabic script*/ + langHindi = 21, /* smDevanagari script*/ + langThai = 22, /* smThai script*/ + langKorean = 23 /* smKorean script*/ +}; + +enum { + langLithuanian = 24, /* smCentralEuroRoman script*/ + langPolish = 25, /* smCentralEuroRoman script*/ + langHungarian = 26, /* smCentralEuroRoman script*/ + langEstonian = 27, /* smCentralEuroRoman script*/ + langLatvian = 28, /* smCentralEuroRoman script*/ + langSami = 29, /* language of the Sami people of N. Scandinavia */ + langFaroese = 30, /* modified smRoman/Icelandic script */ + langFarsi = 31, /* modified smArabic/Farsi script*/ + langPersian = 31, /* Synonym for langFarsi*/ + langRussian = 32, /* smCyrillic script*/ + langSimpChinese = 33, /* Chinese (Mandarin) in simplified characters*/ + langFlemish = 34, /* smRoman script*/ + langIrishGaelic = 35, /* smRoman or modified smRoman/Celtic script (without dot above) */ + langAlbanian = 36, /* smRoman script*/ + langRomanian = 37, /* modified smRoman/Romanian script*/ + langCzech = 38, /* smCentralEuroRoman script*/ + langSlovak = 39, /* smCentralEuroRoman script*/ + langSlovenian = 40, /* modified smRoman/Croatian script*/ + langYiddish = 41, /* smHebrew script*/ + langSerbian = 42, /* smCyrillic script*/ + langMacedonian = 43, /* smCyrillic script*/ + langBulgarian = 44, /* smCyrillic script*/ + langUkrainian = 45, /* modified smCyrillic/Ukrainian script*/ + langByelorussian = 46, /* smCyrillic script*/ + langBelorussian = 46 /* Synonym for langByelorussian */ +}; + +enum { + langUzbek = 47, /* Cyrillic script*/ + langKazakh = 48, /* Cyrillic script*/ + langAzerbaijani = 49, /* Azerbaijani in Cyrillic script*/ + langAzerbaijanAr = 50, /* Azerbaijani in Arabic script*/ + langArmenian = 51, /* smArmenian script*/ + langGeorgian = 52, /* smGeorgian script*/ + langMoldavian = 53, /* smCyrillic script*/ + langKirghiz = 54, /* Cyrillic script*/ + langTajiki = 55, /* Cyrillic script*/ + langTurkmen = 56, /* Cyrillic script*/ + langMongolian = 57, /* Mongolian in smMongolian script*/ + langMongolianCyr = 58, /* Mongolian in Cyrillic script*/ + langPashto = 59, /* Arabic script*/ + langKurdish = 60, /* smArabic script*/ + langKashmiri = 61, /* Arabic script*/ + langSindhi = 62, /* Arabic script*/ + langTibetan = 63, /* smTibetan script*/ + langNepali = 64, /* smDevanagari script*/ + langSanskrit = 65, /* smDevanagari script*/ + langMarathi = 66, /* smDevanagari script*/ + langBengali = 67, /* smBengali script*/ + langAssamese = 68, /* smBengali script*/ + langGujarati = 69, /* smGujarati script*/ + langPunjabi = 70 /* smGurmukhi script*/ +}; + +enum { + langOriya = 71, /* smOriya script*/ + langMalayalam = 72, /* smMalayalam script*/ + langKannada = 73, /* smKannada script*/ + langTamil = 74, /* smTamil script*/ + langTelugu = 75, /* smTelugu script*/ + langSinhalese = 76, /* smSinhalese script*/ + langBurmese = 77, /* smBurmese script*/ + langKhmer = 78, /* smKhmer script*/ + langLao = 79, /* smLao script*/ + langVietnamese = 80, /* smVietnamese script*/ + langIndonesian = 81, /* smRoman script*/ + langTagalog = 82, /* Roman script*/ + langMalayRoman = 83, /* Malay in smRoman script*/ + langMalayArabic = 84, /* Malay in Arabic script*/ + langAmharic = 85, /* smEthiopic script*/ + langTigrinya = 86, /* smEthiopic script*/ + langOromo = 87, /* smEthiopic script*/ + langSomali = 88, /* smRoman script*/ + langSwahili = 89, /* smRoman script*/ + langKinyarwanda = 90, /* smRoman script*/ + langRuanda = 90, /* synonym for langKinyarwanda*/ + langRundi = 91, /* smRoman script*/ + langNyanja = 92, /* smRoman script*/ + langChewa = 92, /* synonym for langNyanja*/ + langMalagasy = 93, /* smRoman script*/ + langEsperanto = 94 /* Roman script*/ +}; + +enum { + langWelsh = 128, /* modified smRoman/Celtic script*/ + langBasque = 129, /* smRoman script*/ + langCatalan = 130, /* smRoman script*/ + langLatin = 131, /* smRoman script*/ + langQuechua = 132, /* smRoman script*/ + langGuarani = 133, /* smRoman script*/ + langAymara = 134, /* smRoman script*/ + langTatar = 135, /* Cyrillic script*/ + langUighur = 136, /* Arabic script*/ + langDzongkha = 137, /* (lang of Bhutan) smTibetan script*/ + langJavaneseRom = 138, /* Javanese in smRoman script*/ + langSundaneseRom = 139, /* Sundanese in smRoman script*/ + langGalician = 140, /* smRoman script*/ + langAfrikaans = 141 /* smRoman script */ +}; + +enum { + langBreton = 142, /* smRoman or modified smRoman/Celtic script */ + langInuktitut = 143, /* Inuit script using smEthiopic script code */ + langScottishGaelic = 144, /* smRoman or modified smRoman/Celtic script */ + langManxGaelic = 145, /* smRoman or modified smRoman/Celtic script */ + langIrishGaelicScript = 146, /* modified smRoman/Gaelic script (using dot above) */ + langTongan = 147, /* smRoman script */ + langGreekAncient = 148, /* Classical Greek, polytonic orthography */ + langGreenlandic = 149, /* smRoman script */ + langAzerbaijanRoman = 150, /* Azerbaijani in Roman script */ + langNynorsk = 151 /* Norwegian Nyorsk in smRoman*/ +}; + +enum { + langUnspecified = 32767 /* Special code for use in resources (such as 'itlm') */ +}; + +/* + Obsolete language code names (kept for backward compatibility): + Misspelled, ambiguous, misleading, considered pejorative, archaic, etc. +*/ +enum { + langPortugese = 8, /* Use langPortuguese*/ + langMalta = 16, /* Use langMaltese*/ + langYugoslavian = 18, /* (use langCroatian, langSerbian, etc.)*/ + langChinese = 19, /* (use langTradChinese or langSimpChinese)*/ + langLettish = 28, /* Use langLatvian */ + langLapponian = 29, /* Use langSami*/ + langLappish = 29, /* Use langSami*/ + langSaamisk = 29, /* Use langSami */ + langFaeroese = 30, /* Use langFaroese */ + langIrish = 35, /* Use langIrishGaelic */ + langGalla = 87, /* Use langOromo */ + langAfricaans = 141, /* Use langAfrikaans */ + langGreekPoly = 148 /* Use langGreekAncient*/ +}; + +#endif /* __MacScriptExtracts__ */ diff --git a/core/libs/dngwriter/extra/xmp_sdk/XMPFiles/source/FormatSupport/P2_Support.cpp b/core/libs/dngwriter/extra/xmp_sdk/XMPFiles/source/FormatSupport/P2_Support.cpp new file mode 100644 index 0000000000..fb3fdfe6aa --- /dev/null +++ b/core/libs/dngwriter/extra/xmp_sdk/XMPFiles/source/FormatSupport/P2_Support.cpp @@ -0,0 +1,566 @@ + +// ================================================================================================= +// ADOBE SYSTEMS INCORPORATED +// Copyright 2014 Adobe Systems Incorporated +// All Rights Reserved +// +// NOTICE: Adobe permits you to use, modify, and distribute this file in accordance with the terms +// of the Adobe license agreement accompanying it. +// ================================================================================================= + +#include "public/include/XMP_Environment.h" // ! XMP_Environment.h must be the first included header. + +#include "public/include/XMP_Const.h" +#include "public/include/XMP_IO.hpp" + +#include "XMPFiles/source/XMPFiles_Impl.hpp" +#include "source/XMPFiles_IO.hpp" +#include "source/XIO.hpp" +#include "source/ExpatAdapter.hpp" + +#include "source/IOUtils.hpp" + +#include "XMPFiles/source/FormatSupport/P2_Support.hpp" +#include "XMP_MD5.h" +#include + +P2_Clip::P2_Clip(const std::string & p2ClipMetadataFilePath) + try :p2XMLParser(0),p2Root(0),headContentCached(false) + ,p2ClipContent(0),filePath(p2ClipMetadataFilePath) +{ + Host_IO::FileRef hostRef = Host_IO::Open ( p2ClipMetadataFilePath.c_str(), Host_IO::openReadOnly ); + XMPFiles_IO xmlFile ( hostRef, p2ClipMetadataFilePath.c_str(), Host_IO::openReadOnly ); + CreateExpatParser(xmlFile); + xmlFile.Close(); +} +catch(...) +{ + DestroyExpatParser(); + throw; +} + +P2_Clip::~P2_Clip() +{ + DestroyExpatParser(); +} + +void P2_Clip::CreateExpatParser(XMPFiles_IO &xmlFile) +{ + this->p2XMLParser = XMP_NewExpatAdapter ( ExpatAdapter::kUseLocalNamespaces ); + if ( this->p2XMLParser == 0 ) XMP_Throw ( "P2_MetaHandler: Can't create Expat adapter", kXMPErr_NoMemory ); + + XMP_Uns8 buffer [64*1024]; + while ( true ) { + XMP_Int32 ioCount = xmlFile.Read ( buffer, sizeof(buffer) ); + if ( ioCount == 0 ) break; + this->p2XMLParser->ParseBuffer ( buffer, ioCount, false /* not the end */ ); + } + this->p2XMLParser->ParseBuffer ( 0, 0, true ); +} + +void P2_Clip::DestroyExpatParser() +{ + delete this->p2XMLParser; + this->p2XMLParser = 0; + p2Root=0; + headContent.reset(); + headContentCached = false; +} + +XML_NodePtr P2_Clip::GetP2RootNode() +{ + if (p2Root!=0) return p2Root; + // The root element should be P2Main in some namespace. At least 2 different namespaces are in + // use (ending in "v3.0" and "v3.1"). Take whatever this file uses. + + XML_Node & xmlTree = this->p2XMLParser->tree; + XML_NodePtr rootElem = 0; + + for ( size_t i = 0, limit = xmlTree.content.size(); i < limit; ++i ) { + if ( xmlTree.content[i]->kind == kElemNode ) { + rootElem = xmlTree.content[i]; + } + } + + if ( rootElem == 0 ) return 0; + XMP_StringPtr rootLocalName = rootElem->name.c_str() + rootElem->nsPrefixLen; + if ( ! XMP_LitMatch ( rootLocalName, "P2Main" ) ) return 0; + + this->p2Root = rootElem; + return p2Root; +} +static void GetElementLocation(XML_NodePtr p2node,std::string*& elemLoc ) +{ + if ( p2node != 0 && p2node->IsLeafContentNode() ) + { + elemLoc= p2node->GetLeafContentPtr(); + } +} + +static void GetElementValue(XML_NodePtr p2node, XMP_Uns32 &value) +{ + if ( p2node != 0 && p2node->IsLeafContentNode() ) + { + value =atoi(p2node->GetLeafContentValue()); + } +} +void P2_Clip::CacheClipContent() +{ + if (headContentCached) return; + headContentCached = true; + XMP_StringPtr p2NameSpace=GetP2RootNode()->ns.c_str(); + p2ClipContent = GetP2RootNode()->GetNamedElement ( p2NameSpace, "ClipContent" ); + if ( p2ClipContent == 0 ) return; + XML_NodePtr p2node; + + p2node= p2ClipContent->GetNamedElement ( p2NameSpace, "GlobalClipID" ); + GetElementLocation(p2node,headContent.clipId ); + + p2node= p2ClipContent->GetNamedElement ( p2NameSpace, "ClipName" ); + GetElementLocation(p2node,headContent.clipTitle ); + + p2node= p2ClipContent->GetNamedElement ( p2NameSpace, "Duration" ); + GetElementValue(p2node,headContent.duration ); + + p2node= p2ClipContent->GetNamedElement ( p2NameSpace, "EditUnit" ); + GetElementLocation(p2node,headContent.scaleUnit ); + + headContent.clipMetadata= p2ClipContent->GetNamedElement ( p2NameSpace, "ClipMetadata" ); + headContent.essenceList= p2ClipContent->GetNamedElement ( p2NameSpace, "EssenceList" ); + + p2node= p2ClipContent->GetNamedElement ( p2NameSpace, "Relation" ); + if ( p2node != 0 ) + { + XML_NodePtr p2Offset= p2node->GetNamedElement ( p2NameSpace, "OffsetInShot" ); + GetElementValue(p2Offset,headContent.OffsetInShot ); + p2Offset= p2node->GetNamedElement ( p2NameSpace, "GlobalShotID" ); + GetElementLocation(p2Offset,headContent.shotId ); + XML_NodePtr p2connection= p2node->GetNamedElement ( p2NameSpace, "Connection" ); + if ( p2node != 0 ) + { + p2node= p2connection->GetNamedElement ( p2NameSpace, "Top" ); + if ( p2node != 0 ) + { + p2node= p2node->GetNamedElement ( p2NameSpace, "GlobalClipID" ); + GetElementLocation(p2node,headContent.topClipId ); + } + p2node= p2connection->GetNamedElement ( p2NameSpace, "Next" ); + if ( p2node != 0 ) + { + p2node= p2node->GetNamedElement ( p2NameSpace, "GlobalClipID" ); + GetElementLocation(p2node,headContent.nextClipId ); + } + p2node= p2connection->GetNamedElement ( p2NameSpace, "Previous" ); + if ( p2node != 0 ) + { + p2node= p2node->GetNamedElement ( p2NameSpace, "GlobalClipID" ); + GetElementLocation(p2node,headContent.prevClipId ); + } + } + } +} + +bool P2_Clip::IsValidClip() +{ + this->CacheClipContent(); + return headContent.clipId != 0; +} +bool P2_Clip::IsSpannedClip() +{ + return IsValidClip() && headContent.topClipId != 0 &&( headContent.prevClipId != 0 || headContent.nextClipId!=0 ); + +} + +bool P2_Clip::IsTopClip() +{ + return IsValidClip() && headContent.topClipId != 0 && *(headContent.topClipId) == *(headContent.clipId); +} + +XMP_Uns32 P2_Clip::GetOffsetInShot() +{ + this->CacheClipContent(); + return this->headContent.OffsetInShot; +} + +XMP_Uns32 P2_Clip::GetDuration() +{ + this->CacheClipContent(); + return this->headContent.duration; +} + +std::string* P2_Clip::GetClipId() +{ + this->CacheClipContent(); + return this->headContent.clipId; +} + +std::string* P2_Clip::GetClipName() +{ + if ( this->clipName == "" ) + { + std::string tempPath = this->filePath; + XIO::SplitLeafName(&tempPath, &this->clipName); + std::string ext; + XIO::SplitFileExtension(&this->clipName, &ext); + } + return &this->clipName; +} + +std::string P2_Clip::GetClipTitle() +{ + this->CacheClipContent(); + if ( ! this->headContent.clipTitle ) return std::string(""); + return *this->headContent.clipTitle; +} +std::string* P2_Clip::GetNextClipId() +{ + this->CacheClipContent(); + return this->headContent.nextClipId; +} + +std::string* P2_Clip::GetPreviousClipId() +{ + this->CacheClipContent(); + return this->headContent.prevClipId; +} + +std::string* P2_Clip::GetTopClipId() +{ + this->CacheClipContent(); + return this->headContent.topClipId; +} + +std::string* P2_Clip::GetShotId() +{ + this->CacheClipContent(); + return this->headContent.shotId; +} + +std::string* P2_Clip::GetEditUnit() +{ + this->CacheClipContent(); + return this->headContent.scaleUnit; +} + +XML_NodePtr P2_Clip::GetClipContentNode() +{ + this->CacheClipContent(); + return this->p2ClipContent; +} + +XML_NodePtr P2_Clip::GetClipMetadataNode() +{ + this->CacheClipContent(); + return this->headContent.clipMetadata; +} + +XML_NodePtr P2_Clip::GetEssenceListNode() +{ + this->CacheClipContent(); + return this->headContent.essenceList; +} + +std::string P2_Clip::GetXMPFilePath() +{ + std::string ClipMetadataPath = this->GetClipPath(); + std::string ignoreext; + XIO::SplitFileExtension(&ClipMetadataPath,&ignoreext); + return ClipMetadataPath+ ".XMP"; +} + +void P2_Clip::CreateDigest ( std::string * digestStr ) +{ + return; +} + +void P2_Clip::SerializeP2ClipContent(std::string& xmlContentData) +{ + this->p2XMLParser->tree.Serialize ( &xmlContentData ); +} + + +P2_SpannedClip::P2_SpannedClip(const std::string & p2ClipMetadataFilePath): + P2_Clip(p2ClipMetadataFilePath) +{ + P2_Clip* p2Clip= dynamic_cast(this); + spannedP2Clip.insert(p2Clip); + if (p2Clip->GetClipId()) + addedClipIds.insert(*p2Clip->GetClipId()); +} + +bool P2_SpannedClip::AddIfRelated(P2_Clip* newClip) +{ + std::string* tClipId = newClip->GetTopClipId(); + if( tClipId != 0 && *(tClipId)==*this->GetTopClipId() && + newClip->IsValidClip() && addedClipIds.find(*newClip->GetClipId()) == addedClipIds.end()) + { + spannedP2Clip.insert(newClip); + addedClipIds.insert(*newClip->GetClipId()); + return true; + } + return false; +} + +bool P2_SpannedClip::IsComplete() const +{ + RelatedP2ClipList::iterator iter=spannedP2Clip.begin(); + if (! (*iter)->IsTopClip() ) return false; + std::string* next=(*iter)->GetNextClipId(); + while(++iter != spannedP2Clip.end() && + next != 0 && (*iter)->IsValidClip() && + *next == *( (*iter)->GetClipId() ) + ) + next = (*iter)->GetNextClipId(); + if ( iter != spannedP2Clip.end() || next != 0 ) + { + iter=spannedP2Clip.begin(); + std::string* prev= (*iter)->GetClipId(); + while(++iter != spannedP2Clip.end() && + prev != 0 && (*iter)->GetPreviousClipId() !=0 && + *prev == *( (*iter)->GetPreviousClipId() ) + ) + prev= (*iter)->GetClipId(); + if ( iter != spannedP2Clip.end() ) return false; + } + return true; +} + +std::string P2_SpannedClip::GetXMPFilePath() +{ + if ( this->IsComplete() ) + { + std::string ClipMetadataPath = (*spannedP2Clip.begin())->GetClipPath(); + std::string ignoreext; + XIO::SplitFileExtension(&ClipMetadataPath,&ignoreext); + return ClipMetadataPath+ ".XMP"; + }else + { + return P2_Clip::GetXMPFilePath(); + } +} + +void P2_SpannedClip::DigestElement( MD5_CTX & md5Context, XML_NodePtr legacyContext, XMP_StringPtr legacyPropName ) +{ + XML_NodePtr legacyProp = legacyContext->GetNamedElement ( this->GetP2RootNode()->ns.c_str(), legacyPropName ); + + if ( (legacyProp != 0) && legacyProp->IsLeafContentNode() && (! legacyProp->content.empty()) ) { + const XML_Node * xmlValue = legacyProp->content[0]; + MD5Update ( &md5Context, (XMP_Uns8*)xmlValue->value.c_str(), (unsigned int)xmlValue->value.size() ); + } + +} // P2_MetaHandler::DigestLegacyItem +#define kHexDigits "0123456789ABCDEF" + +void P2_SpannedClip::CreateDigest ( std::string * digestStr ) +{ + digestStr->erase(); + if ( this->headContent.clipMetadata == 0 ) return; // Bail if we don't have any legacy XML. + + XMP_StringPtr p2NS = this->GetP2RootNode()->ns.c_str(); + XML_NodePtr legacyContext; + MD5_CTX md5Context; + unsigned char digestBin [16]; + MD5Init ( &md5Context ); + + MD5Update ( &md5Context, (XMP_Uns8*)this->GetClipTitle().c_str(), (unsigned int)this->GetClipTitle().size() ); + if ( headContent.clipId ) + MD5Update ( &md5Context, (XMP_Uns8*)headContent.clipId->c_str(), (unsigned int)headContent.clipId->size() ); + + XMP_Uns32 totalDuration=this->GetDuration(); + std::ostringstream ostr; + ostr << totalDuration; + if ( totalDuration ) + MD5Update ( &md5Context, (XMP_Uns8*)ostr.str().c_str(), (unsigned int)ostr.str().size() ); + if ( headContent.scaleUnit ) + MD5Update ( &md5Context, (XMP_Uns8*)headContent.scaleUnit->c_str(), (unsigned int)headContent.scaleUnit->size() ); + + if ( headContent.shotId ) + MD5Update ( &md5Context, (XMP_Uns8*)headContent.shotId->c_str(), (unsigned int)headContent.shotId->size() ); + if ( headContent.topClipId ) + MD5Update ( &md5Context, (XMP_Uns8*)headContent.topClipId->c_str(), (unsigned int)headContent.topClipId->size() ); + if ( headContent.prevClipId ) + MD5Update ( &md5Context, (XMP_Uns8*)headContent.prevClipId->c_str(), (unsigned int)headContent.prevClipId->size() ); + if ( headContent.nextClipId ) + MD5Update ( &md5Context, (XMP_Uns8*)headContent.nextClipId->c_str(), (unsigned int)headContent.nextClipId->size() ); + + if ( this->headContent.essenceList != 0 ) { + + XML_NodePtr videoContext = this->headContent.essenceList->GetNamedElement ( p2NS, "Video" ); + + if ( videoContext != 0 ) { + this->DigestElement ( md5Context, videoContext, "AspectRatio" ); + this->DigestElement ( md5Context, videoContext, "Codec" ); + this->DigestElement ( md5Context, videoContext, "FrameRate" ); + this->DigestElement ( md5Context, videoContext, "StartTimecode" ); + } + + XML_NodePtr audioContext = this->headContent.essenceList->GetNamedElement ( p2NS, "Audio" ); + + if ( audioContext != 0 ) { + this->DigestElement ( md5Context, audioContext, "SamplingRate" ); + this->DigestElement ( md5Context, audioContext, "BitsPerSample" ); + } + + } + + legacyContext = this->headContent.clipMetadata; + this->DigestElement ( md5Context, legacyContext, "UserClipName" ); + this->DigestElement ( md5Context, legacyContext, "ShotMark" ); + + legacyContext = this->headContent.clipMetadata->GetNamedElement ( p2NS, "Access" ); + /* Rather return than create the digest because the "Access" element is listed as "required" in the P2 spec. + So a P2 file without an "Access" element does not follow the spec and might be corrupt.*/ + if ( legacyContext == 0 ) return; + + this->DigestElement ( md5Context, legacyContext, "Creator" ); + this->DigestElement ( md5Context, legacyContext, "CreationDate" ); + this->DigestElement ( md5Context, legacyContext, "LastUpdateDate" ); + + legacyContext = this->headContent.clipMetadata->GetNamedElement ( p2NS, "Shoot" ); + + if ( legacyContext != 0 ) { + this->DigestElement ( md5Context, legacyContext, "Shooter" ); + + legacyContext = legacyContext->GetNamedElement ( p2NS, "Location" ); + + if ( legacyContext != 0 ) { + this->DigestElement ( md5Context, legacyContext, "PlaceName" ); + this->DigestElement ( md5Context, legacyContext, "Longitude" ); + this->DigestElement ( md5Context, legacyContext, "Latitude" ); + this->DigestElement ( md5Context, legacyContext, "Altitude" ); + } + } + + legacyContext = this->headContent.clipMetadata->GetNamedElement ( p2NS, "Scenario" ); + + if ( legacyContext != 0 ) { + this->DigestElement ( md5Context, legacyContext, "SceneNo." ); + this->DigestElement ( md5Context, legacyContext, "TakeNo." ); + } + + legacyContext = this->headContent.clipMetadata->GetNamedElement ( p2NS, "Device" ); + + if ( legacyContext != 0 ) { + this->DigestElement ( md5Context, legacyContext, "Manufacturer" ); + this->DigestElement ( md5Context, legacyContext, "SerialNo." ); + this->DigestElement ( md5Context, legacyContext, "ModelName" ); + } + + MD5Final ( digestBin, &md5Context ); + + char buffer [40]; + for ( int in = 0, out = 0; in < 16; in += 1, out += 2 ) { + XMP_Uns8 byte = digestBin[in]; + buffer[out] = kHexDigits [ byte >> 4 ]; + buffer[out+1] = kHexDigits [ byte & 0xF ]; + } + buffer[32] = 0; + digestStr->append ( buffer ); + +} + +P2_SpannedClip::~P2_SpannedClip() +{ + RelatedP2ClipList::iterator iter = spannedP2Clip.begin(); + for(;iter!=spannedP2Clip.end();iter++) + { + if (GetClipPath() != (*iter)->GetClipPath()) + delete *iter; + } + spannedP2Clip.clear(); +} + +P2_Clip* P2_SpannedClip::TopP2Clip() +{ + if ( this->IsComplete() && spannedP2Clip.size() > 1 ) + { + return *spannedP2Clip.begin(); + } + return this; +} + +XMP_Uns32 P2_SpannedClip::GetDuration() +{ + if ( IsComplete() ) + { + RelatedP2ClipList::iterator iter = this->spannedP2Clip.begin(); + XMP_Uns32 totalDuration=0; + for(;iter!=spannedP2Clip.end();iter++) + totalDuration+=(*iter)->GetDuration(); + return totalDuration; + } + return P2_Clip::GetDuration(); +} + +void P2_SpannedClip::GetAllClipNames(std::vector & clipNameList) +{ + clipNameList.clear(); + if ( IsComplete() ) + { + RelatedP2ClipList::iterator iter = this->spannedP2Clip.begin(); + for(;iter!=spannedP2Clip.end();iter++) + clipNameList.push_back(*( (*iter)->GetClipName() ) ); + }else + { + clipNameList.push_back(*( this->GetClipName() ) ); + } +} + +P2_Manager::P2_Manager():spannedClips(0) +{ + +} + +P2_Manager::~P2_Manager() +{ + delete this->spannedClips; + this->spannedClips = 0; +} + +void P2_Manager::ProcessClip(std::string & clipPath) +{ + this->spannedClips = new P2_SpannedClip(clipPath); + if ( this->spannedClips->IsSpannedClip()) + { + std::string clipFolder,filename,regExp; + XMP_StringVector clipFileList,regExpVec; + clipFolder=clipPath; + XIO::SplitLeafName ( &clipFolder, &filename ); + regExp = "^\\d\\d\\d\\d\\d\\d.XML$"; + regExpVec.push_back ( regExp ); + regExp = "^\\d\\d\\d\\d\\W\\W.XML$"; + regExpVec.push_back ( regExp ); + regExp = "^\\d\\d\\d\\d\\d\\W.XML$"; + regExpVec.push_back ( regExp ); + regExp = "^\\d\\d\\d\\d\\W\\d.XML$"; + regExpVec.push_back ( regExp ); + IOUtils::GetMatchingChildren ( clipFileList, clipFolder, regExpVec, false, true, true ); + for(XMP_StringVector::iterator iter=clipFileList.begin(); + iter!=clipFileList.end();iter++) + { + P2_Clip * tempClip= new P2_Clip(*iter); + if ( ! spannedClips->AddIfRelated(tempClip) ) + delete tempClip; + } + if(spannedClips->IsComplete()) + { + return; + } + } +} + +bool P2_Manager::IsValidP2() +{ + return spannedClips!= 0; +} + +P2_Clip* P2_Manager::GetManagedClip() +{ + return spannedClips->TopP2Clip(); +} + +P2_SpannedClip* P2_Manager::GetSpannedClip() +{ + return spannedClips; +} + diff --git a/core/libs/dngwriter/extra/xmp_sdk/XMPFiles/source/FormatSupport/P2_Support.hpp b/core/libs/dngwriter/extra/xmp_sdk/XMPFiles/source/FormatSupport/P2_Support.hpp new file mode 100644 index 0000000000..1ecea28cbf --- /dev/null +++ b/core/libs/dngwriter/extra/xmp_sdk/XMPFiles/source/FormatSupport/P2_Support.hpp @@ -0,0 +1,135 @@ +#ifndef __P2_Support_hpp__ +#define __P2_Support_hpp__ 1 + +// ================================================================================================= +// ADOBE SYSTEMS INCORPORATED +// Copyright 2014 Adobe Systems Incorporated +// All Rights Reserved +// +// NOTICE: Adobe permits you to use, modify, and distribute this file in accordance with the terms +// of the Adobe license agreement accompanying it. +// ================================================================================================= + +#include "public/include/XMP_Environment.h" +#include "public/include/XMP_Const.h" + +#include "XMPFiles/source/XMPFiles_Impl.hpp" +#include "source/XIO.hpp" +#include "XMP_MD5.h" +#include + +class P2_Clip { +public: + P2_Clip(const std::string & p2ClipMetadataFilePath); + bool IsSpannedClip() ; + bool IsTopClip() ; + bool IsValidClip() ; + virtual void CreateDigest ( std::string * digestStr ); + XMP_Uns32 GetOffsetInShot(); + XMP_Uns32 GetDuration(); + std::string* GetClipName(); + std::string GetClipTitle(); + std::string* GetClipId(); + std::string* GetNextClipId(); + std::string* GetPreviousClipId(); + std::string* GetTopClipId(); + std::string* GetShotId(); + std::string* GetEditUnit(); + std::string GetClipPath(){return filePath;} + virtual std::string GetXMPFilePath(); + XML_NodePtr GetClipContentNode(); + XML_NodePtr GetClipMetadataNode(); + XML_NodePtr GetEssenceListNode(); + XML_NodePtr GetP2RootNode() ; + void SerializeP2ClipContent(std::string& xmlContentData) ; + virtual ~P2_Clip(); +protected: + class ClipContent + { + public: + ClipContent():clipId(0),scaleUnit(0), + duration(0),OffsetInShot(0),topClipId(0),nextClipId(0), + prevClipId(0),shotId(0),clipMetadata(0),essenceList(0),clipTitle(0){} + std::string* clipTitle; + std::string* clipId; + std::string* scaleUnit; + XMP_Uns32 duration; + XMP_Uns32 OffsetInShot; + std::string* topClipId; + std::string* nextClipId; + std::string* prevClipId; + std::string* shotId; + XML_NodePtr clipMetadata; + XML_NodePtr essenceList; + void reset(){*this=ClipContent();} + }; + ClipContent headContent; +private: + void DestroyExpatParser(); + void CreateExpatParser(XMPFiles_IO &xmlFile); + void CacheClipContent(); + + bool headContentCached; + ExpatAdapter * p2XMLParser; + XML_NodePtr p2Root; + XML_NodePtr p2ClipContent; + std::string filePath; + std::string clipName; + +}; // class P2_Clip +struct P2SpannedClip_Order +{ + bool operator()( P2_Clip* lhs, P2_Clip* rhs) + { + return lhs->GetOffsetInShot() < rhs->GetOffsetInShot(); + } + +}; + +class P2_SpannedClip : public P2_Clip{ +public: + P2_SpannedClip(const std::string & p2ClipMetadataFilePath); + bool AddIfRelated(P2_Clip* openedClip); + bool IsComplete()const; + XMP_Uns32 GetDuration(); + P2_Clip* TopP2Clip() ; + std::string GetXMPFilePath(); + void CreateDigest ( std::string * digestStr ); + void GetAllClipNames(std::vector & clipNameList); + virtual ~P2_SpannedClip(); +private: + P2_SpannedClip(const P2_SpannedClip &); + P2_SpannedClip operator=(const P2_SpannedClip &); + + void DigestElement( MD5_CTX & md5Context, XML_NodePtr legacyContext, XMP_StringPtr legacyPropName ); + + typedef std::multiset RelatedP2ClipList; + std::set addedClipIds; + RelatedP2ClipList spannedP2Clip; + +}; // class P2_SpannedClip + +// ================================================================================================= +class P2_Manager { +public: + P2_Manager(); + void ProcessClip(std::string & clipPath); + P2_Clip* GetManagedClip(); + P2_SpannedClip* GetSpannedClip(); + bool IsValidP2(); + ~P2_Manager(); + +private: + + P2_SpannedClip* spannedClips; + +}; // class P2_Manager + + +// ================================================================================================= + + + +// ================================================================================================= + +#endif // __P2_Support_hpp__ diff --git a/core/libs/dngwriter/extra/xmp_sdk/XMPFiles/source/FormatSupport/PNG_Support.cpp b/core/libs/dngwriter/extra/xmp_sdk/XMPFiles/source/FormatSupport/PNG_Support.cpp new file mode 100644 index 0000000000..c58fc87eac --- /dev/null +++ b/core/libs/dngwriter/extra/xmp_sdk/XMPFiles/source/FormatSupport/PNG_Support.cpp @@ -0,0 +1,341 @@ +// ================================================================================================= +// ADOBE SYSTEMS INCORPORATED +// Copyright 2008 Adobe Systems Incorporated +// All Rights Reserved +// +// NOTICE: Adobe permits you to use, modify, and distribute this file in accordance with the terms +// of the Adobe license agreement accompanying it. +// ================================================================================================= +#include "public/include/XMP_Environment.h" // ! XMP_Environment.h must be the first included header. +#include "public/include/XMP_Const.h" + +#include "XMPFiles/source/FormatSupport/PNG_Support.hpp" + +#include "source/XIO.hpp" + +#include + +typedef std::basic_string filebuffer; + +namespace CRC +{ + /* Table of CRCs of all 8-bit messages. */ + static unsigned long crc_table[256]; + + /* Flag: has the table been computed? Initially false. */ + static int crc_table_computed = 0; + + /* Make the table for a fast CRC. */ + static void make_crc_table(void) + { + unsigned long c; + int n, k; + + for (n = 0; n < 256; n++) + { + c = (unsigned long) n; + for (k = 0; k < 8; k++) + { + if (c & 1) + { + c = 0xedb88320L ^ (c >> 1); + } + else + { + c = c >> 1; + } + } + crc_table[n] = c; + } + crc_table_computed = 1; + } + + /* Update a running CRC with the bytes buf[0..len-1]--the CRC + should be initialized to all 1's, and the transmitted value + is the 1's complement of the final running CRC (see the + crc() routine below). */ + + static unsigned long update_crc(unsigned long crc, unsigned char *buf, int len) + { + unsigned long c = crc; + int n; + + if (!crc_table_computed) + { + make_crc_table(); + } + + for (n = 0; n < len; n++) + { + c = crc_table[(c ^ buf[n]) & 0xff] ^ (c >> 8); + } + + return c; + } + + /* Return the CRC of the bytes buf[0..len-1]. */ + static unsigned long crc(unsigned char *buf, int len) + { + return update_crc(0xffffffffL, buf, len) ^ 0xffffffffL; + } +} // namespace CRC + +namespace PNG_Support +{ + enum chunkType { + // Critical chunks - (shall appear in this order, except PLTE is optional) + IHDR = 'IHDR', + PLTE = 'PLTE', + IDAT = 'IDAT', + IEND = 'IEND', + // Ancillary chunks - (need not appear in this order) + cHRM = 'cHRM', + gAMA = 'gAMA', + iCCP = 'iCCP', + sBIT = 'sBIT', + sRGB = 'sRGB', + bKGD = 'bKGD', + hIST = 'hIST', + tRNS = 'tRNS', + pHYs = 'pHYs', + sPLT = 'sPLT', + tIME = 'tIME', + iTXt = 'iTXt', + tEXt = 'tEXt', + zTXt = 'zTXt' + + }; + + // ============================================================================================= + + long OpenPNG ( XMP_IO* fileRef, ChunkState & inOutChunkState ) + { + XMP_Uns64 pos = 0; + long name; + XMP_Uns32 len; + + pos = fileRef->Seek ( 8, kXMP_SeekFromStart ); + if (pos != 8) return 0; + + // read first and following chunks + while ( ReadChunk ( fileRef, inOutChunkState, &name, &len, pos) ) {} + + return (long)inOutChunkState.chunks.size(); + + } + + // ============================================================================================= + + bool ReadChunk ( XMP_IO* fileRef, ChunkState & inOutChunkState, long * chunkType, XMP_Uns32 * chunkLength, XMP_Uns64 & inOutPosition ) + { + try + { + XMP_Uns64 startPosition = inOutPosition; + long bytesRead; + char buffer[4]; + + bytesRead = fileRef->Read ( buffer, 4 ); + if ( bytesRead != 4 ) return false; + inOutPosition += 4; + *chunkLength = GetUns32BE(buffer); + + bytesRead = fileRef->Read ( buffer, 4 ); + if ( bytesRead != 4 ) return false; + inOutPosition += 4; + *chunkType = GetUns32BE(buffer); + + inOutPosition += *chunkLength; + + bytesRead = fileRef->Read ( buffer, 4 ); + if ( bytesRead != 4 ) return false; + inOutPosition += 4; + long crc = GetUns32BE(buffer); + + ChunkData newChunk; + + newChunk.pos = startPosition; + newChunk.len = *chunkLength; + newChunk.type = *chunkType; + + // check for XMP in iTXt-chunk + if (newChunk.type == iTXt) + { + CheckiTXtChunkHeader(fileRef, inOutChunkState, newChunk); + } + + inOutChunkState.chunks.push_back ( newChunk ); + + fileRef->Seek ( inOutPosition, kXMP_SeekFromStart ); + + } catch ( ... ) { + + return false; + + } + + return true; + + } + + // ============================================================================================= + + bool WriteXMPChunk ( XMP_IO* fileRef, XMP_Uns32 len, const char* inBuffer ) + { + bool ret = false; + unsigned long datalen = (4 + ITXT_HEADER_LEN + len); + unsigned char* buffer = new unsigned char[datalen]; + + try + { + size_t pos = 0; + memcpy(&buffer[pos], ITXT_CHUNK_TYPE, 4); + pos += 4; + memcpy(&buffer[pos], ITXT_HEADER_DATA, ITXT_HEADER_LEN); + pos += ITXT_HEADER_LEN; + memcpy(&buffer[pos], inBuffer, len); + + unsigned long crc_value = MakeUns32BE( CalculateCRC( buffer, datalen )); + datalen -= 4; + unsigned long len_value = MakeUns32BE( datalen ); + datalen += 4; + + fileRef->Write ( &len_value, 4 ); + fileRef->Write ( buffer, datalen ); + fileRef->Write ( &crc_value, 4 ); + + ret = true; + } + catch ( ... ) {} + + delete [] buffer; + + return ret; + } + + // ============================================================================================= + + bool CopyChunk ( XMP_IO* sourceRef, XMP_IO* destRef, ChunkData& chunk ) + { + try + { + sourceRef->Seek ( chunk.pos, kXMP_SeekFromStart ); + XIO::Copy (sourceRef, destRef, (chunk.len + 12)); + + } catch ( ... ) { + + return false; + + } + + return true; + } + + // ============================================================================================= + + unsigned long UpdateChunkCRC( XMP_IO* fileRef, ChunkData& inOutChunkData ) + { + unsigned long ret = 0; + unsigned long datalen = (inOutChunkData.len + 4); + unsigned char* buffer = new unsigned char[datalen]; + + try + { + fileRef->Seek ( (inOutChunkData.pos + 4), kXMP_SeekFromStart ); + + size_t pos = 0; + long bytesRead = fileRef->Read ( &buffer[pos], (inOutChunkData.len + 4) ); + + unsigned long crc = CalculateCRC( buffer, (inOutChunkData.len + 4) ); + unsigned long crc_value = MakeUns32BE( crc ); + + fileRef->Seek ( (inOutChunkData.pos + 4 + 4 + inOutChunkData.len), kXMP_SeekFromStart ); + fileRef->Write ( &crc_value, 4 ); + + ret = crc; + } + catch ( ... ) {} + + delete [] buffer; + + return ret; + } + + // ============================================================================================= + + bool CheckIHDRChunkHeader ( ChunkData& inOutChunkData ) + { + return (inOutChunkData.type == IHDR); + } + + // ============================================================================================= + + unsigned long CheckiTXtChunkHeader ( XMP_IO* fileRef, ChunkState& inOutChunkState, ChunkData& inOutChunkData ) + { + try + { + fileRef->Seek ( (inOutChunkData.pos + 8), kXMP_SeekFromStart ); + + char buffer[ITXT_HEADER_LEN]; + long bytesRead = fileRef->Read ( buffer, ITXT_HEADER_LEN ); + + if (bytesRead == ITXT_HEADER_LEN) + { + if (memcmp(buffer, ITXT_HEADER_DATA, ITXT_HEADER_LEN) == 0) + { + // return length of XMP + if (inOutChunkData.len > ITXT_HEADER_LEN) + { + inOutChunkState.xmpPos = inOutChunkData.pos + 8 + ITXT_HEADER_LEN; + inOutChunkState.xmpLen = inOutChunkData.len - ITXT_HEADER_LEN; + inOutChunkState.xmpChunk = inOutChunkData; + inOutChunkData.xmp = true; + + return inOutChunkState.xmpLen; + } + } + } + } + catch ( ... ) {} + + return 0; + } + + bool ReadBuffer ( XMP_IO* fileRef, XMP_Uns64 & pos, XMP_Uns32 len, char * outBuffer ) + { + try + { + if ( (fileRef == 0) || (outBuffer == 0) ) return false; + + fileRef->Seek ( pos, kXMP_SeekFromStart ); + long bytesRead = fileRef->Read ( outBuffer, len ); + if ( XMP_Uns32(bytesRead) != len ) return false; + + return true; + } + catch ( ... ) {} + + return false; + } + + bool WriteBuffer ( XMP_IO* fileRef, XMP_Uns64 & pos, XMP_Uns32 len, const char * inBuffer ) + { + try + { + if ( (fileRef == 0) || (inBuffer == 0) ) return false; + + fileRef->Seek ( pos, kXMP_SeekFromStart ); + fileRef->Write ( inBuffer, len ); + + return true; + } + catch ( ... ) {} + + return false; + } + + unsigned long CalculateCRC( unsigned char* inBuffer, XMP_Uns32 len ) + { + return CRC::update_crc(0xffffffffL, inBuffer, len) ^ 0xffffffffL; + } + +} // namespace PNG_Support diff --git a/core/libs/dngwriter/extra/xmp_sdk/XMPFiles/source/FormatSupport/PNG_Support.hpp b/core/libs/dngwriter/extra/xmp_sdk/XMPFiles/source/FormatSupport/PNG_Support.hpp new file mode 100644 index 0000000000..6b0afbf269 --- /dev/null +++ b/core/libs/dngwriter/extra/xmp_sdk/XMPFiles/source/FormatSupport/PNG_Support.hpp @@ -0,0 +1,78 @@ +#ifndef __PNG_Support_hpp__ +#define __PNG_Support_hpp__ 1 + +// ================================================================================================= +// ADOBE SYSTEMS INCORPORATED +// Copyright 2007 Adobe Systems Incorporated +// All Rights Reserved +// +// NOTICE: Adobe permits you to use, modify, and distribute this file in accordance with the terms +// of the Adobe license agreement accompanying it. +// ================================================================================================= + +#include "public/include/XMP_Environment.h" // ! XMP_Environment.h must be the first included header. + +#include "public/include/XMP_Const.h" +#include "public/include/XMP_IO.hpp" + +#include "XMPFiles/source/XMPFiles_Impl.hpp" +#include "source/XMPFiles_IO.hpp" + +#define PNG_SIGNATURE_LEN 8 +#define PNG_SIGNATURE_DATA "\x89\x50\x4E\x47\x0D\x0A\x1A\x0A" + +#define ITXT_CHUNK_TYPE "iTXt" + +#define ITXT_HEADER_LEN 22 +#define ITXT_HEADER_DATA "XML:com.adobe.xmp\0\0\0\0\0" + +namespace PNG_Support +{ + class ChunkData + { + public: + ChunkData() : pos(0), len(0), type(0), xmp(false) {} + virtual ~ChunkData() {} + + // | length | type | data | crc(type+data) | + // | 4 | 4 | val(length) | 4 | + // + XMP_Uns64 pos; // file offset of chunk + XMP_Uns32 len; // length of chunk data + long type; // name/type of chunk + bool xmp; // iTXt-chunk with XMP ? + }; + + typedef std::vector ChunkVector; + typedef ChunkVector::iterator ChunkIterator; + + class ChunkState + { + public: + ChunkState() : xmpPos(0), xmpLen(0) {} + virtual ~ChunkState() {} + + XMP_Uns64 xmpPos; + XMP_Uns32 xmpLen; + ChunkData xmpChunk; + ChunkVector chunks; /* vector of chunks */ + }; + + long OpenPNG ( XMP_IO* fileRef, ChunkState& inOutChunkState ); + + bool ReadChunk ( XMP_IO* fileRef, ChunkState& inOutChunkState, long* chunkType, XMP_Uns32* chunkLength, XMP_Uns64& inOutPosition ); + bool WriteXMPChunk ( XMP_IO* fileRef, XMP_Uns32 len, const char* inBuffer ); + bool CopyChunk ( XMP_IO* sourceRef, XMP_IO* destRef, ChunkData& chunk ); + unsigned long UpdateChunkCRC( XMP_IO* fileRef, ChunkData& inOutChunkData ); + + bool CheckIHDRChunkHeader ( ChunkData& inOutChunkData ); + unsigned long CheckiTXtChunkHeader ( XMP_IO* fileRef, ChunkState& inOutChunkState, ChunkData& inOutChunkData ); + + bool ReadBuffer ( XMP_IO* fileRef, XMP_Uns64& pos, XMP_Uns32 len, char* outBuffer ); + bool WriteBuffer ( XMP_IO* fileRef, XMP_Uns64& pos, XMP_Uns32 len, const char* inBuffer ); + + unsigned long CalculateCRC( unsigned char* inBuffer, XMP_Uns32 len ); + +} // namespace PNG_Support + +#endif // __PNG_Support_hpp__ diff --git a/core/libs/dngwriter/extra/xmp_sdk/XMPFiles/source/FormatSupport/PSIR_FileWriter.cpp b/core/libs/dngwriter/extra/xmp_sdk/XMPFiles/source/FormatSupport/PSIR_FileWriter.cpp new file mode 100644 index 0000000000..5d06220c20 --- /dev/null +++ b/core/libs/dngwriter/extra/xmp_sdk/XMPFiles/source/FormatSupport/PSIR_FileWriter.cpp @@ -0,0 +1,609 @@ +// ================================================================================================= +// ADOBE SYSTEMS INCORPORATED +// Copyright 2006 Adobe Systems Incorporated +// All Rights Reserved +// +// NOTICE: Adobe permits you to use, modify, and distribute this file in accordance with the terms +// of the Adobe license agreement accompanying it. +// ================================================================================================= + +#include "public/include/XMP_Environment.h" // ! XMP_Environment.h must be the first included header. +#include "public/include/XMP_Const.h" + +#include "XMPFiles/source/FormatSupport/PSIR_Support.hpp" +#include "source/EndianUtils.hpp" +#include "source/XIO.hpp" + +#include + +// ================================================================================================= +/// \file PSIR_FileWriter.cpp +/// \brief Implementation of the file-based or read-write form of PSIR_Manager. +// ================================================================================================= + +// ================================================================================================= +// IsMetadataImgRsrc +// ================= + +static inline bool IsMetadataImgRsrc ( XMP_Uns16 id ) +{ + if ( id == 0 ) return false; + + int i; + for ( i = 0; id < kPSIR_MetadataIDs[i]; ++i ) {} + if ( id == kPSIR_MetadataIDs[i] ) return true; + return false; + +} // IsMetadataImgRsrc + +// ================================================================================================= +// PSIR_FileWriter::DeleteExistingInfo +// =================================== +// +// Delete all existing info about image resources. + +void PSIR_FileWriter::DeleteExistingInfo() +{ + XMP_Assert ( ! (this->memParsed && this->fileParsed) ); + + if ( this->memParsed ) { + if ( this->ownedContent ) free ( this->memContent ); + } else if ( this->fileParsed ) { + InternalRsrcMap::iterator irPos = this->imgRsrcs.begin(); + InternalRsrcMap::iterator irEnd = this->imgRsrcs.end(); + for ( ; irPos != irEnd; ++irPos ) irPos->second.changed = true; // Fool the InternalRsrcInfo destructor. + } + + this->imgRsrcs.clear(); + + this->memContent = 0; + this->memLength = 0; + + this->changed = false; + this->legacyDeleted = false; + this->memParsed = false; + this->fileParsed = false; + this->ownedContent = false; + +} // PSIR_FileWriter::DeleteExistingInfo + +// ================================================================================================= +// PSIR_FileWriter::~PSIR_FileWriter +// ================================= + +PSIR_FileWriter::~PSIR_FileWriter() +{ + XMP_Assert ( ! (this->memParsed && this->fileParsed) ); + + if ( this->ownedContent ) { + XMP_Assert ( this->memContent != 0 ); + free ( this->memContent ); + } + +} // PSIR_FileWriter::~PSIR_FileWriter + +// ================================================================================================= +// PSIR_FileWriter::GetImgRsrc +// =========================== + +bool PSIR_FileWriter::GetImgRsrc ( XMP_Uns16 id, ImgRsrcInfo* info ) const +{ + InternalRsrcMap::const_iterator rsrcPos = this->imgRsrcs.find ( id ); + if ( rsrcPos == this->imgRsrcs.end() ) return false; + + const InternalRsrcInfo & rsrcInfo = rsrcPos->second; + + if ( info != 0 ) { + info->id = rsrcInfo.id; + info->dataLen = rsrcInfo.dataLen; + info->dataPtr = rsrcInfo.dataPtr; + info->origOffset = rsrcInfo.origOffset; + } + + return true; + +} // PSIR_FileWriter::GetImgRsrc + +// ================================================================================================= +// PSIR_FileWriter::SetImgRsrc +// =========================== + +void PSIR_FileWriter::SetImgRsrc ( XMP_Uns16 id, const void* clientPtr, XMP_Uns32 length ) +{ + InternalRsrcInfo* rsrcPtr = 0; + InternalRsrcMap::iterator rsrcPos = this->imgRsrcs.find ( id ); + + if ( rsrcPos == this->imgRsrcs.end() ) { + + // This resource is not yet in the map, create the map entry. + InternalRsrcMap::value_type mapValue ( id, InternalRsrcInfo ( id, length, this->fileParsed ) ); + rsrcPos = this->imgRsrcs.insert ( rsrcPos, mapValue ); + rsrcPtr = &rsrcPos->second; + + } else { + + rsrcPtr = &rsrcPos->second; + + // The resource already exists, make sure the value is actually changing. + if ( (length == rsrcPtr->dataLen) && + (memcmp ( rsrcPtr->dataPtr, clientPtr, length ) == 0) ) { + return; + } + + rsrcPtr->FreeData(); // Release any existing data allocation. + rsrcPtr->dataLen = length; // And this might be changing. + + } + + rsrcPtr->changed = true; + rsrcPtr->dataPtr = malloc ( length ); + if ( rsrcPtr->dataPtr == 0 ) XMP_Throw ( "Out of memory", kXMPErr_NoMemory ); + memcpy ( rsrcPtr->dataPtr, clientPtr, length ); // AUDIT: Safe, malloc'ed length bytes above. + + this->changed = true; + +} // PSIR_FileWriter::SetImgRsrc + +// ================================================================================================= +// PSIR_FileWriter::DeleteImgRsrc +// ============================== + +void PSIR_FileWriter::DeleteImgRsrc ( XMP_Uns16 id ) +{ + InternalRsrcMap::iterator rsrcPos = this->imgRsrcs.find ( id ); + if ( rsrcPos == this->imgRsrcs.end() ) return; // Nothing to delete. + + this->imgRsrcs.erase ( id ); + this->changed = true; + if ( id != kPSIR_XMP ) this->legacyDeleted = true; + +} // PSIR_FileWriter::DeleteImgRsrc + +// ================================================================================================= +// PSIR_FileWriter::IsLegacyChanged +// ================================ + +bool PSIR_FileWriter::IsLegacyChanged() +{ + + if ( ! this->changed ) return false; + if ( this->legacyDeleted ) return true; + + InternalRsrcMap::iterator irPos = this->imgRsrcs.begin(); + InternalRsrcMap::iterator irEnd = this->imgRsrcs.end(); + + for ( ; irPos != irEnd; ++irPos ) { + const InternalRsrcInfo & rsrcInfo = irPos->second; + if ( rsrcInfo.changed && (rsrcInfo.id != kPSIR_XMP) ) return true; + } + + return false; // Can get here if the XMP is the only thing changed. + +} // PSIR_FileWriter::IsLegacyChanged + +// ================================================================================================= +// PSIR_FileWriter::ParseMemoryResources +// ===================================== + +void PSIR_FileWriter::ParseMemoryResources ( const void* data, XMP_Uns32 length, bool copyData /* = true */ ) +{ + this->DeleteExistingInfo(); + this->memParsed = true; + if ( length == 0 ) return; + + // Allocate space for the full in-memory data and copy it. + + if ( ! copyData ) { + this->memContent = (XMP_Uns8*) data; + XMP_Assert ( ! this->ownedContent ); + } else { + if ( length > 100*1024*1024 ) XMP_Throw ( "Outrageous length for memory-based PSIR", kXMPErr_BadPSIR ); + this->memContent = (XMP_Uns8*) malloc ( length ); + if ( this->memContent == 0 ) XMP_Throw ( "Out of memory", kXMPErr_NoMemory ); + memcpy ( this->memContent, data, length ); // AUDIT: Safe, malloc'ed length bytes above. + this->ownedContent = true; + } + this->memLength = length; + + // Capture the info for all of the resources. We're using a map keyed by ID, so only one + // resource of each ID is recognized. Redundant resources are not legit, but have been seen in + // the field. In particular, one case has been seen of a duplicate IIM block with one empty. + // In general we keep the first seen copy to be compatible with Photoshop. A later non-empty + // copy will be taken though if the current one is empty. + + // ! Don't use map[id] to lookup, that creates a default entry if none exists! + + XMP_Uns8* psirPtr = this->memContent; + XMP_Uns8* psirEnd = psirPtr + length; + XMP_Uns8* psirLimit = psirEnd - kMinImgRsrcSize; + + while ( psirPtr <= psirLimit ) { + + XMP_Uns8* origin = psirPtr; // The beginning of this resource. + XMP_Uns32 type = GetUns32BE(psirPtr); + XMP_Uns16 id = GetUns16BE(psirPtr+4); + psirPtr += 6; // Advance to the resource name. + + XMP_Uns8* namePtr = psirPtr; + XMP_Uns16 nameLen = namePtr[0]; // ! The length for the Pascal string, w/ room for "+2". + psirPtr += ((nameLen + 2) & 0xFFFE); // ! Round up to an even offset. Yes, +2! + + if ( psirPtr > psirEnd-4 ) break; // Bad image resource. Throw instead? + + XMP_Uns32 dataLen = GetUns32BE(psirPtr); + psirPtr += 4; // Advance to the resource data. + + XMP_Uns32 dataOffset = (XMP_Uns32) ( psirPtr - this->memContent ); + XMP_Uns8* nextRsrc = psirPtr + ((dataLen + 1) & 0xFFFFFFFEUL); // ! Round up to an even offset. + + if ( (dataLen > length) || (psirPtr > psirEnd-dataLen) ) break; // Bad image resource. Throw instead? + + if ( type != k8BIM ) { + + XMP_Uns32 rsrcOffset = XMP_Uns32( origin - this->memContent ); + XMP_Uns32 rsrcLength = XMP_Uns32( nextRsrc - origin ); // Includes trailing pad. + XMP_Assert ( (rsrcLength & 1) == 0 ); + this->otherRsrcs.push_back ( OtherRsrcInfo ( rsrcOffset, rsrcLength ) ); + + } else { + + InternalRsrcInfo newInfo ( id, dataLen, kIsMemoryBased ); + newInfo.dataPtr = psirPtr; + newInfo.origOffset = dataOffset; + if ( nameLen != 0 ) newInfo.rsrcName = namePtr; + + InternalRsrcMap::iterator rsrcPos = this->imgRsrcs.find ( id ); + if ( rsrcPos == this->imgRsrcs.end() ) { + this->imgRsrcs.insert ( rsrcPos, InternalRsrcMap::value_type ( id, newInfo ) ); + } else if ( (rsrcPos->second.dataLen == 0) && (newInfo.dataLen != 0) ) { + rsrcPos->second = newInfo; + } + + } + + psirPtr = nextRsrc; + + } + +} // PSIR_FileWriter::ParseMemoryResources + +// ================================================================================================= +// PSIR_FileWriter::ParseFileResources +// =================================== + +void PSIR_FileWriter::ParseFileResources ( XMP_IO* fileRef, XMP_Uns32 length ) +{ + // Parse the image resource block. We're using a map keyed by ID, so only one resource of each + // ID is recognized. Redundant resources are not legit, but have been seen in the field. In + // particular, one case has been seen of a duplicate IIM block with one empty. In general we + // keep the first seen copy to be compatible with Photoshop. A later non-empty copy will be + // taken though if the current one is empty. + + // ! Don't use map[id] to lookup, that creates a default entry if none exists! + + // PSIR layout: + // - Uns32 type, usually '8BIM' + // - Uns16 ID + // - PString name + // - Uns8 optional pad for even alignment + // - Uns32 data size + // - data + // - Uns8 optional pad for even alignment + + static const size_t kMinPSIRSize = 12; // 4+2+1+1+4 + + this->DeleteExistingInfo(); + this->fileParsed = true; + if ( length == 0 ) return; + + XMP_Int64 psirOrigin = fileRef->Offset(); // Need this to determine the resource data offsets. + XMP_Int64 fileEnd = psirOrigin + length; + + char nameBuffer [260]; // The name is a PString, at 1+255+1 including length and pad. + + while ( fileRef->Offset() < fileEnd ) { + + if ( ! XIO::CheckFileSpace ( fileRef, kMinPSIRSize ) ) break; // Bad image resource. + + XMP_Int64 thisRsrcPos = fileRef->Offset(); + + XMP_Uns32 type = XIO::ReadUns32_BE ( fileRef ); + XMP_Uns16 id = XIO::ReadUns16_BE ( fileRef ); + + XMP_Uns8 nameLen = XIO::ReadUns8 ( fileRef ); // ! The length for the Pascal string. + XMP_Uns16 paddedLen = (nameLen + 2) & 0xFFFE; // ! Round up to an even total. Yes, +2! + if ( ! XIO::CheckFileSpace ( fileRef, paddedLen+4 ) ) break; // Bad image resource. + + nameBuffer[0] = nameLen; + fileRef->ReadAll ( &nameBuffer[1], paddedLen-1 ); // Include the pad byte, present for zero nameLen. + + XMP_Uns32 dataLen = XIO::ReadUns32_BE ( fileRef ); + XMP_Uns32 dataTotal = ((dataLen + 1) & 0xFFFFFFFEUL); // Round up to an even total. + if ( ! XIO::CheckFileSpace ( fileRef, dataTotal ) ) break; // Bad image resource. + + XMP_Int64 thisDataPos = fileRef->Offset(); + XMP_Int64 nextRsrcPos = thisDataPos + dataTotal; + + if ( type != k8BIM ) { + XMP_Uns32 fullRsrcLen = (XMP_Uns32) (nextRsrcPos - thisRsrcPos); + this->otherRsrcs.push_back ( OtherRsrcInfo ( (XMP_Uns32)thisRsrcPos, fullRsrcLen ) ); + fileRef->Seek ( nextRsrcPos, kXMP_SeekFromStart ); + continue; + } + + InternalRsrcInfo newInfo ( id, dataLen, kIsFileBased ); + InternalRsrcMap::iterator rsrcPos = this->imgRsrcs.find ( id ); + if ( rsrcPos == this->imgRsrcs.end() ) { + rsrcPos = this->imgRsrcs.insert ( rsrcPos, InternalRsrcMap::value_type ( id, newInfo ) ); + } else if ( (rsrcPos->second.dataLen == 0) && (newInfo.dataLen != 0) ) { + rsrcPos->second = newInfo; + } else { + fileRef->Seek ( nextRsrcPos, kXMP_SeekFromStart ); + continue; + } + InternalRsrcInfo* rsrcPtr = &rsrcPos->second; + + rsrcPtr->origOffset = (XMP_Uns32)thisDataPos; + + if ( nameLen > 0 ) { + rsrcPtr->rsrcName = (XMP_Uns8*) malloc ( paddedLen ); + if ( rsrcPtr->rsrcName == 0 ) XMP_Throw ( "Out of memory", kXMPErr_NoMemory ); + memcpy ( (void*)rsrcPtr->rsrcName, nameBuffer, paddedLen ); // AUDIT: Safe, allocated enough bytes above. + } + + if ( ! IsMetadataImgRsrc ( id ) ) { + fileRef->Seek ( nextRsrcPos, kXMP_SeekFromStart ); + continue; + } + + rsrcPtr->dataPtr = malloc ( dataTotal ); // ! Allocate after the IsMetadataImgRsrc check. + if ( rsrcPtr->dataPtr == 0 ) XMP_Throw ( "Out of memory", kXMPErr_NoMemory ); + fileRef->ReadAll ( (void*)rsrcPtr->dataPtr, dataTotal ); + + } + + #if 0 + { + printf ( "\nPSIR_FileWriter::ParseFileResources, count = %d\n", this->imgRsrcs.size() ); + InternalRsrcMap::iterator irPos = this->imgRsrcs.begin(); + InternalRsrcMap::iterator irEnd = this->imgRsrcs.end(); + for ( ; irPos != irEnd; ++irPos ) { + InternalRsrcInfo& thisRsrc = irPos->second; + printf ( " #%d, dataLen %d, origOffset %d (0x%X)%s\n", + thisRsrc.id, thisRsrc.dataLen, thisRsrc.origOffset, thisRsrc.origOffset, + (thisRsrc.changed ? ", changed" : "") ); + } + } + #endif + +} // PSIR_FileWriter::ParseFileResources + +// ================================================================================================= +// PSIR_FileWriter::UpdateMemoryResources +// ====================================== + +XMP_Uns32 PSIR_FileWriter::UpdateMemoryResources ( void** dataPtr ) +{ + if ( this->fileParsed ) XMP_Throw ( "Not memory based", kXMPErr_EnforceFailure ); + + // Compute the size and allocate the new image resource block. + + XMP_Uns32 newLength = 0; + + InternalRsrcMap::iterator irPos = this->imgRsrcs.begin(); + InternalRsrcMap::iterator irEnd = this->imgRsrcs.end(); + + for ( ; irPos != irEnd; ++irPos ) { // Add in the lengths for the 8BIM resources. + const InternalRsrcInfo & rsrcInfo = irPos->second; + newLength += 10; + newLength += ((rsrcInfo.dataLen + 1) & 0xFFFFFFFEUL); + if ( rsrcInfo.rsrcName == 0 ) { + newLength += 2; + } else { + XMP_Uns32 nameLen = rsrcInfo.rsrcName[0]; + newLength += ((nameLen + 2) & 0xFFFFFFFEUL); // ! Yes, +2 for the length and rounding. + } + } + + for ( size_t i = 0; i < this->otherRsrcs.size(); ++i ) { // Add in the non-8BIM resources. + newLength += this->otherRsrcs[i].rsrcLength; + } + + XMP_Uns8* newContent = (XMP_Uns8*) malloc ( newLength ); + if ( newContent == 0 ) XMP_Throw ( "Out of memory", kXMPErr_NoMemory ); + + // Fill in the new image resource block. + + XMP_Uns8* rsrcPtr = newContent; + + for ( irPos = this->imgRsrcs.begin(); irPos != irEnd; ++irPos ) { // Do the 8BIM resources. + + const InternalRsrcInfo & rsrcInfo = irPos->second; + + PutUns32BE ( k8BIM, rsrcPtr ); + rsrcPtr += 4; + PutUns16BE ( rsrcInfo.id, rsrcPtr ); + rsrcPtr += 2; + + if ( rsrcInfo.rsrcName == 0 ) { + PutUns16BE ( 0, rsrcPtr ); + rsrcPtr += 2; + } else { + XMP_Uns32 nameLen = rsrcInfo.rsrcName[0]; + XMP_Assert ( nameLen > 0 ); + if ( (nameLen+1) > (newLength - (rsrcPtr - newContent)) ) { + XMP_Throw ( "Buffer overrun", kXMPErr_InternalFailure ); + } + memcpy ( rsrcPtr, rsrcInfo.rsrcName, nameLen+1 ); // AUDIT: Protected by the above check. + rsrcPtr += nameLen+1; + if ( (nameLen & 1) == 0 ) { + *rsrcPtr = 0; // Round to an even total. + ++rsrcPtr; + } + } + + PutUns32BE ( rsrcInfo.dataLen, rsrcPtr ); + rsrcPtr += 4; + if ( rsrcInfo.dataLen > (newLength - (rsrcPtr - newContent)) ) { + XMP_Throw ( "Buffer overrun", kXMPErr_InternalFailure ); + } + memcpy ( rsrcPtr, rsrcInfo.dataPtr, rsrcInfo.dataLen ); // AUDIT: Protected by the above check. + rsrcPtr += rsrcInfo.dataLen; + if ( (rsrcInfo.dataLen & 1) != 0 ) { // Pad to an even length if necessary. + *rsrcPtr = 0; + ++rsrcPtr; + } + + } + + for ( size_t i = 0; i < this->otherRsrcs.size(); ++i ) { // Do the non-8BIM resources. + XMP_Uns8* srcPtr = this->memContent + this->otherRsrcs[i].rsrcOffset; + XMP_Uns32 srcLen = this->otherRsrcs[i].rsrcLength; + if ( srcLen > (newLength - (rsrcPtr - newContent)) ) { + XMP_Throw ( "Buffer overrun", kXMPErr_InternalFailure ); + } + memcpy ( rsrcPtr, srcPtr, srcLen ); // AUDIT: Protected by the above check. + rsrcPtr += srcLen; // No need to pad, included in the original resource length. + } + + XMP_Assert ( rsrcPtr == (newContent + newLength) ); + + // Parse the rebuilt image resource block. This is the easiest way to reconstruct the map. + + this->ParseMemoryResources ( newContent, newLength, false ); + this->ownedContent = (newLength > 0); // ! We really do own the new content, if not empty. + + if ( dataPtr != 0 ) *dataPtr = newContent; + return newLength; + +} // PSIR_FileWriter::UpdateMemoryResources + +// ================================================================================================= +// PSIR_FileWriter::UpdateFileResources +// ==================================== + +XMP_Uns32 PSIR_FileWriter::UpdateFileResources ( XMP_IO* sourceRef, XMP_IO* destRef, + XMP_AbortProc abortProc, void * abortArg, + XMP_ProgressTracker* progressTracker ) +{ + const XMP_Uns32 zero32 = 0; + const bool checkAbort = (abortProc != 0); + + struct RsrcHeader { + XMP_Uns32 type; + XMP_Uns16 id; + }; + XMP_Assert ( (offsetof(RsrcHeader,type) == 0) && (offsetof(RsrcHeader,id) == 4) ); + + if ( this->memParsed ) XMP_Throw ( "Not file based", kXMPErr_EnforceFailure ); + + InternalRsrcMap::const_iterator rsrcPos; + InternalRsrcMap::const_iterator rsrcEnd = this->imgRsrcs.end(); + + if ( progressTracker != 0 ) { + + float totalLength = 8; + for ( rsrcPos = this->imgRsrcs.begin(); rsrcPos != rsrcEnd; ++rsrcPos ) { + const InternalRsrcInfo& currRsrc = rsrcPos->second; + totalLength += (currRsrc.dataLen + 12); + } + + size_t sizeOtherRsrc = this->otherRsrcs.size(); + for ( size_t i = 0; i < sizeOtherRsrc; ++i ) { + totalLength += this->otherRsrcs[i].rsrcLength; + } + + XMP_Assert ( progressTracker->WorkInProgress() ); + progressTracker->AddTotalWork ( totalLength ); + + } + + XMP_Uns32 destLength = 0; + XMP_Int64 destLenOffset = destRef->Offset(); + destRef->Write ( &destLength, 4 ); // Write a placeholder for the new PSIR section length. + + #if 0 + { + printf ( "\nPSIR_FileWriter::UpdateFileResources, count = %d\n", this->imgRsrcs.size() ); + InternalRsrcMap::iterator irPos = this->imgRsrcs.begin(); + InternalRsrcMap::iterator irEnd = this->imgRsrcs.end(); + for ( ; irPos != irEnd; ++irPos ) { + InternalRsrcInfo& thisRsrc = irPos->second; + printf ( " #%d, dataLen %d, origOffset %d (0x%X)%s\n", + thisRsrc.id, thisRsrc.dataLen, thisRsrc.origOffset, thisRsrc.origOffset, + (thisRsrc.changed ? ", changed" : "") ); + } + } + #endif + + // First write all of the '8BIM' resources from the map. Use the internal data if present, else + // copy the data from the file. + + RsrcHeader outHeader; + outHeader.type = MakeUns32BE ( k8BIM ); + + // printf ( "\nPSIR_FileWriter::UpdateFileResources - 8BIM resources\n" ); + for ( rsrcPos = this->imgRsrcs.begin(); rsrcPos != rsrcEnd; ++rsrcPos ) { + + const InternalRsrcInfo& currRsrc = rsrcPos->second; + + outHeader.id = MakeUns16BE ( currRsrc.id ); + destRef->Write ( &outHeader, 6 ); + destLength += 6; + + if ( currRsrc.rsrcName == 0 ) { + destRef->Write ( &zero32, 2 ); + destLength += 2; + } else { + XMP_Uns16 nameLen = currRsrc.rsrcName[0]; // ! Include room for +1. + XMP_Assert ( nameLen > 0 ); + XMP_Uns16 paddedLen = (nameLen + 2) & 0xFFFE; // ! Round up to an even total. Yes, +2! + destRef->Write ( currRsrc.rsrcName, paddedLen ); + destLength += paddedLen; + } + + XMP_Uns32 dataLen = MakeUns32BE ( currRsrc.dataLen ); + destRef->Write ( &dataLen, 4 ); + // printf ( " #%d, offset %d (0x%X), dataLen %d\n", currRsrc.id, destLength, destLength, currRsrc.dataLen ); + + if ( currRsrc.dataPtr != 0 ) { + destRef->Write ( currRsrc.dataPtr, currRsrc.dataLen ); + } else { + sourceRef->Seek ( currRsrc.origOffset, kXMP_SeekFromStart ); + XIO::Copy ( sourceRef, destRef, currRsrc.dataLen ); + } + + destLength += 4 + currRsrc.dataLen; + + if ( (currRsrc.dataLen & 1) != 0 ) { + destRef->Write ( &zero32, 1 ); // ! Pad the data to an even length. + ++destLength; + } + + } + + // Now write all of the non-8BIM resources. Copy the entire resource chunk from the source file. + + // printf ( "\nPSIR_FileWriter::UpdateFileResources - other resources\n" ); + for ( size_t i = 0; i < this->otherRsrcs.size(); ++i ) { + // printf ( " offset %d (0x%X), length %d", + // this->otherRsrcs[i].rsrcOffset, this->otherRsrcs[i].rsrcOffset, this->otherRsrcs[i].rsrcLength ); + sourceRef->Seek ( this->otherRsrcs[i].rsrcOffset, kXMP_SeekFromStart ); + XIO::Copy ( sourceRef, destRef, this->otherRsrcs[i].rsrcLength ); + destLength += this->otherRsrcs[i].rsrcLength; // Alignment padding is already included. + } + + // Write the final PSIR section length, seek back to the end of the file, return the length. + + // printf ( "\nPSIR_FileWriter::UpdateFileResources - final length %d (0x%X)\n", destLength, destLength ); + destRef->Seek ( destLenOffset, kXMP_SeekFromStart ); + XMP_Uns32 outLen = MakeUns32BE ( destLength ); + destRef->Write ( &outLen, 4 ); + destRef->Seek ( 0, kXMP_SeekFromEnd ); + + // *** Not rebuilding the internal map - turns out we never want it, why pay for the I/O. + // *** Should probably add an option for all of these cases, memory and file based. + + return destLength; + +} // PSIR_FileWriter::UpdateFileResources diff --git a/core/libs/dngwriter/extra/xmp_sdk/XMPFiles/source/FormatSupport/PSIR_MemoryReader.cpp b/core/libs/dngwriter/extra/xmp_sdk/XMPFiles/source/FormatSupport/PSIR_MemoryReader.cpp new file mode 100644 index 0000000000..7432554d7f --- /dev/null +++ b/core/libs/dngwriter/extra/xmp_sdk/XMPFiles/source/FormatSupport/PSIR_MemoryReader.cpp @@ -0,0 +1,112 @@ +// ================================================================================================= +// ADOBE SYSTEMS INCORPORATED +// Copyright 2006 Adobe Systems Incorporated +// All Rights Reserved +// +// NOTICE: Adobe permits you to use, modify, and distribute this file in accordance with the terms +// of the Adobe license agreement accompanying it. +// ================================================================================================= + +#include "public/include/XMP_Environment.h" // ! XMP_Environment.h must be the first included header. +#include "public/include/XMP_Const.h" + +#include "XMPFiles/source/FormatSupport/PSIR_Support.hpp" +#include "source/EndianUtils.hpp" +#include "source/XIO.hpp" + +#include + +// ================================================================================================= +/// \file PSIR_MemoryReader.cpp +/// \brief Implementation of the memory-based read-only form of PSIR_Manager. +// ================================================================================================= + +// ================================================================================================= +// PSIR_MemoryReader::GetImgRsrc +// ============================= + +bool PSIR_MemoryReader::GetImgRsrc ( XMP_Uns16 id, ImgRsrcInfo* info ) const +{ + ImgRsrcMap::const_iterator rsrcPos = this->imgRsrcs.find ( id ); + if ( rsrcPos == this->imgRsrcs.end() ) return false; + + if ( info != 0 ) *info = rsrcPos->second; + return true; + +} // PSIR_MemoryReader::GetImgRsrc + +// ================================================================================================= +// PSIR_MemoryReader::ParseMemoryResources +// ======================================= + +void PSIR_MemoryReader::ParseMemoryResources ( const void* data, XMP_Uns32 length, bool copyData /* = true */ ) +{ + // Get rid of any existing image resources. + + if ( this->ownedContent ) free ( this->psirContent ); + this->ownedContent = false; + this->psirContent = 0; + this->psirLength = 0; + this->imgRsrcs.clear(); + + if ( length == 0 ) return; + + // Allocate space for the full in-memory data and copy it. + + if ( ! copyData ) { + this->psirContent = (XMP_Uns8*) data; + XMP_Assert ( ! this->ownedContent ); + } else { + if ( length > 100*1024*1024 ) XMP_Throw ( "Outrageous length for memory-based PSIR", kXMPErr_BadPSIR ); + this->psirContent = (XMP_Uns8*) malloc(length); + if ( this->psirContent == 0 ) XMP_Throw ( "Out of memory", kXMPErr_NoMemory ); + memcpy ( this->psirContent, data, length ); // AUDIT: Safe, malloc'ed length bytes above. + this->ownedContent = true; + } + + this->psirLength = length; + + // Capture the info for all of the resources. We're using a map keyed by ID, so only one + // resource of each ID is recognized. Redundant resources are not legit, but have been seen in + // the field. In particular, one case has been seen of a duplicate IIM block with one empty. + // In general we keep the first seen copy to be compatible with Photoshop. A later non-empty + // copy will be taken though if the current one is empty. + + // ! Don't use map[id] to lookup, that creates a default entry if none exists! + + XMP_Uns8* psirPtr = this->psirContent; + XMP_Uns8* psirEnd = psirPtr + length; + XMP_Uns8* psirLimit = psirEnd - kMinImgRsrcSize; + + while ( psirPtr <= psirLimit ) { + + XMP_Uns32 type = GetUns32BE(psirPtr); + XMP_Uns16 id = GetUns16BE(psirPtr+4); + psirPtr += 6; // Advance to the resource name. + + XMP_Uns16 nameLen = psirPtr[0]; // ! The length for the Pascal string, w/ room for "+2". + psirPtr += ((nameLen + 2) & 0xFFFE); // ! Round up to an even offset. Yes, +2! + + if ( psirPtr > psirEnd-4 ) break; // Bad image resource. Throw instead? + + XMP_Uns32 dataLen = GetUns32BE(psirPtr); + psirPtr += 4; // Advance to the resource data. + XMP_Uns32 psirOffset = (XMP_Uns32) (psirPtr - this->psirContent); + + if ( (dataLen > length) || (psirPtr > psirEnd-dataLen) ) break; // Bad image resource. Throw instead? + + if ( type == k8BIM ) { // For read-only usage we ignore everything other than '8BIM' resources. + ImgRsrcInfo newInfo ( id, dataLen, psirPtr, psirOffset ); + ImgRsrcMap::iterator rsrcPos = this->imgRsrcs.find ( id ); + if ( rsrcPos == this->imgRsrcs.end() ) { + this->imgRsrcs.insert ( rsrcPos, ImgRsrcMap::value_type ( id, newInfo ) ); + } else if ( (rsrcPos->second.dataLen == 0) && (newInfo.dataLen != 0) ) { + rsrcPos->second = newInfo; + } + } + + psirPtr += ((dataLen + 1) & 0xFFFFFFFEUL); // ! Round up to an even offset. + + } + +} // PSIR_MemoryReader::ParseMemoryResources diff --git a/core/libs/dngwriter/extra/xmp_sdk/XMPFiles/source/FormatSupport/PSIR_Support.hpp b/core/libs/dngwriter/extra/xmp_sdk/XMPFiles/source/FormatSupport/PSIR_Support.hpp new file mode 100644 index 0000000000..bd231b38b8 --- /dev/null +++ b/core/libs/dngwriter/extra/xmp_sdk/XMPFiles/source/FormatSupport/PSIR_Support.hpp @@ -0,0 +1,329 @@ +#ifndef __PSIR_Support_hpp__ +#define __PSIR_Support_hpp__ 1 + +// ================================================================================================= +// ADOBE SYSTEMS INCORPORATED +// Copyright 2006 Adobe Systems Incorporated +// All Rights Reserved +// +// NOTICE: Adobe permits you to use, modify, and distribute this file in accordance with the terms +// of the Adobe license agreement accompanying it. +// ================================================================================================= + +#include "public/include/XMP_Environment.h" // ! XMP_Environment.h must be the first included header. + +#include "public/include/XMP_Const.h" +#include "public/include/XMP_IO.hpp" + +#include "XMPFiles/source/XMPFiles_Impl.hpp" +#include "source/XMPFiles_IO.hpp" + +#include "source/EndianUtils.hpp" +#include "source/XMP_ProgressTracker.hpp" + +#include + +// ================================================================================================= +/// \file PSIR_Support.hpp +/// \brief XMPFiles support for Photoshop image resources. +/// +/// This header provides Photoshop image resource (PSIR) support specific to the needs of XMPFiles. +/// This is not intended for general purpose PSIR processing. PSIR_Manager is an abstract base +/// class with 2 concrete derived classes, PSIR_MemoryReader and PSIR_FileWriter. +/// +/// PSIR_MemoryReader provides read-only support for PSIR streams that are small enough to be kept +/// entirely in memory. This allows optimizations to reduce heap usage and processing code. It is +/// sufficient for browsing access to the image resources (mainly the IPTC) in JPEG files. Think of +/// PSIR_MemoryReader as "memory-based AND read-only". +/// +/// PSIR_FileWriter is for cases where updates are needed or the PSIR stream is too large to be kept +/// entirely in memory. Think of PSIR_FileWriter as "file-based OR read-write". +/// +/// The needs of XMPFiles are well defined metadata access. Only a few image resources are handled. +/// This is the case for all of the derived classes, even though the memory based ones happen to +/// have all of the image resources in memory. Being "handled" means being in the image resource +/// map used by GetImgRsrc. The handled image resources are: +/// \li 1028 - IPTC +/// \li 1034 - Copyrighted flag +/// \li 1035 - Copyright information URL +/// \li 1058 - Exif metadata +/// \li 1060 - XMP +/// \li 1061 - IPTC digest +/// +/// \note These classes are for use only when directly compiled and linked. They should not be +/// packaged in a DLL by themselves. They do not provide any form of C++ ABI protection. +// ================================================================================================= + + +// These aren't inside PSIR_Manager because the static array can't be initialized there. + +enum { + k8BIM = 0x3842494DUL, // The 4 ASCII characters '8BIM'. + kMinImgRsrcSize = 4+2+2+4 // The minimum size for an image resource. +}; + +enum { + kPSIR_IPTC = 1028, + kPSIR_CopyrightFlag = 1034, + kPSIR_CopyrightURL = 1035, + kPSIR_Exif = 1058, + kPSIR_XMP = 1060, + kPSIR_IPTCDigest = 1061 +}; + +enum { kPSIR_MetadataCount = 6 }; +static const XMP_Uns16 kPSIR_MetadataIDs[] = // ! Must be in descending order with 0 sentinel. + { kPSIR_IPTCDigest, kPSIR_XMP, kPSIR_Exif, kPSIR_CopyrightURL, kPSIR_CopyrightFlag, kPSIR_IPTC, 0 }; + +// ================================================================================================= +// ================================================================================================= + +// NOTE: Although Photoshop image resources have a type and ID, for metadatya we only care about +// those of type "8BIM". Resources of other types are preserved in files, but can't be individually +// accessed through the PSIR_Manager API. + +// ================================================================================================= +// PSIR_Manager +// ============ + +class PSIR_Manager { // The abstract base class. +public: + + // --------------------------------------------------------------------------------------------- + // Types and constants + + struct ImgRsrcInfo { + XMP_Uns16 id; + XMP_Uns32 dataLen; + const void* dataPtr; // ! The data is read-only! + XMP_Uns32 origOffset; // The offset (at parse time) of the resource data. + ImgRsrcInfo() : id(0), dataLen(0), dataPtr(0), origOffset(0) {}; + ImgRsrcInfo ( XMP_Uns16 _id, XMP_Uns32 _dataLen, void* _dataPtr, XMP_Uns32 _origOffset ) + : id(_id), dataLen(_dataLen), dataPtr(_dataPtr), origOffset(_origOffset) {}; + }; + + // The origOffset is the absolute file offset for file parses, the memory block offset for + // memory parses. It is the offset of the resource data portion, not the overall resource. + + // --------------------------------------------------------------------------------------------- + // Get the information about a "handled" image resource. Returns false if the image resource is + // not handled, even if it was present in the parsed input. + + virtual bool GetImgRsrc ( XMP_Uns16 id, ImgRsrcInfo* info ) const = 0; + + // --------------------------------------------------------------------------------------------- + // Set the value for an image resource. It can be any resource, even one not originally handled. + + virtual void SetImgRsrc ( XMP_Uns16 id, const void* dataPtr, XMP_Uns32 length ) = 0; + + // --------------------------------------------------------------------------------------------- + // Delete an image resource. Does nothing if the image resource does not exist. + + virtual void DeleteImgRsrc ( XMP_Uns16 id ) = 0; + + // --------------------------------------------------------------------------------------------- + // Determine if the image resources are changed. + + virtual bool IsChanged() = 0; + virtual bool IsLegacyChanged() = 0; + + // --------------------------------------------------------------------------------------------- + + virtual void ParseMemoryResources ( const void* data, XMP_Uns32 length, bool copyData = true ) = 0; + virtual void ParseFileResources ( XMP_IO* fileRef, XMP_Uns32 length ) = 0; + + // --------------------------------------------------------------------------------------------- + // Update the image resources to reflect the changed values. Both \c UpdateMemoryResources and + // \c UpdateFileResources return the new size of the image resource block. The dataPtr returned + // by \c UpdateMemoryResources must be treated as read only. It exists until the PSIR_Manager + // destructor is called. UpdateMemoryResources can be used on a read-only instance to get the + // raw data block info. + + virtual XMP_Uns32 UpdateMemoryResources ( void** dataPtr ) = 0; + virtual XMP_Uns32 UpdateFileResources ( XMP_IO* sourceRef, XMP_IO* destRef, + XMP_AbortProc abortProc, void * abortArg, + XMP_ProgressTracker* progressTracker ) = 0; + + // --------------------------------------------------------------------------------------------- + + virtual ~PSIR_Manager() {}; + +protected: + + PSIR_Manager() {}; + +}; // PSIR_Manager + + +// ================================================================================================= +// ================================================================================================= + + +// ================================================================================================= +// PSIR_MemoryReader +// ================= + +class PSIR_MemoryReader : public PSIR_Manager { // The leaf class for memory-based read-only access. +public: + + bool GetImgRsrc ( XMP_Uns16 id, ImgRsrcInfo* info ) const; + + void SetImgRsrc ( XMP_Uns16 id, const void* dataPtr, XMP_Uns32 length ) { NotAppropriate(); }; + + void DeleteImgRsrc ( XMP_Uns16 id ) { NotAppropriate(); }; + + bool IsChanged() { return false; }; + bool IsLegacyChanged() { return false; }; + + void ParseMemoryResources ( const void* data, XMP_Uns32 length, bool copyData = true ); + void ParseFileResources ( XMP_IO* file, XMP_Uns32 length ) { NotAppropriate(); }; + + XMP_Uns32 UpdateMemoryResources ( void** dataPtr ) { if ( dataPtr != 0 ) *dataPtr = psirContent; return psirLength; }; + XMP_Uns32 UpdateFileResources ( XMP_IO* sourceRef, XMP_IO* destRef, + XMP_AbortProc abortProc, void * abortArg, + XMP_ProgressTracker* progressTracker ) { NotAppropriate(); return 0; }; + + PSIR_MemoryReader() : ownedContent(false), psirLength(0), psirContent(0) {}; + + virtual ~PSIR_MemoryReader() { if ( this->ownedContent ) free ( this->psirContent ); }; + +private: + + // Memory usage notes: PSIR_MemoryReader is for memory-based read-only usage (both apply). There + // is no need to ever allocate separate blocks of memory, everything is used directly from the + // PSIR stream. + + bool ownedContent; + + XMP_Uns32 psirLength; + XMP_Uns8* psirContent; + + typedef std::map ImgRsrcMap; + + ImgRsrcMap imgRsrcs; + + static inline void NotAppropriate() { XMP_Throw ( "Not appropriate for PSIR_Reader", kXMPErr_InternalFailure ); }; + +}; // PSIR_MemoryReader + + +// ================================================================================================= +// ================================================================================================= + + +// ================================================================================================= +// PSIR_FileWriter +// =============== + +class PSIR_FileWriter : public PSIR_Manager { // The leaf class for file-based read-write access. +public: + + bool GetImgRsrc ( XMP_Uns16 id, ImgRsrcInfo* info ) const; + + void SetImgRsrc ( XMP_Uns16 id, const void* dataPtr, XMP_Uns32 length ); + + void DeleteImgRsrc ( XMP_Uns16 id ); + + bool IsChanged() { return this->changed; }; + + bool IsLegacyChanged(); + + void ParseMemoryResources ( const void* data, XMP_Uns32 length, bool copyData = true ); + void ParseFileResources ( XMP_IO* file, XMP_Uns32 length ); + + XMP_Uns32 UpdateMemoryResources ( void** dataPtr ); + XMP_Uns32 UpdateFileResources ( XMP_IO* sourceRef, XMP_IO* destRef, + XMP_AbortProc abortProc, void * abortArg, + XMP_ProgressTracker* progressTracker ); + + PSIR_FileWriter() : changed(false), legacyDeleted(false), memParsed(false), fileParsed(false), + ownedContent(false), memLength(0), memContent(0) {}; + + virtual ~PSIR_FileWriter(); + + // Memory usage notes: PSIR_FileWriter is for file-based OR read/write usage. For memory-based + // streams the dataPtr and rsrcName are initially into the stream. The dataPtr becomes a + // separate allocation when SetImgRsrc is called, the rsrcName stays into the original stream. + // For file-based streams the dataPtr and rsrcName are always a separate allocation. Again, + // the dataPtr changes when SetImgRsrc is called, the rsrcName stays unchanged. + + // ! The working data values are always big endian, no matter where stored. It is the client's + // ! responsibility to flip them as necessary. + + static const bool kIsFileBased = true; // For use in the InternalRsrcInfo constructor. + static const bool kIsMemoryBased = false; + + struct InternalRsrcInfo { + public: + + bool changed; + bool fileBased; + XMP_Uns16 id; + XMP_Uns32 dataLen; + void* dataPtr; // ! Null if the value is not captured! + XMP_Uns32 origOffset; // The offset (at parse time) of the resource data. + XMP_Uns8* rsrcName; // ! A Pascal string, leading length byte, no nul terminator! + + inline void FreeData() { + if ( this->fileBased || this->changed ) { + if ( this->dataPtr != 0 ) { free ( this->dataPtr ); this->dataPtr = 0; } + } + } + + inline void FreeName() { + if ( this->fileBased && (this->rsrcName != 0) ) { + free ( this->rsrcName ); + this->rsrcName = 0; + } + } + + InternalRsrcInfo ( XMP_Uns16 _id, XMP_Uns32 _dataLen, bool _fileBased ) + : changed(false), fileBased(_fileBased), id(_id), dataLen(_dataLen), dataPtr(0), + origOffset(0), rsrcName(0) {}; + ~InternalRsrcInfo() { this->FreeData(); this->FreeName(); }; + + void operator= ( const InternalRsrcInfo & in ) + { + // ! Gag! Transfer ownership of the dataPtr and rsrcName! + this->FreeData(); + memcpy ( this, &in, sizeof(*this) ); // AUDIT: Use of sizeof(InternalRsrcInfo) is safe. + *((void**)&in.dataPtr) = 0; // The pointer is now owned by "this". + *((void**)&in.rsrcName) = 0; // The pointer is now owned by "this". + }; + + private: + + InternalRsrcInfo() // Hidden on purpose, fileBased must be properly set. + : changed(false), fileBased(false), id(0), dataLen(0), dataPtr(0), origOffset(0), rsrcName(0) {}; + + }; + + // The origOffset is the absolute file offset for file parses, the memory block offset for + // memory parses. It is the offset of the resource data portion, not the overall resource. + +private: + + bool changed, legacyDeleted; + bool memParsed, fileParsed; + bool ownedContent; + + XMP_Uns32 memLength; + XMP_Uns8* memContent; + + typedef std::map InternalRsrcMap; + InternalRsrcMap imgRsrcs; + + struct OtherRsrcInfo { // For the resources of types other than "8BIM". + XMP_Uns32 rsrcOffset; // The offset of the resource origin, the type field. + XMP_Uns32 rsrcLength; // The full length of the resource, offset to the next resource. + OtherRsrcInfo() : rsrcOffset(0), rsrcLength(0) {}; + OtherRsrcInfo ( XMP_Uns32 _rsrcOffset, XMP_Uns32 _rsrcLength ) + : rsrcOffset(_rsrcOffset), rsrcLength(_rsrcLength) {}; + }; + std::vector otherRsrcs; + + void DeleteExistingInfo(); + +}; // PSIR_FileWriter + +#endif // __PSIR_Support_hpp__ diff --git a/core/libs/dngwriter/extra/xmp_sdk/XMPFiles/source/FormatSupport/PackageFormat_Support.cpp b/core/libs/dngwriter/extra/xmp_sdk/XMPFiles/source/FormatSupport/PackageFormat_Support.cpp new file mode 100644 index 0000000000..9405293f6f --- /dev/null +++ b/core/libs/dngwriter/extra/xmp_sdk/XMPFiles/source/FormatSupport/PackageFormat_Support.cpp @@ -0,0 +1,124 @@ +// ================================================================================================= +// ADOBE SYSTEMS INCORPORATED +// Copyright 2013 Adobe Systems Incorporated +// All Rights Reserved +// +// NOTICE: Adobe permits you to use, modify, and distribute this file in accordance with the terms +// of the Adobe license agreement accompanying it. +// ================================================================================================= + +#include "public/include/XMP_Environment.h" // ! XMP_Environment.h must be the first included header. + +#include "XMPFiles/source/XMPFiles_Impl.hpp" +#include "XMPFiles/source/FormatSupport/PackageFormat_Support.hpp" +#include + +// ================================================================================================= +/// \file PackageFormat_Support.cpp +/// +// ================================================================================================= + + +// ================================================================================================= +// PackageFormat_Support::GetPostfixRange +// ================================ + +#if 0 +XMP_Bool PackageFormat_Support::GetPostfixRange ( XMP_FileFormat format , XMP_StringPtr extension, XMP_Uns32 * range ) +{ + switch ( format ) { + case kXMP_XDCAM_EXFile: + range[0] = 1; range[1] = 99; + break; + case kXMP_CanonXFFile: + range[0] = 1; range[1] = 99; + break; + case kXMP_XDCAM_SAMFile: + case kXMP_XDCAM_FAMFile: + range[0] = 1; range[1] = 99; + break; + case kXMP_P2File: + range[0] = 0; range[1] = 99;// for voice memo files + if(strcmp(extension, ".MXF") == 0) + range[1] = 15;// for audio essence files + break; + default: + return false; + } + return true; +} // PackageFormat_Support::GetPostfixRange +#endif + +// ================================================================================================= +// PackageFormat_Support::AddResourceIfExists +// ================================ + +bool PackageFormat_Support::AddResourceIfExists ( XMP_StringVector * resourceList, const XMP_VarString & file ) +{ + if ( Host_IO::Exists ( file.c_str() ) ) { + resourceList->push_back ( file ); + return true; + } + return false; +} // PackageFormat_Support::AddResourceIfExists + +#if 0 +// ================================================================================================= +// PackageFormat_Support::AddResourceIfExists +// ================================ + +bool PackageFormat_Support::AddResourceIfExists ( XMP_StringVector * resourceList, const XMP_VarString & noExtPath, + XMP_StringPtr extension, XMP_FileFormat format ) +{ + XMP_Uns32 range[2]; + XMP_Bool atLeastOneFileAdded = false, fileAdded = false; + XMP_VarString iStr, filePath; + if ( GetPostfixRange ( format, extension, range ) ) { + for( XMP_Uns32 index = range[0]; index <= range[1] ; ++index ) + { + SXMPUtils::ConvertFromInt ( index, NULL, &iStr ) ; + if ( index < LEAST_TWO_DIGIT_INT ) + iStr = '0' + iStr; + filePath = noExtPath + iStr + extension; + fileAdded = AddResourceIfExists ( resourceList, filePath ); + atLeastOneFileAdded |= fileAdded ; + } + } + return atLeastOneFileAdded; +} // PackageFormat_Support::AddResourceIfExists + +#endif +// ================================================================================================= +// PackageFormat_Support::AddResourceIfExists +// ================================ +bool PackageFormat_Support::AddResourceIfExists ( XMP_StringVector * resourceList, const XMP_VarString & folderPath, + XMP_StringPtr prefix, XMP_StringPtr postfix ) +{ + Host_IO::FolderRef folderHandle = Host_IO::OpenFolder ( folderPath.c_str() ); + if ( folderHandle == Host_IO::noFolderRef || !prefix || !postfix ) + return false;// can't open folder. + XMP_VarString fileName, filePath; + size_t fileNameLength; + size_t prefixLength = strlen ( prefix ); + size_t postfixLength = strlen ( postfix ); + bool atleastOneFileAdded = false; + while ( Host_IO::GetNextChild ( folderHandle, &fileName ) ) + { + fileNameLength = fileName.length(); + // Check if the file name starts with prefix and ends with postfix + if ( fileNameLength >= ( prefixLength + postfixLength ) && + fileName.compare ( fileNameLength-postfixLength, postfixLength, postfix ) == 0 && + fileName.compare ( 0, prefixLength, prefix ) == 0) + { + filePath = folderPath + kDirChar + fileName; + PackageFormat_Support::AddResourceIfExists ( resourceList, filePath ); + atleastOneFileAdded = true; + } + } + // close folder + Host_IO::CloseFolder ( folderHandle ); + return atleastOneFileAdded; +} // PackageFormat_Support::AddResourceIfExists + + +// ================================================================================================= diff --git a/core/libs/dngwriter/extra/xmp_sdk/XMPFiles/source/FormatSupport/PackageFormat_Support.hpp b/core/libs/dngwriter/extra/xmp_sdk/XMPFiles/source/FormatSupport/PackageFormat_Support.hpp new file mode 100644 index 0000000000..8883c9ee14 --- /dev/null +++ b/core/libs/dngwriter/extra/xmp_sdk/XMPFiles/source/FormatSupport/PackageFormat_Support.hpp @@ -0,0 +1,39 @@ +#ifndef __PackageFormat_Support_hpp__ +#define __PackageFormat_Support_hpp__ 1 + +// ================================================================================================= +// ADOBE SYSTEMS INCORPORATED +// Copyright 2013 Adobe Systems Incorporated +// All Rights Reserved +// +// NOTICE: Adobe permits you to use, modify, and distribute this file in accordance with the terms +// of the Adobe license agreement accompanying it. +// ================================================================================================= + +#include "public/include/XMP_Environment.h" // ! This must be the first include. + +#include "source/XMP_LibUtils.hpp" + +// ================================================================================================= +/// \file PackageFormat_Support.hpp +/// \brief XMPFiles support for folder based formats. +/// +// ================================================================================================= + +namespace PackageFormat_Support +{ + + // Checks if the file at path "file" exists. + // If it exists then it adds to "resourceList" and returns true. + bool AddResourceIfExists ( XMP_StringVector * resourceList, const XMP_VarString & file ); + + // This function adds all the existing files in the specified folder whose name starts with prefix and ends with postfix. + bool AddResourceIfExists ( XMP_StringVector * resourceList, const XMP_VarString & folderPath, + XMP_StringPtr prefix, XMP_StringPtr postfix); + + +} // namespace PackageFormat_Support + +// ================================================================================================= + +#endif // __PackageFormat_Support_hpp__ diff --git a/core/libs/dngwriter/extra/xmp_sdk/XMPFiles/source/FormatSupport/PostScript_Support.cpp b/core/libs/dngwriter/extra/xmp_sdk/XMPFiles/source/FormatSupport/PostScript_Support.cpp new file mode 100644 index 0000000000..fec55fbfc1 --- /dev/null +++ b/core/libs/dngwriter/extra/xmp_sdk/XMPFiles/source/FormatSupport/PostScript_Support.cpp @@ -0,0 +1,1093 @@ +// ================================================================================================= +// ADOBE SYSTEMS INCORPORATED +// Copyright 2012 Adobe Systems Incorporated +// All Rights Reserved +// +// NOTICE: Adobe permits you to use, modify, and distribute this file in accordance with the terms +// of the Adobe license agreement accompanying it. +// ================================================================================================= + +#include "XMPFiles/source/FormatSupport/PostScript_Support.hpp" +#include "XMP.hpp" +#include +#include + +// ================================================================================================= +// PostScript_Support::HasCodesGT127 +// ================================= +// +// function to detect character codes greater than 127 in a string +bool PostScript_Support::HasCodesGT127(const std::string & value) +{ + size_t vallen=value.length(); + for (size_t index=0;index127) + { + return true; + } + } + return false; +} + +// ================================================================================================= +// PostScript_Support::SkipTabsAndSpaces +// ===================================== +// +// function moves the file pointer ahead such that it skips all tabs and spaces +bool PostScript_Support::SkipTabsAndSpaces(XMP_IO* file,IOBuffer& ioBuf) +{ + while ( true ) + { + if ( ! CheckFileSpace ( file, &ioBuf, 1 ) ) return false; + if ( ! IsSpaceOrTab ( *ioBuf.ptr ) ) break; + ++ioBuf.ptr; + } + return true; +} + +// ================================================================================================= +// PostScript_Support::SkipUntilNewline +// ==================================== +// +// function moves the file pointer ahead such that it skips all characters until a newline +bool PostScript_Support::SkipUntilNewline(XMP_IO* file,IOBuffer& ioBuf) +{ + char ch; + do + { + if ( ! CheckFileSpace ( file, &ioBuf, 1 ) ) return false; + ch = *ioBuf.ptr; + ++ioBuf.ptr; + } while ( ! IsNewline ( ch ) ); + if (ch==kCR &&*ioBuf.ptr==kLF) + { + if ( ! CheckFileSpace ( file, &ioBuf, 1 ) ) return false; + ++ioBuf.ptr; + } + return true; +} + + +// ================================================================================================= +// RevRefillBuffer and RevCheckFileSpace +// ====================================== +// +// These helpers are similar to RefillBuffer and CheckFileSpace with the difference that the it traverses +// the file stream in reverse order +void PostScript_Support::RevRefillBuffer ( XMP_IO* fileRef, IOBuffer* ioBuf ) +{ + // Refill including part of the current data, seek back to the new buffer origin and read. + size_t reverseSeek = ioBuf->limit - ioBuf->ptr; + if (ioBuf->filePos>kIOBufferSize) + { + ioBuf->filePos = fileRef->Seek ( -((XMP_Int64)(kIOBufferSize+reverseSeek)), kXMP_SeekFromCurrent ); + ioBuf->len = fileRef->Read ( &ioBuf->data[0], kIOBufferSize ); + ioBuf->ptr = &ioBuf->data[0]+ioBuf->len; + ioBuf->limit = ioBuf->ptr ; + } + else + { + XMP_Int64 rev = (ioBuf->ptr-&ioBuf->data[0]) + ioBuf->filePos; + ioBuf->filePos = fileRef->Seek ( 0, kXMP_SeekFromStart ); + ioBuf->len = fileRef->Read ( &ioBuf->data[0], kIOBufferSize ); + if ( rev > (XMP_Int64)ioBuf->len )throw XMP_Error ( kXMPErr_ExternalFailure, "Seek failure in FillBuffer" ); + ioBuf->ptr = &ioBuf->data[0]+rev; + ioBuf->limit = &ioBuf->data[0]+ioBuf->len; + } + + +} +bool PostScript_Support::RevCheckFileSpace ( XMP_IO* fileRef, IOBuffer* ioBuf, size_t neededLen ) +{ + if ( size_t(ioBuf->ptr - &ioBuf->data[0]) < size_t(neededLen) ) + { // ! Avoid VS.Net compare warnings. + PostScript_Support::RevRefillBuffer ( fileRef, ioBuf ); + } + return (size_t(ioBuf->ptr - &ioBuf->data[0]) >= size_t(neededLen)); +} + +// ================================================================================================= +// SearchBBoxInTrailer +// =================== +// +// Function searches the Bounding Box in the comments after the %Trailer +// this function gets called when the DSC comment BoundingBox: value is +// (atend) +// returns true if atleast one BoundingBox: is found after %Trailer +inline static bool SearchBBoxInTrailer(XMP_IO* fileRef,IOBuffer& ioBuf) +{ + bool bboxfoundintrailer=false; + if ( ! PostScript_Support::SkipTabsAndSpaces( fileRef, ioBuf ) ) return false; + if ( ! IsNewline ( *ioBuf.ptr ) ) return false; + ++ioBuf.ptr; + // Scan for all the %%Trailer outside %%BeginDocument: & %%EndDocument comments + while(true) + { + if ( ! CheckFileSpace ( fileRef, &ioBuf, kPSContainsBeginDocString.length() ) ) return false; + if ( CheckBytes ( ioBuf.ptr, Uns8Ptr(kPSContainsTrailerString.c_str()), kPSContainsTrailerString.length() )) + { + //found %%Trailer now search for proper %%BoundingBox + ioBuf.ptr+=kPSContainsTrailerString.length(); + //skip chars after %%Trailer till newline + if ( ! PostScript_Support::SkipUntilNewline( fileRef, ioBuf ) ) return false; + while(true) + { + if ( ! CheckFileSpace ( fileRef, &ioBuf, kPSContainsBBoxString.length() ) ) return false; + //check for "%%BoundingBox:" + if (CheckBytes ( ioBuf.ptr, Uns8Ptr(kPSContainsBBoxString.c_str()), kPSContainsBBoxString.length() ) ) + { + //found "%%BoundingBox:" + ioBuf.ptr+=kPSContainsBBoxString.length(); + // Skip leading spaces and tabs. + if ( ! PostScript_Support::SkipTabsAndSpaces( fileRef, ioBuf ) ) return false; + if ( IsNewline ( *ioBuf.ptr ) ) return false; // Reached the end of the %%BoundingBox comment. + bboxfoundintrailer=true; + break; + } + //skip chars till newline + if ( ! PostScript_Support::SkipUntilNewline( fileRef, ioBuf ) ) return false; + } + + break; + } + else if ( CheckBytes ( ioBuf.ptr, Uns8Ptr(kPSContainsBeginDocString.c_str()), kPSContainsBeginDocString.length() ) ) + { + //"%%BeginDocument:" Found search for "%%EndDocument" + ioBuf.ptr+=kPSContainsBeginDocString.length(); + //skip chars after "%%BeginDocument:" till newline + if ( ! PostScript_Support::SkipUntilNewline( fileRef, ioBuf ) ) return false; + while(true) + { + if ( ! CheckFileSpace ( fileRef, &ioBuf, kPSContainsEndDocString.length() ) ) return false; + //check for "%%EndDocument" + if (CheckBytes ( ioBuf.ptr, Uns8Ptr(kPSContainsEndDocString.c_str()), kPSContainsEndDocString.length() ) ) + { + //found "%%EndDocument" + ioBuf.ptr+=kPSContainsEndDocString.length(); + break; + } + //skip chars till newline + if ( ! PostScript_Support::SkipUntilNewline( fileRef, ioBuf ) ) return false; + }// while to search %%EndDocument + + } //else if to consume a pair of %%BeginDocument: and %%EndDocument + //skip chars till newline + if ( ! PostScript_Support::SkipUntilNewline( fileRef, ioBuf ) ) return false; + }// while for scanning %%BoundingBox after %%Trailer + if (!bboxfoundintrailer) return false; + return true; +} + +// ================================================================================================= +// PostScript_Support::IsValidPSFile +// ================================= +// +// Determines if the file is a valid PostScript or EPS file +// Checks done +// Looks for a valid Poscript header +// For EPS file checks for a valid Bounding Box comment +bool PostScript_Support::IsValidPSFile(XMP_IO* fileRef,XMP_FileFormat &format) +{ + IOBuffer ioBuf; + XMP_Int64 psOffset; + size_t psLength; + XMP_Uns32 fileheader,psMajorVer, psMinorVer,epsMajorVer,epsMinorVer; + char ch; + // Check for the binary EPSF preview header. + + fileRef->Rewind(); + if ( ! CheckFileSpace ( fileRef, &ioBuf, 4 ) ) return false; + fileheader = GetUns32BE ( ioBuf.ptr ); + + if ( fileheader == 0xC5D0D3C6 ) + { + + if ( ! CheckFileSpace ( fileRef, &ioBuf, 30 ) ) return false; + + psOffset = GetUns32LE ( ioBuf.ptr+4 ); // PostScript offset. + psLength = GetUns32LE ( ioBuf.ptr+8 ); // PostScript length. + + FillBuffer ( fileRef, psOffset, &ioBuf ); // Make sure buffer starts at psOffset for length check. + if ( (ioBuf.len < kIOBufferSize) && (ioBuf.len < psLength) ) return false; // Not enough PostScript. + + } + + // Check the start of the PostScript DSC header comment. + + if ( ! CheckFileSpace ( fileRef, &ioBuf, (kPSFileTag.length() + 3 + 1) ) ) return false; + if ( ! CheckBytes ( ioBuf.ptr, Uns8Ptr(kPSFileTag.c_str()), kPSFileTag.length() ) ) return false; + ioBuf.ptr += kPSFileTag.length(); + + // Check the PostScript DSC major version number. + + psMajorVer = 0; + while ( (ioBuf.ptr < ioBuf.limit) && IsNumeric( *ioBuf.ptr ) ) + { + psMajorVer = (psMajorVer * 10) + (*ioBuf.ptr - '0'); + if ( psMajorVer > 1000 ) return false; // Overflow. + ioBuf.ptr += 1; + } + if ( psMajorVer < 3 ) return false; // The version must be at least 3.0. + + if ( ! CheckFileSpace ( fileRef, &ioBuf, 3 ) ) return false; + if ( *ioBuf.ptr != '.' ) return false; // No minor number. + ioBuf.ptr += 1; + + // Check the PostScript DSC minor version number. + + psMinorVer = 0; + while ( (ioBuf.ptr < ioBuf.limit) && IsNumeric( *ioBuf.ptr ) ) + { + psMinorVer = (psMinorVer * 10) + (*ioBuf.ptr - '0'); + if ( psMinorVer > 1000 ) return false; // Overflow. + ioBuf.ptr += 1; + } + + switch( format ) + { + case kXMP_PostScriptFile: + { + // Almost done for plain PostScript, check for whitespace. + + if ( ! CheckFileSpace ( fileRef, &ioBuf, 1 ) ) return false; + if ( ! IsWhitespace(*ioBuf.ptr) ) return false; + ioBuf.ptr += 1; + + break; + } + case kXMP_UnknownFile: + { + if ( ! SkipTabsAndSpaces ( fileRef, ioBuf ) ) return false; + + if ( ! CheckFileSpace ( fileRef, &ioBuf, 5 ) ) return false; + // checked PS header to this point Atkleast a PostScript File + format=kXMP_PostScriptFile; + //return true if no "EPSF-" is found as it is a valid PS atleast + if ( ! CheckBytes ( ioBuf.ptr, Uns8Ptr("EPSF-"), 5 ) ) return true; + + }//intentional fall through for further checking of unknown files + case kXMP_EPSFile: + { + + if ( ! SkipTabsAndSpaces ( fileRef, ioBuf ) ) return false; + // Check for the EPSF keyword on the header comment. + + if ( ! CheckFileSpace ( fileRef, &ioBuf, 5+3+1 ) ) return false; + if ( ! CheckBytes ( ioBuf.ptr, Uns8Ptr("EPSF-"), 5 ) ) return false; + ioBuf.ptr += 5; + + // Check the EPS major version number. + + epsMajorVer = 0; + while ( (ioBuf.ptr < ioBuf.limit) &&IsNumeric( *ioBuf.ptr ) ) { + epsMajorVer = (epsMajorVer * 10) + (*ioBuf.ptr - '0'); + if ( epsMajorVer > 1000 ) return false; // Overflow. + ioBuf.ptr += 1; + } + if ( epsMajorVer < 3 ) return false; // The version must be at least 3.0. + + if ( ! CheckFileSpace ( fileRef, &ioBuf, 3 ) ) return false; + if ( *ioBuf.ptr != '.' ) return false; // No minor number. + ioBuf.ptr += 1; + + // Check the EPS minor version number. + + epsMinorVer = 0; + while ( (ioBuf.ptr < ioBuf.limit) && IsNumeric( *ioBuf.ptr ) ) { + epsMinorVer = (epsMinorVer * 10) + (*ioBuf.ptr - '0'); + if ( epsMinorVer > 1000 ) return false; // Overflow. + ioBuf.ptr += 1; + } + + if ( ! SkipTabsAndSpaces ( fileRef, ioBuf ) ) return false; + if ( ! CheckFileSpace ( fileRef, &ioBuf, 1 ) ) return false; + if ( ! IsNewline( *ioBuf.ptr ) ) return false; + ch=*ioBuf.ptr; + ioBuf.ptr += 1; + if (ch==kCR &&*ioBuf.ptr==kLF) + { + if ( ! CheckFileSpace ( fileRef, &ioBuf, 1 ) ) return false; + ++ioBuf.ptr; + } + + + while ( true ) + { + if ( ! CheckFileSpace ( fileRef, &ioBuf, kPSContainsBBoxString.length() ) ) return false; + + if ( CheckBytes ( ioBuf.ptr, Uns8Ptr(kPSEndCommentString.c_str()), kPSEndCommentString.length() ) //explicit endcommentcheck + || *ioBuf.ptr!='%' || !(*(ioBuf.ptr+1)>32 && *(ioBuf.ptr+1)<=126 )) // implicit endcomment check + { + // Found "%%EndComments", don't look any further. + return false; + } + else if ( ! CheckBytes ( ioBuf.ptr, Uns8Ptr(kPSContainsBBoxString.c_str()), kPSContainsBBoxString.length() ) ) + { + // Not "%%EndComments" or "%%BoundingBox:", skip past the end of this line. + if ( ! SkipUntilNewline ( fileRef, ioBuf ) ) return true; + } + else + { + + // Found "%%BoundingBox:", look for llx lly urx ury. + ioBuf.ptr += kPSContainsBBoxString.length(); + //Check for atleast a mandatory space b/w "%%BoundingBox:" and llx lly urx ury + if ( ! CheckFileSpace ( fileRef, &ioBuf, 1 ) ) return false; + if ( ! IsSpaceOrTab ( *ioBuf.ptr ) ) return false; + ioBuf.ptr++; + + while ( true ) + { + // Skip leading spaces and tabs. + if ( ! SkipTabsAndSpaces( fileRef, ioBuf ) ) return false; + if ( IsNewline ( *ioBuf.ptr ) ) return false; // Reached the end of the %%BoundingBox comment. + + //if the comment is %%BoundingBox: (atend) go past the %%Trailer to check BBox + bool bboxfoundintrailer=false; + if (*ioBuf.ptr=='(') + { + if ( ! CheckFileSpace ( fileRef, &ioBuf, kPSContainsAtendString.length() ) ) return false; + + if ( CheckBytes ( ioBuf.ptr, Uns8Ptr(kPSContainsAtendString.c_str()), kPSContainsAtendString.length() ) ) + { + // search for Bounding Box Past Trailer + ioBuf.ptr += kPSContainsAtendString.length(); + bboxfoundintrailer=SearchBBoxInTrailer( fileRef, ioBuf ); + } + + if (!bboxfoundintrailer) + return false; + + }//if (*ioBuf.ptr=='(') + + int noOfIntegers=0; + // verifies for llx lly urx ury. + while(true) + { + if ( ! CheckFileSpace ( fileRef, &ioBuf, 1 ) ) return false; + if(IsPlusMinusSign(*ioBuf.ptr )) + ++ioBuf.ptr; + bool atleastOneNumeric=false; + while ( true) + { + if ( ! CheckFileSpace ( fileRef, &ioBuf, 1 ) ) return false; + if (!IsNumeric ( *ioBuf.ptr ) ) break; + ++ioBuf.ptr; + atleastOneNumeric=true; + } + if (!atleastOneNumeric) return false; + + if ( ! SkipTabsAndSpaces( fileRef, ioBuf ) ) return false; + noOfIntegers++; + if ( IsNewline ( *ioBuf.ptr ) ) break; + } + if (noOfIntegers!=4) + return false; + format=kXMP_EPSFile; + return true; + } + + } //Found "%%BoundingBox:" + + } // Outer marker loop. + } + default: + { + return false; + } + } + return true; +} + + +// ================================================================================================= +// PostScript_Support::IsSFDFilterUsed +// ================================= +// +// Determines Whether the metadata is embedded using the Sub-FileDecode Approach or no +// In case of Sub-FileDecode filter approach the metaData can be easily extended without +// the need to inject a new XMP packet before the existing Packet. +// +bool PostScript_Support::IsSFDFilterUsed(XMP_IO* &fileRef, XMP_Int64 xpacketOffset) +{ + IOBuffer ioBuf; + fileRef->Rewind(); + fileRef->Seek((xpacketOffset/kIOBufferSize)*kIOBufferSize,kXMP_SeekFromStart); + if ( ! CheckFileSpace ( fileRef, &ioBuf,xpacketOffset%kIOBufferSize ) ) return false; + ioBuf.ptr+=(xpacketOffset%kIOBufferSize); + //skip white spaces + while(true) + { + if ( ! PostScript_Support::RevCheckFileSpace ( fileRef, &ioBuf, 1 ) ) return false; + if (!IsWhitespace(*ioBuf.ptr)) break; + --ioBuf.ptr; + } + std::string temp; + bool filterFound=false; + while(true) + { + if ( ! PostScript_Support::RevCheckFileSpace ( fileRef, &ioBuf, 1 ) ) return false; + if (*ioBuf.ptr==')') + { + --ioBuf.ptr; + while(true) + { + //get the string till '(' + if ( ! PostScript_Support::RevCheckFileSpace ( fileRef, &ioBuf, 1 ) ) return false ; + temp+=*ioBuf.ptr; + --ioBuf.ptr; + if (*ioBuf.ptr=='(') + { + if(filterFound) + { + reverse(temp.begin(), temp.end()); + if(!temp.compare("SubFileDecode")) + return true; + } + if ( ! PostScript_Support::RevCheckFileSpace ( fileRef, &ioBuf, 1 ) ) return false ; + --ioBuf.ptr; + temp.clear(); + break; + } + } + + filterFound=false; + } + else if(*ioBuf.ptr=='[') + { + //end of SubFileDecode Filter parsing + return false; + } + else if(*ioBuf.ptr=='k' ) + { + if ( ! PostScript_Support::RevCheckFileSpace ( fileRef, &ioBuf, 4 ) ) return false; + if(IsWhitespace(*(ioBuf.ptr-4))&& *(ioBuf.ptr-3)=='m' + && *(ioBuf.ptr-2)=='a' && *(ioBuf.ptr-1)=='r' ) + //end of SubFileDecode Filter parsing + return false; + while(true)//ignore till any special mark + { + if ( ! PostScript_Support::RevCheckFileSpace ( fileRef, &ioBuf, 4 ) ) return false; + if (IsWhitespace(*(ioBuf.ptr))||*(ioBuf.ptr)=='['|| + *(ioBuf.ptr)=='<' ||*(ioBuf.ptr)=='>') break; + --ioBuf.ptr; + } + filterFound=false; + } + else if(*ioBuf.ptr=='<') + { + --ioBuf.ptr; + if ( ! PostScript_Support::RevCheckFileSpace ( fileRef, &ioBuf, 1 ) ) return false; + if (*(ioBuf.ptr)=='<') + { + //end of SubFileDecode Filter parsing + return false; + } + while(true)//ignore till any special mark + { + if ( ! PostScript_Support::RevCheckFileSpace ( fileRef, &ioBuf, 4 ) ) return false; + if (IsWhitespace(*(ioBuf.ptr))||*(ioBuf.ptr)=='['|| + *(ioBuf.ptr)=='<' ||*(ioBuf.ptr)=='>') break; + --ioBuf.ptr; + } + filterFound=false; + } + else if(*ioBuf.ptr=='>') + { + --ioBuf.ptr; + if ( ! PostScript_Support::RevCheckFileSpace ( fileRef, &ioBuf, 1 ) ) return false; + if (*(ioBuf.ptr)=='>')//ignore the dictionary + { + --ioBuf.ptr; + XMP_Int16 count=1; + while(true) + { + if ( ! PostScript_Support::RevCheckFileSpace ( fileRef, &ioBuf, 2 ) ) return false; + if(*(ioBuf.ptr)=='<' && *(ioBuf.ptr-1)=='<') + { + count--; + ioBuf.ptr-=2; + } + else if(*(ioBuf.ptr)=='>' && *(ioBuf.ptr-1)=='>') + { + count++; + ioBuf.ptr-=2; + } + else + { + ioBuf.ptr-=1; + } + if(count==0) + break; + } + } + filterFound=false; + } + else + { + while(true) + { + if ( ! PostScript_Support::RevCheckFileSpace ( fileRef, &ioBuf, 1 ) ) return false; + temp+=(*ioBuf.ptr); + --ioBuf.ptr; + if (*ioBuf.ptr=='/') + { + if(filterFound) + { + reverse(temp.begin(), temp.end()); + if(!temp.compare("SubFileDecode")) + return true; + } + temp.clear(); + filterFound=false; + break; + } + else if(IsWhitespace(*ioBuf.ptr)) + { + reverse(temp.begin(), temp.end()); + if(!temp.compare("filter")&&!filterFound) + filterFound=true; + else + filterFound=false; + temp.clear(); + break; + } + } + if ( ! PostScript_Support::RevCheckFileSpace ( fileRef, &ioBuf, 1 ) ) return false; + --ioBuf.ptr; + } + while(true) + { + if ( ! PostScript_Support::RevCheckFileSpace ( fileRef, &ioBuf, 1 ) ) return false; + if (!IsWhitespace(*ioBuf.ptr)) break; + --ioBuf.ptr; + } + + } + return false; + +} + + +// ================================================================================================= +// constructDateTime +// ================================= +// +// If input string date is of the format D:YYYYMMDDHHmmSSOHH'mm' and valid +// output format YYYY-MM-DDThh:mm:ssTZD is returned +// +static void constructDateTime(const std::string &input,std::string& outDate) +{ + std::string date; + XMP_DateTime datetime; + std::string verdate; + size_t start =0; + if(input[0]=='D' && input[1]==':') + start=2; + if (input.length() >=14+start) + { + for(int x=0;x<4;x++) + { + date+=input[start+x];//YYYY + } + date+='-'; + start+=4; + for(int x=0;x<2;x++) + { + date+=input[start+x];//MM + } + date+='-'; + start+=2; + for(int x=0;x<2;x++) + { + date+=input[start+x];//DD + } + date+='T'; + start+=2; + for(int x=0;x<2;x++) + { + date+=input[start+x];//HH + } + + date+=':'; + start+=2; + for(int x=0;x<2;x++) + { + date+=input[start+x];//MM + } + + date+=':'; + start+=2; + for(int x=0;x<2;x++) + { + date+=input[start+x];//SS + } + start+=2; + if ((input[start]=='+' || input[start]=='-' )&&input.length() ==19+start) + { + date+=input[start]; + start++; + for(int x=0;x<2;x++) + { + date+=input[start+x];//hh + } + date+=':'; + start+=2; + for(int x=0;x<2;x++) + { + date+=input[start+x];//mm + } + } + else + { + date+='Z'; + } + + try + { + SXMPUtils::ConvertToDate ( date.c_str(),&datetime); + SXMPUtils::ConvertFromDate (datetime,&verdate); + } + catch(...) + { + return; + } + outDate=verdate; + } + +} + +// ================================================================================================= +// GetNumber +// ========= +// +// Extracts number from a char string +// +static short GetNumber(const char **inString,short noOfChars=SHRT_MAX) +{ + const char* inStr=*inString; + short number=0; + while(IsNumeric ( *inStr )&& noOfChars--) + { + number=number*10 +inStr[0]-'0'; + inStr++; + } + *inString=inStr; + return number; +} + +// ================================================================================================= +// tokeniseDateString +// ================== +// +// Parses Date string and tokenizes it for extracting different parts of a date +// +static bool tokeniseDateString(const char* inString,std::vector &tokens ) +{ + const char* inStr= inString; + PostScript_Support::DateTimeTokens dttoken; + //skip leading whitespace + while ( inStr[0]!='\0' ) + { + while( IsSpaceOrTab ( *inStr ) || *inStr =='(' + ||*inStr ==')' ||*inStr==',') + { + ++inStr; + } + if (*inStr=='\0') return true; + dttoken=PostScript_Support::DateTimeTokens(); + if (IsNumeric(*inStr)) + { + while(IsNumeric(*inStr)||(IsDelimiter(*inStr)&&dttoken.noOfDelimiter==0)|| + (dttoken.delimiter==*inStr && dttoken.noOfDelimiter!=0)) + { + if (IsDelimiter(*inStr)) + { + dttoken.delimiter=inStr[0]; + dttoken.noOfDelimiter++; + } + dttoken.token+=*(inStr++); + } + tokens.push_back(dttoken); + } + else if (IsAlpha(*inStr)) + { + if(*inStr=='D'&& *(inStr+1)==':') + { + inStr+=2; + while( IsNumeric(*inStr)) + { + dttoken.token+=*(inStr++); + } + } + else + { + while( IsAlpha(*inStr)) + { + dttoken.token+=*(inStr++); + } + + } + tokens.push_back(dttoken); + } + else if (*inStr=='-' ||*inStr=='+') + { + dttoken.token+=*(inStr++); + while( IsNumeric(*inStr)||*inStr==':') + { + if (*inStr==':') + { + dttoken.delimiter=inStr[0]; + dttoken.noOfDelimiter++; + } + dttoken.token+=*(inStr++); + } + tokens.push_back(dttoken); + } + else + { + ++inStr; + } + } + return true; +} + +// ================================================================================================= +// SwapMonthDateIfNeeded +// ===================== +// +// Swaps month and date value if it creates a possible valid date +// +static void SwapMonthDateIfNeeded(short &day, short&month) +{ + if(month>12&& day<13) + { + short temp=month; + month=day; + day=temp; + } +} + +// ================================================================================================= +// AdjustYearIfNeeded +// ===================== +// +// Guess the year for a two digit year in a date +// +static void AdjustYearIfNeeded(short &year) +{ + if (year<100) + { + if (year >40) + { + year=1900+year; + } + else + { + year=2000+year; + } + } +} + +static bool IsLeapYear ( XMP_Int32 year ) +{ + if ( year < 0 ) year = -year + 1; // Fold the negative years, assuming there is a year 0. + if ( (year % 4) != 0 ) return false; // Not a multiple of 4. + if ( (year % 100) != 0 ) return true; // A multiple of 4 but not a multiple of 100. + if ( (year % 400) == 0 ) return true; // A multiple of 400. + return false; // A multiple of 100 but not a multiple of 400. +} +// ================================================================================================= +// PostScript_Support::ConvertToDate +// ================================= +// +// Converts date string from native of Postscript to YYYY-MM-DDThh:mm:ssTZD if a valid date is identified +// +std::string PostScript_Support::ConvertToDate(const char* inString) +{ + PostScript_Support::Date date(0,0,0,0,0,0); + std::string dateTimeString; + short number=0; + std::vector tokenzs; + tokeniseDateString(inString,tokenzs ); + std::vector:: const_iterator itr=tokenzs.begin(); + for(;itr!=tokenzs.end();itr++) + { + if(itr->token[0]=='+' ||itr->token[0]=='-') + { + const char *str=itr->token.c_str(); + date.offsetSign=*(str++); + date.offsetHour=GetNumber(&str,2); + if (*str==':')str++; + date.offsetMin=GetNumber(&str,2); + if (!(date.offsetHour<=12 && date.offsetHour>=0 + &&date.offsetMin>=0 && date.offsetMin<=59)) + { + date.offsetSign='+'; + date.offsetHour=0; + date.offsetMin=0; + } + else + { + date.containsOffset= true; + } + } + else if(itr->noOfDelimiter!=0) + {//either a date or time token + if(itr->noOfDelimiter==2 && itr->delimiter=='/') + { + if(date.day==0&& date.month==0 && date.year==0) + { + const char *str=itr->token.c_str(); + number=GetNumber(&str); + str++; + if (number<32) + { + date.month=number; + date.day=GetNumber(&str); + SwapMonthDateIfNeeded(date.day, date.month); + str++; + date.year=GetNumber(&str); + AdjustYearIfNeeded(date.year); + } + else + { + date.year=number; + AdjustYearIfNeeded(date.year); + date.month=GetNumber(&str); + str++; + date.day=GetNumber(&str); + SwapMonthDateIfNeeded(date.day, date.month); + } + } + } + else if(itr->noOfDelimiter==2 && itr->delimiter==':') + { + const char *str=itr->token.c_str(); + date.hours=GetNumber(&str); + str++; + date.minutes=GetNumber(&str); + str++; + date.seconds=GetNumber(&str); + if (date.hours>23|| date.minutes>59|| date.seconds>59) + { + date.hours=0; + date.minutes=0; + date.seconds=0; + } + } + else if(itr->noOfDelimiter==1 && itr->delimiter==':') + { + const char *str=itr->token.c_str(); + date.hours=GetNumber(&str); + str++; + date.minutes=GetNumber(&str); + if (date.hours>23|| date.minutes>59) + { + date.hours=0; + date.minutes=0; + } + } + else if(itr->noOfDelimiter==2 && itr->delimiter=='-') + { + const char *str=itr->token.c_str(); + number=GetNumber(&str); + str++; + if (number>31) + { + date.year=number; + AdjustYearIfNeeded(date.year); + date.month=GetNumber(&str); + str++; + date.day=GetNumber(&str); + SwapMonthDateIfNeeded(date.day, date.month); + } + else + { + date.month=number; + date.day=GetNumber(&str); + str++; + SwapMonthDateIfNeeded(date.day, date.month); + date.year=GetNumber(&str); + AdjustYearIfNeeded(date.year); + } + } + else if(itr->noOfDelimiter==2 && itr->delimiter=='.') + { + const char *str=itr->token.c_str(); + date.year=GetNumber(&str); + str++; + AdjustYearIfNeeded(date.year); + date.month=GetNumber(&str); + str++; + date.day=GetNumber(&str); + SwapMonthDateIfNeeded(date.day, date.month); + + } + } + else if (IsAlpha(itr->token[0])) + { //this can be a name of the Month + // name of the day is ignored + short month=0; + std::string lowerToken=itr->token; + std::transform(lowerToken.begin(), lowerToken.end(), lowerToken.begin(), ::tolower); + if(!lowerToken.compare("jan")||!lowerToken.compare("january")) + { + month=1; + } + else if (!lowerToken.compare("feb")||!lowerToken.compare("february")) + { + month=2; + } + else if (!lowerToken.compare("mar")||!lowerToken.compare("march")) + { + month=3; + } + else if (!lowerToken.compare("apr")||!lowerToken.compare("april")) + { + month=4; + } + else if (!lowerToken.compare("may")) + { + month=5; + } + else if (!lowerToken.compare("jun")||!lowerToken.compare("june")) + { + month=6; + } + else if (!lowerToken.compare("jul")||!lowerToken.compare("july")) + { + month=7; + } + else if (!lowerToken.compare("aug")||!lowerToken.compare("august")) + { + month=8; + } + else if (!lowerToken.compare("sep")||!lowerToken.compare("september")) + { + month=9; + } + else if (!lowerToken.compare("oct")||!lowerToken.compare("october")) + { + month=10; + } + else if (!lowerToken.compare("nov")||!lowerToken.compare("november")) + { + month=11; + } + else if (!lowerToken.compare("dec")||!lowerToken.compare("december")) + { + month=12; + } + else if (!lowerToken.compare("pm")) + { + if (date.hours<13) + { + date.hours+=12; + } + } + else if (itr->token.length()>14) + { + constructDateTime(itr->token,dateTimeString); + } + if(month!=0 && date.month==0) + { + date.month=month; + if(date.day==0) + { + if(itr!=tokenzs.begin()) + { + --itr; + if (itr->noOfDelimiter==0 && IsNumeric(itr->token[0]) ) + { + const char * str=itr->token.c_str(); + short day= GetNumber(&str); + if (day<=31 &&day >=1) + { + date.day=day; + } + } + ++itr; + } + if(itr!=tokenzs.end()) + { + ++itr; + if (itr!=tokenzs.end()&&itr->noOfDelimiter==0 && IsNumeric(itr->token[0]) ) + { + const char * str=itr->token.c_str(); + short day= GetNumber(&str); + if (day<=31 &&day >=1) + { + date.day=day; + } + } + } + } + } + } + else if (IsNumeric(itr->token[0]) && date.year==0&&itr->token.length()==4) + {//this is the year + const char * str=itr->token.c_str(); + date.year=GetNumber(&str); + } + else if (itr->token.length()>=14) + { + constructDateTime(itr->token,dateTimeString); + } + } + if (dateTimeString.length()==0) + { + char dtstr[100]; + XMP_DateTime datetime; + if ( date.year < 10000 && date.month < 13 && date.month > 0 && date.day > 0 ) + { + bool isValidDate=true; + if ( date.month == 2 ) + { + if ( IsLeapYear ( date.year ) ) + { + if ( date.day > 29 ) isValidDate=false; + } + else + { + if ( date.day > 28 ) isValidDate=false; + } + } + else if ( date.month == 4 || date.month == 6 || date.month == 9 + || date.month == 11 ) + { + if ( date.day > 30 ) isValidDate=false; + } + else + { + if ( date.day > 31 ) isValidDate=false; + } + if( isValidDate && ! ( date == PostScript_Support::Date ( 0, 0, 0, 0, 0, 0 ) ) ) + { + if ( date.containsOffset ) + { + sprintf(dtstr,"%04d-%02d-%02dT%02d:%02d:%02d%c%02d:%02d",date.year,date.month,date.day, + date.hours,date.minutes,date.seconds,date.offsetSign,date.offsetHour,date.offsetMin); + } + else + { + sprintf(dtstr,"%04d-%02d-%02dT%02d:%02d:%02dZ",date.year,date.month,date.day, + date.hours,date.minutes,date.seconds); + } + try + { + + SXMPUtils::ConvertToDate ( dtstr, &datetime ) ; + SXMPUtils::ConvertFromDate ( datetime, &dateTimeString ) ; + } + catch(...) + { + } + } + } + } + + return dateTimeString; +} +// ================================================================================================= diff --git a/core/libs/dngwriter/extra/xmp_sdk/XMPFiles/source/FormatSupport/PostScript_Support.hpp b/core/libs/dngwriter/extra/xmp_sdk/XMPFiles/source/FormatSupport/PostScript_Support.hpp new file mode 100644 index 0000000000..688fa6da05 --- /dev/null +++ b/core/libs/dngwriter/extra/xmp_sdk/XMPFiles/source/FormatSupport/PostScript_Support.hpp @@ -0,0 +1,266 @@ +#ifndef __PostScript_Support_hpp__ +#define __PostScript_Support_hpp__ 1 + +// ================================================================================================= +// ADOBE SYSTEMS INCORPORATED +// Copyright 2012 Adobe Systems Incorporated +// All Rights Reserved +// +// NOTICE: Adobe permits you to use, modify, and distribute this file in accordance with the terms +// of the Adobe license agreement accompanying it. +// ================================================================================================= + +#include "public/include/XMP_Environment.h" + +#include "public/include/XMP_Const.h" +#include "public/include/XMP_IO.hpp" + +#include "XMPFiles/source/XMPFiles_Impl.hpp" +#include "source/XMPFiles_IO.hpp" +#include "source/XIO.hpp" + + +#define MAX_NO_MARK 100 +#define IsNumeric( ch ) ( ch >='0' && ch<='9' ) +#define Uns8Ptr(p) ((XMP_Uns8 *) (p)) +#define IsPlusMinusSign(ch) ( ch =='+' || ch=='-' ) +#define IsDateDelimiter( ch ) ( ((ch) == '/') || ((ch) == '-') || ((ch) == '.') ) +#define IsTimeDelimiter( ch ) ( ((ch) == ':') ) +#define IsDelimiter(ch) (IsDateDelimiter( ch ) || IsTimeDelimiter( ch )) +#define IsAlpha(ch) ((ch>=97 &&ch <=122) || (ch>=65 && ch<=91)) + + +enum { + kPSHint_NoMarker = 0, + kPSHint_NoMain = 1, + kPSHint_MainFirst = 2, + kPSHint_MainLast = 3 +}; + +enum UpdateMethod{ + kPS_None = 0, + kPS_Inplace = 1, + kPS_ExpandSFDFilter = 2, + kPS_InjectNew = 3 +}; + +enum TokenFlag{ + // -------------------- + // Flags for native Metadata and DSC commnents in EPS format + + /// No Native MetaData + kPS_NoData = 0x00000001, + /// Document Creator tool + kPS_CreatorTool = 0x00000002, + /// Document Creation Date + kPS_CreateDate = 0x00000004, + /// Document Modify Date + kPS_ModifyDate = 0x00000008, + /// Document Creator/Author + kPS_Creator = 0x00000010, + /// Document Title + kPS_Title = 0x00000020, + /// Document Desciption + kPS_Description = 0x00000040, + /// Document Subject/Keywords + kPS_Subject = 0x00000080, + /// ADO_ContainsXMP hint + kPS_ADOContainsXMP = 0x00000100, + /// End Comments + kPS_EndComments = 0x00000200, + /// Begin Prolog + kPS_BeginProlog = 0x00000400, + /// End Prolog + kPS_EndProlog = 0x00000800, + /// Begin Setup + kPS_BeginSetup = 0x00001000, + /// End Setup + kPS_EndSetup = 0x00002000, + /// Page + kPS_Page = 0x00004000, + /// End Page Comments + kPS_EndPageComments = 0x00008000, + /// Begin Page SetUp + kPS_BeginPageSetup = 0x00010000, + /// End Page SetUp + kPS_EndPageSetup = 0x00020000, + /// Trailer + kPS_Trailer = 0x00040000, + /// EOF + kPS_EOF = 0x00080000, + /// End PostScript + kPS_EndPostScript = 0x00100000, + /// Max Token + kPS_MaxToken = 0x00200000 +}; + +enum NativeMetadataIndex{ + // -------------------- + // Index native Metadata ina PS file + kPS_dscCreator = 0, + kPS_dscCreateDate = 1, + kPS_dscFor = 2, + kPS_dscTitle = 3, + kPS_docInfoCreator = 4, + kPS_docInfoCreateDate = 5, + kPS_docInfoModDate = 6, + kPS_docInfoAuthor = 7, + kPS_docInfoTitle = 8, + kPS_docInfoSubject = 9, + kPS_docInfoKeywords = 10, + kPS_MaxNativeIndexValue +}; + +static XMP_Uns64 nativeIndextoFlag[]={ kPS_CreatorTool, + kPS_CreateDate, + kPS_Creator, + kPS_Title, + kPS_CreatorTool, + kPS_CreateDate, + kPS_ModifyDate, + kPS_Creator, + kPS_Title, + kPS_Description, + kPS_Subject + }; + +static const std::string kPSFileTag = "%!PS-Adobe-"; +static const std::string kPSContainsXMPString = "%ADO_ContainsXMP:"; +static const std::string kPSContainsBBoxString = "%%BoundingBox:"; +static const std::string kPSContainsBeginDocString = "%%BeginDocument:"; +static const std::string kPSContainsEndDocString = "%%EndDocument"; +static const std::string kPSContainsTrailerString = "%%Trailer"; +static const std::string kPSContainsCreatorString = "%%Creator:"; +static const std::string kPSContainsCreateDateString = "%%CreationDate:"; +static const std::string kPSContainsForString = "%%For:"; +static const std::string kPSContainsTitleString = "%%Title:"; +static const std::string kPSContainsAtendString = "(atend)"; +static const std::string kPSEndCommentString = "%%EndComments"; // ! Assumed shorter than kPSContainsXMPString. +static const std::string kPSContainsDocInfoString = "/DOCINFO"; +static const std::string kPSContainsPdfmarkString = "pdfmark"; +static const std::string kPS_XMPHintMainFirst="%ADO_ContainsXMP: MainFirst\n"; +static const std::string kPS_XMPHintMainLast="%ADO_ContainsXMP: MainLast\n"; + +// For new xpacket injection into the EPS file is done in Postscript using the pdfmark operator +// There are different conventions described for EPS and PS files in XMP Spec part 3. +// The tokens kEPS_Injectdata1, kEPS_Injectdata2 and kEPS_Injectdata3 are used to +// embedd xpacket in EPS files.the xpacket is written inbetween kEPS_Injectdata1 and kEPS_Injectdata2. +// The tokens kPS_Injectdata1 and kPS_Injectdata2 are used to embedd xpacket in DSC compliant PS files +// The code inside the tokens is taken from examples in XMP Spec part 3 +// section 2.6.2 PS, EPS (PostScript® and Encapsulated PostScript) +static const std::string kEPS_Injectdata1="\n/currentdistillerparams where\n" +"{pop currentdistillerparams /CoreDistVersion get 5000 lt} {true} ifelse\n" +"{userdict /EPSHandler1_pdfmark /cleartomark load put\n" +"userdict /EPSHandler1_ReadMetadata_pdfmark {flushfile cleartomark} bind put}\n" +"{ userdict /EPSHandler1_pdfmark /pdfmark load put\n" +"userdict /EPSHandler1_ReadMetadata_pdfmark {/PUT pdfmark} bind put } ifelse\n" +"[/NamespacePush EPSHandler1_pdfmark\n" +"[/_objdef {eps_metadata_stream} /type /stream /OBJ EPSHandler1_pdfmark\n" +"[{eps_metadata_stream} 2 dict begin\n" +"/Type /Metadata def /Subtype /XML def currentdict end /PUT EPSHandler1_pdfmark\n" +"[{eps_metadata_stream}\n" +"currentfile 0 (% &&end EPS XMP packet marker&&)\n" +"/SubFileDecode filter EPSHandler1_ReadMetadata_pdfmark\n"; + +static const std::string kPS_Injectdata1="\n/currentdistillerparams where\n" +"{pop currentdistillerparams /CoreDistVersion get 5000 lt} {true} ifelse\n" +"{userdict /PSHandler1_pdfmark /cleartomark load put\n" +"userdict /PSHandler1_ReadMetadata_pdfmark {flushfile cleartomark} bind put}\n" +"{ userdict /PSHandler1_pdfmark /pdfmark load put\n" +"userdict /PSHandler1_ReadMetadata_pdfmark {/PUT pdfmark} bind put } ifelse\n" +"[/NamespacePush PSHandler1_pdfmark\n" +"[/_objdef {ps_metadata_stream} /type /stream /OBJ PSHandler1_pdfmark\n" +"[{ps_metadata_stream} 2 dict begin\n" +"/Type /Metadata def /Subtype /XML def currentdict end /PUT PSHandler1_pdfmark\n" +"[{ps_metadata_stream}\n" +"currentfile 0 (% &&end PS XMP packet marker&&)\n" +"/SubFileDecode filter PSHandler1_ReadMetadata_pdfmark\n"; + +static const std::string kEPS_Injectdata2="\n% &&end EPS XMP packet marker&&\n" +"[/Document\n" +"1 dict begin /Metadata {eps_metadata_stream} def\n" +"currentdict end /BDC EPSHandler1_pdfmark\n" +"[/NamespacePop EPSHandler1_pdfmark\n"; + + +static const std::string kPS_Injectdata2="\n% &&end PS XMP packet marker&&\n" +"[{Catalog} {ps_metadata_stream} /Metadata PSHandler1_pdfmark\n" +"[/NamespacePop PSHandler1_pdfmark\n"; + +static const std::string kEPS_Injectdata3="\n/currentdistillerparams where\n" + "{pop currentdistillerparams /CoreDistVersion get 5000 lt} {true} ifelse\n" + "{userdict /EPSHandler1_pdfmark /cleartomark load put}\n" + "{ userdict /EPSHandler1_pdfmark /pdfmark load put} ifelse\n" + "[/EMC EPSHandler1_pdfmark\n"; + + +namespace PostScript_Support +{ + struct Date + { + short day; + short month; + short year; + short hours; + short minutes; + short seconds; + bool containsOffset; + char offsetSign; + short offsetHour; + short offsetMin; + Date(short pday=1,short pmonth=1,short pyear=1900,short phours=0, + short pminutes=0,short pseconds=0):day(pday),month(pmonth), + year(pyear),hours(phours),minutes(pminutes),seconds(pseconds), + containsOffset(false),offsetSign('+'),offsetHour(0),offsetMin(0) + { + } + bool operator==(const Date &a) + { + return this->day==a.day && + this->month==a.month && + this->year==a.year && + this->hours==a.hours && + this->minutes==a.minutes && + this->seconds==a.seconds && + this->containsOffset==a.containsOffset && + this->offsetSign==a.offsetSign && + this->offsetHour==a.offsetHour && + this->offsetMin==a.offsetMin; + } + }; + struct DateTimeTokens + { + std::string token; + short noOfDelimiter; + char delimiter; + DateTimeTokens(std::string ptoken="",short pnoOfDelimiter=0,char pdelimiter=0): + token(ptoken),noOfDelimiter(pnoOfDelimiter),delimiter(pdelimiter) + { + } + }; + + //function to parse strings and get date out of it + std::string ConvertToDate(const char* inString); + // These helpers are similar to RefillBuffer and CheckFileSpace with the difference that the it traverses + // the file stream in reverse order + void RevRefillBuffer ( XMP_IO* fileRef, IOBuffer* ioBuf ); + bool RevCheckFileSpace ( XMP_IO* fileRef, IOBuffer* ioBuf, size_t neededLen ); + + // function to detect character codes greater than 127 in a string + bool HasCodesGT127(const std::string & value); + + // function moves the file pointer ahead such that it skips all tabs and spaces + bool SkipTabsAndSpaces(XMP_IO* file,IOBuffer& ioBuf); + + // function moves the file pointer ahead such that it skips all characters until a newline + bool SkipUntilNewline(XMP_IO* file,IOBuffer& ioBuf); + + // function to detect character codes greater than 127 in a string + bool IsValidPSFile(XMP_IO* fileRef,XMP_FileFormat &format); + + // Determines Whether the metadata is embedded using the Sub-FileDecode Approach or no + bool IsSFDFilterUsed(XMP_IO* &fileRef, XMP_Int64 xpacketOffset); + +} // namespace PostScript_Support + +#endif // __PostScript_Support_hpp__ diff --git a/core/libs/dngwriter/extra/xmp_sdk/XMPFiles/source/FormatSupport/QuickTime_Support.cpp b/core/libs/dngwriter/extra/xmp_sdk/XMPFiles/source/FormatSupport/QuickTime_Support.cpp new file mode 100644 index 0000000000..8e2d45a5c7 --- /dev/null +++ b/core/libs/dngwriter/extra/xmp_sdk/XMPFiles/source/FormatSupport/QuickTime_Support.cpp @@ -0,0 +1,1331 @@ +// ================================================================================================= +// ADOBE SYSTEMS INCORPORATED +// Copyright 2009 Adobe Systems Incorporated +// All Rights Reserved +// +// NOTICE: Adobe permits you to use, modify, and distribute this file in accordance with the terms +// of the Adobe license agreement accompanying it. +// ================================================================================================= + +#include "public/include/XMP_Environment.h" // ! XMP_Environment.h must be the first included header. +#include "public/include/XMP_Const.h" + +#if XMP_MacBuild + #include +#elif XMP_iOSBuild + #include + #include "XMPFiles/source/FormatSupport/MacScriptExtracts.h" +#else + #include "XMPFiles/source/FormatSupport/MacScriptExtracts.h" +#endif + +#include "XMPFiles/source/FormatSupport/QuickTime_Support.hpp" + +#include "source/UnicodeConversions.hpp" +#include "source/UnicodeInlines.incl_cpp" +#include "XMPFiles/source/FormatSupport/Reconcile_Impl.hpp" +#include "source/XIO.hpp" +#include "source/EndianUtils.hpp" + +// ================================================================================================= + +static const char * kMacRomanUTF8 [128] = { // UTF-8 mappings for MacRoman 80..FF. + "\xC3\x84", "\xC3\x85", "\xC3\x87", "\xC3\x89", "\xC3\x91", "\xC3\x96", "\xC3\x9C", "\xC3\xA1", + "\xC3\xA0", "\xC3\xA2", "\xC3\xA4", "\xC3\xA3", "\xC3\xA5", "\xC3\xA7", "\xC3\xA9", "\xC3\xA8", + "\xC3\xAA", "\xC3\xAB", "\xC3\xAD", "\xC3\xAC", "\xC3\xAE", "\xC3\xAF", "\xC3\xB1", "\xC3\xB3", + "\xC3\xB2", "\xC3\xB4", "\xC3\xB6", "\xC3\xB5", "\xC3\xBA", "\xC3\xB9", "\xC3\xBB", "\xC3\xBC", + "\xE2\x80\xA0", "\xC2\xB0", "\xC2\xA2", "\xC2\xA3", "\xC2\xA7", "\xE2\x80\xA2", "\xC2\xB6", "\xC3\x9F", + "\xC2\xAE", "\xC2\xA9", "\xE2\x84\xA2", "\xC2\xB4", "\xC2\xA8", "\xE2\x89\xA0", "\xC3\x86", "\xC3\x98", + "\xE2\x88\x9E", "\xC2\xB1", "\xE2\x89\xA4", "\xE2\x89\xA5", "\xC2\xA5", "\xC2\xB5", "\xE2\x88\x82", "\xE2\x88\x91", + "\xE2\x88\x8F", "\xCF\x80", "\xE2\x88\xAB", "\xC2\xAA", "\xC2\xBA", "\xCE\xA9", "\xC3\xA6", "\xC3\xB8", + "\xC2\xBF", "\xC2\xA1", "\xC2\xAC", "\xE2\x88\x9A", "\xC6\x92", "\xE2\x89\x88", "\xE2\x88\x86", "\xC2\xAB", + "\xC2\xBB", "\xE2\x80\xA6", "\xC2\xA0", "\xC3\x80", "\xC3\x83", "\xC3\x95", "\xC5\x92", "\xC5\x93", + "\xE2\x80\x93", "\xE2\x80\x94", "\xE2\x80\x9C", "\xE2\x80\x9D", "\xE2\x80\x98", "\xE2\x80\x99", "\xC3\xB7", "\xE2\x97\x8A", + "\xC3\xBF", "\xC5\xB8", "\xE2\x81\x84", "\xE2\x82\xAC", "\xE2\x80\xB9", "\xE2\x80\xBA", "\xEF\xAC\x81", "\xEF\xAC\x82", + "\xE2\x80\xA1", "\xC2\xB7", "\xE2\x80\x9A", "\xE2\x80\x9E", "\xE2\x80\xB0", "\xC3\x82", "\xC3\x8A", "\xC3\x81", + "\xC3\x8B", "\xC3\x88", "\xC3\x8D", "\xC3\x8E", "\xC3\x8F", "\xC3\x8C", "\xC3\x93", "\xC3\x94", + "\xEF\xA3\xBF", "\xC3\x92", "\xC3\x9A", "\xC3\x9B", "\xC3\x99", "\xC4\xB1", "\xCB\x86", "\xCB\x9C", + "\xC2\xAF", "\xCB\x98", "\xCB\x99", "\xCB\x9A", "\xC2\xB8", "\xCB\x9D", "\xCB\x9B", "\xCB\x87" +}; + +static const XMP_Uns32 kMacRomanCP [128] = { // Unicode codepoints for MacRoman 80..FF. + 0x00C4, 0x00C5, 0x00C7, 0x00C9, 0x00D1, 0x00D6, 0x00DC, 0x00E1, + 0x00E0, 0x00E2, 0x00E4, 0x00E3, 0x00E5, 0x00E7, 0x00E9, 0x00E8, + 0x00EA, 0x00EB, 0x00ED, 0x00EC, 0x00EE, 0x00EF, 0x00F1, 0x00F3, + 0x00F2, 0x00F4, 0x00F6, 0x00F5, 0x00FA, 0x00F9, 0x00FB, 0x00FC, + 0x2020, 0x00B0, 0x00A2, 0x00A3, 0x00A7, 0x2022, 0x00B6, 0x00DF, + 0x00AE, 0x00A9, 0x2122, 0x00B4, 0x00A8, 0x2260, 0x00C6, 0x00D8, + 0x221E, 0x00B1, 0x2264, 0x2265, 0x00A5, 0x00B5, 0x2202, 0x2211, + 0x220F, 0x03C0, 0x222B, 0x00AA, 0x00BA, 0x03A9, 0x00E6, 0x00F8, + 0x00BF, 0x00A1, 0x00AC, 0x221A, 0x0192, 0x2248, 0x2206, 0x00AB, + 0x00BB, 0x2026, 0x00A0, 0x00C0, 0x00C3, 0x00D5, 0x0152, 0x0153, + 0x2013, 0x2014, 0x201C, 0x201D, 0x2018, 0x2019, 0x00F7, 0x25CA, + 0x00FF, 0x0178, 0x2044, 0x20AC, 0x2039, 0x203A, 0xFB01, 0xFB02, + 0x2021, 0x00B7, 0x201A, 0x201E, 0x2030, 0x00C2, 0x00CA, 0x00C1, + 0x00CB, 0x00C8, 0x00CD, 0x00CE, 0x00CF, 0x00CC, 0x00D3, 0x00D4, + 0xF8FF, 0x00D2, 0x00DA, 0x00DB, 0x00D9, 0x0131, 0x02C6, 0x02DC, // ! U+F8FF is private use solid Apple icon. + 0x00AF, 0x02D8, 0x02D9, 0x02DA, 0x00B8, 0x02DD, 0x02DB, 0x02C7 +}; + +// ------------------------------------------------------------------------------------------------- + +static const XMP_Uns16 kMacLangToScript_0_94 [95] = { + + /* langEnglish (0) */ smRoman, + /* langFrench (1) */ smRoman, + /* langGerman (2) */ smRoman, + /* langItalian (3) */ smRoman, + /* langDutch (4) */ smRoman, + /* langSwedish (5) */ smRoman, + /* langSpanish (6) */ smRoman, + /* langDanish (7) */ smRoman, + /* langPortuguese (8) */ smRoman, + /* langNorwegian (9) */ smRoman, + + /* langHebrew (10) */ smHebrew, + /* langJapanese (11) */ smJapanese, + /* langArabic (12) */ smArabic, + /* langFinnish (13) */ smRoman, + /* langGreek (14) */ smRoman, + /* langIcelandic (15) */ smRoman, + /* langMaltese (16) */ smRoman, + /* langTurkish (17) */ smRoman, + /* langCroatian (18) */ smRoman, + /* langTradChinese (19) */ smTradChinese, + + /* langUrdu (20) */ smArabic, + /* langHindi (21) */ smDevanagari, + /* langThai (22) */ smThai, + /* langKorean (23) */ smKorean, + /* langLithuanian (24) */ smCentralEuroRoman, + /* langPolish (25) */ smCentralEuroRoman, + /* langHungarian (26) */ smCentralEuroRoman, + /* langEstonian (27) */ smCentralEuroRoman, + /* langLatvian (28) */ smCentralEuroRoman, + /* langSami (29) */ kNoMacScript, // ! Not known, missing from Apple comments. + + /* langFaroese (30) */ smRoman, + /* langFarsi (31) */ smArabic, + /* langRussian (32) */ smCyrillic, + /* langSimpChinese (33) */ smSimpChinese, + /* langFlemish (34) */ smRoman, + /* langIrishGaelic (35) */ smRoman, + /* langAlbanian (36) */ smRoman, + /* langRomanian (37) */ smRoman, + /* langCzech (38) */ smCentralEuroRoman, + /* langSlovak (39) */ smCentralEuroRoman, + + /* langSlovenian (40) */ smRoman, + /* langYiddish (41) */ smHebrew, + /* langSerbian (42) */ smCyrillic, + /* langMacedonian (43) */ smCyrillic, + /* langBulgarian (44) */ smCyrillic, + /* langUkrainian (45) */ smCyrillic, + /* langBelorussian (46) */ smCyrillic, + /* langUzbek (47) */ smCyrillic, + /* langKazakh (48) */ smCyrillic, + /* langAzerbaijani (49) */ smCyrillic, + + /* langAzerbaijanAr (50) */ smArabic, + /* langArmenian (51) */ smArmenian, + /* langGeorgian (52) */ smGeorgian, + /* langMoldavian (53) */ smCyrillic, + /* langKirghiz (54) */ smCyrillic, + /* langTajiki (55) */ smCyrillic, + /* langTurkmen (56) */ smCyrillic, + /* langMongolian (57) */ smMongolian, + /* langMongolianCyr (58) */ smCyrillic, + /* langPashto (59) */ smArabic, + + /* langKurdish (60) */ smArabic, + /* langKashmiri (61) */ smArabic, + /* langSindhi (62) */ smArabic, + /* langTibetan (63) */ smTibetan, + /* langNepali (64) */ smDevanagari, + /* langSanskrit (65) */ smDevanagari, + /* langMarathi (66) */ smDevanagari, + /* langBengali (67) */ smBengali, + /* langAssamese (68) */ smBengali, + /* langGujarati (69) */ smGujarati, + + /* langPunjabi (70) */ smGurmukhi, + /* langOriya (71) */ smOriya, + /* langMalayalam (72) */ smMalayalam, + /* langKannada (73) */ smKannada, + /* langTamil (74) */ smTamil, + /* langTelugu (75) */ smTelugu, + /* langSinhalese (76) */ smSinhalese, + /* langBurmese (77) */ smBurmese, + /* langKhmer (78) */ smKhmer, + /* langLao (79) */ smLao, + + /* langVietnamese (80) */ smVietnamese, + /* langIndonesian (81) */ smRoman, + /* langTagalog (82) */ smRoman, + /* langMalayRoman (83) */ smRoman, + /* langMalayArabic (84) */ smArabic, + /* langAmharic (85) */ smEthiopic, + /* langTigrinya (86) */ smEthiopic, + /* langOromo (87) */ smEthiopic, + /* langSomali (88) */ smRoman, + /* langSwahili (89) */ smRoman, + + /* langKinyarwanda (90) */ smRoman, + /* langRundi (91) */ smRoman, + /* langNyanja (92) */ smRoman, + /* langMalagasy (93) */ smRoman, + /* langEsperanto (94) */ smRoman + +}; // kMacLangToScript_0_94 + +static const XMP_Uns16 kMacLangToScript_128_151 [24] = { + + /* langWelsh (128) */ smRoman, + /* langBasque (129) */ smRoman, + + /* langCatalan (130) */ smRoman, + /* langLatin (131) */ smRoman, + /* langQuechua (132) */ smRoman, + /* langGuarani (133) */ smRoman, + /* langAymara (134) */ smRoman, + /* langTatar (135) */ smCyrillic, + /* langUighur (136) */ smArabic, + /* langDzongkha (137) */ smTibetan, + /* langJavaneseRom (138) */ smRoman, + /* langSundaneseRom (139) */ smRoman, + + /* langGalician (140) */ smRoman, + /* langAfrikaans (141) */ smRoman, + /* langBreton (142) */ smRoman, + /* langInuktitut (143) */ smEthiopic, + /* langScottishGaelic (144) */ smRoman, + /* langManxGaelic (145) */ smRoman, + /* langIrishGaelicScript (146) */ smRoman, + /* langTongan (147) */ smRoman, + /* langGreekAncient (148) */ smGreek, + /* langGreenlandic (149) */ smRoman, + + /* langAzerbaijanRoman (150) */ smRoman, + /* langNynorsk (151) */ smRoman + +}; // kMacLangToScript_128_151 + +// ------------------------------------------------------------------------------------------------- + +static const char * kMacToXMPLang_0_94 [95] = { + + /* langEnglish (0) */ "en", + /* langFrench (1) */ "fr", + /* langGerman (2) */ "de", + /* langItalian (3) */ "it", + /* langDutch (4) */ "nl", + /* langSwedish (5) */ "sv", + /* langSpanish (6) */ "es", + /* langDanish (7) */ "da", + /* langPortuguese (8) */ "pt", + /* langNorwegian (9) */ "no", + + /* langHebrew (10) */ "he", + /* langJapanese (11) */ "ja", + /* langArabic (12) */ "ar", + /* langFinnish (13) */ "fi", + /* langGreek (14) */ "el", + /* langIcelandic (15) */ "is", + /* langMaltese (16) */ "mt", + /* langTurkish (17) */ "tr", + /* langCroatian (18) */ "hr", + /* langTradChinese (19) */ "zh", + + /* langUrdu (20) */ "ur", + /* langHindi (21) */ "hi", + /* langThai (22) */ "th", + /* langKorean (23) */ "ko", + /* langLithuanian (24) */ "lt", + /* langPolish (25) */ "pl", + /* langHungarian (26) */ "hu", + /* langEstonian (27) */ "et", + /* langLatvian (28) */ "lv", + /* langSami (29) */ "se", + + /* langFaroese (30) */ "fo", + /* langFarsi (31) */ "fa", + /* langRussian (32) */ "ru", + /* langSimpChinese (33) */ "zh", + /* langFlemish (34) */ "nl", + /* langIrishGaelic (35) */ "ga", + /* langAlbanian (36) */ "sq", + /* langRomanian (37) */ "ro", + /* langCzech (38) */ "cs", + /* langSlovak (39) */ "sk", + + /* langSlovenian (40) */ "sl", + /* langYiddish (41) */ "yi", + /* langSerbian (42) */ "sr", + /* langMacedonian (43) */ "mk", + /* langBulgarian (44) */ "bg", + /* langUkrainian (45) */ "uk", + /* langBelorussian (46) */ "be", + /* langUzbek (47) */ "uz", + /* langKazakh (48) */ "kk", + /* langAzerbaijani (49) */ "az", + + /* langAzerbaijanAr (50) */ "az", + /* langArmenian (51) */ "hy", + /* langGeorgian (52) */ "ka", + /* langMoldavian (53) */ "ro", + /* langKirghiz (54) */ "ky", + /* langTajiki (55) */ "tg", + /* langTurkmen (56) */ "tk", + /* langMongolian (57) */ "mn", + /* langMongolianCyr (58) */ "mn", + /* langPashto (59) */ "ps", + + /* langKurdish (60) */ "ku", + /* langKashmiri (61) */ "ks", + /* langSindhi (62) */ "sd", + /* langTibetan (63) */ "bo", + /* langNepali (64) */ "ne", + /* langSanskrit (65) */ "sa", + /* langMarathi (66) */ "mr", + /* langBengali (67) */ "bn", + /* langAssamese (68) */ "as", + /* langGujarati (69) */ "gu", + + /* langPunjabi (70) */ "pa", + /* langOriya (71) */ "or", + /* langMalayalam (72) */ "ml", + /* langKannada (73) */ "kn", + /* langTamil (74) */ "ta", + /* langTelugu (75) */ "te", + /* langSinhalese (76) */ "si", + /* langBurmese (77) */ "my", + /* langKhmer (78) */ "km", + /* langLao (79) */ "lo", + + /* langVietnamese (80) */ "vi", + /* langIndonesian (81) */ "id", + /* langTagalog (82) */ "tl", + /* langMalayRoman (83) */ "ms", + /* langMalayArabic (84) */ "ms", + /* langAmharic (85) */ "am", + /* langTigrinya (86) */ "ti", + /* langOromo (87) */ "om", + /* langSomali (88) */ "so", + /* langSwahili (89) */ "sw", + + /* langKinyarwanda (90) */ "rw", + /* langRundi (91) */ "rn", + /* langNyanja (92) */ "ny", + /* langMalagasy (93) */ "mg", + /* langEsperanto (94) */ "eo" + +}; // kMacToXMPLang_0_94 + +static const char * kMacToXMPLang_128_151 [24] = { + + /* langWelsh (128) */ "cy", + /* langBasque (129) */ "eu", + + /* langCatalan (130) */ "ca", + /* langLatin (131) */ "la", + /* langQuechua (132) */ "qu", + /* langGuarani (133) */ "gn", + /* langAymara (134) */ "ay", + /* langTatar (135) */ "tt", + /* langUighur (136) */ "ug", + /* langDzongkha (137) */ "dz", + /* langJavaneseRom (138) */ "jv", + /* langSundaneseRom (139) */ "su", + + /* langGalician (140) */ "gl", + /* langAfrikaans (141) */ "af", + /* langBreton (142) */ "br", + /* langInuktitut (143) */ "iu", + /* langScottishGaelic (144) */ "gd", + /* langManxGaelic (145) */ "gv", + /* langIrishGaelicScript (146) */ "ga", + /* langTongan (147) */ "to", + /* langGreekAncient (148) */ "", // ! Has no ISO 639-1 2 letter code. + /* langGreenlandic (149) */ "kl", + + /* langAzerbaijanRoman (150) */ "az", + /* langNynorsk (151) */ "nn" + +}; // kMacToXMPLang_128_151 + +// ------------------------------------------------------------------------------------------------- + +#if XMP_WinBuild + +static UINT kMacScriptToWinCP[33] = { + /* smRoman (0) */ 10000, // There don't seem to be symbolic constants. + /* smJapanese (1) */ 10001, // From http://msdn.microsoft.com/en-us/library/dd317756(VS.85).aspx + /* smTradChinese (2) */ 10002, + /* smKorean (3) */ 10003, + /* smArabic (4) */ 10004, + /* smHebrew (5) */ 10005, + /* smGreek (6) */ 10006, + /* smCyrillic (7) */ 10007, + /* smRSymbol (8) */ 0, + /* smDevanagari (9) */ 0, + /* smGurmukhi (10) */ 0, + /* smGujarati (11) */ 0, + /* smOriya (12) */ 0, + /* smBengali (13) */ 0, + /* smTamil (14) */ 0, + /* smTelugu (15) */ 0, + /* smKannada (16) */ 0, + /* smMalayalam (17) */ 0, + /* smSinhalese (18) */ 0, + /* smBurmese (19) */ 0, + /* smKhmer (20) */ 0, + /* smThai (21) */ 10021, + /* smLao (22) */ 0, + /* smGeorgian (23) */ 0, + /* smArmenian (24) */ 0, + /* smSimpChinese (25) */ 10008, + /* smTibetan (26) */ 0, + /* smMongolian (27) */ 0, + /* smEthiopic/smGeez (28) */ 0, + /* smCentralEuroRoman (29) */ 10029, + /* smVietnamese (30) */ 0, + /* smExtArabic (31) */ 0, + /* smUninterp (32) */ 0 +}; // kMacScriptToWinCP + +static UINT kMacToWinCP_0_94 [95] = { + + /* langEnglish (0) */ 0, + /* langFrench (1) */ 0, + /* langGerman (2) */ 0, + /* langItalian (3) */ 0, + /* langDutch (4) */ 0, + /* langSwedish (5) */ 0, + /* langSpanish (6) */ 0, + /* langDanish (7) */ 0, + /* langPortuguese (8) */ 0, + /* langNorwegian (9) */ 0, + + /* langHebrew (10) */ 10005, + /* langJapanese (11) */ 10001, + /* langArabic (12) */ 10004, + /* langFinnish (13) */ 0, + /* langGreek (14) */ 10006, + /* langIcelandic (15) */ 10079, + /* langMaltese (16) */ 0, + /* langTurkish (17) */ 10081, + /* langCroatian (18) */ 10082, + /* langTradChinese (19) */ 10002, + + /* langUrdu (20) */ 0, + /* langHindi (21) */ 0, + /* langThai (22) */ 10021, + /* langKorean (23) */ 10003, + /* langLithuanian (24) */ 0, + /* langPolish (25) */ 0, + /* langHungarian (26) */ 0, + /* langEstonian (27) */ 0, + /* langLatvian (28) */ 0, + /* langSami (29) */ 0, + + /* langFaroese (30) */ 0, + /* langFarsi (31) */ 0, + /* langRussian (32) */ 0, + /* langSimpChinese (33) */ 10008, + /* langFlemish (34) */ 0, + /* langIrishGaelic (35) */ 0, + /* langAlbanian (36) */ 0, + /* langRomanian (37) */ 10010, + /* langCzech (38) */ 0, + /* langSlovak (39) */ 0, + + /* langSlovenian (40) */ 0, + /* langYiddish (41) */ 0, + /* langSerbian (42) */ 0, + /* langMacedonian (43) */ 0, + /* langBulgarian (44) */ 0, + /* langUkrainian (45) */ 10017, + /* langBelorussian (46) */ 0, + /* langUzbek (47) */ 0, + /* langKazakh (48) */ 0, + /* langAzerbaijani (49) */ 0, + + /* langAzerbaijanAr (50) */ 0, + /* langArmenian (51) */ 0, + /* langGeorgian (52) */ 0, + /* langMoldavian (53) */ 0, + /* langKirghiz (54) */ 0, + /* langTajiki (55) */ 0, + /* langTurkmen (56) */ 0, + /* langMongolian (57) */ 0, + /* langMongolianCyr (58) */ 0, + /* langPashto (59) */ 0, + + /* langKurdish (60) */ 0, + /* langKashmiri (61) */ 0, + /* langSindhi (62) */ 0, + /* langTibetan (63) */ 0, + /* langNepali (64) */ 0, + /* langSanskrit (65) */ 0, + /* langMarathi (66) */ 0, + /* langBengali (67) */ 0, + /* langAssamese (68) */ 0, + /* langGujarati (69) */ 0, + + /* langPunjabi (70) */ 0, + /* langOriya (71) */ 0, + /* langMalayalam (72) */ 0, + /* langKannada (73) */ 0, + /* langTamil (74) */ 0, + /* langTelugu (75) */ 0, + /* langSinhalese (76) */ 0, + /* langBurmese (77) */ 0, + /* langKhmer (78) */ 0, + /* langLao (79) */ 0, + + /* langVietnamese (80) */ 0, + /* langIndonesian (81) */ 0, + /* langTagalog (82) */ 0, + /* langMalayRoman (83) */ 0, + /* langMalayArabic (84) */ 0, + /* langAmharic (85) */ 0, + /* langTigrinya (86) */ 0, + /* langOromo (87) */ 0, + /* langSomali (88) */ 0, + /* langSwahili (89) */ 0, + + /* langKinyarwanda (90) */ 0, + /* langRundi (91) */ 0, + /* langNyanja (92) */ 0, + /* langMalagasy (93) */ 0, + /* langEsperanto (94) */ 0 + +}; // kMacToWinCP_0_94 + +#endif + + +#if XMP_iOSBuild + +static XMP_Uns32 kMacScriptToIOSEncodingCF[33] = { + /* smRoman (0) */ kCFStringEncodingMacRoman, + /* smJapanese (1) */ kCFStringEncodingMacJapanese, + /* smTradChinese (2) */ kCFStringEncodingMacChineseTrad, + /* smKorean (3) */ kCFStringEncodingMacKorean, + /* smArabic (4) */ kCFStringEncodingMacArabic, + /* smHebrew (5) */ kCFStringEncodingMacHebrew, + /* smGreek (6) */ kCFStringEncodingMacGreek, + /* smCyrillic (7) */ kCFStringEncodingMacCyrillic, + /* smRSymbol (8) */ kCFStringEncodingMacSymbol, + /* smDevanagari (9) */ kCFStringEncodingMacDevanagari, + /* smGurmukhi (10) */ kCFStringEncodingMacGurmukhi, + /* smGujarati (11) */ kCFStringEncodingMacGujarati, + /* smOriya (12) */ kCFStringEncodingMacOriya, + /* smBengali (13) */ kCFStringEncodingMacBengali, + /* smTamil (14) */ kCFStringEncodingMacTamil, + /* smTelugu (15) */ kCFStringEncodingMacTelugu, + /* smKannada (16) */ kCFStringEncodingMacKannada, + /* smMalayalam (17) */ kCFStringEncodingMacMalayalam, + /* smSinhalese (18) */ kCFStringEncodingMacSinhalese, + /* smBurmese (19) */ kCFStringEncodingMacBurmese, + /* smKhmer (20) */ kCFStringEncodingMacKhmer, + /* smThai (21) */ kCFStringEncodingMacThai, + /* smLao (22) */ kCFStringEncodingMacLaotian, + /* smGeorgian (23) */ kCFStringEncodingMacGeorgian, + /* smArmenian (24) */ kCFStringEncodingMacArmenian, + /* smSimpChinese (25) */ kCFStringEncodingMacChineseSimp, + /* smTibetan (26) */ kCFStringEncodingMacTibetan, + /* smMongolian (27) */ kCFStringEncodingMacMongolian, + /* smEthiopic/smGeez (28) */ kCFStringEncodingMacEthiopic, + /* smCentralEuroRoman (29) */ kCFStringEncodingMacCentralEurRoman, + /* smVietnamese (30) */ kCFStringEncodingMacVietnamese, + /* smExtArabic (31) */ kCFStringEncodingMacExtArabic, + /* smUninterp (32) */ kCFStringEncodingMacVT100 +}; // kMacScriptToIOSEncodingCF + +static XMP_Uns32 kMacToIOSEncodingCF_0_94 [95] = { + + /* langEnglish (0) */ kCFStringEncodingMacRoman, + /* langFrench (1) */ kCFStringEncodingMacRoman, + /* langGerman (2) */ kCFStringEncodingMacRoman, + /* langItalian (3) */ kCFStringEncodingMacRoman, + /* langDutch (4) */ kCFStringEncodingMacRoman, + /* langSwedish (5) */ kCFStringEncodingMacRoman, + /* langSpanish (6) */ kCFStringEncodingMacRoman, + /* langDanish (7) */ kCFStringEncodingMacRoman, + /* langPortuguese (8) */ kCFStringEncodingMacRoman, + /* langNorwegian (9) */ kCFStringEncodingMacRoman, + + /* langHebrew (10) */ kCFStringEncodingMacHebrew, + /* langJapanese (11) */ kCFStringEncodingMacJapanese, + /* langArabic (12) */ kCFStringEncodingMacArabic, + /* langFinnish (13) */ kCFStringEncodingMacRoman, + /* langGreek (14) */ kCFStringEncodingMacGreek, + /* langIcelandic (15) */ kCFStringEncodingMacIcelandic, + /* langMaltese (16) */ kCFStringEncodingMacRoman, + /* langTurkish (17) */ kCFStringEncodingMacTurkish, + /* langCroatian (18) */ kCFStringEncodingMacCroatian, + /* langTradChinese (19) */ kCFStringEncodingMacChineseTrad, + + /* langUrdu (20) */ kCFStringEncodingMacArabic, + /* langHindi (21) */ kCFStringEncodingMacDevanagari, + /* langThai (22) */ kCFStringEncodingMacThai, + /* langKorean (23) */ kCFStringEncodingMacKorean, + /* langLithuanian (24) */ kCFStringEncodingMacCentralEurRoman, + /* langPolish (25) */ kCFStringEncodingMacCentralEurRoman, + /* langHungarian (26) */ kCFStringEncodingMacCentralEurRoman, + /* langEstonian (27) */ kCFStringEncodingMacCentralEurRoman, + /* langLatvian (28) */ kCFStringEncodingMacCentralEurRoman, + /* langSami (29) */ kCFStringEncodingInvalidId, + + /* langFaroese (30) */ kCFStringEncodingMacRoman, + /* langFarsi (31) */ kCFStringEncodingMacFarsi, + /* langRussian (32) */ kCFStringEncodingMacCyrillic, + /* langSimpChinese (33) */ kCFStringEncodingMacChineseSimp, + /* langFlemish (34) */ kCFStringEncodingMacRoman, + /* langIrishGaelic (35) */ kCFStringEncodingMacRoman, + /* langAlbanian (36) */ kCFStringEncodingMacRoman, + /* langRomanian (37) */ kCFStringEncodingMacRomanian, + /* langCzech (38) */ kCFStringEncodingMacCentralEurRoman, + /* langSlovak (39) */ kCFStringEncodingMacCentralEurRoman, + + /* langSlovenian (40) */ kCFStringEncodingMacRoman, + /* langYiddish (41) */ kCFStringEncodingMacHebrew, + /* langSerbian (42) */ kCFStringEncodingMacCyrillic, + /* langMacedonian (43) */ kCFStringEncodingMacCyrillic, + /* langBulgarian (44) */ kCFStringEncodingMacCyrillic, + /* langUkrainian (45) */ kCFStringEncodingMacUkrainian, + /* langBelorussian (46) */ kCFStringEncodingMacCyrillic, + /* langUzbek (47) */ kCFStringEncodingMacCyrillic, + /* langKazakh (48) */ kCFStringEncodingMacCyrillic, + /* langAzerbaijani (49) */ kCFStringEncodingMacCyrillic, + + /* langAzerbaijanAr (50) */ kCFStringEncodingMacArabic, + /* langArmenian (51) */ kCFStringEncodingMacArmenian, + /* langGeorgian (52) */ kCFStringEncodingMacGeorgian, + /* langMoldavian (53) */ kCFStringEncodingMacCyrillic, + /* langKirghiz (54) */ kCFStringEncodingMacCyrillic, + /* langTajiki (55) */ kCFStringEncodingMacCyrillic, + /* langTurkmen (56) */ kCFStringEncodingMacCyrillic, + /* langMongolian (57) */ kCFStringEncodingMacMongolian, + /* langMongolianCyr (58) */ kCFStringEncodingMacCyrillic, + /* langPashto (59) */ kCFStringEncodingMacArabic, + + /* langKurdish (60) */ kCFStringEncodingMacArabic, + /* langKashmiri (61) */ kCFStringEncodingMacArabic, + /* langSindhi (62) */ kCFStringEncodingMacArabic, + /* langTibetan (63) */ kCFStringEncodingMacTibetan, + /* langNepali (64) */ kCFStringEncodingMacDevanagari, + /* langSanskrit (65) */ kCFStringEncodingMacDevanagari, + /* langMarathi (66) */ kCFStringEncodingMacDevanagari, + /* langBengali (67) */ kCFStringEncodingMacBengali, + /* langAssamese (68) */ kCFStringEncodingMacBengali, + /* langGujarati (69) */ kCFStringEncodingMacGujarati, + + /* langPunjabi (70) */ kCFStringEncodingMacGurmukhi, + /* langOriya (71) */ kCFStringEncodingMacOriya, + /* langMalayalam (72) */ kCFStringEncodingMacMalayalam, + /* langKannada (73) */ kCFStringEncodingMacKannada, + /* langTamil (74) */ kCFStringEncodingMacTamil, + /* langTelugu (75) */ kCFStringEncodingMacTelugu, + /* langSinhalese (76) */ kCFStringEncodingMacSinhalese, + /* langBurmese (77) */ kCFStringEncodingMacBurmese, + /* langKhmer (78) */ kCFStringEncodingMacKhmer, + /* langLao (79) */ kCFStringEncodingMacLaotian, + + /* langVietnamese (80) */ kCFStringEncodingMacVietnamese, + /* langIndonesian (81) */ kCFStringEncodingMacRoman, + /* langTagalog (82) */ kCFStringEncodingMacRoman, + /* langMalayRoman (83) */ kCFStringEncodingMacRoman, + /* langMalayArabic (84) */ kCFStringEncodingMacArabic, + /* langAmharic (85) */ kCFStringEncodingMacEthiopic, + /* langTigrinya (86) */ kCFStringEncodingMacEthiopic, + /* langOromo (87) */ kCFStringEncodingMacEthiopic, + /* langSomali (88) */ kCFStringEncodingMacRoman, + /* langSwahili (89) */ kCFStringEncodingMacRoman, + + /* langKinyarwanda (90) */ kCFStringEncodingMacRoman, + /* langRundi (91) */ kCFStringEncodingMacRoman, + /* langNyanja (92) */ kCFStringEncodingMacRoman, + /* langMalagasy (93) */ kCFStringEncodingMacRoman, + /* langEsperanto (94) */ kCFStringEncodingMacRoman + +}; // kMacToIOSEncodingCF_0_94 + +#endif + +// ================================================================================================= +// GetMacScript +// ============ + +static XMP_Uns16 GetMacScript ( XMP_Uns16 macLang ) +{ + XMP_Uns16 macScript = kNoMacScript; + + if ( macLang <= 94 ) { + macScript = kMacLangToScript_0_94[macLang]; + } else if ( (128 <= macLang) && (macLang <= 151) ) { + macScript = kMacLangToScript_128_151[macLang-128]; + } + + return macScript; + +} // GetMacScript + + +#if XMP_iOSBuild +// ================================================================================================= +// GetIOSEncodingCF +// ======== + +static XMP_Uns32 GetIOSEncodingCF ( XMP_Uns16 macLang ) +{ + XMP_Uns32 encCF = kCFStringEncodingInvalidId; + + if ( macLang <= 94 ) encCF = kMacToIOSEncodingCF_0_94[macLang]; + + if ( encCF == kCFStringEncodingInvalidId || !CFStringIsEncodingAvailable(encCF)) { + XMP_Uns16 macScript = GetMacScript ( macLang ); + if ( macScript != kNoMacScript ) encCF = kMacScriptToIOSEncodingCF[macScript]; + } + + return encCF; + +} // GetIOSEncodingCF +#endif + +// ================================================================================================= +// GetWinCP +// ======== + +#if XMP_WinBuild + +static UINT GetWinCP ( XMP_Uns16 macLang ) +{ + UINT winCP = 0; + + if ( macLang <= 94 ) winCP = kMacToWinCP_0_94[macLang]; + + if ( winCP == 0 ) { + XMP_Uns16 macScript = GetMacScript ( macLang ); + if ( macScript != kNoMacScript ) winCP = kMacScriptToWinCP[macScript]; + } + + return winCP; + +} // GetWinCP + +#endif + +// ================================================================================================= +// GetXMPLang +// ========== + +static XMP_StringPtr GetXMPLang ( XMP_Uns16 macLang ) +{ + XMP_StringPtr xmpLang = ""; + + if ( macLang <= 94 ) { + xmpLang = kMacToXMPLang_0_94[macLang]; + } else if ( (128 <= macLang) && (macLang <= 151) ) { + xmpLang = kMacToXMPLang_128_151[macLang-128]; + } + + return xmpLang; + +} // GetXMPLang + +// ================================================================================================= +// GetMacLang +// ========== + +static XMP_Uns16 GetMacLang ( std::string * xmpLang ) +{ + if ( *xmpLang == "" ) return kNoMacLang; + + size_t hyphenPos = xmpLang->find ( '-' ); // Make sure the XMP language is "generic". + if ( hyphenPos != std::string::npos ) xmpLang->erase ( hyphenPos ); + + for ( XMP_Uns16 i = 0; i <= 94; ++i ) { // Using std::map would be faster. + if ( *xmpLang == kMacToXMPLang_0_94[i] ) return i; + } + + for ( XMP_Uns16 i = 128; i <= 151; ++i ) { // Using std::map would be faster. + if ( *xmpLang == kMacToXMPLang_128_151[i-128] ) return i; + } + + return kNoMacLang; + +} // GetMacLang + +// ================================================================================================= +// MacRomanToUTF8 +// ============== + +static void MacRomanToUTF8 ( const std::string & macRoman, std::string * utf8 ) +{ + utf8->erase(); + + for ( XMP_Uns8* chPtr = (XMP_Uns8*)macRoman.c_str(); *chPtr != 0; ++chPtr ) { // ! Don't trust that char is unsigned. + if ( *chPtr < 0x80 ) { + (*utf8) += (char)*chPtr; + } else { + (*utf8) += kMacRomanUTF8[(*chPtr)-0x80]; + } + } + +} // MacRomanToUTF8 + +// ================================================================================================= +// UTF8ToMacRoman +// ============== + +static void UTF8ToMacRoman ( const std::string & utf8, std::string * macRoman ) +{ + macRoman->erase(); + bool inNonMRSpan = false; + + for ( const XMP_Uns8 * chPtr = (XMP_Uns8*)utf8.c_str(); *chPtr != 0; ++chPtr ) { // ! Don't trust that char is unsigned. + if ( *chPtr < 0x80 ) { + (*macRoman) += (char)*chPtr; + inNonMRSpan = false; + } else { + XMP_Uns32 cp = GetCodePoint ( &chPtr ); + --chPtr; // Make room for the loop increment. + XMP_Uns8 mr; + for ( mr = 0; (mr < 0x80) && (cp != kMacRomanCP[mr]); ++mr ) {}; // Using std::map would be faster. + if ( mr < 0x80 ) { + (*macRoman) += (char)(mr+0x80); + inNonMRSpan = false; + } else if ( ! inNonMRSpan ) { + (*macRoman) += '?'; + inNonMRSpan = true; + } + } + } + +} // UTF8ToMacRoman + +// ================================================================================================= +// IsMacLangKnown +// ============== + +static inline bool IsMacLangKnown ( XMP_Uns16 macLang ) +{ + XMP_Uns16 macScript = GetMacScript ( macLang ); + if ( macScript == kNoMacScript ) return false; + + #if XMP_UNIXBuild + if ( macScript != smRoman ) return false; + #elif XMP_WinBuild + if ( GetWinCP(macLang) == 0 ) return false; + #endif + + return true; + +} // IsMacLangKnown + +// ================================================================================================= +// ConvertToMacLang +// ================ + +bool ConvertToMacLang ( const std::string & utf8Value, XMP_Uns16 macLang, std::string * macValue ) +{ + macValue->erase(); + if ( macLang == kNoMacLang ) macLang = 0; // *** Zero is English, ought to use the "active" OS lang. + if ( ! IsMacLangKnown ( macLang ) ) return false; + + #if XMP_MacBuild + XMP_Uns16 macScript = GetMacScript ( macLang ); + ReconcileUtils::UTF8ToMacEncoding ( macScript, macLang, (XMP_Uns8*)utf8Value.c_str(), utf8Value.size(), macValue ); + #elif XMP_UNIXBuild + UTF8ToMacRoman ( utf8Value, macValue ); + #elif XMP_WinBuild + UINT winCP = GetWinCP ( macLang ); + ReconcileUtils::UTF8ToWinEncoding ( winCP, (XMP_Uns8*)utf8Value.c_str(), utf8Value.size(), macValue ); + #elif XMP_iOSBuild + XMP_Uns32 iosEncCF = GetIOSEncodingCF(macLang); + ReconcileUtils::IOSConvertEncoding(kCFStringEncodingUTF8, iosEncCF, (XMP_Uns8*)utf8Value.c_str(), utf8Value.size(), macValue); + #endif + + return true; + +} // ConvertToMacLang + +// ================================================================================================= +// ConvertFromMacLang +// ================== + +bool ConvertFromMacLang ( const std::string & macValue, XMP_Uns16 macLang, std::string * utf8Value ) +{ + utf8Value->erase(); + if ( ! IsMacLangKnown ( macLang ) ) return false; + + #if XMP_MacBuild + XMP_Uns16 macScript = GetMacScript ( macLang ); + ReconcileUtils::MacEncodingToUTF8 ( macScript, macLang, (XMP_Uns8*)macValue.c_str(), macValue.size(), utf8Value ); + #elif XMP_UNIXBuild + MacRomanToUTF8 ( macValue, utf8Value ); + #elif XMP_WinBuild + UINT winCP = GetWinCP ( macLang ); + ReconcileUtils::WinEncodingToUTF8 ( winCP, (XMP_Uns8*)macValue.c_str(), macValue.size(), utf8Value ); + #elif XMP_iOSBuild + XMP_Uns32 iosEncCF = GetIOSEncodingCF(macLang); + ReconcileUtils::IOSConvertEncoding(iosEncCF, kCFStringEncodingUTF8, (XMP_Uns8*)macValue.c_str(), macValue.size(), utf8Value); +#endif + + return true; + +} // ConvertFromMacLang + +// ================================================================================================= +// ================================================================================================= +// TradQT_Manager +// ================================================================================================= +// ================================================================================================= + +// ================================================================================================= +// TradQT_Manager::ParseCachedBoxes +// ================================ +// +// Parse the cached '©...' children of the 'moov'/'udta' box. The contents of each cached box are +// a sequence of "mini boxes" analogous to XMP AltText arrays. Each mini box has a 16-bit size, +// 16-bit language code, and text. The size is only the text size. The language codes are Macintosh +// Script Manager langXyz codes. The text encoding is implicit in the language, see comments in +// Apple's Script.h header. + +bool TradQT_Manager::ParseCachedBoxes ( const MOOV_Manager & moovMgr ) +{ + MOOV_Manager::BoxInfo udtaInfo; + MOOV_Manager::BoxRef udtaRef = moovMgr.GetBox ( "moov/udta", &udtaInfo ); + if ( udtaRef == 0 ) return false; + + for ( XMP_Uns32 i = 0; i < udtaInfo.childCount; ++i ) { + + MOOV_Manager::BoxInfo currInfo; + MOOV_Manager::BoxRef currRef = moovMgr.GetNthChild ( udtaRef, i, &currInfo ); + if ( currRef == 0 ) break; // Sanity check, should not happen. + if ( (currInfo.boxType >> 24) != 0xA9 ) continue; + if ( currInfo.contentSize < 2+2+1 ) continue; // Want enough for a non-empty value. + + InfoMapPos newInfo = this->parsedBoxes.insert ( this->parsedBoxes.end(), + InfoMap::value_type ( currInfo.boxType, ParsedBoxInfo ( currInfo.boxType ) ) ); + std::vector * newValues = &newInfo->second.values; + + XMP_Uns8 * boxPtr = (XMP_Uns8*) currInfo.content; + XMP_Uns8 * boxEnd = boxPtr + currInfo.contentSize; + XMP_Uns16 miniLen, macLang; + + for ( ; boxPtr < boxEnd-4; boxPtr += miniLen ) { + + miniLen = 4 + GetUns16BE ( boxPtr ); // ! Include header in local miniLen. + macLang = GetUns16BE ( boxPtr+2); + if ( (miniLen <= 4) || (miniLen > (boxEnd - boxPtr)) ) continue; // Ignore bad or empty values. + + XMP_StringPtr valuePtr = (char*)(boxPtr+4); + size_t valueLen = miniLen - 4; + + newValues->push_back ( ValueInfo() ); + ValueInfo * newValue = &newValues->back(); + + // Only set the XMP language if the Mac script is known, i.e. the value can be converted. + + newValue->macLang = macLang; + if ( IsMacLangKnown ( macLang ) ) newValue->xmpLang = GetXMPLang ( macLang ); + newValue->macValue.assign ( valuePtr, valueLen ); + + } + + } + + return (! this->parsedBoxes.empty()); + +} // TradQT_Manager::ParseCachedBoxes + +// ================================================================================================= +// TradQT_Manager::ImportSimpleXMP +// =============================== +// +// Update a simple XMP property if the QT value looks newer. + +bool TradQT_Manager::ImportSimpleXMP ( XMP_Uns32 id, SXMPMeta * xmp, XMP_StringPtr ns, XMP_StringPtr prop ) const +{ + + try { + + InfoMapCPos infoPos = this->parsedBoxes.find ( id ); + if ( infoPos == this->parsedBoxes.end() ) return false; + if ( infoPos->second.values.empty() ) return false; + + std::string xmpValue, tempValue; + XMP_OptionBits flags; + bool xmpExists = xmp->GetProperty ( ns, prop, &xmpValue, &flags ); + if ( xmpExists && (! XMP_PropIsSimple ( flags )) ) { + XMP_Throw ( "TradQT_Manager::ImportSimpleXMP - XMP property must be simple", kXMPErr_BadParam ); + } + + bool convertOK; + const ValueInfo & qtItem = infoPos->second.values[0]; // ! Use the first QT entry. + + if ( xmpExists ) { + convertOK = ConvertToMacLang ( xmpValue, qtItem.macLang, &tempValue ); + if ( ! convertOK ) return false; // throw? + if ( tempValue == qtItem.macValue ) return false; // QT value matches back converted XMP value. + } + + convertOK = ConvertFromMacLang ( qtItem.macValue, qtItem.macLang, &tempValue ); + if ( ! convertOK ) return false; // throw? + xmp->SetProperty ( ns, prop, tempValue.c_str() ); + return true; + + } catch ( ... ) { + + return false; // Don't let one failure abort other imports. + + } + +} // TradQT_Manager::ImportSimpleXMP + +// ================================================================================================= +// TradQT_Manager::ImportLangItem +// ============================== +// +// Update a specific XMP AltText item if the QuickTime value looks newer. + +bool TradQT_Manager::ImportLangItem ( const ValueInfo & qtItem, SXMPMeta * xmp, + XMP_StringPtr ns, XMP_StringPtr langArray ) const +{ + + try { + + XMP_StringPtr genericLang, specificLang; + if ( qtItem.xmpLang[0] != 0 ) { + genericLang = qtItem.xmpLang; + specificLang = qtItem.xmpLang; + } else { + genericLang = ""; + specificLang = "x-default"; + } + + bool convertOK; + std::string xmpValue, tempValue, actualLang; + bool xmpExists = xmp->GetLocalizedText ( ns, langArray, genericLang, specificLang, &actualLang, &xmpValue, 0 ); + if ( xmpExists ) { + convertOK = ConvertToMacLang ( xmpValue, qtItem.macLang, &tempValue ); + if ( ! convertOK ) return false; // throw? + if ( tempValue == qtItem.macValue ) return true; // QT value matches back converted XMP value. + specificLang = actualLang.c_str(); + } + + convertOK = ConvertFromMacLang ( qtItem.macValue, qtItem.macLang, &tempValue ); + if ( ! convertOK ) return false; // throw? + xmp->SetLocalizedText ( ns, langArray, "", specificLang, tempValue.c_str() ); + return true; + + } catch ( ... ) { + + return false; // Don't let one failure abort other imports. + + } + +} // TradQT_Manager::ImportLangItem + +// ================================================================================================= +// TradQT_Manager::ImportLangAltXMP +// ================================ +// +// Update items in the XMP array if the QT value looks newer. + +bool TradQT_Manager::ImportLangAltXMP ( XMP_Uns32 id, SXMPMeta * xmp, XMP_StringPtr ns, XMP_StringPtr langArray ) const +{ + + try { + + InfoMapCPos infoPos = this->parsedBoxes.find ( id ); + if ( infoPos == this->parsedBoxes.end() ) return false; + if ( infoPos->second.values.empty() ) return false; // Quit now if there are no values. + + XMP_OptionBits flags; + bool xmpExists = xmp->GetProperty ( ns, langArray, 0, &flags ); + if ( ! xmpExists ) { + xmp->SetProperty ( ns, langArray, 0, kXMP_PropArrayIsAltText ); + } else if ( ! XMP_ArrayIsAltText ( flags ) ) { + XMP_Throw ( "TradQT_Manager::ImportLangAltXMP - XMP array must be AltText", kXMPErr_BadParam ); + } + + // Process all of the QT values, looking up the appropriate XMP language for each. + + bool haveMappings = false; + const ValueVector & qtValues = infoPos->second.values; + + for ( size_t i = 0, limit = qtValues.size(); i < limit; ++i ) { + const ValueInfo & qtItem = qtValues[i]; + if ( *qtItem.xmpLang == 0 ) continue; // Only do known mappings in the loop. + haveMappings |= this->ImportLangItem ( qtItem, xmp, ns, langArray ); + } + + if ( ! haveMappings ) { + // If nothing mapped, process the first QT item to XMP's "x-default". + haveMappings = this->ImportLangItem ( qtValues[0], xmp, ns, langArray ); // ! No xmpLang implies "x-default". + } + + return haveMappings; + + } catch ( ... ) { + + return false; // Don't let one failure abort other imports. + + } + +} // TradQT_Manager::ImportLangAltXMP + +// ================================================================================================= +// TradQT_Manager::ExportSimpleXMP +// =============================== +// +// Export a simple XMP value to the first existing QuickTime item. Delete all of the QT values if the +// XMP value is empty or the XMP does not exist. + +// ! We don't create new QuickTime items since we don't know the language. + +void TradQT_Manager::ExportSimpleXMP ( XMP_Uns32 id, const SXMPMeta & xmp, XMP_StringPtr ns, XMP_StringPtr prop, + bool createWithZeroLang /* = false */ ) +{ + std::string xmpValue, macValue; + + InfoMapPos infoPos = this->parsedBoxes.find ( id ); + bool qtFound = (infoPos != this->parsedBoxes.end()) && (! infoPos->second.values.empty()); + + bool xmpFound = xmp.GetProperty ( ns, prop, &xmpValue, 0 ); + if ( (! xmpFound) || (xmpValue.empty()) ) { + if ( qtFound ) { + this->parsedBoxes.erase ( infoPos ); + this->changed = true; + } + return; + } + + XMP_Assert ( xmpFound ); + if ( ! qtFound ) { + if ( ! createWithZeroLang ) return; + infoPos = this->parsedBoxes.insert ( this->parsedBoxes.end(), + InfoMap::value_type ( id, ParsedBoxInfo ( id ) ) ); + ValueVector * newValues = &infoPos->second.values; + newValues->push_back ( ValueInfo() ); + ValueInfo * newValue = &newValues->back(); + newValue->macLang = 0; // Happens to be langEnglish. + newValue->xmpLang = kMacToXMPLang_0_94[0]; + this->changed = infoPos->second.changed = true; + } + + ValueInfo * qtItem = &infoPos->second.values[0]; // ! Use the first QT entry. + if ( ! IsMacLangKnown ( qtItem->macLang ) ) return; + + bool convertOK = ConvertToMacLang ( xmpValue, qtItem->macLang, &macValue ); + if ( convertOK && (macValue != qtItem->macValue) ) { + qtItem->macValue = macValue; + this->changed = infoPos->second.changed = true; + } + +} // TradQT_Manager::ExportSimpleXMP + +// ================================================================================================= +// TradQT_Manager::ExportLangAltXMP +// ================================ +// +// Export XMP LangAlt array items to QuickTime, where the language and encoding mappings are known. +// If there are no known language and encoding mappings, map the XMP default item to the first +// existing QuickTime item. + +void TradQT_Manager::ExportLangAltXMP ( XMP_Uns32 id, const SXMPMeta & xmp, XMP_StringPtr ns, XMP_StringPtr langArray ) +{ + bool haveMappings = false; + std::string xmpPath, xmpValue, xmpLang, macValue; + + InfoMapPos infoPos = this->parsedBoxes.find ( id ); + if ( infoPos == this->parsedBoxes.end() ) { + infoPos = this->parsedBoxes.insert ( this->parsedBoxes.end(), + InfoMap::value_type ( id, ParsedBoxInfo ( id ) ) ); + } + + ValueVector * qtValues = &infoPos->second.values; + XMP_Index xmpCount = xmp.CountArrayItems ( ns, langArray ); + bool convertOK; + + if ( xmpCount == 0 ) { + // Delete the "mappable" QuickTime items if there are no XMP values. Leave the others alone. + for ( int i = (int)qtValues->size()-1; i > 0; --i ) { // ! Need a signed index. + if ( (*qtValues)[i].xmpLang[0] != 0 ) { + qtValues->erase ( qtValues->begin() + i ); + this->changed = infoPos->second.changed = true; + } + } + return; + } + + // Go through the XMP and look for a related macLang QuickTime item to update or create. + + for ( XMP_Index xmpIndex = 1; xmpIndex <= xmpCount; ++xmpIndex ) { // ! XMP index starts at 1! + + SXMPUtils::ComposeArrayItemPath ( ns, langArray, xmpIndex, &xmpPath ); + if ( !xmp.GetProperty ( ns, xmpPath.c_str(), &xmpValue, 0 ) ) continue; + xmp.GetQualifier ( ns, xmpPath.c_str(), kXMP_NS_XML, "lang", &xmpLang, 0 ); + if ( xmpLang == "x-default" ) continue; + + XMP_Uns16 macLang = GetMacLang ( &xmpLang ); + if ( macLang == kNoMacLang ) continue; + + size_t qtIndex, qtLimit; + for ( qtIndex = 0, qtLimit = qtValues->size(); qtIndex < qtLimit; ++qtIndex ) { + if ( (*qtValues)[qtIndex].macLang == macLang ) break; + } + + if ( qtIndex == qtLimit ) { + // No existing QuickTime item, try to create one. + if ( ! IsMacLangKnown ( macLang ) ) continue; + qtValues->push_back ( ValueInfo() ); + qtIndex = qtValues->size() - 1; + ValueInfo * newItem = &((*qtValues)[qtIndex]); + newItem->macLang = macLang; + newItem->xmpLang = GetXMPLang ( macLang ); // ! Use the 2 character root language. + } + + ValueInfo * qtItem = &((*qtValues)[qtIndex]); + qtItem->marked = true; // Mark it whether updated or not, don't delete it in the next pass. + + convertOK = ConvertToMacLang ( xmpValue, qtItem->macLang, &macValue ); + if ( convertOK && (macValue != qtItem->macValue) ) { + qtItem->macValue.swap ( macValue ); // No need to make a copy. + haveMappings = true; + } + + } + this->changed |= haveMappings; + infoPos->second.changed |= haveMappings; + + // Go through the QuickTime items that are unmarked and delete those that have an xmpLang + // and known macScript. Clear all marks. + + for ( int i = (int)qtValues->size()-1; i > 0; --i ) { // ! Need a signed index. + ValueInfo * qtItem = &((*qtValues)[i]); + if ( qtItem->marked ) { + qtItem->marked = false; + } else if ( (qtItem->xmpLang[0] != 0) && IsMacLangKnown ( qtItem->macLang ) ) { + qtValues->erase ( qtValues->begin() + i ); + this->changed = infoPos->second.changed = true; + } + } + + // If there were no mappings, export the XMP default item to the first QT item. + + if ( (! haveMappings) && (! qtValues->empty()) ) { + + bool ok = xmp.GetLocalizedText ( ns, langArray, "", "x-default", 0, &xmpValue, 0 ); + if ( ! ok ) return; + + ValueInfo * qtItem = &((*qtValues)[0]); + if ( ! IsMacLangKnown ( qtItem->macLang ) ) return; + + convertOK = ConvertToMacLang ( xmpValue, qtItem->macLang, &macValue ); + if ( convertOK && (macValue != qtItem->macValue) ) { + qtItem->macValue.swap ( macValue ); // No need to make a copy. + this->changed = infoPos->second.changed = true; + } + + } + +} // TradQT_Manager::ExportLangAltXMP + +// ================================================================================================= +// TradQT_Manager::UpdateChangedBoxes +// ================================== + +void TradQT_Manager::UpdateChangedBoxes ( MOOV_Manager * moovMgr ) +{ + MOOV_Manager::BoxInfo udtaInfo; + MOOV_Manager::BoxRef udtaRef = moovMgr->GetBox ( "moov/udta", &udtaInfo ); + XMP_Assert ( (udtaRef != 0) || (udtaInfo.childCount == 0) ); + + if ( udtaRef != 0 ) { // Might not have been a moov/udta box in the parse. + + // First go through the moov/udta/©... children and delete those that are not in the map. + + for ( XMP_Uns32 ordinal = udtaInfo.childCount; ordinal > 0; --ordinal ) { // ! Go backwards because of deletions. + + MOOV_Manager::BoxInfo currInfo; + MOOV_Manager::BoxRef currRef = moovMgr->GetNthChild ( udtaRef, (ordinal-1), &currInfo ); + if ( currRef == 0 ) break; // Sanity check, should not happen. + if ( (currInfo.boxType >> 24) != 0xA9 ) continue; + if ( currInfo.contentSize < 2+2+1 ) continue; // These were skipped by ParseCachedBoxes. + + InfoMapPos infoPos = this->parsedBoxes.find ( currInfo.boxType ); + if ( infoPos == this->parsedBoxes.end() ) moovMgr->DeleteNthChild ( udtaRef, (ordinal-1) ); + + } + + } + + // Now go through the changed items in the map and update them in the moov/udta subtree. + + InfoMapCPos infoPos = this->parsedBoxes.begin(); + InfoMapCPos infoEnd = this->parsedBoxes.end(); + + for ( ; infoPos != infoEnd; ++infoPos ) { + + ParsedBoxInfo * qtItem = (ParsedBoxInfo*) &infoPos->second; + if ( ! qtItem->changed ) continue; + qtItem->changed = false; + + XMP_Uns32 qtTotalSize = 0; // Total size of the QT values, ignoring empty values. + for ( size_t i = 0, limit = qtItem->values.size(); i < limit; ++i ) { + if ( ! qtItem->values[i].macValue.empty() ) { + if ( qtItem->values[i].macValue.size() > 0xFFFF ) qtItem->values[i].macValue.erase ( 0xFFFF ); + qtTotalSize += (XMP_Uns32)(2+2 + qtItem->values[i].macValue.size()); + } + } + + if ( udtaRef == 0 ) { // Might not have been a moov/udta box in the parse. + moovMgr->SetBox ( "moov/udta", 0, 0 ); + udtaRef = moovMgr->GetBox ( "moov/udta", &udtaInfo ); + XMP_Assert ( udtaRef != 0 ); + } + + if ( qtTotalSize == 0 ) { + + moovMgr->DeleteTypeChild ( udtaRef, qtItem->id ); + + } else { + + // Compose the complete box content. + + RawDataBlock fullValue; + fullValue.assign ( qtTotalSize, 0 ); + XMP_Uns8 * valuePtr = &fullValue[0]; + + for ( size_t i = 0, limit = qtItem->values.size(); i < limit; ++i ) { + XMP_Assert ( qtItem->values[i].macValue.size() <= 0xFFFF ); + XMP_Uns16 textSize = (XMP_Uns16)qtItem->values[i].macValue.size(); + if ( textSize == 0 ) continue; + PutUns16BE ( textSize, valuePtr ); valuePtr += 2; + PutUns16BE ( qtItem->values[i].macLang, valuePtr ); valuePtr += 2; + memcpy ( valuePtr, qtItem->values[i].macValue.c_str(), textSize ); valuePtr += textSize; + } + + // Look for an existing box to update, else add a new one. + + MOOV_Manager::BoxInfo itemInfo; + MOOV_Manager::BoxRef itemRef = moovMgr->GetTypeChild ( udtaRef, qtItem->id, &itemInfo ); + + if ( itemRef != 0 ) { + moovMgr->SetBox ( itemRef, &fullValue[0], qtTotalSize ); + } else { + moovMgr->AddChildBox ( udtaRef, qtItem->id, &fullValue[0], qtTotalSize ); + } + + } + + } + +} // TradQT_Manager::UpdateChangedBoxes + +// ================================================================================================= diff --git a/core/libs/dngwriter/extra/xmp_sdk/XMPFiles/source/FormatSupport/QuickTime_Support.hpp b/core/libs/dngwriter/extra/xmp_sdk/XMPFiles/source/FormatSupport/QuickTime_Support.hpp new file mode 100644 index 0000000000..691e746ef1 --- /dev/null +++ b/core/libs/dngwriter/extra/xmp_sdk/XMPFiles/source/FormatSupport/QuickTime_Support.hpp @@ -0,0 +1,105 @@ +#ifndef __QuickTime_Support_hpp__ +#define __QuickTime_Support_hpp__ 1 + +// ================================================================================================= +// ADOBE SYSTEMS INCORPORATED +// Copyright 2009 Adobe Systems Incorporated +// All Rights Reserved +// +// NOTICE: Adobe permits you to use, modify, and distribute this file in accordance with the terms +// of the Adobe license agreement accompanying it. +// ================================================================================================= + +#include "public/include/XMP_Environment.h" // ! This must be the first include. + +#include "public/include/XMP_Const.h" +#include "public/include/XMP_IO.hpp" + +#include +#include +#include + +#include "XMPFiles/source/XMPFiles_Impl.hpp" +#include "XMPFiles/source/FormatSupport/ISOBaseMedia_Support.hpp" +#include "XMPFiles/source/FormatSupport/MOOV_Support.hpp" + +// ================================================================================================= +// ================================================================================================= + +// ================================================================================================= +// TradQT_Manager +// ============== + +// Support for selected traditional QuickTime metadata items. The supported items are the children +// of the 'moov'/'udta' box whose type begins with 0xA9, a MacRoman copyright symbol. Each of these +// is a box whose contents are a sequence of "mini boxes" analogous to XMP AltText arrays. Each mini +// box has a 16-bit size, 16-bit language code, and text. The language code values are the old +// Macintosh Script Manager langXyz codes, the text encoding is implicit, see Mac Script.h. + +enum { // List of recognized items from the QuickTime 'moov'/'udta' box. + // These items are defined by Adobe. + kQTilst_Reel = 0xA952454CUL, // '©REL' + kQTilst_Timecode = 0xA954494DUL, // '©TIM' + kQTilst_TimeScale = 0xA9545343UL, // '©TSC' + kQTilst_TimeSize = 0xA954535AUL // '©TSZ' +}; + +enum { + kNoMacLang = 0xFFFF, + kNoMacScript = 0xFFFF +}; + +extern bool ConvertToMacLang ( const std::string & utf8Value, XMP_Uns16 macLang, std::string * macValue ); +extern bool ConvertFromMacLang ( const std::string & macValue, XMP_Uns16 macLang, std::string * utf8Value ); + +class TradQT_Manager { +public: + + TradQT_Manager() : changed(false) {}; + + bool ParseCachedBoxes ( const MOOV_Manager & moovMgr ); + + bool ImportSimpleXMP ( XMP_Uns32 id, SXMPMeta * xmp, XMP_StringPtr ns, XMP_StringPtr prop ) const; + bool ImportLangAltXMP ( XMP_Uns32 id, SXMPMeta * xmp, XMP_StringPtr ns, XMP_StringPtr langArray ) const; + + void ExportSimpleXMP ( XMP_Uns32 id, const SXMPMeta & xmp, XMP_StringPtr ns, XMP_StringPtr prop, + bool createWithZeroLang = false ); + void ExportLangAltXMP ( XMP_Uns32 id, const SXMPMeta & xmp, XMP_StringPtr ns, XMP_StringPtr langArray ); + + bool IsChanged() const { return this->changed; }; + + void UpdateChangedBoxes ( MOOV_Manager * moovMgr ); + +private: + + struct ValueInfo { + bool marked; + XMP_Uns16 macLang; + XMP_StringPtr xmpLang; // ! Only set if macLang is known, i.e. the value can be converted. + std::string macValue; + ValueInfo() : marked(false), macLang(kNoMacLang), xmpLang("") {}; + }; + typedef std::vector ValueVector; + typedef ValueVector::iterator ValueInfoPos; + typedef ValueVector::const_iterator ValueInfoCPos; + + struct ParsedBoxInfo { + XMP_Uns32 id; + ValueVector values; + bool changed; + ParsedBoxInfo() : id(0), changed(false) {}; + ParsedBoxInfo ( XMP_Uns32 _id ) : id(_id), changed(false) {}; + }; + + typedef std::map < XMP_Uns32, ParsedBoxInfo > InfoMap; // Metadata item kind and content info. + typedef InfoMap::iterator InfoMapPos; + typedef InfoMap::const_iterator InfoMapCPos; + + InfoMap parsedBoxes; + bool changed; + + bool ImportLangItem ( const ValueInfo & qtItem, SXMPMeta * xmp, XMP_StringPtr ns, XMP_StringPtr langArray ) const; + +}; // TradQT_Manager + +#endif // __QuickTime_Support_hpp__ diff --git a/core/libs/dngwriter/extra/xmp_sdk/XMPFiles/source/FormatSupport/RIFF.cpp b/core/libs/dngwriter/extra/xmp_sdk/XMPFiles/source/FormatSupport/RIFF.cpp new file mode 100644 index 0000000000..6930190a69 --- /dev/null +++ b/core/libs/dngwriter/extra/xmp_sdk/XMPFiles/source/FormatSupport/RIFF.cpp @@ -0,0 +1,890 @@ +// ================================================================================================= +// ADOBE SYSTEMS INCORPORATED +// Copyright 2009 Adobe Systems Incorporated +// All Rights Reserved +// +// NOTICE: Adobe permits you to use, modify, and distribute this file in accordance with the terms +// of the Adobe license agreement accompanying it. +// ================================================================================================= + +#include "public/include/XMP_Environment.h" // ! XMP_Environment.h must be the first included header. + +#include "public/include/XMP_Const.h" +#include "public/include/XMP_IO.hpp" + +#include "XMPFiles/source/XMPFiles_Impl.hpp" +#include "source/XMPFiles_IO.hpp" +#include "source/XIO.hpp" + +// must have access to handler class fields... +#include "XMPFiles/source/FormatSupport/RIFF.hpp" +#include "XMPFiles/source/FormatSupport/RIFF_Support.hpp" +#include "XMPFiles/source/FileHandlers/RIFF_Handler.hpp" + +#include + +using namespace RIFF; + +namespace RIFF { + +// GENERAL STATIC FUNCTIONS //////////////////////////////////////// + +Chunk* getChunk ( ContainerChunk* parent, RIFF_MetaHandler* handler ) +{ + XMP_IO* file = handler->parent->ioRef; + XMP_Uns8 level = handler->level; + XMP_Uns32 peek = XIO::PeekUns32_LE ( file ); + + if ( level == 0 ) + { + XMP_Validate( peek == kChunk_RIFF, "expected RIFF chunk not found", kXMPErr_BadFileFormat ); + XMP_Enforce( parent == NULL ); + } + else + { + XMP_Validate( peek != kChunk_RIFF, "unexpected RIFF chunk below top-level", kXMPErr_BadFileFormat ); + XMP_Enforce( parent != NULL ); + } + + switch( peek ) + { + case kChunk_RIFF: + return new ContainerChunk( parent, handler ); + case kChunk_LIST: + { + if ( level != 1 ) break; // only care on this level + + // look further (beyond 4+4 = beyond id+size) to check on relevance + file->Seek ( 8, kXMP_SeekFromCurrent ); + XMP_Uns32 containerType = XIO::PeekUns32_LE ( file ); + file->Seek ( -8, kXMP_SeekFromCurrent ); + + bool isRelevantList = ( containerType== kType_INFO || containerType == kType_Tdat || containerType == kType_hdrl ); + if ( !isRelevantList ) break; + return new ContainerChunk( parent, handler ); + } + case kChunk_XMP: + if ( level != 1 ) break; // ignore on inappropriate levels (might be compound metadata?) + return new XMPChunk( parent, handler ); + case kChunk_DISP: + { + if ( level != 1 ) break; // only care on this level + // peek even further to see if type is 0x001 and size is reasonable + file ->Seek ( 4, kXMP_SeekFromCurrent ); // jump DISP + XMP_Uns32 dispSize = XIO::ReadUns32_LE( file ); + XMP_Uns32 dispType = XIO::ReadUns32_LE( file ); + file ->Seek ( -12, kXMP_SeekFromCurrent ); // rewind, be in front of chunkID again + + // only take as a relevant disp if both criteria met, + // otherwise treat as generic chunk! + if ( (dispType == 0x0001) && ( dispSize < 256 * 1024 ) ) + { + ValueChunk* r = new ValueChunk( parent, handler ); + handler->dispChunk = r; + return r; + } + break; // treat as irrelevant (non-0x1) DISP chunks as generic chunk + } + case kChunk_bext: + { + if ( level != 1 ) break; // only care on this level + // store for now in a value chunk + ValueChunk* r = new ValueChunk( parent, handler ); + handler->bextChunk = r; + return r; + } + case kChunk_PrmL: + { + if ( level != 1 ) break; // only care on this level + ValueChunk* r = new ValueChunk( parent, handler ); + handler->prmlChunk = r; + return r; + } + case kChunk_Cr8r: + { + if ( level != 1 ) break; // only care on this level + ValueChunk* r = new ValueChunk( parent, handler ); + handler->cr8rChunk = r; + return r; + } + case kChunk_JUNQ: + case kChunk_JUNK: + { + JunkChunk* r = new JunkChunk( parent, handler ); + return r; + } + case kChunk_IDIT: + { + if ( level != 2 ) break; // only care on this level + ValueChunk* r = new ValueChunk( parent, handler ); + handler->iditChunk = r; + return r; + } + } + // this "default:" section must be ouside switch bracket, to be + // reachable by all those break statements above: + + + // digest 'valuable' container chunks: LIST:INFO, LIST:Tdat + bool insideRelevantList = ( level==2 && parent->id == kChunk_LIST + && ( parent->containerType== kType_INFO || parent->containerType == kType_Tdat )); + + if ( insideRelevantList ) + { + ValueChunk* r = new ValueChunk( parent, handler ); + return r; + } + + // general chunk of no interest, treat as unknown blob + return new Chunk( parent, handler, true, chunk_GENERAL ); +} + +// BASE CLASS CHUNK /////////////////////////////////////////////// +// ad hoc creation +Chunk::Chunk( ContainerChunk* parent, ChunkType c, XMP_Uns32 id ) +{ + this->hasChange = false; + this->chunkType = c; // base class assumption + this->parent = parent; + this->id = id; + this->oldSize = 0; + this->newSize = 8; + this->oldPos = 0; // inevitable for ad-hoc + this->needSizeFix = false; + + // good parenting for latter destruction + if ( this->parent != NULL ) + { + this->parent->children.push_back( this ); + if( this->chunkType == chunk_VALUE ) + this->parent->childmap.insert( std::make_pair( this->id, (ValueChunk*) this ) ); + } +} + +// parsing creation +Chunk::Chunk( ContainerChunk* parent, RIFF_MetaHandler* handler, bool skip, ChunkType c ) +{ + chunkType = c; // base class assumption + this->parent = parent; + this->oldSize = 0; + this->hasChange = false; // [2414649] valid assumption at creation time + + XMP_IO* file = handler->parent->ioRef; + + this->oldPos = file->Offset(); + this->id = XIO::ReadUns32_LE( file ); + this->oldSize = XIO::ReadUns32_LE( file ) + 8; + + // Make sure the size is within expected bounds. + XMP_Int64 chunkEnd = this->oldPos + this->oldSize; + XMP_Int64 chunkLimit = handler->oldFileSize; + if ( parent != 0 ) chunkLimit = parent->oldPos + parent->oldSize; + if ( chunkEnd > chunkLimit ) { + bool isUpdate = XMP_OptionIsSet ( handler->parent->openFlags, kXMPFiles_OpenForUpdate ); + bool repairFile = XMP_OptionIsSet ( handler->parent->openFlags, kXMPFiles_OpenRepairFile ); + if ( (! isUpdate) || (repairFile && (parent == 0)) ) { + this->oldSize = chunkLimit - this->oldPos; + } else { + XMP_Throw ( "Bad RIFF chunk size", kXMPErr_BadFileFormat ); + } + } + + this->newSize = this->oldSize; + this->needSizeFix = false; + + if ( skip ) file->Seek ( (this->oldSize - 8), kXMP_SeekFromCurrent ); + + // "good parenting", essential for latter destruction. + if ( this->parent != NULL ) + { + this->parent->children.push_back( this ); + if( this->chunkType == chunk_VALUE ) + this->parent->childmap.insert( std::make_pair( this->id, (ValueChunk*) this ) ); + } +} + +void Chunk::changesAndSize( RIFF_MetaHandler* handler ) +{ + // only unknown chunks should reach this method, + // all others must reach overloads, hence little to do here: + hasChange = false; // unknown chunk ==> no change, naturally + this->newSize = this->oldSize; +} + +std::string Chunk::toString(XMP_Uns8 level ) +{ + char buffer[256]; + snprintf( buffer, 255, "%.4s -- " + "oldSize: 0x%.8llX, " + "newSize: 0x%.8llX, " + "oldPos: 0x%.8llX\n", + (char*)(&this->id), this->oldSize, this->newSize, this->oldPos ); + return std::string(buffer); +} + +void Chunk::write( RIFF_MetaHandler* handler, XMP_IO* file , bool isMainChunk ) +{ + throw new XMP_Error(kXMPErr_InternalFailure, "Chunk::write never to be called for unknown chunks."); +} + +Chunk::~Chunk() +{ + //nothing +} + +// CONTAINER CHUNK ///////////////////////////////////////////////// +// a) creation +// [2376832] expectedSize - minimum padding "parking size" to use, if not available append to end +ContainerChunk::ContainerChunk( ContainerChunk* parent, XMP_Uns32 id, XMP_Uns32 containerType ) : Chunk( NULL /* !! */, chunk_CONTAINER, id ) +{ + // accept no unparented ConatinerChunks + XMP_Enforce( parent != NULL ); + + this->containerType = containerType; + this->newSize = 12; + this->parent = parent; + + chunkVect* siblings = &parent->children; + + // add at end. ( oldSize==0 will flag optimization later in the process) + siblings->push_back( this ); +} + +// b) parsing +ContainerChunk::ContainerChunk( ContainerChunk* parent, RIFF_MetaHandler* handler ) : Chunk( parent, handler, false, chunk_CONTAINER ) +{ + bool repairMode = ( 0 != ( handler->parent->openFlags & kXMPFiles_OpenRepairFile )); + + try + { + XMP_IO* file = handler->parent->ioRef; + XMP_Uns8 level = handler->level; + + // get type of container chunk + this->containerType = XIO::ReadUns32_LE( file ); + + // ensure legality of top-level chunks + if ( level == 0 && handler->riffChunks.size() > 0 ) + { + XMP_Validate( handler->parent->format == kXMP_AVIFile, "only AVI may have multiple top-level chunks", kXMPErr_BadFileFormat ); + XMP_Validate( this->containerType == kType_AVIX, "all chunks beyond main chunk must be type AVIX", kXMPErr_BadFileFormat ); + } + + // has *relevant* subChunks? (there might be e.g. non-INFO LIST chunks we don't care about) + bool hasSubChunks = ( ( this->id == kChunk_RIFF ) || + ( this->id == kChunk_LIST && this->containerType == kType_INFO ) || + ( this->id == kChunk_LIST && this->containerType == kType_Tdat ) || + ( this->id == kChunk_LIST && this->containerType == kType_hdrl ) + ); + XMP_Int64 endOfChunk = this->oldPos + this->oldSize; + + // this statement catches beyond-EoF-offsets on any level + // exception: level 0, tolerate if in repairMode + if ( (level == 0) && repairMode && (endOfChunk > handler->oldFileSize) ) + { + endOfChunk = handler->oldFileSize; // assign actual file size + this->oldSize = endOfChunk - this->oldPos; //reversely calculate correct oldSize + } + + XMP_Validate( endOfChunk <= handler->oldFileSize, "offset beyond EoF", kXMPErr_BadFileFormat ); + + Chunk* curChild = 0; + if ( hasSubChunks ) + { + handler->level++; + while ( file->Offset() < endOfChunk ) + { + curChild = RIFF::getChunk( this, handler ); + + // digest pad byte - no value validation (0), since some 3rd party files have non-0-padding. + if ( file->Offset() % 2 == 1 ) + { + // [1521093] tolerate missing pad byte at very end of file: + XMP_Uns8 pad; + file->Read ( &pad, 1 ); // Read the pad, tolerate being at EOF. + + } + + // within relevant LISTs, relentlesly delete junk chunks (create a single one + // at end as part of updateAndChanges() + if ( (containerType== kType_INFO || containerType == kType_Tdat) + && ( curChild->chunkType == chunk_JUNK ) ) + { + this->children.pop_back(); + delete curChild; + } // for other chunks: join neighouring Junk chunks into one + else if ( (curChild->chunkType == chunk_JUNK) && ( this->children.size() >= 2 ) ) + { + // nb: if there are e.g 2 chunks, then last one is at(1), prev one at(0) ==> '-2' + Chunk* prevChunk = this->children.at( this->children.size() - 2 ); + if ( prevChunk->chunkType == chunk_JUNK ) + { + // stack up size to prior chunk + prevChunk->oldSize += curChild->oldSize; + prevChunk->newSize += curChild->newSize; + XMP_Enforce( prevChunk->oldSize == prevChunk->newSize ); + // destroy current chunk + this->children.pop_back(); + delete curChild; + } + } + } + handler->level--; + XMP_Validate( file->Offset() == endOfChunk, "subchunks exceed outer chunk size", kXMPErr_BadFileFormat ); + + // pointers for later legacy processing + if ( level==1 && this->id==kChunk_LIST && this->containerType == kType_INFO ) + handler->listInfoChunk = this; + if ( level==1 && this->id==kChunk_LIST && this->containerType == kType_Tdat ) + handler->listTdatChunk = this; + if ( level == 1 && this->id == kChunk_LIST && this->containerType == kType_hdrl ) + handler->listHdlrChunk = this; + } + else // skip non-interest container chunk + { + file->Seek ( (this->oldSize - 8 - 4), kXMP_SeekFromCurrent ); + } // if - else + + } // try + catch (XMP_Error& e) { + this->release(); // free resources + if ( this->parent != 0) + this->parent->children.pop_back(); // hereby taken care of, so removing myself... + + throw e; // re-throw + } +} + +void ContainerChunk::changesAndSize( RIFF_MetaHandler* handler ) +{ + + // Walk the container subtree adjusting the children that have size changes. The only containers + // are RIFF and LIST chunks, they are treated differently. + // + // LISTs get recomposed as a whole. Existing JUNK children of a LIST are removed, existing real + // children are left in order with their new size, new children have already been appended. The + // LIST as a whole gets a new size that is the sum of the final children. + // + // Special rules apply to various children of a RIFF container. FIrst, adjacent JUNK children + // are combined, this simplifies maximal reuse. The children are recursively adjusted in order + // to get their final size. + // + // Try to determine the final placement of each RIFF child using general rules: + // - if the size is unchanged: leave at current location + // - if the chunk is at the end of the last RIFF chunk and grows: leave at current location + // - if there is enough following JUNK: add part of the JUNK, adjust remaining JUNK size + // - if it shrinks by 9 bytes or more: carve off trailing JUNK + // - try to find adequate JUNK in the current parent + // + // Use child-specific rules as a last resort: + // - if it is LIST:INFO: delete it, must be in first RIFF chunk + // - for others: move to end of last RIFF chunk, make old space JUNK + + // ! Don't create any junk chunks of exactly 8 bytes, just a header and no content. That has a + // ! size field of zero, which hits a crashing bug in some versions of Windows Media Player. + + bool isRIFFContainer = (this->id == kChunk_RIFF); + bool isLISTContainer = (this->id == kChunk_LIST); + XMP_Enforce ( isRIFFContainer | isLISTContainer ); + + XMP_Index childIndex; // Could be local to the loops, this simplifies debuging. Need a signed type! + Chunk * currChild; + + if ( this->children.empty() ) { + if ( isRIFFContainer) { + this->newSize = 12; // Keep a minimal size container. + } else { + this->newSize = 0; // Will get removed from parent in outer call. + } + this->hasChange = true; + return; // Nothing more to do without children. + } + + // Collapse adjacent RIFF junk children, remove all LIST junk children. Work back to front to + // simplify the effect of .erase() on the loop. Purposely ignore the first chunk. + + for ( childIndex = (XMP_Index)this->children.size() - 1; childIndex > 0; --childIndex ) { + + currChild = this->children[childIndex]; + if ( currChild->chunkType != chunk_JUNK ) continue; + + if ( isRIFFContainer ) { + Chunk * prevChild = this->children[childIndex-1]; + if ( prevChild->chunkType != chunk_JUNK ) continue; + prevChild->oldSize += currChild->oldSize; + prevChild->newSize += currChild->newSize; + prevChild->hasChange = true; + } + + this->children.erase ( this->children.begin() + childIndex ); + delete currChild; + this->hasChange = true; + + } + + // Process the children of RIFF and LIST containers to get their final size. Remove empty + // children. Work back to front to simplify the effect of .erase() on the loop. Do not ignore + // the first chunk. + + for ( childIndex = (XMP_Index)this->children.size() - 1; childIndex >= 0; --childIndex ) { + + currChild = this->children[childIndex]; + + ++handler->level; + currChild->changesAndSize ( handler ); + --handler->level; + + if ( (currChild->newSize == 8) || (currChild->newSize == 0) ) { // ! The newSIze is supposed to include the header. + this->children.erase ( this->children.begin() + childIndex ); + delete currChild; + this->hasChange = true; + } else { + this->hasChange |= currChild->hasChange; + currChild->needSizeFix = (currChild->newSize != currChild->oldSize); + if ( currChild->needSizeFix && (currChild->newSize > currChild->oldSize) && + (this == handler->lastChunk) && (childIndex+1 == (XMP_Index)this->children.size()) ) { + // Let an existing last-in-file chunk grow in-place. Shrinking is conceptually OK, + // but complicates later sanity check that the main AVI chunk is not OK to append + // other chunks later. Ignore new chunks, they might reuse junk space. + if ( currChild->oldSize != 0 ) currChild->needSizeFix = false; + } + } + + } + + // Go through the children of a RIFF container, adjusting the placement as necessary. In brief, + // things can only grow at the end of the last RIFF chunk, and non-junk chunks can't be shifted. + + if ( isRIFFContainer ) { + + for ( childIndex = 0; childIndex < (XMP_Index)this->children.size(); ++childIndex ) { + + currChild = this->children[childIndex]; + if ( ! currChild->needSizeFix ) continue; + currChild->needSizeFix = false; + + XMP_Int64 sizeDiff = currChild->newSize - currChild->oldSize; // Positive for growth. + XMP_Uns8 padSize = (currChild->newSize & 1); // Need a pad for odd size. + + // See if the following chunk is junk that can be utilized. + + Chunk * nextChild = 0; + if ( childIndex+1 < (XMP_Index)this->children.size() ) nextChild = this->children[childIndex+1]; + + if ( (nextChild != 0) && (nextChild->chunkType == chunk_JUNK) ) { + if ( nextChild->newSize >= (9 + sizeDiff + padSize) ) { + + // Incorporate part of the trailing junk, or make the trailing junk grow. + nextChild->newSize -= sizeDiff; + nextChild->newSize -= padSize; + nextChild->hasChange = true; + continue; + + } else if ( nextChild->newSize == (sizeDiff + padSize) ) { + + // Incorporate all of the trailing junk. + this->children.erase ( this->children.begin() + childIndex + 1 ); + delete nextChild; + continue; + + } + } + + // See if the chunk shrinks enough to turn the leftover space into junk. + + if ( (sizeDiff + padSize) <= -9 ) { + this->children.insert ( (this->children.begin() + childIndex + 1), new JunkChunk ( NULL, ((-sizeDiff) - padSize) ) ); + continue; + } + + // Look through the parent for a usable span of junk. + + XMP_Index junkIndex; + Chunk * junkChunk = 0; + for ( junkIndex = 0; junkIndex < (XMP_Index)this->children.size(); ++junkIndex ) { + junkChunk = this->children[junkIndex]; + if ( junkChunk->chunkType != chunk_JUNK ) continue; + if ( (junkChunk->newSize >= (9 + currChild->newSize + padSize)) || + (junkChunk->newSize == (currChild->newSize + padSize)) ) break; + } + + if ( junkIndex < (XMP_Index)this->children.size() ) { + + // Use part or all of the junk for the relocated chunk, replace the old space with junk. + + if ( junkChunk->newSize == (currChild->newSize + padSize) ) { + + // The found junk is an exact fit. + this->children[junkIndex] = currChild; + delete junkChunk; + + } else { + + // The found junk has excess space. Insert the moving chunk and shrink the junk. + XMP_Assert ( junkChunk->newSize >= (9 + currChild->newSize + padSize) ); + junkChunk->newSize -= (currChild->newSize + padSize); + junkChunk->hasChange = true; + this->children.insert ( (this->children.begin() + junkIndex), currChild ); + if ( junkIndex < childIndex ) ++childIndex; // The insertion moved the current child. + + } + + if ( currChild->oldSize != 0 ) { + this->children[childIndex] = new JunkChunk ( 0, currChild->oldSize ); // Replace the old space with junk. + } else { + this->children.erase ( this->children.begin() + childIndex ); // Remove the newly created chunk's old location. + --childIndex; // Make the next loop iteration not skip a chunk. + } + + continue; + + } + + // If this is a LIST:INFO chunk not in the last of multiple RIFF chunks, then give up + // and replace it with oldSize junk. Preserve the first RIFF chunk's original size. + + bool isListInfo = (currChild->id == kChunk_LIST) && (currChild->chunkType == chunk_CONTAINER) && + (((ContainerChunk*)currChild)->containerType == kType_INFO); + + if ( isListInfo && (handler->riffChunks.size() > 1) && + (this->id == kChunk_RIFF) && (this != handler->lastChunk) ) { + + if ( currChild->oldSize != 0 ) { + this->children[childIndex] = new JunkChunk ( 0, currChild->oldSize ); + } else { + this->children.erase ( this->children.begin() + childIndex ); + --childIndex; // Make the next loop iteration not skip a chunk. + } + + delete currChild; + continue; + + } + + // Move the chunk to the end of the last RIFF chunk and make the old space junk. + + if ( (this == handler->lastChunk) && (childIndex+1 == (XMP_Index)this->children.size()) ) continue; // Already last. + + handler->lastChunk->children.push_back( currChild ); + if ( currChild->oldSize != 0 ) { + this->children[childIndex] = new JunkChunk ( 0, currChild->oldSize ); // Replace the old space with junk. + } else { + this->children.erase ( this->children.begin() + childIndex ); // Remove the newly created chunk's old location. + --childIndex; // Make the next loop iteration not skip a chunk. + } + + } + + } + + // Compute the finished container's new size (for both RIFF and LIST). + + this->newSize = 12; // Start with standard container header. + for ( childIndex = 0; childIndex < (XMP_Index)this->children.size(); ++childIndex ) { + currChild = this->children[childIndex]; + this->newSize += currChild->newSize; + this->newSize += (this->newSize & 1); // Round up if odd. + } + + XMP_Validate ( (this->newSize <= 0xFFFFFFFFLL), "No single chunk may be above 4 GB", kXMPErr_Unimplemented ); + +} + +std::string ContainerChunk::toString(XMP_Uns8 level ) +{ + XMP_Int64 offset= 12; // compute offsets, just for informational purposes + // (actually only correct for first chunk) + + char buffer[256]; + snprintf( buffer, 255, "%.4s:%.4s, " + "oldSize: 0x%8llX, " + "newSize: 0x%.8llX, " + "oldPos: 0x%.8llX\n", + (char*)(&this->id), (char*)(&this->containerType), this->oldSize, this->newSize, this->oldPos ); + + std::string r(buffer); + chunkVectIter iter; + for( iter = this->children.begin(); iter != this->children.end(); iter++ ) + { + char buffer[256]; + snprintf( buffer, 250, "offset 0x%.8llX", offset ); + r += std::string ( level*4, ' ' ) + std::string( buffer ) + ":" + (*iter)->toString( level + 1 ); + offset += (*iter)->newSize; + if ( offset % 2 == 1 ) + offset++; + } + return std::string(r); +} + +void ContainerChunk::write( RIFF_MetaHandler* handler, XMP_IO* file, bool isMainChunk ) +{ + if ( isMainChunk ) + file ->Rewind(); + + // enforce even position + XMP_Int64 chunkStart = file->Offset(); + XMP_Int64 chunkEnd = chunkStart + this->newSize; + XMP_Enforce( chunkStart % 2 == 0 ); + chunkVect *rc = &this->children; + + // [2473303] have to write back-to-front to avoid stomp-on-feet + XMP_Int64 childStart = chunkEnd; + for ( XMP_Int32 chunkNo = (XMP_Int32)(rc->size() -1); chunkNo >= 0; chunkNo-- ) + { + Chunk* cur = rc->at(chunkNo); + + // pad byte first + if ( cur->newSize % 2 == 1 ) + { + childStart--; + file->Seek ( childStart, kXMP_SeekFromStart ); + XIO::WriteUns8( file, 0 ); + } + + // then contents + childStart-= cur->newSize; + file->Seek ( childStart, kXMP_SeekFromStart ); + switch ( cur->chunkType ) + { + case chunk_GENERAL: //COULDDO enfore no change, since not write-out-able + if ( cur->oldPos != childStart ) + XIO::Move( file, cur->oldPos, file, childStart, cur->oldSize ); + break; + default: + cur->write( handler, file, false ); + break; + } // switch + + } // for + XMP_Enforce ( chunkStart + 12 == childStart); + file->Seek ( chunkStart, kXMP_SeekFromStart ); + + XIO::WriteUns32_LE( file, this->id ); + XIO::WriteUns32_LE( file, (XMP_Uns32) this->newSize - 8 ); // validated in changesAndSize() above + XIO::WriteUns32_LE( file, this->containerType ); + +} + +void ContainerChunk::release() +{ + // free subchunks + Chunk* curChunk; + while( ! this->children.empty() ) + { + curChunk = this->children.back(); + delete curChunk; + this->children.pop_back(); + } +} + +ContainerChunk::~ContainerChunk() +{ + this->release(); // free resources +} + +// XMP CHUNK /////////////////////////////////////////////// +// a) create + +// a) creation +XMPChunk::XMPChunk( ContainerChunk* parent ) : Chunk( parent, chunk_XMP , kChunk_XMP ) +{ + // nothing +} + +// b) parse +XMPChunk::XMPChunk( ContainerChunk* parent, RIFF_MetaHandler* handler ) : Chunk( parent, handler, false, chunk_XMP ) +{ + chunkType = chunk_XMP; + XMP_IO* file = handler->parent->ioRef; + XMP_Uns8 level = handler->level; + + handler->packetInfo.offset = this->oldPos + 8; + handler->packetInfo.length = (XMP_Int32) this->oldSize - 8; + + handler->xmpPacket.reserve ( handler->packetInfo.length ); + handler->xmpPacket.assign ( handler->packetInfo.length, ' ' ); + file->ReadAll ( (void*)handler->xmpPacket.data(), handler->packetInfo.length ); + + handler->containsXMP = true; // last, after all possible failure + + // pointer for later processing + handler->xmpChunk = this; +} + +void XMPChunk::changesAndSize( RIFF_MetaHandler* handler ) +{ + XMP_Enforce( &handler->xmpPacket != 0 ); + XMP_Enforce( handler->xmpPacket.size() > 0 ); + this->newSize = 8 + handler->xmpPacket.size(); + + XMP_Validate( this->newSize <= 0xFFFFFFFFLL, "no single chunk may be above 4 GB", kXMPErr_InternalFailure ); + + // a complete no-change would have been caught in XMPFiles common code anyway + this->hasChange = true; +} + +void XMPChunk::write( RIFF_MetaHandler* handler, XMP_IO* file, bool isMainChunk ) +{ + XIO::WriteUns32_LE( file, kChunk_XMP ); + XIO::WriteUns32_LE( file, (XMP_Uns32) this->newSize - 8 ); // validated in changesAndSize() above + file->Write ( handler->xmpPacket.data(), (XMP_Int32)handler->xmpPacket.size() ); +} + +// Value CHUNK /////////////////////////////////////////////// +// a) creation +ValueChunk::ValueChunk( ContainerChunk* parent, std::string value, XMP_Uns32 id ) : Chunk( parent, chunk_VALUE, id ) +{ + this->oldValue = std::string(); + this->SetValue( value ); +} + +// b) parsing +ValueChunk::ValueChunk( ContainerChunk* parent, RIFF_MetaHandler* handler ) : Chunk( parent, handler, false, chunk_VALUE ) +{ + // set value: ----------------- + XMP_IO* file = handler->parent->ioRef; + XMP_Uns8 level = handler->level; + + // unless changed through reconciliation, assume for now. + // IMPORTANT to stay true to the original (no \0 cleanup or similar) + // since unknown value chunks might not be fully understood, + // hence must be precisely preserved !!! + + XMP_Int32 length = (XMP_Int32) this->oldSize - 8; + this->oldValue.reserve( length ); + this->oldValue.assign( length + 1, '\0' ); + file->ReadAll ( (void*)this->oldValue.data(), length ); + + this->newValue = this->oldValue; + this->newSize = this->oldSize; +} + +void ValueChunk::SetValue( std::string value, bool optionalNUL /* = false */ ) +{ + this->newValue.assign( value ); + if ( (! optionalNUL) || ((value.size() & 1) == 1) ) { + // ! The NUL should be optional in WAV to avoid a parsing bug in Audition 3 - can't handle implicit pad byte. + this->newValue.append( 1, '\0' ); // append zero termination as explicit part of string + } + this->newSize = this->newValue.size() + 8; +} + +void ValueChunk::changesAndSize( RIFF_MetaHandler* handler ) +{ + // Don't simply assign to this->hasChange, it might already be true. + if ( this->newValue.size() != this->oldValue.size() ) { + this->hasChange = true; + } else if ( strncmp ( this->oldValue.c_str(), this->newValue.c_str(), this->newValue.size() ) != 0 ) { + this->hasChange = true; + } +} + +void ValueChunk::write( RIFF_MetaHandler* handler, XMP_IO* file, bool isMainChunk ) +{ + XIO::WriteUns32_LE( file, this->id ); + XIO::WriteUns32_LE( file, (XMP_Uns32)this->newSize - 8 ); + file->Write ( this->newValue.data() , (XMP_Int32)this->newSize - 8 ); +} + +/* remove value chunk if existing. + return true if it was existing. */ +bool ContainerChunk::removeValue( XMP_Uns32 id ) +{ + valueMap* cm = &this->childmap; + valueMapIter iter = cm->find( id ); + + if( iter == cm->end() ) + return false; //not found + + ValueChunk* propChunk = iter->second; + + // remove from vector (difficult) + chunkVect* cv = &this->children; + chunkVectIter cvIter; + for (cvIter = cv->begin(); cvIter != cv->end(); ++cvIter ) + { + if ( (*cvIter)->id == id ) + break; // found! + } + XMP_Validate( cvIter != cv->end(), "property not found in children vector", kXMPErr_InternalFailure ); + cv->erase( cvIter ); + + // remove from map (easy) + cm->erase( iter ); + + delete propChunk; + return true; // found and removed +} + +/* returns iterator to (first) occurence of this chunk. + iterator to the end of the map if chunk pointer is not found */ +chunkVectIter ContainerChunk::getChild( Chunk* needle ) +{ + chunkVectIter iter; + for( iter = this->children.begin(); iter != this->children.end(); iter++ ) + { + if ( (*iter) == needle ) return iter; + } + return this->children.end(); +} + +/* replaces a chunk by a JUNK chunk. + Also frees memory of prior chunk. */ +void ContainerChunk::replaceChildWithJunk( Chunk* child, bool deleteChild ) +{ + chunkVectIter iter = getChild( child ); + if ( iter == this->children.end() ) { + throw new XMP_Error(kXMPErr_InternalFailure, "replaceChildWithJunk: childChunk not found."); + } + + *iter = new JunkChunk ( NULL, child->oldSize ); + if ( deleteChild ) delete child; + + this->hasChange = true; +} + +// JunkChunk /////////////////////////////////////////////////// +// a) creation +JunkChunk::JunkChunk( ContainerChunk* parent, XMP_Int64 size ) : Chunk( parent, chunk_JUNK, kChunk_JUNK ) +{ + XMP_Assert( size >= 8 ); + this->oldSize = size; + this->newSize = size; + this->hasChange = true; +} + +// b) parsing +JunkChunk::JunkChunk( ContainerChunk* parent, RIFF_MetaHandler* handler ) : Chunk( parent, handler, true, chunk_JUNK ) +{ + chunkType = chunk_JUNK; +} + +void JunkChunk::changesAndSize( RIFF_MetaHandler* handler ) +{ + this->newSize = this->oldSize; // optimization at a later stage + XMP_Validate( this->newSize <= 0xFFFFFFFFLL, "no single chunk may be above 4 GB", kXMPErr_InternalFailure ); + if ( this->id == kChunk_JUNQ ) this->hasChange = true; // Force ID change to JUNK. +} + +// zeroBuffer, etc to write out empty native padding +const static XMP_Uns32 kZeroBufferSize64K = 64 * 1024; +static XMP_Uns8 kZeroes64K [ kZeroBufferSize64K ]; // C semantics guarantee zero initialization. + +void JunkChunk::write( RIFF_MetaHandler* handler, XMP_IO* file, bool isMainChunk ) +{ + XIO::WriteUns32_LE( file, kChunk_JUNK ); // write JUNK, never JUNQ + XMP_Enforce( this->newSize < 0xFFFFFFFF ); + XMP_Enforce( this->newSize >= 8 ); // minimum size of any chunk + XMP_Uns32 innerSize = (XMP_Uns32)this->newSize - 8; + XIO::WriteUns32_LE( file, innerSize ); + + // write out in 64K chunks + while ( innerSize > kZeroBufferSize64K ) + { + file->Write ( kZeroes64K , kZeroBufferSize64K ); + innerSize -= kZeroBufferSize64K; + } + file->Write ( kZeroes64K , innerSize ); +} + +} // namespace RIFF diff --git a/core/libs/dngwriter/extra/xmp_sdk/XMPFiles/source/FormatSupport/RIFF.hpp b/core/libs/dngwriter/extra/xmp_sdk/XMPFiles/source/FormatSupport/RIFF.hpp new file mode 100644 index 0000000000..4bb1f6e0e1 --- /dev/null +++ b/core/libs/dngwriter/extra/xmp_sdk/XMPFiles/source/FormatSupport/RIFF.hpp @@ -0,0 +1,331 @@ +#ifndef __RIFF_hpp__ +#define __RIFF_hpp__ 1 + +// ================================================================================================= +// ADOBE SYSTEMS INCORPORATED +// Copyright 2009 Adobe Systems Incorporated +// All Rights Reserved +// +// NOTICE: Adobe permits you to use, modify, and distribute this file in accordance with the terms +// of the Adobe license agreement accompanying it. +// ================================================================================================= + +#include "public/include/XMP_Environment.h" // ! XMP_Environment.h must be the first included header. + +#include "public/include/XMP_Const.h" +#include "public/include/XMP_IO.hpp" + +#include "XMPFiles/source/XMPFiles_Impl.hpp" +#include "source/XMPFiles_IO.hpp" + +#include +#include + +// ahead declaration: +class RIFF_MetaHandler; + +namespace RIFF { + + enum ChunkType { + chunk_GENERAL, //unknown or not relevant + chunk_CONTAINER, + chunk_XMP, + chunk_VALUE, + chunk_JUNK, + NO_CHUNK // used as precessor to first chunk, etc. + }; + + // ahead declarations + class Chunk; + class ContainerChunk; + class ValueChunk; + class XMPChunk; + + // (scope: only used in RIFF_Support and RIFF_Handler.cpp + // ==> no need to overspecify with lengthy names ) + + typedef std::vector chunkVect; // coulddo: narrow down toValueChunk (could give trouble with JUNK though) + typedef chunkVect::iterator chunkVectIter; // or refactor ?? + + typedef std::vector containerVect; + typedef containerVect::iterator containerVectIter; + + typedef std::map valueMap; + typedef valueMap::iterator valueMapIter; + + + // format chunks+types + const XMP_Uns32 kChunk_RIFF = 0x46464952; + const XMP_Uns32 kType_AVI_ = 0x20495641; + const XMP_Uns32 kType_AVIX = 0x58495641; + const XMP_Uns32 kType_WAVE = 0x45564157; + + const XMP_Uns32 kChunk_JUNK = 0x4B4E554A; + const XMP_Uns32 kChunk_JUNQ = 0x514E554A; + + // other container chunks + const XMP_Uns32 kChunk_LIST = 0x5453494C; + const XMP_Uns32 kType_INFO = 0x4F464E49; + const XMP_Uns32 kType_Tdat = 0x74616454; + + // other relevant chunks + const XMP_Uns32 kChunk_XMP = 0x584D505F; // "_PMX" + const XMP_Uns32 kChunk_IDIT = 0x54494449; // "TIDI" + + // relevant for Index Correction + // LIST: + const XMP_Uns32 kType_hdrl = 0x6C726468; + const XMP_Uns32 kType_strl = 0x6C727473; + const XMP_Uns32 kChunk_indx = 0x78646E69; + const XMP_Uns32 kChunk_ixXX = 0x58587869; + const XMP_Uns32 kType_movi = 0x69766F6D; + + //should occur only in AVI + const XMP_Uns32 kChunk_Cr8r = 0x72387243; + const XMP_Uns32 kChunk_PrmL = 0x4C6D7250; + + //should occur only in WAV + const XMP_Uns32 kChunk_DISP = 0x50534944; + const XMP_Uns32 kChunk_bext = 0x74786562; + + // LIST/INFO constants + const XMP_Uns32 kPropChunkIART = 0x54524149; + const XMP_Uns32 kPropChunkICMT = 0x544D4349; + const XMP_Uns32 kPropChunkICOP = 0x504F4349; + const XMP_Uns32 kPropChunkICRD = 0x44524349; + const XMP_Uns32 kPropChunkIENG = 0x474E4549; + const XMP_Uns32 kPropChunkIGNR = 0x524E4749; + const XMP_Uns32 kPropChunkINAM = 0x4D414E49; + const XMP_Uns32 kPropChunkISFT = 0x54465349; + const XMP_Uns32 kPropChunkIARL = 0x4C524149; + + const XMP_Uns32 kPropChunkIMED = 0x44454D49; + const XMP_Uns32 kPropChunkISRF = 0x46525349; + const XMP_Uns32 kPropChunkICMS = 0x4C524149; + const XMP_Uns32 kPropChunkIPRD = 0x534D4349; + const XMP_Uns32 kPropChunkISRC = 0x44525049; + const XMP_Uns32 kPropChunkITCH = 0x43525349; + + const XMP_Uns32 kPropChunk_tc_O =0x4F5F6374; + const XMP_Uns32 kPropChunk_tc_A =0x415F6374; + const XMP_Uns32 kPropChunk_rn_O =0x4F5F6E72; + const XMP_Uns32 kPropChunk_rn_A =0x415F6E72; + + /////////////////////////////////////////////////////////////// + + enum PropType { // from a simplified, opinionated legacy angle + prop_SIMPLE, + prop_TIMEVALUE, + prop_LOCALIZED_TEXT, + prop_ARRAYITEM, // ( here: a solitary one) + }; + + struct Mapping { + XMP_Uns32 chunkID; + const char* ns; + const char* prop; + PropType propType; + }; + + // bext Mappings, piece-by-piece: + static Mapping bextDescription = { 0, kXMP_NS_BWF, "description", prop_SIMPLE }; + static Mapping bextOriginator = { 0, kXMP_NS_BWF, "originator", prop_SIMPLE }; + static Mapping bextOriginatorRef = { 0, kXMP_NS_BWF, "originatorReference", prop_SIMPLE }; + static Mapping bextOriginationDate = { 0, kXMP_NS_BWF, "originationDate", prop_SIMPLE }; + static Mapping bextOriginationTime = { 0, kXMP_NS_BWF, "originationTime", prop_SIMPLE }; + static Mapping bextTimeReference = { 0, kXMP_NS_BWF, "timeReference", prop_SIMPLE }; + static Mapping bextVersion = { 0, kXMP_NS_BWF, "version", prop_SIMPLE }; + static Mapping bextUMID = { 0, kXMP_NS_BWF, "umid", prop_SIMPLE }; + static Mapping bextCodingHistory = { 0, kXMP_NS_BWF, "codingHistory", prop_SIMPLE }; + + // LIST:INFO properties + static Mapping listInfoProps[] = { + // reconciliations CS4 and before: + { kPropChunkIART, kXMP_NS_DM, "artist" , prop_SIMPLE }, + { kPropChunkICMT, kXMP_NS_DM, "logComment" , prop_SIMPLE }, + { kPropChunkICOP, kXMP_NS_DC, "rights" , prop_LOCALIZED_TEXT }, + { kPropChunkICRD, kXMP_NS_XMP, "CreateDate" , prop_SIMPLE }, + { kPropChunkIENG, kXMP_NS_DM, "engineer" , prop_SIMPLE }, + { kPropChunkIGNR, kXMP_NS_DM, "genre" , prop_SIMPLE }, + { kPropChunkINAM, kXMP_NS_DC, "title" , prop_LOCALIZED_TEXT }, // ( was wrongly dc:album in pre-CS4) + { kPropChunkISFT, kXMP_NS_XMP, "CreatorTool", prop_SIMPLE }, + + // RIFF/*/LIST/INFO properties, new in CS5, both AVI and WAV + + { kPropChunkIMED, kXMP_NS_DC, "source" , prop_SIMPLE }, + { kPropChunkISRF, kXMP_NS_DC, "type" , prop_ARRAYITEM }, + // TO ENABLE { kPropChunkIARL, kXMP_NS_DC, "subject" , prop_SIMPLE }, // array !! (not x-default language alternative) + //{ kPropChunkICMS, to be decided, "" , prop_SIMPLE }, + //{ kPropChunkIPRD, to be decided, "" , prop_SIMPLE }, + //{ kPropChunkISRC, to be decided, "" , prop_SIMPLE }, + //{ kPropChunkITCH, to be decided, "" , prop_SIMPLE }, + + { 0, 0, 0 } // sentinel + }; + + static Mapping listTdatProps[] = { + // reconciliations CS4 and before: + { kPropChunk_tc_O, kXMP_NS_DM, "startTimecode" , prop_TIMEVALUE }, // special: must end up in dm:timeValue child + { kPropChunk_tc_A, kXMP_NS_DM, "altTimecode" , prop_TIMEVALUE }, // special: must end up in dm:timeValue child + { kPropChunk_rn_O, kXMP_NS_DM, "tapeName" , prop_SIMPLE }, + { kPropChunk_rn_A, kXMP_NS_DM, "altTapeName" , prop_SIMPLE }, + { 0, 0, 0 } // sentinel + }; + + // ================================================================================================= + // ImportCr8rItems + // =============== +#if SUNOS_SPARC || SUNOS_X86 + #pragma pack ( 1 ) +#else + #pragma pack ( push, 1 ) +#endif //#if SUNOS_SPARC || SUNOS_X86 + struct PrmLBoxContent { + XMP_Uns32 magic; + XMP_Uns32 size; + XMP_Uns16 verAPI; + XMP_Uns16 verCode; + XMP_Uns32 exportType; + XMP_Uns16 MacVRefNum; + XMP_Uns32 MacParID; + char filePath[260]; + }; + + enum { kExportTypeMovie = 0, kExportTypeStill = 1, kExportTypeAudio = 2, kExportTypeCustom = 3 }; + + struct Cr8rBoxContent { + XMP_Uns32 magic; + XMP_Uns32 size; + XMP_Uns16 majorVer; + XMP_Uns16 minorVer; + XMP_Uns32 creatorCode; + XMP_Uns32 appleEvent; + char fileExt[16]; + char appOptions[16]; + char appName[32]; + }; +#if SUNOS_SPARC || SUNOS_X86 + #pragma pack ( ) +#else + #pragma pack ( pop ) +#endif //#if SUNOS_SPARC || SUNOS_X86 + + // static getter, determines appropriate chunkType (peeking)and returns + // the respective constructor. It's the caller's responsibility to + // delete obtained chunk. + Chunk* getChunk ( ContainerChunk* parent, RIFF_MetaHandler* handler ); + + class Chunk + { + public: + ChunkType chunkType; // set by constructor + ContainerChunk* parent; // 0 on top-level + + XMP_Uns32 id; // the first four bytes, first byte of highest value + XMP_Int64 oldSize; // actual chunk size INCLUDING the 8/12 header bytes, + XMP_Int64 oldPos; // file position of this chunk + + // both set as part of changesAndSize() + XMP_Int64 newSize; + bool hasChange; + bool needSizeFix; // used in changesAndSize() only + + // Constructors /////////////////////// + // parsing + Chunk( ContainerChunk* parent, RIFF_MetaHandler* handler, bool skip, ChunkType c /*= chunk_GENERAL*/ ); + // ad-hoc creation + Chunk( ContainerChunk* parent, ChunkType c, XMP_Uns32 id ); + + /* returns true, if something has changed in chunk (which needs specific write-out, + this->newSize is expected to be set by this routine */ + virtual void changesAndSize( RIFF_MetaHandler* handler ); + virtual std::string toString(XMP_Uns8 level = 0); + virtual void write( RIFF_MetaHandler* handler, XMP_IO* file, bool isMainChunk = false ); + + virtual ~Chunk(); + + }; // class Chunk + + class XMPChunk : public Chunk + { + public: + XMPChunk( ContainerChunk* parent ); + XMPChunk( ContainerChunk* parent, RIFF_MetaHandler* handler ); + + void changesAndSize( RIFF_MetaHandler* handler ); + void write( RIFF_MetaHandler* handler, XMP_IO* file, bool isMainChunk = false ); + + }; + + // any chunk, whose value should be stored, e.g. LIST:INFO, LIST:Tdat + class ValueChunk : public Chunk + { + public: + std::string oldValue, newValue; + + // for ad-hoc creation (upon write) + ValueChunk( ContainerChunk* parent, std::string value, XMP_Uns32 id ); + + // for parsing + ValueChunk( ContainerChunk* parent, RIFF_MetaHandler* handler ); + + enum { kNULisOptional = true }; + + void SetValue( std::string value, bool optionalNUL = false ); + void changesAndSize( RIFF_MetaHandler* handler ); + void write( RIFF_MetaHandler* handler, XMP_IO* file, bool isMainChunk = false ); + + // own destructor not needed. + }; + + // relevant (level 1) JUNQ and JUNK chunks... + class JunkChunk : public Chunk + { + public: + // construction + JunkChunk( ContainerChunk* parent, XMP_Int64 size ); + // parsing + JunkChunk( ContainerChunk* parent, RIFF_MetaHandler* handler ); + + // own destructor not needed. + + void changesAndSize( RIFF_MetaHandler* handler ); + void write( RIFF_MetaHandler* handler, XMP_IO* file, bool isMainChunk = false ); + }; + + + class ContainerChunk : public Chunk + { + public: + XMP_Uns32 containerType; // e.g. kType_INFO as in "LIST:INFO" + + chunkVect children; // used for cleanup/destruction, ordering... + valueMap childmap; // only for efficient *value* access (inside LIST), *not* used for other containers + + // construct + ContainerChunk( ContainerChunk* parent, XMP_Uns32 id, XMP_Uns32 containerType ); + // parse + ContainerChunk( ContainerChunk* parent, RIFF_MetaHandler* handler ); + + bool removeValue( XMP_Uns32 id ); + + /* returns iterator to (first) occurence of this chunk. + iterator to the end of the map if chunk pointer is not found */ + chunkVectIter getChild( Chunk* needle ); + + void replaceChildWithJunk( Chunk* child, bool deleteChild = true ); + + void changesAndSize( RIFF_MetaHandler* handler ); + std::string toString(XMP_Uns8 level = 0); + void write( RIFF_MetaHandler* handler, XMP_IO* file, bool isMainChunk = false ); + + // destroy + void release(); // used by destructor and on error in constructor + ~ContainerChunk(); + + }; // class ContainerChunk + +} // namespace RIFF + + +#endif // __RIFF_hpp__ diff --git a/core/libs/dngwriter/extra/xmp_sdk/XMPFiles/source/FormatSupport/RIFF_Support.cpp b/core/libs/dngwriter/extra/xmp_sdk/XMPFiles/source/FormatSupport/RIFF_Support.cpp new file mode 100644 index 0000000000..a64c98eae9 --- /dev/null +++ b/core/libs/dngwriter/extra/xmp_sdk/XMPFiles/source/FormatSupport/RIFF_Support.cpp @@ -0,0 +1,1269 @@ +// ================================================================================================= +// ADOBE SYSTEMS INCORPORATED +// Copyright 2008 Adobe Systems Incorporated +// All Rights Reserved +// +// NOTICE: Adobe permits you to use, modify, and distribute this file in accordance with the terms +// of the Adobe license agreement accompanying it. +// ================================================================================================= + +#include "public/include/XMP_Environment.h" // ! XMP_Environment.h must be the first included header. + +#include "public/include/XMP_Const.h" +#include "public/include/XMP_IO.hpp" + +#include "XMPFiles/source/XMPFiles_Impl.hpp" +#include "source/XMPFiles_IO.hpp" +#include "source/XIO.hpp" + +// must have access to handler class fields... +#include "XMPFiles/source/FormatSupport/RIFF.hpp" +#include "XMPFiles/source/FileHandlers/RIFF_Handler.hpp" +#include "XMPFiles/source/FormatSupport/RIFF_Support.hpp" +#include "XMPFiles/source/FormatSupport/Reconcile_Impl.hpp" + +#include + +#define MIN(a, b) ((a) < (b) ? (a) : (b)) + +using namespace RIFF; +namespace RIFF { + +// The minimum BEXT chunk size should be 610 (incl. 8 byte header/size field) +XMP_Int32 MIN_BEXT_SIZE = 610; // = > 8 + ( 256+32+32+10+8+4+4+2+64+190+0 ) + +// An assumed secure max value of 100 MB. +XMP_Int32 MAX_BEXT_SIZE = 100 * 1024 * 1024; + +// CR8R, PrmL have fixed sizes +XMP_Int32 CR8R_SIZE = 0x5C; +XMP_Int32 PRML_SIZE = 0x122; + +// IDIT chunk size +XMP_Int32 IDIT_SIZE = 0x1A; + +static const char* sHexChars = "0123456789ABCDEF"; + +// Encode a string of raw data bytes into a HexString (w/o spaces, i.e. "DEADBEEF"). +// No insertation/acceptance of whitespace/linefeeds. No output/tolerance of lowercase. +// returns true, if *all* characters returned are zero (or if 0 bytes are returned). +static bool EncodeToHexString ( XMP_StringPtr rawStr, + XMP_StringLen rawLen, + std::string* encodedStr ) +{ + bool allZero = true; // assume for now + + if ( (rawStr == 0) && (rawLen != 0) ) + XMP_Throw ( "EncodeToHexString: null rawStr", kXMPErr_BadParam ); + if ( encodedStr == 0 ) + XMP_Throw ( "EncodeToHexString: null encodedStr", kXMPErr_BadParam ); + + encodedStr->erase(); + if ( rawLen == 0 ) return allZero; + encodedStr->reserve ( rawLen * 2 ); + + for( XMP_Uns32 i = 0; i < rawLen; i++ ) + { + // first, second nibble + XMP_Uns8 first = rawStr[i] >> 4; + XMP_Uns8 second = rawStr[i] & 0xF; + + if ( allZero && (( first != 0 ) || (second != 0))) + allZero = false; + + encodedStr->append( 1, sHexChars[first] ); + encodedStr->append( 1, sHexChars[second] ); + } + + return allZero; +} // EncodeToHexString + +// ------------------------------------------------------------------------------------------------- +// DecodeFromHexString +// ---------------- +// +// Decode a hex string to raw data bytes. +// * Input must be all uppercase and w/o any whitespace, strictly (0-9A-Z)* (i.e. "DEADBEEF0099AABC") +// * No insertation/acceptance of whitespace/linefeeds. +// * bNo use/tolerance of lowercase. +// * Number of bytes in the encoded String must be even. +// * returns true if everything went well, false if illegal (non 0-9A-F) character encountered + +static bool DecodeFromHexString ( XMP_StringPtr encodedStr, + XMP_StringLen encodedLen, + std::string* rawStr ) +{ + if ( (encodedLen % 2) != 0 ) + return false; + rawStr->erase(); + if ( encodedLen == 0 ) return true; + rawStr->reserve ( encodedLen / 2 ); + + for( XMP_Uns32 i = 0; i < encodedLen; ) + { + XMP_Uns8 upperNibble = encodedStr[i]; + if ( (upperNibble < 48) || ( (upperNibble > 57 ) && ( upperNibble < 65 ) ) || (upperNibble > 70) ) + return false; + if ( upperNibble >= 65 ) + upperNibble -= 7; // shift A-F area adjacent to 0-9 + upperNibble -= 48; // 'shift' to a value [0..15] + upperNibble = ( upperNibble << 4 ); + i++; + + XMP_Uns8 lowerNibble = encodedStr[i]; + if ( (lowerNibble < 48) || ( (lowerNibble > 57 ) && ( lowerNibble < 65 ) ) || (lowerNibble > 70) ) + return false; + if ( lowerNibble >= 65 ) + lowerNibble -= 7; // shift A-F area adjacent to 0-9 + lowerNibble -= 48; // 'shift' to a value [0..15] + i++; + + rawStr->append ( 1, (upperNibble + lowerNibble) ); + } + return true; +} // DecodeFromHexString + +// Converts input string to an ascii output string +// - terminates at first 0 +// - replaces all non ascii with 0x3F ('?') +// - produces up to maxOut characters (note that several UTF-8 character bytes can 'melt' to one byte '?' in ascii.) +static XMP_StringLen convertToASCII( XMP_StringPtr input, XMP_StringLen inputLen, std::string* output, XMP_StringLen maxOutputLen ) +{ + if ( (input == 0) && (inputLen != 0) ) + XMP_Throw ( "convertToASCII: null input string", kXMPErr_BadParam ); + if ( output == 0) + XMP_Throw ( "convertToASCII: null output string", kXMPErr_BadParam ); + if ( maxOutputLen == 0) + XMP_Throw ( "convertToASCII: zero maxOutputLen chars", kXMPErr_BadParam ); + + output->reserve(inputLen); + output->erase(); + + bool isUTF8 = ReconcileUtils::IsUTF8( input, inputLen ); + XMP_StringLen outputLen = 0; + + for ( XMP_Uns32 i=0; i < inputLen; i++ ) + { + XMP_Uns8 c = (XMP_Uns8) input[i]; + if ( c == 0 ) // early 0 termination, leave. + break; + if ( c > 127 ) // uft-8 multi-byte sequence. + { + if ( isUTF8 ) // skip all high bytes + { + // how many bytes in this ? + if ( c >= 0xC2 && c <= 0xDF ) + i+=1; // 2-byte sequence + else if ( c >= 0xE0 && c <= 0xEF ) + i+=2; // 3-byte sequence + else if ( c >= 0xF0 && c <= 0xF4 ) + i+=3; // 4-byte sequence + else + continue; //invalid sequence, look for next 'low' byte .. + } // thereafter and 'else': just append a question mark: + output->append( 1, '?' ); + } + else // regular valid ascii. 1 byte. + { + output->append( 1, input[i] ); + } + outputLen++; + if ( outputLen >= maxOutputLen ) + break; // (may be even or even greater due to UFT-8 multi-byte jumps) + } + + return outputLen; +} + +/** + * ensures that native property gets returned as UTF-8 (may or mayn not already be UTF-8) + * - also takes care of "moot padding" (pre-mature zero termination) + * - propertyExists: it is important to know if there as an existing, non zero property + * even (in the event of serverMode) it is not actually returned, but an empty string instead. + */ +static std::string nativePropertyToUTF8 ( XMP_StringPtr cstring, XMP_StringLen maxSize, bool* propertyExists ) +{ + // the value might be properly 0-terminated, prematurely or not + // at all, hence scan through to find actual size + XMP_StringLen size = 0; + for ( size = 0; size < maxSize; size++ ) + { + if ( cstring[size] == 0 ) + break; + } + + (*propertyExists) = ( size > 0 ); + + std::string utf8(""); + if ( ReconcileUtils::IsUTF8( cstring, size ) ) + utf8 = std::string( cstring, size ); //use utf8 directly + else + { + if ( ! ignoreLocalText ) + { + #if ! UNIX_ENV // n/a anyway, since always ignoreLocalText on Unix + ReconcileUtils::LocalToUTF8( cstring, size, &utf8 ); + #endif + } + } + return utf8; +} + +// reads maxSize bytes from file (not "up to", exactly fullSize) +// puts it into a string, sets respective tree property +static std::string getBextField ( const char* data, XMP_Uns32 offset, XMP_Uns32 maxSize ) +{ + if (data == 0) + XMP_Throw ( "getBextField: null data pointer", kXMPErr_BadParam ); + if ( maxSize == 0) + XMP_Throw ( "getBextField: maxSize must be greater than 0", kXMPErr_BadParam ); + + std::string r; + convertToASCII( data+offset, maxSize, &r, maxSize ); + return r; +} + +static void importBextChunkToXMP( RIFF_MetaHandler* handler, ValueChunk* bextChunk ) +{ + // if there's a bext chunk, there is data... + handler->containsXMP = true; // very important for treatment on caller level + + XMP_Enforce( bextChunk->oldSize >= MIN_BEXT_SIZE ); + XMP_Enforce( bextChunk->oldSize < MAX_BEXT_SIZE ); + + const char* data = bextChunk->oldValue.data(); + std::string value; + + // register bext namespace: + SXMPMeta::RegisterNamespace( kXMP_NS_BWF, "bext:", 0 ); + + // bextDescription ------------------------------------------------ + value = getBextField( data, 0, 256 ); + if ( value.size() > 0 ) + handler->xmpObj.SetProperty( bextDescription.ns, bextDescription.prop, value.c_str() ); + + // bextOriginator ------------------------------------------------- + value = getBextField( data, 256, 32 ); + if ( value.size() > 0 ) + handler->xmpObj.SetProperty( bextOriginator.ns , bextOriginator.prop, value.c_str() ); + + // bextOriginatorRef ---------------------------------------------- + value = getBextField( data, 256+32, 32 ); + if ( value.size() > 0 ) + handler->xmpObj.SetProperty( bextOriginatorRef.ns , bextOriginatorRef.prop, value.c_str() ); + + // bextOriginationDate -------------------------------------------- + value = getBextField( data, 256+32+32, 10 ); + if ( value.size() > 0 ) + handler->xmpObj.SetProperty( bextOriginationDate.ns , bextOriginationDate.prop, value.c_str() ); + + // bextOriginationTime -------------------------------------------- + value = getBextField( data, 256+32+32+10, 8 ); + if ( value.size() > 0 ) + handler->xmpObj.SetProperty( bextOriginationTime.ns , bextOriginationTime.prop, value.c_str() ); + + // bextTimeReference ---------------------------------------------- + // thanx to nice byte order, all 8 bytes can be read as one: + XMP_Uns64 timeReferenceFull = GetUns64LE( &(data[256+32+32+10+8 ] ) ); + value.erase(); + SXMPUtils::ConvertFromInt64( timeReferenceFull, "%llu", &value ); + handler->xmpObj.SetProperty( bextTimeReference.ns, bextTimeReference.prop, value ); + + // bextVersion ---------------------------------------------------- + XMP_Uns16 bwfVersion = GetUns16LE( &(data[256+32+32+10+8+8] ) ); + value.erase(); + SXMPUtils::ConvertFromInt( bwfVersion, "", &value ); + handler->xmpObj.SetProperty( bextVersion.ns, bextVersion.prop, value ); + + // bextUMID ------------------------------------------------------- + // binary string is already in memory, must convert to hex string + std::string umidString; + bool allZero = EncodeToHexString( &(data[256+32+32+10+8+8+2]), 64, &umidString ); + if (! allZero ) + handler->xmpObj.SetProperty( bextUMID.ns, bextUMID.prop, umidString ); + + // bextCodingHistory ---------------------------------------------- + bool hasCodingHistory = bextChunk->oldSize > MIN_BEXT_SIZE; + + if ( hasCodingHistory ) + { + XMP_StringLen codingHistorySize = (XMP_StringLen) (bextChunk->oldSize - MIN_BEXT_SIZE); + std::string codingHistory; + convertToASCII( &data[MIN_BEXT_SIZE-8], codingHistorySize, &codingHistory, codingHistorySize ); + if (! codingHistory.empty() ) + handler->xmpObj.SetProperty( bextCodingHistory.ns, bextCodingHistory.prop, codingHistory ); + } +} // importBextChunkToXMP + +static XMP_Int32 GetMonth( const char * valuePtr ) +{ + // This will check 3 characters (4,5,6) of the input and return corresponding months as 1,2,...,12 + // else it will return 0 + // Input should as follows : wda mon dd hh:mm:ss yyyy\n\0 + char firstChar = tolower( valuePtr[ 4 ] ); + char secondChar = tolower( valuePtr[ 5 ] ); + char thirdChar = tolower( valuePtr[ 6 ] ); + if ( firstChar == 'j' && secondChar == 'a' && thirdChar == 'n' ) + return 1; + if ( firstChar == 'f' && secondChar == 'e' && thirdChar == 'b' ) + return 2; + if ( firstChar == 'm' && secondChar == 'a' ) + { + if ( thirdChar == 'r' ) + return 3; + if ( thirdChar == 'y' ) + return 5; + } + if ( firstChar == 'a' && secondChar == 'p' && thirdChar == 'r' ) + return 4; + if ( firstChar == 'j' && secondChar == 'u' ) + { + if ( thirdChar == 'n' ) + return 6; + if ( thirdChar == 'l' ) + return 7; + } + if ( firstChar == 'a' && secondChar == 'u' && thirdChar == 'g' ) + return 8; + if ( firstChar == 's' && secondChar == 'e' && thirdChar == 'p' ) + return 9; + if ( firstChar == 'o' && secondChar == 'c' && thirdChar == 't' ) + return 10; + if ( firstChar == 'n' && secondChar == 'o' && thirdChar == 'v' ) + return 11; + if ( firstChar == 'd' && secondChar == 'e' && thirdChar == 'c' ) + return 12; + return 0; +} + +static XMP_Uns32 GatherUnsignedInt ( const char * strPtr, size_t count ) +{ + XMP_Uns32 value = 0; + const char * strEnd = strPtr + count; + + while ( strPtr < strEnd ) { + if ( *strPtr == ' ' ) ++strPtr; + else break; + } + + while ( strPtr < strEnd ) { + char ch = *strPtr; + if ( (ch < '0') || (ch > '9') ) break; + value = value*10 + (ch - '0'); + ++strPtr; + } + + return value; + +} // GatherUnsignedInt + +static void importIditChunkToXMP( RIFF_MetaHandler* handler, ValueChunk* iditChunk ) +{ + // if there iss a IDIT chunk, there is data... + handler->containsXMP = true; // very important for treatment on caller level + + // Size has been already checked in calling function + XMP_Enforce( iditChunk->oldSize == IDIT_SIZE + 8 ); + const char * valuePtr = iditChunk->oldValue.c_str(); + XMP_Enforce( valuePtr[ IDIT_SIZE - 2 ] == 0x0A ); + XMP_Enforce( valuePtr[ 13 ] == ':' && valuePtr[ 16 ] == ':' ); + XMP_DateTime dateTime; + dateTime.month = GetMonth( valuePtr ); + dateTime.day = GatherUnsignedInt( valuePtr + 8, 2 ); + dateTime.hour = GatherUnsignedInt( valuePtr + 11, 2 ); + dateTime.minute = GatherUnsignedInt( valuePtr + 14, 2 ); + dateTime.second = GatherUnsignedInt( valuePtr + 17, 2 ); + dateTime.year = GatherUnsignedInt( valuePtr + 20, 4 ); + handler->xmpObj.SetProperty_Date( kXMP_NS_EXIF, "DateTimeOriginal", dateTime ); + +} // importIditChunkToXMP + +static void importPrmLToXMP( RIFF_MetaHandler* handler, ValueChunk* prmlChunk ) +{ + bool haveXMP = false; + + XMP_Enforce( prmlChunk->oldSize == PRML_SIZE ); + PrmLBoxContent rawPrmL; + XMP_Assert ( sizeof ( rawPrmL ) == PRML_SIZE - 8 ); // double check tight packing. + XMP_Assert ( sizeof ( rawPrmL.filePath ) == 260 ); + memcpy ( &rawPrmL, prmlChunk->oldValue.data(), sizeof (rawPrmL) ); + + if ( rawPrmL.magic != 0xBEEFCAFE ) { + Flip4 ( &rawPrmL.exportType ); // The only numeric field that we care about. + } + + rawPrmL.filePath[259] = 0; // Ensure a terminating nul. + if ( rawPrmL.filePath[0] != 0 ) { + if ( rawPrmL.filePath[0] == '/' ) { + haveXMP = true; + handler->xmpObj.SetStructField ( kXMP_NS_CreatorAtom, "macAtom", + kXMP_NS_CreatorAtom, "posixProjectPath", rawPrmL.filePath ); + } else if ( XMP_LitNMatch ( rawPrmL.filePath, "\\\\?\\", 4 ) ) { + haveXMP = true; + handler->xmpObj.SetStructField ( kXMP_NS_CreatorAtom, "windowsAtom", + kXMP_NS_CreatorAtom, "uncProjectPath", rawPrmL.filePath ); + } + } + + const char * exportStr = 0; + switch ( rawPrmL.exportType ) { + case kExportTypeMovie : exportStr = "movie"; break; + case kExportTypeStill : exportStr = "still"; break; + case kExportTypeAudio : exportStr = "audio"; break; + case kExportTypeCustom : exportStr = "custom"; break; + } + if ( exportStr != 0 ) { + haveXMP = true; + handler->xmpObj.SetStructField ( kXMP_NS_DM, "projectRef", kXMP_NS_DM, "type", exportStr ); + } + + handler->containsXMP |= haveXMP; // mind the '|=' +} // importCr8rToXMP + +static void importCr8rToXMP( RIFF_MetaHandler* handler, ValueChunk* cr8rChunk ) +{ + bool haveXMP = false; + + XMP_Enforce( cr8rChunk->oldSize == CR8R_SIZE ); + Cr8rBoxContent rawCr8r; + XMP_Assert ( sizeof ( rawCr8r ) == CR8R_SIZE - 8 ); // double check tight packing. + memcpy ( &rawCr8r, cr8rChunk->oldValue.data(), sizeof (rawCr8r) ); + + if ( rawCr8r.magic != 0xBEEFCAFE ) { + Flip4 ( &rawCr8r.creatorCode ); // The only numeric fields that we care about. + Flip4 ( &rawCr8r.appleEvent ); + } + + std::string fieldPath; + + SXMPUtils::ComposeStructFieldPath ( kXMP_NS_CreatorAtom, "macAtom", kXMP_NS_CreatorAtom, "applicationCode", &fieldPath ); + if ( rawCr8r.creatorCode != 0 ) { + haveXMP = true; + handler->xmpObj.SetProperty_Int64 ( kXMP_NS_CreatorAtom, fieldPath.c_str(), (XMP_Int64)rawCr8r.creatorCode ); // ! Unsigned trickery. + } + + SXMPUtils::ComposeStructFieldPath ( kXMP_NS_CreatorAtom, "macAtom", kXMP_NS_CreatorAtom, "invocationAppleEvent", &fieldPath ); + if ( rawCr8r.appleEvent != 0 ) { + haveXMP = true; + handler->xmpObj.SetProperty_Int64 ( kXMP_NS_CreatorAtom, fieldPath.c_str(), (XMP_Int64)rawCr8r.appleEvent ); // ! Unsigned trickery. + } + + rawCr8r.fileExt[15] = 0; // Ensure a terminating nul. + if ( rawCr8r.fileExt[0] != 0 ) { + haveXMP = true; + handler->xmpObj.SetStructField ( kXMP_NS_CreatorAtom, "windowsAtom", kXMP_NS_CreatorAtom, "extension", rawCr8r.fileExt ); + } + + rawCr8r.appOptions[15] = 0; // Ensure a terminating nul. + if ( rawCr8r.appOptions[0] != 0 ) { + haveXMP = true; + handler->xmpObj.SetStructField ( kXMP_NS_CreatorAtom, "windowsAtom", kXMP_NS_CreatorAtom, "invocationFlags", rawCr8r.appOptions ); + } + + rawCr8r.appName[31] = 0; // Ensure a terminating nul. + if ( rawCr8r.appName[0] != 0 ) { + haveXMP = true; + handler->xmpObj.SetProperty ( kXMP_NS_XMP, "CreatorTool", rawCr8r.appName ); + } + + handler->containsXMP |= haveXMP; // mind the '|=' +} // importCr8rToXMP + + +static void importListChunkToXMP( RIFF_MetaHandler* handler, ContainerChunk* listChunk, Mapping mapping[], bool xmpHasPriority ) +{ + valueMap* cm = &listChunk->childmap; + for (int p=0; mapping[p].chunkID != 0; p++) // go through legacy chunks + { + valueMapIter result = cm->find(mapping[p].chunkID); + if( result != cm->end() ) // if value found + { + ValueChunk* propChunk = result->second; + + bool propertyExists = false; + std::string utf8 = nativePropertyToUTF8( + propChunk->oldValue.c_str(), + (XMP_StringLen)propChunk->oldValue.size(), &propertyExists ); + + if ( utf8.size() > 0 ) // if property is not-empty, set Property + { + switch ( mapping[p].propType ) + { + case prop_TIMEVALUE: + if ( xmpHasPriority && + handler->xmpObj.DoesStructFieldExist( mapping[p].ns, mapping[p].prop, kXMP_NS_DM, "timeValue" )) + break; // skip if XMP has precedence and exists + handler->xmpObj.SetStructField( mapping[p].ns, mapping[p].prop, + kXMP_NS_DM, "timeValue", utf8.c_str() ); + break; + case prop_LOCALIZED_TEXT: + if ( xmpHasPriority && handler->xmpObj.GetLocalizedText( mapping[p].ns , + mapping[p].prop, "" , "x-default", 0, 0, 0 )) + break; // skip if XMP has precedence and exists + handler->xmpObj.SetLocalizedText( mapping[p].ns , mapping[p].prop, + "" , "x-default" , utf8.c_str() ); + if ( mapping[p].chunkID == kPropChunkINAM ) + handler->hasListInfoINAM = true; // needs to be known for special 3-way merge around dc:title + break; + case prop_ARRAYITEM: + if ( xmpHasPriority && + handler->xmpObj.DoesArrayItemExist( mapping[p].ns, mapping[p].prop, 1 )) + break; // skip if XMP has precedence and exists + handler->xmpObj.DeleteProperty( mapping[p].ns, mapping[p].prop ); + handler->xmpObj.AppendArrayItem( mapping[p].ns, mapping[p].prop, kXMP_PropValueIsArray, utf8.c_str(), kXMP_NoOptions ); + break; + case prop_SIMPLE: + if ( xmpHasPriority && + handler->xmpObj.DoesPropertyExist( mapping[p].ns, mapping[p].prop )) + break; // skip if XMP has precedence and exists + handler->xmpObj.SetProperty( mapping[p].ns, mapping[p].prop, utf8.c_str() ); + break; + default: + XMP_Throw( "internal error" , kXMPErr_InternalFailure ); + } + + handler->containsXMP = true; // very important for treatment on caller level + } + else if ( ! propertyExists) // otherwise remove it. + { // [2389942] don't, if legacy value is existing but non-retrievable (due to server mode) + switch ( mapping[p].propType ) + { + case prop_TIMEVALUE: + if ( (!xmpHasPriority) && // forward deletion only if XMP has no priority + handler->xmpObj.DoesPropertyExist( mapping[p].ns, mapping[p].prop )) + handler->xmpObj.DeleteProperty( mapping[p].ns, mapping[p].prop ); + break; + case prop_LOCALIZED_TEXT: + if ( (!xmpHasPriority) && // forward deletion only if XMP has no priority + handler->xmpObj.DoesPropertyExist( mapping[p].ns, mapping[p].prop )) + handler->xmpObj.DeleteLocalizedText( mapping[p].ns, mapping[p].prop, "", "x-default" ); + break; + case prop_ARRAYITEM: + case prop_SIMPLE: + if ( (!xmpHasPriority) && // forward deletion only if XMP has no priority + handler->xmpObj.DoesPropertyExist( mapping[p].ns, mapping[p].prop )) + handler->xmpObj.DeleteProperty( mapping[p].ns, mapping[p].prop ); + break; + default: + XMP_Throw( "internal error" , kXMPErr_InternalFailure ); + } + } + } + } // for +} +void importProperties( RIFF_MetaHandler* handler ) +{ + bool hasDigest = handler->xmpObj.GetProperty( kXMP_NS_WAV, "NativeDigest", NULL , NULL ); + if ( hasDigest ) + { + // remove! since it now becomse a 'new' handler file + handler->xmpObj.DeleteProperty( kXMP_NS_WAV, "NativeDigest" ); + } + + // BWF Bext extension chunk ----------------------------------------------- + if ( handler->parent->format == kXMP_WAVFile && // applies only to WAV + handler->bextChunk != 0 ) //skip if no BEXT chunk found. + { + importBextChunkToXMP( handler, handler->bextChunk ); + } + + // PrmL chunk -------------------------------------------------------------- + if ( handler->prmlChunk != 0 && handler->prmlChunk->oldSize == PRML_SIZE ) + { + importPrmLToXMP( handler, handler->prmlChunk ); + } + + // Cr8r chunk -------------------------------------------------------------- + if ( handler->cr8rChunk != 0 && handler->cr8rChunk->oldSize == CR8R_SIZE ) + { + importCr8rToXMP( handler, handler->cr8rChunk ); + } + + // LIST:INFO -------------------------------------------------------------- + if ( handler->listInfoChunk != 0) //skip if no LIST:INFO chunk found. + importListChunkToXMP( handler, handler->listInfoChunk, listInfoProps, hasDigest ); + + // LIST:Tdat -------------------------------------------------------------- + if ( handler->listTdatChunk != 0) + importListChunkToXMP( handler, handler->listTdatChunk, listTdatProps, hasDigest ); + + // DISP (do last, higher priority than INAM ) ----------------------------- + bool takeXMP = false; // assume for now + if ( hasDigest ) + { + std::string actualLang, value; + bool r = handler->xmpObj.GetLocalizedText( kXMP_NS_DC, "title", "" , "x-default" , &actualLang, &value, NULL ); + if ( r && (actualLang == "x-default") ) takeXMP = true; + } + + if ( (!takeXMP) && handler->dispChunk != 0) //skip if no LIST:INFO chunk found. + { + std::string* value = &handler->dispChunk->oldValue; + if ( value->size() >= 4 ) // ignore contents if file too small + { + XMP_StringPtr cstring = value->c_str(); + XMP_StringLen size = (XMP_StringLen) value->size(); + + size -= 4; // skip first four bytes known to contain constant + cstring += 4; + + bool propertyExists = false; + std::string utf8 = nativePropertyToUTF8( cstring, size, &propertyExists ); + + if ( utf8.size() > 0 ) + { + handler->xmpObj.SetLocalizedText( kXMP_NS_DC, "title", "" , "x-default" , utf8.c_str() ); + handler->containsXMP = true; // very important for treatment on caller level + } + else + { + // found as part of [2389942] + // forward deletion may only happen if no LIST:INFO/INAM is present: + if ( ! handler->hasListInfoINAM && + ! propertyExists ) // ..[2389942]part2: and if truly no legacy property + { // (not just an unreadable one due to ServerMode). + handler->xmpObj.DeleteProperty( kXMP_NS_DC, "title" ); + } + } + } // if size sufficient + } // handler->dispChunk + + // IDIT chunk -------------------------------------------------------------- + if ( handler->parent->format == kXMP_AVIFile && // Only for AVI file + handler->iditChunk != 0 && handler->iditChunk->oldSize == IDIT_SIZE + 8 ) // Including header size i.e, ID + size + { + importIditChunkToXMP( handler, handler->iditChunk ); + } + +} // importProperties + +//////////////////////////////////////////////////////////////////////////////// +// EXPORT +//////////////////////////////////////////////////////////////////////////////// + +void relocateWronglyPlacedXMPChunk( RIFF_MetaHandler* handler ) +{ + XMP_IO* file = handler->parent->ioRef; + RIFF::containerVect *rc = &handler->riffChunks; + RIFF::ContainerChunk* lastChunk = rc->at( rc->size()-1 ); + + // 1) XMPPacket + // needChunk exists but is not in lastChunk ? + if ( + handler->xmpChunk != 0 && // XMP Chunk existing? + (XMP_Uns32)rc->size() > 1 && // more than 1 top-level chunk (otherwise pointless) + lastChunk->getChild( handler->xmpChunk ) == lastChunk->children.end() // not already in last chunk? + ) + { + RIFF::ContainerChunk* cur; + chunkVectIter child; + XMP_Int32 chunkNo; + + // find and relocate to last chunk: + for ( chunkNo = (XMP_Int32)rc->size()-2 ; chunkNo >= 0; chunkNo-- ) // ==> start with second-last chunk + { + cur = rc->at(chunkNo); + child = cur->getChild( handler->xmpChunk ); + if ( child != cur->children.end() ) // found? + break; + } // for + + if ( chunkNo < 0 ) // already in place? nothing left to do. + return; + + lastChunk->children.push_back( *child ); // nb: order matters! + cur->replaceChildWithJunk( *child, false ); + cur->hasChange = true; // [2414649] initialize early-on i.e: here + } // if +} // relocateWronglyPlacedXMPChunk + +// writes to buffer up to max size, +// 0 termination only if shorter than maxSize +// converts down to ascii +static void setBextField ( std::string* value, XMP_Uns8* data, XMP_Uns32 offset, XMP_Uns32 maxSize ) +{ + XMP_Validate( value != 0, "setBextField: null value string pointer", kXMPErr_BadParam ); + XMP_Validate( data != 0, "setBextField: null data value", kXMPErr_BadParam ); + XMP_Validate( maxSize > 0, "setBextField: maxSize must be greater than 0", kXMPErr_BadParam ); + + std::string ascii; + XMP_StringLen actualSize = convertToASCII( value->data(), (XMP_StringLen) value->size() , &ascii , maxSize ); + strncpy( (char*)(data + offset), ascii.data(), actualSize ); +} + +// add bwf-bext related data to bext chunk, create if not existing yet. +// * in fact, since bext is fully fixed and known, there can be no unknown subchunks worth keeping: +// * prepare bext chunk in buffer +// * value changed/created if needed only, otherways remove chunk +// * remove bext-mapped properties from xmp (non-redundant storage) +// note: ValueChunk**: adress of pointer to allow changing the pointer itself (i.e. chunk creation) +static void exportXMPtoBextChunk( RIFF_MetaHandler* handler, ValueChunk** bextChunk ) +{ + // register bext namespace ( if there was no import, this is news, otherwise harmless moot) + SXMPMeta::RegisterNamespace( kXMP_NS_BWF, "bext:", 0 ); + + bool chunkUsed = false; // assume for now + SXMPMeta* xmp = &handler->xmpObj; + + // prepare buffer, need to know CodingHistory size as the only variable + XMP_Int32 bextBufferSize = MIN_BEXT_SIZE - 8; // -8 because of header + std::string value; + if ( xmp->GetProperty( bextCodingHistory.ns, bextCodingHistory.prop, &value, (XMP_OptionBits *) kXMP_NoOptions )) + { + bextBufferSize += ((XMP_StringLen)value.size()) + 1 ; // add to size (and a trailing zero) + } + + // create and clear buffer + XMP_Uns8* buffer = new XMP_Uns8[bextBufferSize]; + for (XMP_Int32 i = 0; i < bextBufferSize; i++ ) + buffer[i] = 0; + + // grab props, write into buffer, remove from XMP /////////////////////////// + // bextDescription ------------------------------------------------ + if ( xmp->GetProperty( bextDescription.ns, bextDescription.prop, &value, (XMP_OptionBits *) kXMP_NoOptions ) ) + { + setBextField( &value, (XMP_Uns8*) buffer, 0, 256 ); + xmp->DeleteProperty( bextDescription.ns, bextDescription.prop) ; + chunkUsed = true; + } + // bextOriginator ------------------------------------------------- + if ( xmp->GetProperty( bextOriginator.ns , bextOriginator.prop, &value, (XMP_OptionBits *) kXMP_NoOptions ) ) + { + setBextField( &value, (XMP_Uns8*) buffer, 256, 32 ); + xmp->DeleteProperty( bextOriginator.ns , bextOriginator.prop ); + chunkUsed = true; + } + // bextOriginatorRef ---------------------------------------------- + if ( xmp->GetProperty( bextOriginatorRef.ns , bextOriginatorRef.prop, &value, (XMP_OptionBits *) kXMP_NoOptions ) ) + { + setBextField( &value, (XMP_Uns8*) buffer, 256+32, 32 ); + xmp->DeleteProperty( bextOriginatorRef.ns , bextOriginatorRef.prop ); + chunkUsed = true; + } + // bextOriginationDate -------------------------------------------- + if ( xmp->GetProperty( bextOriginationDate.ns , bextOriginationDate.prop, &value, (XMP_OptionBits *) kXMP_NoOptions ) ) + { + setBextField( &value, (XMP_Uns8*) buffer, 256+32+32, 10 ); + xmp->DeleteProperty( bextOriginationDate.ns , bextOriginationDate.prop ); + chunkUsed = true; + } + // bextOriginationTime -------------------------------------------- + if ( xmp->GetProperty( bextOriginationTime.ns , bextOriginationTime.prop, &value, (XMP_OptionBits *) kXMP_NoOptions ) ) + { + setBextField( &value, (XMP_Uns8*) buffer, 256+32+32+10, 8 ); + xmp->DeleteProperty( bextOriginationTime.ns , bextOriginationTime.prop ); + chunkUsed = true; + } + // bextTimeReference ---------------------------------------------- + // thanx to friendly byte order, all 8 bytes can be written in one go: + if ( xmp->GetProperty( bextTimeReference.ns, bextTimeReference.prop, &value, (XMP_OptionBits *) kXMP_NoOptions ) ) + { + try + { + XMP_Int64 v = SXMPUtils::ConvertToInt64( value.c_str() ); + PutUns64LE( v, &(buffer[256+32+32+10+8] )); + chunkUsed = true; + } + catch (XMP_Error& e) + { + if ( e.GetID() != kXMPErr_BadParam ) + throw e; // re-throw on any other error + } // 'else' tolerate ( time reference remains 0x00000000 ) + // valid or not, do not store redundantly: + xmp->DeleteProperty( bextTimeReference.ns, bextTimeReference.prop ); + } + + // bextVersion ---------------------------------------------------- + // set version=1, no matter what. + PutUns16LE( 1, &(buffer[256+32+32+10+8+8]) ); + xmp->DeleteProperty( bextVersion.ns, bextVersion.prop ); + + // bextUMID ------------------------------------------------------- + if ( xmp->GetProperty( bextUMID.ns, bextUMID.prop, &value, (XMP_OptionBits *) kXMP_NoOptions ) ) + { + std::string rawStr; + + if ( !DecodeFromHexString( value.data(), (XMP_StringLen) value.size(), &rawStr ) ) + { + delete [] buffer; // important. + XMP_Throw ( "EncodeFromHexString: illegal umid string. Must contain an even number of 0-9 and uppercase A-F chars.", kXMPErr_BadParam ); + } + + // if UMID is smaller/longer than 64 byte for any reason, + // truncate/do a partial write (just like for any other bext property) + + memcpy( (char*) &(buffer[256+32+32+10+8+8+2]), rawStr.data(), MIN( 64, rawStr.size() ) ); + xmp->DeleteProperty( bextUMID.ns, bextUMID.prop ); + chunkUsed = true; + } + + // bextCodingHistory ---------------------------------------------- + if ( xmp->GetProperty( bextCodingHistory.ns, bextCodingHistory.prop, &value, (XMP_OptionBits *) kXMP_NoOptions ) ) + { + std::string ascii; + convertToASCII( value.data(), (XMP_StringLen) value.size() , &ascii, (XMP_StringLen) value.size() ); + strncpy( (char*) &(buffer[MIN_BEXT_SIZE-8]), ascii.data(), ascii.size() ); + xmp->DeleteProperty( bextCodingHistory.ns, bextCodingHistory.prop ); + chunkUsed = true; + } + + // always delete old, recreate if needed + if ( *bextChunk != 0 ) + { + (*bextChunk)->parent->replaceChildWithJunk( *bextChunk ); + (*bextChunk) = 0; // clear direct Chunk pointer + } + + if ( chunkUsed) + *bextChunk = new ValueChunk( handler->riffChunks.at(0), std::string( (char*)buffer, bextBufferSize ), kChunk_bext ); + + delete [] buffer; // important. +} + +static inline void SetBufferedString ( char * dest, const std::string source, size_t limit ) +{ + memset ( dest, 0, limit ); + size_t count = source.size(); + if ( count >= limit ) count = limit - 1; // Ensure a terminating nul. + memcpy ( dest, source.c_str(), count ); +} + +static void exportXMPtoCr8rChunk ( RIFF_MetaHandler* handler, ValueChunk** cr8rChunk ) +{ + const SXMPMeta & xmp = handler->xmpObj; + + // Make sure an existing Cr8r chunk has the proper fixed length. + bool haveOldCr8r = (*cr8rChunk != 0); + if ( haveOldCr8r && ((*cr8rChunk)->oldSize != sizeof(Cr8rBoxContent)+8) ) { + (*cr8rChunk)->parent->replaceChildWithJunk ( *cr8rChunk ); // Wrong length, the existing chunk must be bad. + (*cr8rChunk) = 0; + haveOldCr8r = false; + } + + bool haveNewCr8r = false; + std::string creatorCode, appleEvent, fileExt, appOptions, appName; + + haveNewCr8r |= xmp.GetStructField ( kXMP_NS_CreatorAtom, "macAtom", kXMP_NS_CreatorAtom, "applicationCode", &creatorCode, 0 ); + haveNewCr8r |= xmp.GetStructField ( kXMP_NS_CreatorAtom, "macAtom", kXMP_NS_CreatorAtom, "invocationAppleEvent", &appleEvent, 0 ); + haveNewCr8r |= xmp.GetStructField ( kXMP_NS_CreatorAtom, "windowsAtom", kXMP_NS_CreatorAtom, "extension", &fileExt, 0 ); + haveNewCr8r |= xmp.GetStructField ( kXMP_NS_CreatorAtom, "windowsAtom", kXMP_NS_CreatorAtom, "invocationFlags", &appOptions, 0 ); + haveNewCr8r |= xmp.GetProperty ( kXMP_NS_XMP, "CreatorTool", &appName, 0 ); + + if ( ! haveNewCr8r ) { // Get rid of an existing Cr8r chunk if there is no new XMP. + if ( haveOldCr8r ) { + (*cr8rChunk)->parent->replaceChildWithJunk ( *cr8rChunk ); + *cr8rChunk = 0; + } + return; + } + + if ( ! haveOldCr8r ) { + *cr8rChunk = new ValueChunk ( handler->lastChunk, std::string(), kChunk_Cr8r ); + } + + std::string strValue; + strValue.assign ( (sizeof(Cr8rBoxContent) - 1), '\0' ); // ! Use size-1 because SetValue appends a trailing 0 byte. + (*cr8rChunk)->SetValue ( strValue ); // ! Just get the space available. + XMP_Assert ( (*cr8rChunk)->newValue.size() == sizeof(Cr8rBoxContent) ); + (*cr8rChunk)->hasChange = true; + + Cr8rBoxContent * newCr8r = (Cr8rBoxContent*) (*cr8rChunk)->newValue.data(); + + if ( ! haveOldCr8r ) { + + newCr8r->magic = MakeUns32LE ( 0xBEEFCAFE ); + newCr8r->size = MakeUns32LE ( sizeof(Cr8rBoxContent) ); + newCr8r->majorVer = MakeUns16LE ( 1 ); + + } else { + + const Cr8rBoxContent * oldCr8r = (Cr8rBoxContent*) (*cr8rChunk)->oldValue.data(); + memcpy ( newCr8r, oldCr8r, sizeof(Cr8rBoxContent) ); + if ( GetUns32LE ( &newCr8r->magic ) != 0xBEEFCAFE ) { // Make sure we write LE numbers. + Flip4 ( &newCr8r->magic ); + Flip4 ( &newCr8r->size ); + Flip2 ( &newCr8r->majorVer ); + Flip2 ( &newCr8r->minorVer ); + Flip4 ( &newCr8r->creatorCode ); + Flip4 ( &newCr8r->appleEvent ); + } + + } + + if ( ! creatorCode.empty() ) { + newCr8r->creatorCode = MakeUns32LE ( (XMP_Uns32) strtoul ( creatorCode.c_str(), 0, 0 ) ); + } + + if ( ! appleEvent.empty() ) { + newCr8r->appleEvent = MakeUns32LE ( (XMP_Uns32) strtoul ( appleEvent.c_str(), 0, 0 ) ); + } + + if ( ! fileExt.empty() ) SetBufferedString ( newCr8r->fileExt, fileExt, sizeof ( newCr8r->fileExt ) ); + if ( ! appOptions.empty() ) SetBufferedString ( newCr8r->appOptions, appOptions, sizeof ( newCr8r->appOptions ) ); + if ( ! appName.empty() ) SetBufferedString ( newCr8r->appName, appName, sizeof ( newCr8r->appName ) ); + +} + +// Returns numbers of leap years between 1800 and provided year value. Both end values will be inclusive for getting this field. +// For leap year this will be handled later in GetIDITString() function +static XMP_Uns32 GetLeapYearsNumber( const XMP_Uns32 & year ) +{ + + XMP_Uns32 numLeapYears = ( year / 4 ); + numLeapYears -= ( year / 100 ); + numLeapYears += ( ( year + 200 ) / 400 ); // 200 is added becuase our base is 1800 which give modulas 200 by divinding with 400 + return numLeapYears; + +} //GetLeapYearsNumber + +static XMP_Uns32 GetDays( const XMP_Int32 & month, const bool &isLeapYear ) +{ + // Adding number of days as per last month of the provided year + // Leap year case is handled later + XMP_Uns32 numDays = 0; + switch ( month ) + { + case 2: + numDays = 31; + break; + case 3: + numDays = 31 + 28; + break; + case 4: + numDays = 31 + 28 + 31; + break; + case 5: + numDays = 31 + 28 + 31 + 30; + break; + case 6: + numDays = 31 + 28 + 31 + 30 + 31; + break; + case 7: + numDays = 31 + 28 + 31 + 30 + 31 + 30; + break; + case 8: + numDays = 31 + 28 + 31 + 30 + 31 + 30 + 31; + break; + case 9: + numDays = 31 + 28 + 31 + 30 + 31 + 30 + 31 + 31; + break; + case 10: + numDays = 31 + 28 + 31 + 30 + 31 + 30 + 31 + 31 + 30; + break; + case 11: + numDays = 31 + 28 + 31 + 30 + 31 + 30 + 31 + 31 + 30 + 31; + break; + case 12: + numDays = 31 + 28 + 31 + 30 + 31 + 30 + 31 + 31 + 30 + 31 + 30; + break; + default: + break; + } + + // Adding one day for leap year and month above than feb + if ( isLeapYear == true && month > 2 ) + numDays += 1; + + return numDays; + +} // GetDays + +static const std::string GetIDITString( const XMP_DateTime & targetDate ) +{ + // Date 1 Jan 1800 is treating as base date for handling this issue + // 1800 is chosen becuase of consistency in calender after 1752 + + XMP_Uns64 numOfDays = 0; // Specifies number of days after 1 jan 1800 + XMP_Uns32 year = targetDate.year - 1800; + + // 2000 was the first exception when year dividing by 100 was still a leap year + bool isLeapYear = ( year % 4 != 0 ) ? false : ( year % 100 != 0 ) ? true : ( ( year % 400 != 200 ) ? false : true ); + + // Adding days according to normal year and adjusting days for leap years + numOfDays = 365 * year; + numOfDays += GetLeapYearsNumber( year ); + + // Adding days according to the month + numOfDays += GetDays( targetDate.month, isLeapYear ); + + // GetLeapYearsNumber() function is also considering provided year for calculating number of leap numbers between provided year + // and 1800. This consideration is done by inclusive both end values. So both GetLeapYearsNumber() and GetDays() would have added + // extra day for higher end year for leap year. + // So, we need to decrease one day from number of days field + if ( isLeapYear ) + --numOfDays; + + // Adding days according to provided month + numOfDays += targetDate.day; + + // Weekday starting from Wednesday i.e., Wed will be the first day of the week. + // This day was choosen because 1 Jan 1800 was Wednesday + XMP_Uns8 weekDayNum = numOfDays % 7; + std::string weekDay; + switch ( weekDayNum ) + { + case 0: + weekDay = "Tue"; + break; + case 1: + weekDay = "Wed"; + break; + case 2: + weekDay = "Thu"; + break; + case 3: + weekDay = "Fri"; + break; + case 4: + weekDay = "Sat"; + break; + case 5: + weekDay = "Sun"; + break; + case 6: + weekDay = "Mon"; + break; + default: + break; + } + + // Stream to convert into IDIT format + std::stringstream iditStream; + iditStream << weekDay; + iditStream.put( ' ' ); + + // IDIT needs 3 character codes for month + const char * monthArray[] = { "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" }; + iditStream << monthArray[ targetDate.month - 1 ]; + iditStream.put( ' ' ); + + if ( targetDate.day < 10 ) + iditStream.put( '0' ); + iditStream << targetDate.day; + iditStream.put( ' ' ); + + if ( targetDate.hour < 10 ) + iditStream.put( '0' ); + iditStream << targetDate.hour; + iditStream.put( ':' ); + + if ( targetDate.minute < 10 ) + iditStream.put( '0' ); + iditStream << targetDate.minute; + iditStream.put( ':' ); + + if ( targetDate.second < 10 ) + iditStream.put( '0' ); + iditStream << targetDate.second; + iditStream.put( ' ' ); + + // No need to handle casese for year 1 to 999 + /* + if ( targetDate.year < 10 ) + iditStream << " "; + else if ( targetDate.year < 100 ) + iditStream << " "; + else if ( targetDate.year < 1000 ) + iditStream << " "; + */ + // Year will be in the range of 1800-3999 + iditStream << targetDate.year; + + // Adding new line charcter for IDIT + iditStream.put( '\n' ); + + return iditStream.str(); + +} // GetIDITString + +static void exportXMPtoIDITChunk( RIFF_MetaHandler* handler ) +{ + // exif:DateTimeOriginal -> IDIT chunk + ContainerChunk * hdlrChunk = handler->listHdlrChunk; + if ( hdlrChunk == 0 ) + XMP_Throw( "Header of AVI file (hdlr chunk) must exists", kXMPErr_BadFileFormat ); + + XMP_DateTime dateTime; + bool propExists = handler->xmpObj.GetProperty_Date( kXMP_NS_EXIF, "DateTimeOriginal", &dateTime, 0 ); + if ( !propExists ) + { + if ( handler->iditChunk != 0 ) + { + // Exception would have thrown if we don't find hdlr chunk for AVI file + XMP_Assert( hdlrChunk != 0 ); + bool isSuccess = hdlrChunk->removeValue( kChunk_IDIT ); + if ( !isSuccess ) + XMP_Throw( "Removal of IDIT block fails", kXMPErr_InternalFailure ); + handler->iditChunk = 0; + hdlrChunk->hasChange = true; + } + // Else no need to do anything + } + else + { + if ( dateTime.year < 1800 || dateTime.year > 3999 ) + XMP_Throw( "For IDIT block, XMP currently supports years in between 1800 and 3999 (Both inclusive).", kXMPErr_InternalFailure ); + + /* + Conversion need to be done from XMP date time to IDIT structure. + XMP_DateTime accepts any value but IDIT needs to have weekday, month-day, month, year and time. + */ + + // Silently modifying dateTime for invalid dates. + if ( dateTime.month < 1 ) + dateTime.month = 1; + if ( dateTime.month > 12 ) + dateTime.month = 12; + if ( dateTime.day < 1 ) + dateTime.day = 1; + if ( dateTime.day > 31 ) + dateTime.day = 31; + + const std::string iditString = GetIDITString( dateTime ); + + // If no IDIT exits then create one + if ( handler->iditChunk == 0 ) + handler->iditChunk = new ValueChunk( hdlrChunk, std::string(), kChunk_IDIT ); + else if ( strncmp( iditString.c_str(), handler->iditChunk->oldValue.c_str(), IDIT_SIZE ) == 0 ) // Equal + return; + + // Setting the IDIT value + handler->iditChunk->hasChange = true; + handler->iditChunk->SetValue( iditString, true ); + + } + +} // exportXMPtoIDITChunk + +static void exportXMPtoListChunk( XMP_Uns32 id, XMP_Uns32 containerType, + RIFF_MetaHandler* handler, ContainerChunk** listChunk, Mapping mapping[]) +{ + // note: ContainerChunk**: adress of pointer to allow changing the pointer itself (i.e. chunk creation) + SXMPMeta* xmp = &handler->xmpObj; + bool listChunkIsNeeded = false; // assume for now + + // ! The NUL is optional in WAV to avoid a parsing bug in Audition 3 - can't handle implicit pad byte. + bool optionalNUL = (handler->parent->format == kXMP_WAVFile); + + for ( int p=0; mapping[p].chunkID != 0; ++p ) { // go through all potential property mappings + + bool propExists = false; + std::string value, actualLang; + + switch ( mapping[p].propType ) { + + // get property. if existing, remove from XMP (to avoid redundant storage) + case prop_TIMEVALUE: + propExists = xmp->GetStructField ( mapping[p].ns, mapping[p].prop, kXMP_NS_DM, "timeValue", &value, 0 ); + break; + + case prop_LOCALIZED_TEXT: + propExists = xmp->GetLocalizedText ( mapping[p].ns, mapping[p].prop, "", "x-default", &actualLang, &value, 0); + if ( actualLang != "x-default" ) propExists = false; // no "x-default" => nothing to reconcile ! + break; + + case prop_ARRAYITEM: + propExists = xmp->GetArrayItem ( mapping[p].ns, mapping[p].prop, 1, &value, 0 ); + break; + + case prop_SIMPLE: + propExists = xmp->GetProperty ( mapping[p].ns, mapping[p].prop, &value, 0 ); + break; + + default: + XMP_Throw ( "internal error", kXMPErr_InternalFailure ); + + } + + if ( ! propExists ) { + + if ( *listChunk != 0 ) (*listChunk)->removeValue ( mapping[p].chunkID ); + + } else { + + listChunkIsNeeded = true; + if ( *listChunk == 0 ) *listChunk = new ContainerChunk ( handler->riffChunks[0], id, containerType ); + + valueMap* cm = &(*listChunk)->childmap; + valueMapIter result = cm->find( mapping[p].chunkID ); + ValueChunk* propChunk = 0; + + if ( result != cm->end() ) { + propChunk = result->second; + } else { + propChunk = new ValueChunk ( *listChunk, std::string(), mapping[p].chunkID ); + } + + propChunk->SetValue ( value.c_str(), optionalNUL ); + + } + + } // for each property + + if ( (! listChunkIsNeeded) && (*listChunk != 0) && ((*listChunk)->children.size() == 0) ) { + (*listChunk)->parent->replaceChildWithJunk ( *listChunk ); + (*listChunk) = 0; // reset direct Chunk pointer + } + +} + +void exportAndRemoveProperties ( RIFF_MetaHandler* handler ) +{ + SXMPMeta xmpObj = handler->xmpObj; + + exportXMPtoCr8rChunk ( handler, &handler->cr8rChunk ); + + // 1/5 BWF Bext extension chunk ----------------------------------------------- + if ( handler->parent->format == kXMP_WAVFile ) { // applies only to WAV + exportXMPtoBextChunk ( handler, &handler->bextChunk ); + } + + // 2/5 DISP chunk + if ( handler->parent->format == kXMP_WAVFile ) { // create for WAVE only + + std::string actualLang, xmpValue; + bool r = xmpObj.GetLocalizedText ( kXMP_NS_DC, "title", "" , "x-default" , &actualLang, &xmpValue, 0 ); + + if ( r && ( actualLang == "x-default" ) ) { // prop exists? + + // the 'right' DISP is lead by a 32 bit low endian 0x0001 + std::string dispValue = std::string( "\x1\0\0\0", 4 ); + dispValue.append ( xmpValue ); + + if ( handler->dispChunk == 0 ) { + handler->dispChunk = new RIFF::ValueChunk ( handler->riffChunks.at(0), std::string(), kChunk_DISP ); + } + + // ! The NUL is optional in WAV to avoid a parsing bug in Audition 3 - can't handle implicit pad byte. + handler->dispChunk->SetValue ( dispValue, ValueChunk::kNULisOptional ); + + } else { // remove Disp Chunk.. + + if ( handler->dispChunk != 0 ) { // ..if existing + ContainerChunk* mainChunk = handler->riffChunks.at(0); + Chunk* needle = handler->dispChunk; + chunkVectIter iter = mainChunk->getChild ( needle ); + if ( iter != mainChunk->children.end() ) { + mainChunk->replaceChildWithJunk ( *iter ); + handler->dispChunk = 0; + mainChunk->hasChange = true; + } + } + + } + + } + + // 3/5 LIST:INFO + exportXMPtoListChunk ( kChunk_LIST, kType_INFO, handler, &handler->listInfoChunk, listInfoProps ); + + // 4/5 LIST:Tdat + exportXMPtoListChunk ( kChunk_LIST, kType_Tdat, handler, &handler->listTdatChunk, listTdatProps ); + + // 5/5 LIST:HDRL:IDIT + if ( handler->parent->format == kXMP_AVIFile ) + exportXMPtoIDITChunk ( handler ); + +} + +} // namespace RIFF diff --git a/core/libs/dngwriter/extra/xmp_sdk/XMPFiles/source/FormatSupport/RIFF_Support.hpp b/core/libs/dngwriter/extra/xmp_sdk/XMPFiles/source/FormatSupport/RIFF_Support.hpp new file mode 100644 index 0000000000..c88599bd07 --- /dev/null +++ b/core/libs/dngwriter/extra/xmp_sdk/XMPFiles/source/FormatSupport/RIFF_Support.hpp @@ -0,0 +1,42 @@ +#ifndef __RIFF_Support_hpp__ +#define __RIFF_Support_hpp__ 1 + +// ================================================================================================= +// ADOBE SYSTEMS INCORPORATED +// Copyright 2009 Adobe Systems Incorporated +// All Rights Reserved +// +// NOTICE: Adobe permits you to use, modify, and distribute this file in accordance with the terms +// of the Adobe license agreement accompanying it. +// ================================================================================================= + +#include "public/include/XMP_Environment.h" // ! This must be the first include. +#include +#include "XMPFiles/source/XMPFiles_Impl.hpp" + +// ahead declaration: +class RIFF_MetaHandler; + +namespace RIFF { + + // declare ahead + class Chunk; + class ContainerChunk; + class ValueChunk; + class XMPChunk; + + /* This rountines imports the properties found into the + xmp packet. Use after parsing. */ + void importProperties( RIFF_MetaHandler* handler ); + + /* This rountines exports XMP properties to the respective Chunks, + creating those if needed. No writing to file here. */ + void exportAndRemoveProperties( RIFF_MetaHandler* handler ); + + /* will relocated a wrongly placed chunk (one of XMP, LIST:Info, LIST:Tdat= + from RIFF::avix back to main chunk. Chunk itself not touched. */ + void relocateWronglyPlacedXMPChunk( RIFF_MetaHandler* handler ); + +} // namespace RIFF + +#endif // __RIFF_Support_hpp__ diff --git a/core/libs/dngwriter/extra/xmp_sdk/XMPFiles/source/FormatSupport/ReconcileIPTC.cpp b/core/libs/dngwriter/extra/xmp_sdk/XMPFiles/source/FormatSupport/ReconcileIPTC.cpp new file mode 100644 index 0000000000..142cdd6dac --- /dev/null +++ b/core/libs/dngwriter/extra/xmp_sdk/XMPFiles/source/FormatSupport/ReconcileIPTC.cpp @@ -0,0 +1,855 @@ +// ================================================================================================= +// ADOBE SYSTEMS INCORPORATED +// Copyright 2006 Adobe Systems Incorporated +// All Rights Reserved +// +// NOTICE: Adobe permits you to use, modify, and distribute this file in accordance with the terms +// of the Adobe license agreement accompanying it. +// ================================================================================================= + +#include "public/include/XMP_Environment.h" // ! XMP_Environment.h must be the first included header. +#include "public/include/XMP_Const.h" + +#include "XMPFiles/source/FormatSupport/Reconcile_Impl.hpp" +#include "source/XIO.hpp" + +#include + +#if XMP_WinBuild + #pragma warning ( disable : 4800 ) // forcing value to bool 'true' or 'false' (performance warning) + #pragma warning ( disable : 4996 ) // '...' was declared deprecated +#endif + +// ================================================================================================= +/// \file ReconcileIPTC.cpp +/// \brief Utilities to reconcile between XMP and legacy IPTC and PSIR metadata. +/// +// ================================================================================================= + +// ================================================================================================= +// NormalizeToCR +// ============= + +static inline void NormalizeToCR ( std::string * value ) +{ + char * strPtr = (char*) value->data(); + char * strEnd = strPtr + value->size(); + + for ( ; strPtr < strEnd; ++strPtr ) { + if ( *strPtr == kLF ) *strPtr = kCR; + } + +} // NormalizeToCR + +// ================================================================================================= +// NormalizeToLF +// ============= + +static inline void NormalizeToLF ( std::string * value ) +{ + char * strPtr = (char*) value->data(); + char * strEnd = strPtr + value->size(); + + for ( ; strPtr < strEnd; ++strPtr ) { + if ( *strPtr == kCR ) *strPtr = kLF; + } + +} // NormalizeToLF + +// ================================================================================================= +// ComputeIPTCDigest +// ================= +// +// Compute a 128 bit (16 byte) MD5 digest of the full IPTC block. + +static inline void ComputeIPTCDigest ( const void * iptcPtr, const XMP_Uns32 iptcLen, MD5_Digest * digest ) +{ + MD5_CTX context; + + MD5Init ( &context ); + MD5Update ( &context, (XMP_Uns8*)iptcPtr, iptcLen ); + MD5Final ( *digest, &context ); + +} // ComputeIPTCDigest; + +// ================================================================================================= +// PhotoDataUtils::CheckIPTCDigest +// =============================== + +int PhotoDataUtils::CheckIPTCDigest ( const void * newPtr, const XMP_Uns32 newLen, const void * oldDigest ) +{ + MD5_Digest newDigest; + ComputeIPTCDigest ( newPtr, newLen, &newDigest ); + if ( memcmp ( &newDigest, oldDigest, 16 ) == 0 ) return kDigestMatches; + return kDigestDiffers; + +} // PhotoDataUtils::CheckIPTCDigest + +// ================================================================================================= +// PhotoDataUtils::SetIPTCDigest +// ============================= + +void PhotoDataUtils::SetIPTCDigest ( void * iptcPtr, XMP_Uns32 iptcLen, PSIR_Manager * psir ) +{ + MD5_Digest newDigest; + + ComputeIPTCDigest ( iptcPtr, iptcLen, &newDigest ); + psir->SetImgRsrc ( kPSIR_IPTCDigest, &newDigest, sizeof(newDigest) ); + +} // PhotoDataUtils::SetIPTCDigest + +// ================================================================================================= +// ================================================================================================= + +// ================================================================================================= +// PhotoDataUtils::ImportIPTC_Simple +// ================================= + +void PhotoDataUtils::ImportIPTC_Simple ( const IPTC_Manager & iptc, SXMPMeta * xmp, + XMP_Uns8 id, const char * xmpNS, const char * xmpProp ) +{ + std::string utf8Str; + size_t count = iptc.GetDataSet_UTF8 ( id, &utf8Str ); + + if ( count != 0 ) { + NormalizeToLF ( &utf8Str ); + xmp->SetProperty ( xmpNS, xmpProp, utf8Str.c_str() ); + } + +} // PhotoDataUtils::ImportIPTC_Simple + +// ================================================================================================= +// PhotoDataUtils::ImportIPTC_LangAlt +// ================================== + +void PhotoDataUtils::ImportIPTC_LangAlt ( const IPTC_Manager & iptc, SXMPMeta * xmp, + XMP_Uns8 id, const char * xmpNS, const char * xmpProp ) +{ + std::string utf8Str; + size_t count = iptc.GetDataSet_UTF8 ( id, &utf8Str ); + + if ( count != 0 ) { + NormalizeToLF ( &utf8Str ); + xmp->SetLocalizedText ( xmpNS, xmpProp, "", "x-default", utf8Str.c_str() ); + } + +} // PhotoDataUtils::ImportIPTC_LangAlt + +// ================================================================================================= +// PhotoDataUtils::ImportIPTC_Array +// ================================ + +void PhotoDataUtils::ImportIPTC_Array ( const IPTC_Manager & iptc, SXMPMeta * xmp, + XMP_Uns8 id, const char * xmpNS, const char * xmpProp ) +{ + std::string utf8Str; + size_t count = iptc.GetDataSet ( id, 0 ); + + xmp->DeleteProperty ( xmpNS, xmpProp ); + + XMP_OptionBits arrayForm = kXMP_PropArrayIsUnordered; + if ( XMP_LitMatch ( xmpNS, kXMP_NS_DC ) && XMP_LitMatch ( xmpProp, "creator" ) ) arrayForm = kXMP_PropArrayIsOrdered; + + for ( size_t ds = 0; ds < count; ++ds ) { + (void) iptc.GetDataSet_UTF8 ( id, &utf8Str, ds ); + NormalizeToLF ( &utf8Str ); + xmp->AppendArrayItem ( xmpNS, xmpProp, arrayForm, utf8Str.c_str() ); + } + +} // PhotoDataUtils::ImportIPTC_Array + +// ================================================================================================= +// PhotoDataUtils::ImportIPTC_Date +// =============================== +// +// An IPTC (IIM) date is 8 characters, YYYYMMDD. Include the time portion if it is present. The IPTC +// time is HHMMSSxHHMM, where 'x' is '+' or '-'. Be tolerant of some ill-formed dates and times. +// Apparently some non-Adobe apps put strings like "YYYY-MM-DD" or "HH:MM:SSxHH:MM" in the IPTC. +// Allow a missing time zone portion. + +// *** The date/time handling differs from the MWG 1.0.1 policy, following a proposed tweak to MWG: +// *** Exif DateTimeOriginal <-> XMP exif:DateTimeOriginal +// *** IPTC DateCreated <-> XMP photoshop:DateCreated +// *** Exif DateTimeDigitized <-> IPTC DigitalCreateDate <-> XMP xmp:CreateDate + +void PhotoDataUtils::ImportIPTC_Date ( XMP_Uns8 dateID, const IPTC_Manager & iptc, SXMPMeta * xmp ) +{ + XMP_Uns8 timeID; + XMP_StringPtr xmpNS, xmpProp; + + if ( dateID == kIPTC_DateCreated ) { + timeID = kIPTC_TimeCreated; + xmpNS = kXMP_NS_Photoshop; + xmpProp = "DateCreated"; + } else if ( dateID == kIPTC_DigitalCreateDate ) { + timeID = kIPTC_DigitalCreateTime; + xmpNS = kXMP_NS_XMP; + xmpProp = "CreateDate"; + } else { + XMP_Throw ( "Unrecognized dateID", kXMPErr_BadParam ); + } + + // First gather the date portion. + + IPTC_Manager::DataSetInfo dsInfo; + size_t count = iptc.GetDataSet ( dateID, &dsInfo ); + if ( count == 0 || dsInfo.dataLen == 0 ) return; + + size_t chPos, digits; + XMP_DateTime xmpDate; + memset ( &xmpDate, 0, sizeof(xmpDate) ); + + chPos = 0; + for ( digits = 0; digits < 4; ++digits, ++chPos ) { + if ( (chPos >= dsInfo.dataLen) || (dsInfo.dataPtr[chPos] < '0') || (dsInfo.dataPtr[chPos] > '9') ) break; + xmpDate.year = (xmpDate.year * 10) + (dsInfo.dataPtr[chPos] - '0'); + } + + if ( ( chPos < dsInfo.dataLen ) && dsInfo.dataPtr[chPos] == '-' ) ++chPos; + for ( digits = 0; digits < 2; ++digits, ++chPos ) { + if ( (chPos >= dsInfo.dataLen) || (dsInfo.dataPtr[chPos] < '0') || (dsInfo.dataPtr[chPos] > '9') ) break; + xmpDate.month = (xmpDate.month * 10) + (dsInfo.dataPtr[chPos] - '0'); + } + if ( xmpDate.month < 1 ) xmpDate.month = 1; + if ( xmpDate.month > 12 ) xmpDate.month = 12; + + if ( ( chPos < dsInfo.dataLen ) && dsInfo.dataPtr[chPos] == '-' ) ++chPos; + for ( digits = 0; digits < 2; ++digits, ++chPos ) { + if ( (chPos >= dsInfo.dataLen) || (dsInfo.dataPtr[chPos] < '0') || (dsInfo.dataPtr[chPos] > '9') ) break; + xmpDate.day = (xmpDate.day * 10) + (dsInfo.dataPtr[chPos] - '0'); + } + if ( xmpDate.day < 1 ) xmpDate.day = 1; + if ( xmpDate.day > 31 ) xmpDate.day = 28; // Close enough. + + if ( chPos != dsInfo.dataLen ) return; // The DataSet is ill-formed. + xmpDate.hasDate = true; + + // Now add the time portion if present. + + count = iptc.GetDataSet ( timeID, &dsInfo ); + if ( count != 0 && dsInfo.dataLen > 0 ) { + + chPos = 0; + for ( digits = 0; digits < 2; ++digits, ++chPos ) { + if ( (chPos >= dsInfo.dataLen) || (dsInfo.dataPtr[chPos] < '0') || (dsInfo.dataPtr[chPos] > '9') ) break; + xmpDate.hour = (xmpDate.hour * 10) + (dsInfo.dataPtr[chPos] - '0'); + } + if ( xmpDate.hour < 0 ) xmpDate.hour = 0; + if ( xmpDate.hour > 23 ) xmpDate.hour = 23; + + if ( ( chPos < dsInfo.dataLen ) && dsInfo.dataPtr[chPos] == ':' ) ++chPos; + for ( digits = 0; digits < 2; ++digits, ++chPos ) { + if ( (chPos >= dsInfo.dataLen) || (dsInfo.dataPtr[chPos] < '0') || (dsInfo.dataPtr[chPos] > '9') ) break; + xmpDate.minute = (xmpDate.minute * 10) + (dsInfo.dataPtr[chPos] - '0'); + } + if ( xmpDate.minute < 0 ) xmpDate.minute = 0; + if ( xmpDate.minute > 59 ) xmpDate.minute = 59; + + if ( ( chPos < dsInfo.dataLen ) && dsInfo.dataPtr[chPos] == ':' ) ++chPos; + for ( digits = 0; digits < 2; ++digits, ++chPos ) { + if ( (chPos >= dsInfo.dataLen) || (dsInfo.dataPtr[chPos] < '0') || (dsInfo.dataPtr[chPos] > '9') ) break; + xmpDate.second = (xmpDate.second * 10) + (dsInfo.dataPtr[chPos] - '0'); + } + if ( xmpDate.second < 0 ) xmpDate.second = 0; + if ( xmpDate.second > 59 ) xmpDate.second = 59; + + xmpDate.hasTime = true; + + if ( ( chPos < dsInfo.dataLen ) && (dsInfo.dataPtr[chPos] != ' ') && (dsInfo.dataPtr[chPos] != 0) ) { // Tolerate a missing TZ. + + if ( ( chPos < dsInfo.dataLen ) && ( dsInfo.dataPtr[chPos] == '+' ) ) { + xmpDate.tzSign = kXMP_TimeEastOfUTC; + } else if ( ( chPos < dsInfo.dataLen ) && dsInfo.dataPtr[chPos] == '-' ) { + xmpDate.tzSign = kXMP_TimeWestOfUTC; + } else if ( chPos != dsInfo.dataLen ) { + return; // The DataSet is ill-formed. + } + + ++chPos; // Move past the time zone sign. + for ( digits = 0; digits < 2; ++digits, ++chPos ) { + if ( (chPos >= dsInfo.dataLen) || (dsInfo.dataPtr[chPos] < '0') || (dsInfo.dataPtr[chPos] > '9') ) break; + xmpDate.tzHour = (xmpDate.tzHour * 10) + (dsInfo.dataPtr[chPos] - '0'); + } + if ( xmpDate.tzHour < 0 ) xmpDate.tzHour = 0; + if ( xmpDate.tzHour > 23 ) xmpDate.tzHour = 23; + + if ( ( chPos < dsInfo.dataLen ) && dsInfo.dataPtr[chPos] == ':' ) ++chPos; + for ( digits = 0; digits < 2; ++digits, ++chPos ) { + if ( (chPos >= dsInfo.dataLen) || (dsInfo.dataPtr[chPos] < '0') || (dsInfo.dataPtr[chPos] > '9') ) break; + xmpDate.tzMinute = (xmpDate.tzMinute * 10) + (dsInfo.dataPtr[chPos] - '0'); + } + if ( xmpDate.tzMinute < 0 ) xmpDate.tzMinute = 0; + if ( xmpDate.tzMinute > 59 ) xmpDate.tzMinute = 59; + + if ( chPos != dsInfo.dataLen ) return; // The DataSet is ill-formed. + xmpDate.hasTimeZone = true; + + } + + } + + // Finally, set the XMP property. + + xmp->SetProperty_Date ( xmpNS, xmpProp, xmpDate ); + +} // PhotoDataUtils::ImportIPTC_Date + +// ================================================================================================= +// ImportIPTC_IntellectualGenre +// ============================ +// +// Import DataSet 2:04. In the IIM this is a 3 digit number, a colon, and an optional text name. +// Even though the number is the more formal part, the IPTC4XMP rule is that the name is imported to +// XMP and the number is dropped. Also, even though IIMv4.1 says that 2:04 is repeatable, the XMP +// property to which it is mapped is simple. + +static void ImportIPTC_IntellectualGenre ( const IPTC_Manager & iptc, SXMPMeta * xmp ) +{ + std::string utf8Str; + size_t count = iptc.GetDataSet_UTF8 ( kIPTC_IntellectualGenre, &utf8Str ); + + if ( count == 0 ) return; + NormalizeToLF ( &utf8Str ); + + XMP_StringPtr namePtr = utf8Str.c_str() + 4; + + if ( utf8Str.size() <= 4 ) { + // No name in the IIM. Look up the number in our list of known genres. + int i; + XMP_StringPtr numPtr = utf8Str.c_str(); + for ( i = 0; kIntellectualGenreMappings[i].refNum != 0; ++i ) { + if ( strncmp ( numPtr, kIntellectualGenreMappings[i].refNum, 3 ) == 0 ) break; + } + if ( kIntellectualGenreMappings[i].refNum == 0 ) return; + namePtr = kIntellectualGenreMappings[i].name; + } + + xmp->SetProperty ( kXMP_NS_IPTCCore, "IntellectualGenre", namePtr ); + +} // ImportIPTC_IntellectualGenre + +// ================================================================================================= +// ImportIPTC_SubjectCode +// ====================== +// +// Import all 2:12 DataSets into an unordered array. In the IIM each DataSet is composed of 5 colon +// separated sections: a provider name, an 8 digit reference number, and 3 optional names for the +// levels of the reference number hierarchy. The IPTC4XMP mapping rule is that only the reference +// number is imported to XMP. + +static void ImportIPTC_SubjectCode ( const IPTC_Manager & iptc, SXMPMeta * xmp ) +{ + std::string utf8Str; + size_t count = iptc.GetDataSet_UTF8 ( kIPTC_SubjectCode, 0 ); + + for ( size_t ds = 0; ds < count; ++ds ) { + + (void) iptc.GetDataSet_UTF8 ( kIPTC_SubjectCode, &utf8Str, ds ); + + char * refNumPtr = (char*) utf8Str.c_str(); + for ( ; (*refNumPtr != ':') && (*refNumPtr != 0); ++refNumPtr ) {} + if ( *refNumPtr == 0 ) continue; // This DataSet is ill-formed. + + char * refNumEnd = refNumPtr + 1; + for ( ; (*refNumEnd != ':') && (*refNumEnd != 0); ++refNumEnd ) {} + if ( (refNumEnd - refNumPtr) != 8 ) continue; // This DataSet is ill-formed. + *refNumEnd = 0; // Ensure a terminating nul for the reference number portion. + + xmp->AppendArrayItem ( kXMP_NS_IPTCCore, "SubjectCode", kXMP_PropArrayIsUnordered, refNumPtr ); + + } + +} // ImportIPTC_SubjectCode + +// ================================================================================================= +// PhotoDataUtils::Import2WayIPTC +// ============================== + +void PhotoDataUtils::Import2WayIPTC ( const IPTC_Manager & iptc, SXMPMeta * xmp, int iptcDigestState ) +{ + std::string oldStr, newStr; + IPTC_Writer oldIPTC; + + if ( iptcDigestState == kDigestDiffers ) { + PhotoDataUtils::ExportIPTC ( *xmp, &oldIPTC ); // Predict old IPTC DataSets based on the existing XMP. + } + + size_t newCount; + IPTC_Manager::DataSetInfo newInfo, oldInfo; + + for ( size_t i = 0; kKnownDataSets[i].dsNum != 255; ++i ) { + + const DataSetCharacteristics & thisDS = kKnownDataSets[i]; + if ( thisDS.mapForm >= kIPTC_Map3Way ) continue; // The mapping is handled elsewhere, or not at all. + + bool haveXMP = xmp->DoesPropertyExist ( thisDS.xmpNS, thisDS.xmpProp ); + newCount = PhotoDataUtils::GetNativeInfo ( iptc, thisDS.dsNum, iptcDigestState, haveXMP, &newInfo ); + if ( ( newCount == 0 ) || ( newInfo.dataLen == 0 ) ) continue; // GetNativeInfo returns 0 for ignored local text. + // For no data in dataset, don't import or delete anything + if ( iptcDigestState == kDigestMissing || iptcDigestState == kDigestMatches ) { + if ( haveXMP ) continue; // Keep the existing XMP. + } else if ( ! PhotoDataUtils::IsValueDifferent ( iptc, oldIPTC, thisDS.dsNum ) ) { + continue; // Don't import values that match the previous export. + } + + // The IPTC wins. Delete any existing XMP and import the DataSet. + + xmp->DeleteProperty ( thisDS.xmpNS, thisDS.xmpProp ); + + try { // Don't let errors with one stop the others. + + switch ( thisDS.mapForm ) { + + case kIPTC_MapSimple : + ImportIPTC_Simple ( iptc, xmp, thisDS.dsNum, thisDS.xmpNS, thisDS.xmpProp ); + break; + + case kIPTC_MapLangAlt : + ImportIPTC_LangAlt ( iptc, xmp, thisDS.dsNum, thisDS.xmpNS, thisDS.xmpProp ); + break; + + case kIPTC_MapArray : + ImportIPTC_Array ( iptc, xmp, thisDS.dsNum, thisDS.xmpNS, thisDS.xmpProp ); + break; + + case kIPTC_MapSpecial : + if ( thisDS.dsNum == kIPTC_DateCreated ) { + PhotoDataUtils::ImportIPTC_Date ( thisDS.dsNum, iptc, xmp ); + } else if ( thisDS.dsNum == kIPTC_IntellectualGenre ) { + ImportIPTC_IntellectualGenre ( iptc, xmp ); + } else if ( thisDS.dsNum == kIPTC_SubjectCode ) { + ImportIPTC_SubjectCode ( iptc, xmp ); + } else { + XMP_Assert ( false ); // Catch mapping errors. + } + break; + + } + + } catch ( ... ) { + + // Do nothing, let other imports proceed. + // ? Notify client? + + } + + } + +} // PhotoDataUtils::Import2WayIPTC + +// ================================================================================================= +// PhotoDataUtils::ImportPSIR +// ========================== +// +// There are only 2 standalone Photoshop image resources for XMP properties: +// 1034 - Copyright Flag - 0/1 Boolean mapped to xmpRights:Marked. +// 1035 - Copyright URL - Local OS text mapped to xmpRights:WebStatement. + +// ! Photoshop does not use a true/false/missing model for PSIR 1034. Instead it essentially uses a +// ! yes/don't-know model when importing. A missing or 0 value for PSIR 1034 cause xmpRights:Marked +// ! to be deleted. + +void PhotoDataUtils::ImportPSIR ( const PSIR_Manager & psir, SXMPMeta * xmp, int iptcDigestState ) +{ + PSIR_Manager::ImgRsrcInfo rsrcInfo; + bool import; + + if ( iptcDigestState == kDigestMatches ) return; + + try { // Don't let errors with one stop the others. + import = psir.GetImgRsrc ( kPSIR_CopyrightFlag, &rsrcInfo ); + if ( import ) import = (! xmp->DoesPropertyExist ( kXMP_NS_XMP_Rights, "Marked" )); + if ( import && (rsrcInfo.dataLen == 1) && (*((XMP_Uns8*)rsrcInfo.dataPtr) != 0) ) { + xmp->SetProperty_Bool ( kXMP_NS_XMP_Rights, "Marked", true ); + } + } catch ( ... ) { + // Do nothing, let other imports proceed. + // ? Notify client? + } + + try { // Don't let errors with one stop the others. + import = psir.GetImgRsrc ( kPSIR_CopyrightURL, &rsrcInfo ); + if ( import ) import = (! xmp->DoesPropertyExist ( kXMP_NS_XMP_Rights, "WebStatement" )); + if ( import ) { + std::string utf8; + if ( ReconcileUtils::IsUTF8 ( rsrcInfo.dataPtr, rsrcInfo.dataLen ) ) { + utf8.assign ( (char*)rsrcInfo.dataPtr, rsrcInfo.dataLen ); + } else if ( ! ignoreLocalText ) { + ReconcileUtils::LocalToUTF8 ( rsrcInfo.dataPtr, rsrcInfo.dataLen, &utf8 ); + } else { + import = false; // Inhibit the SetProperty call. + } + if ( import ) xmp->SetProperty ( kXMP_NS_XMP_Rights, "WebStatement", utf8.c_str() ); + } + } catch ( ... ) { + // Do nothing, let other imports proceed. + // ? Notify client? + } + +} // PhotoDataUtils::ImportPSIR; + +// ================================================================================================= +// ================================================================================================= + +// ================================================================================================= +// ExportIPTC_Simple +// ================= + +static void ExportIPTC_Simple ( const SXMPMeta & xmp, IPTC_Manager * iptc, + const char * xmpNS, const char * xmpProp, XMP_Uns8 id ) +{ + std::string value; + XMP_OptionBits xmpFlags; + + bool found = xmp.GetProperty ( xmpNS, xmpProp, &value, &xmpFlags ); + if ( ! found ) { + iptc->DeleteDataSet ( id ); + return; + } + + if ( ! XMP_PropIsSimple ( xmpFlags ) ) return; // ? Complain? Delete the DataSet? + + NormalizeToCR ( &value ); + + size_t iptcCount = iptc->GetDataSet ( id, 0 ); + if ( iptcCount > 1 ) iptc->DeleteDataSet ( id ); + + iptc->SetDataSet_UTF8 ( id, value.c_str(), (XMP_Uns32)value.size(), 0 ); // ! Don't append a 2nd DataSet! + +} // ExportIPTC_Simple + +// ================================================================================================= +// ExportIPTC_LangAlt +// ================== + +static void ExportIPTC_LangAlt ( const SXMPMeta & xmp, IPTC_Manager * iptc, + const char * xmpNS, const char * xmpProp, XMP_Uns8 id ) +{ + std::string value; + XMP_OptionBits xmpFlags; + + bool found = xmp.GetProperty ( xmpNS, xmpProp, 0, &xmpFlags ); + if ( ! found ) { + iptc->DeleteDataSet ( id ); + return; + } + + if ( ! XMP_ArrayIsAltText ( xmpFlags ) ) return; // ? Complain? Delete the DataSet? + + found = xmp.GetLocalizedText ( xmpNS, xmpProp, "", "x-default", 0, &value, 0 ); + if ( ! found ) { + iptc->DeleteDataSet ( id ); + return; + } + + NormalizeToCR ( &value ); + + size_t iptcCount = iptc->GetDataSet ( id, 0 ); + if ( iptcCount > 1 ) iptc->DeleteDataSet ( id ); + + iptc->SetDataSet_UTF8 ( id, value.c_str(), (XMP_Uns32)value.size(), 0 ); // ! Don't append a 2nd DataSet! + +} // ExportIPTC_LangAlt + +// ================================================================================================= +// ExportIPTC_Array +// ================ +// +// Array exporting needs a bit of care to preserve the detection of XMP-only updates. If the current +// XMP and IPTC array sizes differ, delete the entire IPTC and append all new values. If they match, +// set the individual values in order - which lets SetDataSet apply its no-change optimization. + +static void ExportIPTC_Array ( const SXMPMeta & xmp, IPTC_Manager * iptc, + const char * xmpNS, const char * xmpProp, XMP_Uns8 id ) +{ + std::string value; + XMP_OptionBits xmpFlags; + + bool found = xmp.GetProperty ( xmpNS, xmpProp, 0, &xmpFlags ); + if ( ! found ) { + iptc->DeleteDataSet ( id ); + return; + } + + if ( ! XMP_PropIsArray ( xmpFlags ) ) return; // ? Complain? Delete the DataSet? + + XMP_Index xmpCount = xmp.CountArrayItems ( xmpNS, xmpProp ); + XMP_Index iptcCount = (XMP_Index) iptc->GetDataSet ( id, 0 ); + + if ( xmpCount != iptcCount ) iptc->DeleteDataSet ( id ); + + for ( XMP_Index ds = 0; ds < xmpCount; ++ds ) { // ! XMP arrays are indexed from 1, IPTC from 0. + + (void) xmp.GetArrayItem ( xmpNS, xmpProp, ds+1, &value, &xmpFlags ); + if ( ! XMP_PropIsSimple ( xmpFlags ) ) continue; // ? Complain? + + NormalizeToCR ( &value ); + + iptc->SetDataSet_UTF8 ( id, value.c_str(), (XMP_Uns32)value.size(), ds ); // ! Appends if necessary. + + } + +} // ExportIPTC_Array + +// ================================================================================================= +// ExportIPTC_IntellectualGenre +// ============================ +// +// Export DataSet 2:04. In the IIM this is a 3 digit number, a colon, and a text name. Even though +// the number is the more formal part, the IPTC4XMP rule is that the name is imported to XMP and the +// number is dropped. Also, even though IIMv4.1 says that 2:04 is repeatable, the XMP property to +// which it is mapped is simple. Look up the XMP value in a list of known genres to get the number. + +static void ExportIPTC_IntellectualGenre ( const SXMPMeta & xmp, IPTC_Manager * iptc ) +{ + std::string xmpValue; + XMP_OptionBits xmpFlags; + + bool found = xmp.GetProperty ( kXMP_NS_IPTCCore, "IntellectualGenre", &xmpValue, &xmpFlags ); + if ( ! found ) { + iptc->DeleteDataSet ( kIPTC_IntellectualGenre ); + return; + } + + if ( ! XMP_PropIsSimple ( xmpFlags ) ) return; // ? Complain? Delete the DataSet? + + NormalizeToCR ( &xmpValue ); + + int i; + XMP_StringPtr namePtr = xmpValue.c_str(); + for ( i = 0; kIntellectualGenreMappings[i].name != 0; ++i ) { + if ( strcmp ( namePtr, kIntellectualGenreMappings[i].name ) == 0 ) break; + } + if ( kIntellectualGenreMappings[i].name == 0 ) return; // Not a known genre, don't export it. + + std::string iimValue = kIntellectualGenreMappings[i].refNum; + iimValue += ':'; + iimValue += xmpValue; + + size_t iptcCount = iptc->GetDataSet ( kIPTC_IntellectualGenre, 0 ); + if ( iptcCount > 1 ) iptc->DeleteDataSet ( kIPTC_IntellectualGenre ); + + iptc->SetDataSet_UTF8 ( kIPTC_IntellectualGenre, iimValue.c_str(), (XMP_Uns32)iimValue.size(), 0 ); // ! Don't append a 2nd DataSet! + +} // ExportIPTC_IntellectualGenre + +// ================================================================================================= +// ExportIPTC_SubjectCode +// ====================== +// +// Export 2:12 DataSets from an unordered array. In the IIM each DataSet is composed of 5 colon +// separated sections: a provider name, an 8 digit reference number, and 3 optional names for the +// levels of the reference number hierarchy. The IPTC4XMP mapping rule is that only the reference +// number is imported to XMP. We export with a fixed provider of "IPTC" and no optional names. + +static void ExportIPTC_SubjectCode ( const SXMPMeta & xmp, IPTC_Manager * iptc ) +{ + std::string xmpValue, iimValue; + XMP_OptionBits xmpFlags; + + bool found = xmp.GetProperty ( kXMP_NS_IPTCCore, "SubjectCode", 0, &xmpFlags ); + if ( ! found ) { + iptc->DeleteDataSet ( kIPTC_SubjectCode ); + return; + } + + if ( ! XMP_PropIsArray ( xmpFlags ) ) return; // ? Complain? Delete the DataSet? + + XMP_Index xmpCount = xmp.CountArrayItems ( kXMP_NS_IPTCCore, "SubjectCode" ); + XMP_Index iptcCount = (XMP_Index) iptc->GetDataSet ( kIPTC_SubjectCode, 0 ); + + if ( xmpCount != iptcCount ) iptc->DeleteDataSet ( kIPTC_SubjectCode ); + + for ( XMP_Index ds = 0; ds < xmpCount; ++ds ) { // ! XMP arrays are indexed from 1, IPTC from 0. + + (void) xmp.GetArrayItem ( kXMP_NS_IPTCCore, "SubjectCode", ds+1, &xmpValue, &xmpFlags ); + if ( ! XMP_PropIsSimple ( xmpFlags ) ) continue; // ? Complain? + if ( xmpValue.size() != 8 ) continue; // ? Complain? + + iimValue = "IPTC:"; + iimValue += xmpValue; + iimValue += ":::"; // Add the separating colons for the empty name portions. + + iptc->SetDataSet_UTF8 ( kIPTC_SubjectCode, iimValue.c_str(), (XMP_Uns32)iimValue.size(), ds ); // ! Appends if necessary. + + } + +} // ExportIPTC_SubjectCode + +// ================================================================================================= +// ExportIPTC_Date +// =============== +// +// The IPTC date and time are "YYYYMMDD" and "HHMMSSxHHMM" where 'x' is '+' or '-'. Export the IPTC +// time only if already present, or if the XMP has a time portion. + +// *** The date/time handling differs from the MWG 1.0 policy, following a proposed tweak to MWG: +// *** Exif DateTimeOriginal <-> IPTC DateCreated <-> XMP photoshop:DateCreated +// *** Exif DateTimeDigitized <-> IPTC DigitalCreateDate <-> XMP xmp:CreateDate + +static void ExportIPTC_Date ( XMP_Uns8 dateID, const SXMPMeta & xmp, IPTC_Manager * iptc ) +{ + XMP_Uns8 timeID; + XMP_StringPtr xmpNS, xmpProp; + + if ( dateID == kIPTC_DateCreated ) { + timeID = kIPTC_TimeCreated; + xmpNS = kXMP_NS_Photoshop; + xmpProp = "DateCreated"; + } else if ( dateID == kIPTC_DigitalCreateDate ) { + timeID = kIPTC_DigitalCreateTime; + xmpNS = kXMP_NS_XMP; + xmpProp = "CreateDate"; + } else { + XMP_Throw ( "Unrecognized dateID", kXMPErr_BadParam ); + } + + iptc->DeleteDataSet ( dateID ); // ! Either the XMP does not exist and we want to + iptc->DeleteDataSet ( timeID ); // ! delete the IPTC, or we're replacing the IPTC. + + XMP_DateTime xmpValue; + bool found = xmp.GetProperty_Date ( xmpNS, xmpProp, &xmpValue, 0 ); + if ( ! found ) return; + + char iimValue[16]; // AUDIT: Big enough for "YYYYMMDD" (8) and "HHMMSS+HHMM" (11). + + // Set the IIM date portion as YYYYMMDD with zeroes for unknown parts. + + snprintf ( iimValue, sizeof(iimValue), "%04d%02d%02d", // AUDIT: Use of sizeof(iimValue) is safe. + xmpValue.year, xmpValue.month, xmpValue.day ); + + iptc->SetDataSet_UTF8 ( dateID, iimValue, 8 ); + + // Set the IIM time portion as HHMMSS+HHMM (or -HHMM). Allow a missing time zone. + + if ( xmpValue.hasTimeZone ) { + snprintf ( iimValue, sizeof(iimValue), "%02d%02d%02d%c%02d%02d", // AUDIT: Use of sizeof(iimValue) is safe. + xmpValue.hour, xmpValue.minute, xmpValue.second, + ((xmpValue.tzSign == kXMP_TimeWestOfUTC) ? '-' : '+'), xmpValue.tzHour, xmpValue.tzMinute ); + iptc->SetDataSet_UTF8 ( timeID, iimValue, 11 ); + } else if ( xmpValue.hasTime ) { + snprintf ( iimValue, sizeof(iimValue), "%02d%02d%02d", // AUDIT: Use of sizeof(iimValue) is safe. + xmpValue.hour, xmpValue.minute, xmpValue.second ); + iptc->SetDataSet_UTF8 ( timeID, iimValue, 6 ); + } else { + iptc->DeleteDataSet ( timeID ); + } + +} // ExportIPTC_Date + +// ================================================================================================= +// PhotoDataUtils::ExportIPTC +// ========================== + +void PhotoDataUtils::ExportIPTC ( const SXMPMeta & xmp, IPTC_Manager * iptc ) +{ + + for ( size_t i = 0; kKnownDataSets[i].dsNum != 255; ++i ) { + + try { // Don't let errors with one stop the others. + + const DataSetCharacteristics & thisDS = kKnownDataSets[i]; + if ( thisDS.mapForm >= kIPTC_UnmappedText ) continue; + + switch ( thisDS.mapForm ) { + + case kIPTC_MapSimple : + ExportIPTC_Simple ( xmp, iptc, thisDS.xmpNS, thisDS.xmpProp, thisDS.dsNum ); + break; + + case kIPTC_MapLangAlt : + ExportIPTC_LangAlt ( xmp, iptc, thisDS.xmpNS, thisDS.xmpProp, thisDS.dsNum ); + break; + + case kIPTC_MapArray : + ExportIPTC_Array ( xmp, iptc, thisDS.xmpNS, thisDS.xmpProp, thisDS.dsNum ); + break; + + case kIPTC_MapSpecial : + if ( thisDS.dsNum == kIPTC_DateCreated ) { + ExportIPTC_Date ( thisDS.dsNum, xmp, iptc ); + } else if ( thisDS.dsNum == kIPTC_IntellectualGenre ) { + ExportIPTC_IntellectualGenre ( xmp, iptc ); + } else if ( thisDS.dsNum == kIPTC_SubjectCode ) { + ExportIPTC_SubjectCode ( xmp, iptc ); + } else { + XMP_Assert ( false ); // Catch mapping errors. + } + break; + + case kIPTC_Map3Way : // The 3 way case is special for import, not for export. + if ( thisDS.dsNum == kIPTC_DigitalCreateDate ) { + // ! Special case: Don't create IIM DigitalCreateDate. This can avoid PSD + // ! full rewrite due to new mapping from xmp:CreateDate. + if ( iptc->GetDataSet ( thisDS.dsNum, 0 ) > 0 ) ExportIPTC_Date ( thisDS.dsNum, xmp, iptc ); + } else if ( thisDS.dsNum == kIPTC_Creator ) { + ExportIPTC_Array ( xmp, iptc, kXMP_NS_DC, "creator", kIPTC_Creator ); + } else if ( thisDS.dsNum == kIPTC_CopyrightNotice ) { + ExportIPTC_LangAlt ( xmp, iptc, kXMP_NS_DC, "rights", kIPTC_CopyrightNotice ); + } else if ( thisDS.dsNum == kIPTC_Description ) { + ExportIPTC_LangAlt ( xmp, iptc, kXMP_NS_DC, "description", kIPTC_Description ); + } else { + XMP_Assert ( false ); // Catch mapping errors. + } + + } + + } catch ( ... ) { + + // Do nothing, let other exports proceed. + // ? Notify client? + + } + + } + +} // PhotoDataUtils::ExportIPTC; + +// ================================================================================================= +// PhotoDataUtils::ExportPSIR +// ========================== +// +// There are only 2 standalone Photoshop image resources for XMP properties: +// 1034 - Copyright Flag - 0/1 Boolean mapped to xmpRights:Marked. +// 1035 - Copyright URL - Local OS text mapped to xmpRights:WebStatement. + +// ! We don't bother with the CR<->LF normalization for xmpRights:WebStatement. Very little chance +// ! of having a raw CR character in a URI. + +void PhotoDataUtils::ExportPSIR ( const SXMPMeta & xmp, PSIR_Manager * psir ) +{ + bool found; + std::string utf8Value; + + try { // Don't let errors with one stop the others. + found = xmp.GetProperty ( kXMP_NS_XMP_Rights, "Marked", &utf8Value, 0 ); + if ( ! found ) { + psir->DeleteImgRsrc ( kPSIR_CopyrightFlag ); + } else { + bool copyrighted = SXMPUtils::ConvertToBool ( utf8Value ); + psir->SetImgRsrc ( kPSIR_CopyrightFlag, ©righted, 1 ); + } + } catch ( ... ) { + // Do nothing, let other exports proceed. + // ? Notify client? + } + + try { // Don't let errors with one stop the others. + found = xmp.GetProperty ( kXMP_NS_XMP_Rights, "WebStatement", &utf8Value, 0 ); + if ( ! found ) { + psir->DeleteImgRsrc ( kPSIR_CopyrightURL ); + } else if ( ! ignoreLocalText ) { + std::string localValue; + ReconcileUtils::UTF8ToLocal ( utf8Value.c_str(), utf8Value.size(), &localValue ); + psir->SetImgRsrc ( kPSIR_CopyrightURL, localValue.c_str(), (XMP_Uns32)localValue.size() ); + } else if ( ReconcileUtils::IsASCII ( utf8Value.c_str(), utf8Value.size() ) ) { + psir->SetImgRsrc ( kPSIR_CopyrightURL, utf8Value.c_str(), (XMP_Uns32)utf8Value.size() ); + } else { + psir->DeleteImgRsrc ( kPSIR_CopyrightURL ); + } + } catch ( ... ) { + // Do nothing, let other exports proceed. + // ? Notify client? + } + +} // PhotoDataUtils::ExportPSIR; diff --git a/core/libs/dngwriter/extra/xmp_sdk/XMPFiles/source/FormatSupport/ReconcileLegacy.cpp b/core/libs/dngwriter/extra/xmp_sdk/XMPFiles/source/FormatSupport/ReconcileLegacy.cpp new file mode 100644 index 0000000000..e71af09113 --- /dev/null +++ b/core/libs/dngwriter/extra/xmp_sdk/XMPFiles/source/FormatSupport/ReconcileLegacy.cpp @@ -0,0 +1,205 @@ +// ================================================================================================= +// ADOBE SYSTEMS INCORPORATED +// Copyright 2006 Adobe Systems Incorporated +// All Rights Reserved +// +// NOTICE: Adobe permits you to use, modify, and distribute this file in accordance with the terms +// of the Adobe license agreement accompanying it. +// ================================================================================================= + +#include "public/include/XMP_Environment.h" // ! XMP_Environment.h must be the first included header. +#include "public/include/XMP_Const.h" + +#include "XMPFiles/source/FormatSupport/ReconcileLegacy.hpp" +#include "XMPFiles/source/FormatSupport/Reconcile_Impl.hpp" +#include "source/XIO.hpp" + +// ================================================================================================= +/// \file ReconcileLegacy.cpp +/// \brief Top level parts of utilities to reconcile between XMP and legacy metadata forms such as +/// TIFF/Exif and IPTC. +/// +// ================================================================================================= + +// ================================================================================================= +// ImportPhotoData +// =============== +// +// Import legacy metadata for JPEG, TIFF, and Photoshop files into the XMP. The caller must have +// already done the file specific processing to select the appropriate sources of the TIFF stream, +// the Photoshop image resources, and the IPTC. + +#define SaveExifTag(ns,prop) \ + if ( xmp->DoesPropertyExist ( ns, prop ) ) SXMPUtils::DuplicateSubtree ( *xmp, &savedExif, ns, prop ) +#define RestoreExifTag(ns,prop) \ + if ( savedExif.DoesPropertyExist ( ns, prop ) ) SXMPUtils::DuplicateSubtree ( savedExif, xmp, ns, prop ) + +void ImportPhotoData ( const TIFF_Manager & exif, + const IPTC_Manager & iptc, + const PSIR_Manager & psir, + int iptcDigestState, + SXMPMeta * xmp, + XMP_OptionBits options /* = 0 */ ) +{ + bool haveXMP = XMP_OptionIsSet ( options, k2XMP_FileHadXMP ); + bool haveExif = XMP_OptionIsSet ( options, k2XMP_FileHadExif ); + bool haveIPTC = XMP_OptionIsSet ( options, k2XMP_FileHadIPTC ); + + // Save some new Exif writebacks that can be XMP-only from older versions, delete all of the + // XMP's tiff: and exif: namespaces (they should only reflect native Exif), then put back the + // saved writebacks (which might get replaced by the native Exif values in the Import calls). + // The value of exif:ISOSpeedRatings is saved for special case handling of ISO over 65535. + + bool haveOldExif = true; // Default to old Exif if no version tag. + TIFF_Manager::TagInfo tagInfo; + bool found = exif.GetTag ( kTIFF_ExifIFD, kTIFF_ExifVersion, &tagInfo ); + if ( found && (tagInfo.type == kTIFF_UndefinedType) && (tagInfo.count == 4) ) { + haveOldExif = (strncmp ( (char*)tagInfo.dataPtr, "0230", 4 ) < 0); + } + + SXMPMeta savedExif; + + SaveExifTag ( kXMP_NS_EXIF, "DateTimeOriginal" ); + SaveExifTag ( kXMP_NS_EXIF, "GPSLatitude" ); + SaveExifTag ( kXMP_NS_EXIF, "GPSLongitude" ); + SaveExifTag ( kXMP_NS_EXIF, "GPSTimeStamp" ); + SaveExifTag ( kXMP_NS_EXIF, "GPSAltitude" ); + SaveExifTag ( kXMP_NS_EXIF, "GPSAltitudeRef" ); + SaveExifTag ( kXMP_NS_EXIF, "ISOSpeedRatings" ); + + SXMPUtils::RemoveProperties ( xmp, kXMP_NS_TIFF, 0, kXMPUtil_DoAllProperties ); + SXMPUtils::RemoveProperties ( xmp, kXMP_NS_EXIF, 0, kXMPUtil_DoAllProperties ); + if ( ! haveOldExif ) SXMPUtils::RemoveProperties ( xmp, kXMP_NS_ExifEX, 0, kXMPUtil_DoAllProperties ); + + RestoreExifTag ( kXMP_NS_EXIF, "DateTimeOriginal" ); + RestoreExifTag ( kXMP_NS_EXIF, "GPSLatitude" ); + RestoreExifTag ( kXMP_NS_EXIF, "GPSLongitude" ); + RestoreExifTag ( kXMP_NS_EXIF, "GPSTimeStamp" ); + RestoreExifTag ( kXMP_NS_EXIF, "GPSAltitude" ); + RestoreExifTag ( kXMP_NS_EXIF, "GPSAltitudeRef" ); + RestoreExifTag ( kXMP_NS_EXIF, "ISOSpeedRatings" ); + + // Not obvious here, but the logic in PhotoDataUtils follows the MWG reader guidelines. + + PhotoDataUtils::ImportPSIR ( psir, xmp, iptcDigestState ); + + if ( haveIPTC ) PhotoDataUtils::Import2WayIPTC ( iptc, xmp, iptcDigestState ); + if ( haveExif ) PhotoDataUtils::Import2WayExif ( exif, xmp, iptcDigestState ); + + if ( haveExif | haveIPTC ) PhotoDataUtils::Import3WayItems ( exif, iptc, xmp, iptcDigestState ); + + // If photoshop:DateCreated does not exist try to create it from exif:DateTimeOriginal. + + if ( ! xmp->DoesPropertyExist ( kXMP_NS_Photoshop, "DateCreated" ) ) { + std::string exifValue; + bool haveExifDTO = xmp->GetProperty ( kXMP_NS_EXIF, "DateTimeOriginal", &exifValue, 0 ); + if ( haveExifDTO ) xmp->SetProperty ( kXMP_NS_Photoshop, "DateCreated", exifValue.c_str() ); + } + +} // ImportPhotoData + +// ================================================================================================= +// ExportPhotoData +// =============== + +void ExportPhotoData ( XMP_FileFormat destFormat, + SXMPMeta * xmp, + TIFF_Manager * exif, // Pass 0 if not wanted. + IPTC_Manager * iptc, // Pass 0 if not wanted. + PSIR_Manager * psir, // Pass 0 if not wanted. + XMP_OptionBits options /* = 0 */ ) +{ + XMP_Assert ( (destFormat == kXMP_JPEGFile) || (destFormat == kXMP_TIFFFile) || (destFormat == kXMP_PhotoshopFile) ); + + // Do not write IPTC-IIM or PSIR in DNG files (which are a variant of TIFF). + + if ( (destFormat == kXMP_TIFFFile) && (exif != 0) && + exif->GetTag ( kTIFF_PrimaryIFD, kTIFF_DNGVersion, 0 ) ) { + + iptc = 0; // These prevent calls to ExportIPTC and ExportPSIR. + psir = 0; + + exif->DeleteTag ( kTIFF_PrimaryIFD, kTIFF_IPTC ); // These remove any existing IPTC and PSIR. + exif->DeleteTag ( kTIFF_PrimaryIFD, kTIFF_PSIR ); + + } + + // Export the individual metadata items to the non-XMP forms. Set the IPTC digest whether or not + // it changed, it might not have been present or correct before. + + bool iptcChanged = false; // Save explicitly, internal flag is reset by UpdateMemoryDataSets. + + void * iptcPtr = 0; + XMP_Uns32 iptcLen = 0; + + if ( iptc != 0 ) { + PhotoDataUtils::ExportIPTC ( *xmp, iptc ); + iptcChanged = iptc->IsChanged(); + if ( iptcChanged ) iptc->UpdateMemoryDataSets(); + iptcLen = iptc->GetBlockInfo ( &iptcPtr ); + if ( psir != 0 ) PhotoDataUtils::SetIPTCDigest ( iptcPtr, iptcLen, psir ); + } + + if ( exif != 0 ) PhotoDataUtils::ExportExif ( xmp, exif ); + if ( psir != 0 ) PhotoDataUtils::ExportPSIR ( *xmp, psir ); + + // Now update the non-XMP collections of metadata according to the file format. Do not update + // the XMP here, that is done in the file handlers after deciding if an XMP-only in-place + // update should be done. + // - JPEG has the IPTC in PSIR 1028, the Exif and PSIR are marker segments. + // - TIFF has the IPTC and PSIR in primary IFD tags. + // - PSD has everything in PSIRs. + + if ( destFormat == kXMP_JPEGFile ) { + + if ( iptcChanged && (psir != 0) ) psir->SetImgRsrc ( kPSIR_IPTC, iptcPtr, iptcLen ); + + } else if ( destFormat == kXMP_TIFFFile ) { + + XMP_Assert ( exif != 0 ); + + if ( iptcChanged ) exif->SetTag ( kTIFF_PrimaryIFD, kTIFF_IPTC, kTIFF_UndefinedType, iptcLen, iptcPtr ); + + if ( (psir != 0) && psir->IsChanged() ) { + void* psirPtr; + XMP_Uns32 psirLen = psir->UpdateMemoryResources ( &psirPtr ); + exif->SetTag ( kTIFF_PrimaryIFD, kTIFF_PSIR, kTIFF_UndefinedType, psirLen, psirPtr ); + } + + } else if ( destFormat == kXMP_PhotoshopFile ) { + + XMP_Assert ( psir != 0 ); + + if ( iptcChanged ) psir->SetImgRsrc ( kPSIR_IPTC, iptcPtr, iptcLen ); + + if ( (exif != 0) && exif->IsChanged() ) { + void* exifPtr; + XMP_Uns32 exifLen = exif->UpdateMemoryStream ( &exifPtr ); + psir->SetImgRsrc ( kPSIR_Exif, exifPtr, exifLen ); + } + + } + + // Strip the tiff: and exif: namespaces from the XMP, we're done with them. Save the Exif + // ISOSpeedRatings if any of the values are over 0xFFFF, the native tag is SHORT. Lower level + // code already kept or stripped the XMP form. + + bool haveOldExif = true; // Default to old Exif if no version tag. + if ( exif != 0 ) { + TIFF_Manager::TagInfo tagInfo; + bool found = exif->GetTag ( kTIFF_ExifIFD, kTIFF_ExifVersion, &tagInfo ); + if ( found && (tagInfo.type == kTIFF_UndefinedType) && (tagInfo.count == 4) ) { + haveOldExif = (strncmp ( (char*)tagInfo.dataPtr, "0230", 4 ) < 0); + } + } + + SXMPMeta savedExif; + SaveExifTag ( kXMP_NS_EXIF, "ISOSpeedRatings" ); + + SXMPUtils::RemoveProperties ( xmp, kXMP_NS_TIFF, 0, kXMPUtil_DoAllProperties ); + SXMPUtils::RemoveProperties ( xmp, kXMP_NS_EXIF, 0, kXMPUtil_DoAllProperties ); + if ( ! haveOldExif ) SXMPUtils::RemoveProperties ( xmp, kXMP_NS_ExifEX, 0, kXMPUtil_DoAllProperties ); + + RestoreExifTag ( kXMP_NS_EXIF, "ISOSpeedRatings" ); + +} // ExportPhotoData diff --git a/core/libs/dngwriter/extra/xmp_sdk/XMPFiles/source/FormatSupport/ReconcileLegacy.hpp b/core/libs/dngwriter/extra/xmp_sdk/XMPFiles/source/FormatSupport/ReconcileLegacy.hpp new file mode 100644 index 0000000000..16333a38f8 --- /dev/null +++ b/core/libs/dngwriter/extra/xmp_sdk/XMPFiles/source/FormatSupport/ReconcileLegacy.hpp @@ -0,0 +1,272 @@ +#ifndef __ReconcileLegacy_hpp__ +#define __ReconcileLegacy_hpp__ 1 + +// ================================================================================================= +// ADOBE SYSTEMS INCORPORATED +// Copyright 2006 Adobe Systems Incorporated +// All Rights Reserved +// +// NOTICE: Adobe permits you to use, modify, and distribute this file in accordance with the terms +// of the Adobe license agreement accompanying it. +// ================================================================================================= + +#include "public/include/XMP_Environment.h" // ! This must be the first include. + +#include "XMPFiles/source/FormatSupport/TIFF_Support.hpp" +#include "XMPFiles/source/FormatSupport/PSIR_Support.hpp" +#include "XMPFiles/source/FormatSupport/IPTC_Support.hpp" + +// ================================================================================================= +/// \file ReconcileLegacy.hpp +/// \brief Utilities to reconcile between XMP and photo metadata forms such as TIFF/Exif and IPTC. +/// +// ================================================================================================= + +// ImportPhotoData imports TIFF/Exif and IPTC metadata from JPEG, TIFF, and Photoshop files into +// XMP. The caller must have already done the file specific processing to select the appropriate +// sources of the TIFF stream, the Photoshop image resources, and the IPTC. +// +// The reconciliation logic used here is based on the Metadata Working Group guidelines. This is a +// simpler approach than used previously - which was modeled after historical Photoshop behavior. + +enum { // Bits for the options to ImportJTPtoXMP. + k2XMP_FileHadXMP = 0x0001, // Set if the file had an XMP packet. + k2XMP_FileHadIPTC = 0x0002, // Set if the file had legacy IPTC. + k2XMP_FileHadExif = 0x0004 // Set if the file had legacy Exif. +}; + +extern void ImportPhotoData ( const TIFF_Manager & exif, + const IPTC_Manager & iptc, + const PSIR_Manager & psir, + int iptcDigestState, + SXMPMeta * xmp, + XMP_OptionBits options = 0 ); + +// ExportPhotoData exports XMP into TIFF/Exif and IPTC metadata for JPEG, TIFF, and Photoshop files. + +extern void ExportPhotoData ( XMP_FileFormat destFormat, + SXMPMeta * xmp, + TIFF_Manager * exif, // Pass 0 if not wanted. + IPTC_Manager * iptc, // Pass 0 if not wanted. + PSIR_Manager * psir, // Pass 0 if not wanted. + XMP_OptionBits options = 0 ); + +// *** Mapping notes need revision for MWG related changes. + +// ================================================================================================= +// Summary of TIFF/Exif mappings to XMP +// ==================================== +// +// The mapping for each tag is driven mainly by the tag ID, and secondarily by the type. E.g. there +// is no blanket rule that all ASCII tags are mapped to simple strings in XMP. Some, such as +// SubSecTime or GPSLatitudeRef, are combined with other tags; others, like Flash, are reformated. +// However, most tags are in fact mapped in an obvious manner based on their type and count. +// +// Photoshop practice has been to truncate ASCII tags at the first NUL, not supporting the TIFF +// specification's notion of multi-part ASCII values. +// +// Rational values are mapped to XMP as "num/denom". +// +// The tags of UNDEFINED type that are mapped to XMP text are either special cases like ExifVersion +// or the strings with an explicit encoding like UserComment. +// +// Latitude and logitude are mapped to XMP as "DDD,MM,SSk" or "DDD,MM.mmk"; k is N, S, E, or W. +// +// Flash struct in XMP separates the Fired, Return, Mode, Function, and RedEyeMode portions of the +// Exif value. Fired, Function, and RedEyeMode are Boolean; Return and Mode are integers. +// +// The OECF/SFR, CFA, and DeviceSettings tables are described in the XMP spec. +// +// Instead of iterating through all tags in the various IFDs, it is probably more efficient to have +// explicit processing for the tags that get special treatment, and a static table listing those +// that get mapped by type and count. The type and count processing will verify that the actual +// type and count are as expected, if not the tag is ignored. +// +// Here are the primary (0th) IFD tags that get special treatment: +// +// 270, 33432 - ASCII mapped to alt-text['x-default'] +// 306 - DateTime master +// 315 - ASCII mapped to text seq[1] +// +// Here are the primary (0th) IFD tags that get mapped by type and count: +// +// 256, 257, 258, 259, 262, 271, 272, 274, 277, 282, 283, 284, 296, 301, 305, 318, 319, +// 529, 530, 531, 532 +// +// Here are the Exif IFD tags that get special treatment: +// +// 34856, 41484 - OECF/SFR table +// 36864, 40960 - 4 ASCII chars to text +// 36867, 36868 - DateTime master +// 37121 - 4 UInt8 to integer seq +// 37385 - Flash struct +// 37510 - explicitly encoded text to alt-text['x-default'] +// 41728, 41729 - UInt8 to integer +// 41730 - CFA table +// 41995 - DeviceSettings table +// +// Here are the Exif IFD tags that get mapped by type and count: +// +// 33434, 33437, 34850, 34852, 34855, 37122, 37377, 37378, 37379, 37380, 37381, 37382, 37383, 37384, +// 37386, 37396, 40961, 40962, 40963, 40964, 41483, 41486, 41487, 41488, 41492, 41493, 41495, 41985, +// 41986, 41987, 41988, 41989, 41990, 41991, 41992, 41993, 41994, 41996, 42016 +// +// Here are the GPS IFD tags that get special treatment: +// +// 0 - 4 UInt8 to text "n.n.n.n" +// 2, 4, 20, 22 - Latitude or longitude master +// 7 - special DateTime master, the time part +// 27, 28 - explicitly encoded text +// +// Here are the GPS IFD tags that get mapped by type and count: +// +// 5, 6, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 23, 24, 25, 26, 30 +// ================================================================================================= + +// *** What about the Camera Raw tags that MDKit maps: +// *** 0xFDE8, 0xFDE9, 0xFDEA, 0xFE4C, 0xFE4D, 0xFE4E, 0xFE4F, 0xFE50, 0xFE51, 0xFE52, 0xFE53, +// *** 0xFE54, 0xFE55, 0xFE56, 0xFE57, 0xFE58 + +// ================================================================================================= +// Summary of TIFF/Exif mappings from XMP +// ====================================== +// +// Only a small number of properties are written back from XMP to TIFF/Exif. Most of the TIFF/Exif +// tags mapped into XMP are information about the image or capture process, not things that users +// should be editing. The tags that can be edited and written back to TIFF/Exif are: +// +// 270, 274, 282, 283, 296, 305, 306, 315, 33432; 36867, 36868, 37510, 40964 +// ================================================================================================= + +// ================================================================================================= +// Details of TIFF/Exif mappings +// ============================= +// +// General (primary and thumbnail, 0th and 1st) IFD tags +// tag TIFF type count Name XMP mapping +// +// 256 SHORTorLONG 1 ImageWidth integer +// 257 SHORTorLONG 1 ImageLength integer +// 258 SHORT 3 BitsPerSample integer seq +// 259 SHORT 1 Compression integer +// 262 SHORT 1 PhotometricInterpretation integer +// 270 ASCII Any ImageDescription text, dc:description['x-default'] +// 271 ASCII Any Make text +// 272 ASCII Any Model text +// 274 SHORT 1 Orientation integer +// 277 SHORT 1 SamplesPerPixel integer +// 282 RATIONAL 1 XResolution rational +// 283 RATIONAL 1 YResolution rational +// 284 SHORT 1 PlanarConfiguration integer +// 296 SHORT 1 ResolutionUnit integer +// 301 SHORT 3*256 TransferFunction integer seq +// 305 ASCII Any Software text, xmp:CreatorTool +// 306 ASCII 20 DateTime date, master of 37520, xmp:DateTime +// 315 ASCII Any Artist text, dc:creator[1] +// 318 RATIONAL 2 WhitePoint rational seq +// 319 RATIONAL 6 PrimaryChromaticities rational seq +// 529 RATIONAL 3 YCbCrCoefficients rational seq +// 530 SHORT 2 YCbCrSubSampling integer seq +// 531 SHORT 1 YCbCrPositioning integer +// 532 RATIONAL 6 ReferenceBlackWhite rational seq +// 33432 ASCII Any Copyright text, dc:rights['x-default'] +// +// Exif IFD tags +// tag TIFF type count Name XMP mapping +// +// 33434 RATIONAL 1 ExposureTime rational +// 33437 RATIONAL 1 FNumber rational +// 34850 SHORT 1 ExposureProgram integer +// 34852 ASCII Any SpectralSensitivity text +// 34855 SHORT Any ISOSpeedRatings integer seq +// 34856 UNDEFINED Any OECF OECF/SFR table +// 36864 UNDEFINED 4 ExifVersion text, Exif has 4 ASCII chars +// 36867 ASCII 20 DateTimeOriginal date, master of 37521 +// 36868 ASCII 20 DateTimeDigitized date, master of 37522 +// 37121 UNDEFINED 4 ComponentsConfiguration integer seq, Exif has 4 UInt8 +// 37122 RATIONAL 1 CompressedBitsPerPixel rational +// 37377 SRATIONAL 1 ShutterSpeedValue rational +// 37378 RATIONAL 1 ApertureValue rational +// 37379 SRATIONAL 1 BrightnessValue rational +// 37380 SRATIONAL 1 ExposureBiasValue rational +// 37381 RATIONAL 1 MaxApertureValue rational +// 37382 RATIONAL 1 SubjectDistance rational +// 37383 SHORT 1 MeteringMode integer +// 37384 SHORT 1 LightSource integer +// 37385 SHORT 1 Flash Flash struct +// 37386 RATIONAL 1 FocalLength rational +// 37396 SHORT 2..4 SubjectArea integer seq +// 37510 UNDEFINED Any UserComment text, explicit encoding, exif:UserComment['x-default] +// 37520 ASCII Any SubSecTime date, with 306 +// 37521 ASCII Any SubSecTimeOriginal date, with 36867 +// 37522 ASCII Any SubSecTimeDigitized date, with 36868 +// 40960 UNDEFINED 4 FlashpixVersion text, Exif has 4 ASCII chars +// 40961 SHORT 1 ColorSpace integer +// 40962 SHORTorLONG 1 PixelXDimension integer +// 40963 SHORTorLONG 1 PixelYDimension integer +// 40964 ASCII 13 RelatedSoundFile text +// 41483 RATIONAL 1 FlashEnergy rational +// 41484 UNDEFINED Any SpatialFrequencyResponse OECF/SFR table +// 41486 RATIONAL 1 FocalPlaneXResolution rational +// 41487 RATIONAL 1 FocalPlaneYResolution rational +// 41488 SHORT 1 FocalPlaneResolutionUnit integer +// 41492 SHORT 2 SubjectLocation integer seq +// 41493 RATIONAL 1 ExposureIndex rational +// 41495 SHORT 1 SensingMethod integer +// 41728 UNDEFINED 1 FileSource integer, Exif has UInt8 +// 41729 UNDEFINED 1 SceneType integer, Exif has UInt8 +// 41730 UNDEFINED Any CFAPattern CFA table +// 41985 SHORT 1 CustomRendered integer +// 41986 SHORT 1 ExposureMode integer +// 41987 SHORT 1 WhiteBalance integer +// 41988 RATIONAL 1 DigitalZoomRatio rational +// 41989 SHORT 1 FocalLengthIn35mmFilm integer +// 41990 SHORT 1 SceneCaptureType integer +// 41991 SHORT 1 GainControl integer +// 41992 SHORT 1 Contrast integer +// 41993 SHORT 1 Saturation integer +// 41994 SHORT 1 Sharpness integer +// 41995 UNDEFINED Any DeviceSettingDescription DeviceSettings table +// 41996 SHORT 1 SubjectDistanceRange integer +// 42016 ASCII 33 ImageUniqueID text +// +// GPS IFD tags +// tag TIFF type count Name XMP mapping +// +// 0 BYTE 4 GPSVersionID text, "n.n.n.n", Exif has 4 UInt8 +// 1 ASCII 2 GPSLatitudeRef latitude, with 2 +// 2 RATIONAL 3 GPSLatitude latitude, master of 2 +// 3 ASCII 2 GPSLongitudeRef longitude, with 4 +// 4 RATIONAL 3 GPSLongitude longitude, master of 3 +// 5 BYTE 1 GPSAltitudeRef integer +// 6 RATIONAL 1 GPSAltitude rational +// 7 RATIONAL 3 GPSTimeStamp date, master of 29 +// 8 ASCII Any GPSSatellites text +// 9 ASCII 2 GPSStatus text +// 10 ASCII 2 GPSMeasureMode text +// 11 RATIONAL 1 GPSDOP rational +// 12 ASCII 2 GPSSpeedRef text +// 13 RATIONAL 1 GPSSpeed rational +// 14 ASCII 2 GPSTrackRef text +// 15 RATIONAL 1 GPSTrack rational +// 16 ASCII 2 GPSImgDirectionRef text +// 17 RATIONAL 1 GPSImgDirection rational +// 18 ASCII Any GPSMapDatum text +// 19 ASCII 2 GPSDestLatitudeRef latitude, with 20 +// 20 RATIONAL 3 GPSDestLatitude latitude, master of 19 +// 21 ASCII 2 GPSDestLongitudeRef longitude, with 22 +// 22 RATIONAL 3 GPSDestLongitude logitude, master of 21 +// 23 ASCII 2 GPSDestBearingRef text +// 24 RATIONAL 1 GPSDestBearing rational +// 25 ASCII 2 GPSDestDistanceRef text +// 26 RATIONAL 1 GPSDestDistance rational +// 27 UNDEFINED Any GPSProcessingMethod text, explicit encoding +// 28 UNDEFINED Any GPSAreaInformation text, explicit encoding +// 29 ASCII 11 GPSDateStamp date, with 29 +// 30 SHORT 1 GPSDifferential integer +// +// ================================================================================================= + +// ================================================================================================= + +#endif // #ifndef __ReconcileLegacy_hpp__ diff --git a/core/libs/dngwriter/extra/xmp_sdk/XMPFiles/source/FormatSupport/ReconcileTIFF.cpp b/core/libs/dngwriter/extra/xmp_sdk/XMPFiles/source/FormatSupport/ReconcileTIFF.cpp new file mode 100644 index 0000000000..f9e1165b26 --- /dev/null +++ b/core/libs/dngwriter/extra/xmp_sdk/XMPFiles/source/FormatSupport/ReconcileTIFF.cpp @@ -0,0 +1,3448 @@ +// ================================================================================================= +// ADOBE SYSTEMS INCORPORATED +// Copyright 2006 Adobe Systems Incorporated +// All Rights Reserved +// +// NOTICE: Adobe permits you to use, modify, and distribute this file in accordance with the terms +// of the Adobe license agreement accompanying it. +// ================================================================================================= + +#include "public/include/XMP_Environment.h" // ! XMP_Environment.h must be the first included header. +#include "public/include/XMP_Const.h" + +#include "XMPFiles/source/FormatSupport/Reconcile_Impl.hpp" +#include "source/UnicodeConversions.hpp" +#include "source/XIO.hpp" + +#include +#if XMP_WinBuild + #define snprintf _snprintf +#endif + +#include "source/EndianUtils.hpp" + +#if XMP_WinBuild + #pragma warning ( disable : 4146 ) // unary minus operator applied to unsigned type + #pragma warning ( disable : 4800 ) // forcing value to bool 'true' or 'false' + #pragma warning ( disable : 4996 ) // '...' was declared deprecated +#endif + +// ================================================================================================= +/// \file ReconcileTIFF.cpp +/// \brief Utilities to reconcile between XMP and legacy TIFF/Exif metadata. +/// +// ================================================================================================= + +// ================================================================================================= + +#ifndef SupportOldExifProperties + #define SupportOldExifProperties 1 + // This controls support of the old Adobe names for things that have official names as of Exif 2.3. +#endif + +// ================================================================================================= +// Tables of the TIFF/Exif tags that are mapped into XMP. For the most part, the tags have obvious +// mappings based on their IFD, tag number, type and count. These tables do not list tags that are +// mapped as subsidiary parts of others, e.g. TIFF SubSecTime or GPS Info GPSDateStamp. Tags that +// have special mappings are marked by having an empty string for the XMP property name. + +// ! These tables have the tags listed in the order of tables 3, 4, 5, and 12 of Exif 2.2, with the +// ! exception of ImageUniqueID (which is listed at the end of the Exif mappings). This order is +// ! very important to consistent checking of the legacy status. The NativeDigest properties list +// ! all possible mapped tags in this order. The NativeDigest strings are compared as a whole, so +// ! the same tags listed in a different order would compare as different. + +// ! The sentinel tag value can't be 0, that is a valid GPS Info tag, 0xFFFF is unused so far. + +enum { + kExport_Never = 0, // Never export. + kExport_Always = 1, // Add, modify, or delete. + kExport_NoDelete = 2, // Add or modify, do not delete if no XMP. + kExport_InjectOnly = 3 // Add tag if new, never modify or delete existing values. +}; + +struct TIFF_MappingToXMP { + XMP_Uns16 id; + XMP_Uns16 type; + XMP_Uns32 count; // Zero means any. + XMP_Uns8 exportMode; + const char * ns; // The namespace of the mapped XMP property. + const char * name; // The name of the mapped XMP property. +}; + +enum { kAnyCount = 0 }; + +static const TIFF_MappingToXMP sPrimaryIFDMappings[] = { // A blank name indicates a special mapping. + { /* 256 */ kTIFF_ImageWidth, kTIFF_ShortOrLongType, 1, kExport_Never, kXMP_NS_TIFF, "ImageWidth" }, + { /* 257 */ kTIFF_ImageLength, kTIFF_ShortOrLongType, 1, kExport_Never, kXMP_NS_TIFF, "ImageLength" }, + { /* 258 */ kTIFF_BitsPerSample, kTIFF_ShortType, 3, kExport_Never, kXMP_NS_TIFF, "BitsPerSample" }, + { /* 259 */ kTIFF_Compression, kTIFF_ShortType, 1, kExport_Never, kXMP_NS_TIFF, "Compression" }, + { /* 262 */ kTIFF_PhotometricInterpretation, kTIFF_ShortType, 1, kExport_Never, kXMP_NS_TIFF, "PhotometricInterpretation" }, + { /* 274 */ kTIFF_Orientation, kTIFF_ShortType, 1, kExport_NoDelete, kXMP_NS_TIFF, "Orientation" }, + { /* 277 */ kTIFF_SamplesPerPixel, kTIFF_ShortType, 1, kExport_Never, kXMP_NS_TIFF, "SamplesPerPixel" }, + { /* 284 */ kTIFF_PlanarConfiguration, kTIFF_ShortType, 1, kExport_Never, kXMP_NS_TIFF, "PlanarConfiguration" }, + { /* 529 */ kTIFF_YCbCrCoefficients, kTIFF_RationalType, 3, kExport_Never, kXMP_NS_TIFF, "YCbCrCoefficients" }, + { /* 530 */ kTIFF_YCbCrSubSampling, kTIFF_ShortType, 2, kExport_Never, kXMP_NS_TIFF, "YCbCrSubSampling" }, + { /* 282 */ kTIFF_XResolution, kTIFF_RationalType, 1, kExport_NoDelete, kXMP_NS_TIFF, "XResolution" }, + { /* 283 */ kTIFF_YResolution, kTIFF_RationalType, 1, kExport_NoDelete, kXMP_NS_TIFF, "YResolution" }, + { /* 296 */ kTIFF_ResolutionUnit, kTIFF_ShortType, 1, kExport_NoDelete, kXMP_NS_TIFF, "ResolutionUnit" }, + { /* 301 */ kTIFF_TransferFunction, kTIFF_ShortType, 3*256, kExport_Never, kXMP_NS_TIFF, "TransferFunction" }, + { /* 318 */ kTIFF_WhitePoint, kTIFF_RationalType, 2, kExport_Never, kXMP_NS_TIFF, "WhitePoint" }, + { /* 319 */ kTIFF_PrimaryChromaticities, kTIFF_RationalType, 6, kExport_Never, kXMP_NS_TIFF, "PrimaryChromaticities" }, + { /* 531 */ kTIFF_YCbCrPositioning, kTIFF_ShortType, 1, kExport_Never, kXMP_NS_TIFF, "YCbCrPositioning" }, + { /* 532 */ kTIFF_ReferenceBlackWhite, kTIFF_RationalType, 6, kExport_Never, kXMP_NS_TIFF, "ReferenceBlackWhite" }, + { /* 306 */ kTIFF_DateTime, kTIFF_ASCIIType, 20, kExport_Always, "", "" }, // ! Has a special mapping. + { /* 270 */ kTIFF_ImageDescription, kTIFF_ASCIIType, kAnyCount, kExport_Always, "", "" }, // ! Has a special mapping. + { /* 271 */ kTIFF_Make, kTIFF_ASCIIType, kAnyCount, kExport_InjectOnly, kXMP_NS_TIFF, "Make" }, + { /* 272 */ kTIFF_Model, kTIFF_ASCIIType, kAnyCount, kExport_InjectOnly, kXMP_NS_TIFF, "Model" }, + { /* 305 */ kTIFF_Software, kTIFF_ASCIIType, kAnyCount, kExport_Always, kXMP_NS_TIFF, "Software" }, // Has alias to xmp:CreatorTool. + { /* 315 */ kTIFF_Artist, kTIFF_ASCIIType, kAnyCount, kExport_Always, "", "" }, // ! Has a special mapping. + { /* 33432 */ kTIFF_Copyright, kTIFF_ASCIIType, kAnyCount, kExport_Always, "", "" }, // ! Has a special mapping. + { 0xFFFF, 0, 0, 0 } // ! Must end with sentinel. +}; + +static const TIFF_MappingToXMP sExifIFDMappings[] = { + + // From Exif 2.3 table 7: + { /* 36864 */ kTIFF_ExifVersion, kTIFF_UndefinedType, 4, kExport_InjectOnly, "", "" }, // ! Has a special mapping. + { /* 40960 */ kTIFF_FlashpixVersion, kTIFF_UndefinedType, 4, kExport_Never, "", "" }, // ! Has a special mapping. + { /* 40961 */ kTIFF_ColorSpace, kTIFF_ShortType, 1, kExport_InjectOnly, kXMP_NS_EXIF, "ColorSpace" }, + { /* 42240 */ kTIFF_Gamma, kTIFF_RationalType, 1, kExport_InjectOnly, kXMP_NS_ExifEX, "Gamma" }, + { /* 37121 */ kTIFF_ComponentsConfiguration, kTIFF_UndefinedType, 4, kExport_InjectOnly, "", "" }, // ! Has a special mapping. + { /* 37122 */ kTIFF_CompressedBitsPerPixel, kTIFF_RationalType, 1, kExport_InjectOnly, kXMP_NS_EXIF, "CompressedBitsPerPixel" }, + { /* 40962 */ kTIFF_PixelXDimension, kTIFF_ShortOrLongType, 1, kExport_InjectOnly, kXMP_NS_EXIF, "PixelXDimension" }, + { /* 40963 */ kTIFF_PixelYDimension, kTIFF_ShortOrLongType, 1, kExport_InjectOnly, kXMP_NS_EXIF, "PixelYDimension" }, + { /* 37510 */ kTIFF_UserComment, kTIFF_UndefinedType, kAnyCount, kExport_Always, "", "" }, // ! Has a special mapping. + { /* 40964 */ kTIFF_RelatedSoundFile, kTIFF_ASCIIType, kAnyCount, kExport_Always, kXMP_NS_EXIF, "RelatedSoundFile" }, // ! Exif spec says count of 13. + { /* 36867 */ kTIFF_DateTimeOriginal, kTIFF_ASCIIType, 20, kExport_Always, "", "" }, // ! Has a special mapping. + { /* 36868 */ kTIFF_DateTimeDigitized, kTIFF_ASCIIType, 20, kExport_Always, "", "" }, // ! Has a special mapping. + { /* 42016 */ kTIFF_ImageUniqueID, kTIFF_ASCIIType, 33, kExport_InjectOnly, kXMP_NS_EXIF, "ImageUniqueID" }, + { /* 42032 */ kTIFF_CameraOwnerName, kTIFF_ASCIIType, kAnyCount, kExport_InjectOnly, kXMP_NS_ExifEX, "CameraOwnerName" }, + { /* 42033 */ kTIFF_BodySerialNumber, kTIFF_ASCIIType, kAnyCount, kExport_InjectOnly, kXMP_NS_ExifEX, "BodySerialNumber" }, + { /* 42034 */ kTIFF_LensSpecification, kTIFF_RationalType, 4, kExport_InjectOnly, kXMP_NS_ExifEX, "LensSpecification" }, + { /* 42035 */ kTIFF_LensMake, kTIFF_ASCIIType, kAnyCount, kExport_InjectOnly, kXMP_NS_ExifEX, "LensMake" }, + { /* 42036 */ kTIFF_LensModel, kTIFF_ASCIIType, kAnyCount, kExport_InjectOnly, kXMP_NS_ExifEX, "LensModel" }, + { /* 42037 */ kTIFF_LensSerialNumber, kTIFF_ASCIIType, kAnyCount, kExport_InjectOnly, kXMP_NS_ExifEX, "LensSerialNumber" }, + + // From Exif 2.3 table 8: + { /* 33434 */ kTIFF_ExposureTime, kTIFF_RationalType, 1, kExport_InjectOnly, kXMP_NS_EXIF, "ExposureTime" }, + { /* 33437 */ kTIFF_FNumber, kTIFF_RationalType, 1, kExport_InjectOnly, kXMP_NS_EXIF, "FNumber" }, + { /* 34850 */ kTIFF_ExposureProgram, kTIFF_ShortType, 1, kExport_InjectOnly, kXMP_NS_EXIF, "ExposureProgram" }, + { /* 34852 */ kTIFF_SpectralSensitivity, kTIFF_ASCIIType, kAnyCount, kExport_InjectOnly, kXMP_NS_EXIF, "SpectralSensitivity" }, + { /* 34855 */ kTIFF_PhotographicSensitivity, kTIFF_ShortType, 1, kExport_InjectOnly, "", "" }, // ! Has a special mapping. + { /* 34856 */ kTIFF_OECF, kTIFF_UndefinedType, kAnyCount, kExport_Never, "", "" }, // ! Has a special mapping. + { /* 34864 */ kTIFF_SensitivityType, kTIFF_ShortType, 1, kExport_InjectOnly, "", "" }, // ! Has a special mapping. + { /* 34865 */ kTIFF_StandardOutputSensitivity, kTIFF_LongType, 1, kExport_InjectOnly, "", "" }, // ! Has a special mapping. + { /* 34866 */ kTIFF_RecommendedExposureIndex, kTIFF_LongType, 1, kExport_InjectOnly, "", "" }, // ! Has a special mapping. + { /* 34867 */ kTIFF_ISOSpeed, kTIFF_LongType, 1, kExport_InjectOnly, "", "" }, // ! Has a special mapping. + { /* 34868 */ kTIFF_ISOSpeedLatitudeyyy, kTIFF_LongType, 1, kExport_InjectOnly, "", "" }, // ! Has a special mapping. + { /* 34869 */ kTIFF_ISOSpeedLatitudezzz, kTIFF_LongType, 1, kExport_InjectOnly, "", "" }, // ! Has a special mapping. + { /* 37377 */ kTIFF_ShutterSpeedValue, kTIFF_SRationalType, 1, kExport_InjectOnly, kXMP_NS_EXIF, "ShutterSpeedValue" }, + { /* 37378 */ kTIFF_ApertureValue, kTIFF_RationalType, 1, kExport_InjectOnly, kXMP_NS_EXIF, "ApertureValue" }, + { /* 37379 */ kTIFF_BrightnessValue, kTIFF_SRationalType, 1, kExport_InjectOnly, kXMP_NS_EXIF, "BrightnessValue" }, + { /* 37380 */ kTIFF_ExposureBiasValue, kTIFF_SRationalType, 1, kExport_InjectOnly, kXMP_NS_EXIF, "ExposureBiasValue" }, + { /* 37381 */ kTIFF_MaxApertureValue, kTIFF_RationalType, 1, kExport_InjectOnly, kXMP_NS_EXIF, "MaxApertureValue" }, + { /* 37382 */ kTIFF_SubjectDistance, kTIFF_RationalType, 1, kExport_InjectOnly, kXMP_NS_EXIF, "SubjectDistance" }, + { /* 37383 */ kTIFF_MeteringMode, kTIFF_ShortType, 1, kExport_InjectOnly, kXMP_NS_EXIF, "MeteringMode" }, + { /* 37384 */ kTIFF_LightSource, kTIFF_ShortType, 1, kExport_InjectOnly, kXMP_NS_EXIF, "LightSource" }, + { /* 37385 */ kTIFF_Flash, kTIFF_ShortType, 1, kExport_InjectOnly, "", "" }, // ! Has a special mapping. + { /* 37386 */ kTIFF_FocalLength, kTIFF_RationalType, 1, kExport_InjectOnly, kXMP_NS_EXIF, "FocalLength" }, + { /* 37396 */ kTIFF_SubjectArea, kTIFF_ShortType, kAnyCount, kExport_Never, kXMP_NS_EXIF, "SubjectArea" }, // ! Actually 2..4. + { /* 41483 */ kTIFF_FlashEnergy, kTIFF_RationalType, 1, kExport_InjectOnly, kXMP_NS_EXIF, "FlashEnergy" }, + { /* 41484 */ kTIFF_SpatialFrequencyResponse, kTIFF_UndefinedType, kAnyCount, kExport_InjectOnly, "", "" }, // ! Has a special mapping. + { /* 41486 */ kTIFF_FocalPlaneXResolution, kTIFF_RationalType, 1, kExport_InjectOnly, kXMP_NS_EXIF, "FocalPlaneXResolution" }, + { /* 41487 */ kTIFF_FocalPlaneYResolution, kTIFF_RationalType, 1, kExport_InjectOnly, kXMP_NS_EXIF, "FocalPlaneYResolution" }, + { /* 41488 */ kTIFF_FocalPlaneResolutionUnit, kTIFF_ShortType, 1, kExport_InjectOnly, kXMP_NS_EXIF, "FocalPlaneResolutionUnit" }, + { /* 41492 */ kTIFF_SubjectLocation, kTIFF_ShortType, 2, kExport_Never, kXMP_NS_EXIF, "SubjectLocation" }, + { /* 41493 */ kTIFF_ExposureIndex, kTIFF_RationalType, 1, kExport_InjectOnly, kXMP_NS_EXIF, "ExposureIndex" }, + { /* 41495 */ kTIFF_SensingMethod, kTIFF_ShortType, 1, kExport_InjectOnly, kXMP_NS_EXIF, "SensingMethod" }, + { /* 41728 */ kTIFF_FileSource, kTIFF_UndefinedType, 1, kExport_InjectOnly, "", "" }, // ! Has a special mapping. + { /* 41729 */ kTIFF_SceneType, kTIFF_UndefinedType, 1, kExport_InjectOnly, "", "" }, // ! Has a special mapping. + { /* 41730 */ kTIFF_CFAPattern, kTIFF_UndefinedType, kAnyCount, kExport_InjectOnly, "", "" }, // ! Has a special mapping. + { /* 41985 */ kTIFF_CustomRendered, kTIFF_ShortType, 1, kExport_Never, kXMP_NS_EXIF, "CustomRendered" }, + { /* 41986 */ kTIFF_ExposureMode, kTIFF_ShortType, 1, kExport_InjectOnly, kXMP_NS_EXIF, "ExposureMode" }, + { /* 41987 */ kTIFF_WhiteBalance, kTIFF_ShortType, 1, kExport_InjectOnly, kXMP_NS_EXIF, "WhiteBalance" }, + { /* 41988 */ kTIFF_DigitalZoomRatio, kTIFF_RationalType, 1, kExport_InjectOnly, kXMP_NS_EXIF, "DigitalZoomRatio" }, + { /* 41989 */ kTIFF_FocalLengthIn35mmFilm, kTIFF_ShortType, 1, kExport_InjectOnly, kXMP_NS_EXIF, "FocalLengthIn35mmFilm" }, + { /* 41990 */ kTIFF_SceneCaptureType, kTIFF_ShortType, 1, kExport_InjectOnly, kXMP_NS_EXIF, "SceneCaptureType" }, + { /* 41991 */ kTIFF_GainControl, kTIFF_ShortType, 1, kExport_InjectOnly, kXMP_NS_EXIF, "GainControl" }, + { /* 41992 */ kTIFF_Contrast, kTIFF_ShortType, 1, kExport_InjectOnly, kXMP_NS_EXIF, "Contrast" }, + { /* 41993 */ kTIFF_Saturation, kTIFF_ShortType, 1, kExport_InjectOnly, kXMP_NS_EXIF, "Saturation" }, + { /* 41994 */ kTIFF_Sharpness, kTIFF_ShortType, 1, kExport_InjectOnly, kXMP_NS_EXIF, "Sharpness" }, + { /* 41995 */ kTIFF_DeviceSettingDescription, kTIFF_UndefinedType, kAnyCount, kExport_InjectOnly, "", "" }, // ! Has a special mapping. + { /* 41996 */ kTIFF_SubjectDistanceRange, kTIFF_ShortType, 1, kExport_InjectOnly, kXMP_NS_EXIF, "SubjectDistanceRange" }, + + { 0xFFFF, 0, 0, 0 } // ! Must end with sentinel. +}; + +static const TIFF_MappingToXMP sGPSInfoIFDMappings[] = { + { /* 0 */ kTIFF_GPSVersionID, kTIFF_ByteType, 4, kExport_InjectOnly, "", "" }, // ! Has a special mapping. + { /* 2 */ kTIFF_GPSLatitude, kTIFF_RationalType, 3, kExport_Always, "", "" }, // ! Has a special mapping. + { /* 4 */ kTIFF_GPSLongitude, kTIFF_RationalType, 3, kExport_Always, "", "" }, // ! Has a special mapping. + { /* 5 */ kTIFF_GPSAltitudeRef, kTIFF_ByteType, 1, kExport_Always, kXMP_NS_EXIF, "GPSAltitudeRef" }, + { /* 6 */ kTIFF_GPSAltitude, kTIFF_RationalType, 1, kExport_Always, kXMP_NS_EXIF, "GPSAltitude" }, + { /* 7 */ kTIFF_GPSTimeStamp, kTIFF_RationalType, 3, kExport_Always, "", "" }, // ! Has a special mapping. + { /* 8 */ kTIFF_GPSSatellites, kTIFF_ASCIIType, kAnyCount, kExport_InjectOnly, kXMP_NS_EXIF, "GPSSatellites" }, + { /* 9 */ kTIFF_GPSStatus, kTIFF_ASCIIType, 2, kExport_InjectOnly, kXMP_NS_EXIF, "GPSStatus" }, + { /* 10 */ kTIFF_GPSMeasureMode, kTIFF_ASCIIType, 2, kExport_InjectOnly, kXMP_NS_EXIF, "GPSMeasureMode" }, + { /* 11 */ kTIFF_GPSDOP, kTIFF_RationalType, 1, kExport_InjectOnly, kXMP_NS_EXIF, "GPSDOP" }, + { /* 12 */ kTIFF_GPSSpeedRef, kTIFF_ASCIIType, 2, kExport_InjectOnly, kXMP_NS_EXIF, "GPSSpeedRef" }, + { /* 13 */ kTIFF_GPSSpeed, kTIFF_RationalType, 1, kExport_InjectOnly, kXMP_NS_EXIF, "GPSSpeed" }, + { /* 14 */ kTIFF_GPSTrackRef, kTIFF_ASCIIType, 2, kExport_InjectOnly, kXMP_NS_EXIF, "GPSTrackRef" }, + { /* 15 */ kTIFF_GPSTrack, kTIFF_RationalType, 1, kExport_InjectOnly, kXMP_NS_EXIF, "GPSTrack" }, + { /* 16 */ kTIFF_GPSImgDirectionRef, kTIFF_ASCIIType, 2, kExport_InjectOnly, kXMP_NS_EXIF, "GPSImgDirectionRef" }, + { /* 17 */ kTIFF_GPSImgDirection, kTIFF_RationalType, 1, kExport_InjectOnly, kXMP_NS_EXIF, "GPSImgDirection" }, + { /* 18 */ kTIFF_GPSMapDatum, kTIFF_ASCIIType, kAnyCount, kExport_InjectOnly, kXMP_NS_EXIF, "GPSMapDatum" }, + { /* 20 */ kTIFF_GPSDestLatitude, kTIFF_RationalType, 3, kExport_InjectOnly, "", "" }, // ! Has a special mapping. + { /* 22 */ kTIFF_GPSDestLongitude, kTIFF_RationalType, 3, kExport_InjectOnly, "", "" }, // ! Has a special mapping. + { /* 23 */ kTIFF_GPSDestBearingRef, kTIFF_ASCIIType, 2, kExport_InjectOnly, kXMP_NS_EXIF, "GPSDestBearingRef" }, + { /* 24 */ kTIFF_GPSDestBearing, kTIFF_RationalType, 1, kExport_InjectOnly, kXMP_NS_EXIF, "GPSDestBearing" }, + { /* 25 */ kTIFF_GPSDestDistanceRef, kTIFF_ASCIIType, 2, kExport_InjectOnly, kXMP_NS_EXIF, "GPSDestDistanceRef" }, + { /* 26 */ kTIFF_GPSDestDistance, kTIFF_RationalType, 1, kExport_InjectOnly, kXMP_NS_EXIF, "GPSDestDistance" }, + { /* 27 */ kTIFF_GPSProcessingMethod, kTIFF_UndefinedType, kAnyCount, kExport_InjectOnly, "", "" }, // ! Has a special mapping. + { /* 28 */ kTIFF_GPSAreaInformation, kTIFF_UndefinedType, kAnyCount, kExport_InjectOnly, "", "" }, // ! Has a special mapping. + { /* 30 */ kTIFF_GPSDifferential, kTIFF_ShortType, 1, kExport_InjectOnly, kXMP_NS_EXIF, "GPSDifferential" }, + { /* 31 */ kTIFF_GPSHPositioningError, kTIFF_RationalType, 1, kExport_InjectOnly, kXMP_NS_ExifEX, "GPSHPositioningError" }, + { 0xFFFF, 0, 0, 0 } // ! Must end with sentinel. +}; + +// ================================================================================================= + +static void // ! Needed by Import2WayExif +ExportTIFF_Date ( const SXMPMeta & xmp, const char * xmpNS, const char * xmpProp, TIFF_Manager * tiff, XMP_Uns16 mainID ); + +// ================================================================================================= + +static XMP_Uns32 GatherInt ( const char * strPtr, size_t count ) +{ + XMP_Uns32 value = 0; + const char * strEnd = strPtr + count; + + while ( strPtr < strEnd ) { + char ch = *strPtr; + if ( (ch < '0') || (ch > '9') ) break; + value = value*10 + (ch - '0'); + ++strPtr; + } + + return value; + +} // GatherInt + +// ================================================================================================= + +static size_t TrimTrailingSpaces ( char * firstChar, size_t origLen ) +{ + if ( origLen == 0 ) return 0; + + char * lastChar = firstChar + origLen - 1; + if ( (*lastChar != ' ') && (*lastChar != 0) ) return origLen; // Nothing to do. + + while ( (firstChar <= lastChar) && ((*lastChar == ' ') || (*lastChar == 0)) ) --lastChar; + + XMP_Assert ( (lastChar == firstChar-1) || + ((lastChar >= firstChar) && (*lastChar != ' ') && (*lastChar != 0)) ); + + size_t newLen = (size_t)((lastChar+1) - firstChar); + XMP_Assert ( newLen <= origLen ); + + if ( newLen < origLen ) { + ++lastChar; + *lastChar = 0; + } + + return newLen; + +} // TrimTrailingSpaces + +static void TrimTrailingSpaces ( TIFF_Manager::TagInfo * info ) +{ + info->dataLen = (XMP_Uns32) TrimTrailingSpaces ( (char*)info->dataPtr, (size_t)info->dataLen ); +} + +static void TrimTrailingSpaces ( std::string * stdstr ) +{ + size_t origLen = stdstr->size(); + size_t newLen = TrimTrailingSpaces ( (char*)stdstr->c_str(), origLen ); + if ( newLen != origLen ) stdstr->erase ( newLen ); +} + +// ================================================================================================= + +bool PhotoDataUtils::GetNativeInfo ( const TIFF_Manager & exif, XMP_Uns8 ifd, XMP_Uns16 id, TIFF_Manager::TagInfo * info ) +{ + bool haveExif = exif.GetTag ( ifd, id, info ); + + if ( haveExif ) { + + XMP_Uns32 i; + char * chPtr; + + XMP_Assert ( (info->dataPtr != 0) || (info->dataLen == 0) ); // Null pointer requires zero length. + + bool isDate = ((id == kTIFF_DateTime) || (id == kTIFF_DateTimeOriginal) || (id == kTIFF_DateTimeOriginal)); + + for ( i = 0, chPtr = (char*)info->dataPtr; i < info->dataLen; ++i, ++chPtr ) { + if ( isDate && (*chPtr == ':') ) continue; // Ignore colons, empty dates have spaces and colons. + if ( (*chPtr != ' ') && (*chPtr != 0) ) break; // Break if the Exif value is non-empty. + } + + if ( i == info->dataLen ) { + haveExif = false; // Ignore empty Exif. + } else { + TrimTrailingSpaces ( info ); + if ( info->dataLen == 0 ) haveExif = false; + } + + } + + return haveExif; + +} // PhotoDataUtils::GetNativeInfo + +// ================================================================================================= + +size_t PhotoDataUtils::GetNativeInfo ( const IPTC_Manager & iptc, XMP_Uns8 id, int digestState, bool haveXMP, IPTC_Manager::DataSetInfo * info ) +{ + size_t iptcCount = 0; + + iptcCount = iptc.GetDataSet ( id, info ); + + if ( ignoreLocalText && (iptcCount > 0) && (! iptc.UsingUTF8()) ) { + // Check to see if the new value(s) should be ignored. + size_t i; + IPTC_Manager::DataSetInfo tmpInfo; + for ( i = 0; i < iptcCount; ++i ) { + (void) iptc.GetDataSet ( id, &tmpInfo, i ); + if ( ReconcileUtils::IsASCII ( tmpInfo.dataPtr, tmpInfo.dataLen ) ) break; + } + if ( i == iptcCount ) iptcCount = 0; // Return 0 if value(s) should be ignored. + } + + return iptcCount; + +} // PhotoDataUtils::GetNativeInfo + +// ================================================================================================= + +bool PhotoDataUtils::IsValueDifferent ( const TIFF_Manager::TagInfo & exifInfo, const std::string & xmpValue, std::string * exifValue ) +{ + if ( exifInfo.dataLen == 0 ) return false; // Ignore empty Exif values. + + if ( ReconcileUtils::IsUTF8 ( exifInfo.dataPtr, exifInfo.dataLen ) ) { // ! Note that ASCII is UTF-8. + exifValue->assign ( (char*)exifInfo.dataPtr, exifInfo.dataLen ); + } else { + if ( ignoreLocalText ) return false; + ReconcileUtils::LocalToUTF8 ( exifInfo.dataPtr, exifInfo.dataLen, exifValue ); + } + + return (*exifValue != xmpValue); + +} // PhotoDataUtils::IsValueDifferent + +// ================================================================================================= + +bool PhotoDataUtils::IsValueDifferent ( const IPTC_Manager & newIPTC, const IPTC_Manager & oldIPTC, XMP_Uns8 id ) +{ + IPTC_Manager::DataSetInfo newInfo; + size_t newCount = newIPTC.GetDataSet ( id, &newInfo ); + if ( newCount == 0 ) return false; // Ignore missing new IPTC values. + + IPTC_Manager::DataSetInfo oldInfo; + size_t oldCount = oldIPTC.GetDataSet ( id, &oldInfo ); + if ( oldCount == 0 ) return true; // Missing old IPTC values differ. + + if ( newCount != oldCount ) return true; + + std::string oldStr, newStr; + + for ( newCount = 0; newCount < oldCount; ++newCount ) { + + if ( ignoreLocalText & (! newIPTC.UsingUTF8()) ) { // Check to see if the new value should be ignored. + (void) newIPTC.GetDataSet ( id, &newInfo, newCount ); + if ( ! ReconcileUtils::IsASCII ( newInfo.dataPtr, newInfo.dataLen ) ) continue; + } + + (void) newIPTC.GetDataSet_UTF8 ( id, &newStr, newCount ); + (void) oldIPTC.GetDataSet_UTF8 ( id, &oldStr, newCount ); + if ( newStr.size() == 0 ) continue; // Ignore empty new IPTC. + if ( newStr != oldStr ) break; + + } + + return ( newCount != oldCount ); // Not different if all values matched. + +} // PhotoDataUtils::IsValueDifferent + +// ================================================================================================= +// ================================================================================================= + +// ================================================================================================= +// ImportSingleTIFF_Short +// ====================== + +static void +ImportSingleTIFF_Short ( const TIFF_Manager::TagInfo & tagInfo, const bool nativeEndian, + SXMPMeta * xmp, const char * xmpNS, const char * xmpProp ) +{ + try { // Don't let errors with one stop the others. + + XMP_Uns16 binValue = GetUns16AsIs ( tagInfo.dataPtr ); + if ( ! nativeEndian ) binValue = Flip2 ( binValue ); + + char strValue[20]; + snprintf ( strValue, sizeof(strValue), "%hu", binValue ); // AUDIT: Using sizeof(strValue) is safe. + + xmp->SetProperty ( xmpNS, xmpProp, strValue ); + + } catch ( ... ) { + // Do nothing, let other imports proceed. + // ? Notify client? + } + +} // ImportSingleTIFF_Short + +// ================================================================================================= +// ImportSingleTIFF_Long +// ===================== + +static void +ImportSingleTIFF_Long ( const TIFF_Manager::TagInfo & tagInfo, const bool nativeEndian, + SXMPMeta * xmp, const char * xmpNS, const char * xmpProp ) +{ + try { // Don't let errors with one stop the others. + + XMP_Uns32 binValue = GetUns32AsIs ( tagInfo.dataPtr ); + if ( ! nativeEndian ) binValue = Flip4 ( binValue ); + + char strValue[20]; + snprintf ( strValue, sizeof(strValue), "%lu", (unsigned long)binValue ); // AUDIT: Using sizeof(strValue) is safe. + + xmp->SetProperty ( xmpNS, xmpProp, strValue ); + + } catch ( ... ) { + // Do nothing, let other imports proceed. + // ? Notify client? + } + +} // ImportSingleTIFF_Long + +// ================================================================================================= +// ImportSingleTIFF_Rational +// ========================= + +static void +ImportSingleTIFF_Rational ( const TIFF_Manager::TagInfo & tagInfo, const bool nativeEndian, + SXMPMeta * xmp, const char * xmpNS, const char * xmpProp ) +{ + try { // Don't let errors with one stop the others. + + XMP_Uns32 * binPtr = (XMP_Uns32*)tagInfo.dataPtr; + XMP_Uns32 binNum = GetUns32AsIs ( &binPtr[0] ); + XMP_Uns32 binDenom = GetUns32AsIs ( &binPtr[1] ); + if ( ! nativeEndian ) { + binNum = Flip4 ( binNum ); + binDenom = Flip4 ( binDenom ); + } + + char strValue[40]; + snprintf ( strValue, sizeof(strValue), "%lu/%lu", (unsigned long)binNum, (unsigned long)binDenom ); // AUDIT: Using sizeof(strValue) is safe. + + xmp->SetProperty ( xmpNS, xmpProp, strValue ); + + } catch ( ... ) { + // Do nothing, let other imports proceed. + // ? Notify client? + } + +} // ImportSingleTIFF_Rational + +// ================================================================================================= +// ImportSingleTIFF_SRational +// ========================== + +static void +ImportSingleTIFF_SRational ( const TIFF_Manager::TagInfo & tagInfo, const bool nativeEndian, + SXMPMeta * xmp, const char * xmpNS, const char * xmpProp ) +{ + try { // Don't let errors with one stop the others. + +#if SUNOS_SPARC || XMP_IOS_ARM + XMP_Uns32 binPtr[2]; + memcpy(&binPtr, tagInfo.dataPtr, sizeof(XMP_Uns32)*2); +#else + XMP_Uns32 * binPtr = (XMP_Uns32*)tagInfo.dataPtr; +#endif //#if SUNOS_SPARC || XMP_IOS_ARM + XMP_Int32 binNum = GetUns32AsIs ( &binPtr[0] ); + XMP_Int32 binDenom = GetUns32AsIs ( &binPtr[1] ); + if ( ! nativeEndian ) { + Flip4 ( &binNum ); + Flip4 ( &binDenom ); + } + + char strValue[40]; + snprintf ( strValue, sizeof(strValue), "%ld/%ld", (unsigned long)binNum, (unsigned long)binDenom ); // AUDIT: Using sizeof(strValue) is safe. + + xmp->SetProperty ( xmpNS, xmpProp, strValue ); + + } catch ( ... ) { + // Do nothing, let other imports proceed. + // ? Notify client? + } + +} // ImportSingleTIFF_SRational + +// ================================================================================================= +// ImportSingleTIFF_ASCII +// ====================== + +static void +ImportSingleTIFF_ASCII ( const TIFF_Manager::TagInfo & tagInfo, + SXMPMeta * xmp, const char * xmpNS, const char * xmpProp ) +{ + try { // Don't let errors with one stop the others. + + TrimTrailingSpaces ( (TIFF_Manager::TagInfo*) &tagInfo ); + if ( tagInfo.dataLen == 0 ) return; // Ignore empty tags. + + const char * chPtr = (const char *)tagInfo.dataPtr; + const bool hasNul = (chPtr[tagInfo.dataLen-1] == 0); + const bool isUTF8 = ReconcileUtils::IsUTF8 ( chPtr, tagInfo.dataLen ); + + if ( isUTF8 && hasNul ) { + xmp->SetProperty ( xmpNS, xmpProp, chPtr ); + } else { + std::string strValue; + if ( isUTF8 ) { + strValue.assign ( chPtr, tagInfo.dataLen ); + } else { + if ( ignoreLocalText ) return; + ReconcileUtils::LocalToUTF8 ( chPtr, tagInfo.dataLen, &strValue ); + } + xmp->SetProperty ( xmpNS, xmpProp, strValue.c_str() ); + } + + } catch ( ... ) { + // Do nothing, let other imports proceed. + // ? Notify client? + } + +} // ImportSingleTIFF_ASCII + +// ================================================================================================= +// ImportSingleTIFF_Byte +// ===================== + +static void +ImportSingleTIFF_Byte ( const TIFF_Manager::TagInfo & tagInfo, + SXMPMeta * xmp, const char * xmpNS, const char * xmpProp ) +{ + try { // Don't let errors with one stop the others. + + XMP_Uns8 binValue = *((XMP_Uns8*)tagInfo.dataPtr); + + char strValue[20]; + snprintf ( strValue, sizeof(strValue), "%hu", (XMP_Uns16)binValue ); // AUDIT: Using sizeof(strValue) is safe. + + xmp->SetProperty ( xmpNS, xmpProp, strValue ); + + } catch ( ... ) { + // Do nothing, let other imports proceed. + // ? Notify client? + } + +} // ImportSingleTIFF_Byte + +// ================================================================================================= +// ImportSingleTIFF_SByte +// ====================== + +static void +ImportSingleTIFF_SByte ( const TIFF_Manager::TagInfo & tagInfo, + SXMPMeta * xmp, const char * xmpNS, const char * xmpProp ) +{ + try { // Don't let errors with one stop the others. + + XMP_Int8 binValue = *((XMP_Int8*)tagInfo.dataPtr); + + char strValue[20]; + snprintf ( strValue, sizeof(strValue), "%hd", (short)binValue ); // AUDIT: Using sizeof(strValue) is safe. + + xmp->SetProperty ( xmpNS, xmpProp, strValue ); + + } catch ( ... ) { + // Do nothing, let other imports proceed. + // ? Notify client? + } + +} // ImportSingleTIFF_SByte + +// ================================================================================================= +// ImportSingleTIFF_SShort +// ======================= + +static void +ImportSingleTIFF_SShort ( const TIFF_Manager::TagInfo & tagInfo, const bool nativeEndian, + SXMPMeta * xmp, const char * xmpNS, const char * xmpProp ) +{ + try { // Don't let errors with one stop the others. + + XMP_Int16 binValue = *((XMP_Int16*)tagInfo.dataPtr); + if ( ! nativeEndian ) Flip2 ( &binValue ); + + char strValue[20]; + snprintf ( strValue, sizeof(strValue), "%hd", binValue ); // AUDIT: Using sizeof(strValue) is safe. + + xmp->SetProperty ( xmpNS, xmpProp, strValue ); + + } catch ( ... ) { + // Do nothing, let other imports proceed. + // ? Notify client? + } + +} // ImportSingleTIFF_SShort + +// ================================================================================================= +// ImportSingleTIFF_SLong +// ====================== + +static void +ImportSingleTIFF_SLong ( const TIFF_Manager::TagInfo & tagInfo, const bool nativeEndian, + SXMPMeta * xmp, const char * xmpNS, const char * xmpProp ) +{ + try { // Don't let errors with one stop the others. + + XMP_Int32 binValue = *((XMP_Int32*)tagInfo.dataPtr); + if ( ! nativeEndian ) Flip4 ( &binValue ); + + char strValue[20]; + snprintf ( strValue, sizeof(strValue), "%ld", (long)binValue ); // AUDIT: Using sizeof(strValue) is safe. + + xmp->SetProperty ( xmpNS, xmpProp, strValue ); + + } catch ( ... ) { + // Do nothing, let other imports proceed. + // ? Notify client? + } + +} // ImportSingleTIFF_SLong + +// ================================================================================================= +// ImportSingleTIFF_Float +// ====================== + +static void +ImportSingleTIFF_Float ( const TIFF_Manager::TagInfo & tagInfo, const bool nativeEndian, + SXMPMeta * xmp, const char * xmpNS, const char * xmpProp ) +{ + try { // Don't let errors with one stop the others. + + float binValue = *((float*)tagInfo.dataPtr); + if ( ! nativeEndian ) Flip4 ( &binValue ); + + xmp->SetProperty_Float ( xmpNS, xmpProp, binValue ); + + } catch ( ... ) { + // Do nothing, let other imports proceed. + // ? Notify client? + } + +} // ImportSingleTIFF_Float + +// ================================================================================================= +// ImportSingleTIFF_Double +// ======================= + +static void +ImportSingleTIFF_Double ( const TIFF_Manager::TagInfo & tagInfo, const bool nativeEndian, + SXMPMeta * xmp, const char * xmpNS, const char * xmpProp ) +{ + try { // Don't let errors with one stop the others. + + double binValue = *((double*)tagInfo.dataPtr); + if ( ! nativeEndian ) Flip8 ( &binValue ); + + xmp->SetProperty_Float ( xmpNS, xmpProp, binValue ); // ! Yes, SetProperty_Float. + + } catch ( ... ) { + // Do nothing, let other imports proceed. + // ? Notify client? + } + +} // ImportSingleTIFF_Double + +// ================================================================================================= +// ImportSingleTIFF +// ================ + +static void +ImportSingleTIFF ( const TIFF_Manager::TagInfo & tagInfo, const bool nativeEndian, + SXMPMeta * xmp, const char * xmpNS, const char * xmpProp ) +{ + + // We've got a tag to map to XMP, decide how based on actual type and the expected count. Using + // the actual type eliminates a ShortOrLong case. Using the expected count is needed to know + // whether to create an XMP array. The actual count for an array could be 1. Put the most + // common cases first for better iCache utilization. + + switch ( tagInfo.type ) { + + case kTIFF_ShortType : + ImportSingleTIFF_Short ( tagInfo, nativeEndian, xmp, xmpNS, xmpProp ); + break; + + case kTIFF_LongType : + ImportSingleTIFF_Long ( tagInfo, nativeEndian, xmp, xmpNS, xmpProp ); + break; + + case kTIFF_RationalType : + ImportSingleTIFF_Rational ( tagInfo, nativeEndian, xmp, xmpNS, xmpProp ); + break; + + case kTIFF_SRationalType : + ImportSingleTIFF_SRational ( tagInfo, nativeEndian, xmp, xmpNS, xmpProp ); + break; + + case kTIFF_ASCIIType : + ImportSingleTIFF_ASCII ( tagInfo, xmp, xmpNS, xmpProp ); + break; + + case kTIFF_ByteType : + ImportSingleTIFF_Byte ( tagInfo, xmp, xmpNS, xmpProp ); + break; + + case kTIFF_SByteType : + ImportSingleTIFF_SByte ( tagInfo, xmp, xmpNS, xmpProp ); + break; + + case kTIFF_SShortType : + ImportSingleTIFF_SShort ( tagInfo, nativeEndian, xmp, xmpNS, xmpProp ); + break; + + case kTIFF_SLongType : + ImportSingleTIFF_SLong ( tagInfo, nativeEndian, xmp, xmpNS, xmpProp ); + break; + + case kTIFF_FloatType : + ImportSingleTIFF_Float ( tagInfo, nativeEndian, xmp, xmpNS, xmpProp ); + break; + + case kTIFF_DoubleType : + ImportSingleTIFF_Double ( tagInfo, nativeEndian, xmp, xmpNS, xmpProp ); + break; + + } + +} // ImportSingleTIFF + +// ================================================================================================= +// ================================================================================================= + +// ================================================================================================= +// ImportArrayTIFF_Short +// ===================== + +static void +ImportArrayTIFF_Short ( const TIFF_Manager::TagInfo & tagInfo, const bool nativeEndian, + SXMPMeta * xmp, const char * xmpNS, const char * xmpProp ) +{ + try { // Don't let errors with one stop the others. + + XMP_Uns16 * binPtr = (XMP_Uns16*)tagInfo.dataPtr; + + xmp->DeleteProperty ( xmpNS, xmpProp ); // ! Don't keep appending, create a new array. + + for ( size_t i = 0; i < tagInfo.count; ++i, ++binPtr ) { + + XMP_Uns16 binValue = *binPtr; + if ( ! nativeEndian ) binValue = Flip2 ( binValue ); + + char strValue[20]; + snprintf ( strValue, sizeof(strValue), "%hu", binValue ); // AUDIT: Using sizeof(strValue) is safe. + + xmp->AppendArrayItem ( xmpNS, xmpProp, kXMP_PropArrayIsOrdered, strValue ); + + } + + } catch ( ... ) { + // Do nothing, let other imports proceed. + // ? Notify client? + } + +} // ImportArrayTIFF_Short + +// ================================================================================================= +// ImportArrayTIFF_Long +// ==================== + +static void +ImportArrayTIFF_Long ( const TIFF_Manager::TagInfo & tagInfo, const bool nativeEndian, + SXMPMeta * xmp, const char * xmpNS, const char * xmpProp ) +{ + try { // Don't let errors with one stop the others. + + XMP_Uns32 * binPtr = (XMP_Uns32*)tagInfo.dataPtr; + + xmp->DeleteProperty ( xmpNS, xmpProp ); // ! Don't keep appending, create a new array. + + for ( size_t i = 0; i < tagInfo.count; ++i, ++binPtr ) { + + XMP_Uns32 binValue = *binPtr; + if ( ! nativeEndian ) binValue = Flip4 ( binValue ); + + char strValue[20]; + snprintf ( strValue, sizeof(strValue), "%lu", (unsigned long)binValue ); // AUDIT: Using sizeof(strValue) is safe. + + xmp->AppendArrayItem ( xmpNS, xmpProp, kXMP_PropArrayIsOrdered, strValue ); + + } + + } catch ( ... ) { + // Do nothing, let other imports proceed. + // ? Notify client? + } + +} // ImportArrayTIFF_Long + +// ================================================================================================= +// ImportArrayTIFF_Rational +// ======================== + +static void +ImportArrayTIFF_Rational ( const TIFF_Manager::TagInfo & tagInfo, const bool nativeEndian, + SXMPMeta * xmp, const char * xmpNS, const char * xmpProp ) +{ + try { // Don't let errors with one stop the others. + + XMP_Uns32 * binPtr = (XMP_Uns32*)tagInfo.dataPtr; + + xmp->DeleteProperty ( xmpNS, xmpProp ); // ! Don't keep appending, create a new array. + + for ( size_t i = 0; i < tagInfo.count; ++i, binPtr += 2 ) { + + XMP_Uns32 binNum = GetUns32AsIs ( &binPtr[0] ); + XMP_Uns32 binDenom = GetUns32AsIs ( &binPtr[1] ); + if ( ! nativeEndian ) { + binNum = Flip4 ( binNum ); + binDenom = Flip4 ( binDenom ); + } + + char strValue[40]; + snprintf ( strValue, sizeof(strValue), "%lu/%lu", (unsigned long)binNum, (unsigned long)binDenom ); // AUDIT: Using sizeof(strValue) is safe. + + xmp->AppendArrayItem ( xmpNS, xmpProp, kXMP_PropArrayIsOrdered, strValue ); + + } + + } catch ( ... ) { + // Do nothing, let other imports proceed. + // ? Notify client? + } + +} // ImportArrayTIFF_Rational + +// ================================================================================================= +// ImportArrayTIFF_SRational +// ========================= + +static void +ImportArrayTIFF_SRational ( const TIFF_Manager::TagInfo & tagInfo, const bool nativeEndian, + SXMPMeta * xmp, const char * xmpNS, const char * xmpProp ) +{ + try { // Don't let errors with one stop the others. + + XMP_Int32 * binPtr = (XMP_Int32*)tagInfo.dataPtr; + + xmp->DeleteProperty ( xmpNS, xmpProp ); // ! Don't keep appending, create a new array. + + for ( size_t i = 0; i < tagInfo.count; ++i, binPtr += 2 ) { + + XMP_Int32 binNum = binPtr[0]; + XMP_Int32 binDenom = binPtr[1]; + if ( ! nativeEndian ) { + Flip4 ( &binNum ); + Flip4 ( &binDenom ); + } + + char strValue[40]; + snprintf ( strValue, sizeof(strValue), "%ld/%ld", (long)binNum, (long)binDenom ); // AUDIT: Using sizeof(strValue) is safe. + + xmp->AppendArrayItem ( xmpNS, xmpProp, kXMP_PropArrayIsOrdered, strValue ); + + } + + } catch ( ... ) { + // Do nothing, let other imports proceed. + // ? Notify client? + } + +} // ImportArrayTIFF_SRational + +// ================================================================================================= +// ImportArrayTIFF_ASCII +// ===================== + +static void +ImportArrayTIFF_ASCII ( const TIFF_Manager::TagInfo & tagInfo, + SXMPMeta * xmp, const char * xmpNS, const char * xmpProp ) +{ + try { // Don't let errors with one stop the others. + + TrimTrailingSpaces ( (TIFF_Manager::TagInfo*) &tagInfo ); + if ( tagInfo.dataLen == 0 ) return; // Ignore empty tags. + + const char * chPtr = (const char *)tagInfo.dataPtr; + const char * chEnd = chPtr + tagInfo.dataLen; + const bool hasNul = (chPtr[tagInfo.dataLen-1] == 0); + const bool isUTF8 = ReconcileUtils::IsUTF8 ( chPtr, tagInfo.dataLen ); + + std::string strValue; + + if ( (! isUTF8) || (! hasNul) ) { + if ( isUTF8 ) { + strValue.assign ( chPtr, tagInfo.dataLen ); + } else { + if ( ignoreLocalText ) return; + ReconcileUtils::LocalToUTF8 ( chPtr, tagInfo.dataLen, &strValue ); + } + chPtr = strValue.c_str(); + chEnd = chPtr + strValue.size(); + } + + xmp->DeleteProperty ( xmpNS, xmpProp ); // ! Don't keep appending, create a new array. + + for ( ; chPtr < chEnd; chPtr += (strlen(chPtr) + 1) ) { + xmp->AppendArrayItem ( xmpNS, xmpProp, kXMP_PropArrayIsOrdered, chPtr ); + } + + } catch ( ... ) { + // Do nothing, let other imports proceed. + // ? Notify client? + } + +} // ImportArrayTIFF_ASCII + +// ================================================================================================= +// ImportArrayTIFF_Byte +// ==================== + +static void +ImportArrayTIFF_Byte ( const TIFF_Manager::TagInfo & tagInfo, + SXMPMeta * xmp, const char * xmpNS, const char * xmpProp ) +{ + try { // Don't let errors with one stop the others. + + XMP_Uns8 * binPtr = (XMP_Uns8*)tagInfo.dataPtr; + + xmp->DeleteProperty ( xmpNS, xmpProp ); // ! Don't keep appending, create a new array. + + for ( size_t i = 0; i < tagInfo.count; ++i, ++binPtr ) { + + XMP_Uns8 binValue = *binPtr; + + char strValue[20]; + snprintf ( strValue, sizeof(strValue), "%hu", (XMP_Uns16)binValue ); // AUDIT: Using sizeof(strValue) is safe. + + xmp->AppendArrayItem ( xmpNS, xmpProp, kXMP_PropArrayIsOrdered, strValue ); + + } + + } catch ( ... ) { + // Do nothing, let other imports proceed. + // ? Notify client? + } + +} // ImportArrayTIFF_Byte + +// ================================================================================================= +// ImportArrayTIFF_SByte +// ===================== + +static void +ImportArrayTIFF_SByte ( const TIFF_Manager::TagInfo & tagInfo, + SXMPMeta * xmp, const char * xmpNS, const char * xmpProp ) +{ + try { // Don't let errors with one stop the others. + + XMP_Int8 * binPtr = (XMP_Int8*)tagInfo.dataPtr; + + xmp->DeleteProperty ( xmpNS, xmpProp ); // ! Don't keep appending, create a new array. + + for ( size_t i = 0; i < tagInfo.count; ++i, ++binPtr ) { + + XMP_Int8 binValue = *binPtr; + + char strValue[20]; + snprintf ( strValue, sizeof(strValue), "%hd", (short)binValue ); // AUDIT: Using sizeof(strValue) is safe. + + xmp->AppendArrayItem ( xmpNS, xmpProp, kXMP_PropArrayIsOrdered, strValue ); + + } + + } catch ( ... ) { + // Do nothing, let other imports proceed. + // ? Notify client? + } + +} // ImportArrayTIFF_SByte + +// ================================================================================================= +// ImportArrayTIFF_SShort +// ====================== + +static void +ImportArrayTIFF_SShort ( const TIFF_Manager::TagInfo & tagInfo, const bool nativeEndian, + SXMPMeta * xmp, const char * xmpNS, const char * xmpProp ) +{ + try { // Don't let errors with one stop the others. + + XMP_Int16 * binPtr = (XMP_Int16*)tagInfo.dataPtr; + + xmp->DeleteProperty ( xmpNS, xmpProp ); // ! Don't keep appending, create a new array. + + for ( size_t i = 0; i < tagInfo.count; ++i, ++binPtr ) { + + XMP_Int16 binValue = *binPtr; + if ( ! nativeEndian ) Flip2 ( &binValue ); + + char strValue[20]; + snprintf ( strValue, sizeof(strValue), "%hd", binValue ); // AUDIT: Using sizeof(strValue) is safe. + + xmp->AppendArrayItem ( xmpNS, xmpProp, kXMP_PropArrayIsOrdered, strValue ); + + } + + } catch ( ... ) { + // Do nothing, let other imports proceed. + // ? Notify client? + } + +} // ImportArrayTIFF_SShort + +// ================================================================================================= +// ImportArrayTIFF_SLong +// ===================== + +static void +ImportArrayTIFF_SLong ( const TIFF_Manager::TagInfo & tagInfo, const bool nativeEndian, + SXMPMeta * xmp, const char * xmpNS, const char * xmpProp ) +{ + try { // Don't let errors with one stop the others. + + XMP_Int32 * binPtr = (XMP_Int32*)tagInfo.dataPtr; + + xmp->DeleteProperty ( xmpNS, xmpProp ); // ! Don't keep appending, create a new array. + + for ( size_t i = 0; i < tagInfo.count; ++i, ++binPtr ) { + + XMP_Int32 binValue = *binPtr; + if ( ! nativeEndian ) Flip4 ( &binValue ); + + char strValue[20]; + snprintf ( strValue, sizeof(strValue), "%ld", (long)binValue ); // AUDIT: Using sizeof(strValue) is safe. + + xmp->AppendArrayItem ( xmpNS, xmpProp, kXMP_PropArrayIsOrdered, strValue ); + + } + + } catch ( ... ) { + // Do nothing, let other imports proceed. + // ? Notify client? + } + +} // ImportArrayTIFF_SLong + +// ================================================================================================= +// ImportArrayTIFF_Float +// ===================== + +static void +ImportArrayTIFF_Float ( const TIFF_Manager::TagInfo & tagInfo, const bool nativeEndian, + SXMPMeta * xmp, const char * xmpNS, const char * xmpProp ) +{ + try { // Don't let errors with one stop the others. + + float * binPtr = (float*)tagInfo.dataPtr; + + xmp->DeleteProperty ( xmpNS, xmpProp ); // ! Don't keep appending, create a new array. + + for ( size_t i = 0; i < tagInfo.count; ++i, ++binPtr ) { + + float binValue = *binPtr; + if ( ! nativeEndian ) Flip4 ( &binValue ); + + std::string strValue; + SXMPUtils::ConvertFromFloat ( binValue, "", &strValue ); + + xmp->AppendArrayItem ( xmpNS, xmpProp, kXMP_PropArrayIsOrdered, strValue.c_str() ); + + } + + } catch ( ... ) { + // Do nothing, let other imports proceed. + // ? Notify client? + } + +} // ImportArrayTIFF_Float + +// ================================================================================================= +// ImportArrayTIFF_Double +// ====================== + +static void +ImportArrayTIFF_Double ( const TIFF_Manager::TagInfo & tagInfo, const bool nativeEndian, + SXMPMeta * xmp, const char * xmpNS, const char * xmpProp ) +{ + try { // Don't let errors with one stop the others. + + double * binPtr = (double*)tagInfo.dataPtr; + + xmp->DeleteProperty ( xmpNS, xmpProp ); // ! Don't keep appending, create a new array. + + for ( size_t i = 0; i < tagInfo.count; ++i, ++binPtr ) { + + double binValue = *binPtr; + if ( ! nativeEndian ) Flip8 ( &binValue ); + + std::string strValue; + SXMPUtils::ConvertFromFloat ( binValue, "", &strValue ); // ! Yes, ConvertFromFloat. + + xmp->AppendArrayItem ( xmpNS, xmpProp, kXMP_PropArrayIsOrdered, strValue.c_str() ); + + } + + } catch ( ... ) { + // Do nothing, let other imports proceed. + // ? Notify client? + } + +} // ImportArrayTIFF_Double + +// ================================================================================================= +// ImportArrayTIFF +// =============== + +static void +ImportArrayTIFF ( const TIFF_Manager::TagInfo & tagInfo, const bool nativeEndian, + SXMPMeta * xmp, const char * xmpNS, const char * xmpProp ) +{ + + // We've got a tag to map to XMP, decide how based on actual type and the expected count. Using + // the actual type eliminates a ShortOrLong case. Using the expected count is needed to know + // whether to create an XMP array. The actual count for an array could be 1. Put the most + // common cases first for better iCache utilization. + + switch ( tagInfo.type ) { + + case kTIFF_ShortType : + ImportArrayTIFF_Short ( tagInfo, nativeEndian, xmp, xmpNS, xmpProp ); + break; + + case kTIFF_LongType : + ImportArrayTIFF_Long ( tagInfo, nativeEndian, xmp, xmpNS, xmpProp ); + break; + + case kTIFF_RationalType : + ImportArrayTIFF_Rational ( tagInfo, nativeEndian, xmp, xmpNS, xmpProp ); + break; + + case kTIFF_SRationalType : + ImportArrayTIFF_SRational ( tagInfo, nativeEndian, xmp, xmpNS, xmpProp ); + break; + + case kTIFF_ASCIIType : + ImportArrayTIFF_ASCII ( tagInfo, xmp, xmpNS, xmpProp ); + break; + + case kTIFF_ByteType : + ImportArrayTIFF_Byte ( tagInfo, xmp, xmpNS, xmpProp ); + break; + + case kTIFF_SByteType : + ImportArrayTIFF_SByte ( tagInfo, xmp, xmpNS, xmpProp ); + break; + + case kTIFF_SShortType : + ImportArrayTIFF_SShort ( tagInfo, nativeEndian, xmp, xmpNS, xmpProp ); + break; + + case kTIFF_SLongType : + ImportArrayTIFF_SLong ( tagInfo, nativeEndian, xmp, xmpNS, xmpProp ); + break; + + case kTIFF_FloatType : + ImportArrayTIFF_Float ( tagInfo, nativeEndian, xmp, xmpNS, xmpProp ); + break; + + case kTIFF_DoubleType : + ImportArrayTIFF_Double ( tagInfo, nativeEndian, xmp, xmpNS, xmpProp ); + break; + + } + +} // ImportArrayTIFF + +// ================================================================================================= +// ImportTIFF_CheckStandardMapping +// =============================== + +static bool +ImportTIFF_CheckStandardMapping ( const TIFF_Manager::TagInfo & tagInfo, const TIFF_MappingToXMP & mapInfo ) +{ + XMP_Assert ( (kTIFF_ByteType <= tagInfo.type) && (tagInfo.type <= kTIFF_LastType) ); + XMP_Assert ( mapInfo.type <= kTIFF_LastType ); + + if ( (tagInfo.type < kTIFF_ByteType) || (tagInfo.type > kTIFF_LastType) ) return false; + + if ( tagInfo.type != mapInfo.type ) { + // Be tolerant of reasonable mismatches among numeric types. + if ( kTIFF_IsIntegerType[mapInfo.type] ) { + if ( ! kTIFF_IsIntegerType[tagInfo.type] ) return false; + } else if ( kTIFF_IsRationalType[mapInfo.type] ) { + if ( ! kTIFF_IsRationalType[tagInfo.type] ) return false; + } else if ( kTIFF_IsFloatType[mapInfo.type] ) { + if ( ! kTIFF_IsFloatType[tagInfo.type] ) return false; + } else { + return false; + } + } + + if ( (tagInfo.count != mapInfo.count) && // Maybe there is a problem because the counts don't match. + // (mapInfo.count != kAnyCount) && ... don't need this because of the new check below ... + (mapInfo.count == 1) ) return false; // Be tolerant of mismatch in expected array size. + + return true; + +} // ImportTIFF_CheckStandardMapping + +// ================================================================================================= +// ImportTIFF_StandardMappings +// =========================== + +static void +ImportTIFF_StandardMappings ( XMP_Uns8 ifd, const TIFF_Manager & tiff, SXMPMeta * xmp ) +{ + const bool nativeEndian = tiff.IsNativeEndian(); + TIFF_Manager::TagInfo tagInfo; + + const TIFF_MappingToXMP * mappings = 0; + + if ( ifd == kTIFF_PrimaryIFD ) { + mappings = sPrimaryIFDMappings; + } else if ( ifd == kTIFF_ExifIFD ) { + mappings = sExifIFDMappings; + } else if ( ifd == kTIFF_GPSInfoIFD ) { + mappings = sGPSInfoIFDMappings; + } else { + XMP_Throw ( "Invalid IFD for standard mappings", kXMPErr_InternalFailure ); + } + + for ( size_t i = 0; mappings[i].id != 0xFFFF; ++i ) { + + try { // Don't let errors with one stop the others. + + const TIFF_MappingToXMP & mapInfo = mappings[i]; + const bool mapSingle = ((mapInfo.count == 1) || (mapInfo.type == kTIFF_ASCIIType)); + + if ( mapInfo.name[0] == 0 ) continue; // Skip special mappings, handled higher up. + + bool found = tiff.GetTag ( ifd, mapInfo.id, &tagInfo ); + if ( ! found ) continue; + + XMP_Assert ( tagInfo.type != kTIFF_UndefinedType ); // These must have a special mapping. + if ( tagInfo.type == kTIFF_UndefinedType ) continue; + if ( ! ImportTIFF_CheckStandardMapping ( tagInfo, mapInfo ) ) continue; + + if ( mapSingle ) { + ImportSingleTIFF ( tagInfo, nativeEndian, xmp, mapInfo.ns, mapInfo.name ); + } else { + ImportArrayTIFF ( tagInfo, nativeEndian, xmp, mapInfo.ns, mapInfo.name ); + } + + } catch ( ... ) { + + // Do nothing, let other imports proceed. + // ? Notify client? + + } + + } + +} // ImportTIFF_StandardMappings + +// ================================================================================================= +// ================================================================================================= + +// ================================================================================================= +// ImportTIFF_Date +// =============== +// +// Convert an Exif 2.2 master date/time tag plus associated fractional seconds to an XMP date/time. +// The Exif date/time part is a 20 byte ASCII value formatted as "YYYY:MM:DD HH:MM:SS" with a +// terminating nul. Any of the numeric portions can be blanks if unknown. The fractional seconds +// are a nul terminated ASCII string with possible space padding. They are literally the fractional +// part, the digits that would be to the right of the decimal point. + +static void +ImportTIFF_Date ( const TIFF_Manager & tiff, const TIFF_Manager::TagInfo & dateInfo, + SXMPMeta * xmp, const char * xmpNS, const char * xmpProp ) +{ + XMP_Uns16 secID = 0; + switch ( dateInfo.id ) { + case kTIFF_DateTime : secID = kTIFF_SubSecTime; break; + case kTIFF_DateTimeOriginal : secID = kTIFF_SubSecTimeOriginal; break; + case kTIFF_DateTimeDigitized : secID = kTIFF_SubSecTimeDigitized; break; + } + + try { // Don't let errors with one stop the others. + + if ( (dateInfo.type != kTIFF_ASCIIType) || (dateInfo.count != 20) ) return; + + const char * dateStr = (const char *) dateInfo.dataPtr; + if ( (dateStr[4] != ':') || (dateStr[7] != ':') || + (dateStr[10] != ' ') || (dateStr[13] != ':') || (dateStr[16] != ':') ) return; + + XMP_DateTime binValue; + + binValue.year = GatherInt ( &dateStr[0], 4 ); + binValue.month = GatherInt ( &dateStr[5], 2 ); + binValue.day = GatherInt ( &dateStr[8], 2 ); + if ( (binValue.year != 0) | (binValue.month != 0) | (binValue.day != 0) ) binValue.hasDate = true; + + binValue.hour = GatherInt ( &dateStr[11], 2 ); + binValue.minute = GatherInt ( &dateStr[14], 2 ); + binValue.second = GatherInt ( &dateStr[17], 2 ); + binValue.nanoSecond = 0; // Get the fractional seconds later. + if ( (binValue.hour != 0) | (binValue.minute != 0) | (binValue.second != 0) ) binValue.hasTime = true; + + binValue.tzSign = 0; // ! Separate assignment, avoid VS integer truncation warning. + binValue.tzHour = binValue.tzMinute = 0; + binValue.hasTimeZone = false; // Exif times have no zone. + + // *** Consider looking at the TIFF/EP TimeZoneOffset tag? + + TIFF_Manager::TagInfo secInfo; + bool found = tiff.GetTag ( kTIFF_ExifIFD, secID, &secInfo ); // ! Subseconds are all in the Exif IFD. + + if ( found && (secInfo.type == kTIFF_ASCIIType) ) { + const char * fracPtr = (const char *) secInfo.dataPtr; + binValue.nanoSecond = GatherInt ( fracPtr, secInfo.dataLen ); + size_t digits = 0; + for ( ; (('0' <= *fracPtr) && (*fracPtr <= '9')); ++fracPtr ) ++digits; + for ( ; digits < 9; ++digits ) binValue.nanoSecond *= 10; + if ( binValue.nanoSecond != 0 ) binValue.hasTime = true; + } + + xmp->SetProperty_Date ( xmpNS, xmpProp, binValue ); + + } catch ( ... ) { + // Do nothing, let other imports proceed. + // ? Notify client? + } + +} // ImportTIFF_Date + +// ================================================================================================= +// ImportTIFF_LocTextASCII +// ======================= + +static void +ImportTIFF_LocTextASCII ( const TIFF_Manager & tiff, XMP_Uns8 ifd, XMP_Uns16 tagID, + SXMPMeta * xmp, const char * xmpNS, const char * xmpProp ) +{ + try { // Don't let errors with one stop the others. + + TIFF_Manager::TagInfo tagInfo; + + bool found = tiff.GetTag ( ifd, tagID, &tagInfo ); + if ( (! found) || (tagInfo.type != kTIFF_ASCIIType) ) return; + + TrimTrailingSpaces ( (TIFF_Manager::TagInfo*) &tagInfo ); + if ( tagInfo.dataLen == 0 ) return; // Ignore empty tags. + + const char * chPtr = (const char *)tagInfo.dataPtr; + const bool hasNul = (chPtr[tagInfo.dataLen-1] == 0); + const bool isUTF8 = ReconcileUtils::IsUTF8 ( chPtr, tagInfo.dataLen ); + + if ( isUTF8 && hasNul ) { + xmp->SetLocalizedText ( xmpNS, xmpProp, "", "x-default", chPtr ); + } else { + std::string strValue; + if ( isUTF8 ) { + strValue.assign ( chPtr, tagInfo.dataLen ); + } else { + if ( ignoreLocalText ) return; + ReconcileUtils::LocalToUTF8 ( chPtr, tagInfo.dataLen, &strValue ); + } + xmp->SetLocalizedText ( xmpNS, xmpProp, "", "x-default", strValue.c_str() ); + } + + } catch ( ... ) { + // Do nothing, let other imports proceed. + // ? Notify client? + } + +} // ImportTIFF_LocTextASCII + +// ================================================================================================= +// ImportTIFF_EncodedString +// ======================== + +static void +ImportTIFF_EncodedString ( const TIFF_Manager & tiff, const TIFF_Manager::TagInfo & tagInfo, + SXMPMeta * xmp, const char * xmpNS, const char * xmpProp, bool isLangAlt = false ) +{ + try { // Don't let errors with one stop the others. + + std::string strValue; + + bool ok = tiff.DecodeString ( tagInfo.dataPtr, tagInfo.dataLen, &strValue ); + if ( ! ok ) return; + + TrimTrailingSpaces ( &strValue ); + if ( strValue.empty() ) return; + + if ( ! isLangAlt ) { + xmp->SetProperty ( xmpNS, xmpProp, strValue.c_str() ); + } else { + xmp->SetLocalizedText ( xmpNS, xmpProp, "", "x-default", strValue.c_str() ); + } + + } catch ( ... ) { + // Do nothing, let other imports proceed. + // ? Notify client? + } + +} // ImportTIFF_EncodedString + +// ================================================================================================= +// ImportTIFF_Flash +// ================ + +static void +ImportTIFF_Flash ( const TIFF_Manager::TagInfo & tagInfo, bool nativeEndian, + SXMPMeta * xmp, const char * xmpNS, const char * xmpProp ) +{ + try { // Don't let errors with one stop the others. + + XMP_Uns16 binValue = GetUns16AsIs ( tagInfo.dataPtr ); + if ( ! nativeEndian ) binValue = Flip2 ( binValue ); + + bool fired = (bool)(binValue & 1); // Avoid implicit 0/1 conversion. + int rtrn = (binValue >> 1) & 3; + int mode = (binValue >> 3) & 3; + bool function = (bool)((binValue >> 5) & 1); + bool redEye = (bool)((binValue >> 6) & 1); + + static const char * sTwoBits[] = { "0", "1", "2", "3" }; + + xmp->SetStructField ( kXMP_NS_EXIF, "Flash", kXMP_NS_EXIF, "Fired", (fired ? kXMP_TrueStr : kXMP_FalseStr) ); + xmp->SetStructField ( kXMP_NS_EXIF, "Flash", kXMP_NS_EXIF, "Return", sTwoBits[rtrn] ); + xmp->SetStructField ( kXMP_NS_EXIF, "Flash", kXMP_NS_EXIF, "Mode", sTwoBits[mode] ); + xmp->SetStructField ( kXMP_NS_EXIF, "Flash", kXMP_NS_EXIF, "Function", (function ? kXMP_TrueStr : kXMP_FalseStr) ); + xmp->SetStructField ( kXMP_NS_EXIF, "Flash", kXMP_NS_EXIF, "RedEyeMode", (redEye ? kXMP_TrueStr : kXMP_FalseStr) ); + + } catch ( ... ) { + // Do nothing, let other imports proceed. + // ? Notify client? + } + +} // ImportTIFF_Flash + +// ================================================================================================= +// ImportConversionTable +// ===================== +// +// Although the XMP for the OECF and SFR tables is the same, the Exif is not. The OECF table has +// signed rational values and the SFR table has unsigned. But they are otherwise the same. + +static void +ImportConversionTable ( const TIFF_Manager::TagInfo & tagInfo, bool nativeEndian, + SXMPMeta * xmp, const char * xmpNS, const char * xmpProp ) +{ + const bool isSigned = (tagInfo.id == kTIFF_OECF); + XMP_Assert ( (tagInfo.id == kTIFF_OECF) || (tagInfo.id == kTIFF_SpatialFrequencyResponse) ); + + xmp->DeleteProperty ( xmpNS, xmpProp ); + + try { // Don't let errors with one stop the others. + + const XMP_Uns8 * bytePtr = (XMP_Uns8*)tagInfo.dataPtr; + const XMP_Uns8 * byteEnd = bytePtr + tagInfo.dataLen; + + XMP_Uns16 columns = *((XMP_Uns16*)bytePtr); + XMP_Uns16 rows = *((XMP_Uns16*)(bytePtr+2)); + if ( ! nativeEndian ) { + columns = Flip2 ( columns ); + rows = Flip2 ( rows ); + } + + char buffer[40]; + + snprintf ( buffer, sizeof(buffer), "%d", columns ); // AUDIT: Use of sizeof(buffer) is safe. + xmp->SetStructField ( xmpNS, xmpProp, kXMP_NS_EXIF, "Columns", buffer ); + snprintf ( buffer, sizeof(buffer), "%d", rows ); // AUDIT: Use of sizeof(buffer) is safe. + xmp->SetStructField ( xmpNS, xmpProp, kXMP_NS_EXIF, "Rows", buffer ); + + std::string arrayPath; + + SXMPUtils::ComposeStructFieldPath ( xmpNS, xmpProp, kXMP_NS_EXIF, "Names", &arrayPath ); + + bytePtr += 4; // Move to the list of names. Don't convert from local text, should really be ASCII. + for ( size_t i = columns; i > 0; --i ) { + size_t nameLen = strlen((XMP_StringPtr)bytePtr) + 1; // ! Include the terminating nul. + if ( (bytePtr + nameLen) > byteEnd ) XMP_Throw ( "OECF-SFR name overflow", kXMPErr_BadValue ); + if ( ! ReconcileUtils::IsUTF8 ( bytePtr, nameLen ) ) XMP_Throw ( "OECF-SFR name error", kXMPErr_BadValue ); + xmp->AppendArrayItem ( xmpNS, arrayPath.c_str(), kXMP_PropArrayIsOrdered, (XMP_StringPtr)bytePtr ); + bytePtr += nameLen; + } + + if ( (byteEnd - bytePtr) != (8 * columns * rows) ) XMP_Throw ( "OECF-SFR data overflow", kXMPErr_BadValue ); + SXMPUtils::ComposeStructFieldPath ( xmpNS, xmpProp, kXMP_NS_EXIF, "Values", &arrayPath ); + + XMP_Uns32 * binPtr = (XMP_Uns32*)bytePtr; + for ( size_t i = (columns * rows); i > 0; --i, binPtr += 2 ) { + + XMP_Uns32 binNum = binPtr[0]; + XMP_Uns32 binDenom = binPtr[1]; + if ( ! nativeEndian ) { + Flip4 ( &binNum ); + Flip4 ( &binDenom ); + } + + if ( (binDenom == 0) && (binNum != 0) ) XMP_Throw ( "OECF-SFR data overflow", kXMPErr_BadValue ); + if ( isSigned ) { + snprintf ( buffer, sizeof(buffer), "%ld/%ld", (long)binNum, (long)binDenom ); // AUDIT: Use of sizeof(buffer) is safe. + } else { + snprintf ( buffer, sizeof(buffer), "%lu/%lu", (unsigned long)binNum, (unsigned long)binDenom ); // AUDIT: Use of sizeof(buffer) is safe. + } + + xmp->AppendArrayItem ( xmpNS, arrayPath.c_str(), kXMP_PropArrayIsOrdered, buffer ); + + } + + return; + + } catch ( ... ) { + xmp->DeleteProperty ( xmpNS, xmpProp ); + // ? Notify client? + } + +} // ImportConversionTable + +// ================================================================================================= +// ImportTIFF_CFATable +// =================== + +static void +ImportTIFF_CFATable ( const TIFF_Manager::TagInfo & tagInfo, bool nativeEndian, + SXMPMeta * xmp, const char * xmpNS, const char * xmpProp ) +{ + try { // Don't let errors with one stop the others. + + const XMP_Uns8 * bytePtr = (XMP_Uns8*)tagInfo.dataPtr; + const XMP_Uns8 * byteEnd = bytePtr + tagInfo.dataLen; + + XMP_Uns16 columns = *((XMP_Uns16*)bytePtr); + XMP_Uns16 rows = *((XMP_Uns16*)(bytePtr+2)); + if ( ! nativeEndian ) { + columns = Flip2 ( columns ); + rows = Flip2 ( rows ); + } + + char buffer[20]; + std::string arrayPath; + + snprintf ( buffer, sizeof(buffer), "%d", columns ); // AUDIT: Use of sizeof(buffer) is safe. + xmp->SetStructField ( xmpNS, xmpProp, kXMP_NS_EXIF, "Columns", buffer ); + snprintf ( buffer, sizeof(buffer), "%d", rows ); // AUDIT: Use of sizeof(buffer) is safe. + xmp->SetStructField ( xmpNS, xmpProp, kXMP_NS_EXIF, "Rows", buffer ); + + bytePtr += 4; // Move to the matrix of values. + if ( (byteEnd - bytePtr) != (columns * rows) ) goto BadExif; // Make sure the values are present. + + SXMPUtils::ComposeStructFieldPath ( xmpNS, xmpProp, kXMP_NS_EXIF, "Values", &arrayPath ); + + for ( size_t i = (columns * rows); i > 0; --i, ++bytePtr ) { + snprintf ( buffer, sizeof(buffer), "%hu", (XMP_Uns16)(*bytePtr) ); // AUDIT: Use of sizeof(buffer) is safe. + xmp->AppendArrayItem ( xmpNS, arrayPath.c_str(), kXMP_PropArrayIsOrdered, buffer ); + } + + return; + + BadExif: // Ignore the tag if the table is ill-formed. + xmp->DeleteProperty ( xmpNS, xmpProp ); + return; + + } catch ( ... ) { + // Do nothing, let other imports proceed. + // ? Notify client? + } + +} // ImportTIFF_CFATable + +// ================================================================================================= +// ImportTIFF_DSDTable +// =================== + +static void +ImportTIFF_DSDTable ( const TIFF_Manager & tiff, const TIFF_Manager::TagInfo & tagInfo, + SXMPMeta * xmp, const char * xmpNS, const char * xmpProp ) +{ + try { // Don't let errors with one stop the others. + + const XMP_Uns8 * bytePtr = (XMP_Uns8*)tagInfo.dataPtr; + const XMP_Uns8 * byteEnd = bytePtr + tagInfo.dataLen; + + XMP_Uns16 columns = *((XMP_Uns16*)bytePtr); + XMP_Uns16 rows = *((XMP_Uns16*)(bytePtr+2)); + if ( ! tiff.IsNativeEndian() ) { + columns = Flip2 ( columns ); + rows = Flip2 ( rows ); + } + + char buffer[20]; + + snprintf ( buffer, sizeof(buffer), "%d", columns ); // AUDIT: Use of sizeof(buffer) is safe. + xmp->SetStructField ( xmpNS, xmpProp, kXMP_NS_EXIF, "Columns", buffer ); + snprintf ( buffer, sizeof(buffer), "%d", rows ); // AUDIT: Use of sizeof(buffer) is safe. + xmp->SetStructField ( xmpNS, xmpProp, kXMP_NS_EXIF, "Rows", buffer ); + + std::string arrayPath; + SXMPUtils::ComposeStructFieldPath ( xmpNS, xmpProp, kXMP_NS_EXIF, "Settings", &arrayPath ); + + bytePtr += 4; // Move to the list of settings. + UTF16Unit * utf16Ptr = (UTF16Unit*)bytePtr; + UTF16Unit * utf16End = (UTF16Unit*)byteEnd; + + std::string utf8; + + // Figure 17 in the Exif 2.2 spec is unclear. It has counts for rows and columns, but the + // settings are listed as 1..n, not as a rectangular matrix. So, ignore the counts and copy + // strings until the end of the Exif value. + + while ( utf16Ptr < utf16End ) { + + size_t nameLen = 0; + while ( utf16Ptr[nameLen] != 0 ) ++nameLen; + ++nameLen; // ! Include the terminating nul. + if ( (utf16Ptr + nameLen) > utf16End ) goto BadExif; + + try { + FromUTF16 ( utf16Ptr, nameLen, &utf8, tiff.IsBigEndian() ); + } catch ( ... ) { + goto BadExif; // Ignore the tag if there are conversion errors. + } + + xmp->AppendArrayItem ( xmpNS, arrayPath.c_str(), kXMP_PropArrayIsOrdered, utf8.c_str() ); + + utf16Ptr += nameLen; + + } + + return; + + BadExif: // Ignore the tag if the table is ill-formed. + xmp->DeleteProperty ( xmpNS, xmpProp ); + return; + + } catch ( ... ) { + // Do nothing, let other imports proceed. + // ? Notify client? + } + +} // ImportTIFF_DSDTable + +// ================================================================================================= +// ImportTIFF_GPSCoordinate +// ======================== + +static void +ImportTIFF_GPSCoordinate ( const TIFF_Manager & tiff, const TIFF_Manager::TagInfo & posInfo, + SXMPMeta * xmp, const char * xmpNS, const char * xmpProp ) +{ + try { // Don't let errors with one stop the others. Tolerate ill-formed values where reasonable. + + const bool nativeEndian = tiff.IsNativeEndian(); + + if ( (posInfo.type != kTIFF_RationalType) || (posInfo.count == 0) ) return; + + XMP_Uns16 refID = posInfo.id - 1; // ! The GPS refs and coordinates are all tag n and n+1. + TIFF_Manager::TagInfo refInfo; + bool found = tiff.GetTag ( kTIFF_GPSInfoIFD, refID, &refInfo ); + if ( (! found) || (refInfo.count == 0) ) return; + char ref = *((char*)refInfo.dataPtr); + if ( (ref != 'N') && (ref != 'S') && (ref != 'E') && (ref != 'W') ) return; + + XMP_Uns32 * binPtr = (XMP_Uns32*)posInfo.dataPtr; + XMP_Uns32 degNum = 0, degDenom = 1; // Defaults for missing parts. + XMP_Uns32 minNum = 0, minDenom = 1; + XMP_Uns32 secNum = 0, secDenom = 1; + if ( ! nativeEndian ) { + degDenom = Flip4 ( degDenom ); // So they can be flipped again below. + minDenom = Flip4 ( minDenom ); + secDenom = Flip4 ( secDenom ); + } + + degNum = GetUns32AsIs ( &binPtr[0] ); + degDenom = GetUns32AsIs ( &binPtr[1] ); + + if ( posInfo.count >= 2 ) { + minNum = GetUns32AsIs ( &binPtr[2] ); + minDenom = GetUns32AsIs ( &binPtr[3] ); + if ( posInfo.count >= 3 ) { + secNum = GetUns32AsIs ( &binPtr[4] ); + secDenom = GetUns32AsIs ( &binPtr[5] ); + } + } + + if ( ! nativeEndian ) { + degNum = Flip4 ( degNum ); + degDenom = Flip4 ( degDenom ); + minNum = Flip4 ( minNum ); + minDenom = Flip4 ( minDenom ); + secNum = Flip4 ( secNum ); + secDenom = Flip4 ( secDenom ); + } + + // *** No check is made, whether the numerator exceed the maximum values, + // which is 90 for Latitude and 180 for Longitude + // ! The Exif spec says denominator must not be zero, but in reality they can be + + char buffer[40]; + + // Simplest case: all denominators are 1 + if ( (degDenom == 1) && (minDenom == 1) && (secDenom == 1) ) { + + snprintf ( buffer, sizeof(buffer), "%lu,%lu,%lu%c", (unsigned long)degNum, (unsigned long)minNum, (unsigned long)secNum, ref ); // AUDIT: Using sizeof(buffer) is safe. + + } else if ( (degDenom == 0 && degNum != 0) || (minDenom == 0 && minNum != 0) || (secDenom == 0 && secNum != 0) ) { + + // Error case: a denominator is zero and the numerator is non-zero + return; // Do not continue with import + + } else { + + // Determine precision + // The code rounds up to the next power of 10 to get the number of fractional digits + XMP_Uns32 maxDenom = degDenom; + if ( minDenom > maxDenom ) maxDenom = minDenom; + if ( secDenom > maxDenom ) maxDenom = secDenom; + + int fracDigits = 1; + while ( maxDenom > 10 ) { ++fracDigits; maxDenom = maxDenom/10; } + + // Calculate the values + // At this point we know that the fractions are either 0/0, 0/y or x/y + + double degrees, minutes; + + // Degrees + if ( degDenom == 0 && degNum == 0 ) { + degrees = 0; + } else { + degrees = (double)( (XMP_Uns32)((double)degNum / (double)degDenom) ); // Just the integral number of degrees. + } + + // Minutes + if ( minDenom == 0 && minNum == 0 ) { + minutes = 0; + } else { + double temp = 0; + if( degrees != 0 ) temp = ((double)degNum / (double)degDenom) - degrees; + + minutes = (temp * 60.0) + ((double)minNum / (double)minDenom); + } + + // Seconds, are added to minutes + if ( secDenom != 0 && secNum != 0 ) { + minutes += ((double)secNum / (double)secDenom) / 60.0; + } + + snprintf ( buffer, sizeof(buffer), "%.0f,%.*f%c", degrees, fracDigits, minutes, ref ); // AUDIT: Using sizeof(buffer) is safe. + + } + + xmp->SetProperty ( xmpNS, xmpProp, buffer ); + + } catch ( ... ) { + // Do nothing, let other imports proceed. + // ? Notify client? + } + +} // ImportTIFF_GPSCoordinate + +// ================================================================================================= +// ImportTIFF_GPSTimeStamp +// ======================= + +static void +ImportTIFF_GPSTimeStamp ( const TIFF_Manager & tiff, const TIFF_Manager::TagInfo & timeInfo, + SXMPMeta * xmp, const char * xmpNS, const char * xmpProp ) +{ + try { // Don't let errors with one stop the others. + + const bool nativeEndian = tiff.IsNativeEndian(); + + bool haveDate; + TIFF_Manager::TagInfo dateInfo; + haveDate = tiff.GetTag ( kTIFF_GPSInfoIFD, kTIFF_GPSDateStamp, &dateInfo ); + if ( ! haveDate ) haveDate = tiff.GetTag ( kTIFF_ExifIFD, kTIFF_DateTimeOriginal, &dateInfo ); + if ( ! haveDate ) haveDate = tiff.GetTag ( kTIFF_ExifIFD, kTIFF_DateTimeDigitized, &dateInfo ); + if ( ! haveDate ) return; + + const char * dateStr = (const char *) dateInfo.dataPtr; + if ( ((dateStr[4] != ':') && (dateStr[4] != '-')) || ((dateStr[7] != ':') && (dateStr[7] != '-')) ) return; + if ( (dateStr[10] != 0) && (dateStr[10] != ' ') ) return; + + XMP_Uns32 * binPtr = (XMP_Uns32*)timeInfo.dataPtr; + XMP_Uns32 hourNum = GetUns32AsIs ( &binPtr[0] ); + XMP_Uns32 hourDenom = GetUns32AsIs ( &binPtr[1] ); + XMP_Uns32 minNum = GetUns32AsIs ( &binPtr[2] ); + XMP_Uns32 minDenom = GetUns32AsIs ( &binPtr[3] ); + XMP_Uns32 secNum = GetUns32AsIs ( &binPtr[4] ); + XMP_Uns32 secDenom = GetUns32AsIs ( &binPtr[5] ); + if ( ! nativeEndian ) { + hourNum = Flip4 ( hourNum ); + hourDenom = Flip4 ( hourDenom ); + minNum = Flip4 ( minNum ); + minDenom = Flip4 ( minDenom ); + secNum = Flip4 ( secNum ); + secDenom = Flip4 ( secDenom ); + } + + double fHour, fMin, fSec, fNano, temp; + fSec = (double)secNum / (double)secDenom; + temp = (double)minNum / (double)minDenom; + fMin = (double)((XMP_Uns32)temp); + fSec += (temp - fMin) * 60.0; + temp = (double)hourNum / (double)hourDenom; + fHour = (double)((XMP_Uns32)temp); + fSec += (temp - fHour) * 3600.0; + temp = (double)((XMP_Uns32)fSec); + fNano = ((fSec - temp) * (1000.0*1000.0*1000.0)) + 0.5; // Try to avoid n999... problems. + fSec = temp; + + XMP_DateTime binStamp; + binStamp.year = GatherInt ( dateStr, 4 ); + binStamp.month = GatherInt ( dateStr+5, 2 ); + binStamp.day = GatherInt ( dateStr+8, 2 ); + binStamp.hour = (XMP_Int32)fHour; + binStamp.minute = (XMP_Int32)fMin; + binStamp.second = (XMP_Int32)fSec; + binStamp.nanoSecond = (XMP_Int32)fNano; + binStamp.hasTimeZone = true; // Exif GPS TimeStamp is implicitly UTC. + binStamp.tzSign = kXMP_TimeIsUTC; + binStamp.tzHour = binStamp.tzMinute = 0; + + xmp->SetProperty_Date ( xmpNS, xmpProp, binStamp ); + + } catch ( ... ) { + // Do nothing, let other imports proceed. + // ? Notify client? + } + +} // ImportTIFF_GPSTimeStamp + +// ================================================================================================= + +static void ImportTIFF_PhotographicSensitivity ( const TIFF_Manager & exif, SXMPMeta * xmp ) { + + // PhotographicSensitivity has special cases for values over 65534 because the tag is SHORT. It + // has a count of "any", but all known cameras used a count of 1. Exif 2.3 still allows a count + // of "any" but says only 1 value should ever be recorded. We only process the first value if + // the count is greater than 1. + // + // Prior to Exif 2.3 the tag was called ISOSpeedRatings. Some cameras omit the tag and some + // write 65535. Most put the real ISO in MakerNote, which might be in the XMP from ACR. + // + // With Exif 2.3 five speed-related tags were added of type LONG: StandardOutputSensitivity, + // RecommendedExposureIndex, ISOSpeed, ISOSpeedLatitudeyyy, and ISOSpeedLatitudezzz. The tag + // SensitivityType was added to say which of these five is copied to PhotographicSensitivity. + // + // Import logic: + // Save exif:ISOSpeedRatings when cleaning namespaces (done in ImportPhotoData) + // If ExifVersion is less than "0230" (or missing) + // If PhotographicSensitivity[1] < 65535 or no exif:ISOSpeedRatings: set exif:ISOSpeedRatings from tag + // Else (ExifVersion is at least "0230") + // Import SensitivityType and related long tags + // If PhotographicSensitivity[1] < 65535: set exifEX:PhotographicSensitivity and exif:ISOSpeedRatings from tag + // Else (no PhotographicSensitivity tag or value is 65535) + // Set exifEX:PhotographicSensitivity from tag (missing or 65535) + // If have SensitivityType and indicated tag: set exif:ISOSpeedRatings from indicated tag + + try { + + bool found; + TIFF_Manager::TagInfo tagInfo; + + bool haveOldExif = true; // Default to old Exif if no version tag. + bool haveTag34855 = false; + bool haveLowISO = false; // Set for real if haveTag34855 is true. + + XMP_Uns32 valueTag34855; // ! Only care about the first value if more than 1. + + found = exif.GetTag ( kTIFF_ExifIFD, kTIFF_ExifVersion, &tagInfo ); + if ( found && (tagInfo.type == kTIFF_UndefinedType) && (tagInfo.count == 4) ) { + haveOldExif = (strncmp ( (char*)tagInfo.dataPtr, "0230", 4 ) < 0); + } + + haveTag34855 = exif.GetTag_Integer ( kTIFF_ExifIFD, kTIFF_PhotographicSensitivity, &valueTag34855 ); + if ( haveTag34855 ) haveLowISO = (valueTag34855 < 65535); + + if ( haveOldExif ) { // Exif version is before 2.3, use just the old tag and property. + + if ( haveTag34855 ) { + if ( haveLowISO || (! xmp->DoesPropertyExist ( kXMP_NS_EXIF, "ISOSpeedRatings" )) ) { + xmp->DeleteProperty ( kXMP_NS_EXIF, "ISOSpeedRatings" ); + xmp->AppendArrayItem ( kXMP_NS_EXIF, "ISOSpeedRatings", kXMP_PropArrayIsOrdered, "" ); + xmp->SetProperty_Int ( kXMP_NS_EXIF, "ISOSpeedRatings[1]", valueTag34855 ); + } + } + + } else { // Exif version is 2.3 or newer, use the Exif 2.3 tags and properties. + + // Import the SensitivityType and related long tags. + + XMP_Uns16 whichLongTag = 0; + XMP_Uns32 sensitivityType, tagValue; + + bool haveSensitivityType = exif.GetTag_Integer ( kTIFF_ExifIFD, kTIFF_SensitivityType, &sensitivityType ); + if ( haveSensitivityType ) { + xmp->SetProperty_Int ( kXMP_NS_ExifEX, "SensitivityType", sensitivityType ); + switch ( sensitivityType ) { + case 1 : // Use StandardOutputSensitivity for both 1 and 4. + case 4 : whichLongTag = kTIFF_StandardOutputSensitivity; break; + case 2 : whichLongTag = kTIFF_RecommendedExposureIndex; break; + case 3 : // Use ISOSpeed for all of 3, 5, 6, and 7. + case 5 : + case 6 : + case 7 : whichLongTag = kTIFF_ISOSpeed; break; + } + } + + found = exif.GetTag_Integer ( kTIFF_ExifIFD, kTIFF_StandardOutputSensitivity, &tagValue ); + if ( found ) { + xmp->SetProperty_Int64 ( kXMP_NS_ExifEX, "StandardOutputSensitivity", tagValue ); + } + + found = exif.GetTag_Integer ( kTIFF_ExifIFD, kTIFF_RecommendedExposureIndex, &tagValue ); + if ( found ) { + xmp->SetProperty_Int64 ( kXMP_NS_ExifEX, "RecommendedExposureIndex", tagValue ); + } + + found = exif.GetTag_Integer ( kTIFF_ExifIFD, kTIFF_ISOSpeed, &tagValue ); + if ( found ) { + xmp->SetProperty_Int64 ( kXMP_NS_ExifEX, "ISOSpeed", tagValue ); + } + + found = exif.GetTag_Integer ( kTIFF_ExifIFD, kTIFF_ISOSpeedLatitudeyyy, &tagValue ); + if ( found ) { + xmp->SetProperty_Int64 ( kXMP_NS_ExifEX, "ISOSpeedLatitudeyyy", tagValue ); + } + + found = exif.GetTag_Integer ( kTIFF_ExifIFD, kTIFF_ISOSpeedLatitudezzz, &tagValue ); + if ( found ) { + xmp->SetProperty_Int64 ( kXMP_NS_ExifEX, "ISOSpeedLatitudezzz", tagValue ); + } + + // Deal with the special cases for exifEX:PhotographicSensitivity and exif:ISOSpeedRatings. + + if ( haveTag34855 & haveLowISO ) { // The easier low ISO case. + + xmp->DeleteProperty ( kXMP_NS_EXIF, "ISOSpeedRatings" ); + xmp->AppendArrayItem ( kXMP_NS_EXIF, "ISOSpeedRatings", kXMP_PropArrayIsOrdered, "" ); + xmp->SetProperty_Int ( kXMP_NS_EXIF, "ISOSpeedRatings[1]", valueTag34855 ); + xmp->SetProperty_Int ( kXMP_NS_ExifEX, "PhotographicSensitivity", valueTag34855 ); + + } else { // The more complex high ISO case, or no PhotographicSensitivity tag. + + if ( haveTag34855 ) { + XMP_Assert ( valueTag34855 == 65535 ); + xmp->SetProperty_Int ( kXMP_NS_ExifEX, "PhotographicSensitivity", valueTag34855 ); + } + + if ( whichLongTag != 0 ) { + found = exif.GetTag ( kTIFF_ExifIFD, whichLongTag, &tagInfo ); + if ( found && (tagInfo.type == kTIFF_LongType) && (tagInfo.count == 1) ){ + xmp->DeleteProperty ( kXMP_NS_EXIF, "ISOSpeedRatings" ); + xmp->AppendArrayItem ( kXMP_NS_EXIF, "ISOSpeedRatings", kXMP_PropArrayIsOrdered, "" ); + xmp->SetProperty_Int ( kXMP_NS_EXIF, "ISOSpeedRatings[1]", exif.GetUns32 ( tagInfo.dataPtr ) ); + } + } + + } + + } + + } catch ( ... ) { + + // Do nothing, don't let failures here stop other exports. + + } + +} // ImportTIFF_PhotographicSensitivity + +// ================================================================================================= +// ================================================================================================= + +// ================================================================================================= +// PhotoDataUtils::Import2WayExif +// ============================== +// +// Import the TIFF/Exif tags that have 2 way mappings to XMP, i.e. no correspondence to IPTC. +// These are always imported for the tiff: and exif: namespaces, but not for others. + +void +PhotoDataUtils::Import2WayExif ( const TIFF_Manager & exif, SXMPMeta * xmp, int iptcDigestState ) +{ + const bool nativeEndian = exif.IsNativeEndian(); + + bool found, foundFromXMP; + TIFF_Manager::TagInfo tagInfo; + XMP_OptionBits flags; + + ImportTIFF_StandardMappings ( kTIFF_PrimaryIFD, exif, xmp ); + ImportTIFF_StandardMappings ( kTIFF_ExifIFD, exif, xmp ); + ImportTIFF_StandardMappings ( kTIFF_GPSInfoIFD, exif, xmp ); + + // -------------------------------------------------------- + // Add the old Adobe names for new Exif 2.3 tags if wanted. + + #if SupportOldExifProperties + + // CameraOwnerName -> aux:OwnerName + found = exif.GetTag ( kTIFF_ExifIFD, kTIFF_CameraOwnerName, &tagInfo ); + if ( found && (tagInfo.type == kTIFF_ASCIIType) && (tagInfo.count > 0) ) { + ImportSingleTIFF ( tagInfo, nativeEndian, xmp, kXMP_NS_EXIF_Aux, "OwnerName" ); + } + + // BodySerialNumber -> aux:SerialNumber + found = exif.GetTag ( kTIFF_ExifIFD, kTIFF_BodySerialNumber, &tagInfo ); + if ( found && (tagInfo.type == kTIFF_ASCIIType) && (tagInfo.count > 0) ) { + ImportSingleTIFF ( tagInfo, nativeEndian, xmp, kXMP_NS_EXIF_Aux, "SerialNumber" ); + } + + // LensModel -> aux:Lens + found = exif.GetTag ( kTIFF_ExifIFD, kTIFF_LensModel, &tagInfo ); + if ( found && (tagInfo.type == kTIFF_ASCIIType) && (tagInfo.count > 0) ) { + ImportSingleTIFF ( tagInfo, nativeEndian, xmp, kXMP_NS_EXIF_Aux, "Lens" ); + } + + // LensSpecification -> aux:LensInfo as a single string - use the XMP form for simplicity + found = xmp->GetProperty ( kXMP_NS_ExifEX, "LensSpecification", 0, &flags ); + if ( found && XMP_PropIsArray(flags) ) { + std::string fullStr, oneItem; + size_t count = (size_t) xmp->CountArrayItems ( kXMP_NS_ExifEX, "LensSpecification" ); + if ( count > 0 ) { + (void) xmp->GetArrayItem ( kXMP_NS_ExifEX, "LensSpecification", 1, &fullStr, 0 ); + for ( size_t i = 2; i <= count; ++i ) { + fullStr += ' '; + (void) xmp->GetArrayItem ( kXMP_NS_ExifEX, "LensSpecification", i, &oneItem, 0 ); + fullStr += oneItem; + } + } + xmp->SetProperty ( kXMP_NS_EXIF_Aux, "LensInfo", fullStr.c_str(), kXMP_DeleteExisting ); + } + + #endif + + // -------------------------------------------------------------------------------------------- + // Fixup erroneous cases that appear to have a negative value for GPSAltitude in the Exif. This + // treats any value with the high bit set as a negative, which is more likely in practice than + // an actual value over 2 billion. + + found = exif.GetTag ( kTIFF_GPSInfoIFD, kTIFF_GPSAltitude, &tagInfo ); + if ( found && (tagInfo.type == kTIFF_RationalType) && (tagInfo.count == 1) ) { + + XMP_Uns32 num = exif.GetUns32 ( tagInfo.dataPtr ); + XMP_Uns32 denom = exif.GetUns32 ( (XMP_Uns8*)tagInfo.dataPtr + 4 ); + + bool fixXMP = false; + bool numNeg = num >> 31; + bool denomNeg = denom >> 31; + + if ( denomNeg ) { // The denominator looks negative, shift the sign to the numerator. + denom = -denom; + num = -num; + numNeg = num >> 31; + fixXMP = true; + } + + if ( numNeg ) { // The numerator looks negative, fix the XMP. + xmp->SetProperty ( kXMP_NS_EXIF, "GPSAltitudeRef", "1" ); + num = -num; + fixXMP = true; + } + + if ( fixXMP ) { + char buffer [32]; + snprintf ( buffer, sizeof(buffer), "%lu/%lu", (unsigned long) num, (unsigned long) denom ); // AUDIT: Using sizeof(buffer) is safe. + xmp->SetProperty ( kXMP_NS_EXIF, "GPSAltitude", buffer ); + } + + } + + // --------------------------------------------------------------- + // Import DateTimeOriginal and DateTime if the XMP doss not exist. + + found = exif.GetTag ( kTIFF_ExifIFD, kTIFF_DateTimeOriginal, &tagInfo ); + foundFromXMP = xmp->DoesPropertyExist ( kXMP_NS_EXIF, "DateTimeOriginal" ); + + if ( found && (! foundFromXMP) && (tagInfo.type == kTIFF_ASCIIType) ) { + ImportTIFF_Date ( exif, tagInfo, xmp, kXMP_NS_EXIF, "DateTimeOriginal" ); + } + + found = exif.GetTag ( kTIFF_PrimaryIFD, kTIFF_DateTime, &tagInfo ); + foundFromXMP = xmp->DoesPropertyExist ( kXMP_NS_XMP, "ModifyDate" ); + + if ( found && (! foundFromXMP) && (tagInfo.type == kTIFF_ASCIIType) ) { + ImportTIFF_Date ( exif, tagInfo, xmp, kXMP_NS_XMP, "ModifyDate" ); + } + + // ---------------------------------------------------- + // Import the Exif IFD tags that have special mappings. + + // There are moderately complex import special cases for PhotographicSensitivity. + ImportTIFF_PhotographicSensitivity ( exif, xmp ); + + // 42032 CameraOwnerName has a standard mapping. As a special case also set dc:creator if there + // is no Exif Artist tag and no dc:creator in the XMP. + found = exif.GetTag ( kTIFF_PrimaryIFD, kTIFF_Artist, &tagInfo ); + foundFromXMP = xmp->DoesPropertyExist ( kXMP_NS_DC, "creator" ); + if ( (! found) && (! foundFromXMP) ) { + found = exif.GetTag ( kTIFF_ExifIFD, kTIFF_CameraOwnerName, &tagInfo ); + if ( found ) { + std::string xmpValue ( (char*)tagInfo.dataPtr, tagInfo.dataLen ); + xmp->AppendArrayItem ( kXMP_NS_DC, "creator", kXMP_PropArrayIsOrdered, xmpValue.c_str() ); + } + } + + // 36864 ExifVersion is 4 "undefined" ASCII characters. + found = exif.GetTag ( kTIFF_ExifIFD, kTIFF_ExifVersion, &tagInfo ); + if ( found && (tagInfo.type == kTIFF_UndefinedType) && (tagInfo.count == 4) ) { + char str[5]; + + *((XMP_Uns32*)str) = GetUns32AsIs ( tagInfo.dataPtr ); + str[4] = 0; + xmp->SetProperty ( kXMP_NS_EXIF, "ExifVersion", str ); + } + + // 40960 FlashpixVersion is 4 "undefined" ASCII characters. + found = exif.GetTag ( kTIFF_ExifIFD, kTIFF_FlashpixVersion, &tagInfo ); + if ( found && (tagInfo.type == kTIFF_UndefinedType) && (tagInfo.count == 4) ) { + char str[5]; + + *((XMP_Uns32*)str) = GetUns32AsIs ( tagInfo.dataPtr ); + str[4] = 0; + xmp->SetProperty ( kXMP_NS_EXIF, "FlashpixVersion", str ); + } + + // 37121 ComponentsConfiguration is an array of 4 "undefined" UInt8 bytes. + found = exif.GetTag ( kTIFF_ExifIFD, kTIFF_ComponentsConfiguration, &tagInfo ); + if ( found && (tagInfo.type == kTIFF_UndefinedType) && (tagInfo.count == 4) ) { + ImportArrayTIFF_Byte ( tagInfo, xmp, kXMP_NS_EXIF, "ComponentsConfiguration" ); + } + + // 37510 UserComment is a string with explicit encoding. + found = exif.GetTag ( kTIFF_ExifIFD, kTIFF_UserComment, &tagInfo ); + if ( found ) { + ImportTIFF_EncodedString ( exif, tagInfo, xmp, kXMP_NS_EXIF, "UserComment", true /* isLangAlt */ ); + } + + // 34856 OECF is an OECF/SFR table. + found = exif.GetTag ( kTIFF_ExifIFD, kTIFF_OECF, &tagInfo ); + if ( found ) { + ImportConversionTable ( tagInfo, nativeEndian, xmp, kXMP_NS_EXIF, "OECF" ); + } + + // 37385 Flash is a UInt16 collection of bit fields and is mapped to a struct in XMP. + found = exif.GetTag ( kTIFF_ExifIFD, kTIFF_Flash, &tagInfo ); + if ( found && (tagInfo.type == kTIFF_ShortType) && (tagInfo.count == 1) ) { + ImportTIFF_Flash ( tagInfo, nativeEndian, xmp, kXMP_NS_EXIF, "Flash" ); + } + + // 41484 SpatialFrequencyResponse is an OECF/SFR table. + found = exif.GetTag ( kTIFF_ExifIFD, kTIFF_SpatialFrequencyResponse, &tagInfo ); + if ( found ) { + ImportConversionTable ( tagInfo, nativeEndian, xmp, kXMP_NS_EXIF, "SpatialFrequencyResponse" ); + } + + // 41728 FileSource is an "undefined" UInt8. + found = exif.GetTag ( kTIFF_ExifIFD, kTIFF_FileSource, &tagInfo ); + if ( found && (tagInfo.type == kTIFF_UndefinedType) && (tagInfo.count == 1) ) { + ImportSingleTIFF_Byte ( tagInfo, xmp, kXMP_NS_EXIF, "FileSource" ); + } + + // 41729 SceneType is an "undefined" UInt8. + found = exif.GetTag ( kTIFF_ExifIFD, kTIFF_SceneType, &tagInfo ); + if ( found && (tagInfo.type == kTIFF_UndefinedType) && (tagInfo.count == 1) ) { + ImportSingleTIFF_Byte ( tagInfo, xmp, kXMP_NS_EXIF, "SceneType" ); + } + + // 41730 CFAPattern is a custom table. + found = exif.GetTag ( kTIFF_ExifIFD, kTIFF_CFAPattern, &tagInfo ); + if ( found ) { + ImportTIFF_CFATable ( tagInfo, nativeEndian, xmp, kXMP_NS_EXIF, "CFAPattern" ); + } + + // 41995 DeviceSettingDescription is a custom table. + found = exif.GetTag ( kTIFF_ExifIFD, kTIFF_DeviceSettingDescription, &tagInfo ); + if ( found ) { + ImportTIFF_DSDTable ( exif, tagInfo, xmp, kXMP_NS_EXIF, "DeviceSettingDescription" ); + } + + // -------------------------------------------------------- + // Import the GPS Info IFD tags that have special mappings. + + // 0 GPSVersionID is 4 UInt8 bytes and mapped as "n.n.n.n". + found = exif.GetTag ( kTIFF_GPSInfoIFD, kTIFF_GPSVersionID, &tagInfo ); + if ( found && (tagInfo.type == kTIFF_ByteType) && (tagInfo.count == 4) ) { + const XMP_Uns8 * binValue = (const XMP_Uns8 *) tagInfo.dataPtr; + char strOut[20];// ! Value could be up to 16 bytes: "255.255.255.255" plus nul. + snprintf ( strOut, sizeof(strOut), "%u.%u.%u.%u", // AUDIT: Use of sizeof(strOut) is safe. + binValue[0], binValue[1], binValue[2], binValue[3] ); + xmp->SetProperty ( kXMP_NS_EXIF, "GPSVersionID", strOut ); + } + + // 2 GPSLatitude is a GPS coordinate master. + found = exif.GetTag ( kTIFF_GPSInfoIFD, kTIFF_GPSLatitude, &tagInfo ); + if ( found ) { + ImportTIFF_GPSCoordinate ( exif, tagInfo, xmp, kXMP_NS_EXIF, "GPSLatitude" ); + } + + // 4 GPSLongitude is a GPS coordinate master. + found = exif.GetTag ( kTIFF_GPSInfoIFD, kTIFF_GPSLongitude, &tagInfo ); + if ( found ) { + ImportTIFF_GPSCoordinate ( exif, tagInfo, xmp, kXMP_NS_EXIF, "GPSLongitude" ); + } + + // 7 GPSTimeStamp is a UTC time as 3 rationals, mated with the optional GPSDateStamp. + found = exif.GetTag ( kTIFF_GPSInfoIFD, kTIFF_GPSTimeStamp, &tagInfo ); + if ( found && (tagInfo.type == kTIFF_RationalType) && (tagInfo.count == 3) ) { + ImportTIFF_GPSTimeStamp ( exif, tagInfo, xmp, kXMP_NS_EXIF, "GPSTimeStamp" ); + } + + // 20 GPSDestLatitude is a GPS coordinate master. + found = exif.GetTag ( kTIFF_GPSInfoIFD, kTIFF_GPSDestLatitude, &tagInfo ); + if ( found ) { + ImportTIFF_GPSCoordinate ( exif, tagInfo, xmp, kXMP_NS_EXIF, "GPSDestLatitude" ); + } + + // 22 GPSDestLongitude is a GPS coordinate master. + found = exif.GetTag ( kTIFF_GPSInfoIFD, kTIFF_GPSDestLongitude, &tagInfo ); + if ( found ) { + ImportTIFF_GPSCoordinate ( exif, tagInfo, xmp, kXMP_NS_EXIF, "GPSDestLongitude" ); + } + + // 27 GPSProcessingMethod is a string with explicit encoding. + found = exif.GetTag ( kTIFF_GPSInfoIFD, kTIFF_GPSProcessingMethod, &tagInfo ); + if ( found ) { + ImportTIFF_EncodedString ( exif, tagInfo, xmp, kXMP_NS_EXIF, "GPSProcessingMethod" ); + } + + // 28 GPSAreaInformation is a string with explicit encoding. + found = exif.GetTag ( kTIFF_GPSInfoIFD, kTIFF_GPSAreaInformation, &tagInfo ); + if ( found ) { + ImportTIFF_EncodedString ( exif, tagInfo, xmp, kXMP_NS_EXIF, "GPSAreaInformation" ); + } + +} // PhotoDataUtils::Import2WayExif + +// ================================================================================================= +// Import3WayDateTime +// ================== + +static void Import3WayDateTime ( XMP_Uns16 exifTag, const TIFF_Manager & exif, const IPTC_Manager & iptc, + SXMPMeta * xmp, int iptcDigestState, const IPTC_Manager & oldIPTC ) +{ + XMP_Uns8 iptcDS; + XMP_StringPtr xmpNS, xmpProp; + + if ( exifTag == kTIFF_DateTimeOriginal ) { + iptcDS = kIPTC_DateCreated; + xmpNS = kXMP_NS_Photoshop; + xmpProp = "DateCreated"; + } else if ( exifTag == kTIFF_DateTimeDigitized ) { + iptcDS = kIPTC_DigitalCreateDate; + xmpNS = kXMP_NS_XMP; + xmpProp = "CreateDate"; + } else { + XMP_Throw ( "Unrecognized dateID", kXMPErr_BadParam ); + } + + size_t iptcCount; + bool haveXMP, haveExif, haveIPTC; // ! These are manipulated to simplify MWG-compliant logic. + std::string xmpValue, exifValue, iptcValue; + TIFF_Manager::TagInfo exifInfo; + IPTC_Manager::DataSetInfo iptcInfo; + + // Get the basic info about available values. + haveXMP = xmp->GetProperty ( xmpNS, xmpProp, &xmpValue, 0 ); + iptcCount = PhotoDataUtils::GetNativeInfo ( iptc, iptcDS, iptcDigestState, haveXMP, &iptcInfo ); + haveIPTC = (iptcCount > 0); + haveExif = (! haveXMP) && (! haveIPTC) && PhotoDataUtils::GetNativeInfo ( exif, kTIFF_ExifIFD, exifTag, &exifInfo ); + XMP_Assert ( (! (haveExif & haveXMP)) & (! (haveExif & haveIPTC)) ); + + if ( haveIPTC && ((iptcDigestState == kDigestDiffers) || (!haveXMP && !haveExif)) ) { + + PhotoDataUtils::ImportIPTC_Date ( iptcDS, iptc, xmp ); + + } else if ( haveExif && (exifInfo.type == kTIFF_ASCIIType) ) { + + // Only import the Exif form if the non-TZ information differs from the XMP. + + TIFF_FileWriter exifFromXMP; + TIFF_Manager::TagInfo infoFromXMP; + + ExportTIFF_Date ( *xmp, xmpNS, xmpProp, &exifFromXMP, exifTag ); + bool foundFromXMP = exifFromXMP.GetTag ( kTIFF_ExifIFD, exifTag, &infoFromXMP ); + + if ( (! foundFromXMP) || (exifInfo.dataLen != infoFromXMP.dataLen) || + (! XMP_LitNMatch ( (char*)exifInfo.dataPtr, (char*)infoFromXMP.dataPtr, exifInfo.dataLen )) ) { + ImportTIFF_Date ( exif, exifInfo, xmp, xmpNS, xmpProp ); + } + + } + +} // Import3WayDateTime + +// ================================================================================================= +// PhotoDataUtils::Import3WayItems +// =============================== +// +// Handle the imports that involve all 3 of Exif, IPTC, and XMP. There are only 4 properties with +// 3-way mappings, copyright, description, creator, and date/time. Following the MWG guidelines, +// this general policy is applied separately to each: +// +// If the new IPTC digest differs from the stored digest (favor IPTC over Exif and XMP) +// If the IPTC value differs from the predicted old IPTC value +// Import the IPTC value, including deleting the XMP +// Else if the Exif is non-empty and differs from the XMP +// Import the Exif value (does not delete existing XMP) +// Else if the stored IPTC digest is missing (favor Exif over IPTC, or IPTC over missing XMP) +// If the Exif is non-empty and differs from the XMP +// Import the Exif value (does not delete existing XMP) +// Else if the XMP is missing and the Exif is missing or empty +// Import the IPTC value +// Else (the new IPTC digest matches the stored digest - ignore the IPTC) +// If the Exif is non-empty and differs from the XMP +// Import the Exif value (does not delete existing XMP) +// +// Note that missing or empty Exif will never cause existing XMP to be deleted. This is a pragmatic +// choice to improve compatibility with pre-MWG software. There are few Exif-only editors for these +// 3-way properties, there are important existing IPTC-only editors. + +// ------------------------------------------------------------------------------------------------- + +void PhotoDataUtils::Import3WayItems ( const TIFF_Manager & exif, const IPTC_Manager & iptc, SXMPMeta * xmp, int iptcDigestState ) +{ + size_t iptcCount; + + bool haveXMP, haveExif, haveIPTC; // ! These are manipulated to simplify MWG-compliant logic. + std::string xmpValue, exifValue, iptcValue; + + TIFF_Manager::TagInfo exifInfo; + IPTC_Manager::DataSetInfo iptcInfo; + + IPTC_Writer oldIPTC; + if ( iptcDigestState == kDigestDiffers ) { + PhotoDataUtils::ExportIPTC ( *xmp, &oldIPTC ); // Predict old IPTC DataSets based on the existing XMP. + } + + // --------------------------------------------------------------------------------- + // Process the copyright. Replace internal nuls in the Exif to "merge" the portions. + + // Get the basic info about available values. + haveXMP = xmp->GetLocalizedText ( kXMP_NS_DC, "rights", "", "x-default", 0, &xmpValue, 0 ); + iptcCount = PhotoDataUtils::GetNativeInfo ( iptc, kIPTC_CopyrightNotice, iptcDigestState, haveXMP, &iptcInfo ); + haveIPTC = (iptcCount > 0); + haveExif = (! haveXMP) && (! haveIPTC) && PhotoDataUtils::GetNativeInfo ( exif, kTIFF_PrimaryIFD, kTIFF_Copyright, &exifInfo ); + XMP_Assert ( (! (haveExif & haveXMP)) & (! (haveExif & haveIPTC)) ); + + if ( haveExif && (exifInfo.dataLen > 1) ) { // Replace internal nul characters with linefeed. + for ( XMP_Uns32 i = 0; i < exifInfo.dataLen-1; ++i ) { + if ( ((char*)exifInfo.dataPtr)[i] == 0 ) ((char*)exifInfo.dataPtr)[i] = 0x0A; + } + } + + if ( haveIPTC && ((iptcDigestState == kDigestDiffers) || (!haveXMP && !haveExif)) ) { + PhotoDataUtils::ImportIPTC_LangAlt ( iptc, xmp, kIPTC_CopyrightNotice, kXMP_NS_DC, "rights" ); + } else if ( haveExif && PhotoDataUtils::IsValueDifferent ( exifInfo, xmpValue, &exifValue ) ) { + xmp->SetLocalizedText ( kXMP_NS_DC, "rights", "", "x-default", exifValue.c_str() ); + } + + // ------------------------ + // Process the description. + + // Get the basic info about available values. + haveXMP = xmp->GetLocalizedText ( kXMP_NS_DC, "description", "", "x-default", 0, &xmpValue, 0 ); + iptcCount = PhotoDataUtils::GetNativeInfo ( iptc, kIPTC_Description, iptcDigestState, haveXMP, &iptcInfo ); + haveIPTC = (iptcCount > 0); + haveExif = (! haveXMP) && (! haveIPTC) && PhotoDataUtils::GetNativeInfo ( exif, kTIFF_PrimaryIFD, kTIFF_ImageDescription, &exifInfo ); + XMP_Assert ( (! (haveExif & haveXMP)) & (! (haveExif & haveIPTC)) ); + + if ( haveIPTC && ((iptcDigestState == kDigestDiffers) || (!haveXMP && !haveExif)) ) { + PhotoDataUtils::ImportIPTC_LangAlt ( iptc, xmp, kIPTC_Description, kXMP_NS_DC, "description" ); + } else if ( haveExif && PhotoDataUtils::IsValueDifferent ( exifInfo, xmpValue, &exifValue ) ) { + xmp->SetLocalizedText ( kXMP_NS_DC, "description", "", "x-default", exifValue.c_str() ); + } + + // ------------------------------------------------------------------------------------------- + // Process the creator. The XMP and IPTC are arrays, the Exif is a semicolon separated string. + + // Get the basic info about available values. + haveXMP = xmp->DoesPropertyExist ( kXMP_NS_DC, "creator" ); + haveExif = PhotoDataUtils::GetNativeInfo ( exif, kTIFF_PrimaryIFD, kTIFF_Artist, &exifInfo ); + iptcCount = PhotoDataUtils::GetNativeInfo ( iptc, kIPTC_Creator, iptcDigestState, haveXMP, &iptcInfo ); + haveIPTC = (iptcCount > 0); + haveExif = (! haveXMP) && (! haveIPTC) && PhotoDataUtils::GetNativeInfo ( exif, kTIFF_PrimaryIFD, kTIFF_Artist, &exifInfo ); + XMP_Assert ( (! (haveExif & haveXMP)) & (! (haveExif & haveIPTC)) ); + + if ( haveIPTC && ((iptcDigestState == kDigestDiffers) || (!haveXMP && !haveExif)) ) { + PhotoDataUtils::ImportIPTC_Array ( iptc, xmp, kIPTC_Creator, kXMP_NS_DC, "creator" ); + } else if ( haveExif && PhotoDataUtils::IsValueDifferent ( exifInfo, xmpValue, &exifValue ) ) { + SXMPUtils::SeparateArrayItems ( xmp, kXMP_NS_DC, "creator", + (kXMP_PropArrayIsOrdered | kXMPUtil_AllowCommas), exifValue ); + } + + // ------------------------------------------------------------------------------ + // Process DateTimeDigitized; DateTimeOriginal and DateTime are 2-way. + // *** Exif DateTimeOriginal <-> XMP exif:DateTimeOriginal + // *** IPTC DateCreated <-> XMP photoshop:DateCreated + // *** Exif DateTimeDigitized <-> IPTC DigitalCreateDate <-> XMP xmp:CreateDate + // *** TIFF DateTime <-> XMP xmp:ModifyDate + + Import3WayDateTime ( kTIFF_DateTimeDigitized, exif, iptc, xmp, iptcDigestState, oldIPTC ); + +} // PhotoDataUtils::Import3WayItems + +// ================================================================================================= +// ================================================================================================= + +// ================================================================================================= + +static bool DecodeRational ( const char * ratio, XMP_Uns32 * num, XMP_Uns32 * denom ) { + + unsigned long locNum, locDenom; + char nextChar; // Used to make sure sscanf consumes all of the string. + + int items = sscanf ( ratio, "%lu/%lu%c", &locNum, &locDenom, &nextChar ); // AUDIT: This is safe, check the calls. + + if ( items != 2 ) { + if ( items != 1 ) return false; + locDenom = 1; // The XMP was just an integer, assume a denominator of 1. + } + + *num = (XMP_Uns32)locNum; + *denom = (XMP_Uns32)locDenom; + return true; + +} // DecodeRational + +// ================================================================================================= +// ExportSingleTIFF +// ================ +// +// This is only called when the XMP exists and will be exported. And only for standard mappings. + +// ! Only implemented for the types known to be needed. + +static void +ExportSingleTIFF ( TIFF_Manager * tiff, XMP_Uns8 ifd, const TIFF_MappingToXMP & mapInfo, + bool nativeEndian, const std::string & xmpValue ) +{ + XMP_Assert ( (mapInfo.count == 1) || (mapInfo.type == kTIFF_ASCIIType) ); + XMP_Assert ( mapInfo.name[0] != 0 ); // Must be a standard mapping. + + bool ok; + char nextChar; // Used to make sure sscanf consumes all of the string. + + switch ( mapInfo.type ) { + + case kTIFF_ByteType : { + unsigned short binValue; + int items = sscanf ( xmpValue.c_str(), "%hu%c", &binValue, &nextChar ); // AUDIT: Using xmpValue.c_str() is safe. + if ( items != 1 ) return; // ? complain? notify client? + tiff->SetTag_Byte ( ifd, mapInfo.id, (XMP_Uns8)binValue ); + break; + } + + case kTIFF_ShortType : { + unsigned long binValue; + int items = sscanf ( xmpValue.c_str(), "%lu%c", &binValue, &nextChar ); // AUDIT: Using xmpValue.c_str() is safe. + if ( items != 1 ) return; // ? complain? notify client? + tiff->SetTag_Short ( ifd, mapInfo.id, (XMP_Uns16)binValue ); + break; + } + + case kTIFF_LongType : { + unsigned long binValue; + int items = sscanf ( xmpValue.c_str(), "%lu%c", &binValue, &nextChar ); // AUDIT: Using xmpValue.c_str() is safe. + if ( items != 1 ) return; // ? complain? notify client? + tiff->SetTag_Long ( ifd, mapInfo.id, (XMP_Uns32)binValue ); + break; + } + + case kTIFF_ShortOrLongType : { + unsigned long binValue; + int items = sscanf ( xmpValue.c_str(), "%lu%c", &binValue, &nextChar ); // AUDIT: Using xmpValue.c_str() is safe. + if ( items != 1 ) return; // ? complain? notify client? + if ( binValue <= 0xFFFF ) { + tiff->SetTag_Short ( ifd, mapInfo.id, (XMP_Uns16)binValue ); + } else { + tiff->SetTag_Long ( ifd, mapInfo.id, (XMP_Uns32)binValue ); + } + break; + } + + case kTIFF_RationalType : { // The XMP is formatted as "num/denom". + XMP_Uns32 num, denom; + ok = DecodeRational ( xmpValue.c_str(), &num, &denom ); + if ( ! ok ) return; // ? complain? notify client? + tiff->SetTag_Rational ( ifd, mapInfo.id, num, denom ); + break; + } + + case kTIFF_SRationalType : { // The XMP is formatted as "num/denom". + signed long num, denom; + int items = sscanf ( xmpValue.c_str(), "%ld/%ld%c", &num, &denom, &nextChar ); // AUDIT: Using xmpValue.c_str() is safe. + if ( items != 2 ) { + if ( items != 1 ) return; // ? complain? notify client? + denom = 1; // The XMP was just an integer, assume a denominator of 1. + } + tiff->SetTag_SRational ( ifd, mapInfo.id, (XMP_Int32)num, (XMP_Int32)denom ); + break; + } + + case kTIFF_ASCIIType : + tiff->SetTag ( ifd, mapInfo.id, kTIFF_ASCIIType, (XMP_Uns32)(xmpValue.size()+1), xmpValue.c_str() ); + break; + + default: + XMP_Assert ( false ); // Force a debug assert for unexpected types. + + } + +} // ExportSingleTIFF + +// ================================================================================================= +// ExportArrayTIFF +// ================ +// +// This is only called when the XMP exists and will be exported. And only for standard mappings. + +// ! Only implemented for the types known to be needed. + +static void +ExportArrayTIFF ( TIFF_Manager * tiff, XMP_Uns8 ifd, const TIFF_MappingToXMP & mapInfo, bool nativeEndian, + const SXMPMeta & xmp, const char * xmpNS, const char * xmpArray ) +{ + XMP_Assert ( (mapInfo.count != 1) && (mapInfo.type != kTIFF_ASCIIType) ); + XMP_Assert ( mapInfo.name[0] != 0 ); // Must be a standard mapping. + XMP_Assert ( (mapInfo.type == kTIFF_ShortType) || (mapInfo.type == kTIFF_RationalType) ); + XMP_Assert ( xmp.DoesPropertyExist ( xmpNS, xmpArray ) ); + + size_t arraySize = xmp.CountArrayItems ( xmpNS, xmpArray ); + if ( arraySize == 0 ) { + tiff->DeleteTag ( ifd, mapInfo.id ); + return; + } + + if ( mapInfo.type == kTIFF_ShortType ) { + + std::vector shortVector; + shortVector.assign ( arraySize, 0 ); + XMP_Uns16 * shortPtr = (XMP_Uns16*) &shortVector[0]; + + std::string itemPath; + XMP_Int32 int32; + XMP_Uns16 uns16; + for ( size_t i = 1; i <= arraySize; ++i, ++shortPtr ) { + SXMPUtils::ComposeArrayItemPath ( xmpNS, xmpArray, (XMP_Index)i, &itemPath ); + xmp.GetProperty_Int ( xmpNS, itemPath.c_str(), &int32, 0 ); + uns16 = (XMP_Uns16)int32; + if ( ! nativeEndian ) uns16 = Flip2 ( uns16 ); + *shortPtr = uns16; + } + + tiff->SetTag ( ifd, mapInfo.id, kTIFF_ShortType, (XMP_Uns32)arraySize, &shortVector[0] ); + + } else if ( mapInfo.type == kTIFF_RationalType ) { + + std::vector rationalVector; + rationalVector.assign ( 2*arraySize, 0 ); + XMP_Uns32 * rationalPtr = (XMP_Uns32*) &rationalVector[0]; + + std::string itemPath, xmpValue; + XMP_Uns32 num, denom; + for ( size_t i = 1; i <= arraySize; ++i, rationalPtr += 2 ) { + SXMPUtils::ComposeArrayItemPath ( xmpNS, xmpArray, (XMP_Index)i, &itemPath ); + bool isPropoerty = xmp.GetProperty ( xmpNS, itemPath.c_str(), &xmpValue, 0 ); + if ( ! isPropoerty ) return; + bool ok = DecodeRational ( xmpValue.c_str(), &num, &denom ); + if ( ! ok ) return; + if ( ! nativeEndian ) { num = Flip4 ( num ); denom = Flip4 ( denom ); } + rationalPtr[0] = num; + rationalPtr[1] = denom; + } + + tiff->SetTag ( ifd, mapInfo.id, kTIFF_RationalType, (XMP_Uns32)arraySize, &rationalVector[0] ); + + } + +} // ExportArrayTIFF + +// ================================================================================================= +// ExportTIFF_StandardMappings +// =========================== + +static void +ExportTIFF_StandardMappings ( XMP_Uns8 ifd, TIFF_Manager * tiff, const SXMPMeta & xmp ) +{ + const bool nativeEndian = tiff->IsNativeEndian(); + TIFF_Manager::TagInfo tagInfo; + std::string xmpValue; + XMP_OptionBits xmpForm; + + const TIFF_MappingToXMP * mappings = 0; + + if ( ifd == kTIFF_PrimaryIFD ) { + mappings = sPrimaryIFDMappings; + } else if ( ifd == kTIFF_ExifIFD ) { + mappings = sExifIFDMappings; + } else if ( ifd == kTIFF_GPSInfoIFD ) { + mappings = sGPSInfoIFDMappings; + } else { + XMP_Throw ( "Invalid IFD for standard mappings", kXMPErr_InternalFailure ); + } + + for ( size_t i = 0; mappings[i].id != 0xFFFF; ++i ) { + + try { // Don't let errors with one stop the others. + + const TIFF_MappingToXMP & mapInfo = mappings[i]; + + if ( mapInfo.exportMode == kExport_Never ) continue; + if ( mapInfo.name[0] == 0 ) continue; // Skip special mappings, handled higher up. + + bool haveTIFF = tiff->GetTag ( ifd, mapInfo.id, &tagInfo ); + if ( haveTIFF && (mapInfo.exportMode == kExport_InjectOnly) ) continue; + + bool haveXMP = xmp.GetProperty ( mapInfo.ns, mapInfo.name, &xmpValue, &xmpForm ); + if ( ! haveXMP ) { + + if ( haveTIFF && (mapInfo.exportMode == kExport_Always) ) tiff->DeleteTag ( ifd, mapInfo.id ); + + } else { + + XMP_Assert ( tagInfo.type != kTIFF_UndefinedType ); // These must have a special mapping. + if ( tagInfo.type == kTIFF_UndefinedType ) continue; + + const bool mapSingle = ((mapInfo.count == 1) || (mapInfo.type == kTIFF_ASCIIType)); + if ( mapSingle ) { + if ( ! XMP_PropIsSimple ( xmpForm ) ) continue; // ? Notify client? + ExportSingleTIFF ( tiff, ifd, mapInfo, nativeEndian, xmpValue ); + } else { + if ( ! XMP_PropIsArray ( xmpForm ) ) continue; // ? Notify client? + ExportArrayTIFF ( tiff, ifd, mapInfo, nativeEndian, xmp, mapInfo.ns, mapInfo.name ); + } + + } + + } catch ( ... ) { + + // Do nothing, let other imports proceed. + // ? Notify client? + + } + + } + +} // ExportTIFF_StandardMappings + +// ================================================================================================= +// ExportTIFF_Date +// =============== +// +// Convert an XMP date/time to an Exif 2.2 master date/time tag plus associated fractional seconds. +// The Exif date/time part is a 20 byte ASCII value formatted as "YYYY:MM:DD HH:MM:SS" with a +// terminating nul. The fractional seconds are a nul terminated ASCII string with possible space +// padding. They are literally the fractional part, the digits that would be to the right of the +// decimal point. + +static void +ExportTIFF_Date ( const SXMPMeta & xmp, const char * xmpNS, const char * xmpProp, TIFF_Manager * tiff, XMP_Uns16 mainID ) +{ + XMP_Uns8 mainIFD = kTIFF_ExifIFD; + XMP_Uns16 fracID=0; + switch ( mainID ) { + case kTIFF_DateTime : mainIFD = kTIFF_PrimaryIFD; fracID = kTIFF_SubSecTime; break; + case kTIFF_DateTimeOriginal : fracID = kTIFF_SubSecTimeOriginal; break; + case kTIFF_DateTimeDigitized : fracID = kTIFF_SubSecTimeDigitized; break; + } + + try { // Don't let errors with one stop the others. + + std::string xmpStr; + bool foundXMP = xmp.GetProperty ( xmpNS, xmpProp, &xmpStr, 0 ); + if ( ! foundXMP ) { + tiff->DeleteTag ( mainIFD, mainID ); + tiff->DeleteTag ( kTIFF_ExifIFD, fracID ); // ! The subseconds are always in the Exif IFD. + return; + } + + // Format using all of the numbers. Then overwrite blanks for missing fields. The fields + // missing from the XMP are detected with length checks: YYYY-MM-DDThh:mm:ss + // < 18 - no seconds + // < 15 - no minutes + // < 12 - no hours + // < 9 - no day + // < 6 - no month + // < 1 - no year + + XMP_DateTime xmpBin; + SXMPUtils::ConvertToDate ( xmpStr.c_str(), &xmpBin ); + + char buffer[24]; + snprintf ( buffer, sizeof(buffer), "%04d:%02d:%02d %02d:%02d:%02d", // AUDIT: Use of sizeof(buffer) is safe. + xmpBin.year, xmpBin.month, xmpBin.day, xmpBin.hour, xmpBin.minute, xmpBin.second ); + + size_t xmpLen = xmpStr.size(); + if ( xmpLen < 18 ) { + buffer[17] = buffer[18] = ' '; + if ( xmpLen < 15 ) { + buffer[14] = buffer[15] = ' '; + if ( xmpLen < 12 ) { + buffer[11] = buffer[12] = ' '; + if ( xmpLen < 9 ) { + buffer[8] = buffer[9] = ' '; + if ( xmpLen < 6 ) { + buffer[5] = buffer[6] = ' '; + if ( xmpLen < 1 ) { + buffer[0] = buffer[1] = buffer[2] = buffer[3] = ' '; + } + } + } + } + } + } + + tiff->SetTag_ASCII ( mainIFD, mainID, buffer ); + + if ( xmpBin.nanoSecond == 0 ) { + + tiff->DeleteTag ( kTIFF_ExifIFD, fracID ); + + } else { + + snprintf ( buffer, sizeof(buffer), "%09d", xmpBin.nanoSecond ); // AUDIT: Use of sizeof(buffer) is safe. + for ( size_t i = strlen(buffer)-1; i > 0; --i ) { + if ( buffer[i] != '0' ) break; + buffer[i] = 0; // Strip trailing zero digits. + } + + tiff->SetTag_ASCII ( kTIFF_ExifIFD, fracID, buffer ); // ! The subseconds are always in the Exif IFD. + + } + + } catch ( ... ) { + // Do nothing, let other exports proceed. + // ? Notify client? + } + +} // ExportTIFF_Date + +// ================================================================================================= +// ExportTIFF_ArrayASCII +// ===================== +// +// Catenate all of the XMP array values into a string. Use a "; " separator for Artist, nul for others. + +static void +ExportTIFF_ArrayASCII ( const SXMPMeta & xmp, const char * xmpNS, const char * xmpProp, + TIFF_Manager * tiff, XMP_Uns8 ifd, XMP_Uns16 id ) +{ + try { // Don't let errors with one stop the others. + + std::string itemValue, fullValue; + XMP_OptionBits xmpFlags; + + bool foundXMP = xmp.GetProperty ( xmpNS, xmpProp, 0, &xmpFlags ); + if ( ! foundXMP ) { + tiff->DeleteTag ( ifd, id ); + return; + } + + if ( ! XMP_PropIsArray ( xmpFlags ) ) return; // ? Complain? Delete the tag? + + if ( id == kTIFF_Artist ) { + SXMPUtils::CatenateArrayItems ( xmp, xmpNS, xmpProp, 0, 0, + (kXMP_PropArrayIsOrdered | kXMPUtil_AllowCommas), &fullValue ); + fullValue += '\x0'; // ! Need explicit final nul for SetTag below. + } else { + size_t count = xmp.CountArrayItems ( xmpNS, xmpProp ); + for ( size_t i = 1; i <= count; ++i ) { // ! XMP arrays are indexed from 1. + (void) xmp.GetArrayItem ( xmpNS, xmpProp, (XMP_Index)i, &itemValue, &xmpFlags ); + if ( ! XMP_PropIsSimple ( xmpFlags ) ) continue; // ? Complain? + fullValue.append ( itemValue ); + fullValue.append ( 1, '\x0' ); + } + } + + tiff->SetTag ( ifd, id, kTIFF_ASCIIType, (XMP_Uns32)fullValue.size(), fullValue.c_str() ); // ! Already have trailing nul. + + } catch ( ... ) { + // Do nothing, let other exports proceed. + // ? Notify client? + } + +} // ExportTIFF_ArrayASCII + +// ================================================================================================= +// ExportTIFF_LocTextASCII +// ====================== + +static void +ExportTIFF_LocTextASCII ( const SXMPMeta & xmp, const char * xmpNS, const char * xmpProp, + TIFF_Manager * tiff, XMP_Uns8 ifd, XMP_Uns16 id ) +{ + try { // Don't let errors with one stop the others. + + std::string xmpValue; + + bool foundXMP = xmp.GetLocalizedText ( xmpNS, xmpProp, "", "x-default", 0, &xmpValue, 0 ); + if ( ! foundXMP ) { + tiff->DeleteTag ( ifd, id ); + return; + } + + tiff->SetTag ( ifd, id, kTIFF_ASCIIType, (XMP_Uns32)( xmpValue.size()+1 ), xmpValue.c_str() ); + + } catch ( ... ) { + // Do nothing, let other exports proceed. + // ? Notify client? + } + +} // ExportTIFF_LocTextASCII + +// ================================================================================================= +// ExportTIFF_EncodedString +// ======================== + +static void +ExportTIFF_EncodedString ( const SXMPMeta & xmp, const char * xmpNS, const char * xmpProp, + TIFF_Manager * tiff, XMP_Uns8 ifd, XMP_Uns16 id, bool isLangAlt = false ) +{ + try { // Don't let errors with one stop the others. + + std::string xmpValue; + XMP_OptionBits xmpFlags; + + bool foundXMP = xmp.GetProperty ( xmpNS, xmpProp, &xmpValue, &xmpFlags ); + if ( ! foundXMP ) { + tiff->DeleteTag ( ifd, id ); + return; + } + + if ( ! isLangAlt ) { + if ( ! XMP_PropIsSimple ( xmpFlags ) ) return; // ? Complain? Delete the tag? + } else { + if ( ! XMP_ArrayIsAltText ( xmpFlags ) ) return; // ? Complain? Delete the tag? + bool ok = xmp.GetLocalizedText ( xmpNS, xmpProp, "", "x-default", 0, &xmpValue, 0 ); + if ( ! ok ) return; // ? Complain? Delete the tag? + } + + XMP_Uns8 encoding = kTIFF_EncodeASCII; + for ( size_t i = 0; i < xmpValue.size(); ++i ) { + if ( (XMP_Uns8)xmpValue[i] >= 0x80 ) { + encoding = kTIFF_EncodeUnicode; + break; + } + } + + tiff->SetTag_EncodedString ( ifd, id, xmpValue.c_str(), encoding ); + + } catch ( ... ) { + // Do nothing, let other exports proceed. + // ? Notify client? + } + +} // ExportTIFF_EncodedString + +// ================================================================================================= +// ExportTIFF_GPSCoordinate +// ======================== +// +// The XMP format is either "deg,min,secR" or "deg,min.fracR", where 'R' is the reference direction, +// 'N', 'S', 'E', or 'W'. The location gets output as ( deg/1, min/1, sec/1 ) for the first form, +// and ( deg/1, minFrac/denom, 0/1 ) for the second form. + +// ! We arbitrarily limit the number of fractional minute digits to 6 to avoid overflow in the +// ! combined numerator. But we don't otherwise check for overflow or range errors. + +static void +ExportTIFF_GPSCoordinate ( const SXMPMeta & xmp, const char * xmpNS, const char * xmpProp, + TIFF_Manager * tiff, XMP_Uns8 ifd, XMP_Uns16 _id ) +{ + XMP_Uns16 refID = _id-1; // ! The GPS refs and locations are all tag N-1 and N pairs. + XMP_Uns16 locID = _id; + + XMP_Assert ( (locID & 1) == 0 ); + + try { // Don't let errors with one stop the others. Tolerate ill-formed values where reasonable. + + std::string xmpValue; + XMP_OptionBits xmpFlags; + + bool foundXMP = xmp.GetProperty ( xmpNS, xmpProp, &xmpValue, &xmpFlags ); + if ( ! foundXMP ) { + tiff->DeleteTag ( ifd, refID ); + tiff->DeleteTag ( ifd, locID ); + return; + } + + if ( ! XMP_PropIsSimple ( xmpFlags ) ) return; + + const char * chPtr = xmpValue.c_str(); // Guaranteed to have a nul terminator. + + XMP_Uns32 deg=0, minNum=0, minDenom=1, sec=0; + + // Extract the degree part, it must be present. + + while ( (*chPtr == ' ') || (*chPtr == '\t') ) ++chPtr; + if ( (*chPtr < '0') || (*chPtr > '9') ) return; // Bad XMP string. + for ( ; ('0' <= *chPtr) && (*chPtr <= '9'); ++chPtr ) deg = deg*10 + (*chPtr - '0'); + while ( (*chPtr == ' ') || (*chPtr == '\t') ) ++chPtr; + if ( (*chPtr == ',') || (*chPtr == ';') ) ++chPtr; + while ( (*chPtr == ' ') || (*chPtr == '\t') ) ++chPtr; + + // Extract the whole minutes if present. + + if ( ('0' <= *chPtr) && (*chPtr <= '9') ) { + + for ( ; ('0' <= *chPtr) && (*chPtr <= '9'); ++chPtr ) minNum = minNum*10 + (*chPtr - '0'); + + // Extract the fractional minutes or seconds if present. + + if ( *chPtr == '.' ) { + + ++chPtr; // Skip the period. + for ( ; ('0' <= *chPtr) && (*chPtr <= '9'); ++chPtr ) { + if ( minDenom > 100*1000 ) continue; // Don't accumulate any more digits. + minDenom *= 10; + minNum = minNum*10 + (*chPtr - '0'); + } + + } else { + + while ( (*chPtr == ' ') || (*chPtr == '\t') ) ++chPtr; + if ( (*chPtr == ',') || (*chPtr == ';') ) ++chPtr; + while ( (*chPtr == ' ') || (*chPtr == '\t') ) ++chPtr; + for ( ; ('0' <= *chPtr) && (*chPtr <= '9'); ++chPtr ) sec = sec*10 + (*chPtr - '0'); + + } + + } + + // The compass direction part is required. But it isn't worth the bother to make sure it is + // appropriate for the tag. Little chance of ever seeing an error, it causes no great harm. + + while ( (*chPtr == ' ') || (*chPtr == '\t') ) ++chPtr; + if ( (*chPtr == ',') || (*chPtr == ';') ) ++chPtr; // Tolerate ',' or ';' here also. + while ( (*chPtr == ' ') || (*chPtr == '\t') ) ++chPtr; + + char ref[2]; + ref[0] = *chPtr; + ref[1] = 0; + + if ( ('a' <= ref[0]) && (ref[0] <= 'z') ) ref[0] -= 0x20; + if ( (ref[0] != 'N') && (ref[0] != 'S') && (ref[0] != 'E') && (ref[0] != 'W') ) return; + + tiff->SetTag ( ifd, refID, kTIFF_ASCIIType, 2, &ref[0] ); + + XMP_Uns32 loc[6]; + tiff->PutUns32 ( deg, &loc[0] ); + tiff->PutUns32 ( 1, &loc[1] ); + tiff->PutUns32 ( minNum, &loc[2] ); + tiff->PutUns32 ( minDenom, &loc[3] ); + tiff->PutUns32 ( sec, &loc[4] ); + tiff->PutUns32 ( 1, &loc[5] ); + + tiff->SetTag ( ifd, locID, kTIFF_RationalType, 3, &loc[0] ); + + } catch ( ... ) { + + // Do nothing, let other exports proceed. + // ? Notify client? + + } + +} // ExportTIFF_GPSCoordinate + +// ================================================================================================= +// ExportTIFF_GPSTimeStamp +// ======================= +// +// The Exif is in 2 tags, GPSTimeStamp and GPSDateStamp. The time is 3 rationals for the hour, minute, +// and second in UTC. The date is a nul terminated string "YYYY:MM:DD". + +static const double kBillion = 1000.0*1000.0*1000.0; +static const double mMaxSec = 4.0*kBillion - 1.0; + +static void +ExportTIFF_GPSTimeStamp ( const SXMPMeta & xmp, const char * xmpNS, const char * xmpProp, TIFF_Manager * tiff ) +{ + + try { // Don't let errors with one stop the others. + + XMP_DateTime binXMP; + bool foundXMP = xmp.GetProperty_Date ( xmpNS, xmpProp, &binXMP, 0 ); + if ( ! foundXMP ) { + tiff->DeleteTag ( kTIFF_GPSInfoIFD, kTIFF_GPSTimeStamp ); + tiff->DeleteTag ( kTIFF_GPSInfoIFD, kTIFF_GPSDateStamp ); + return; + } + + SXMPUtils::ConvertToUTCTime ( &binXMP ); + + XMP_Uns32 exifTime[6]; + tiff->PutUns32 ( binXMP.hour, &exifTime[0] ); + tiff->PutUns32 ( 1, &exifTime[1] ); + tiff->PutUns32 ( binXMP.minute, &exifTime[2] ); + tiff->PutUns32 ( 1, &exifTime[3] ); + if ( binXMP.nanoSecond == 0 ) { + tiff->PutUns32 ( binXMP.second, &exifTime[4] ); + tiff->PutUns32 ( 1, &exifTime[5] ); + } else { + double fSec = (double)binXMP.second + ((double)binXMP.nanoSecond / kBillion ); + XMP_Uns32 denom = 1000*1000; // Choose microsecond resolution by default. + TIFF_Manager::TagInfo oldInfo; + bool hadExif = tiff->GetTag ( kTIFF_GPSInfoIFD, kTIFF_GPSTimeStamp, &oldInfo ); + if ( hadExif && (oldInfo.type == kTIFF_RationalType) && (oldInfo.count == 3) ) { + XMP_Uns32 oldDenom = tiff->GetUns32 ( &(((XMP_Uns32*)oldInfo.dataPtr)[5]) ); + if ( oldDenom != 1 ) denom = oldDenom; + } + fSec *= denom; + while ( fSec > mMaxSec ) { fSec /= 10; denom /= 10; } + tiff->PutUns32 ( (XMP_Uns32)fSec, &exifTime[4] ); + tiff->PutUns32 ( denom, &exifTime[5] ); + } + tiff->SetTag ( kTIFF_GPSInfoIFD, kTIFF_GPSTimeStamp, kTIFF_RationalType, 3, &exifTime[0] ); + + char exifDate[16]; // AUDIT: Long enough, only need 11. + snprintf ( exifDate, 12, "%04d:%02d:%02d", binXMP.year, binXMP.month, binXMP.day ); + if ( exifDate[10] == 0 ) { // Make sure there is no value overflow. + tiff->SetTag ( kTIFF_GPSInfoIFD, kTIFF_GPSDateStamp, kTIFF_ASCIIType, 11, exifDate ); + } + + } catch ( ... ) { + // Do nothing, let other exports proceed. + // ? Notify client? + } + +} // ExportTIFF_GPSTimeStamp + +// ================================================================================================= + +static void ExportTIFF_PhotographicSensitivity ( SXMPMeta * xmp, TIFF_Manager * exif ) { + + // PhotographicSensitivity has special cases for values over 65534 because the tag is SHORT. It + // has a count of "any", but all known cameras used a count of 1. Exif 2.3 still allows a count + // of "any" but says only 1 value should ever be recorded. We only process the first value if + // the count is greater than 1. + // + // Prior to Exif 2.3 the tag was called ISOSpeedRatings. Some cameras omit the tag and some + // write 65535. Most put the real ISO in MakerNote, which be in the XMP from ACR. + // + // With Exif 2.3 five speed-related tags were added of type LONG: StandardOutputSensitivity, + // RecommendedExposureIndex, ISOSpeed, ISOSpeedLatitudeyyy, and ISOSpeedLatitudezzz. The tag + // SensitivityType was added to say which of these five is copied to PhotographicSensitivity. + // + // Export logic: + // If ExifVersion is less than "0230" (or missing) + // If exif:ISOSpeedRatings[1] <= 65535: inject tag (if missing), remove exif:ISOSpeedRatings + // Else (ExifVersion is at least "0230") + // If no exifEX:PhotographicSensitivity: set it from exif:ISOSpeedRatings[1] + // Remove exif:ISOSpeedRatings (to not be saved in namespace cleanup) + // If exifEX:PhotographicSensitivity <= 65535: inject tag (if missing) + // Else if exifEX:PhotographicSensitivity over 65535 + // If no PhotographicSensitivity tag and no SensitivityType tag and no ISOSpeed tag: + // Inject PhotographicSensitivity tag as 65535 + // If no exifEX:SensitivityType and no exifEX:ISOSpeed + // Set exifEX:SensitivityType to 3 + // Set exifEX:ISOSpeed to exifEX:PhotographicSensitivity + // Inject SensitivityType and long tags (if missing) + // Save exif:ISOSpeedRatings when cleaning namespaces (done in ExportPhotoData) + + try { + + bool foundXMP, foundExif; + TIFF_Manager::TagInfo tagInfo; + std::string xmpValue; + XMP_OptionBits flags; + XMP_Int32 binValue = 0; + + bool haveOldExif = true; // Default to old Exif if no version tag. + + foundExif = exif->GetTag ( kTIFF_ExifIFD, kTIFF_ExifVersion, &tagInfo ); + if ( foundExif && (tagInfo.type == kTIFF_UndefinedType) && (tagInfo.count == 4) ) { + haveOldExif = (strncmp ( (char*)tagInfo.dataPtr, "0230", 4 ) < 0); + } + + if ( haveOldExif ) { // Exif version is before 2.3, use just the old tag and property. + + foundXMP = xmp->GetProperty ( kXMP_NS_EXIF, "ISOSpeedRatings", 0, &flags ); + if ( foundXMP && XMP_PropIsArray(flags) && (xmp->CountArrayItems ( kXMP_NS_EXIF, "ISOSpeedRatings" ) > 0) ) { + foundXMP = xmp->GetProperty_Int ( kXMP_NS_EXIF, "ISOSpeedRatings[1]", &binValue, 0 ); + } + + if ( ! foundXMP ) { + foundXMP = xmp->GetProperty_Int ( kXMP_NS_ExifEX, "PhotographicSensitivity", &binValue, 0 ); + } + + if ( foundXMP && (0 <= binValue) && (binValue <= 65535) ) { + xmp->DeleteProperty ( kXMP_NS_EXIF, "ISOSpeedRatings" ); // So namespace cleanup won't keep it. + foundExif = exif->GetTag ( kTIFF_ExifIFD, kTIFF_PhotographicSensitivity, &tagInfo ); + if ( ! foundExif ) { + exif->SetTag_Short ( kTIFF_ExifIFD, kTIFF_PhotographicSensitivity, (XMP_Uns16)binValue ); + } + } + + } else { // Exif version is 2.3 or newer, use the Exif 2.3 tags and properties. + + // Deal with the special cases for exifEX:PhotographicSensitivity and exif:ISOSpeedRatings. + + if ( ! xmp->DoesPropertyExist ( kXMP_NS_ExifEX, "PhotographicSensitivity" ) ) { + foundXMP = xmp->GetProperty ( kXMP_NS_EXIF, "ISOSpeedRatings", 0, &flags ); + if ( foundXMP && XMP_PropIsArray(flags) && + (xmp->CountArrayItems ( kXMP_NS_EXIF, "ISOSpeedRatings" ) > 0) ) { + xmp->GetArrayItem ( kXMP_NS_EXIF, "ISOSpeedRatings", 1, &xmpValue, 0 ); + xmp->SetProperty ( kXMP_NS_ExifEX, "PhotographicSensitivity", xmpValue.c_str() ); + } + } + + xmp->DeleteProperty ( kXMP_NS_EXIF, "ISOSpeedRatings" ); // Don't want it kept after namespace cleanup. + + foundXMP = xmp->GetProperty_Int ( kXMP_NS_ExifEX, "PhotographicSensitivity", &binValue, 0 ); + + if ( foundXMP && (0 <= binValue) && (binValue <= 65535) ) { // The simpler low ISO case. + + foundExif = exif->GetTag ( kTIFF_ExifIFD, kTIFF_PhotographicSensitivity, &tagInfo ); + if ( ! foundExif ) { + exif->SetTag_Short ( kTIFF_ExifIFD, kTIFF_PhotographicSensitivity, (XMP_Uns16)binValue ); + } + + } else if ( foundXMP ) { // The more commplex high ISO case. + + bool havePhotographicSensitivityTag = exif->GetTag ( kTIFF_ExifIFD, kTIFF_PhotographicSensitivity, 0 ); + bool haveSensitivityTypeTag = exif->GetTag ( kTIFF_ExifIFD, kTIFF_SensitivityType, 0 ); + bool haveISOSpeedTag = exif->GetTag ( kTIFF_ExifIFD, kTIFF_ISOSpeed, 0 ); + + bool haveSensitivityTypeXMP = xmp->DoesPropertyExist ( kXMP_NS_ExifEX, "SensitivityType" ); + bool haveISOSpeedXMP = xmp->DoesPropertyExist ( kXMP_NS_ExifEX, "ISOSpeed" ); + + if ( (! havePhotographicSensitivityTag) && (! haveSensitivityTypeTag) && (! haveISOSpeedTag) ) { + + exif->SetTag_Short ( kTIFF_ExifIFD, kTIFF_PhotographicSensitivity, 65535 ); + + if ( (! haveSensitivityTypeXMP) && (! haveISOSpeedXMP) ) { + xmp->SetProperty ( kXMP_NS_ExifEX, "SensitivityType", "3" ); + xmp->SetProperty_Int ( kXMP_NS_ExifEX, "ISOSpeed", binValue ); + } + + } + + } + + // Export SensitivityType and the related long tags. Must be done after the special + // cases because they might set exifEX:SensitivityType and exifEX:ISOSpeed. + + foundExif = exif->GetTag ( kTIFF_ExifIFD, kTIFF_SensitivityType, &tagInfo ); + if ( ! foundExif ) { + foundXMP = xmp->GetProperty_Int ( kXMP_NS_ExifEX, "SensitivityType", &binValue, 0 ); + if ( foundXMP && (0 <= binValue) && (binValue <= 65535) ) { + exif->SetTag_Short ( kTIFF_ExifIFD, kTIFF_SensitivityType, (XMP_Uns16)binValue ); + } + } + + foundExif = exif->GetTag ( kTIFF_ExifIFD, kTIFF_StandardOutputSensitivity, &tagInfo ); + if ( ! foundExif ) { + foundXMP = xmp->GetProperty_Int ( kXMP_NS_ExifEX, "StandardOutputSensitivity", &binValue, 0 ); + if ( foundXMP && (binValue >= 0) ) { + exif->SetTag_Long ( kTIFF_ExifIFD, kTIFF_StandardOutputSensitivity, (XMP_Uns32)binValue ); + } + } + + foundExif = exif->GetTag ( kTIFF_ExifIFD, kTIFF_RecommendedExposureIndex, &tagInfo ); + if ( ! foundExif ) { + foundXMP = xmp->GetProperty_Int ( kXMP_NS_ExifEX, "RecommendedExposureIndex", &binValue, 0 ); + if ( foundXMP && (binValue >= 0) ) { + exif->SetTag_Long ( kTIFF_ExifIFD, kTIFF_RecommendedExposureIndex, (XMP_Uns32)binValue ); + } + } + + foundExif = exif->GetTag ( kTIFF_ExifIFD, kTIFF_ISOSpeed, &tagInfo ); + if ( ! foundExif ) { + foundXMP = xmp->GetProperty_Int ( kXMP_NS_ExifEX, "ISOSpeed", &binValue, 0 ); + if ( foundXMP && (binValue >= 0) ) { + exif->SetTag_Long ( kTIFF_ExifIFD, kTIFF_ISOSpeed, (XMP_Uns32)binValue ); + } + } + + foundExif = exif->GetTag ( kTIFF_ExifIFD, kTIFF_ISOSpeedLatitudeyyy, &tagInfo ); + if ( ! foundExif ) { + foundXMP = xmp->GetProperty_Int ( kXMP_NS_ExifEX, "ISOSpeedLatitudeyyy", &binValue, 0 ); + if ( foundXMP && (binValue >= 0) ) { + exif->SetTag_Long ( kTIFF_ExifIFD, kTIFF_ISOSpeedLatitudeyyy, (XMP_Uns32)binValue ); + } + } + + foundExif = exif->GetTag ( kTIFF_ExifIFD, kTIFF_ISOSpeedLatitudezzz, &tagInfo ); + if ( ! foundExif ) { + foundXMP = xmp->GetProperty_Int ( kXMP_NS_ExifEX, "ISOSpeedLatitudezzz", &binValue, 0 ); + if ( foundXMP && (binValue >= 0) ) { + exif->SetTag_Long ( kTIFF_ExifIFD, kTIFF_ISOSpeedLatitudezzz, (XMP_Uns32)binValue ); + } + } + + } + + } catch ( ... ) { + + // Do nothing, don't let this failure stop other exports. + + } + +} // ExportTIFF_PhotographicSensitivity + +// ================================================================================================= +// ================================================================================================= + +// ================================================================================================= +// PhotoDataUtils::ExportExif +// ========================== + +void +PhotoDataUtils::ExportExif ( SXMPMeta * xmp, TIFF_Manager * exif ) +{ + bool haveXMP; + std::string xmpValue; + + XMP_Int32 int32; + XMP_Uns8 uns8; + + // --------------------------------------------------------- + // Read the old Adobe names for new Exif 2.3 tags if wanted. + + #if SupportOldExifProperties + + XMP_OptionBits flags; + + haveXMP = xmp->DoesPropertyExist ( kXMP_NS_ExifEX, "PhotographicSensitivity" ); + if ( ! haveXMP ) { + haveXMP = xmp->GetProperty ( kXMP_NS_EXIF, "ISOSpeedRatings", 0, &flags ); + if ( haveXMP && XMP_PropIsArray(flags) ) { + haveXMP = xmp->GetArrayItem ( kXMP_NS_EXIF, "ISOSpeedRatings", 1, &xmpValue, 0 ); + if ( haveXMP ) xmp->SetProperty ( kXMP_NS_ExifEX, "PhotographicSensitivity", xmpValue.c_str() ); + } + } + + haveXMP = xmp->DoesPropertyExist ( kXMP_NS_ExifEX, "CameraOwnerName" ); + if ( ! haveXMP ) { + haveXMP = xmp->GetProperty ( kXMP_NS_EXIF_Aux, "OwnerName", &xmpValue, 0 ); + if ( haveXMP ) xmp->SetProperty ( kXMP_NS_ExifEX, "CameraOwnerName", xmpValue.c_str() ); + } + + haveXMP = xmp->DoesPropertyExist ( kXMP_NS_ExifEX, "BodySerialNumber" ); + if ( ! haveXMP ) { + haveXMP = xmp->GetProperty ( kXMP_NS_EXIF_Aux, "SerialNumber", &xmpValue, 0 ); + if ( haveXMP ) xmp->SetProperty ( kXMP_NS_ExifEX, "BodySerialNumber", xmpValue.c_str() ); + } + + haveXMP = xmp->DoesPropertyExist ( kXMP_NS_ExifEX, "LensModel" ); + if ( ! haveXMP ) { + haveXMP = xmp->GetProperty ( kXMP_NS_EXIF_Aux, "Lens", &xmpValue, 0 ); + if ( haveXMP ) xmp->SetProperty ( kXMP_NS_ExifEX, "LensModel", xmpValue.c_str() ); + } + + haveXMP = xmp->DoesPropertyExist ( kXMP_NS_ExifEX, "LensSpecification" ); + if ( ! haveXMP ) { + haveXMP = xmp->GetProperty ( kXMP_NS_EXIF_Aux, "LensInfo", &xmpValue, 0 ); + if ( haveXMP ) { + size_t start, end; + std::string nextItem; + for ( start = 0; start < xmpValue.size(); start = end + 1 ) { + end = xmpValue.find ( ' ', start ); + if ( end == start ) continue; + if ( end == std::string::npos ) end = xmpValue.size(); + nextItem = xmpValue.substr ( start, (end-start) ); + xmp->AppendArrayItem ( kXMP_NS_ExifEX, "LensSpecification", kXMP_PropArrayIsOrdered, nextItem.c_str() ); + } + } + } + + #endif + + // Do all of the table driven standard exports. + + ExportTIFF_StandardMappings ( kTIFF_PrimaryIFD, exif, *xmp ); + ExportTIFF_StandardMappings ( kTIFF_ExifIFD, exif, *xmp ); + ExportTIFF_StandardMappings ( kTIFF_GPSInfoIFD, exif, *xmp ); + + // -------------------------------------------------------------------------------------------- + // Fixup erroneous cases that appear to have a negative value for GPSAltitude in the Exif. This + // treats any value with the high bit set as a negative, which is more likely in practice than + // an actual value over 2 billion. The XMP was exported by the tables and is left alone since it + // won't be kept in the file. + + TIFF_Manager::Rational altValue; + bool haveExif = exif->GetTag_Rational ( kTIFF_GPSInfoIFD, kTIFF_GPSAltitude, &altValue ); + if ( haveExif ) { + + bool fixExif = false; + + if ( altValue.denom >> 31 ) { // Shift the sign to the numerator. + altValue.denom = -altValue.denom; + altValue.num = -altValue.num; + fixExif = true; + } + + if ( altValue.num >> 31 ) { // Fix the numerator and set GPSAltitudeRef. + exif->SetTag_Byte ( kTIFF_GPSInfoIFD, kTIFF_GPSAltitudeRef, 1 ); + altValue.num = -altValue.num; + fixExif = true; + } + + if ( fixExif ) exif->SetTag_Rational ( kTIFF_GPSInfoIFD, kTIFF_GPSAltitude, altValue.num, altValue.denom ); + + } + + // Export dc:description to TIFF ImageDescription, and exif:UserComment to EXIF UserComment. + + // *** This is not following the MWG guidelines. The policy here tries to be more backward compatible. + + ExportTIFF_LocTextASCII ( *xmp, kXMP_NS_DC, "description", + exif, kTIFF_PrimaryIFD, kTIFF_ImageDescription ); + + ExportTIFF_EncodedString ( *xmp, kXMP_NS_EXIF, "UserComment", + exif, kTIFF_ExifIFD, kTIFF_UserComment, true /* isLangAlt */ ); + + // Export all of the date/time tags. + // ! Special case: Don't create Exif DateTimeDigitized. This can avoid PSD full rewrite due to + // ! new mapping from xmp:CreateDate. + + if ( exif->GetTag ( kTIFF_ExifIFD, kTIFF_DateTimeDigitized, 0 ) ) { + ExportTIFF_Date ( *xmp, kXMP_NS_XMP, "CreateDate", exif, kTIFF_DateTimeDigitized ); + } + + ExportTIFF_Date ( *xmp, kXMP_NS_EXIF, "DateTimeOriginal", exif, kTIFF_DateTimeOriginal ); + ExportTIFF_Date ( *xmp, kXMP_NS_XMP, "ModifyDate", exif, kTIFF_DateTime ); + + // Export the remaining TIFF, Exif, and GPS IFD tags. + + ExportTIFF_ArrayASCII ( *xmp, kXMP_NS_DC, "creator", exif, kTIFF_PrimaryIFD, kTIFF_Artist ); + + ExportTIFF_LocTextASCII ( *xmp, kXMP_NS_DC, "rights", exif, kTIFF_PrimaryIFD, kTIFF_Copyright ); + + haveXMP = xmp->GetProperty ( kXMP_NS_EXIF, "ExifVersion", &xmpValue, 0 ); + if ( haveXMP && (xmpValue.size() == 4) && (! exif->GetTag ( kTIFF_ExifIFD, kTIFF_ExifVersion, 0 )) ) { + // 36864 ExifVersion is 4 "undefined" ASCII characters. + exif->SetTag ( kTIFF_ExifIFD, kTIFF_ExifVersion, kTIFF_UndefinedType, 4, xmpValue.data() ); + } + + // There are moderately complex export special cases for PhotographicSensitivity. + ExportTIFF_PhotographicSensitivity ( xmp, exif ); + + haveXMP = xmp->DoesPropertyExist ( kXMP_NS_EXIF, "ComponentsConfiguration" ); + if ( haveXMP && (xmp->CountArrayItems ( kXMP_NS_EXIF, "ComponentsConfiguration" ) == 4) && + (! exif->GetTag ( kTIFF_ExifIFD, kTIFF_ComponentsConfiguration, 0 )) ) { + // 37121 ComponentsConfiguration is an array of 4 "undefined" UInt8 bytes. + XMP_Uns8 compConfig[4]; + xmp->GetProperty_Int ( kXMP_NS_EXIF, "ComponentsConfiguration[1]", &int32, 0 ); + compConfig[0] = (XMP_Uns8)int32; + xmp->GetProperty_Int ( kXMP_NS_EXIF, "ComponentsConfiguration[2]", &int32, 0 ); + compConfig[1] = (XMP_Uns8)int32; + xmp->GetProperty_Int ( kXMP_NS_EXIF, "ComponentsConfiguration[3]", &int32, 0 ); + compConfig[2] = (XMP_Uns8)int32; + xmp->GetProperty_Int ( kXMP_NS_EXIF, "ComponentsConfiguration[4]", &int32, 0 ); + compConfig[3] = (XMP_Uns8)int32; + exif->SetTag ( kTIFF_ExifIFD, kTIFF_ComponentsConfiguration, kTIFF_UndefinedType, 4, &compConfig[0] ); + } + + haveXMP = xmp->DoesPropertyExist ( kXMP_NS_EXIF, "Flash" ); + if ( haveXMP && (! exif->GetTag ( kTIFF_ExifIFD, kTIFF_Flash, 0 )) ) { + // 37385 Flash is a UInt16 collection of bit fields and is mapped to a struct in XMP. + XMP_Uns16 binFlash = 0; + bool field; + haveXMP = xmp->GetProperty_Bool ( kXMP_NS_EXIF, "Flash/exif:Fired", &field, 0 ); + if ( haveXMP & field ) binFlash |= 0x0001; + haveXMP = xmp->GetProperty_Int ( kXMP_NS_EXIF, "Flash/exif:Return", &int32, 0 ); + if ( haveXMP ) binFlash |= (int32 & 3) << 1; + haveXMP = xmp->GetProperty_Int ( kXMP_NS_EXIF, "Flash/exif:Mode", &int32, 0 ); + if ( haveXMP ) binFlash |= (int32 & 3) << 3; + haveXMP = xmp->GetProperty_Bool ( kXMP_NS_EXIF, "Flash/exif:Function", &field, 0 ); + if ( haveXMP & field ) binFlash |= 0x0020; + haveXMP = xmp->GetProperty_Bool ( kXMP_NS_EXIF, "Flash/exif:RedEyeMode", &field, 0 ); + if ( haveXMP & field ) binFlash |= 0x0040; + exif->SetTag_Short ( kTIFF_ExifIFD, kTIFF_Flash, binFlash ); + } + + haveXMP = xmp->GetProperty_Int ( kXMP_NS_EXIF, "FileSource", &int32, 0 ); + if ( haveXMP && (! exif->GetTag ( kTIFF_ExifIFD, kTIFF_FileSource, 0 )) ) { + // 41728 FileSource is an "undefined" UInt8. + uns8 = (XMP_Uns8)int32; + exif->SetTag ( kTIFF_ExifIFD, kTIFF_FileSource, kTIFF_UndefinedType, 1, &uns8 ); + } + + haveXMP = xmp->GetProperty_Int ( kXMP_NS_EXIF, "SceneType", &int32, 0 ); + if ( haveXMP && (! exif->GetTag ( kTIFF_ExifIFD, kTIFF_SceneType, 0 )) ) { + // 41729 SceneType is an "undefined" UInt8. + uns8 = (XMP_Uns8)int32; + exif->SetTag ( kTIFF_ExifIFD, kTIFF_SceneType, kTIFF_UndefinedType, 1, &uns8 ); + } + + // *** Deferred inject-only tags: SpatialFrequencyResponse, DeviceSettingDescription, CFAPattern + + haveXMP = xmp->GetProperty ( kXMP_NS_EXIF, "GPSVersionID", &xmpValue, 0 ); // This is inject-only. + if ( haveXMP && (! exif->GetTag ( kTIFF_GPSInfoIFD, kTIFF_GPSVersionID, 0 )) ) { + XMP_Uns8 gpsID[4]; // 0 GPSVersionID is 4 UInt8 bytes and mapped in XMP as "n.n.n.n". + unsigned int fields[4]; // ! Need ints for output from sscanf. + int count = sscanf ( xmpValue.c_str(), "%u.%u.%u.%u", &fields[0], &fields[1], &fields[2], &fields[3] ); + if ( (count == 4) && (fields[0] <= 255) && (fields[1] <= 255) && (fields[2] <= 255) && (fields[3] <= 255) ) { + gpsID[0] = fields[0]; gpsID[1] = fields[1]; gpsID[2] = fields[2]; gpsID[3] = fields[3]; + exif->SetTag ( kTIFF_GPSInfoIFD, kTIFF_GPSVersionID, kTIFF_ByteType, 4, &gpsID[0] ); + } + } + + ExportTIFF_GPSCoordinate ( *xmp, kXMP_NS_EXIF, "GPSLatitude", exif, kTIFF_GPSInfoIFD, kTIFF_GPSLatitude ); + + ExportTIFF_GPSCoordinate ( *xmp, kXMP_NS_EXIF, "GPSLongitude", exif, kTIFF_GPSInfoIFD, kTIFF_GPSLongitude ); + + ExportTIFF_GPSTimeStamp ( *xmp, kXMP_NS_EXIF, "GPSTimeStamp", exif ); + + // The following GPS tags are inject-only. + + haveXMP = xmp->DoesPropertyExist ( kXMP_NS_EXIF, "GPSDestLatitude" ); + if ( haveXMP && (! exif->GetTag ( kTIFF_GPSInfoIFD, kTIFF_GPSDestLatitude, 0 )) ) { + ExportTIFF_GPSCoordinate ( *xmp, kXMP_NS_EXIF, "GPSDestLatitude", exif, kTIFF_GPSInfoIFD, kTIFF_GPSDestLatitude ); + } + + haveXMP = xmp->DoesPropertyExist ( kXMP_NS_EXIF, "GPSDestLongitude" ); + if ( haveXMP && (! exif->GetTag ( kTIFF_GPSInfoIFD, kTIFF_GPSDestLongitude, 0 )) ) { + ExportTIFF_GPSCoordinate ( *xmp, kXMP_NS_EXIF, "GPSDestLongitude", exif, kTIFF_GPSInfoIFD, kTIFF_GPSDestLongitude ); + } + + haveXMP = xmp->GetProperty ( kXMP_NS_EXIF, "GPSProcessingMethod", &xmpValue, 0 ); + if ( haveXMP && (! xmpValue.empty()) && (! exif->GetTag ( kTIFF_GPSInfoIFD, kTIFF_GPSProcessingMethod, 0 )) ) { + // 27 GPSProcessingMethod is a string with explicit encoding. + ExportTIFF_EncodedString ( *xmp, kXMP_NS_EXIF, "GPSProcessingMethod", exif, kTIFF_GPSInfoIFD, kTIFF_GPSProcessingMethod ); + } + + haveXMP = xmp->GetProperty ( kXMP_NS_EXIF, "GPSAreaInformation", &xmpValue, 0 ); + if ( haveXMP && (! xmpValue.empty()) && (! exif->GetTag ( kTIFF_GPSInfoIFD, kTIFF_GPSAreaInformation, 0 )) ) { + // 28 GPSAreaInformation is a string with explicit encoding. + ExportTIFF_EncodedString ( *xmp, kXMP_NS_EXIF, "GPSAreaInformation", exif, kTIFF_GPSInfoIFD, kTIFF_GPSAreaInformation ); + } + +} // PhotoDataUtils::ExportExif diff --git a/core/libs/dngwriter/extra/xmp_sdk/XMPFiles/source/FormatSupport/Reconcile_Impl.cpp b/core/libs/dngwriter/extra/xmp_sdk/XMPFiles/source/FormatSupport/Reconcile_Impl.cpp new file mode 100644 index 0000000000..ca0e1a08a4 --- /dev/null +++ b/core/libs/dngwriter/extra/xmp_sdk/XMPFiles/source/FormatSupport/Reconcile_Impl.cpp @@ -0,0 +1,471 @@ +// ================================================================================================= +// ADOBE SYSTEMS INCORPORATED +// Copyright 2006 Adobe Systems Incorporated +// All Rights Reserved +// +// NOTICE: Adobe permits you to use, modify, and distribute this file in accordance with the terms +// of the Adobe license agreement accompanying it. +// ================================================================================================= + +#include "public/include/XMP_Environment.h" // ! This must be the first include. +#include "public/include/XMP_Const.h" + +#include "XMPFiles/source/FormatSupport/Reconcile_Impl.hpp" +#include "source/UnicodeConversions.hpp" +#include "source/XIO.hpp" + +#if XMP_WinBuild +#elif XMP_MacBuild + #include +#elif XMP_iOSBuild + #include +#endif + +// ================================================================================================= +/// \file Reconcile_Impl.cpp +/// \brief Implementation utilities for the photo metadata reconciliation support. +/// +// ================================================================================================= + +// ================================================================================================= +// ReconcileUtils::IsASCII +// ======================= +// +// See if a string is 7 bit ASCII. + +bool ReconcileUtils::IsASCII ( const void * textPtr, size_t textLen ) +{ + + for ( const XMP_Uns8 * textPos = (XMP_Uns8*)textPtr; textLen > 0; --textLen, ++textPos ) { + if ( *textPos >= 0x80 ) return false; + } + + return true; + +} // ReconcileUtils::IsASCII + +// ================================================================================================= +// ReconcileUtils::IsUTF8 +// ====================== +// +// See if a string contains valid UTF-8. Allow nul bytes, they can appear inside of multi-part Exif +// strings. We don't use CodePoint_from_UTF8_Multi in UnicodeConversions because it throws an +// exception for non-Unicode and we don't need to actually compute the code points. + +bool ReconcileUtils::IsUTF8 ( const void * textPtr, size_t textLen ) +{ + const XMP_Uns8 * textPos = (XMP_Uns8*)textPtr; + const XMP_Uns8 * textEnd = textPos + textLen; + + while ( textPos < textEnd ) { + + if ( *textPos < 0x80 ) { + + ++textPos; // ASCII is UTF-8, tolerate nuls. + + } else { + + // ------------------------------------------------------------------------------------- + // We've got a multibyte UTF-8 character. The first byte has the number of bytes as the + // number of high order 1 bits. The remaining bytes must have 1 and 0 as the top 2 bits. + + #if 0 // *** This might be a more effcient way to count the bytes. + static XMP_Uns8 kByteCounts[16] = { 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 2, 2, 3, 4 }; + size_t bytesNeeded = kByteCounts [ *textPos >> 4 ]; + if ( (bytesNeeded < 2) || ((bytesNeeded == 4) && ((*textPos & 0x08) != 0)) ) return false; + if ( (textPos + bytesNeeded) > textEnd ) return false; + #endif + + size_t bytesNeeded = 0; // Count the high order 1 bits in the first byte. + for ( XMP_Uns8 temp = *textPos; temp > 0x7F; temp = temp << 1 ) ++bytesNeeded; + // *** Consider CPU-specific assembly inline, e.g. cntlzw on PowerPC. + + if ( (bytesNeeded < 2) || (bytesNeeded > 4) || ((textPos+bytesNeeded) > textEnd) ) return false; + + for ( --bytesNeeded, ++textPos; bytesNeeded > 0; --bytesNeeded, ++textPos ) { + if ( (*textPos >> 6) != 2 ) return false; + } + + } + + } + + return true; // ! Returns true for empty strings. + +} // ReconcileUtils::IsUTF8 + +// ================================================================================================= +// UTF8ToHostEncoding +// ================== + +#if XMP_WinBuild + + void ReconcileUtils::UTF8ToWinEncoding ( UINT codePage, const XMP_Uns8 * utf8Ptr, size_t utf8Len, std::string * host ) + { + + std::string utf16; // WideCharToMultiByte wants native UTF-16. + ToUTF16Native ( (UTF8Unit*)utf8Ptr, utf8Len, &utf16 ); + + LPCWSTR utf16Ptr = (LPCWSTR) utf16.c_str(); + size_t utf16Len = utf16.size() / 2; + + int hostLen = WideCharToMultiByte ( codePage, 0, utf16Ptr, (int)utf16Len, 0, 0, 0, 0 ); + host->assign ( hostLen, ' ' ); // Allocate space for the results. + + (void) WideCharToMultiByte ( codePage, 0, utf16Ptr, (int)utf16Len, (LPSTR)host->data(), hostLen, 0, 0 ); + XMP_Assert ( hostLen == host->size() ); + + } // UTF8ToWinEncoding + +#elif XMP_MacBuild + + void ReconcileUtils::UTF8ToMacEncoding ( XMP_Uns16 macScript, XMP_Uns16 macLang, const XMP_Uns8 * utf8Ptr, size_t utf8Len, std::string * host ) + { + OSStatus err; + + TextEncoding destEncoding; + if ( macLang == langUnspecified ) macLang = kTextLanguageDontCare; + err = UpgradeScriptInfoToTextEncoding ( macScript, macLang, kTextRegionDontCare, 0, &destEncoding ); + if ( err != noErr ) XMP_Throw ( "UpgradeScriptInfoToTextEncoding failed", kXMPErr_ExternalFailure ); + + UnicodeMapping mappingInfo; + mappingInfo.mappingVersion = kUnicodeUseLatestMapping; + mappingInfo.otherEncoding = GetTextEncodingBase ( destEncoding ); + mappingInfo.unicodeEncoding = CreateTextEncoding ( kTextEncodingUnicodeDefault, + kUnicodeNoSubset, kUnicodeUTF8Format ); + + UnicodeToTextInfo converterInfo; + err = CreateUnicodeToTextInfo ( &mappingInfo, &converterInfo ); + if ( err != noErr ) XMP_Throw ( "CreateUnicodeToTextInfo failed", kXMPErr_ExternalFailure ); + + try { // ! Need to call DisposeUnicodeToTextInfo before exiting. + + OptionBits convFlags = kUnicodeUseFallbacksMask | + kUnicodeLooseMappingsMask | kUnicodeDefaultDirectionMask; + ByteCount bytesRead, bytesWritten; + + enum { kBufferLen = 1000 }; // Ought to be enough in practice, without using too much stack. + char buffer [kBufferLen]; + + host->reserve ( utf8Len ); // As good a guess as any. + + while ( utf8Len > 0 ) { + // Ignore all errors from ConvertFromUnicodeToText. It returns info like "output + // buffer full" or "use substitution" as errors. + err = ConvertFromUnicodeToText ( converterInfo, utf8Len, (UniChar*)utf8Ptr, convFlags, + 0, 0, 0, 0, kBufferLen, &bytesRead, &bytesWritten, buffer ); + if ( bytesRead == 0 ) break; // Make sure forward progress happens. + host->append ( &buffer[0], bytesWritten ); + utf8Ptr += bytesRead; + utf8Len -= bytesRead; + } + + DisposeUnicodeToTextInfo ( &converterInfo ); + + } catch ( ... ) { + + DisposeUnicodeToTextInfo ( &converterInfo ); + throw; + + } + + } // UTF8ToMacEncoding + +#elif XMP_UNIXBuild + + // ! Does not exist, must not be called, for Generic UNIX builds. + +#endif + +// ================================================================================================= +// ReconcileUtils::UTF8ToLocal +// =========================== + +void ReconcileUtils::UTF8ToLocal ( const void * _utf8Ptr, size_t utf8Len, std::string * local ) +{ + const XMP_Uns8* utf8Ptr = (XMP_Uns8*)_utf8Ptr; + + local->erase(); + + if ( ReconcileUtils::IsASCII ( utf8Ptr, utf8Len ) ) { + local->assign ( (const char *)utf8Ptr, utf8Len ); + return; + } + + #if XMP_WinBuild + + UTF8ToWinEncoding ( CP_ACP, utf8Ptr, utf8Len, local ); + + #elif XMP_MacBuild + + UTF8ToMacEncoding ( smSystemScript, kTextLanguageDontCare, utf8Ptr, utf8Len, local ); + + #elif XMP_UNIXBuild + + XMP_Throw ( "Generic UNIX does not have conversions between local and Unicode", kXMPErr_Unavailable ); + + #elif XMP_iOSBuild + + IOSConvertEncoding(kCFStringEncodingUTF8, CFStringGetSystemEncoding(), utf8Ptr, utf8Len, local); + + + + + #endif + +} // ReconcileUtils::UTF8ToLocal + +// ================================================================================================= +// ReconcileUtils::UTF8ToLatin1 +// ============================ + +void ReconcileUtils::UTF8ToLatin1 ( const void * _utf8Ptr, size_t utf8Len, std::string * latin1 ) +{ + const XMP_Uns8* utf8Ptr = (XMP_Uns8*)_utf8Ptr; + const XMP_Uns8* utf8End = utf8Ptr + utf8Len; + + latin1->erase(); + latin1->reserve ( utf8Len ); // As good a guess as any, at least enough, exact for ASCII. + + bool inBadRun = false; + + while ( utf8Ptr < utf8End ) { + + if ( *utf8Ptr <= 0x7F ) { + + (*latin1) += (char)*utf8Ptr; // Have an ASCII character. + inBadRun = false; + ++utf8Ptr; + + } else if ( utf8Ptr == (utf8End - 1) ) { + + inBadRun = false; + ++utf8Ptr; // Ignore a bad end to the UTF-8. + + } else { + + XMP_Assert ( (utf8End - utf8Ptr) >= 2 ); + XMP_Uns16 ch16 = GetUns16BE ( utf8Ptr ); // A Latin-1 80..FF is 2 UTF-8 bytes. + + if ( (0xC280 <= ch16) && (ch16 <= 0xC2BF) ) { + + (*latin1) += (char)(ch16 & 0xFF); // UTF-8 C280..C2BF are Latin-1 80..BF. + inBadRun = false; + utf8Ptr += 2; + + } else if ( (0xC380 <= ch16) && (ch16 <= 0xC3BF) ) { + + (*latin1) += (char)((ch16 & 0xFF) + 0x40); // UTF-8 C380..C3BF are Latin-1 C0..FF. + inBadRun = false; + utf8Ptr += 2; + + } else { + + if ( ! inBadRun ) { + inBadRun = true; + (*latin1) += "(?)"; // Mark the run of out of scope UTF-8. + } + + ++utf8Ptr; // Skip the presumably well-formed UTF-8 character. + while ( (utf8Ptr < utf8End) && ((*utf8Ptr & 0xC0) == 0x80) ) ++utf8Ptr; + + } + + } + + } + + XMP_Assert ( utf8Ptr == utf8End ); + +} // ReconcileUtils::UTF8ToLatin1 + +// ================================================================================================= +// HostEncodingToUTF8 +// ================== + +#if XMP_WinBuild + + void ReconcileUtils::WinEncodingToUTF8 ( UINT codePage, const XMP_Uns8 * hostPtr, size_t hostLen, std::string * utf8 ) + { + + int utf16Len = MultiByteToWideChar ( codePage, 0, (LPCSTR)hostPtr, (int)hostLen, 0, 0 ); + std::vector utf16 ( utf16Len, 0 ); // MultiByteToWideChar returns native UTF-16. + + (void) MultiByteToWideChar ( codePage, 0, (LPCSTR)hostPtr, (int)hostLen, (LPWSTR)&utf16[0], utf16Len ); + FromUTF16Native ( &utf16[0], (int)utf16Len, utf8 ); + + } // WinEncodingToUTF8 + +#elif XMP_MacBuild + + void ReconcileUtils::MacEncodingToUTF8 ( XMP_Uns16 macScript, XMP_Uns16 macLang, const XMP_Uns8 * hostPtr, size_t hostLen, std::string * utf8 ) + { + OSStatus err; + + TextEncoding srcEncoding; + if ( macLang == langUnspecified ) macLang = kTextLanguageDontCare; + err = UpgradeScriptInfoToTextEncoding ( macScript, macLang, kTextRegionDontCare, 0, &srcEncoding ); + if ( err != noErr ) XMP_Throw ( "UpgradeScriptInfoToTextEncoding failed", kXMPErr_ExternalFailure ); + + UnicodeMapping mappingInfo; + mappingInfo.mappingVersion = kUnicodeUseLatestMapping; + mappingInfo.otherEncoding = GetTextEncodingBase ( srcEncoding ); + mappingInfo.unicodeEncoding = CreateTextEncoding ( kTextEncodingUnicodeDefault, + kUnicodeNoSubset, kUnicodeUTF8Format ); + + TextToUnicodeInfo converterInfo; + err = CreateTextToUnicodeInfo ( &mappingInfo, &converterInfo ); + if ( err != noErr ) XMP_Throw ( "CreateTextToUnicodeInfo failed", kXMPErr_ExternalFailure ); + + try { // ! Need to call DisposeTextToUnicodeInfo before exiting. + + ByteCount bytesRead, bytesWritten; + + enum { kBufferLen = 1000 }; // Ought to be enough in practice, without using too much stack. + char buffer [kBufferLen]; + + utf8->reserve ( hostLen ); // As good a guess as any. + + while ( hostLen > 0 ) { + // Ignore all errors from ConvertFromTextToUnicode. It returns info like "output + // buffer full" or "use substitution" as errors. + err = ConvertFromTextToUnicode ( converterInfo, hostLen, hostPtr, kNilOptions, + 0, 0, 0, 0, kBufferLen, &bytesRead, &bytesWritten, (UniChar*)buffer ); + if ( bytesRead == 0 ) break; // Make sure forward progress happens. + utf8->append ( &buffer[0], bytesWritten ); + hostPtr += bytesRead; + hostLen -= bytesRead; + } + + DisposeTextToUnicodeInfo ( &converterInfo ); + + } catch ( ... ) { + + DisposeTextToUnicodeInfo ( &converterInfo ); + throw; + + } + + } // MacEncodingToUTF8 + +#elif XMP_UNIXBuild + + // ! Does not exist, must not be called, for Generic UNIX builds. + +#endif + +// ================================================================================================= +// ReconcileUtils::LocalToUTF8 +// =========================== + +void ReconcileUtils::LocalToUTF8 ( const void * _localPtr, size_t localLen, std::string * utf8 ) +{ + const XMP_Uns8* localPtr = (XMP_Uns8*)_localPtr; + + utf8->erase(); + + if ( ReconcileUtils::IsASCII ( localPtr, localLen ) ) { + utf8->assign ( (const char *)localPtr, localLen ); + return; + } + + #if XMP_WinBuild + + WinEncodingToUTF8 ( CP_ACP, localPtr, localLen, utf8 ); + + #elif XMP_MacBuild + + MacEncodingToUTF8 ( smSystemScript, kTextLanguageDontCare, localPtr, localLen, utf8 ); + + #elif XMP_UNIXBuild + + XMP_Throw ( "Generic UNIX does not have conversions between local and Unicode", kXMPErr_Unavailable ); + + #elif XMP_iOSBuild + + IOSConvertEncoding(CFStringGetSystemEncoding(), kCFStringEncodingUTF8, localPtr, localLen, utf8); + + + #endif + +} // ReconcileUtils::LocalToUTF8 + +// ================================================================================================= +// ReconcileUtils::Latin1ToUTF8 +// ============================ + +void ReconcileUtils::Latin1ToUTF8 ( const void * _latin1Ptr, size_t latin1Len, std::string * utf8 ) +{ + const XMP_Uns8* latin1Ptr = (XMP_Uns8*)_latin1Ptr; + const XMP_Uns8* latin1End = latin1Ptr + latin1Len; + + utf8->erase(); + utf8->reserve ( latin1Len ); // As good a guess as any, exact for ASCII. + + for ( ; latin1Ptr < latin1End; ++latin1Ptr ) { + + XMP_Uns8 ch8 = *latin1Ptr; + + if ( ch8 <= 0x7F ) { + (*utf8) += (char)ch8; // Have an ASCII character. + } else if ( ch8 <= 0xBF ) { + (*utf8) += 0xC2; // Latin-1 80..BF are UTF-8 C280..C2BF. + (*utf8) += (char)ch8; + } else { + (*utf8) += 0xC3; // Latin-1 C0..FF are UTF-8 C380..C3BF. + (*utf8) += (char)(ch8 - 0x40); + } + + } + +} // ReconcileUtils::Latin1ToUTF8 + + +// ================================================================================================= +// ReconcileUtils::NativeToUTF8 +// ============================ + +void ReconcileUtils::NativeToUTF8( const std::string & input, std::string & output ) +{ + output.erase(); + // IF it is not UTF-8 + if( ! ReconcileUtils::IsUTF8( input.c_str(), input.length() ) ) + { + // And ServerMode is not active + if( ! ignoreLocalText ) + { + // Convert it to UTF-8 + ReconcileUtils::LocalToUTF8( input.c_str(), input.length(), &output ); + } + } + else // If it is already UTF-8 + { + output = input; + } +} // ReconcileUtils::NativeToUTF8 + + +#if XMP_iOSBuild + void ReconcileUtils::IOSConvertEncoding(XMP_Uns32 srcEncoding, XMP_Uns32 destEncoding, const XMP_Uns8 * inputPtr, size_t inputLen, std::string * output) + { + if(srcEncoding == kCFStringEncodingInvalidId || destEncoding == kCFStringEncodingInvalidId || + !CFStringIsEncodingAvailable(srcEncoding) || !CFStringIsEncodingAvailable(destEncoding)) + return; + CFStringRef cStrRef = CFStringCreateWithBytesNoCopy(NULL, inputPtr, inputLen, srcEncoding, false, kCFAllocatorNull); + if(cStrRef == NULL) + return; + CFRange inputRange = CFRangeMake(0, CFStringGetLength(cStrRef)); + const size_t kBufferLen = 1000; + while(inputRange.length > 0) + { + XMP_Uns8 buffer[kBufferLen]; + CFIndex charsWritten; + CFIndex charsProcessed = CFStringGetBytes(cStrRef, inputRange, destEncoding, 0, FALSE, buffer, kBufferLen, &charsWritten); + if (charsProcessed == 0) break; + output->append(reinterpret_cast(&buffer[0]), charsWritten); + inputRange.location += charsProcessed; + inputRange.length -= charsProcessed; + } + CFRelease(cStrRef); + } +#endif diff --git a/core/libs/dngwriter/extra/xmp_sdk/XMPFiles/source/FormatSupport/Reconcile_Impl.hpp b/core/libs/dngwriter/extra/xmp_sdk/XMPFiles/source/FormatSupport/Reconcile_Impl.hpp new file mode 100644 index 0000000000..4cfac3e1c9 --- /dev/null +++ b/core/libs/dngwriter/extra/xmp_sdk/XMPFiles/source/FormatSupport/Reconcile_Impl.hpp @@ -0,0 +1,106 @@ +#ifndef __Reconcile_Impl_hpp__ +#define __Reconcile_Impl_hpp__ 1 + +// ================================================================================================= +// ADOBE SYSTEMS INCORPORATED +// Copyright 2006 Adobe Systems Incorporated +// All Rights Reserved +// +// NOTICE: Adobe permits you to use, modify, and distribute this file in accordance with the terms +// of the Adobe license agreement accompanying it. +// ================================================================================================= + +#include "public/include/XMP_Environment.h" // ! This must be the first include. + +#include "XMPFiles/source/FormatSupport/ReconcileLegacy.hpp" +#include "XMP_MD5.h" + +// ================================================================================================= +/// \file Reconcile_Impl.hpp +/// \brief Implementation utilities for the legacy metadata reconciliation support. +/// +// ================================================================================================= + +typedef XMP_Uns8 MD5_Digest[16]; // ! Should be in MD5.h. + +enum { + kDigestMissing = -1, // A partial import is done, existing XMP is left alone. + kDigestDiffers = 0, // A full import is done, existing XMP is deleted or replaced. + kDigestMatches = +1 // No importing is done. +}; + +namespace ReconcileUtils { + + // *** These ought to be with the Unicode conversions. + + static const char * kHexDigits = "0123456789ABCDEF"; + + bool IsASCII ( const void * _textPtr, size_t textLen ); + bool IsUTF8 ( const void * _textPtr, size_t textLen ); + + void UTF8ToLocal ( const void * _utf8Ptr, size_t utf8Len, std::string * local ); + void UTF8ToLatin1 ( const void * _utf8Ptr, size_t utf8Len, std::string * latin1 ); + void LocalToUTF8 ( const void * _localPtr, size_t localLen, std::string * utf8 ); + void Latin1ToUTF8 ( const void * _latin1Ptr, size_t latin1Len, std::string * utf8 ); + + // + // Checks if the input string is UTF-8 encoded. If not, it tries to convert it to UTF-8 + // This is only done, if Server Mode is not active! + // @param input the native input string + // @return The input if it is already UTF-8, the converted input + // or an empty string if no conversion is possible because of ServerMode + // + void NativeToUTF8 ( const std::string & input, std::string & output ); + + #if XMP_WinBuild + void UTF8ToWinEncoding ( UINT codePage, const XMP_Uns8 * utf8Ptr, size_t utf8Len, std::string * host ); + void WinEncodingToUTF8 ( UINT codePage, const XMP_Uns8 * hostPtr, size_t hostLen, std::string * utf8 ); + #elif XMP_MacBuild + void UTF8ToMacEncoding ( XMP_Uns16 macScript, XMP_Uns16 macLang, const XMP_Uns8 * utf8Ptr, size_t utf8Len, std::string * host ); + void MacEncodingToUTF8 ( XMP_Uns16 macScript, XMP_Uns16 macLang, const XMP_Uns8 * hostPtr, size_t hostLen, std::string * utf8 ); + #elif XMP_iOSBuild + void IOSConvertEncoding(XMP_Uns32 srcEncoding, XMP_Uns32 destEncoding, const XMP_Uns8 * inputPtr, size_t inputLen, std::string * output); + #endif + +}; // ReconcileUtils + +namespace PhotoDataUtils { + + int CheckIPTCDigest ( const void * newPtr, const XMP_Uns32 newLen, const void * oldDigest ); + void SetIPTCDigest ( void * iptcPtr, XMP_Uns32 iptcLen, PSIR_Manager * psir ); + + bool GetNativeInfo ( const TIFF_Manager & exif, XMP_Uns8 ifd, XMP_Uns16 id, TIFF_Manager::TagInfo * info ); + size_t GetNativeInfo ( const IPTC_Manager & iptc, XMP_Uns8 id, int digestState, + bool haveXMP, IPTC_Manager::DataSetInfo * info ); + + bool IsValueDifferent ( const TIFF_Manager::TagInfo & exifInfo, + const std::string & xmpValue, std::string * exifValue ); + bool IsValueDifferent ( const IPTC_Manager & newIPTC, const IPTC_Manager & oldIPTC, XMP_Uns8 id ); + + void ImportPSIR ( const PSIR_Manager & psir, SXMPMeta * xmp, int iptcDigestState ); + + void Import2WayIPTC ( const IPTC_Manager & iptc, SXMPMeta * xmp, int iptcDigestState ); + void Import2WayExif ( const TIFF_Manager & exif, SXMPMeta * xmp, int iptcDigestState ); + + void Import3WayItems ( const TIFF_Manager & exif, const IPTC_Manager & iptc, SXMPMeta * xmp, int iptcDigestState ); + + void ExportPSIR ( const SXMPMeta & xmp, PSIR_Manager * psir ); + void ExportIPTC ( const SXMPMeta & xmp, IPTC_Manager * iptc ); + void ExportExif ( SXMPMeta * xmp, TIFF_Manager * exif ); + + // These need to be exposed for use in Import3WayItem: + + void ImportIPTC_Simple ( const IPTC_Manager & iptc, SXMPMeta * xmp, + XMP_Uns8 id, const char * xmpNS, const char * xmpProp ); + + void ImportIPTC_LangAlt ( const IPTC_Manager & iptc, SXMPMeta * xmp, + XMP_Uns8 id, const char * xmpNS, const char * xmpProp ); + + void ImportIPTC_Array ( const IPTC_Manager & iptc, SXMPMeta * xmp, + XMP_Uns8 id, const char * xmpNS, const char * xmpProp ); + + void ImportIPTC_Date ( XMP_Uns8 dateID, const IPTC_Manager & iptc, SXMPMeta * xmp ); + +}; // PhotoDataUtils + +#endif // __Reconcile_Impl_hpp__ diff --git a/core/libs/dngwriter/extra/xmp_sdk/XMPFiles/source/FormatSupport/SVG_Adapter.cpp b/core/libs/dngwriter/extra/xmp_sdk/XMPFiles/source/FormatSupport/SVG_Adapter.cpp new file mode 100644 index 0000000000..120fa3cbb4 --- /dev/null +++ b/core/libs/dngwriter/extra/xmp_sdk/XMPFiles/source/FormatSupport/SVG_Adapter.cpp @@ -0,0 +1,464 @@ +// ================================================================================================= +// Copyright 2015 Adobe Systems Incorporated +// All Rights Reserved. +// +// NOTICE: Adobe permits you to use, modify, and distribute this file in accordance with the terms +// of the Adobe license agreement accompanying it. +// +// This file includes implementation of SVG metadata, according to Scalable Vector Graphics (SVG) 1.1 Specification. +// "https://www.w3.org/TR/2003/REC-SVG11-20030114/" +// Copyright © 1994-2002 World Wide Web Consortium, (Massachusetts Institute of Technology, +// Institut National de Recherche en Informatique et en Automatique, Keio University). +// All Rights Reserved . http://www.w3.org/Consortium/Legal +// +// ================================================================================================= + +#include "public/include/XMP_Environment.h" // ! Must be the first #include! + +#include "XMPFiles/source/FormatSupport/SVG_Adapter.hpp" + +#include "expat.h" +#include + +using namespace std; + +#if XMP_WinBuild +#pragma warning ( disable : 4996 ) // '...' was declared deprecated +#endif + +#define FullNameSeparator '@' + +// ================================================================================================= + +static void StartNamespaceDeclHandler( void * userData, XMP_StringPtr prefix, XMP_StringPtr uri ); +static void EndNamespaceDeclHandler( void * userData, XMP_StringPtr prefix ); +static void StartElementHandler( void * userData, XMP_StringPtr name, XMP_StringPtr* attrs ); +static void EndElementHandler( void * userData, XMP_StringPtr name ); +static void CharacterDataHandler( void * userData, XMP_StringPtr cData, int len ); +static void ProcessingInstructionHandler( void * userData, XMP_StringPtr target, XMP_StringPtr data ); +static void DeclarationHandler( void *userData, const XML_Char *version, const XML_Char *encoding, int standalone ); + +static bool isRequireData = false; +static XMP_Uns32 reqDepth = 0; + +// Flag is provided to support behaviour like Expat Adapter +#if BanAllEntityUsage + +// For now we do this by banning DOCTYPE entirely. This is easy and consistent with what is +// available in recent Java XML parsers. Another, somewhat less drastic, approach would be to +// ban all entity declarations. We can't allow declarations and ban references, Expat does not +// call the SkippedEntityHandler for references in attribute values. + +// ! Standard entities (&, <, >, ", ', and numeric character references) are +// ! not banned. Expat handles them transparently no matter what. + +static void StartDoctypeDeclHandler( void * userData, XMP_StringPtr doctypeName, + XMP_StringPtr sysid, XMP_StringPtr pubid, int has_internal_subset ); + +#endif + +// ================================================================================================= + +SVG_Adapter::SVG_Adapter() : parser(0), registeredNamespaces(0), firstSVGElementOffset(-1), depth(0) +{ + + this->parser = XML_ParserCreateNS( 0, FullNameSeparator ); + + if ( this->parser == 0 ) { + XMP_Error error( kXMPErr_NoMemory, "Failure creating Expat parser" ); + this->NotifyClient( kXMPErrSev_ProcessFatal, error ); + } + else + { + this->registeredNamespaces = new XMP_NamespaceTable(); + + XML_SetUserData( this->parser, this ); + XML_SetNamespaceDeclHandler( this->parser, StartNamespaceDeclHandler, EndNamespaceDeclHandler ); + XML_SetElementHandler( this->parser, StartElementHandler, EndElementHandler ); + XML_SetCharacterDataHandler( this->parser, CharacterDataHandler ); + XML_SetProcessingInstructionHandler( this->parser, ProcessingInstructionHandler ); + XML_SetXmlDeclHandler( this->parser, DeclarationHandler ); + +#if BanAllEntityUsage + XML_SetStartDoctypeDeclHandler( this->parser, StartDoctypeDeclHandler ); + isAborted = false; +#endif + + this->parseStack.push_back( &this->tree ); // Push the XML root node. + } +} // SVG_Adapter::SVG_Adapter + +// ================================================================================================= + +SVG_Adapter::~SVG_Adapter() +{ + if ( this->parser != 0 ) XML_ParserFree( this->parser ); + this->parser = 0; + + if ( this->registeredNamespaces != 0 ) delete ( this->registeredNamespaces ); + this->registeredNamespaces = 0; + +} // SVG_Adapter::~SVG_Adapter + +// ================================================================================================= + +OffsetStruct SVG_Adapter::GetElementOffsets( std::string elementName ) +{ + IteratorStringOffsetStruct iterator = this->mOffsetsMap.find( elementName ); + if ( iterator != mOffsetsMap.end() ) + return iterator->second; + + return OffsetStruct(); +} // SVG_Adapter::GetElementOffset + +// ================================================================================================= + +void SVG_Adapter::RegisterElement( std::string elementName, std::string reqParent ) +{ + IteratorStringOffsetStruct iterator = this->mOffsetsMap.find( elementName ); + if ( iterator == mOffsetsMap.end() ) + { + this->mOffsetsMap.insert( iterator, std::pair( elementName, OffsetStruct( reqParent ) ) ); + } +} // SVG_Adapter::RegisterElement + +// ================================================================================================= + +XMP_Int64 SVG_Adapter::GetPIOffset( std::string PIName, XMP_Uns32 requiredIndex /* = 1 */ ) +{ + if ( this->parser != 0 ) + { + std::pair iterator = this->mPIWithOffsetMap.equal_range( PIName ); + if ( iterator.first != iterator.second ) + { + XMP_Uns32 index = 0; + IteratorStringXMP_Int64 indexIterator = iterator.first; + for ( ; index < ( requiredIndex - 1 ) && indexIterator != iterator.second; ++indexIterator, ++index ); + if ( index == requiredIndex - 1 ) + return indexIterator->second; + } + } + return -1; +} // SVG_Adapter::GetPIOffset + +// ================================================================================================= + +void SVG_Adapter::RegisterPI( std::string PIName ) +{ + IteratorStringXMP_Int64 iterator = this->mPIWithOffsetMap.find( PIName ); + if ( iterator == mPIWithOffsetMap.end() ) + { + this->mPIWithOffsetMap.insert( iterator, std::pair( PIName, -1 ) ); + } +} // SVG_Adapter::RegisterPI + +// ================================================================================================= + +XMP_Bool SVG_Adapter::IsParsingRequire( ) +{ + for ( IteratorStringOffsetStruct iterator = this->mOffsetsMap.begin(); iterator != this->mOffsetsMap.end(); ++iterator ) + { + if ( iterator->second.startOffset == -1 || iterator->second.endOffset == -1 || iterator->second.nextOffset == -1 ) + return true; + } + return false; +} // SVG_Adapter::IsParsingRequire + +// ================================================================================================= + +// This version of parsing throw an error +void SVG_Adapter::ParseBuffer( const void * buffer, size_t length, bool last /* = true */ ) +{ + enum XML_Status status; + + if ( length == 0 ) { // Expat does not like empty buffers. + if ( !last ) return; + const char * kOneSpace = " "; + buffer = kOneSpace; + length = 1; + } + + status = XML_Parse( this->parser, ( const char * ) buffer, static_cast< XMP_StringLen >( length ), last ); + +#if BanAllEntityUsage + if ( this->isAborted ) { + XMP_Error error( kXMPErr_BadXML, "DOCTYPE is not allowed" ) + this->NotifyClient( kXMPErrSev_Recoverable, error ); + } +#endif + + if ( status != XML_STATUS_OK ) { + + XMP_Error error( kXMPErr_BadXML, "Invalid SVG file" ); + this->NotifyClient( kXMPErrSev_OperationFatal, error ); + + } + +} // SVG_Adapter::ParseBuffer + +// ================================================================================================= + +// This version of parsing doesn't throw error but returns false if any error is encountered +// This is required just for checkformat +XMP_Bool SVG_Adapter::ParseBufferNoThrow( const void * buffer, size_t length, bool last /* = true */ ) +{ + enum XML_Status status; + + if ( length == 0 ) { // Expat does not like empty buffers. + if ( !last ) return false; + const char * kOneSpace = " "; + buffer = kOneSpace; + length = 1; + } + + status = XML_Parse( this->parser, ( const char * ) buffer, static_cast< XMP_StringLen >( length ), last ); + +#if BanAllEntityUsage + if ( this->isAborted ) { + XMP_Error error( kXMPErr_BadXML, "DOCTYPE is not allowed" ) + this->NotifyClient( kXMPErrSev_Recoverable, error ); + } +#endif + + if ( status != XML_STATUS_OK ) + return false; + else + return true; + +} // SVG_Adapter::ParseBufferNoThrow + +// ================================================================================================= + +static void ParseFullNS( XMP_StringPtr fullName, string & NS, string &localName ) +{ + // Expat delivers the full name as a catenation of namespace URI, separator, and local name. + size_t sepPos = strlen( fullName ); + for ( --sepPos; sepPos > 0; --sepPos ) { + if ( fullName[ sepPos ] == FullNameSeparator ) break; + } + + if ( fullName[ sepPos ] == FullNameSeparator ) + { + localName = fullName + sepPos + 1; + NS.assign( fullName, sepPos ); + } + else + localName = fullName; + +} // ParseFullNS + +// ================================================================================================= + +static void StartNamespaceDeclHandler( void * userData, XMP_StringPtr prefix, XMP_StringPtr uri ) +{ + + SVG_Adapter * thiz = ( SVG_Adapter* ) userData; + + if ( prefix == 0 ) prefix = "_dflt_"; // Have default namespace. + if ( uri == 0 ) return; // Ignore, have xmlns:pre="", no URI to register. + + ( void ) thiz->registeredNamespaces->Define( uri, prefix, 0, 0 ); + +} // StartNamespaceDeclHandler + +// ================================================================================================= + +static void EndNamespaceDeclHandler( void * userData, XMP_StringPtr prefix ) +{ + IgnoreParam( userData ); + IgnoreParam( prefix ); + if ( prefix == 0 ) prefix = "_dflt_"; // Have default namespace. + +} // EndNamespaceDeclHandler + +// ================================================================================================= + +static void StartElementHandler( void * userData, XMP_StringPtr name, XMP_StringPtr* attrs ) +{ + // In case, if name is NULL then ParseBuffer would return with error status + SVG_Adapter * thiz = ( SVG_Adapter* ) userData; + thiz->depth++; + if ( thiz->depth > 3 ) + return; + else if ( thiz->firstSVGElementOffset == -1 && thiz->depth == 2 ) + thiz->firstSVGElementOffset = XML_GetCurrentByteIndex( thiz->parser ); + else + { + if ( !thiz->mPrevRequiredElement.empty() ) + { + IteratorStringOffsetStruct iterator = thiz->mOffsetsMap.find( thiz->mPrevRequiredElement ); + if ( iterator != thiz->mOffsetsMap.end() ) + iterator->second.nextOffset = XML_GetCurrentByteIndex( thiz->parser ); + thiz->mPrevRequiredElement.clear(); + } + } + + string NS, localName; + ParseFullNS( name, NS, localName ); + + IteratorStringOffsetStruct iterator = thiz->mOffsetsMap.find( localName ); + if ( iterator == thiz->mOffsetsMap.end() && localName != "svg" ) + return; + + XML_Node * parentNode = thiz->parseStack.back(); + XML_Node * elemNode = new XML_Node( parentNode, "", kElemNode ); + + if ( strncmp( localName.c_str(), name, localName.length() ) != 0 ) + { + XMP_StringPtr prefix; + XMP_StringLen prefixLen; + bool found = thiz->registeredNamespaces->GetPrefix( NS.c_str(), &prefix, &prefixLen ); + if ( !found ) { + XMP_Error error( kXMPErr_ExternalFailure, "Unknown URI in Expat full name" ); + thiz->NotifyClient( kXMPErrSev_OperationFatal, error ); + } + elemNode->ns = NS; + elemNode->nsPrefixLen = prefixLen; // ! Includes the ':'. + + if ( strcmp( prefix, "_dflt_:" ) == 0 ) + { + elemNode->name = localName; + elemNode->nsPrefixLen = 0; + } + else + { + elemNode->name = prefix; + elemNode->name += localName; + } + } + else + { + elemNode->name = localName; // The name is not in a namespace. + } + + parentNode->content.push_back( elemNode ); + thiz->parseStack.push_back( elemNode ); + + if ( iterator != thiz->mOffsetsMap.end() && iterator->second.parent == parentNode->name ) + { + reqDepth = thiz->depth; + isRequireData = true; + if ( iterator->second.startOffset == -1 ) + iterator->second.startOffset = XML_GetCurrentByteIndex( thiz->parser ); + } + else + { + isRequireData = false; + } + +} // StartElementHandler + +// ================================================================================================= + +static void EndElementHandler( void * userData, XMP_StringPtr name ) +{ + SVG_Adapter * thiz = ( SVG_Adapter* ) userData; + + thiz->depth--; + if ( thiz->depth > 2 ) + return; + + string NS, localName; + ParseFullNS( name, NS, localName ); + + IteratorStringOffsetStruct iterator = thiz->mOffsetsMap.find( localName ); + if ( iterator != thiz->mOffsetsMap.end() ) + { + // StartOffset flag is provided to reject the elements of non-required namespace + // Endoffset flag is provided to maintain state of first available element + // Depth flag is provided to support for workflow like <title>... + if ( iterator->second.startOffset != -1 && iterator->second.endOffset == -1 && thiz->depth == reqDepth - 1 ) + { + iterator->second.endOffset = XML_GetCurrentByteIndex( thiz->parser ); + thiz->mPrevRequiredElement = localName; + } + } + else if ( localName != "svg" ) + return; + + ( void ) thiz->parseStack.pop_back(); + +} // EndElementHandler + +// ================================================================================================= + +static void CharacterDataHandler( void * userData, XMP_StringPtr cData, int len ) +{ + if ( !isRequireData ) + return; + isRequireData = false; + SVG_Adapter * thiz = ( SVG_Adapter* ) userData; + + if ( ( cData == 0 ) || ( len == 0 ) ) { cData = ""; len = 0; } + + XML_Node * parentNode = thiz->parseStack.back(); + XML_Node * cDataNode = new XML_Node( parentNode, "", kCDataNode ); + + cDataNode->value.assign( cData, len ); + parentNode->content.push_back( cDataNode ); + +} // CharacterDataHandler + +// ================================================================================================= + +static void ProcessingInstructionHandler( void * userData, XMP_StringPtr target, XMP_StringPtr data ) +{ + + if ( target == NULL || strncmp( target, "xpacket", 7 ) != 0 ) + return; // Ignore all PIs except the XMP packet wrapper. + SVG_Adapter * thiz = ( SVG_Adapter* ) userData; + XML_Node * parentNode = thiz->parseStack.back(); + if ( parentNode->name != "metadata" ) + return; + IteratorStringXMP_Int64 iterator = thiz->mPIWithOffsetMap.find( target ); + if ( iterator != thiz->mPIWithOffsetMap.end() ) + { + if ( iterator->second == -1 ) + iterator->second = XML_GetCurrentByteIndex( thiz->parser ); + else + thiz->mPIWithOffsetMap.insert( std::pair( target, XML_GetCurrentByteIndex( thiz->parser ) ) ); + } + + if ( data == 0 ) data = ""; + XML_Node * piNode = new XML_Node( parentNode, target, kPINode ); + + piNode->value.assign( data ); + parentNode->content.push_back( piNode ); + +} // ProcessingInstructionHandler + +// ================================================================================================= + +static void DeclarationHandler( void *userData, const XML_Char *version, const XML_Char *encoding, int standalone ) +{ + if ( encoding == NULL || strlen( encoding ) != 5 || + ( tolower( encoding[ 0 ] ) == 'u' + && tolower( encoding[ 1 ] ) == 't' + && tolower( encoding[ 2 ] ) == 'f' + && encoding[ 3 ] == '-' + && encoding[ 4 ] == '8' ) ) + return; + else + { + SVG_Adapter * thiz = ( SVG_Adapter* ) userData; + ( void ) ( XML_StopParser( thiz->parser, false ) ); + } +} // DeclarationHandler + +// ================================================================================================= + +#if BanAllEntityUsage +static void StartDoctypeDeclHandler( void * userData, XMP_StringPtr doctypeName, + XMP_StringPtr sysid, XMP_StringPtr pubid, int has_internal_subset ) +{ + IgnoreParam( userData ); + + SVG_Adapter * thiz = ( SVG_Adapter* ) userData; + + thiz->isAborted = true; // ! Can't throw an exception across the plain C Expat frames. + ( void ) XML_StopParser( thiz->parser, XML_FALSE /* not resumable */ ); + +} // StartDoctypeDeclHandler +#endif + +// ================================================================================================= diff --git a/core/libs/dngwriter/extra/xmp_sdk/XMPFiles/source/FormatSupport/SVG_Adapter.hpp b/core/libs/dngwriter/extra/xmp_sdk/XMPFiles/source/FormatSupport/SVG_Adapter.hpp new file mode 100644 index 0000000000..0c325a5f73 --- /dev/null +++ b/core/libs/dngwriter/extra/xmp_sdk/XMPFiles/source/FormatSupport/SVG_Adapter.hpp @@ -0,0 +1,79 @@ +#ifndef __SVG_Adapter_hpp__ +#define __SVG_Adapter_hpp__ + +// ================================================================================================= +// Copyright 2015 Adobe Systems Incorporated +// All Rights Reserved. +// +// NOTICE: Adobe permits you to use, modify, and distribute this file in accordance with the terms +// of the Adobe license agreement accompanying it. +// +// This file includes implementation of SVG metadata, according to Scalable Vector Graphics (SVG) 1.1 Specification. +// "https://www.w3.org/TR/2003/REC-SVG11-20030114/" +// Copyright © 1994-2002 World Wide Web Consortium, (Massachusetts Institute of Technology, +// Institut National de Recherche en Informatique et en Automatique, Keio University). +// All Rights Reserved . http://www.w3.org/Consortium/Legal +// +// ================================================================================================= + +#include "public/include/XMP_Environment.h" // ! Must be the first #include! +#include "source/XMLParserAdapter.hpp" + +// ================================================================================================= +// Derived SVG parser adapter for Expat. +// ================================================================================================= + +#ifndef BanAllEntityUsage +#define BanAllEntityUsage 0 +#endif + +struct XML_ParserStruct; // ! Hack to avoid exposing expat.h to all clients. +typedef struct XML_ParserStruct *XML_Parser; +typedef std::map::iterator IteratorStringXMP_Int64; + +struct OffsetStruct +{ + XMP_Int64 startOffset; + XMP_Int64 nextOffset; + XMP_Int64 endOffset; + std::string parent; + OffsetStruct() : startOffset( -1 ), nextOffset( -1 ), endOffset( -1 ) {} + OffsetStruct( std::string reqParent ) : startOffset( -1 ), nextOffset( -1 ), endOffset( -1 ), parent( reqParent ) {} +}; + +typedef std::map::iterator IteratorStringOffsetStruct; + +class SVG_Adapter : public XMLParserAdapter { +public: + + XML_Parser parser; + XMP_NamespaceTable * registeredNamespaces; + +#if BanAllEntityUsage + bool isAborted; +#endif + + SVG_Adapter( ); + virtual ~SVG_Adapter(); + + virtual void ParseBuffer( const void * buffer, size_t length, bool last = true ); + virtual XMP_Bool ParseBufferNoThrow( const void * buffer, size_t length, bool last = true ); + + virtual OffsetStruct GetElementOffsets( std::string elementName ); + virtual void RegisterElement( std::string elementName, std::string reqParent ); + + virtual XMP_Int64 GetPIOffset( std::string PIName, XMP_Uns32 requiredIndex = 1 ); + virtual void RegisterPI( std::string PIName ); + virtual XMP_Bool IsParsingRequire(); + + std::multimap mPIWithOffsetMap; + std::map mOffsetsMap; + XMP_Int64 firstSVGElementOffset; + + std::string mPrevRequiredElement; + XMP_Uns32 depth; +}; + +// ================================================================================================= + +#endif // __SVG_Adapter_hpp__ diff --git a/core/libs/dngwriter/extra/xmp_sdk/XMPFiles/source/FormatSupport/SWF_Support.cpp b/core/libs/dngwriter/extra/xmp_sdk/XMPFiles/source/FormatSupport/SWF_Support.cpp new file mode 100644 index 0000000000..218f98e4b8 --- /dev/null +++ b/core/libs/dngwriter/extra/xmp_sdk/XMPFiles/source/FormatSupport/SWF_Support.cpp @@ -0,0 +1,484 @@ +// ================================================================================================= +// ADOBE SYSTEMS INCORPORATED +// Copyright 2007 Adobe Systems Incorporated +// All Rights Reserved +// +// NOTICE: Adobe permits you to use, modify, and distribute this file in accordance with the terms +// of the Adobe license agreement accompanying it. +// ================================================================================================= + +#include "public/include/XMP_Environment.h" // ! XMP_Environment.h must be the first included header. + +#include "public/include/XMP_Const.h" +#include "public/include/XMP_IO.hpp" + +#include "XMPFiles/source/XMPFiles_Impl.hpp" +#include "source/XMPFiles_IO.hpp" +#include "source/XIO.hpp" + +#include "XMPFiles/source/FormatSupport/SWF_Support.hpp" + +#include "zlib.h" + +// ================================================================================================= + +XMP_Uns32 SWF_IO::FileHeaderSize ( XMP_Uns8 rectBits ) { + + // Return the full size of the SWF header, adding the fixed size to the variable RECT size. + + XMP_Uns8 bitsPerField = rectBits >> 3; + XMP_Uns32 rectBytes = ((5 + (4 * bitsPerField)) / 8) + 1; + + return SWF_IO::HeaderFixedSize + rectBytes; + +} // SWF_IO::FileHeaderSize + +// ================================================================================================= + +bool SWF_IO::GetTagInfo ( const RawDataBlock & swfStream, XMP_Uns32 tagOffset, SWF_IO::TagInfo * info ) { + + if ( tagOffset >= swfStream.size() ) return false; + XMP_Uns32 spaceLeft = swfStream.size() - tagOffset; + XMP_Uns8 headerSize = 2; + if ( spaceLeft < headerSize ) return false; // The minimum empty tag is a 2 byte header. + + XMP_Uns16 tagHeader = GetUns16LE ( &swfStream[tagOffset] ); + + info->tagID = tagHeader >> 6; + info->tagOffset = tagOffset; + info->contentLength = tagHeader & SWF_IO::TagLengthMask; + + if ( info->contentLength != SWF_IO::TagLengthMask ) { + info->hasLongHeader = false; + } else { + headerSize = 6; + if ( spaceLeft < headerSize ) return false; // Make sure there is room for the extended length. + info->contentLength = GetUns32LE ( &swfStream[tagOffset+2] ); + info->hasLongHeader = true; + } + + if ( (spaceLeft - headerSize) < info->contentLength ) return false; + + return true; + +} // SWF_IO::GetTagInfo + +// ================================================================================================= + +static inline XMP_Uns32 TagHeaderSize ( const SWF_IO::TagInfo & info ) { + + XMP_Uns8 headerSize = 2; + if ( info.hasLongHeader ) headerSize = 6; + return headerSize; + +} // TagHeaderSize + +// ================================================================================================= + +XMP_Uns32 SWF_IO::FullTagLength ( const SWF_IO::TagInfo & info ) { + + return TagHeaderSize ( info ) + info.contentLength; + +} // SWF_IO::FullTagLength + +// ================================================================================================= + +XMP_Uns32 SWF_IO::ContentOffset ( const SWF_IO::TagInfo & info ) { + + return info.tagOffset + TagHeaderSize ( info ); + +} // SWF_IO::ContentOffset + +// ================================================================================================= + +XMP_Uns32 SWF_IO::NextTagOffset ( const SWF_IO::TagInfo & info ) { + + return info.tagOffset + FullTagLength ( info ); + +} // SWF_IO::NextTagOffset + +// ================================================================================================= + +static inline void AppendData ( RawDataBlock * dataOut, XMP_Uns8 * buffer, size_t count ) { + + size_t prevSize = dataOut->size(); // ! Don't save a pointer, there might be a reallocation. + dataOut->insert ( dataOut->end(), count, 0 ); // Add space to the RawDataBlock. + memcpy ( &((*dataOut)[prevSize]), buffer, count ); + +} // AppendData + +// ================================================================================================= + +XMP_Int64 SWF_IO::DecompressFileToMemory ( XMP_IO * fileIn, RawDataBlock * dataOut ) { + + fileIn->Rewind(); + dataOut->clear(); + + static const size_t bufferSize = 64*1024; + XMP_Uns8 bufferIn [ bufferSize ]; + XMP_Uns8 bufferOut [ bufferSize ]; + + int err; + z_stream zipState; + memset ( &zipState, 0, sizeof(zipState) ); + err = inflateInit ( &zipState ); + XMP_Enforce ( err == Z_OK ); + + XMP_Int32 ioCount; + XMP_Int64 offsetIn; + const XMP_Int64 lengthIn = fileIn->Length(); + XMP_Enforce ( ((XMP_Int64)SWF_IO::HeaderPrefixSize <= lengthIn) && (lengthIn <= SWF_IO::MaxExpandedSize) ); + + // Set the uncompressed part of the header. Save the expanded size from the file. + + fileIn->ReadAll ( bufferIn, SWF_IO::HeaderPrefixSize ); + offsetIn = SWF_IO::HeaderPrefixSize; + XMP_Uns32 expectedFullSize = GetUns32LE ( &bufferIn[4] ); + + AppendData ( dataOut, bufferIn, SWF_IO::HeaderPrefixSize ); // Copy the compressed stream's prefix. + PutUns32LE ( SWF_IO::ExpandedSignature, &(*dataOut)[0] ); // Change the signature. + (*dataOut)[3] = bufferIn[3]; // Keep the SWF version. + + // Read the input file, feed it to the decompression engine, writing as needed. + + zipState.next_out = &bufferOut[0]; // Initial output conditions. Must be set before the input loop! + zipState.avail_out = bufferSize; + + while ( offsetIn < lengthIn ) { + + // Read the next chunk of input. + ioCount = fileIn->Read ( bufferIn, bufferSize ); + XMP_Enforce ( ioCount > 0 ); + offsetIn += ioCount; + zipState.next_in = &bufferIn[0]; + zipState.avail_in = ioCount; + + // Process all of this input, writing as needed. + + err = Z_OK; + while ( (zipState.avail_in > 0) && (err == Z_OK) ) { + + XMP_Assert ( zipState.avail_out > 0 ); // Sanity check for output buffer space. + err = inflate ( &zipState, Z_NO_FLUSH ); + XMP_Enforce ( (err == Z_OK) || (err == Z_STREAM_END) ); + + if ( zipState.avail_out == 0 ) { + AppendData ( dataOut, bufferOut, bufferSize ); + zipState.next_out = &bufferOut[0]; + zipState.avail_out = bufferSize; + } + + } + + } + + // Finish the decompression and write the final output. + + do { + + ioCount = bufferSize - zipState.avail_out; // Make sure there is room for inflate to do more. + if ( ioCount > 0 ) { + AppendData ( dataOut, bufferOut, ioCount ); + zipState.next_out = &bufferOut[0]; + zipState.avail_out = bufferSize; + } + + err = inflate ( &zipState, Z_NO_FLUSH ); + XMP_Enforce ( (err == Z_OK) || (err == Z_STREAM_END) || (err == Z_BUF_ERROR) ); + + } while ( err == Z_OK ); + + ioCount = bufferSize - zipState.avail_out; // Write any final output. + if ( ioCount > 0 ) { + AppendData ( dataOut, bufferOut, ioCount ); + zipState.next_out = &bufferOut[0]; + zipState.avail_out = bufferSize; + } + + // Done. Make sure the file header has the true decompressed size. + + XMP_Int64 lengthOut = zipState.total_out + SWF_IO::HeaderPrefixSize; + if ( lengthOut != expectedFullSize ) PutUns32LE ( (XMP_Uns32)lengthOut, &((*dataOut)[4]) ); + inflateEnd ( &zipState ); + return lengthOut; + +} // SWF_IO::DecompressFileToMemory + +// ================================================================================================= + +XMP_Int64 SWF_IO::CompressMemoryToFile ( const RawDataBlock & dataIn, XMP_IO* fileOut ) { + + fileOut->Rewind(); + fileOut->Truncate ( 0 ); + + static const size_t bufferSize = 64*1024; + XMP_Uns8 bufferOut [ bufferSize ]; + + int err; + z_stream zipState; + memset ( &zipState, 0, sizeof(zipState) ); + err = deflateInit ( &zipState, Z_DEFAULT_COMPRESSION ); + XMP_Enforce ( err == Z_OK ); + + XMP_Int32 ioCount; + const size_t lengthIn = dataIn.size(); + XMP_Enforce ( SWF_IO::HeaderPrefixSize <= lengthIn ); + + // Write the uncompressed part of the file header. + + PutUns32LE ( SWF_IO::CompressedSignature, &bufferOut[0] ); + bufferOut[3] = dataIn[3]; // Copy the SWF version. + PutUns32LE ( lengthIn, &bufferOut[4] ); + fileOut->Write ( bufferOut, SWF_IO::HeaderPrefixSize ); + + // Feed the input to the compression engine in one step, write the output as available. + + zipState.next_in = (Bytef*)&dataIn[SWF_IO::HeaderPrefixSize]; + zipState.avail_in = lengthIn - SWF_IO::HeaderPrefixSize; + zipState.next_out = &bufferOut[0]; + zipState.avail_out = bufferSize; + + while ( zipState.avail_in > 0 ) { + + XMP_Assert ( zipState.avail_out > 0 ); // Sanity check for output buffer space. + err = deflate ( &zipState, Z_NO_FLUSH ); + XMP_Enforce ( err == Z_OK ); + + if ( zipState.avail_out == 0 ) { + fileOut->Write ( bufferOut, bufferSize ); + zipState.next_out = &bufferOut[0]; + zipState.avail_out = bufferSize; + } + + } + + // Finish the compression and write the final output. + + do { + + err = deflate ( &zipState, Z_FINISH ); + XMP_Enforce ( (err == Z_OK) || (err == Z_STREAM_END) ); + ioCount = bufferSize - zipState.avail_out; // See if there is output to write. + + if ( ioCount > 0 ) { + fileOut->Write ( bufferOut, ioCount ); + zipState.next_out = &bufferOut[0]; + zipState.avail_out = bufferSize; + } + + } while ( err != Z_STREAM_END ); + + // Done. + + XMP_Int64 lengthOut = zipState.total_out; + deflateEnd ( &zipState ); + return lengthOut; + +} // SWF_IO::CompressMemoryToFile + +// ================================================================================================= + +#if 0 // ! Not used, but save it for later transfer to a general ZIP utility file. + +XMP_Int64 SWF_IO::DecompressFileToFile ( XMP_IO * fileIn, XMP_IO * fileOut ) { + + fileIn->Rewind(); + fileOut->Rewind(); + fileOut->Truncate ( 0 ); + + static const size_t bufferSize = 64*1024; + XMP_Uns8 bufferIn [ bufferSize ]; + XMP_Uns8 bufferOut [ bufferSize ]; + + int err; + z_stream zipState; + memset ( zipState, 0, sizeof(zipState) ); + err = inflateInit ( &zipState ); + XMP_Enforce ( err == Z_OK ); + + XMP_Int32 ioCount; + XMP_Int64 offsetIn; + const XMP_Int64 lengthIn = fileIn->Length(); + XMP_Enforce ( (lengthIn >= SWF_IO::HeaderPrefixSize) && (lengthIn <= SWF_IO::MaxExpandedSize) ); + + // Copy the uncompressed part of the file header. Save the expanded size from the header. + + fileIn->ReadAll ( bufferIn, SWF_IO::HeaderPrefixSize ); + fileOut.Write ( bufferIn, SWF_IO::HeaderPrefixSize ); + offsetIn = SWF_IO::HeaderPrefixSize; + + XMP_Uns32 expectedFullSize = GetUns32LE ( &bufferIn[4] ); + + // Read the input file, feed it to the decompression engine, writing as needed. + + zipState.next_out = &bufferOut[0]; // Initial output conditions. Must be set before the input loop! + zipState.avail_out = bufferSize; + + while ( offsetIn < lengthIn ) { + + // Read the next chunk of input. + ioCount = fileIn->Read ( bufferIn, bufferSize ); + XMP_Enforce ( ioCount > 0 ); + offsetIn += ioCount; + zipState.next_in = &bufferIn[0]; + zipState.avail_in = ioCount; + + // Process all of this input, writing as needed. + + err = Z_OK; + while ( (zipState.avail_in > 0) && (err == Z_OK) ) { + + XMP_Assert ( zipState.avail_out > 0 ); // Sanity check for output buffer space. + err = inflate ( &zipState, Z_NO_FLUSH ); + XMP_Enforce ( (err == Z_OK) || (err == Z_STREAM_END) ); + + if ( zipState.avail_out == 0 ) { + fileOut->write ( bufferOut, bufferSize ); + zipState.next_out = &bufferOut[0]; + zipState.avail_out = bufferSize; + } + + } + + } + + // Finish the decompression and write the final output. + + do { + + ioCount = bufferSize - zipState.avail_out; // Make sure there is room for inflate. + if ( ioCount > 0 ) { + fileOut->write ( bufferOut, ioCount ); + zipState.next_out = &bufferOut[0]; + zipState.avail_out = bufferSize; + } + + err = inflate ( &zipState, Z_NO_FLUSH ); + XMP_Enforce ( (err == Z_OK) || (err == Z_STREAM_END) || (err == Z_BUF_ERROR) ); + ioCount = bufferSize - zipState.avail_out; // See if there is output to write. + if ( ioCount > 0 ) { + fileOut->write ( bufferOut, ioCount ); + zipState.next_out = &bufferOut[0]; + zipState.avail_out = bufferSize; + } + + } while ( err == Z_OK ); + + ioCount = bufferSize - zipState.avail_out; // Write any final output. + if ( ioCount > 0 ) { + fileOut->write ( bufferOut, ioCount ); + zipState.next_out = &bufferOut[0]; + zipState.avail_out = bufferSize; + } + + // Done. Make sure the file header has the true decompressed size. + + XMP_Int64 lengthOut = zipState.total_out; + + if ( lengthOut != expectedFullSize ) { + PutUns32LE ( &bufferOut[0], lengthOut ); + fileOut->Seek ( 4, kXMP_SeekFromStart ); + fileOut.Write ( &bufferOut[0], 4 ); + fileOut->Seek ( 0, kXMP_SeekFromEnd ); + } + + inflateEnd ( &zipState ); + return lengthOut; + +} // SWF_IO::DecompressFileToFile + +#endif + +// ================================================================================================= + +#if 0 // ! Not used, but save it for later transfer to a general ZIP utility file. + +XMP_Int64 SWF_IO::CompressFileToFile ( XMP_IO * fileIn, XMP_IO * fileOut ) { + + fileIn->Rewind(); + fileOut->Rewind(); + fileOut->Truncate ( 0 ); + + static const size_t bufferSize = 64*1024; + XMP_Uns8 bufferIn [ bufferSize ]; + XMP_Uns8 bufferOut [ bufferSize ]; + + int err; + z_stream zipState; + memset ( zipState, 0, sizeof(zipState) ); + err = deflateInit ( &zipState, Z_DEFAULT_COMPRESSION ); + XMP_Enforce ( err == Z_OK ); + + XMP_Int32 ioCount; + XMP_Int64 offsetIn; + const XMP_Int64 lengthIn = fileIn->Length(); + XMP_Enforce ( (lengthIn >= SWF_IO::HeaderPrefixSize) && (lengthIn <= SWF_IO::MaxExpandedSize) ); + + // Write the uncompressed part of the file header. + + fileIn->ReadAll ( bufferIn, SWF_IO::HeaderPrefixSize ); + offsetIn = SWF_IO::HeaderPrefixSize; + + PutUns32LE ( SWF_IO::CompressedSignature, &bufferOut[0] ); + bufferOut[3] = bufferIn[3]; // Copy the SWF version. + PutUns32LE ( &bufferOut[4], lengthIn ); + fileOut.Write ( bufferOut, SWF_IO::HeaderPrefixSize ); + + // Read the input file, feed it to the compression engine, writing as needed. + + zipState.next_out = &bufferOut[0]; // Initial output conditions. Must be set before the input loop! + zipState.avail_out = bufferSize; + + while ( offsetIn < lengthIn ) { + + // Read the next chunk of input. + ioCount = fileIn->Read ( bufferIn, bufferSize ); + XMP_Enforce ( ioCount > 0 ); + offsetIn += ioCount; + zipState.next_in = &bufferIn[0]; + zipState.avail_in = ioCount; + + // Process all of this input, writing as needed. Yes, we need a loop. Compression means less + // output than input, but a previous read has probably left partial compression results. + + while ( zipState.avail_in > 0 ) { + + XMP_Assert ( zipState.avail_out > 0 ); // Sanity check for output buffer space. + err = deflate ( &zipState, Z_NO_FLUSH ); + XMP_Enforce ( err == Z_OK ); + + if ( zipState.avail_out == 0 ) { + fileOut->write ( bufferOut, bufferSize ); + zipState.next_out = &bufferOut[0]; + zipState.avail_out = bufferSize; + } + + } + + } + + // Finish the compression and write the final output. + + do { + + err = deflate ( &zipState, Z_FINISH ); + XMP_Enforce ( (err == Z_OK) || (err == Z_STREAM_END) ); + ioCount = bufferSize - zipState.avail_out; // See if there is output to write. + + if ( ioCount > 0 ) { + fileOut->write ( bufferOut, ioCount ); + zipState.next_out = &bufferOut[0]; + zipState.avail_out = bufferSize; + } + + } while ( err != Z_STREAM_END ); + + // Done. + + XMP_Int64 lengthOut = zipState.total_out; + deflateEnd ( &zipState ); + return lengthOut; + +} // SWF_IO::CompressFileToFile + +#endif diff --git a/core/libs/dngwriter/extra/xmp_sdk/XMPFiles/source/FormatSupport/SWF_Support.hpp b/core/libs/dngwriter/extra/xmp_sdk/XMPFiles/source/FormatSupport/SWF_Support.hpp new file mode 100644 index 0000000000..243b542811 --- /dev/null +++ b/core/libs/dngwriter/extra/xmp_sdk/XMPFiles/source/FormatSupport/SWF_Support.hpp @@ -0,0 +1,86 @@ +#ifndef __SWF_Support_hpp__ +#define __SWF_Support_hpp__ 1 + +// ================================================================================================= +// ADOBE SYSTEMS INCORPORATED +// Copyright 2008 Adobe Systems Incorporated +// All Rights Reserved +// +// NOTICE: Adobe permits you to use, modify, and distribute this file in accordance with the terms +// of the Adobe license agreement accompanying it. +// ================================================================================================= + +#include "public/include/XMP_Environment.h" // ! XMP_Environment.h must be the first included header. + +#include "public/include/XMP_Const.h" +#include "public/include/XMP_IO.hpp" + +#include "XMPFiles/source/XMPFiles_Impl.hpp" + +#include "zlib.h" + +// ================================================================================================= + +namespace SWF_IO { + + const XMP_Int64 MaxExpandedSize = 0xFFFFFFFFUL; // The file header has a UInt32 expanded size field. + + const size_t HeaderPrefixSize = 8; // The uncompressed first part of the file header. + const size_t HeaderFixedSize = 12; // The fixed size part of the file header, omits the RECT. + + const XMP_Uns32 CompressedSignature = 0x00535743; // The low 3 bytes are "SWC". + const XMP_Uns32 ExpandedSignature = 0x00535746; // The low 3 bytes are "SWF". + // Note: Can't use char* here, it causes duplicate symbols with xcode. + + const XMP_Uns16 FileAttributesTagID = 69; + const XMP_Uns16 MetadataTagID = 77; + + const XMP_Uns8 TagLengthMask = 0x3F; + const XMP_Uns8 HasMetadataMask = 0x10; + + // A SWF file begins with a variable length header. The header layout is: + // + // UInt8[3] - "FWS" for uncompressed SWF and "CWS" for compressed SWF + // UInt8 - SWF format version + // UInt32 - Length of uncompressed file, little endian + // RECT - packed bit RECT structure + // UInt16 - frame rate, little endian, really 8.8 fixed point + // UInt16 - frame count, little endian + // + // If the first 4 bytes are read as a little endian UInt32 they become "vSWC" and "vSWF", where + // the "v" byte is the version format version. + // + // SWF compression starts 8 bytes into the file, after the length field in the header. + // The length in the header is everything. If compressed this is 8 plus the decompressed size. + // + // Following the header is a sequence of tags. Each tag begins with a little endian UInt16 whose + // upper 10 bits are the tag ID and lower 6 bits are a length for the content. If this length is + // 63 (0x3F) then a little endian Int32 follows with the content length. + // + // The FileAttributes tag, #69, has a flag byte and 3 reserved bytes following the header. There + // is only 1 flag bit that we care about, HasMetadata with the mask 0x10. + // + // The Metadata tag, #77, has content that is the UTF-8 XMP, preferably as small as possible. + + XMP_Uns32 FileHeaderSize ( XMP_Uns8 rectBits ); + + class TagInfo { + public: + bool hasLongHeader; + XMP_Uns16 tagID; + XMP_Uns32 tagOffset, contentLength; + TagInfo() : hasLongHeader(false), tagID(0), tagOffset(0), contentLength(0) {}; + ~TagInfo() {}; + }; + + bool GetTagInfo ( const RawDataBlock & swfStream, XMP_Uns32 tagOffset, TagInfo * info ); + XMP_Uns32 FullTagLength ( const TagInfo & info ); + XMP_Uns32 ContentOffset ( const TagInfo & info ); + XMP_Uns32 NextTagOffset ( const TagInfo & info ); + + XMP_Int64 DecompressFileToMemory ( XMP_IO * fileIn, RawDataBlock * dataOut ); + XMP_Int64 CompressMemoryToFile ( const RawDataBlock & dataIn, XMP_IO* fileOut ); + +}; // SWF_IO + +#endif // __SWF_Support_hpp__ diff --git a/core/libs/dngwriter/extra/xmp_sdk/XMPFiles/source/FormatSupport/TIFF_FileWriter.cpp b/core/libs/dngwriter/extra/xmp_sdk/XMPFiles/source/FormatSupport/TIFF_FileWriter.cpp new file mode 100644 index 0000000000..20c6012810 --- /dev/null +++ b/core/libs/dngwriter/extra/xmp_sdk/XMPFiles/source/FormatSupport/TIFF_FileWriter.cpp @@ -0,0 +1,2044 @@ +// ================================================================================================= +// ADOBE SYSTEMS INCORPORATED +// Copyright 2006 Adobe Systems Incorporated +// All Rights Reserved +// +// NOTICE: Adobe permits you to use, modify, and distribute this file in accordance with the terms +// of the Adobe license agreement accompanying it. +// ================================================================================================= + +#include "public/include/XMP_Environment.h" // ! XMP_Environment.h must be the first included header. +#include "public/include/XMP_Const.h" + +#include "XMPFiles/source/FormatSupport/TIFF_Support.hpp" + +#include "source/XIO.hpp" + +#include "source/EndianUtils.hpp" + +// ================================================================================================= +/// \file TIFF_FileWriter.cpp +/// \brief TIFF_FileWriter is used for memory-based read-write access and all file-based access. +/// +/// \c TIFF_FileWriter is used for memory-based read-write access and all file-based access. The +/// main internal data structure is the InternalTagMap, a std::map that uses the tag number as the +/// key and InternalTagInfo as the value. There are 5 of these maps, one for each of the recognized +/// IFDs. The maps contain an entry for each tag in the IFD, whether we capture the data or not. The +/// dataPtr and dataLen fields in the InternalTagInfo are zero if the tag is not captured. +// ================================================================================================= + +// ================================================================================================= +// TIFF_FileWriter::TIFF_FileWriter +// ================================ +// +// Set big endian Get/Put routines so that routines are in place for creating TIFF without a parse. +// Parsing will reset them to the proper endianness for the stream. Big endian is a good default +// since JPEG and PSD files are big endian overall. + +TIFF_FileWriter::TIFF_FileWriter() : changed(false), legacyDeleted(false), memParsed(false), + fileParsed(false), ownedStream(false), memStream(0), tiffLength(0) +{ + + XMP_Uns8 bogusTIFF [kEmptyTIFFLength]; + + bogusTIFF[0] = 0x4D; + bogusTIFF[1] = 0x4D; + bogusTIFF[2] = 0x00; + bogusTIFF[3] = 0x2A; + bogusTIFF[4] = bogusTIFF[5] = bogusTIFF[6] = bogusTIFF[7] = 0x00; + + (void) this->CheckTIFFHeader ( bogusTIFF, sizeof ( bogusTIFF ) ); + +} // TIFF_FileWriter::TIFF_FileWriter + +// ================================================================================================= +// TIFF_FileWriter::~TIFF_FileWriter +// ================================= + +TIFF_FileWriter::~TIFF_FileWriter() +{ + XMP_Assert ( ! (this->memParsed && this->fileParsed) ); + + if ( this->ownedStream ) { + XMP_Assert ( this->memStream != 0 ); + free ( this->memStream ); + } + +} // TIFF_FileWriter::~TIFF_FileWriter + +// ================================================================================================= +// TIFF_FileWriter::DeleteExistingInfo +// =================================== + +void TIFF_FileWriter::DeleteExistingInfo() +{ + XMP_Assert ( ! (this->memParsed && this->fileParsed) ); + + if ( this->ownedStream ) free ( this->memStream ); // ! Current TIFF might be memory-parsed. + this->memStream = 0; + this->tiffLength = 0; + + for ( int ifd = 0; ifd < kTIFF_KnownIFDCount; ++ifd ) this->containedIFDs[ifd].clear(); + + this->changed = false; + this->legacyDeleted = false; + this->memParsed = false; + this->fileParsed = false; + this->ownedStream = false; + +} // TIFF_FileWriter::DeleteExistingInfo + +// ================================================================================================= +// TIFF_FileWriter::PickIFD +// ======================== + +XMP_Uns8 TIFF_FileWriter::PickIFD ( XMP_Uns8 ifd, XMP_Uns16 id ) +{ + if ( ifd > kTIFF_LastRealIFD ) { + if ( ifd != kTIFF_KnownIFD ) XMP_Throw ( "Invalid IFD number", kXMPErr_BadParam ); + XMP_Throw ( "kTIFF_KnownIFD not yet implemented", kXMPErr_Unimplemented ); + // *** Likely to stay unimplemented until there is a client need. + } + + return ifd; + +} // TIFF_FileWriter::PickIFD + +// ================================================================================================= +// TIFF_FileWriter::FindTagInIFD +// ============================= + +const TIFF_FileWriter::InternalTagInfo* TIFF_FileWriter::FindTagInIFD ( XMP_Uns8 ifd, XMP_Uns16 id ) const +{ + ifd = PickIFD ( ifd, id ); + const InternalTagMap& currIFD = this->containedIFDs[ifd].tagMap; + + InternalTagMap::const_iterator tagPos = currIFD.find ( id ); + if ( tagPos == currIFD.end() ) return 0; + return &tagPos->second; + +} // TIFF_FileWriter::FindTagInIFD + +// ================================================================================================= +// TIFF_FileWriter::GetIFD +// ======================= + +bool TIFF_FileWriter::GetIFD ( XMP_Uns8 ifd, TagInfoMap* ifdMap ) const +{ + if ( ifd > kTIFF_LastRealIFD ) XMP_Throw ( "Invalid IFD number", kXMPErr_BadParam ); + const InternalTagMap& currIFD = this->containedIFDs[ifd].tagMap; + + InternalTagMap::const_iterator tagPos = currIFD.begin(); + InternalTagMap::const_iterator tagEnd = currIFD.end(); + + if ( ifdMap != 0 ) ifdMap->clear(); + if ( tagPos == tagEnd ) return false; // Empty IFD. + + if ( ifdMap != 0 ) { + for ( ; tagPos != tagEnd; ++tagPos ) { + const InternalTagInfo& intInfo = tagPos->second; + TagInfo extInfo ( intInfo.id, intInfo.type, intInfo.count, intInfo.dataPtr, intInfo.dataLen ); + (*ifdMap)[intInfo.id] = extInfo; + } + } + + return true; + +} // TIFF_FileWriter::GetIFD + +// ================================================================================================= +// TIFF_FileWriter::GetValueOffset +// =============================== + +XMP_Uns32 TIFF_FileWriter::GetValueOffset ( XMP_Uns8 ifd, XMP_Uns16 id ) const +{ + const InternalTagInfo* thisTag = this->FindTagInIFD ( ifd, id ); + if ( (thisTag == 0) || (thisTag->origDataLen == 0) ) return 0; + + return thisTag->origDataOffset; + +} // TIFF_FileWriter::GetValueOffset + +// ================================================================================================= +// TIFF_FileWriter::GetTag +// ======================= + +bool TIFF_FileWriter::GetTag ( XMP_Uns8 ifd, XMP_Uns16 id, TagInfo* info ) const +{ + const InternalTagInfo* thisTag = this->FindTagInIFD ( ifd, id ); + if ( thisTag == 0 ) return false; + + if ( info != 0 ) { + + info->id = thisTag->id; + info->type = thisTag->type; + info->count = thisTag->dataLen / (XMP_Uns32)kTIFF_TypeSizes[thisTag->type]; + info->dataLen = thisTag->dataLen; + info->dataPtr = (const void*)(thisTag->dataPtr); + + } + + return true; + +} // TIFF_FileWriter::GetTag + +// ================================================================================================= +// TIFF_FileWriter::SetTag +// ======================= + +void TIFF_FileWriter::SetTag ( XMP_Uns8 ifd, XMP_Uns16 id, XMP_Uns16 type, XMP_Uns32 count, const void* clientPtr ) +{ + if ( (type < kTIFF_ByteType) || (type > kTIFF_LastType) ) XMP_Throw ( "Invalid TIFF tag type", kXMPErr_BadParam ); + size_t typeSize = kTIFF_TypeSizes[type]; + size_t fullSize = count * typeSize; + + ifd = PickIFD ( ifd, id ); + InternalTagMap& currIFD = this->containedIFDs[ifd].tagMap; + + InternalTagInfo* tagPtr = 0; + InternalTagMap::iterator tagPos = currIFD.find ( id ); + + if ( tagPos == currIFD.end() ) { + + // The tag does not yet exist, add it. + InternalTagMap::value_type mapValue ( id, InternalTagInfo ( id, type, count, this->fileParsed ) ); + tagPos = currIFD.insert ( tagPos, mapValue ); + tagPtr = &tagPos->second; + + } else { + + tagPtr = &tagPos->second; + + // The tag already exists, make sure the value is actually changing. + if ( (type == tagPtr->type) && (count == tagPtr->count) && + (memcmp ( clientPtr, tagPtr->dataPtr, tagPtr->dataLen ) == 0) ) { + return; // ! The value is unchanged, exit. + } + + tagPtr->FreeData(); // Release any existing data allocation. + + tagPtr->type = type; // These might be changing also. + tagPtr->count = count; + + } + + tagPtr->changed = true; + tagPtr->dataLen = (XMP_Uns32)fullSize; + + if ( fullSize <= 4 ) { + // The data is less than 4 bytes, store it in the smallValue field using native endianness. + tagPtr->dataPtr = (XMP_Uns8*) &tagPtr->smallValue; + } else { + // The data is more than 4 bytes, make a copy. + tagPtr->dataPtr = (XMP_Uns8*) malloc ( fullSize ); + if ( tagPtr->dataPtr == 0 ) XMP_Throw ( "Out of memory", kXMPErr_NoMemory ); + } + memcpy ( tagPtr->dataPtr, clientPtr, fullSize ); // AUDIT: Safe, space guaranteed to be fullSize. + + if ( ! this->nativeEndian ) { + if ( typeSize == 2 ) { + XMP_Uns16* flipPtr = (XMP_Uns16*) tagPtr->dataPtr; + for ( XMP_Uns32 i = 0; i < count; ++i ) Flip2 ( flipPtr[i] ); + } else if ( typeSize == 4 ) { + XMP_Uns32* flipPtr = (XMP_Uns32*) tagPtr->dataPtr; + for ( XMP_Uns32 i = 0; i < count; ++i ) Flip4 ( flipPtr[i] ); + } else if ( typeSize == 8 ) { + XMP_Uns64* flipPtr = (XMP_Uns64*) tagPtr->dataPtr; + for ( XMP_Uns32 i = 0; i < count; ++i ) Flip8 ( flipPtr[i] ); + } + } + + this->containedIFDs[ifd].changed = true; + this->changed = true; + +} // TIFF_FileWriter::SetTag + +// ================================================================================================= +// TIFF_FileWriter::DeleteTag +// ========================== + +void TIFF_FileWriter::DeleteTag ( XMP_Uns8 ifd, XMP_Uns16 id ) +{ + ifd = PickIFD ( ifd, id ); + InternalTagMap& currIFD = this->containedIFDs[ifd].tagMap; + + InternalTagMap::iterator tagPos = currIFD.find ( id ); + if ( tagPos == currIFD.end() ) return; // ! Don't set the changed flags if the tag didn't exist. + + currIFD.erase ( tagPos ); + this->containedIFDs[ifd].changed = true; + this->changed = true; + if ( (ifd != kTIFF_PrimaryIFD) || (id != kTIFF_XMP) ) this->legacyDeleted = true; + +} // TIFF_FileWriter::DeleteTag + +// ================================================================================================= + +static inline XMP_Uns8 GetUns8 ( const void* dataPtr ) +{ + return *((XMP_Uns8*)dataPtr); +} + +// ================================================================================================= +// TIFF_FileWriter::GetTag_Integer +// =============================== + +bool TIFF_FileWriter::GetTag_Integer ( XMP_Uns8 ifd, XMP_Uns16 id, XMP_Uns32* data ) const +{ + const InternalTagInfo* thisTag = this->FindTagInIFD ( ifd, id ); + if ( thisTag == 0 ) return false; + if ( thisTag->count != 1 ) return false; + + XMP_Uns32 uns32; + XMP_Int32 int32; + + switch ( thisTag->type ) { + + case kTIFF_ByteType: + uns32 = GetUns8 ( thisTag->dataPtr ); + break; + + case kTIFF_ShortType: + uns32 = this->GetUns16 ( thisTag->dataPtr ); + break; + + case kTIFF_LongType: + uns32 = this->GetUns32 ( thisTag->dataPtr ); + break; + + case kTIFF_SByteType: + int32 = (XMP_Int8) GetUns8 ( thisTag->dataPtr ); + uns32 = (XMP_Uns32) int32; // Make sure sign bits are properly set. + break; + + case kTIFF_SShortType: + int32 = (XMP_Int16) this->GetUns16 ( thisTag->dataPtr ); + uns32 = (XMP_Uns32) int32; // Make sure sign bits are properly set. + break; + + case kTIFF_SLongType: + int32 = (XMP_Int32) this->GetUns32 ( thisTag->dataPtr ); + uns32 = (XMP_Uns32) int32; // Make sure sign bits are properly set. + break; + + default: return false; + + } + + if ( data != 0 ) *data = uns32; + return true; + +} // TIFF_FileWriter::GetTag_Integer + +// ================================================================================================= +// TIFF_FileWriter::GetTag_Byte +// ============================ + +bool TIFF_FileWriter::GetTag_Byte ( XMP_Uns8 ifd, XMP_Uns16 id, XMP_Uns8* data ) const +{ + const InternalTagInfo* thisTag = this->FindTagInIFD ( ifd, id ); + if ( thisTag == 0 ) return false; + if ( (thisTag->type != kTIFF_ByteType) || (thisTag->dataLen != 1) ) return false; + + if ( data != 0 ) *data = *thisTag->dataPtr; + return true; + +} // TIFF_FileWriter::GetTag_Byte + +// ================================================================================================= +// TIFF_FileWriter::GetTag_SByte +// ============================= + +bool TIFF_FileWriter::GetTag_SByte ( XMP_Uns8 ifd, XMP_Uns16 id, XMP_Int8* data ) const +{ + const InternalTagInfo* thisTag = this->FindTagInIFD ( ifd, id ); + if ( thisTag == 0 ) return false; + if ( (thisTag->type != kTIFF_SByteType) || (thisTag->dataLen != 1) ) return false; + + if ( data != 0 ) *data = *thisTag->dataPtr; + return true; + +} // TIFF_FileWriter::GetTag_SByte + +// ================================================================================================= +// TIFF_FileWriter::GetTag_Short +// ============================= + +bool TIFF_FileWriter::GetTag_Short ( XMP_Uns8 ifd, XMP_Uns16 id, XMP_Uns16* data ) const +{ + const InternalTagInfo* thisTag = this->FindTagInIFD ( ifd, id ); + if ( thisTag == 0 ) return false; + if ( (thisTag->type != kTIFF_ShortType) || (thisTag->dataLen != 2) ) return false; + + if ( data != 0 ) *data = this->GetUns16 ( thisTag->dataPtr ); + return true; + +} // TIFF_FileWriter::GetTag_Short + +// ================================================================================================= +// TIFF_FileWriter::GetTag_SShort +// ============================== + +bool TIFF_FileWriter::GetTag_SShort ( XMP_Uns8 ifd, XMP_Uns16 id, XMP_Int16* data ) const +{ + const InternalTagInfo* thisTag = this->FindTagInIFD ( ifd, id ); + if ( thisTag == 0 ) return false; + if ( (thisTag->type != kTIFF_SShortType) || (thisTag->dataLen != 2) ) return false; + + if ( data != 0 ) *data = (XMP_Int16) this->GetUns16 ( thisTag->dataPtr ); + return true; + +} // TIFF_FileWriter::GetTag_SShort + +// ================================================================================================= +// TIFF_FileWriter::GetTag_Long +// ============================ + +bool TIFF_FileWriter::GetTag_Long ( XMP_Uns8 ifd, XMP_Uns16 id, XMP_Uns32* data ) const +{ + const InternalTagInfo* thisTag = this->FindTagInIFD ( ifd, id ); + if ( thisTag == 0 ) return false; + if ( (thisTag->type != kTIFF_LongType) || (thisTag->dataLen != 4) ) return false; + + if ( data != 0 ) *data = this->GetUns32 ( thisTag->dataPtr ); + return true; + +} // TIFF_FileWriter::GetTag_Long + +// ================================================================================================= +// TIFF_FileWriter::GetTag_SLong +// ============================= + +bool TIFF_FileWriter::GetTag_SLong ( XMP_Uns8 ifd, XMP_Uns16 id, XMP_Int32* data ) const +{ + const InternalTagInfo* thisTag = this->FindTagInIFD ( ifd, id ); + if ( thisTag == 0 ) return false; + if ( (thisTag->type != kTIFF_SLongType) || (thisTag->dataLen != 4) ) return false; + + if ( data != 0 ) *data = (XMP_Int32) this->GetUns32 ( thisTag->dataPtr ); + return true; + +} // TIFF_FileWriter::GetTag_SLong + +// ================================================================================================= +// TIFF_FileWriter::GetTag_Rational +// ================================ + +bool TIFF_FileWriter::GetTag_Rational ( XMP_Uns8 ifd, XMP_Uns16 id, Rational* data ) const +{ + const InternalTagInfo* thisTag = this->FindTagInIFD ( ifd, id ); + if ( (thisTag == 0) || (thisTag->dataPtr == 0) ) return false; + if ( (thisTag->type != kTIFF_RationalType) || (thisTag->dataLen != 8) ) return false; + + if ( data != 0 ) { + XMP_Uns32* dataPtr = (XMP_Uns32*)thisTag->dataPtr; + data->num = this->GetUns32 ( dataPtr ); + data->denom = this->GetUns32 ( dataPtr+1 ); + } + + return true; + +} // TIFF_FileWriter::GetTag_Rational + +// ================================================================================================= +// TIFF_FileWriter::GetTag_SRational +// ================================= + +bool TIFF_FileWriter::GetTag_SRational ( XMP_Uns8 ifd, XMP_Uns16 id, SRational* data ) const +{ + const InternalTagInfo* thisTag = this->FindTagInIFD ( ifd, id ); + if ( (thisTag == 0) || (thisTag->dataPtr == 0) ) return false; + if ( (thisTag->type != kTIFF_SRationalType) || (thisTag->dataLen != 8) ) return false; + + if ( data != 0 ) { + XMP_Uns32* dataPtr = (XMP_Uns32*)thisTag->dataPtr; + data->num = (XMP_Int32) this->GetUns32 ( dataPtr ); + data->denom = (XMP_Int32) this->GetUns32 ( dataPtr+1 ); + } + + return true; + +} // TIFF_FileWriter::GetTag_SRational + +// ================================================================================================= +// TIFF_FileWriter::GetTag_Float +// ============================= + +bool TIFF_FileWriter::GetTag_Float ( XMP_Uns8 ifd, XMP_Uns16 id, float* data ) const +{ + const InternalTagInfo* thisTag = this->FindTagInIFD ( ifd, id ); + if ( thisTag == 0 ) return false; + if ( (thisTag->type != kTIFF_FloatType) || (thisTag->dataLen != 4) ) return false; + + if ( data != 0 ) *data = this->GetFloat ( thisTag->dataPtr ); + return true; + +} // TIFF_FileWriter::GetTag_Float + +// ================================================================================================= +// TIFF_FileWriter::GetTag_Double +// ============================== + +bool TIFF_FileWriter::GetTag_Double ( XMP_Uns8 ifd, XMP_Uns16 id, double* data ) const +{ + const InternalTagInfo* thisTag = this->FindTagInIFD ( ifd, id ); + if ( (thisTag == 0) || (thisTag->dataPtr == 0) ) return false; + if ( (thisTag->type != kTIFF_DoubleType) || (thisTag->dataLen != 8) ) return false; + + if ( data != 0 ) *data = this->GetDouble ( thisTag->dataPtr ); + return true; + +} // TIFF_FileWriter::GetTag_Double + +// ================================================================================================= +// TIFF_FileWriter::GetTag_ASCII +// ============================= + +bool TIFF_FileWriter::GetTag_ASCII ( XMP_Uns8 ifd, XMP_Uns16 id, XMP_StringPtr* dataPtr, XMP_StringLen* dataLen ) const +{ + const InternalTagInfo* thisTag = this->FindTagInIFD ( ifd, id ); + if ( thisTag == 0 ) return false; + if ( (thisTag->dataLen > 4) && (thisTag->dataPtr == 0) ) return false; + if ( thisTag->type != kTIFF_ASCIIType ) return false; + + if ( dataPtr != 0 ) *dataPtr = (XMP_StringPtr)thisTag->dataPtr; + if ( dataLen != 0 ) *dataLen = thisTag->dataLen; + + return true; + +} // TIFF_FileWriter::GetTag_ASCII + +// ================================================================================================= +// TIFF_FileWriter::GetTag_EncodedString +// ===================================== + +bool TIFF_FileWriter::GetTag_EncodedString ( XMP_Uns8 ifd, XMP_Uns16 id, std::string* utf8Str ) const +{ + const InternalTagInfo* thisTag = this->FindTagInIFD ( ifd, id ); + if ( thisTag == 0 ) return false; + if ( thisTag->type != kTIFF_UndefinedType ) return false; + + if ( utf8Str == 0 ) return true; // Return true if the converted string is not wanted. + + bool ok = this->DecodeString ( thisTag->dataPtr, thisTag->dataLen, utf8Str ); + return ok; + +} // TIFF_FileWriter::GetTag_EncodedString + +// ================================================================================================= +// TIFF_FileWriter::SetTag_EncodedString +// ===================================== + +void TIFF_FileWriter::SetTag_EncodedString ( XMP_Uns8 ifd, XMP_Uns16 id, const std::string& utf8Str, XMP_Uns8 encoding ) +{ + std::string encodedStr; + + this->EncodeString ( utf8Str, encoding, &encodedStr ); + this->SetTag ( ifd, id, kTIFF_UndefinedType, (XMP_Uns32)encodedStr.size(), encodedStr.c_str() ); + +} // TIFF_FileWriter::SetTag_EncodedString + +// ================================================================================================= +// TIFF_FileWriter::IsLegacyChanged +// ================================ + +bool TIFF_FileWriter::IsLegacyChanged() +{ + + if ( ! this->changed ) return false; + if ( this->legacyDeleted ) return true; + + for ( int ifd = 0; ifd < kTIFF_KnownIFDCount; ++ifd ) { + + InternalIFDInfo & thisIFD = this->containedIFDs[ifd]; + if ( ! thisIFD.changed ) continue; + + InternalTagMap::iterator tagPos; + InternalTagMap::iterator tagEnd = thisIFD.tagMap.end(); + + for ( tagPos = thisIFD.tagMap.begin(); tagPos != tagEnd; ++tagPos ) { + InternalTagInfo & thisTag = tagPos->second; + if ( thisTag.changed && (thisTag.id != kTIFF_XMP) ) return true; + } + + } + + return false; // Can get here if the XMP tag is the only one changed. + +} // TIFF_FileWriter::IsLegacyChanged + +// ================================================================================================= +// TIFF_FileWriter::ParseMemoryStream +// ================================== + +void TIFF_FileWriter::ParseMemoryStream ( const void* data, XMP_Uns32 length, bool copyData /* = true */, bool isAlreadyLittle /*= false */ ) +{ + this->DeleteExistingInfo(); + this->memParsed = true; + if ( length == 0 ) return; + + // Allocate space for the full in-memory stream and copy it. + + if ( ! copyData ) { + XMP_Assert ( ! this->ownedStream ); + this->memStream = (XMP_Uns8*) data; + } else { + if ( length > 100*1024*1024 ) XMP_Throw ( "Outrageous length for memory-based TIFF", kXMPErr_BadTIFF ); + this->memStream = (XMP_Uns8*) malloc(length); + if ( this->memStream == 0 ) XMP_Throw ( "Out of memory", kXMPErr_NoMemory ); + memcpy ( this->memStream, data, length ); // AUDIT: Safe, malloc'ed length bytes above. + this->ownedStream = true; + } + + this->tiffLength = length; + XMP_Uns32 ifdLimit = this->tiffLength - 6; // An IFD must start before this offset. + + // Find and process the primary, thumbnail, Exif, GPS, and Interoperability IFDs. + + XMP_Uns32 primaryIFDOffset = this->CheckTIFFHeader ( this->memStream, length ); + + if ( primaryIFDOffset != 0 ) { + XMP_Uns32 tnailOffset = this->ProcessMemoryIFD ( primaryIFDOffset, kTIFF_PrimaryIFD ); + if ( tnailOffset != 0 ) { + if ( IsOffsetValid ( tnailOffset, 8, ifdLimit ) ) { // Remove a bad Thumbnail IFD Offset + ( void ) this->ProcessMemoryIFD ( tnailOffset, kTIFF_TNailIFD ); + } else { + XMP_Error error ( kXMPErr_BadTIFF, "Bad IFD offset" ); + this->NotifyClient (kXMPErrSev_Recoverable, error ); + this->DeleteTag ( kTIFF_PrimaryIFD, kTIFF_TNailIFD ); + } + } + } + + const InternalTagInfo* exifIFDTag = this->FindTagInIFD ( kTIFF_PrimaryIFD, kTIFF_ExifIFDPointer ); + if ( (exifIFDTag != 0) && (exifIFDTag->type == kTIFF_LongType) && (exifIFDTag->dataLen == 4) ) { + XMP_Uns32 exifOffset = this->GetUns32 ( exifIFDTag->dataPtr ); + (void) this->ProcessMemoryIFD ( exifOffset, kTIFF_ExifIFD ); + } + + const InternalTagInfo* gpsIFDTag = this->FindTagInIFD ( kTIFF_PrimaryIFD, kTIFF_GPSInfoIFDPointer ); + if ( (gpsIFDTag != 0) && (gpsIFDTag->type == kTIFF_LongType) && (gpsIFDTag->dataLen == 4) ) { + XMP_Uns32 gpsOffset = this->GetUns32 ( gpsIFDTag->dataPtr ); + if ( IsOffsetValid ( gpsOffset, 8, ifdLimit ) ) { // Remove a bad GPS IFD offset. + (void) this->ProcessMemoryIFD ( gpsOffset, kTIFF_GPSInfoIFD ); + } else { + XMP_Error error ( kXMPErr_BadTIFF, "Bad IFD offset" ); + this->NotifyClient (kXMPErrSev_Recoverable, error ); + this->DeleteTag ( kTIFF_PrimaryIFD, kTIFF_GPSInfoIFDPointer ); + } + } + + const InternalTagInfo* interopIFDTag = this->FindTagInIFD ( kTIFF_ExifIFD, kTIFF_InteroperabilityIFDPointer ); + if ( (interopIFDTag != 0) && (interopIFDTag->type == kTIFF_LongType) && (interopIFDTag->dataLen == 4) ) { + XMP_Uns32 interopOffset = this->GetUns32 ( interopIFDTag->dataPtr ); + if ( IsOffsetValid ( interopOffset, 8, ifdLimit ) ) { // Remove a bad Interoperability IFD offset. + (void) this->ProcessMemoryIFD ( interopOffset, kTIFF_InteropIFD ); + } else { + XMP_Error error ( kXMPErr_BadTIFF, "Bad IFD offset" ); + this->NotifyClient (kXMPErrSev_Recoverable, error ); + this->DeleteTag ( kTIFF_ExifIFD, kTIFF_InteroperabilityIFDPointer ); + } + } + + #if 0 + { + printf ( "\nExiting TIFF_FileWriter::ParseMemoryStream\n" ); + for ( int ifd = 0; ifd < kTIFF_KnownIFDCount; ++ifd ) { + InternalIFDInfo & thisIFD = this->containedIFDs[ifd]; + printf ( "\n IFD %d, count %d, mapped %d, offset %d (0x%X), next IFD %d (0x%X)\n", + ifd, thisIFD.origCount, thisIFD.tagMap.size(), + thisIFD.origDataOffset, thisIFD.origDataOffset, thisIFD.origNextIFD, thisIFD.origNextIFD ); + InternalTagMap::iterator tagPos; + InternalTagMap::iterator tagEnd = thisIFD.tagMap.end(); + for ( tagPos = thisIFD.tagMap.begin(); tagPos != tagEnd; ++tagPos ) { + InternalTagInfo & thisTag = tagPos->second; + printf ( " Tag %d, smallValue 0x%X, origDataLen %d, origDataOffset %d (0x%X)\n", + thisTag.id, thisTag.smallValue, thisTag.origDataLen, thisTag.origDataOffset, thisTag.origDataOffset ); + } + } + printf ( "\n" ); + } + #endif + +} // TIFF_FileWriter::ParseMemoryStream + +// ================================================================================================= +// TIFF_FileWriter::ProcessMemoryIFD +// ================================= + +XMP_Uns32 TIFF_FileWriter::ProcessMemoryIFD ( XMP_Uns32 ifdOffset, XMP_Uns8 ifd ) +{ + InternalIFDInfo& ifdInfo ( this->containedIFDs[ifd] ); + + if ( (ifdOffset < 8) || (ifdOffset > (this->tiffLength - kEmptyIFDLength)) ) { + XMP_Error error ( kXMPErr_BadTIFF, "Bad IFD offset" ); + this->NotifyClient ( kXMPErrSev_FileFatal, error ); + } + + XMP_Uns8* ifdPtr = this->memStream + ifdOffset; + XMP_Uns16 tagCount = this->GetUns16 ( ifdPtr ); + RawIFDEntry* ifdEntries = (RawIFDEntry*)(ifdPtr+2); + + if ( tagCount >= 0x8000 ) { + XMP_Error error ( kXMPErr_BadTIFF, "Outrageous IFD count" ); + this->NotifyClient ( kXMPErrSev_FileFatal, error ); + } + + if ( (XMP_Uns32)(2 + tagCount*12 + 4) > (this->tiffLength - ifdOffset) ) { + XMP_Error error ( kXMPErr_BadTIFF, "Out of bounds IFD" ); + this->NotifyClient ( kXMPErrSev_FileFatal, error ); + } + + ifdInfo.origIFDOffset = ifdOffset; + ifdInfo.origCount = tagCount; + + for ( size_t i = 0; i < tagCount; ++i ) { + + RawIFDEntry* rawTag = &ifdEntries[i]; + XMP_Uns16 tagType = this->GetUns16 ( &rawTag->type ); + if ( (tagType < kTIFF_ByteType) || (tagType > kTIFF_LastType) ) continue; // Bad type, skip this tag. + + XMP_Uns16 tagID = this->GetUns16 ( &rawTag->id ); + XMP_Uns32 tagCount = this->GetUns32 ( &rawTag->count ); + + InternalTagMap::value_type mapValue ( tagID, InternalTagInfo ( tagID, tagType, tagCount, kIsMemoryBased ) ); + InternalTagMap::iterator newPos = ifdInfo.tagMap.insert ( ifdInfo.tagMap.end(), mapValue ); + InternalTagInfo& mapTag = newPos->second; + + mapTag.dataLen = mapTag.origDataLen = mapTag.count * (XMP_Uns32)kTIFF_TypeSizes[mapTag.type]; +#if SUNOS_SPARC + mapTag.smallValue = IE.getUns32(&rawTag->dataOrOffset); +#else + mapTag.smallValue = GetUns32AsIs ( &rawTag->dataOrOffset ); // Keep the value or offset in stream byte ordering. +#endif //#if SUNOS_SPARC + + if ( mapTag.dataLen <= 4 ) { + mapTag.origDataOffset = ifdOffset + 2 + (12 * (XMP_Uns32)i) + 8; // Compute the data offset. + } else { + mapTag.origDataOffset = this->GetUns32 ( &rawTag->dataOrOffset ); // Extract the data offset. + // printf ( "FW_ProcessMemoryIFD tag %d large value @ %.8X\n", mapTag.id, mapTag.dataPtr ); + if ( (mapTag.origDataOffset < 8) || (mapTag.origDataOffset >= this->tiffLength) ) { + mapTag.count = mapTag.dataLen = mapTag.origDataLen = mapTag.smallValue = 0; + mapTag.origDataOffset = ifdOffset + 2 + (12 * (XMP_Uns32)i) + 8; // Make this bad tag look empty + } + if ( mapTag.dataLen > (this->tiffLength - mapTag.origDataOffset) ) { + mapTag.count = mapTag.dataLen = mapTag.origDataLen = mapTag.smallValue = 0; + mapTag.origDataOffset = ifdOffset + 2 + (12 * (XMP_Uns32)i) + 8; // Make this bad tag look empty + } + } + mapTag.dataPtr = this->memStream + mapTag.origDataOffset; + + } + + ifdPtr += (2 + tagCount*12); + ifdInfo.origNextIFD = this->GetUns32 ( ifdPtr ); +// The following code modifies a file in case it is invalid, we should keep this fix so that we can track this issue if we receive client bugs for this +#if 0 + if (ifdInfo.origNextIFD != 0) { + if ( (ifdInfo.origNextIFD < 8) || (ifdInfo.origNextIFD > (this->tiffLength - kEmptyIFDLength)) ) { + // Next IFD offset is invalid. Ignore it. + ifdInfo.origNextIFD = 0; + // Should we try to patch it? + ifdInfo.changed = true; + this->changed = true; + } + } +#endif + return ifdInfo.origNextIFD; + +} // TIFF_FileWriter::ProcessMemoryIFD + +// ================================================================================================= +// TIFF_FileWriter::ParseFileStream +// ================================ +// +// The buffered I/O model is worth the logic complexity - as opposed to a simple seek/read for each +// part of the TIFF stream. The vast majority of real-world TIFFs have the primary IFD, Exif IFD, +// and all of their interesting tag values within the first 64K of the file. Well, at least before +// we get around to our edit-by-append approach. + +void TIFF_FileWriter::ParseFileStream ( XMP_IO* fileRef ) +{ + + this->DeleteExistingInfo(); + this->fileParsed = true; + this->tiffLength = (XMP_Uns32) fileRef->Length(); + if ( this->tiffLength < 8 ) return; // Ignore empty or impossibly short. + fileRef->Rewind ( ); + + XMP_Uns32 ifdLimit = this->tiffLength - 6; // An IFD must start before this offset. + + // Find and process the primary, Exif, GPS, and Interoperability IFDs. + + XMP_Uns8 tiffHeader [8]; + fileRef->ReadAll ( tiffHeader, sizeof(tiffHeader) ); + XMP_Uns32 primaryIFDOffset = this->CheckTIFFHeader ( tiffHeader, this->tiffLength ); + + if ( primaryIFDOffset == 0 ) { + return; + } else { + XMP_Uns32 tnailOffset = this->ProcessFileIFD ( kTIFF_PrimaryIFD, primaryIFDOffset, fileRef ); + if ( tnailOffset != 0 ) { + if ( IsOffsetValid ( tnailOffset, 8, ifdLimit ) ) { + ( void ) this->ProcessFileIFD ( kTIFF_TNailIFD, tnailOffset, fileRef ); + } else { + XMP_Error error ( kXMPErr_BadTIFF, "Bad IFD offset" ); + this->NotifyClient ( kXMPErrSev_Recoverable, error ); + this->DeleteTag( kTIFF_PrimaryIFD, kTIFF_TNailIFD ); + } + } + } + + const InternalTagInfo* exifIFDTag = this->FindTagInIFD ( kTIFF_PrimaryIFD, kTIFF_ExifIFDPointer ); + if ( ( exifIFDTag != 0 ) && ( ( exifIFDTag->type == kTIFF_LongType || exifIFDTag->type == kTIFF_IFDType ) && ( exifIFDTag->count == 1 ) ) ) { + XMP_Uns32 exifOffset = this->GetUns32 ( exifIFDTag->dataPtr ); + (void) this->ProcessFileIFD ( kTIFF_ExifIFD, exifOffset, fileRef ); + } + + const InternalTagInfo* gpsIFDTag = this->FindTagInIFD ( kTIFF_PrimaryIFD, kTIFF_GPSInfoIFDPointer ); + if ( ( gpsIFDTag != 0 ) && ( ( gpsIFDTag->type == kTIFF_LongType || gpsIFDTag->type == kTIFF_IFDType ) && ( gpsIFDTag->count == 1 ) ) ) { + XMP_Uns32 gpsOffset = this->GetUns32 ( gpsIFDTag->dataPtr ); + if ( IsOffsetValid (gpsOffset, 8, ifdLimit ) ) { // Remove a bad GPS IFD offset. + (void) this->ProcessFileIFD ( kTIFF_GPSInfoIFD, gpsOffset, fileRef ); + } else { + XMP_Error error ( kXMPErr_BadTIFF, "Bad IFD offset" ); + this->NotifyClient ( kXMPErrSev_Recoverable, error ); + this->DeleteTag ( kTIFF_PrimaryIFD, kTIFF_GPSInfoIFDPointer ); + } + } + + const InternalTagInfo* interopIFDTag = this->FindTagInIFD ( kTIFF_ExifIFD, kTIFF_InteroperabilityIFDPointer ); + if ( ( interopIFDTag != 0 ) && ( ( interopIFDTag->type == kTIFF_LongType || interopIFDTag->type == kTIFF_IFDType ) && ( interopIFDTag->dataLen == 4 ) ) ) { + XMP_Uns32 interopOffset = this->GetUns32 ( interopIFDTag->dataPtr ); + if ( IsOffsetValid (interopOffset, 8, ifdLimit ) ) { // Remove a bad Interoperability IFD offset. + (void) this->ProcessFileIFD ( kTIFF_InteropIFD, interopOffset, fileRef ); + } else { + XMP_Error error ( kXMPErr_BadTIFF, "Bad IFD offset" ); + this->NotifyClient ( kXMPErrSev_Recoverable, error ); + this->DeleteTag ( kTIFF_ExifIFD, kTIFF_InteroperabilityIFDPointer ); + } + } + + #if 0 + { + printf ( "\nExiting TIFF_FileWriter::ParseFileStream\n" ); + for ( int ifd = 0; ifd < kTIFF_KnownIFDCount; ++ifd ) { + InternalIFDInfo & thisIFD = this->containedIFDs[ifd]; + printf ( "\n IFD %d, count %d, mapped %d, offset %d (0x%X), next IFD %d (0x%X)\n", + ifd, thisIFD.origCount, thisIFD.tagMap.size(), + thisIFD.origDataOffset, thisIFD.origDataOffset, thisIFD.origNextIFD, thisIFD.origNextIFD ); + InternalTagMap::iterator tagPos; + InternalTagMap::iterator tagEnd = thisIFD.tagMap.end(); + for ( tagPos = thisIFD.tagMap.begin(); tagPos != tagEnd; ++tagPos ) { + InternalTagInfo & thisTag = tagPos->second; + printf ( " Tag %d, smallValue 0x%X, origDataLen %d, origDataOffset %d (0x%X)\n", + thisTag.id, thisTag.smallValue, thisTag.origDataLen, thisTag.origDataOffset, thisTag.origDataOffset ); + } + } + printf ( "\n" ); + } + #endif + +} // TIFF_FileWriter::ParseFileStream + +// ================================================================================================= +// TIFF_FileWriter::ProcessFileIFD +// =============================== +// +// Each IFD has a UInt16 count of IFD entries, a sequence of 12 byte IFD entries, then a UInt32 +// offset to the next IFD. The integer byte order is determined by the II or MM at the TIFF start. + +XMP_Uns32 TIFF_FileWriter::ProcessFileIFD ( XMP_Uns8 ifd, XMP_Uns32 ifdOffset, XMP_IO* fileRef ) +{ + static const size_t ifdBufferSize = 12*65536; // Enough for the largest possible IFD. + std::vector ifdBuffer(ifdBufferSize); + XMP_Uns8 intBuffer [4]; // For the IFD count and offset to next IFD. + + InternalIFDInfo& ifdInfo ( this->containedIFDs[ifd] ); + + if ( (ifdOffset < 8) || (ifdOffset > (this->tiffLength - kEmptyIFDLength)) ) { + XMP_Throw ( "Bad IFD offset", kXMPErr_BadTIFF ); + } + + fileRef->Seek ( ifdOffset, kXMP_SeekFromStart ); + if ( ! XIO::CheckFileSpace ( fileRef, 2 ) ) return 0; // Bail for a truncated file. + fileRef->ReadAll ( intBuffer, 2 ); + + XMP_Uns16 tagCount = this->GetUns16 ( intBuffer ); + if ( tagCount >= 0x8000 ) return 0; // Maybe wrong byte order. + if ( ! XIO::CheckFileSpace ( fileRef, 12*tagCount ) ) return 0; // Bail for a truncated file. + fileRef->ReadAll ( &ifdBuffer[0], 12*tagCount ); + + if ( ! XIO::CheckFileSpace ( fileRef, 4 ) ) { + ifdInfo.origNextIFD = 0; // Tolerate a trncated file, do the remaining processing. + } else { + fileRef->ReadAll ( intBuffer, 4 ); + ifdInfo.origNextIFD = this->GetUns32 ( intBuffer ); + } + + ifdInfo.origIFDOffset = ifdOffset; + ifdInfo.origCount = tagCount; + + // --------------------------------------------------------------------------------------------- + // First create all of the IFD map entries, capturing short values, and get the next IFD offset. + // We're using a std::map for storage, it automatically eliminates duplicates and provides + // sorted output. Plus the "map[key] = value" assignment conveniently keeps the last encountered + // value, following Photoshop's behavior. + + XMP_Uns8* ifdPtr = &ifdBuffer[0]; // Move to the first IFD entry. + + for ( XMP_Uns16 i = 0; i < tagCount; ++i, ifdPtr += 12 ) { + + RawIFDEntry* rawTag = (RawIFDEntry*)ifdPtr; + XMP_Uns16 tagType = this->GetUns16 ( &rawTag->type ); + if ( (tagType < kTIFF_ByteType) || (tagType > kTIFF_LastType) ) continue; // Bad type, skip this tag. + + XMP_Uns16 tagID = this->GetUns16 ( &rawTag->id ); + XMP_Uns32 tagCount = this->GetUns32 ( &rawTag->count ); + + InternalTagMap::value_type mapValue ( tagID, InternalTagInfo ( tagID, tagType, tagCount, kIsFileBased ) ); + InternalTagMap::iterator newPos = ifdInfo.tagMap.insert ( ifdInfo.tagMap.end(), mapValue ); + InternalTagInfo& mapTag = newPos->second; + + mapTag.dataLen = mapTag.origDataLen = mapTag.count * (XMP_Uns32)kTIFF_TypeSizes[mapTag.type]; + mapTag.smallValue = GetUns32AsIs ( &rawTag->dataOrOffset ); // Keep the value or offset in stream byte ordering. + + if ( mapTag.dataLen <= 4 ) { + mapTag.dataPtr = (XMP_Uns8*) &mapTag.smallValue; + mapTag.origDataOffset = ifdOffset + 2 + (12 * i) + 8; // Compute the data offset. + } else { + mapTag.origDataOffset = this->GetUns32 ( &rawTag->dataOrOffset ); // Extract the data offset. + if ( (mapTag.origDataOffset < 8) || (mapTag.origDataOffset >= this->tiffLength) ) { + mapTag.dataPtr = (XMP_Uns8*) &mapTag.smallValue; // Make this bad tag look empty. + mapTag.origDataOffset = ifdOffset + 2 + (12 * i) + 8; + mapTag.count = mapTag.dataLen = mapTag.origDataLen = mapTag.smallValue = 0; + } + if ( mapTag.dataLen > (this->tiffLength - mapTag.origDataOffset) ) { + mapTag.dataPtr = (XMP_Uns8*) &mapTag.smallValue; // Make this bad tag look empty. + mapTag.origDataOffset = ifdOffset + 2 + (12 * i) + 8; + mapTag.count = mapTag.dataLen = mapTag.origDataLen = mapTag.smallValue = 0; + } + } + + } + + // ------------------------------------------------------------------------ + // Go back over the tag map and extract the data for large recognized tags. + + InternalTagMap::iterator tagPos = ifdInfo.tagMap.begin(); + InternalTagMap::iterator tagEnd = ifdInfo.tagMap.end(); + + const XMP_Uns16* knownTagPtr = sKnownTags[ifd]; // Points into the ordered recognized tag list. + + for ( ; tagPos != tagEnd; ++tagPos ) { + + InternalTagInfo* currTag = &tagPos->second; + + if ( currTag->dataLen <= 4 ) continue; // Short values are already in the smallValue field. + + while ( *knownTagPtr < currTag->id ) ++knownTagPtr; + if ( *knownTagPtr != currTag->id ) continue; // Skip unrecognized tags. + + fileRef->Seek ( currTag->origDataOffset, kXMP_SeekFromStart ); + currTag->dataPtr = (XMP_Uns8*) malloc ( currTag->dataLen ); + if ( currTag->dataPtr == 0 ) XMP_Throw ( "No data block", kXMPErr_NoMemory ); + fileRef->ReadAll ( currTag->dataPtr, currTag->dataLen ); + + } + + // Done, return the next IFD offset. + + return ifdInfo.origNextIFD; + +} // TIFF_FileWriter::ProcessFileIFD + +// ================================================================================================= +// TIFF_FileWriter::IntegrateFromPShop6 +// ==================================== +// +// See comments for ProcessPShop6IFD. + +void TIFF_FileWriter::IntegrateFromPShop6 ( const void * buriedPtr, size_t buriedLen ) +{ + TIFF_MemoryReader buriedExif; + buriedExif.ParseMemoryStream ( buriedPtr, (XMP_Uns32) buriedLen ); + + this->ProcessPShop6IFD ( buriedExif, kTIFF_PrimaryIFD ); + this->ProcessPShop6IFD ( buriedExif, kTIFF_ExifIFD ); + this->ProcessPShop6IFD ( buriedExif, kTIFF_GPSInfoIFD ); + +} // TIFF_FileWriter::IntegrateFromPShop6 + +// ================================================================================================= +// TIFF_FileWriter::CopyTagToMasterIFD +// =================================== +// +// Create a new master IFD entry from a buried Photoshop 6 IFD entry. Don't try to get clever with +// large values, just create a new copy. This preserves a clean separation between the memory-based +// and file-based TIFF processing. + +void* TIFF_FileWriter::CopyTagToMasterIFD ( const TagInfo & ps6Tag, InternalIFDInfo * masterIFD ) +{ + InternalTagMap::value_type mapValue ( ps6Tag.id, InternalTagInfo ( ps6Tag.id, ps6Tag.type, ps6Tag.count, this->fileParsed ) ); + InternalTagMap::iterator newPos = masterIFD->tagMap.insert ( masterIFD->tagMap.end(), mapValue ); + InternalTagInfo& newTag = newPos->second; + + newTag.dataLen = ps6Tag.dataLen; + + if ( newTag.dataLen <= 4 ) { + newTag.dataPtr = (XMP_Uns8*) &newTag.smallValue; + newTag.smallValue = *((XMP_Uns32*)ps6Tag.dataPtr); + } else { + newTag.dataPtr = (XMP_Uns8*) malloc ( newTag.dataLen ); + if ( newTag.dataPtr == 0 ) XMP_Throw ( "Out of memory", kXMPErr_NoMemory ); + memcpy ( newTag.dataPtr, ps6Tag.dataPtr, newTag.dataLen ); // AUDIT: Safe, malloc'ed dataLen bytes above. + } + + newTag.changed = true; // ! See comments with ProcessPShop6IFD. + XMP_Assert ( (newTag.origDataLen == 0) && (newTag.origDataOffset == 0) ); + + masterIFD->changed = true; + + return newPos->second.dataPtr; // ! Return the address within the map entry for small values. + +} // TIFF_FileWriter::CopyTagToMasterIFD + +// ================================================================================================= +// FlipCFATable +// ============ +// +// The CFA pattern table is trivial, a pair of short counts followed by n*m bytes. + +static bool FlipCFATable ( void* voidPtr, XMP_Uns32 tagLen, GetUns16_Proc GetUns16 ) +{ + if ( tagLen < 4 ) return false; + + XMP_Uns16* u16Ptr = (XMP_Uns16*)voidPtr; + + Flip2 ( &u16Ptr[0] ); // Flip the counts to match the master TIFF. + Flip2 ( &u16Ptr[1] ); + + XMP_Uns16 columns = GetUns16 ( &u16Ptr[0] ); // Fetch using the master TIFF's routine. + XMP_Uns16 rows = GetUns16 ( &u16Ptr[1] ); + + if ( tagLen != (XMP_Uns32)(4 + columns*rows) ) return false; + + return true; + +} // FlipCFATable + +// ================================================================================================= +// FlipDSDTable +// ============ +// +// The device settings description table is trivial, a pair of short counts followed by UTF-16 +// strings. So the whole value should be flipped as a sequence of 16 bit items. + +// ! The Exif 2.2 description is a bit garbled. It might be wrong. It would be nice to have a real example. + +static bool FlipDSDTable ( void* voidPtr, XMP_Uns32 tagLen, GetUns16_Proc GetUns16 ) +{ + if ( tagLen < 4 ) return false; + + XMP_Uns16* u16Ptr = (XMP_Uns16*)voidPtr; + for ( size_t i = tagLen/2; i > 0; --i, ++u16Ptr ) Flip2 ( u16Ptr ); + + return true; + +} // FlipDSDTable + +// ================================================================================================= +// FlipOECFSFRTable +// ================ +// +// The OECF and SFR tables have the same layout: +// 2 short counts, columns and rows +// c ASCII strings, null terminated, column names +// c*r rationals + +static bool FlipOECFSFRTable ( void* voidPtr, XMP_Uns32 tagLen, GetUns16_Proc GetUns16 ) +{ + XMP_Uns16* u16Ptr = (XMP_Uns16*)voidPtr; + + Flip2 ( &u16Ptr[0] ); // Flip the data to match the master TIFF. + Flip2 ( &u16Ptr[1] ); + + XMP_Uns16 columns = GetUns16 ( &u16Ptr[0] ); // Fetch using the master TIFF's routine. + XMP_Uns16 rows = GetUns16 ( &u16Ptr[1] ); + + XMP_Uns32 minLen = 4 + columns + (8 * columns * rows); // Minimum legit tag size. + if ( tagLen < minLen ) return false; + + // Compute the start of the rationals from the end of value. No need to walk through the names. + XMP_Uns32* u32Ptr = (XMP_Uns32*) ((XMP_Uns8*)voidPtr + tagLen - (8 * columns * rows)); + + for ( size_t i = 2*columns*rows; i > 0; --i, ++u32Ptr ) Flip4 ( u32Ptr ); + + return true; + +} // FlipOECFSFRTable + +// ================================================================================================= +// TIFF_FileWriter::ProcessPShop6IFD +// ================================= +// +// Photoshop 6 wrote wacky TIFF files that have much of the Exif metadata buried inside of image +// resource 1058, which is itself within tag 34377 in the 0th IFD. This routine moves the buried +// tags up to the parent file. Existing tags are not replaced. +// +// While it is tempting to try to directly use the TIFF_MemoryReader's tweaked IFD info, making that +// visible would compromise implementation separation. Better to pay the modest runtime cost of +// using the official GetIFD method, letting it build the map. +// +// The tags that get moved are marked as being changed, as is the IFD they are moved into, but the +// overall TIFF_FileWriter object is not. We don't want this integration on its own to force a file +// update, but a file update should include these changes. + +// ! Be careful to not move tags that are the nasty Exif explicit offsets, e.g. the Exif or GPS IFD +// ! "pointers". These are tags with a LONG type and count of 1, whose value is an offset into the +// ! buried TIFF stream. We can't reliably plant that offset into the outer IFD structure. + +// ! To make things even more fun, the buried Exif might not have the same endianness as the outer! + +void TIFF_FileWriter::ProcessPShop6IFD ( const TIFF_MemoryReader& buriedExif, XMP_Uns8 ifd ) +{ + bool ok, found; + TagInfoMap ps6IFD; + + found = buriedExif.GetIFD ( ifd, &ps6IFD ); + if ( ! found ) return; + + bool needsFlipping = (this->bigEndian != buriedExif.IsBigEndian()); + + InternalIFDInfo* masterIFD = &this->containedIFDs[ifd]; + + TagInfoMap::const_iterator ps6Pos = ps6IFD.begin(); + TagInfoMap::const_iterator ps6End = ps6IFD.end(); + + for ( ; ps6Pos != ps6End; ++ps6Pos ) { + + // Copy buried tags to the master IFD if they don't already exist there. + + const TagInfo& ps6Tag = ps6Pos->second; + + if ( this->FindTagInIFD ( ifd, ps6Tag.id ) != 0 ) continue; // Keep existing master tags. + if ( needsFlipping && (ps6Tag.id == 37500) ) continue; // Don't copy an unflipped MakerNote. + if ( (ps6Tag.id == kTIFF_ExifIFDPointer) || // Skip the tags that are explicit offsets. + (ps6Tag.id == kTIFF_GPSInfoIFDPointer) || + (ps6Tag.id == kTIFF_JPEGInterchangeFormat) || + (ps6Tag.id == kTIFF_InteroperabilityIFDPointer) ) continue; + + void* voidPtr = this->CopyTagToMasterIFD ( ps6Tag, masterIFD ); + + if ( needsFlipping ) { + switch ( ps6Tag.type ) { + + case kTIFF_ByteType: + case kTIFF_SByteType: + case kTIFF_ASCIIType: + // Nothing more to do. + break; + + case kTIFF_ShortType: + case kTIFF_SShortType: + { + XMP_Uns16* u16Ptr = (XMP_Uns16*)voidPtr; + for ( size_t i = ps6Tag.count; i > 0; --i, ++u16Ptr ) Flip2 ( u16Ptr ); + } + break; + + case kTIFF_LongType: + case kTIFF_SLongType: + case kTIFF_FloatType: + { + XMP_Uns32* u32Ptr = (XMP_Uns32*)voidPtr; + for ( size_t i = ps6Tag.count; i > 0; --i, ++u32Ptr ) Flip4 ( u32Ptr ); + } + break; + + case kTIFF_RationalType: + case kTIFF_SRationalType: + { + XMP_Uns32* ratPtr = (XMP_Uns32*)voidPtr; + for ( size_t i = (2 * ps6Tag.count); i > 0; --i, ++ratPtr ) Flip4 ( ratPtr ); + } + break; + + case kTIFF_DoubleType: + { + XMP_Uns64* u64Ptr = (XMP_Uns64*)voidPtr; + for ( size_t i = ps6Tag.count; i > 0; --i, ++u64Ptr ) Flip8 ( u64Ptr ); + } + break; + + case kTIFF_UndefinedType: + // Fix up the few kinds of special tables that Exif 2.2 defines. + ok = true; // Keep everything that isn't a special table. + if ( ps6Tag.id == kTIFF_CFAPattern ) { + ok = FlipCFATable ( voidPtr, ps6Tag.dataLen, this->GetUns16 ); + } else if ( ps6Tag.id == kTIFF_DeviceSettingDescription ) { + ok = FlipDSDTable ( voidPtr, ps6Tag.dataLen, this->GetUns16 ); + } else if ( (ps6Tag.id == kTIFF_OECF) || (ps6Tag.id == kTIFF_SpatialFrequencyResponse) ) { + ok = FlipOECFSFRTable ( voidPtr, ps6Tag.dataLen, this->GetUns16 ); + } + if ( ! ok ) this->DeleteTag ( ifd, ps6Tag.id ); + break; + + default: + // ? XMP_Throw ( "Unexpected tag type", kXMPErr_InternalFailure ); + this->DeleteTag ( ifd, ps6Tag.id ); + break; + + } + } + + } + +} // TIFF_FileWriter::ProcessPShop6IFD + +// ================================================================================================= +// TIFF_FileWriter::PreflightIFDLinkage +// ==================================== +// +// Preflight special cases for the linkage between IFDs. Three of the IFDs are found through an +// explicit tag, the Exif, GPS, and Interop IFDs. The presence or absence of those IFDs affects the +// presence or absence of the linkage tag, which can affect the IFD containing the linkage tag. The +// thumbnail IFD is chained from the primary IFD, so if the thumbnail IFD is present we make sure +// that the primary IFD isn't empty. + +void TIFF_FileWriter::PreflightIFDLinkage() +{ + + // Do the tag-linked IFDs bottom up, Interop then GPS then Exif. + + if ( this->containedIFDs[kTIFF_InteropIFD].tagMap.empty() ) { + this->DeleteTag ( kTIFF_ExifIFD, kTIFF_InteroperabilityIFDPointer ); + } else if ( ! this->GetTag ( kTIFF_ExifIFD, kTIFF_InteroperabilityIFDPointer, 0 ) ) { + this->SetTag_Long ( kTIFF_ExifIFD, kTIFF_InteroperabilityIFDPointer, 0xABADABAD ); + } + + if ( this->containedIFDs[kTIFF_GPSInfoIFD].tagMap.empty() ) { + this->DeleteTag ( kTIFF_PrimaryIFD, kTIFF_GPSInfoIFDPointer ); + } else if ( ! this->GetTag ( kTIFF_PrimaryIFD, kTIFF_GPSInfoIFDPointer, 0 ) ) { + this->SetTag_Long ( kTIFF_PrimaryIFD, kTIFF_GPSInfoIFDPointer, 0xABADABAD ); + } + + if ( this->containedIFDs[kTIFF_ExifIFD].tagMap.empty() ) { + this->DeleteTag ( kTIFF_PrimaryIFD, kTIFF_ExifIFDPointer ); + } else if ( ! this->GetTag ( kTIFF_PrimaryIFD, kTIFF_ExifIFDPointer, 0 ) ) { + this->SetTag_Long ( kTIFF_PrimaryIFD, kTIFF_ExifIFDPointer, 0xABADABAD ); + } + + // Make sure that the primary IFD is not empty if the thumbnail IFD is not empty. + + if ( this->containedIFDs[kTIFF_PrimaryIFD].tagMap.empty() && + (! this->containedIFDs[kTIFF_TNailIFD].tagMap.empty()) ) { + this->SetTag_Short ( kTIFF_PrimaryIFD, kTIFF_ResolutionUnit, 2 ); // Set Resolution unit to inches. + } + +} // TIFF_FileWriter::PreflightIFDLinkage + +// ================================================================================================= +// TIFF_FileWriter::DetermineVisibleLength +// ======================================= + +XMP_Uns32 TIFF_FileWriter::DetermineVisibleLength() +{ + XMP_Uns32 visibleLength = 8; // Start with the TIFF header size. + + for ( int ifd = 0; ifd < kTIFF_KnownIFDCount; ++ifd ) { + + InternalIFDInfo& ifdInfo ( this->containedIFDs[ifd] ); + size_t tagCount = ifdInfo.tagMap.size(); + if ( tagCount == 0 ) continue; + + visibleLength += (XMP_Uns32)( 6 + (12 * tagCount) ); + + InternalTagMap::iterator tagPos = ifdInfo.tagMap.begin(); + InternalTagMap::iterator tagEnd = ifdInfo.tagMap.end(); + + for ( ; tagPos != tagEnd; ++tagPos ) { + InternalTagInfo & currTag ( tagPos->second ); + if ( currTag.dataLen > 4 ) visibleLength += ((currTag.dataLen + 1) & 0xFFFFFFFE); // ! Round to even lengths. + } + + } + + return visibleLength; + +} // TIFF_FileWriter::DetermineVisibleLength + +// ================================================================================================= +// TIFF_FileWriter::DetermineAppendInfo +// ==================================== + +#ifndef Trace_DetermineAppendInfo + #define Trace_DetermineAppendInfo 0 +#endif + +// An IFD grows if it has more tags than before. +#define DoesIFDGrow(ifd) (this->containedIFDs[ifd].origCount < this->containedIFDs[ifd].tagMap.size()) + +XMP_Uns32 TIFF_FileWriter::DetermineAppendInfo ( XMP_Uns32 appendedOrigin, + bool appendedIFDs[kTIFF_KnownIFDCount], + XMP_Uns32 newIFDOffsets[kTIFF_KnownIFDCount], + bool appendAll /* = false */ ) +{ + XMP_Uns32 appendedLength = 0; + XMP_Assert ( (appendedOrigin & 1) == 0 ); // Make sure it is even. + + #if Trace_DetermineAppendInfo + { + printf ( "\nEntering TIFF_FileWriter::DetermineAppendInfo%s\n", (appendAll ? ", append all" : "") ); + for ( int ifd = 0; ifd < kTIFF_KnownIFDCount; ++ifd ) { + InternalIFDInfo & thisIFD = this->containedIFDs[ifd]; + printf ( "\n IFD %d, origCount %d, map.size %d, origIFDOffset %d (0x%X), origNextIFD %d (0x%X)", + ifd, thisIFD.origCount, thisIFD.tagMap.size(), + thisIFD.origIFDOffset, thisIFD.origIFDOffset, thisIFD.origNextIFD, thisIFD.origNextIFD ); + if ( thisIFD.changed ) printf ( ", changed" ); + if ( thisIFD.origCount < thisIFD.tagMap.size() ) printf ( ", should get appended" ); + printf ( "\n" ); + InternalTagMap::iterator tagPos; + InternalTagMap::iterator tagEnd = thisIFD.tagMap.end(); + for ( tagPos = thisIFD.tagMap.begin(); tagPos != tagEnd; ++tagPos ) { + InternalTagInfo & thisTag = tagPos->second; + printf ( " Tag %d, smallValue 0x%X, origDataLen %d, origDataOffset %d (0x%X)", + thisTag.id, thisTag.smallValue, thisTag.origDataLen, thisTag.origDataOffset, thisTag.origDataOffset ); + if ( thisTag.changed ) printf ( ", changed" ); + if ( (thisTag.dataLen > thisTag.origDataLen) && (thisTag.dataLen > 4) ) printf ( ", should get appended" ); + printf ( "\n" ); + } + } + printf ( "\n" ); + } + #endif + + // Determine which of the IFDs will be appended. If the Exif, GPS, or Interoperability IFDs are + // appended, set dummy values for their offsets in the "owning" IFD. This must be done first + // since this might cause the owning IFD to grow. + + if ( ! appendAll ) { + for ( int i = 0; i < kTIFF_KnownIFDCount ;++i ) appendedIFDs[i] = false; + } else { + for ( int i = 0; i < kTIFF_KnownIFDCount ;++i ) appendedIFDs[i] = (this->containedIFDs[i].tagMap.size() > 0); + } + + appendedIFDs[kTIFF_InteropIFD] |= DoesIFDGrow ( kTIFF_InteropIFD ); + if ( appendedIFDs[kTIFF_InteropIFD] ) { + this->SetTag_Long ( kTIFF_ExifIFD, kTIFF_InteroperabilityIFDPointer, 0xABADABAD ); + } + + appendedIFDs[kTIFF_GPSInfoIFD] |= DoesIFDGrow ( kTIFF_GPSInfoIFD ); + if ( appendedIFDs[kTIFF_GPSInfoIFD] ) { + this->SetTag_Long ( kTIFF_PrimaryIFD, kTIFF_GPSInfoIFDPointer, 0xABADABAD ); + } + + appendedIFDs[kTIFF_ExifIFD] |= DoesIFDGrow ( kTIFF_ExifIFD ); + if ( appendedIFDs[kTIFF_ExifIFD] ) { + this->SetTag_Long ( kTIFF_PrimaryIFD, kTIFF_ExifIFDPointer, 0xABADABAD ); + } + + appendedIFDs[kTIFF_PrimaryIFD] |= DoesIFDGrow ( kTIFF_PrimaryIFD ); + + // The appended data (if any) will be a sequence of an IFD followed by its large values. + // Determine the new offsets for the appended IFDs and tag values, and the total amount of + // appended stuff. The final IFD offset is set in newIFDOffsets for all IFDs, changed or not. + // This makes it easier to set the offsets to the primary and thumbnail IFDs when writing. + + for ( int ifd = 0; ifd < kTIFF_KnownIFDCount ;++ifd ) { + + InternalIFDInfo& ifdInfo ( this->containedIFDs[ifd] ); + size_t tagCount = ifdInfo.tagMap.size(); + + newIFDOffsets[ifd] = ifdInfo.origIFDOffset; // Make the new offset valid for unchanged IFDs. + + if ( ! (appendAll | ifdInfo.changed) ) continue; + if ( tagCount == 0 ) continue; + + if ( appendedIFDs[ifd] ) { + newIFDOffsets[ifd] = appendedOrigin + appendedLength; + appendedLength += (XMP_Uns32)( 6 + (12 * tagCount) ); + } + + InternalTagMap::iterator tagPos = ifdInfo.tagMap.begin(); + InternalTagMap::iterator tagEnd = ifdInfo.tagMap.end(); + + for ( ; tagPos != tagEnd; ++tagPos ) { + + InternalTagInfo & currTag ( tagPos->second ); + if ( (! (appendAll | currTag.changed)) || (currTag.dataLen <= 4) ) continue; + + if ( (currTag.dataLen <= currTag.origDataLen) && (! appendAll) ) { + this->PutUns32 ( currTag.origDataOffset, &currTag.smallValue ); // Reuse the old space. + } else { + this->PutUns32 ( (appendedOrigin + appendedLength), &currTag.smallValue ); // Set the appended offset. + appendedLength += ((currTag.dataLen + 1) & 0xFFFFFFFEUL); // Round to an even size. + } + + } + + } + + // If the Exif, GPS, or Interoperability IFDs get appended, update the tag values for their new offsets. + + if ( appendedIFDs[kTIFF_ExifIFD] ) { + this->SetTag_Long ( kTIFF_PrimaryIFD, kTIFF_ExifIFDPointer, newIFDOffsets[kTIFF_ExifIFD] ); + } + if ( appendedIFDs[kTIFF_GPSInfoIFD] ) { + this->SetTag_Long ( kTIFF_PrimaryIFD, kTIFF_GPSInfoIFDPointer, newIFDOffsets[kTIFF_GPSInfoIFD] ); + } + if ( appendedIFDs[kTIFF_InteropIFD] ) { + this->SetTag_Long ( kTIFF_ExifIFD, kTIFF_InteroperabilityIFDPointer, newIFDOffsets[kTIFF_InteropIFD] ); + } + + #if Trace_DetermineAppendInfo + { + printf ( "Exiting TIFF_FileWriter::DetermineAppendInfo\n" ); + for ( int ifd = 0; ifd < kTIFF_KnownIFDCount; ++ifd ) { + InternalIFDInfo & thisIFD = this->containedIFDs[ifd]; + printf ( "\n IFD %d, origCount %d, map.size %d, origIFDOffset %d (0x%X), origNextIFD %d (0x%X)", + ifd, thisIFD.origCount, thisIFD.tagMap.size(), + thisIFD.origIFDOffset, thisIFD.origIFDOffset, thisIFD.origNextIFD, thisIFD.origNextIFD ); + if ( thisIFD.changed ) printf ( ", changed" ); + if ( appendedIFDs[ifd] ) printf ( ", will be appended at %d (0x%X)", newIFDOffsets[ifd], newIFDOffsets[ifd] ); + printf ( "\n" ); + InternalTagMap::iterator tagPos; + InternalTagMap::iterator tagEnd = thisIFD.tagMap.end(); + for ( tagPos = thisIFD.tagMap.begin(); tagPos != tagEnd; ++tagPos ) { + InternalTagInfo & thisTag = tagPos->second; + printf ( " Tag %d, smallValue 0x%X, origDataLen %d, origDataOffset %d (0x%X)", + thisTag.id, thisTag.smallValue, thisTag.origDataLen, thisTag.origDataOffset, thisTag.origDataOffset ); + if ( thisTag.changed ) printf ( ", changed" ); + if ( (thisTag.dataLen > thisTag.origDataLen) && (thisTag.dataLen > 4) ) { + XMP_Uns32 newOffset = this->GetUns32 ( &thisTag.smallValue ); + printf ( ", will be appended at %d (0x%X)", newOffset, newOffset ); + } + printf ( "\n" ); + } + } + printf ( "\n" ); + } + #endif + + return appendedLength; + +} // TIFF_FileWriter::DetermineAppendInfo + +// ================================================================================================= +// TIFF_FileWriter::UpdateMemByAppend +// ================================== +// +// Normally we update TIFF in a conservative "by-append" manner. Changes are written in-place where +// they fit, anything requiring growth is appended to the end and the old space is abandoned. The +// end for memory-based TIFF is the end of the data block, the end for file-based TIFF is the end of +// the file. This update-by-append model has the advantage of not perturbing any hidden offsets, a +// common feature of proprietary MakerNotes. +// +// When doing the update-by-append we're only going to be modifying things that have changed. This +// means IFDs with changed, added, or deleted tags, and large values for changed or added tags. The +// IFDs and tag values are updated in-place if they fit, leaving holes in the stream if the new +// value is smaller than the old. + +// ** Someday we might want to use the FreeOffsets and FreeByteCounts tags to track free space. +// ** Probably not a huge win in practice though, and the TIFF spec says they are not recommended +// ** for general interchange use. + +void TIFF_FileWriter::UpdateMemByAppend ( XMP_Uns8** newStream_out, XMP_Uns32* newLength_out, + bool appendAll /* = false */, XMP_Uns32 extraSpace /* = 0 */ ) +{ + bool appendedIFDs[kTIFF_KnownIFDCount]; + XMP_Uns32 newIFDOffsets[kTIFF_KnownIFDCount]; + XMP_Uns32 appendedOrigin = ((this->tiffLength + 1) & 0xFFFFFFFEUL); // Start at an even offset. + XMP_Uns32 appendedLength = DetermineAppendInfo ( appendedOrigin, appendedIFDs, newIFDOffsets, appendAll ); + + // Allocate the new block of memory for the full stream. Copy the original stream. Write the + // modified IFDs and values. Finally rebuild the internal IFD info and tag map. + + XMP_Uns32 newLength = appendedOrigin + appendedLength; + XMP_Uns8* newStream = (XMP_Uns8*) malloc ( newLength + extraSpace ); + if ( newStream == 0 ) XMP_Throw ( "Out of memory", kXMPErr_NoMemory ); + + memcpy ( newStream, this->memStream, this->tiffLength ); // AUDIT: Safe, malloc'ed newLength bytes above. + if ( this->tiffLength < appendedOrigin ) { + XMP_Assert ( appendedOrigin == (this->tiffLength + 1) ); + newStream[this->tiffLength] = 0; // Clear the pad byte. + } + + try { // We might get exceptions from the next part and must delete newStream on the way out. + + // Write the modified IFDs and values. Rewrite the full IFD from scratch to make sure the + // tags are now unique and sorted. Copy large changed values to their appropriate location. + + XMP_Uns32 appendedOffset = appendedOrigin; + + for ( int ifd = 0; ifd < kTIFF_KnownIFDCount; ++ifd ) { + + InternalIFDInfo& ifdInfo ( this->containedIFDs[ifd] ); + size_t tagCount = ifdInfo.tagMap.size(); + + if ( ! (appendAll | ifdInfo.changed) ) continue; + if ( tagCount == 0 ) continue; + + XMP_Uns8* ifdPtr = newStream + newIFDOffsets[ifd]; + + if ( appendedIFDs[ifd] ) { + XMP_Assert ( newIFDOffsets[ifd] == appendedOffset ); + appendedOffset += (XMP_Uns32)( 6 + (12 * tagCount) ); + } + + this->PutUns16 ( (XMP_Uns16)tagCount, ifdPtr ); + ifdPtr += 2; + + InternalTagMap::iterator tagPos = ifdInfo.tagMap.begin(); + InternalTagMap::iterator tagEnd = ifdInfo.tagMap.end(); + + for ( ; tagPos != tagEnd; ++tagPos ) { + + InternalTagInfo & currTag ( tagPos->second ); + + this->PutUns16 ( currTag.id, ifdPtr ); + ifdPtr += 2; + this->PutUns16 ( currTag.type, ifdPtr ); + ifdPtr += 2; + this->PutUns32 ( currTag.count, ifdPtr ); + ifdPtr += 4; + + PutUns32AsIs ( currTag.smallValue, ifdPtr ); + + if ( (appendAll | currTag.changed) && (currTag.dataLen > 4) ) { + + XMP_Uns32 valueOffset = this->GetUns32 ( &currTag.smallValue ); + + if ( (currTag.dataLen <= currTag.origDataLen) && (! appendAll) ) { + XMP_Assert ( valueOffset == currTag.origDataOffset ); + } else { + XMP_Assert ( valueOffset == appendedOffset ); + appendedOffset += ((currTag.dataLen + 1) & 0xFFFFFFFEUL); + } + + XMP_Assert ( valueOffset <= newLength ); // Provably true, valueOffset is in the old span, newLength is the new bigger span. + if ( currTag.dataLen > (newLength - valueOffset) ) XMP_Throw ( "Buffer overrun", kXMPErr_InternalFailure ); + memcpy ( (newStream + valueOffset), currTag.dataPtr, currTag.dataLen ); // AUDIT: Protected by the above check. + if ( (currTag.dataLen & 1) != 0 ) newStream[valueOffset+currTag.dataLen] = 0; + + } + + ifdPtr += 4; + + } + + this->PutUns32 ( ifdInfo.origNextIFD, ifdPtr ); + ifdPtr += 4; + + } + + XMP_Assert ( appendedOffset == newLength ); + + // Back fill the offsets for the primary and thumnbail IFDs, if they are now appended. + + if ( appendedIFDs[kTIFF_PrimaryIFD] ) { + this->PutUns32 ( newIFDOffsets[kTIFF_PrimaryIFD], (newStream + 4) ); + } + + if ( appendedIFDs[kTIFF_TNailIFD] ) { + size_t primaryTagCount = this->containedIFDs[kTIFF_PrimaryIFD].tagMap.size(); + if ( primaryTagCount > 0 ) { + XMP_Uns32 tnailLinkOffset = newIFDOffsets[kTIFF_PrimaryIFD] + 2 + (12 * primaryTagCount); + this->PutUns32 ( newIFDOffsets[kTIFF_TNailIFD], (newStream + tnailLinkOffset) ); + } + } + + } catch ( ... ) { + + free ( newStream ); + throw; + + } + + *newStream_out = newStream; + *newLength_out = newLength; + +} // TIFF_FileWriter::UpdateMemByAppend + +// ================================================================================================= +// TIFF_FileWriter::UpdateMemByRewrite +// =================================== +// +// Normally we update TIFF in a conservative "by-append" manner. Changes are written in-place where +// they fit, anything requiring growth is appended to the end and the old space is abandoned. The +// end for memory-based TIFF is the end of the data block, the end for file-based TIFF is the end of +// the file. This update-by-append model has the advantage of not perturbing any hidden offsets, a +// common feature of proprietary MakerNotes. +// +// The condenseStream parameter can be used to rewrite the full stream instead of appending. This +// will discard any MakerNote tag and risks breaking offsets that are hidden. This can be necessary +// though to try to make the TIFF fit in a JPEG file. +// +// We don't do most of the actual rewrite here. We set things up so that UpdateMemByAppend can be +// called to append onto a bare TIFF header. Additional hidden offsets are then handled here. +// +// The hidden offsets for the Exif, GPS, and Interoperability IFDs (tags 34665, 34853, and 40965) +// are handled by the code in DetermineAppendInfo, which is called from UpdateMemByAppend, which is +// called from here. +// +// These other tags are recognized as being hidden offsets when composing a condensed stream: +// 273 - StripOffsets, lengths in tag 279 +// 288 - FreeOffsets, lengths in tag 289 +// 324 - TileOffsets, lengths in tag 325 +// 330 - SubIFDs, lengths within the IFDs (Plus subIFD values and possible chaining!) +// 513 - JPEGInterchangeFormat, length in tag 514 +// 519 - JPEGQTables, each table is 64 bytes +// 520 - JPEGDCTables, lengths ??? +// 521 - JPEGACTables, lengths ??? +// +// Some of these will handled and kept, some will be thrown out, some will cause the rewrite to fail. +// At this time only the JPEG thumbnail tags, 513 and 514, contain hidden data that is kept. The +// final stream layout is whatever UpdateMemByAppend does for the visible content, followed by the +// hidden offset data. The Exif, GPS, and Interoperability IFDs are visible to UpdateMemByAppend. + +// ! So far, a memory-based TIFF rewrite would only be done for the Exif portion of a JPEG file. +// ! In which case we're probably OK to handle JPEGInterchangeFormat (used for compressed thumbnails) +// ! and complain about any of the other hidden offset tags. + +// tag count type + +// 273 n short or long +// 279 n short or long +// 288 n long +// 289 n long +// 324 n long +// 325 n short or long + +// 330 n long + +// 513 1 long +// 514 1 long + +// 519 n long +// 520 n long +// 521 n long + +static XMP_Uns16 kNoGoTags[] = + { + kTIFF_StripOffsets, // 273 *** Should be handled? + kTIFF_StripByteCounts, // 279 *** Should be handled? + kTIFF_FreeOffsets, // 288 *** Should be handled? + kTIFF_FreeByteCounts, // 289 *** Should be handled? + kTIFF_TileOffsets, // 324 *** Should be handled? + kTIFF_TileByteCounts, // 325 *** Should be handled? + kTIFF_SubIFDs, // 330 *** Should be handled? + kTIFF_JPEGQTables, // 519 + kTIFF_JPEGDCTables, // 520 + kTIFF_JPEGACTables, // 521 + 0xFFFF // Must be last as a sentinel. + }; + +static XMP_Uns16 kBanishedTags[] = + { + kTIFF_MakerNote, // *** Should someday support MakerNoteSafety. + 0xFFFF // Must be last as a sentinel. + }; + +struct SimpleHiddenContentInfo { + XMP_Uns8 ifd; + XMP_Uns16 offsetTag, lengthTag; +}; + +struct SimpleHiddenContentLocations { + XMP_Uns32 length, oldOffset, newOffset; + SimpleHiddenContentLocations() : length(0), oldOffset(0), newOffset(0) {}; +}; + +enum { kSimpleHiddenContentCount = 1 }; + +static const SimpleHiddenContentInfo kSimpleHiddenContentInfo [kSimpleHiddenContentCount] = + { + { kTIFF_TNailIFD, kTIFF_JPEGInterchangeFormat, kTIFF_JPEGInterchangeFormatLength } + }; + +// ------------------------------------------------------------------------------------------------- + +void TIFF_FileWriter::UpdateMemByRewrite ( XMP_Uns8** newStream_out, XMP_Uns32* newLength_out ) +{ + const InternalTagInfo* tagInfo; + + // Check for tags that we don't tolerate because they have data we can't (or refuse to) find. + + for ( XMP_Uns8 ifd = 0; ifd < kTIFF_KnownIFDCount; ++ifd ) { + for ( int i = 0; kNoGoTags[i] != 0xFFFF; ++i ) { + tagInfo = this->FindTagInIFD ( ifd, kNoGoTags[i] ); + if ( tagInfo != 0 ) XMP_Throw ( "Tag not tolerated for TIFF rewrite", kXMPErr_Unimplemented ); + } + } + + // Delete unwanted tags. + + for ( XMP_Uns8 ifd = 0; ifd < kTIFF_KnownIFDCount; ++ifd ) { + for ( int i = 0; kBanishedTags[i] != 0xFFFF; ++i ) { + this->DeleteTag ( ifd, kBanishedTags[i] ); + } + } + + // Determine the offsets and additional size for the hidden offset content. Set the offset tags + // to the new offset so that UpdateMemByAppend writes the new offsets. + + XMP_Uns32 hiddenContentLength = 0; + XMP_Uns32 hiddenContentOrigin = this->DetermineVisibleLength(); + + SimpleHiddenContentLocations hiddenLocations [kSimpleHiddenContentCount]; + + for ( int i = 0; i < kSimpleHiddenContentCount; ++i ) { + + const SimpleHiddenContentInfo & hiddenInfo ( kSimpleHiddenContentInfo[i] ); + + bool haveLength = this->GetTag_Integer ( hiddenInfo.ifd, hiddenInfo.lengthTag, &hiddenLocations[i].length ); + bool haveOffset = this->GetTag_Integer ( hiddenInfo.ifd, hiddenInfo.offsetTag, &hiddenLocations[i].oldOffset ); + if ( haveLength != haveOffset ) XMP_Throw ( "Unpaired simple hidden content tag", kXMPErr_BadTIFF ); + if ( (! haveLength) || (hiddenLocations[i].length == 0) ) continue; + + hiddenLocations[i].newOffset = hiddenContentOrigin + hiddenContentLength; + this->SetTag_Long ( hiddenInfo.ifd, hiddenInfo.offsetTag, hiddenLocations[i].newOffset ); + hiddenContentLength += ((hiddenLocations[i].length + 1) & 0xFFFFFFFE); // ! Round up for even offsets. + + } + + // Save any old memory stream for the content behind hidden offsets. Setup a bare TIFF header. + + XMP_Uns8* oldStream = this->memStream; + bool ownedOldStream = this->ownedStream; + + XMP_Uns8 bareTIFF [8]; + if ( this->bigEndian ) { + bareTIFF[0] = 0x4D; bareTIFF[1] = 0x4D; bareTIFF[2] = 0x00; bareTIFF[3] = 0x2A; + } else { + bareTIFF[0] = 0x49; bareTIFF[1] = 0x49; bareTIFF[2] = 0x2A; bareTIFF[3] = 0x00; + } + *((XMP_Uns32*)&bareTIFF[4]) = 0; + + this->memStream = &bareTIFF[0]; + this->tiffLength = sizeof ( bareTIFF ); + this->ownedStream = false; + + // Call UpdateMemByAppend to write the new stream, telling it to append everything. + + this->UpdateMemByAppend ( newStream_out, newLength_out, true, hiddenContentLength ); + + // Copy the hidden content and update the output stream length; + + XMP_Assert ( *newLength_out == hiddenContentOrigin ); + *newLength_out += hiddenContentLength; + + for ( int i = 0; i < kSimpleHiddenContentCount; ++i ) { + + if ( hiddenLocations[i].length == 0 ) continue; + + XMP_Uns8* srcPtr = oldStream + hiddenLocations[i].oldOffset; + XMP_Uns8* destPtr = *newStream_out + hiddenLocations[i].newOffset; + memcpy ( destPtr, srcPtr, hiddenLocations[i].length ); // AUDIT: Safe copy, not user data, computed length. + + } + + // Delete the old stream if appropriate. + + if ( ownedOldStream ) delete ( oldStream ); + +} // TIFF_FileWriter::UpdateMemByRewrite + +// ================================================================================================= +// TIFF_FileWriter::UpdateMemoryStream +// =================================== +// +// Normally we update TIFF in a conservative "by-append" manner. Changes are written in-place where +// they fit, anything requiring growth is appended to the end and the old space is abandoned. The +// end for memory-based TIFF is the end of the data block, the end for file-based TIFF is the end of +// the file. This update-by-append model has the advantage of not perturbing any hidden offsets, a +// common feature of proprietary MakerNotes. +// +// The condenseStream parameter can be used to rewrite the full stream instead of appending. This +// will discard any MakerNote tags and risks breaking offsets that are hidden. This can be necessary +// though to try to make the TIFF fit in a JPEG file. + +XMP_Uns32 TIFF_FileWriter::UpdateMemoryStream ( void** dataPtr, bool condenseStream /* = false */ ) +{ + if ( this->fileParsed ) XMP_Throw ( "Not memory based", kXMPErr_EnforceFailure ); + + this->changed |= condenseStream; // Make sure a compaction request takes effect. + if ( ! this->changed ) { + if ( dataPtr != 0 ) *dataPtr = this->memStream; + return this->tiffLength; + } + + this->PreflightIFDLinkage(); + + bool nowEmpty = true; + for ( size_t i = 0; i < kTIFF_KnownIFDCount; ++i ) { + if ( ! this->containedIFDs[i].tagMap.empty() ) { + nowEmpty = false; + break; + } + } + + XMP_Uns8* newStream = 0; + XMP_Uns32 newLength = 0; + + if ( nowEmpty ) { + + this->DeleteExistingInfo(); // Prepare for an empty reparse. + + } else { + + if ( this->tiffLength == 0 ) { // ! An empty parse does set this->memParsed. + condenseStream = true; // Makes "conjured" TIFF take the full rewrite path. + } + + if ( condenseStream ) { + this->UpdateMemByRewrite ( &newStream, &newLength ); + } else { + this->UpdateMemByAppend ( &newStream, &newLength ); + } + + } + + // Parse the revised stream. This is the cleanest way to rebuild the tag map. + + this->ParseMemoryStream ( newStream, newLength, kDoNotCopyData ); + XMP_Assert ( this->tiffLength == newLength ); + this->ownedStream = (newLength > 0); // ! We really do own the new stream, if not empty. + + if ( dataPtr != 0 ) *dataPtr = this->memStream; + return newLength; + +} // TIFF_FileWriter::UpdateMemoryStream + +// ================================================================================================= +// TIFF_FileWriter::UpdateFileStream +// ================================= +// +// Updating a file stream is done in the same general manner as updating a memory stream, the intro +// comments for UpdateMemoryStream largely apply. The file update happens in 3 phases: +// 1. Determine which IFDs will be appended, and the new offsets for the appended IFDs and tags. +// 2. Do the in-place update for the IFDs and tag values that fit. +// 3. Append the IFDs and tag values that grow. +// +// The file being updated must match the file that was previously parsed. Offsets and lengths saved +// when parsing are used to decide if something can be updated in-place or must be appended. + +// *** The general linked structure of TIFF makes it very difficult to process the file in a single +// *** sequential pass. This implementation uses a simple seek/write model for the in-place updates. +// *** In the future we might want to consider creating a map of what gets updated, allowing use of +// *** a possibly more efficient buffered model. + +// ** Someday we might want to use the FreeOffsets and FreeByteCounts tags to track free space. +// ** Probably not a huge win in practice though, and the TIFF spec says they are not recommended +// ** for general interchange use. + +#ifndef Trace_UpdateFileStream + #define Trace_UpdateFileStream 0 +#endif + +void TIFF_FileWriter::UpdateFileStream ( XMP_IO* fileRef, XMP_ProgressTracker* progressTracker ) +{ + if ( this->memParsed ) XMP_Throw ( "Not file based", kXMPErr_EnforceFailure ); + if ( ! this->changed ) return; + + XMP_Int64 origDataLength = fileRef->Length(); + if ( (origDataLength >> 32) != 0 ) XMP_Throw ( "TIFF files can't exceed 4GB", kXMPErr_BadTIFF ); + + bool appendedIFDs[kTIFF_KnownIFDCount]; + XMP_Uns32 newIFDOffsets[kTIFF_KnownIFDCount]; + + #if Trace_UpdateFileStream + printf ( "\nStarting update of TIFF file stream\n" ); + #endif + + XMP_Uns32 appendedOrigin = (XMP_Uns32)origDataLength; + if ( (appendedOrigin & 1) != 0 ) { + ++appendedOrigin; // Start at an even offset. + fileRef->Seek ( 0, kXMP_SeekFromEnd ); + fileRef->Write ( "\0", 1 ); + } + + this->PreflightIFDLinkage(); + + XMP_Uns32 appendedLength = DetermineAppendInfo ( appendedOrigin, appendedIFDs, newIFDOffsets ); + if ( appendedLength > (0xFFFFFFFFUL - appendedOrigin) ) XMP_Throw ( "TIFF files can't exceed 4GB", kXMPErr_BadTIFF ); + + if ( progressTracker != 0 ) { + + float filesize=0; + + for ( int ifd = 0; ifd < kTIFF_KnownIFDCount; ++ifd ) { + + InternalIFDInfo & thisIFD = this->containedIFDs[ifd]; + if ( ! thisIFD.changed ) continue; + + filesize += (thisIFD.tagMap.size() * sizeof(RawIFDEntry) + 6); + + InternalTagMap::iterator tagPos; + InternalTagMap::iterator tagEnd = thisIFD.tagMap.end(); + + for ( tagPos = thisIFD.tagMap.begin(); tagPos != tagEnd; ++tagPos ) { + InternalTagInfo & thisTag = tagPos->second; + if ( (! thisTag.changed) || (thisTag.dataLen <= 4) ) continue; + filesize += (thisTag.dataLen) ; + } + } + + if ( appendedIFDs[kTIFF_PrimaryIFD] ) filesize += 4; + XMP_Assert ( progressTracker->WorkInProgress() ); + progressTracker->AddTotalWork ( filesize ); + + } + + // Do the in-place update for the IFDs and tag values that fit. This part does separate seeks + // and writes for the IFDs and values. Things to be updated can be anywhere in the file. + + // *** This might benefit from a map of the in-place updates. This would allow use of a possibly + // *** more efficient sequential I/O model. Could even incorporate the safe update file copying. + + for ( int ifd = 0; ifd < kTIFF_KnownIFDCount; ++ifd ) { + + InternalIFDInfo & thisIFD = this->containedIFDs[ifd]; + if ( ! thisIFD.changed ) continue; + + // In order to get a little bit of locality, write the IFD first then the changed tags that + // have large values and fit in-place. + + if ( ! appendedIFDs[ifd] ) { + #if Trace_UpdateFileStream + printf ( " Updating IFD %d in-place at offset %d (0x%X)\n", ifd, thisIFD.origIFDOffset, thisIFD.origIFDOffset ); + #endif + fileRef->Seek ( thisIFD.origIFDOffset, kXMP_SeekFromStart ); + this->WriteFileIFD ( fileRef, thisIFD ); + } + + InternalTagMap::iterator tagPos; + InternalTagMap::iterator tagEnd = thisIFD.tagMap.end(); + + for ( tagPos = thisIFD.tagMap.begin(); tagPos != tagEnd; ++tagPos ) { + InternalTagInfo & thisTag = tagPos->second; + if ( (! thisTag.changed) || (thisTag.dataLen <= 4) || (thisTag.dataLen > thisTag.origDataLen) ) continue; + #if Trace_UpdateFileStream + printf ( " Updating tag %d in IFD %d in-place at offset %d (0x%X)\n", thisTag.id, ifd, thisTag.origDataOffset, thisTag.origDataOffset ); + #endif + fileRef->Seek ( thisTag.origDataOffset, kXMP_SeekFromStart ); + fileRef->Write ( thisTag.dataPtr, thisTag.dataLen ); + } + + } + + // Append the IFDs and tag values that grow. + + XMP_Int64 fileEnd = fileRef->Seek ( 0, kXMP_SeekFromEnd ); + XMP_Assert ( fileEnd == appendedOrigin ); + + for ( int ifd = 0; ifd < kTIFF_KnownIFDCount; ++ifd ) { + + InternalIFDInfo & thisIFD = this->containedIFDs[ifd]; + if ( ! thisIFD.changed ) continue; + + if ( appendedIFDs[ifd] ) { + #if Trace_UpdateFileStream + printf ( " Updating IFD %d by append at offset %d (0x%X)\n", ifd, newIFDOffsets[ifd], newIFDOffsets[ifd] ); + #endif + XMP_Assert ( newIFDOffsets[ifd] == fileRef->Length() ); + this->WriteFileIFD ( fileRef, thisIFD ); + } + + InternalTagMap::iterator tagPos; + InternalTagMap::iterator tagEnd = thisIFD.tagMap.end(); + + for ( tagPos = thisIFD.tagMap.begin(); tagPos != tagEnd; ++tagPos ) { + InternalTagInfo & thisTag = tagPos->second; + if ( (! thisTag.changed) || (thisTag.dataLen <= 4) || (thisTag.dataLen <= thisTag.origDataLen) ) continue; + #if Trace_UpdateFileStream + XMP_Uns32 newOffset = this->GetUns32(&thisTag.origDataOffset); + printf ( " Updating tag %d in IFD %d by append at offset %d (0x%X)\n", thisTag.id, ifd, newOffset, newOffset ); + #endif + XMP_Assert ( this->GetUns32(&thisTag.smallValue) == fileRef->Length() ); + fileRef->Write ( thisTag.dataPtr, thisTag.dataLen ); + if ( (thisTag.dataLen & 1) != 0 ) fileRef->Write ( "\0", 1 ); + } + + } + + // Back-fill the offset for the primary IFD, if it is now appended. + + XMP_Uns32 newOffset; + + if ( appendedIFDs[kTIFF_PrimaryIFD] ) { + this->PutUns32 ( newIFDOffsets[kTIFF_PrimaryIFD], &newOffset ); + #if TraceUpdateFileStream + printf ( " Back-filling offset of primary IFD, pointing to %d (0x%X)\n", newOffset, newOffset ); + #endif + fileRef->Seek ( 4, kXMP_SeekFromStart ); + fileRef->Write ( &newOffset, 4 ); + } + + // Reset the changed flags and original length/offset values. This simulates a reparse of the + // updated file. + + for ( int ifd = 0; ifd < kTIFF_KnownIFDCount; ++ifd ) { + + InternalIFDInfo & thisIFD = this->containedIFDs[ifd]; + if ( ! thisIFD.changed ) continue; + + thisIFD.changed = false; + thisIFD.origCount = (XMP_Uns16)( thisIFD.tagMap.size() ); + thisIFD.origIFDOffset = newIFDOffsets[ifd]; + + InternalTagMap::iterator tagPos; + InternalTagMap::iterator tagEnd = thisIFD.tagMap.end(); + + for ( tagPos = thisIFD.tagMap.begin(); tagPos != tagEnd; ++tagPos ) { + InternalTagInfo & thisTag = tagPos->second; + if ( ! thisTag.changed ) continue; + thisTag.changed = false; + thisTag.origDataLen = thisTag.dataLen; + if ( thisTag.origDataLen > 4 ) thisTag.origDataOffset = this->GetUns32 ( &thisTag.smallValue ); + } + + } + + this->tiffLength = (XMP_Uns32) fileRef->Length(); + fileRef->Seek ( 0, kXMP_SeekFromEnd ); // Can't hurt. + + #if Trace_UpdateFileStream + printf ( "\nFinished update of TIFF file stream\n" ); + #endif + +} // TIFF_FileWriter::UpdateFileStream + +// ================================================================================================= +// TIFF_FileWriter::WriteFileIFD +// ============================= + +void TIFF_FileWriter::WriteFileIFD ( XMP_IO* fileRef, InternalIFDInfo & thisIFD ) +{ + XMP_Uns16 tagCount; + this->PutUns16 ( (XMP_Uns16)thisIFD.tagMap.size(), &tagCount ); + fileRef->Write ( &tagCount, 2 ); + + InternalTagMap::iterator tagPos; + InternalTagMap::iterator tagEnd = thisIFD.tagMap.end(); + + for ( tagPos = thisIFD.tagMap.begin(); tagPos != tagEnd; ++tagPos ) { + + InternalTagInfo & thisTag = tagPos->second; + RawIFDEntry ifdEntry; + + this->PutUns16 ( thisTag.id, &ifdEntry.id ); + this->PutUns16 ( thisTag.type, &ifdEntry.type ); + this->PutUns32 ( thisTag.count, &ifdEntry.count ); + ifdEntry.dataOrOffset = thisTag.smallValue; // ! Already in stream endianness. + + fileRef->Write ( &ifdEntry, sizeof(ifdEntry) ); + XMP_Assert ( sizeof(ifdEntry) == 12 ); + + } + + XMP_Uns32 nextIFD; + this->PutUns32 ( thisIFD.origNextIFD, &nextIFD ); + fileRef->Write ( &nextIFD, 4 ); + +} // TIFF_FileWriter::WriteFileIFD diff --git a/core/libs/dngwriter/extra/xmp_sdk/XMPFiles/source/FormatSupport/TIFF_MemoryReader.cpp b/core/libs/dngwriter/extra/xmp_sdk/XMPFiles/source/FormatSupport/TIFF_MemoryReader.cpp new file mode 100644 index 0000000000..31192c78ab --- /dev/null +++ b/core/libs/dngwriter/extra/xmp_sdk/XMPFiles/source/FormatSupport/TIFF_MemoryReader.cpp @@ -0,0 +1,736 @@ +// ================================================================================================= +// ADOBE SYSTEMS INCORPORATED +// Copyright 2006 Adobe Systems Incorporated +// All Rights Reserved +// +// NOTICE: Adobe permits you to use, modify, and distribute this file in accordance with the terms +// of the Adobe license agreement accompanying it. +// ================================================================================================= + +#include "public/include/XMP_Environment.h" // ! XMP_Environment.h must be the first included header. +#include "public/include/XMP_Const.h" + +#include "XMPFiles/source/FormatSupport/TIFF_Support.hpp" +#include "source/XIO.hpp" +#include "source/EndianUtils.hpp" + +// ================================================================================================= +/// \file TIFF_MemoryReader.cpp +/// \brief Implementation of the memory-based read-only TIFF_Manager. +/// +/// The read-only forms of TIFF_Manager are derived from TIFF_Reader. The GetTag methods are common +/// implementations in TIFF_Reader. The parsing code is different in the TIFF_MemoryReader and +/// TIFF_FileReader constructors. There are also separate destructors to release captured info. +/// +/// The read-only implementations use runtime data that is simple tweaks on the stored form. The +/// memory-based reader has one block of data for the whole TIFF stream. The file-based reader has +/// one for each IFD, plus one for the collected non-local data for each IFD. Otherwise the logic +/// is the same in both cases. +/// +/// The count for each IFD is extracted and a pointer set to the first entry in each IFD (serving as +/// a normal C array pointer). The IFD entries are tweaked as follows: +/// +/// \li The id and type fields are converted to native values. +/// \li The count field is converted to a native byte count. +/// \li If the data is not inline the offset is converted to a pointer. +/// +/// The tag values, whether inline or not, are not converted to native values. The values returned +/// from the GetTag methods are converted on the fly. The id, type, and count fields are easier to +/// convert because their types are fixed. They are used more, and more valuable to convert. +// ================================================================================================= + +// ================================================================================================= +// TIFF_MemoryReader::SortIFD +// ========================== +// +// Does a fairly simple minded insertion-like sort. This sort is not going to be optimal for edge +// cases like and IFD with lots of duplicates. + +// *** Might be better done using read and write pointers and two loops. The first loop moves out +// *** of order tags by a modified bubble sort, shifting the middle down one at a time in the loop. +// *** The first loop stops when a duplicate is hit. The second loop continues by moving the tail +// *** entries up to the appropriate slot. + +void TIFF_MemoryReader::SortIFD ( TweakedIFDInfo* thisIFD ) +{ + + XMP_Uns16 tagCount = thisIFD->count; + TweakedIFDEntry* ifdEntries = thisIFD->entries; + XMP_Uns16 prevTag = GetUns16AsIs ( &ifdEntries[0].id ); + + for ( size_t i = 1; i < tagCount; ++i ) { + + XMP_Uns16 thisTag = GetUns16AsIs ( &ifdEntries[i].id ); + + if ( thisTag > prevTag ) { + + // In proper order. + prevTag = thisTag; + + } else if ( thisTag == prevTag ) { + + // Duplicate tag, keep the 2nd copy, move the tail of the array up, prevTag is unchanged. + memcpy ( &ifdEntries[i-1], &ifdEntries[i], 12*(tagCount-i) ); // AUDIT: Safe, moving tail forward, i >= 1. + --tagCount; + --i; // ! Don't move forward in the array, we've moved the unseen part up. + + } else if ( thisTag < prevTag ) { + + // Out of order, move this tag up, prevTag is unchanged. Might still be a duplicate! + XMP_Int32 j; // ! Need a signed value. + for ( j = (XMP_Int32)i-1; j >= 0; --j ) { + if ( GetUns16AsIs(&ifdEntries[j].id) <= thisTag ) break; + } + + if ( (j >= 0) && (ifdEntries[j].id == thisTag) ) { + + // Out of order duplicate, move it to position j, move the tail of the array up. + ifdEntries[j] = ifdEntries[i]; + memcpy ( &ifdEntries[i], &ifdEntries[i+1], 12*(tagCount-(i+1)) ); // AUDIT: Safe, moving tail forward, i >= 1. + --tagCount; + --i; // ! Don't move forward in the array, we've moved the unseen part up. + + } else { + + // Move the out of order entry to position j+1, move the middle of the array down. + #if ! (SUNOS_SPARC || XMP_IOS_ARM) + TweakedIFDEntry temp = ifdEntries[i]; + ++j; // ! So the insertion index becomes j. + memcpy ( &ifdEntries[j+1], &ifdEntries[j], 12*(i-j) ); // AUDIT: Safe, moving less than i entries to a location before i. + ifdEntries[j] = temp; + #else + void * tempifdEntries = &ifdEntries[i]; + TweakedIFDEntry temp; + memcpy ( &temp, tempifdEntries, sizeof(TweakedIFDEntry) ); + ++j; // ! So the insertion index becomes j. + memcpy ( &ifdEntries[j+1], &ifdEntries[j], 12*(i-j) ); // AUDIT: Safe, moving less than i entries to a location before i. + tempifdEntries = &ifdEntries[j]; + memcpy ( tempifdEntries, &temp, sizeof(TweakedIFDEntry) ); + #endif + + } + + } + + } + + thisIFD->count = tagCount; // Save the final count. + +} // TIFF_MemoryReader::SortIFD + +// ================================================================================================= +// TIFF_MemoryReader::GetIFD +// ========================= + +bool TIFF_MemoryReader::GetIFD ( XMP_Uns8 ifd, TagInfoMap* ifdMap ) const +{ + if ( ifd > kTIFF_LastRealIFD ) XMP_Throw ( "Invalid IFD requested", kXMPErr_InternalFailure ); + const TweakedIFDInfo* thisIFD = &containedIFDs[ifd]; + + if ( ifdMap != 0 ) ifdMap->clear(); + if ( thisIFD->count == 0 ) return false; + + if ( ifdMap != 0 ) { + + for ( size_t i = 0; i < thisIFD->count; ++i ) { + + TweakedIFDEntry* thisTag = &(thisIFD->entries[i]); + if ( (thisTag->type < kTIFF_ByteType) || (thisTag->type > kTIFF_LastType) ) continue; // Bad type, skip this tag. + + TagInfo info ( thisTag->id, thisTag->type, 0, 0, GetUns32AsIs(&thisTag->bytes) ); + info.count = info.dataLen / (XMP_Uns32)kTIFF_TypeSizes[info.type]; + info.dataPtr = this->GetDataPtr ( thisTag ); + + (*ifdMap)[info.id] = info; + + } + + } + + return true; + +} // TIFF_MemoryReader::GetIFD + +// ================================================================================================= +// TIFF_MemoryReader::FindTagInIFD +// =============================== + +const TIFF_MemoryReader::TweakedIFDEntry* TIFF_MemoryReader::FindTagInIFD ( XMP_Uns8 ifd, XMP_Uns16 id ) const +{ + if ( ifd == kTIFF_KnownIFD ) { + // ... lookup the tag in the known tag map + } + + if ( ifd > kTIFF_LastRealIFD ) XMP_Throw ( "Invalid IFD requested", kXMPErr_InternalFailure ); + const TweakedIFDInfo* thisIFD = &containedIFDs[ifd]; + + if ( thisIFD->count == 0 ) return 0; + + XMP_Uns32 spanLength = thisIFD->count; + const TweakedIFDEntry* spanBegin = &(thisIFD->entries[0]); + + while ( spanLength > 1 ) { + + XMP_Uns32 halfLength = spanLength >> 1; // Since spanLength > 1, halfLength > 0. + const TweakedIFDEntry* spanMiddle = spanBegin + halfLength; + + // There are halfLength entries below spanMiddle, then the spanMiddle entry, then + // spanLength-halfLength-1 entries above spanMiddle (which can be none). + + XMP_Uns16 middleID = GetUns16AsIs ( &spanMiddle->id ); + if ( middleID == id ) { + spanBegin = spanMiddle; + break; + } else if ( middleID > id ) { + spanLength = halfLength; // Discard the middle. + } else { + spanBegin = spanMiddle; // Keep a valid spanBegin for the return check, don't use spanMiddle+1. + spanLength -= halfLength; + } + + } + + if ( GetUns16AsIs(&spanBegin->id) != id ) spanBegin = 0; + return spanBegin; + +} // TIFF_MemoryReader::FindTagInIFD + +// ================================================================================================= +// TIFF_MemoryReader::GetValueOffset +// ================================= + +XMP_Uns32 TIFF_MemoryReader::GetValueOffset ( XMP_Uns8 ifd, XMP_Uns16 id ) const +{ + const TweakedIFDEntry* thisTag = this->FindTagInIFD ( ifd, id ); + if ( thisTag == 0 ) return 0; + + XMP_Uns8 * valuePtr = (XMP_Uns8*) this->GetDataPtr ( thisTag ); + + return (XMP_Uns32)(valuePtr - this->tiffStream); // ! TIFF streams can't exceed 4GB. + +} // TIFF_MemoryReader::GetValueOffset + +// ================================================================================================= +// TIFF_MemoryReader::GetTag +// ========================= + +bool TIFF_MemoryReader::GetTag ( XMP_Uns8 ifd, XMP_Uns16 id, TagInfo* info ) const +{ + const TweakedIFDEntry* thisTag = this->FindTagInIFD ( ifd, id ); + if ( thisTag == 0 ) return false; + + XMP_Uns16 thisType = GetUns16AsIs ( &thisTag->type ); + XMP_Uns32 thisBytes = GetUns32AsIs ( &thisTag->bytes ); + + if ( (thisType < kTIFF_ByteType) || (thisType > kTIFF_LastType) ) return false; // Bad type, skip this tag. + + if ( info != 0 ) { + + info->id = GetUns16AsIs ( &thisTag->id ); + info->type = thisType; + info->count = thisBytes / (XMP_Uns32)kTIFF_TypeSizes[thisType]; + info->dataLen = thisBytes; + + info->dataPtr = this->GetDataPtr ( thisTag ); + + } + + return true; + +} // TIFF_MemoryReader::GetTag + +// ================================================================================================= + +static inline XMP_Uns8 GetUns8 ( const void* dataPtr ) +{ + return *((XMP_Uns8*)dataPtr); +} + +// ================================================================================================= +// TIFF_MemoryReader::GetTag_Integer +// ================================= +// +// Tolerate any size or sign. + +bool TIFF_MemoryReader::GetTag_Integer ( XMP_Uns8 ifd, XMP_Uns16 id, XMP_Uns32* data ) const +{ + const TweakedIFDEntry* thisTag = this->FindTagInIFD ( ifd, id ); + if ( thisTag == 0 ) return false; + + if ( thisTag->type > kTIFF_LastType ) return false; // Unknown type. + if ( GetUns32AsIs(&thisTag->bytes) != kTIFF_TypeSizes[thisTag->type] ) return false; // Wrong count. + + XMP_Uns32 uns32; + XMP_Int32 int32; + + switch ( thisTag->type ) { + + case kTIFF_ByteType: + uns32 = GetUns8 ( this->GetDataPtr ( thisTag ) ); + break; + + case kTIFF_ShortType: + uns32 = this->GetUns16 ( this->GetDataPtr ( thisTag ) ); + break; + + case kTIFF_LongType: + uns32 = this->GetUns32 ( this->GetDataPtr ( thisTag ) ); + break; + + case kTIFF_SByteType: + int32 = (XMP_Int8) GetUns8 ( this->GetDataPtr ( thisTag ) ); + uns32 = (XMP_Uns32) int32; // Make sure sign bits are properly set. + break; + + case kTIFF_SShortType: + int32 = (XMP_Int16) this->GetUns16 ( this->GetDataPtr ( thisTag ) ); + uns32 = (XMP_Uns32) int32; // Make sure sign bits are properly set. + break; + + case kTIFF_SLongType: + int32 = (XMP_Int32) this->GetUns32 ( this->GetDataPtr ( thisTag ) ); + uns32 = (XMP_Uns32) int32; // Make sure sign bits are properly set. + break; + + default: return false; + + } + + if ( data != 0 ) *data = uns32; + return true; + +} // TIFF_MemoryReader::GetTag_Integer + +// ================================================================================================= +// TIFF_MemoryReader::GetTag_Byte +// ============================== + +bool TIFF_MemoryReader::GetTag_Byte ( XMP_Uns8 ifd, XMP_Uns16 id, XMP_Uns8* data ) const +{ + const TweakedIFDEntry* thisTag = this->FindTagInIFD ( ifd, id ); + if ( thisTag == 0 ) return false; + if ( (thisTag->type != kTIFF_ByteType) || (thisTag->bytes != 1) ) return false; + + if ( data != 0 ) { + *data = * ( (XMP_Uns8*) this->GetDataPtr ( thisTag ) ); + } + + return true; + +} // TIFF_MemoryReader::GetTag_Byte + +// ================================================================================================= +// TIFF_MemoryReader::GetTag_SByte +// =============================== + +bool TIFF_MemoryReader::GetTag_SByte ( XMP_Uns8 ifd, XMP_Uns16 id, XMP_Int8* data ) const +{ + const TweakedIFDEntry* thisTag = this->FindTagInIFD ( ifd, id ); + if ( thisTag == 0 ) return false; + if ( (thisTag->type != kTIFF_SByteType) || (thisTag->bytes != 1) ) return false; + + if ( data != 0 ) { + *data = * ( (XMP_Int8*) this->GetDataPtr ( thisTag ) ); + } + + return true; + +} // TIFF_MemoryReader::GetTag_SByte + +// ================================================================================================= +// TIFF_MemoryReader::GetTag_Short +// =============================== + +bool TIFF_MemoryReader::GetTag_Short ( XMP_Uns8 ifd, XMP_Uns16 id, XMP_Uns16* data ) const +{ + const TweakedIFDEntry* thisTag = this->FindTagInIFD ( ifd, id ); + if ( thisTag == 0 ) return false; + if ( (thisTag->type != kTIFF_ShortType) || (thisTag->bytes != 2) ) return false; + + if ( data != 0 ) { + *data = this->GetUns16 ( this->GetDataPtr ( thisTag ) ); + } + + return true; + +} // TIFF_MemoryReader::GetTag_Short + +// ================================================================================================= +// TIFF_MemoryReader::GetTag_SShort +// ================================ + +bool TIFF_MemoryReader::GetTag_SShort ( XMP_Uns8 ifd, XMP_Uns16 id, XMP_Int16* data ) const +{ + const TweakedIFDEntry* thisTag = this->FindTagInIFD ( ifd, id ); + if ( thisTag == 0 ) return false; + if ( (thisTag->type != kTIFF_SShortType) || (thisTag->bytes != 2) ) return false; + + if ( data != 0 ) { + *data = (XMP_Int16) this->GetUns16 ( this->GetDataPtr ( thisTag ) ); + } + + return true; + +} // TIFF_MemoryReader::GetTag_SShort + +// ================================================================================================= +// TIFF_MemoryReader::GetTag_Long +// ============================== + +bool TIFF_MemoryReader::GetTag_Long ( XMP_Uns8 ifd, XMP_Uns16 id, XMP_Uns32* data ) const +{ + const TweakedIFDEntry* thisTag = this->FindTagInIFD ( ifd, id ); + if ( thisTag == 0 ) return false; + if ( (thisTag->type != kTIFF_LongType) || (thisTag->bytes != 4) ) return false; + + if ( data != 0 ) { + *data = this->GetUns32 ( this->GetDataPtr ( thisTag ) ); + } + + return true; + +} // TIFF_MemoryReader::GetTag_Long + +// ================================================================================================= +// TIFF_MemoryReader::GetTag_SLong +// =============================== + +bool TIFF_MemoryReader::GetTag_SLong ( XMP_Uns8 ifd, XMP_Uns16 id, XMP_Int32* data ) const +{ + const TweakedIFDEntry* thisTag = this->FindTagInIFD ( ifd, id ); + if ( thisTag == 0 ) return false; + if ( (thisTag->type != kTIFF_SLongType) || (thisTag->bytes != 4) ) return false; + + if ( data != 0 ) { + *data = (XMP_Int32) this->GetUns32 ( this->GetDataPtr ( thisTag ) ); + } + + return true; + +} // TIFF_MemoryReader::GetTag_SLong + +// ================================================================================================= +// TIFF_MemoryReader::GetTag_Rational +// ================================== + +bool TIFF_MemoryReader::GetTag_Rational ( XMP_Uns8 ifd, XMP_Uns16 id, Rational* data ) const +{ + const TweakedIFDEntry* thisTag = this->FindTagInIFD ( ifd, id ); + if ( thisTag == 0 ) return false; + if ( (thisTag->type != kTIFF_RationalType) || (thisTag->bytes != 8) ) return false; + + if ( data != 0 ) { + XMP_Uns32* dataPtr = (XMP_Uns32*) this->GetDataPtr ( thisTag ); + data->num = this->GetUns32 ( dataPtr ); + data->denom = this->GetUns32 ( dataPtr+1 ); + } + + return true; + +} // TIFF_MemoryReader::GetTag_Rational + +// ================================================================================================= +// TIFF_MemoryReader::GetTag_SRational +// =================================== + +bool TIFF_MemoryReader::GetTag_SRational ( XMP_Uns8 ifd, XMP_Uns16 id, SRational* data ) const +{ + const TweakedIFDEntry* thisTag = this->FindTagInIFD ( ifd, id ); + if ( thisTag == 0 ) return false; + if ( (thisTag->type != kTIFF_SRationalType) || (thisTag->bytes != 8) ) return false; + + if ( data != 0 ) { + XMP_Uns32* dataPtr = (XMP_Uns32*) this->GetDataPtr ( thisTag ); + data->num = (XMP_Int32) this->GetUns32 ( dataPtr ); + data->denom = (XMP_Int32) this->GetUns32 ( dataPtr+1 ); + } + + return true; + +} // TIFF_MemoryReader::GetTag_SRational + +// ================================================================================================= +// TIFF_MemoryReader::GetTag_Float +// =============================== + +bool TIFF_MemoryReader::GetTag_Float ( XMP_Uns8 ifd, XMP_Uns16 id, float* data ) const +{ + const TweakedIFDEntry* thisTag = this->FindTagInIFD ( ifd, id ); + if ( thisTag == 0 ) return false; + if ( (thisTag->type != kTIFF_FloatType) || (thisTag->bytes != 4) ) return false; + + if ( data != 0 ) { + *data = this->GetFloat ( this->GetDataPtr ( thisTag ) ); + } + + return true; + +} // TIFF_MemoryReader::GetTag_Float + +// ================================================================================================= +// TIFF_MemoryReader::GetTag_Double +// ================================ + +bool TIFF_MemoryReader::GetTag_Double ( XMP_Uns8 ifd, XMP_Uns16 id, double* data ) const +{ + const TweakedIFDEntry* thisTag = this->FindTagInIFD ( ifd, id ); + if ( thisTag == 0 ) return false; + if ( (thisTag->type != kTIFF_DoubleType) || (thisTag->bytes != 8) ) return false; + + if ( data != 0 ) { + double* dataPtr = (double*) this->GetDataPtr ( thisTag ); + *data = this->GetDouble ( dataPtr ); + } + + return true; + +} // TIFF_MemoryReader::GetTag_Double + +// ================================================================================================= +// TIFF_MemoryReader::GetTag_ASCII +// =============================== + +bool TIFF_MemoryReader::GetTag_ASCII ( XMP_Uns8 ifd, XMP_Uns16 id, XMP_StringPtr* dataPtr, XMP_StringLen* dataLen ) const +{ + const TweakedIFDEntry* thisTag = this->FindTagInIFD ( ifd, id ); + if ( thisTag == 0 ) return false; + if ( thisTag->type != kTIFF_ASCIIType ) return false; + + if ( dataPtr != 0 ) { + *dataPtr = (XMP_StringPtr) this->GetDataPtr ( thisTag ); + } + + if ( dataLen != 0 ) *dataLen = thisTag->bytes; + + return true; + +} // TIFF_MemoryReader::GetTag_ASCII + +// ================================================================================================= +// TIFF_MemoryReader::GetTag_EncodedString +// ======================================= + +bool TIFF_MemoryReader::GetTag_EncodedString ( XMP_Uns8 ifd, XMP_Uns16 id, std::string* utf8Str ) const +{ + const TweakedIFDEntry* thisTag = this->FindTagInIFD ( ifd, id ); + if ( thisTag == 0 ) return false; + if ( thisTag->type != kTIFF_UndefinedType ) return false; + + if ( utf8Str == 0 ) return true; // Return true if the converted string is not wanted. + + bool ok = this->DecodeString ( this->GetDataPtr ( thisTag ), thisTag->bytes, utf8Str ); + return ok; + +} // TIFF_MemoryReader::GetTag_EncodedString + +// ================================================================================================= +// TIFF_MemoryReader::ParseMemoryStream +// ==================================== + +// *** Need to tell TIFF/Exif from TIFF/EP, DNG files are the latter. +// isAlredyLittle is provided for case when data contain no information about Endianess, So need not to check for header +void TIFF_MemoryReader::ParseMemoryStream ( const void* data, XMP_Uns32 length, bool copyData /* = true */, bool isAlredyLittle /* = false */ ) +{ + // Get rid of any current TIFF. + + if ( this->ownedStream ) free ( this->tiffStream ); + this->ownedStream = false; + this->tiffStream = 0; + this->tiffLength = 0; + + for ( size_t i = 0; i < kTIFF_KnownIFDCount; ++i ) { + this->containedIFDs[i].count = 0; + this->containedIFDs[i].entries = 0; + } + + if ( length == 0 ) return; + + // Allocate space for the full in-memory stream and copy it. + + if ( ! copyData ) { + XMP_Assert ( ! this->ownedStream ); + this->tiffStream = (XMP_Uns8*) data; + } else { + if ( length > 100*1024*1024 ) XMP_Throw ( "Outrageous length for memory-based TIFF", kXMPErr_BadTIFF ); + this->tiffStream = (XMP_Uns8*) malloc(length); + if ( this->tiffStream == 0 ) XMP_Throw ( "Out of memory", kXMPErr_NoMemory ); + memcpy ( this->tiffStream, data, length ); // AUDIT: Safe, malloc'ed length bytes above. + this->ownedStream = true; + } + + this->tiffLength = length; + XMP_Uns32 ifdLimit = this->tiffLength - 6; // An IFD must start before this offset. + XMP_Uns32 primaryIFDOffset = 0; + XMP_Uns32 tnailIFDOffset = 0; + // Find and process the primary, Exif, GPS, and Interoperability IFDs. + + // If already is in little Endian then no need to check for Check TIFF header + if ( isAlredyLittle ) + { + this->nativeEndian = ! kBigEndianHost; + this->GetUns16 = GetUns16LE; + this->GetUns32 = GetUns32LE; + this->GetFloat = GetFloatLE; + this->GetDouble = GetDoubleLE; + + this->PutUns16 = PutUns16LE; + this->PutUns32 = PutUns32LE; + this->PutFloat = PutFloatLE; + this->PutDouble = PutDoubleLE; + tnailIFDOffset = this->ProcessOneIFD ( primaryIFDOffset, kTIFF_PrimaryIFD, true ); + } + else + { + primaryIFDOffset = this->CheckTIFFHeader ( this->tiffStream, length ); + + if ( primaryIFDOffset != 0 ) tnailIFDOffset = this->ProcessOneIFD ( primaryIFDOffset, kTIFF_PrimaryIFD ); + } + + // ! Need the thumbnail IFD for checking full Exif APP1 in some JPEG files! + if ( tnailIFDOffset != 0 ) { + if ( IsOffsetValid(tnailIFDOffset, 8, ifdLimit ) ) { // Ignore a bad Thumbnail IFD offset. + (void) this->ProcessOneIFD ( tnailIFDOffset, kTIFF_TNailIFD ); + } else { + XMP_Error error ( kXMPErr_BadTIFF, "Bad IFD offset" ); + this->NotifyClient (kXMPErrSev_Recoverable, error ); + } + } + + const TweakedIFDEntry* exifIFDTag = this->FindTagInIFD ( kTIFF_PrimaryIFD, kTIFF_ExifIFDPointer ); + if ( (exifIFDTag != 0) && (exifIFDTag->type == kTIFF_LongType) && (GetUns32AsIs(&exifIFDTag->bytes) == 4) ) { + XMP_Uns32 exifOffset = this->GetUns32 ( &exifIFDTag->dataOrPos ); + (void) this->ProcessOneIFD ( exifOffset, kTIFF_ExifIFD ); + } + + const TweakedIFDEntry* gpsIFDTag = this->FindTagInIFD ( kTIFF_PrimaryIFD, kTIFF_GPSInfoIFDPointer ); + if ( (gpsIFDTag != 0) && (gpsIFDTag->type == kTIFF_LongType) && (GetUns32AsIs(&gpsIFDTag->bytes) == 4) ) { + XMP_Uns32 gpsOffset = this->GetUns32 ( &gpsIFDTag->dataOrPos ); + if ( IsOffsetValid ( gpsOffset, 8, ifdLimit ) ) { // Ignore a bad GPS IFD offset. + (void) this->ProcessOneIFD ( gpsOffset, kTIFF_GPSInfoIFD ); + } else { + XMP_Error error ( kXMPErr_BadTIFF, "Bad IFD offset" ); + this->NotifyClient (kXMPErrSev_Recoverable, error ); + } + } + + const TweakedIFDEntry* interopIFDTag = this->FindTagInIFD ( kTIFF_ExifIFD, kTIFF_InteroperabilityIFDPointer ); + if ( (interopIFDTag != 0) && (interopIFDTag->type == kTIFF_LongType) && (GetUns32AsIs(&interopIFDTag->bytes) == 4) ) { + XMP_Uns32 interopOffset = this->GetUns32 ( &interopIFDTag->dataOrPos ); + if ( IsOffsetValid ( interopOffset, 8, ifdLimit ) ) { // Ignore a bad Interoperability IFD offset. + (void) this->ProcessOneIFD ( interopOffset, kTIFF_InteropIFD ); + } else { + XMP_Error error ( kXMPErr_BadTIFF, "Bad IFD offset" ); + this->NotifyClient (kXMPErrSev_Recoverable, error ); + } + } + +} // TIFF_MemoryReader::ParseMemoryStream + +// ================================================================================================= +// TIFF_MemoryReader::ProcessOneIFD +// ================================ +// ModifiedInitialCheck is provided for case when data contain no header + +XMP_Uns32 TIFF_MemoryReader::ProcessOneIFD ( XMP_Uns32 ifdOffset, XMP_Uns8 ifd, bool ModifiedInitialCheck /* = false */ ) +{ + TweakedIFDInfo& ifdInfo = this->containedIFDs[ifd]; + + if( ModifiedInitialCheck ) + { + if ((ifdOffset > (this->tiffLength)) ) { + XMP_Error error(kXMPErr_BadTIFF, "Bad IFD offset" ); + this->NotifyClient ( kXMPErrSev_FileFatal, error ); + } + } + else + { + if ( (ifdOffset < 8) || (ifdOffset > (this->tiffLength - kEmptyIFDLength)) ) { + XMP_Error error(kXMPErr_BadTIFF, "Bad IFD offset" ); + this->NotifyClient ( kXMPErrSev_FileFatal, error ); + } + } + + XMP_Uns8* ifdPtr = this->tiffStream + ifdOffset; + XMP_Uns16 ifdCount = this->GetUns16 ( ifdPtr ); + TweakedIFDEntry* ifdEntries = (TweakedIFDEntry*)(ifdPtr+2); + + if ( ifdCount >= 0x8000 ) { + XMP_Error error(kXMPErr_BadTIFF, "Outrageous IFD count" ); + this->NotifyClient ( kXMPErrSev_FileFatal, error ); + } + + if ( (XMP_Uns32)(2 + ifdCount*12 + 4) > (this->tiffLength - ifdOffset) ) { + XMP_Error error(kXMPErr_BadTIFF, "Out of bounds IFD" ); + this->NotifyClient ( kXMPErrSev_FileFatal, error ); + } + + ifdInfo.count = ifdCount; + ifdInfo.entries = ifdEntries; + + XMP_Int32 prevTag = -1; // ! The GPS IFD has a tag 0, so we need a signed initial value. + bool needsSorting = false; + for ( size_t i = 0; i < ifdCount; ++i ) { + + TweakedIFDEntry* thisEntry = &ifdEntries[i]; // Tweak the IFD entry to be more useful. + + if ( ! this->nativeEndian ) { + Flip2 ( &thisEntry->id ); + Flip2 ( &thisEntry->type ); + Flip4 ( &thisEntry->bytes ); + } + + if ( GetUns16AsIs(&thisEntry->id) <= prevTag ) needsSorting = true; + prevTag = GetUns16AsIs ( &thisEntry->id ); + + if ( (GetUns16AsIs(&thisEntry->type) < kTIFF_ByteType) || (GetUns16AsIs(&thisEntry->type) > kTIFF_LastType) ) continue; // Bad type, skip this tag. + + #if ! (SUNOS_SPARC || XMP_IOS_ARM) + + thisEntry->bytes *= (XMP_Uns32)kTIFF_TypeSizes[thisEntry->type]; + if ( thisEntry->bytes > 4 ) { + if ( ! this->nativeEndian ) Flip4 ( &thisEntry->dataOrPos ); + if ( (thisEntry->dataOrPos < 8) || (thisEntry->dataOrPos >= this->tiffLength) ) { + thisEntry->bytes = thisEntry->dataOrPos = 0; // Make this bad tag look empty. + } + if ( thisEntry->bytes > (this->tiffLength - thisEntry->dataOrPos) ) { + thisEntry->bytes = thisEntry->dataOrPos = 0; // Make this bad tag look empty. + } + } + + #else + + void *tempEntryByte = &thisEntry->bytes; + XMP_Uns32 temp = GetUns32AsIs(&thisEntry->bytes); + temp = temp * (XMP_Uns32)kTIFF_TypeSizes[GetUns16AsIs(&thisEntry->type)]; + memcpy ( tempEntryByte, &temp, sizeof(thisEntry->bytes) ); + + // thisEntry->bytes *= (XMP_Uns32)kTIFF_TypeSizes[thisEntry->type]; + if ( GetUns32AsIs(&thisEntry->bytes) > 4 ) { + void *tempEntryDataOrPos = &thisEntry->dataOrPos; + if ( ! this->nativeEndian ) Flip4 ( &thisEntry->dataOrPos ); + if ( (GetUns32AsIs(&thisEntry->dataOrPos) < 8) || (GetUns32AsIs(&thisEntry->dataOrPos) >= this->tiffLength) ) { + // thisEntry->bytes = thisEntry->dataOrPos = 0; // Make this bad tag look empty. + memset ( tempEntryByte, 0, sizeof(XMP_Uns32) ); + memset ( tempEntryDataOrPos, 0, sizeof(XMP_Uns32) ); + } + if ( GetUns32AsIs(&thisEntry->bytes) > (this->tiffLength - GetUns32AsIs(&thisEntry->dataOrPos)) ) { + // thisEntry->bytes = thisEntry->dataOrPos = 0; // Make this bad tag look empty. + memset ( tempEntryByte, 0, sizeof(XMP_Uns32) ); + memset ( tempEntryDataOrPos, 0, sizeof(XMP_Uns32) ); + } + } + + #endif + + } + + ifdPtr += (2 + ifdCount*12); + XMP_Uns32 nextIFDOffset = this->GetUns32 ( ifdPtr ); + + if ( needsSorting ) SortIFD ( &ifdInfo ); // ! Don't perturb the ifdCount used to find the next IFD offset. + + return nextIFDOffset; + +} // TIFF_MemoryReader::ProcessOneIFD + +// ================================================================================================= diff --git a/core/libs/dngwriter/extra/xmp_sdk/XMPFiles/source/FormatSupport/TIFF_Support.cpp b/core/libs/dngwriter/extra/xmp_sdk/XMPFiles/source/FormatSupport/TIFF_Support.cpp new file mode 100644 index 0000000000..da2f9b6a87 --- /dev/null +++ b/core/libs/dngwriter/extra/xmp_sdk/XMPFiles/source/FormatSupport/TIFF_Support.cpp @@ -0,0 +1,456 @@ +// ================================================================================================= +// ADOBE SYSTEMS INCORPORATED +// Copyright 2006 Adobe Systems Incorporated +// All Rights Reserved +// +// NOTICE: Adobe permits you to use, modify, and distribute this file in accordance with the terms +// of the Adobe license agreement accompanying it. +// ================================================================================================= + +#include "public/include/XMP_Environment.h" // ! XMP_Environment.h must be the first included header. +#include "public/include/XMP_Const.h" + +#include "XMPFiles/source/FormatSupport/TIFF_Support.hpp" +#include "source/EndianUtils.hpp" +#include "source/XIO.hpp" + +#include "source/UnicodeConversions.hpp" + +// ================================================================================================= +/// \file TIFF_Support.cpp +/// \brief Manager for parsing and serializing TIFF streams. +/// +// ================================================================================================= + +// ================================================================================================= +// TIFF_Manager::TIFF_Manager +// ========================== + +static bool sFirstCTor = true; + +TIFF_Manager::TIFF_Manager() + : bigEndian(false), nativeEndian(false), errorCallbackPtr( NULL ), + GetUns16(0), GetUns32(0), GetFloat(0), GetDouble(0), + PutUns16(0), PutUns32(0), PutFloat(0), PutDouble(0) +{ + + if ( sFirstCTor ) { + sFirstCTor = false; + #if XMP_DebugBuild + for ( int ifd = 0; ifd < kTIFF_KnownIFDCount; ++ifd ) { // Make sure the known tag arrays are sorted. + for ( const XMP_Uns16* idPtr = sKnownTags[ifd]; *idPtr != 0xFFFF; ++idPtr ) { + XMP_Assert ( *idPtr < *(idPtr+1) ); + } + } + #endif + } + +} // TIFF_Manager::TIFF_Manager + +// ================================================================================================= +// TIFF_Manager::CheckTIFFHeader +// ============================= +// +// Checks the 4 byte TIFF prefix for validity and endianness. Sets the endian flags and the Get +// function pointers. Returns the 0th IFD offset. + +XMP_Uns32 TIFF_Manager::CheckTIFFHeader ( const XMP_Uns8* tiffPtr, XMP_Uns32 length ) +{ + if ( length < kEmptyTIFFLength ) XMP_Throw ( "The TIFF is too small", kXMPErr_BadTIFF ); + + XMP_Uns32 tiffPrefix = (tiffPtr[0] << 24) | (tiffPtr[1] << 16) | (tiffPtr[2] << 8) | (tiffPtr[3]); + + if ( tiffPrefix == kBigEndianPrefix ) { + this->bigEndian = true; + } else if ( tiffPrefix == kLittleEndianPrefix ) { + this->bigEndian = false; + } else { + XMP_Throw ( "Unrecognized TIFF prefix", kXMPErr_BadTIFF ); + } + + this->nativeEndian = (this->bigEndian == kBigEndianHost); + + if ( this->bigEndian ) { + + this->GetUns16 = GetUns16BE; + this->GetUns32 = GetUns32BE; + this->GetFloat = GetFloatBE; + this->GetDouble = GetDoubleBE; + + this->PutUns16 = PutUns16BE; + this->PutUns32 = PutUns32BE; + this->PutFloat = PutFloatBE; + this->PutDouble = PutDoubleBE; + + } else { + + this->GetUns16 = GetUns16LE; + this->GetUns32 = GetUns32LE; + this->GetFloat = GetFloatLE; + this->GetDouble = GetDoubleLE; + + this->PutUns16 = PutUns16LE; + this->PutUns32 = PutUns32LE; + this->PutFloat = PutFloatLE; + this->PutDouble = PutDoubleLE; + + } + + XMP_Uns32 mainIFDOffset = this->GetUns32 ( tiffPtr+4 ); // ! Do this after setting the Get/Put procs! + + if ( mainIFDOffset != 0 ) { // Tolerate empty TIFF even though formally invalid. + if ( (length < (kEmptyTIFFLength + kEmptyIFDLength)) || + (mainIFDOffset < kEmptyTIFFLength) || (mainIFDOffset > (length - kEmptyIFDLength)) ) { + XMP_Throw ( "Invalid primary IFD offset", kXMPErr_BadTIFF ); + } + } + + return mainIFDOffset; + +} // TIFF_Manager::CheckTIFFHeader + +// ================================================================================================= +// TIFF_Manager::SetTag_Integer +// ============================ + +void TIFF_Manager::SetTag_Integer ( XMP_Uns8 ifd, XMP_Uns16 id, XMP_Uns32 data32 ) +{ + + if ( data32 > 0xFFFF ) { + this->SetTag ( ifd, id, kTIFF_LongType, 1, &data32 ); + } else { + XMP_Uns16 data16 = (XMP_Uns16)data32; + this->SetTag ( ifd, id, kTIFF_ShortType, 1, &data16 ); + } + +} // TIFF_Manager::SetTag_Integer + +// ================================================================================================= +// TIFF_Manager::SetTag_Byte +// ========================= + +void TIFF_Manager::SetTag_Byte ( XMP_Uns8 ifd, XMP_Uns16 id, XMP_Uns8 data ) +{ + + this->SetTag ( ifd, id, kTIFF_ByteType, 1, &data ); + +} // TIFF_Manager::SetTag_Byte + +// ================================================================================================= +// TIFF_Manager::SetTag_SByte +// ========================== + +void TIFF_Manager::SetTag_SByte ( XMP_Uns8 ifd, XMP_Uns16 id, XMP_Int8 data ) +{ + + this->SetTag ( ifd, id, kTIFF_SByteType, 1, &data ); + +} // TIFF_Manager::SetTag_SByte + +// ================================================================================================= +// TIFF_Manager::SetTag_Short +// ========================== + +void TIFF_Manager::SetTag_Short ( XMP_Uns8 ifd, XMP_Uns16 id, XMP_Uns16 clientData ) +{ + XMP_Uns16 streamData; + + this->PutUns16 ( clientData, &streamData ); + this->SetTag ( ifd, id, kTIFF_ShortType, 1, &streamData ); + +} // TIFF_Manager::SetTag_Short + +// ================================================================================================= +// TIFF_Manager::SetTag_SShort +// =========================== + + void TIFF_Manager::SetTag_SShort ( XMP_Uns8 ifd, XMP_Uns16 id, XMP_Int16 clientData ) +{ + XMP_Int16 streamData; + + this->PutUns16 ( (XMP_Uns16)clientData, &streamData ); + this->SetTag ( ifd, id, kTIFF_SShortType, 1, &streamData ); + +} // TIFF_Manager::SetTag_SShort + +// ================================================================================================= +// TIFF_Manager::SetTag_Long +// ========================= + + void TIFF_Manager::SetTag_Long ( XMP_Uns8 ifd, XMP_Uns16 id, XMP_Uns32 clientData ) +{ + XMP_Uns32 streamData; + + this->PutUns32 ( clientData, &streamData ); + this->SetTag ( ifd, id, kTIFF_LongType, 1, &streamData ); + +} // TIFF_Manager::SetTag_Long + +// ================================================================================================= +// TIFF_Manager::SetTag_SLong +// ========================== + + void TIFF_Manager::SetTag_SLong ( XMP_Uns8 ifd, XMP_Uns16 id, XMP_Int32 clientData ) +{ + XMP_Int32 streamData; + + this->PutUns32 ( (XMP_Uns32)clientData, &streamData ); + this->SetTag ( ifd, id, kTIFF_SLongType, 1, &streamData ); + +} // TIFF_Manager::SetTag_SLong + +// ================================================================================================= +// TIFF_Manager::SetTag_Rational +// ============================= + +struct StreamRational { XMP_Uns32 num, denom; }; + + void TIFF_Manager::SetTag_Rational ( XMP_Uns8 ifd, XMP_Uns16 id, XMP_Uns32 clientNum, XMP_Uns32 clientDenom ) +{ + StreamRational streamData; + + this->PutUns32 ( clientNum, &streamData.num ); + this->PutUns32 ( clientDenom, &streamData.denom ); + this->SetTag ( ifd, id, kTIFF_RationalType, 1, &streamData ); + +} // TIFF_Manager::SetTag_Rational + +// ================================================================================================= +// TIFF_Manager::SetTag_SRational +// ============================== + + void TIFF_Manager::SetTag_SRational ( XMP_Uns8 ifd, XMP_Uns16 id, XMP_Int32 clientNum, XMP_Int32 clientDenom ) +{ + StreamRational streamData; + + this->PutUns32 ( (XMP_Uns32)clientNum, &streamData.num ); + this->PutUns32 ( (XMP_Uns32)clientDenom, &streamData.denom ); + this->SetTag ( ifd, id, kTIFF_SRationalType, 1, &streamData ); + +} // TIFF_Manager::SetTag_SRational + +// ================================================================================================= +// TIFF_Manager::SetTag_Float +// ========================== + + void TIFF_Manager::SetTag_Float ( XMP_Uns8 ifd, XMP_Uns16 id, float clientData ) +{ + float streamData; + + this->PutFloat ( clientData, &streamData ); + this->SetTag ( ifd, id, kTIFF_FloatType, 1, &streamData ); + +} // TIFF_Manager::SetTag_Float + +// ================================================================================================= +// TIFF_Manager::SetTag_Double +// =========================== + + void TIFF_Manager::SetTag_Double ( XMP_Uns8 ifd, XMP_Uns16 id, double clientData ) +{ + double streamData; + + this->PutDouble ( clientData, &streamData ); + this->SetTag ( ifd, id, kTIFF_DoubleType, 1, &streamData ); + +} // TIFF_Manager::SetTag_Double + +// ================================================================================================= +// TIFF_Manager::SetTag_ASCII +// =========================== + +void TIFF_Manager::SetTag_ASCII ( XMP_Uns8 ifd, XMP_Uns16 id, XMP_StringPtr data ) +{ + + this->SetTag ( ifd, id, kTIFF_ASCIIType, (XMP_Uns32)(strlen(data) + 1), data ); // ! Include trailing nul. + +} // TIFF_Manager::SetTag_ASCII + +// ================================================================================================= +// UTF16_to_UTF8 +// ============= + +static void UTF16_to_UTF8 ( const UTF16Unit * utf16Ptr, size_t utf16Len, bool bigEndian, std::string * outStr ) +{ + UTF16_to_UTF8_Proc ToUTF8 = 0; + if ( bigEndian ) { + ToUTF8 = UTF16BE_to_UTF8; + } else { + ToUTF8 = UTF16LE_to_UTF8; + } + + UTF8Unit buffer [1000]; + size_t inCount, outCount; + + outStr->erase(); + outStr->reserve ( utf16Len * 2 ); // As good a guess as any. + + while ( utf16Len > 0 ) { + ToUTF8 ( utf16Ptr, utf16Len, buffer, sizeof(buffer), &inCount, &outCount ); + outStr->append ( (XMP_StringPtr)buffer, outCount ); + utf16Ptr += inCount; + utf16Len -= inCount; + } + +} // UTF16_to_UTF8 + +// ================================================================================================= +// TIFF_Manager::DecodeString +// ========================== +// +// Convert an explicitly encoded string to UTF-8. The input must be encoded according to table 6 of +// the Exif 2.2 specification. The input pointer is to the start of the 8 byte header, the length is +// the full length. In other words, the pointer and length from the IFD entry. Returns true if the +// encoding is supported and the conversion succeeds. + +// *** JIS encoding is not supported yet. Need a static mapping table from JIS X 208-1990 to Unicode. + +bool TIFF_Manager::DecodeString ( const void * encodedPtr, size_t encodedLen, std::string* utf8Str ) const +{ + utf8Str->erase(); + if ( encodedLen < 8 ) return false; // Need to have at least the 8 byte header. + + XMP_StringPtr typePtr = (XMP_StringPtr)encodedPtr; + XMP_StringPtr valuePtr = typePtr + 8; + size_t valueLen = encodedLen - 8; + + if ( *typePtr == 'A' ) { + + utf8Str->assign ( valuePtr, valueLen ); + return true; + + } else if ( *typePtr == 'U' ) { + + try { + + const UTF16Unit * utf16Ptr = (const UTF16Unit *) valuePtr; + size_t utf16Len = valueLen >> 1; // The number of UTF-16 storage units, not bytes. + if ( utf16Len == 0 ) return false; + bool isBigEndian = this->bigEndian; // Default to stream endian, unless there is a BOM ... + if ( (*utf16Ptr == 0xFEFF) || (*utf16Ptr == 0xFFFE) ) { // Check for an explicit BOM + isBigEndian = (*((XMP_Uns8*)utf16Ptr) == 0xFE); + utf16Ptr += 1; // Don't translate the BOM. + utf16Len -= 1; + if ( utf16Len == 0 ) return false; + } + UTF16_to_UTF8 ( utf16Ptr, utf16Len, isBigEndian, utf8Str ); + return true; + + } catch ( ... ) { + return false; // Ignore the tag if there are conversion errors. + } + + } else if ( *typePtr == 'J' ) { + + return false; // ! Ignore JIS for now. + + } + + return false; // ! Ignore all other encodings. + +} // TIFF_Manager::DecodeString + +// ================================================================================================= +// UTF8_to_UTF16 +// ============= + +static void UTF8_to_UTF16 ( const UTF8Unit * utf8Ptr, size_t utf8Len, bool bigEndian, std::string * outStr ) +{ + UTF8_to_UTF16_Proc ToUTF16 = 0; + if ( bigEndian ) { + ToUTF16 = UTF8_to_UTF16BE; + } else { + ToUTF16 = UTF8_to_UTF16LE; + } + + enum { kUTF16Len = 1000 }; + UTF16Unit buffer [kUTF16Len]; + size_t inCount, outCount; + + outStr->erase(); + outStr->reserve ( utf8Len * 2 ); // As good a guess as any. + + while ( utf8Len > 0 ) { + ToUTF16 ( utf8Ptr, utf8Len, buffer, kUTF16Len, &inCount, &outCount ); + outStr->append ( (XMP_StringPtr)buffer, outCount*2 ); + utf8Ptr += inCount; + utf8Len -= inCount; + } + +} // UTF8_to_UTF16 + +XMP_Bool IsOffsetValid( XMP_Uns32 offset, XMP_Uns32 lowerBound, XMP_Uns32 upperBound ) +{ + if ( (lowerBound <= offset) && (offset < upperBound) ) + return true; + return false; +} + +// ================================================================================================= +// TIFF_Manager::EncodeString +// ========================== +// +// Convert a UTF-8 string to an explicitly encoded form according to table 6 of the Exif 2.2 +// specification. Returns true if the encoding is supported and the conversion succeeds. + +// *** JIS encoding is not supported yet. Need a static mapping table to JIS X 208-1990 from Unicode. + +bool TIFF_Manager::EncodeString ( const std::string& utf8Str, XMP_Uns8 encoding, std::string* encodedStr ) +{ + encodedStr -> erase(); + + if ( encoding == kTIFF_EncodeASCII ) { + + encodedStr->assign ( "ASCII\0\0\0", 8 ); + XMP_Assert (encodedStr->size() == 8 ); + + encodedStr->append ( utf8Str ); // ! Let the caller filter the value. (?) + + return true; + + } else if ( encoding == kTIFF_EncodeUnicode ) { + + encodedStr->assign ( "UNICODE\0", 8 ); + XMP_Assert (encodedStr->size() == 8 ); + + try { + std::string temp; + UTF8_to_UTF16 ( (const UTF8Unit*)utf8Str.c_str(), utf8Str.size(), this->bigEndian, &temp ); + encodedStr->append ( temp ); + return true; + } catch ( ... ) { + return false; // Ignore the tag if there are conversion errors. + } + + } else if ( encoding == kTIFF_EncodeJIS ) { + + XMP_Throw ( "Encoding to JIS is not implemented", kXMPErr_Unimplemented ); + + // encodedStr->assign ( "JIS\0\0\0\0\0", 8 ); + // XMP_Assert (encodedStr->size() == 8 ); + + // ... + + // return true; + + } else { + + XMP_Throw ( "Invalid TIFF string encoding", kXMPErr_BadParam ); + + } + + return false; // ! Should never get here. + +} // TIFF_Manager::EncodeString + +void TIFF_Manager::NotifyClient( XMP_ErrorSeverity severity, XMP_Error & error ) +{ + if (this->errorCallbackPtr) + this->errorCallbackPtr->NotifyClient( severity, error ); + else { + if ( severity != kXMPErrSev_Recoverable ) + throw error; + } +} + +// ================================================================================================= diff --git a/core/libs/dngwriter/extra/xmp_sdk/XMPFiles/source/FormatSupport/TIFF_Support.hpp b/core/libs/dngwriter/extra/xmp_sdk/XMPFiles/source/FormatSupport/TIFF_Support.hpp new file mode 100644 index 0000000000..6d4860b525 --- /dev/null +++ b/core/libs/dngwriter/extra/xmp_sdk/XMPFiles/source/FormatSupport/TIFF_Support.hpp @@ -0,0 +1,990 @@ +#ifndef __TIFF_Support_hpp__ +#define __TIFF_Support_hpp__ 1 + +// ================================================================================================= +// ADOBE SYSTEMS INCORPORATED +// Copyright 2006 Adobe Systems Incorporated +// All Rights Reserved +// +// NOTICE: Adobe permits you to use, modify, and distribute this file in accordance with the terms +// of the Adobe license agreement accompanying it. +// ================================================================================================= + +#include "public/include/XMP_Environment.h" // ! This must be the first include. + +#include +#include +#include + +#include "public/include/XMP_Const.h" +#include "public/include/XMP_IO.hpp" + +#include "XMPFiles/source/XMPFiles_Impl.hpp" + +#include "source/EndianUtils.hpp" + +#include "source/Endian.h" + +#if SUNOS_SPARC || XMP_IOS_ARM + static const IEndian &IE = BigEndian::getInstance(); +#else + static const IEndian &IE = LittleEndian::getInstance(); +#endif //#if SUNOS_SPARC || XMP_IOS_ARM + +// ================================================================================================= +/// \file TIFF_Support.hpp +/// \brief XMPFiles support for TIFF streams. +/// +/// This header provides TIFF stream support specific to the needs of XMPFiles. This is not intended +/// for general purpose TIFF processing. TIFF_Manager is an abstract base class with 2 concrete +/// derived classes, TIFF_MemoryReader and TIFF_FileWriter. +/// +/// TIFF_MemoryReader provides read-only support for TIFF streams that are small enough to be kept +/// entirely in memory. This allows optimizations to reduce heap usage and processing code. It is +/// sufficient for browsing access to the Exif metadata in JPEG and Photoshop files. Think of +/// TIFF_MemoryReader as "memory-based AND read-only". Since the entire TIFF stream is available, +/// GetTag will return information about any tag in the stream. +/// +/// TIFF_FileWriter is for cases where updates are needed or the TIFF stream is too large to be kept +/// entirely in memory. Think of TIFF_FileWriter as "file-based OR read-write". TIFF_FileWriter only +/// maintains information for tags of interest as metadata. +/// +/// The needs of XMPFiles are well defined metadata access. Only 4 IFDs are processed: +/// \li The 0th IFD, for the primary image, the first one in the outer list of IFDs. +/// \li The Exif general metadata IFD, from tag 34665 in the primary image IFD. +/// \li The Exif GPS Info metadata IFD, from tag 34853 in the primary image IFD. +/// \li The Exif Interoperability IFD, from tag 40965 in the Exif general metadata IFD. +/// +/// \note These classes are for use only when directly compiled and linked. They should not be +/// packaged in a DLL by themselves. They do not provide any form of C++ ABI protection. +// ================================================================================================= + + +// ================================================================================================= +// TIFF IFD and type constants +// =========================== +// +// These aren't inside TIFF_Manager because static data members can't be initialized there. + +enum { // Constants for the recognized IFDs. + kTIFF_PrimaryIFD = 0, // The primary image IFD, also called the 0th IFD. + kTIFF_TNailIFD = 1, // The thumbnail image IFD also called the 1st IFD. (not used) + kTIFF_ExifIFD = 2, // The Exif general metadata IFD. + kTIFF_GPSInfoIFD = 3, // The Exif GPS Info IFD. + kTIFF_InteropIFD = 4, // The Exif Interoperability IFD. + kTIFF_LastRealIFD = 4, + kTIFF_KnownIFDCount = 5, + kTIFF_KnownIFD = 9 // The IFD that a tag is known to belong in. +}; + +enum { // Constants for the type field of a tag, as defined by TIFF. + kTIFF_ShortOrLongType = 0, // ! Not part of the TIFF spec, never in a tag! + kTIFF_ByteType = 1, + kTIFF_ASCIIType = 2, + kTIFF_ShortType = 3, + kTIFF_LongType = 4, + kTIFF_RationalType = 5, + kTIFF_SByteType = 6, + kTIFF_UndefinedType = 7, + kTIFF_SShortType = 8, + kTIFF_SLongType = 9, + kTIFF_SRationalType = 10, + kTIFF_FloatType = 11, + kTIFF_DoubleType = 12, + kTIFF_IFDType = 13, + kTIFF_LastType = kTIFF_IFDType +}; + +static const size_t kTIFF_TypeSizes[] = { 0, 1, 1, 2, 4, 8, 1, 1, 2, 4, 8, 4, 8, 4 }; + +static const bool kTIFF_IsIntegerType[] = { 1, 1, 0, 1, 1, 0, 1, 0, 1, 1, 0, 0, 0 }; +static const bool kTIFF_IsRationalType[] = { 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0 }; +static const bool kTIFF_IsFloatType[] = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1 }; + +static const char * kTIFF_TypeNames[] = { "ShortOrLong", "BYTE", "ASCII", "SHORT", "LONG", "RATIONAL", + "SBYTE", "UNDEFINED", "SSHORT", "SLONG", "SRATIONAL", + "FLOAT", "DOUBLE" }; + +enum { // Encodings for SetTag_EncodedString. + kTIFF_EncodeUndefined = 0, + kTIFF_EncodeASCII = 1, + kTIFF_EncodeUnicode = 2, // UTF-16 in the endianness of the TIFF stream. + kTIFF_EncodeJIS = 3, // Exif 2.2 uses JIS X 208-1990. + kTIFF_EncodeUnknown = 9 +}; + +// ================================================================================================= +// Recognized TIFF tags +// ==================== + +// ----------------------------------------------------------------------------------------------- +// An enum of IDs for all of the tags as potential interest as metadata. The numberical order does +// not matter. These are mostly listed in the order of the Exif specification tables for convenience +// of checking correspondence. + +enum { + + // General 0th IFD tags. Some of these can also be in the thumbnail IFD. + + // General tags from Exif 2.3 table 4: + kTIFF_ImageWidth = 256, + kTIFF_ImageLength = 257, + kTIFF_BitsPerSample = 258, + kTIFF_Compression = 259, + kTIFF_PhotometricInterpretation = 262, + kTIFF_Orientation = 274, + kTIFF_SamplesPerPixel = 277, + kTIFF_PlanarConfiguration = 284, + kTIFF_YCbCrCoefficients = 529, + kTIFF_YCbCrSubSampling = 530, + kTIFF_XResolution = 282, + kTIFF_YResolution = 283, + kTIFF_ResolutionUnit = 296, + kTIFF_TransferFunction = 301, + kTIFF_WhitePoint = 318, + kTIFF_PrimaryChromaticities = 319, + kTIFF_YCbCrPositioning = 531, + kTIFF_ReferenceBlackWhite = 532, + kTIFF_DateTime = 306, + kTIFF_ImageDescription = 270, + kTIFF_Make = 271, + kTIFF_Model = 272, + kTIFF_Software = 305, + kTIFF_Artist = 315, + kTIFF_Copyright = 33432, + + // Tags defined by Adobe: + kTIFF_XMP = 700, + kTIFF_IPTC = 33723, + kTIFF_PSIR = 34377, + kTIFF_DNGVersion = 50706, + kTIFF_DNGBackwardVersion = 50707, + + // Additional thumbnail IFD tags. We also care about 256, 257, and 259 in thumbnails. + kTIFF_JPEGInterchangeFormat = 513, + kTIFF_JPEGInterchangeFormatLength = 514, + + // Tags that need special handling when rewriting memory-based TIFF. + kTIFF_StripOffsets = 273, + kTIFF_StripByteCounts = 279, + kTIFF_FreeOffsets = 288, + kTIFF_FreeByteCounts = 289, + kTIFF_TileOffsets = 324, + kTIFF_TileByteCounts = 325, + kTIFF_SubIFDs = 330, + kTIFF_JPEGQTables = 519, + kTIFF_JPEGDCTables = 520, + kTIFF_JPEGACTables = 521, + + // Exif IFD tags defined in Exif 2.3 table 7. + + kTIFF_ExifVersion = 36864, + kTIFF_FlashpixVersion = 40960, + kTIFF_ColorSpace = 40961, + kTIFF_Gamma = 42240, + kTIFF_ComponentsConfiguration = 37121, + kTIFF_CompressedBitsPerPixel = 37122, + kTIFF_PixelXDimension = 40962, + kTIFF_PixelYDimension = 40963, + kTIFF_MakerNote = 37500, // Gets deleted when rewriting memory-based TIFF. + kTIFF_UserComment = 37510, + kTIFF_RelatedSoundFile = 40964, + kTIFF_DateTimeOriginal = 36867, + kTIFF_DateTimeDigitized = 36868, + kTIFF_SubSecTime = 37520, + kTIFF_SubSecTimeOriginal = 37521, + kTIFF_SubSecTimeDigitized = 37522, + kTIFF_ImageUniqueID = 42016, + kTIFF_CameraOwnerName = 42032, + kTIFF_BodySerialNumber = 42033, + kTIFF_LensSpecification = 42034, + kTIFF_LensMake = 42035, + kTIFF_LensModel = 42036, + kTIFF_LensSerialNumber = 42037, + + // Exif IFD tags defined in Exif 2.3 table 8. + + kTIFF_ExposureTime = 33434, + kTIFF_FNumber = 33437, + kTIFF_ExposureProgram = 34850, + kTIFF_SpectralSensitivity = 34852, + kTIFF_PhotographicSensitivity = 34855, // ! Called kTIFF_ISOSpeedRatings before Exif 2.3. + kTIFF_OECF = 34856, + kTIFF_SensitivityType = 34864, + kTIFF_StandardOutputSensitivity = 34865, + kTIFF_RecommendedExposureIndex = 34866, + kTIFF_ISOSpeed = 34867, + kTIFF_ISOSpeedLatitudeyyy = 34868, + kTIFF_ISOSpeedLatitudezzz = 34869, + kTIFF_ShutterSpeedValue = 37377, + kTIFF_ApertureValue = 37378, + kTIFF_BrightnessValue = 37379, + kTIFF_ExposureBiasValue = 37380, + kTIFF_MaxApertureValue = 37381, + kTIFF_SubjectDistance = 37382, + kTIFF_MeteringMode = 37383, + kTIFF_LightSource = 37384, + kTIFF_Flash = 37385, + kTIFF_FocalLength = 37386, + kTIFF_SubjectArea = 37396, + kTIFF_FlashEnergy = 41483, + kTIFF_SpatialFrequencyResponse = 41484, + kTIFF_FocalPlaneXResolution = 41486, + kTIFF_FocalPlaneYResolution = 41487, + kTIFF_FocalPlaneResolutionUnit = 41488, + kTIFF_SubjectLocation = 41492, + kTIFF_ExposureIndex = 41493, + kTIFF_SensingMethod = 41495, + kTIFF_FileSource = 41728, + kTIFF_SceneType = 41729, + kTIFF_CFAPattern = 41730, + kTIFF_CustomRendered = 41985, + kTIFF_ExposureMode = 41986, + kTIFF_WhiteBalance = 41987, + kTIFF_DigitalZoomRatio = 41988, + kTIFF_FocalLengthIn35mmFilm = 41989, + kTIFF_SceneCaptureType = 41990, + kTIFF_GainControl = 41991, + kTIFF_Contrast = 41992, + kTIFF_Saturation = 41993, + kTIFF_Sharpness = 41994, + kTIFF_DeviceSettingDescription = 41995, + kTIFF_SubjectDistanceRange = 41996, + + // GPS IFD tags. + + kTIFF_GPSVersionID = 0, + kTIFF_GPSLatitudeRef = 1, + kTIFF_GPSLatitude = 2, + kTIFF_GPSLongitudeRef = 3, + kTIFF_GPSLongitude = 4, + kTIFF_GPSAltitudeRef = 5, + kTIFF_GPSAltitude = 6, + kTIFF_GPSTimeStamp = 7, + kTIFF_GPSSatellites = 8, + kTIFF_GPSStatus = 9, + kTIFF_GPSMeasureMode = 10, + kTIFF_GPSDOP = 11, + kTIFF_GPSSpeedRef = 12, + kTIFF_GPSSpeed = 13, + kTIFF_GPSTrackRef = 14, + kTIFF_GPSTrack = 15, + kTIFF_GPSImgDirectionRef = 16, + kTIFF_GPSImgDirection = 17, + kTIFF_GPSMapDatum = 18, + kTIFF_GPSDestLatitudeRef = 19, + kTIFF_GPSDestLatitude = 20, + kTIFF_GPSDestLongitudeRef = 21, + kTIFF_GPSDestLongitude = 22, + kTIFF_GPSDestBearingRef = 23, + kTIFF_GPSDestBearing = 24, + kTIFF_GPSDestDistanceRef = 25, + kTIFF_GPSDestDistance = 26, + kTIFF_GPSProcessingMethod = 27, + kTIFF_GPSAreaInformation = 28, + kTIFF_GPSDateStamp = 29, + kTIFF_GPSDifferential = 30, + kTIFF_GPSHPositioningError = 31, + + // Special tags that are links to other IFDs. + + kTIFF_ExifIFDPointer = 34665, // Found in 0th IFD + kTIFF_GPSInfoIFDPointer = 34853, // Found in 0th IFD + kTIFF_InteroperabilityIFDPointer = 40965 // Found in Exif IFD + +}; + +// *** Temporary hack: +#define kTIFF_ISOSpeedRatings kTIFF_PhotographicSensitivity + +// ------------------------------------------------------------------ +// Sorted arrays of the tags that are recognized in the various IFDs. + +static const XMP_Uns16 sKnownPrimaryIFDTags[] = +{ + kTIFF_ImageWidth, // 256 + kTIFF_ImageLength, // 257 + kTIFF_BitsPerSample, // 258 + kTIFF_Compression, // 259 + kTIFF_PhotometricInterpretation, // 262 + kTIFF_ImageDescription, // 270 + kTIFF_Make, // 271 + kTIFF_Model, // 272 + kTIFF_Orientation, // 274 + kTIFF_SamplesPerPixel, // 277 + kTIFF_XResolution, // 282 + kTIFF_YResolution, // 283 + kTIFF_PlanarConfiguration, // 284 + kTIFF_ResolutionUnit, // 296 + kTIFF_TransferFunction, // 301 + kTIFF_Software, // 305 + kTIFF_DateTime, // 306 + kTIFF_Artist, // 315 + kTIFF_WhitePoint, // 318 + kTIFF_PrimaryChromaticities, // 319 + kTIFF_YCbCrCoefficients, // 529 + kTIFF_YCbCrSubSampling, // 530 + kTIFF_YCbCrPositioning, // 531 + kTIFF_ReferenceBlackWhite, // 532 + kTIFF_XMP, // 700 + kTIFF_Copyright, // 33432 + kTIFF_IPTC, // 33723 + kTIFF_PSIR, // 34377 + kTIFF_ExifIFDPointer, // 34665 + kTIFF_GPSInfoIFDPointer, // 34853 + kTIFF_DNGVersion, // 50706 + kTIFF_DNGBackwardVersion, // 50707 + 0xFFFF // Must be last as a sentinel. +}; + +static const XMP_Uns16 sKnownThumbnailIFDTags[] = +{ + kTIFF_ImageWidth, // 256 + kTIFF_ImageLength, // 257 + kTIFF_Compression, // 259 + kTIFF_JPEGInterchangeFormat, // 513 + kTIFF_JPEGInterchangeFormatLength, // 514 + 0xFFFF // Must be last as a sentinel. +}; + +static const XMP_Uns16 sKnownExifIFDTags[] = +{ + kTIFF_ExposureTime, // 33434 + kTIFF_FNumber, // 33437 + kTIFF_ExposureProgram, // 34850 + kTIFF_SpectralSensitivity, // 34852 + kTIFF_PhotographicSensitivity, // 34855 + kTIFF_OECF, // 34856 + kTIFF_SensitivityType, // 34864 + kTIFF_StandardOutputSensitivity, // 34865 + kTIFF_RecommendedExposureIndex, // 34866 + kTIFF_ISOSpeed, // 34867 + kTIFF_ISOSpeedLatitudeyyy, // 34868 + kTIFF_ISOSpeedLatitudezzz, // 34869 + kTIFF_ExifVersion, // 36864 + kTIFF_DateTimeOriginal, // 36867 + kTIFF_DateTimeDigitized, // 36868 + kTIFF_ComponentsConfiguration, // 37121 + kTIFF_CompressedBitsPerPixel, // 37122 + kTIFF_ShutterSpeedValue, // 37377 + kTIFF_ApertureValue, // 37378 + kTIFF_BrightnessValue, // 37379 + kTIFF_ExposureBiasValue, // 37380 + kTIFF_MaxApertureValue, // 37381 + kTIFF_SubjectDistance, // 37382 + kTIFF_MeteringMode, // 37383 + kTIFF_LightSource, // 37384 + kTIFF_Flash, // 37385 + kTIFF_FocalLength, // 37386 + kTIFF_SubjectArea, // 37396 + kTIFF_UserComment, // 37510 + kTIFF_SubSecTime, // 37520 + kTIFF_SubSecTimeOriginal, // 37521 + kTIFF_SubSecTimeDigitized, // 37522 + kTIFF_FlashpixVersion, // 40960 + kTIFF_ColorSpace, // 40961 + kTIFF_PixelXDimension, // 40962 + kTIFF_PixelYDimension, // 40963 + kTIFF_RelatedSoundFile, // 40964 + kTIFF_FlashEnergy, // 41483 + kTIFF_SpatialFrequencyResponse, // 41484 + kTIFF_FocalPlaneXResolution, // 41486 + kTIFF_FocalPlaneYResolution, // 41487 + kTIFF_FocalPlaneResolutionUnit, // 41488 + kTIFF_SubjectLocation, // 41492 + kTIFF_ExposureIndex, // 41493 + kTIFF_SensingMethod, // 41495 + kTIFF_FileSource, // 41728 + kTIFF_SceneType, // 41729 + kTIFF_CFAPattern, // 41730 + kTIFF_CustomRendered, // 41985 + kTIFF_ExposureMode, // 41986 + kTIFF_WhiteBalance, // 41987 + kTIFF_DigitalZoomRatio, // 41988 + kTIFF_FocalLengthIn35mmFilm, // 41989 + kTIFF_SceneCaptureType, // 41990 + kTIFF_GainControl, // 41991 + kTIFF_Contrast, // 41992 + kTIFF_Saturation, // 41993 + kTIFF_Sharpness, // 41994 + kTIFF_DeviceSettingDescription, // 41995 + kTIFF_SubjectDistanceRange, // 41996 + kTIFF_ImageUniqueID, // 42016 + kTIFF_CameraOwnerName, // 42032 + kTIFF_BodySerialNumber, // 42033 + kTIFF_LensSpecification, // 42034 + kTIFF_LensMake, // 42035 + kTIFF_LensModel, // 42036 + kTIFF_LensSerialNumber, // 42037 + kTIFF_Gamma, // 42240 + 0xFFFF // Must be last as a sentinel. +}; + +static const XMP_Uns16 sKnownGPSInfoIFDTags[] = +{ + kTIFF_GPSVersionID, // 0 + kTIFF_GPSLatitudeRef, // 1 + kTIFF_GPSLatitude, // 2 + kTIFF_GPSLongitudeRef, // 3 + kTIFF_GPSLongitude, // 4 + kTIFF_GPSAltitudeRef, // 5 + kTIFF_GPSAltitude, // 6 + kTIFF_GPSTimeStamp, // 7 + kTIFF_GPSSatellites, // 8 + kTIFF_GPSStatus, // 9 + kTIFF_GPSMeasureMode, // 10 + kTIFF_GPSDOP, // 11 + kTIFF_GPSSpeedRef, // 12 + kTIFF_GPSSpeed, // 13 + kTIFF_GPSTrackRef, // 14 + kTIFF_GPSTrack, // 15 + kTIFF_GPSImgDirectionRef, // 16 + kTIFF_GPSImgDirection, // 17 + kTIFF_GPSMapDatum, // 18 + kTIFF_GPSDestLatitudeRef, // 19 + kTIFF_GPSDestLatitude, // 20 + kTIFF_GPSDestLongitudeRef, // 21 + kTIFF_GPSDestLongitude, // 22 + kTIFF_GPSDestBearingRef, // 23 + kTIFF_GPSDestBearing, // 24 + kTIFF_GPSDestDistanceRef, // 25 + kTIFF_GPSDestDistance, // 26 + kTIFF_GPSProcessingMethod, // 27 + kTIFF_GPSAreaInformation, // 28 + kTIFF_GPSDateStamp, // 29 + kTIFF_GPSDifferential, // 30 + kTIFF_GPSHPositioningError, // 31 + 0xFFFF // Must be last as a sentinel. +}; + +static const XMP_Uns16 sKnownInteroperabilityIFDTags[] = +{ + // ! Yes, there are none at present. + 0xFFFF // Must be last as a sentinel. +}; + +// ! Make sure these are in the same order as the IFD enum! +static const XMP_Uns16* sKnownTags[kTIFF_KnownIFDCount] = { sKnownPrimaryIFDTags, + sKnownThumbnailIFDTags, + sKnownExifIFDTags, + sKnownGPSInfoIFDTags, + sKnownInteroperabilityIFDTags }; + + +// ================================================================================================= +// ================================================================================================= + + +// ================================================================================================= +// TIFF_Manager +// ============ + +class TIFF_Manager { // The abstract base class. +public: + + // --------------------------------------------------------------------------------------------- + // Types and constants + + static const XMP_Uns32 kBigEndianPrefix = 0x4D4D002AUL; + static const XMP_Uns32 kLittleEndianPrefix = 0x49492A00UL; + + static const size_t kEmptyTIFFLength = 8; // Just the header. + static const size_t kEmptyIFDLength = 2 + 4; // Entry count and next-IFD offset. + static const size_t kIFDEntryLength = 12; + + struct TagInfo { + XMP_Uns16 id; + XMP_Uns16 type; + XMP_Uns32 count; + const void* dataPtr; // ! The data must not be changed! Stream endian format! + XMP_Uns32 dataLen; // The length in bytes. + TagInfo() : id(0), type(0), count(0), dataPtr(0), dataLen(0) {}; + TagInfo ( XMP_Uns16 _id, XMP_Uns16 _type, XMP_Uns32 _count, const void* _dataPtr, XMP_Uns32 _dataLen ) + : id(_id), type(_type), count(_count), dataPtr(_dataPtr), dataLen(_dataLen) {}; + }; + + typedef std::map TagInfoMap; + + struct Rational { XMP_Uns32 num, denom; }; + struct SRational { XMP_Int32 num, denom; }; + + // --------------------------------------------------------------------------------------------- + // The IsXyzEndian methods return the external endianness of the original parsed TIFF stream. + // The \c GetTag methods return native endian values, the \c SetTag methods take native values. + // The original endianness is preserved in output. + + bool IsBigEndian() const { return this->bigEndian; }; + bool IsLittleEndian() const { return (! this->bigEndian); }; + bool IsNativeEndian() const { return this->nativeEndian; }; + + // --------------------------------------------------------------------------------------------- + // The TIFF_Manager only keeps explicit knowledge of up to 4 IFDs: + // - The primary image IFD, also known as the 0th IFD. This must be present. + // - A possible Exif general metadata IFD, found from tag 34665 in the primary image IFD. + // - A possible Exif GPS metadata IFD, found from tag 34853 in the primary image IFD. + // - A possible Exif Interoperability IFD, found from tag 40965 in the Exif general metadata IFD. + // + // Parsing will silently forget about certain aspects of ill-formed streams. If any tags are + // repeated in an IFD, only the last is kept. Any known tags that are in the wrong IFD are + // removed. Parsing will sort the tags into ascending order, AppendTIFF and ComposeTIFF will + // preserve the sorted order. These fixes do not cause IsChanged to return true, that only + // happens if the client makes explicit changes using SetTag or DeleteTag. + + virtual bool HasExifIFD() const = 0; + virtual bool HasGPSInfoIFD() const = 0; + + // --------------------------------------------------------------------------------------------- + // These are the basic methods to get a map of all of the tags in an IFD, to get or set a tag, + // or to delete a tag. The dataPtr returned by \c GetTag is consided read-only, the client must + // not change it. The ifd parameter to \c GetIFD must be for one of the recognized actual IFDs. + // The ifd parameter for the GetTag or SetTag methods can be a specific IFD of kTIFF_KnownIFD. + // Using the specific IFD will be slightly faster, saving a lookup in the known tag map. An + // exception is thrown if kTIFF_KnownIFD is passed to GetTag or SetTag and the tag is not known. + // \c SetTag replaces an existing tag regardless of type or count. \c DeleteTag deletes a tag, + // it is a no-op if the tag does not exist. \c GetValueOffset returns the offset within the + // parsed stream of the tag's value. It returns 0 if the tag was not in the parsed input. + + virtual bool GetIFD ( XMP_Uns8 ifd, TagInfoMap* ifdMap ) const = 0; + + virtual bool GetTag ( XMP_Uns8 ifd, XMP_Uns16 id, TagInfo* info ) const = 0; + + virtual void SetTag ( XMP_Uns8 ifd, XMP_Uns16 id, XMP_Uns16 type, XMP_Uns32 count, const void* dataPtr ) = 0; + + virtual void DeleteTag ( XMP_Uns8 ifd, XMP_Uns16 id ) = 0; + + virtual XMP_Uns32 GetValueOffset ( XMP_Uns8 ifd, XMP_Uns16 id ) const = 0; + + // --------------------------------------------------------------------------------------------- + // These methods are for tags whose type can be short or long, depending on the actual value. + // \c GetTag_Integer returns false if an existing tag's type is not short, or long, or if the + // count is not 1. \c SetTag_Integer replaces an existing tag regardless of type or count, the + // new tag will have type short or long. + + virtual bool GetTag_Integer ( XMP_Uns8 ifd, XMP_Uns16 id, XMP_Uns32* data ) const = 0; + + void SetTag_Integer ( XMP_Uns8 ifd, XMP_Uns16 id, XMP_Uns32 data ); + + // --------------------------------------------------------------------------------------------- + // These are customized forms of GetTag that verify the type and return a typed value. False is + // returned if the type does not match or if the count is not 1. + + // *** Should also add support for ASCII multi-strings? + + virtual bool GetTag_Byte ( XMP_Uns8 ifd, XMP_Uns16 id, XMP_Uns8* data ) const = 0; + virtual bool GetTag_SByte ( XMP_Uns8 ifd, XMP_Uns16 id, XMP_Int8* data ) const = 0; + virtual bool GetTag_Short ( XMP_Uns8 ifd, XMP_Uns16 id, XMP_Uns16* data ) const = 0; + virtual bool GetTag_SShort ( XMP_Uns8 ifd, XMP_Uns16 id, XMP_Int16* data ) const = 0; + virtual bool GetTag_Long ( XMP_Uns8 ifd, XMP_Uns16 id, XMP_Uns32* data ) const = 0; + virtual bool GetTag_SLong ( XMP_Uns8 ifd, XMP_Uns16 id, XMP_Int32* data ) const = 0; + + virtual bool GetTag_Rational ( XMP_Uns8 ifd, XMP_Uns16 id, Rational* data ) const = 0; + virtual bool GetTag_SRational ( XMP_Uns8 ifd, XMP_Uns16 id, SRational* data ) const = 0; + + virtual bool GetTag_Float ( XMP_Uns8 ifd, XMP_Uns16 id, float* data ) const = 0; + virtual bool GetTag_Double ( XMP_Uns8 ifd, XMP_Uns16 id, double* data ) const = 0; + + virtual bool GetTag_ASCII ( XMP_Uns8 ifd, XMP_Uns16 id, XMP_StringPtr* dataPtr, XMP_StringLen* dataLen ) const = 0; + + // --------------------------------------------------------------------------------------------- + + void SetTag_Byte ( XMP_Uns8 ifd, XMP_Uns16 id, XMP_Uns8 data ); + void SetTag_SByte ( XMP_Uns8 ifd, XMP_Uns16 id, XMP_Int8 data ); + void SetTag_Short ( XMP_Uns8 ifd, XMP_Uns16 id, XMP_Uns16 data ); + void SetTag_SShort ( XMP_Uns8 ifd, XMP_Uns16 id, XMP_Int16 data ); + void SetTag_Long ( XMP_Uns8 ifd, XMP_Uns16 id, XMP_Uns32 data ); + void SetTag_SLong ( XMP_Uns8 ifd, XMP_Uns16 id, XMP_Int32 data ); + + void SetTag_Rational ( XMP_Uns8 ifd, XMP_Uns16 id, XMP_Uns32 num, XMP_Uns32 denom ); + void SetTag_SRational ( XMP_Uns8 ifd, XMP_Uns16 id, XMP_Int32 num, XMP_Int32 denom ); + + void SetTag_Float ( XMP_Uns8 ifd, XMP_Uns16 id, float data ); + void SetTag_Double ( XMP_Uns8 ifd, XMP_Uns16 id, double data ); + + void SetTag_ASCII ( XMP_Uns8 ifd, XMP_Uns16 id, XMP_StringPtr dataPtr ); + + // --------------------------------------------------------------------------------------------- + + virtual bool GetTag_EncodedString ( XMP_Uns8 ifd, XMP_Uns16 id, std::string* utf8Str ) const = 0; + virtual void SetTag_EncodedString ( XMP_Uns8 ifd, XMP_Uns16 id, const std::string& utf8Str, XMP_Uns8 encoding ) = 0; + + bool DecodeString ( const void * encodedPtr, size_t encodedLen, std::string* utf8Str ) const; + bool EncodeString ( const std::string& utf8Str, XMP_Uns8 encoding, std::string* encodedStr ); + + // --------------------------------------------------------------------------------------------- + // \c IsChanged returns true if a read-write stream has changes that need to be saved. This is + // only the case when a \c SetTag method has been called. It is not true for changes related to + // parsing normalization such as sorting of tags. \c IsChanged returns false for read-only streams. + + virtual bool IsChanged() = 0; + + // --------------------------------------------------------------------------------------------- + // \c IsLegacyChanged returns true if a read-write stream has changes that need to be saved to + // tags other than the XMP (tag 700). This only the case when a \c SetTag method has been + // called. It is not true for changes related to parsing normalization such as sorting of tags. + // \c IsLegacyChanged returns false for read-only streams. + + virtual bool IsLegacyChanged() = 0; + + // --------------------------------------------------------------------------------------------- + // \c UpdateMemoryStream is mainly applicable to memory-based read-write streams. It recomposes + // the memory stream to incorporate all changes. The new length and data pointer are returned. + // \c UpdateMemoryStream can be used with a read-only memory stream to get the raw stream info. + // + // \c UpdateFileStream updates file-based TIFF. The client must guarantee that the TIFF portion + // of the file matches that from the parse in the file-based constructor. Offsets saved from that + // parse must still be valid. The open file reference need not be the same, e.g. the client can + // be doing a crash-safe update into a temporary copy. + // + // Both \c UpdateMemoryStream and \c UpdateFileStream use an update-by-append model. Changes are + // written in-place where they fit, anything requiring growth is appended to the end and the old + // space is abandoned. The end for memory-based TIFF is the end of the data block, the end for + // file-based TIFF is the end of the file. This update-by-append model has the advantage of not + // perturbing any hidden offsets, a common feature of proprietary MakerNotes. + // + // The condenseStream parameter to UpdateMemoryStream can be used to rewrite the full stream + // instead of appending. This will discard any MakerNote tags and risks breaking offsets that + // are hidden. This can be necessary though to try to make the TIFF fit in a JPEG file. + + // isAlredyLittle is provided for case when data contain no information about Endianess, So need not to check for header + virtual void ParseMemoryStream ( const void* data, XMP_Uns32 length, bool copyData = true, bool isAlreadyLittle = false ) = 0; + virtual void ParseFileStream ( XMP_IO* fileRef ) = 0; + + virtual void IntegrateFromPShop6 ( const void * buriedPtr, size_t buriedLen ) = 0; + + virtual XMP_Uns32 UpdateMemoryStream ( void** dataPtr, bool condenseStream = false ) = 0; + virtual void UpdateFileStream ( XMP_IO* fileRef, XMP_ProgressTracker* progressTracker ) = 0; + + // --------------------------------------------------------------------------------------------- + + GetUns16_Proc GetUns16; // Get values from the TIFF stream. + GetUns32_Proc GetUns32; // Always native endian on the outside, stream endian in the stream. + GetFloat_Proc GetFloat; + GetDouble_Proc GetDouble; + + PutUns16_Proc PutUns16; // Put values into the TIFF stream. + PutUns32_Proc PutUns32; // Always native endian on the outside, stream endian in the stream. + PutFloat_Proc PutFloat; + PutDouble_Proc PutDouble; + + virtual ~TIFF_Manager() {}; + + virtual void SetErrorCallback ( GenericErrorCallback * ec ) { this->errorCallbackPtr = ec; }; + + virtual void NotifyClient ( XMP_ErrorSeverity severity, XMP_Error & error ); + +protected: + + bool bigEndian, nativeEndian; + + XMP_Uns32 CheckTIFFHeader ( const XMP_Uns8* tiffPtr, XMP_Uns32 length ); + // The pointer is to a buffer of the first 8 bytes. The length is the overall length, used + // to check the primary IFD offset. + + TIFF_Manager(); // Force clients to use the reader or writer derived classes. + + struct RawIFDEntry { + XMP_Uns16 id; + XMP_Uns16 type; + XMP_Uns32 count; + XMP_Uns32 dataOrOffset; + }; + + GenericErrorCallback *errorCallbackPtr; + +}; // TIFF_Manager + + +// ================================================================================================= +// ================================================================================================= + + +// ================================================================================================= +// TIFF_MemoryReader +// ================= + +class TIFF_MemoryReader : public TIFF_Manager { // The derived class for memory-based read-only access. +public: + + bool HasExifIFD() const { return (containedIFDs[kTIFF_ExifIFD].count != 0); }; + bool HasGPSInfoIFD() const { return (containedIFDs[kTIFF_GPSInfoIFD].count != 0); }; + + bool GetIFD ( XMP_Uns8 ifd, TagInfoMap* ifdMap ) const; + + bool GetTag ( XMP_Uns8 ifd, XMP_Uns16 id, TagInfo* info ) const; + + void SetTag ( XMP_Uns8 ifd, XMP_Uns16 id, XMP_Uns16 type, XMP_Uns32 count, const void* dataPtr ) { NotAppropriate(); }; + + void DeleteTag ( XMP_Uns8 ifd, XMP_Uns16 id ) { NotAppropriate(); }; + + XMP_Uns32 GetValueOffset ( XMP_Uns8 ifd, XMP_Uns16 id ) const; + + bool GetTag_Integer ( XMP_Uns8 ifd, XMP_Uns16 id, XMP_Uns32* data ) const; + + bool GetTag_Byte ( XMP_Uns8 ifd, XMP_Uns16 id, XMP_Uns8* data ) const; + bool GetTag_SByte ( XMP_Uns8 ifd, XMP_Uns16 id, XMP_Int8* data ) const; + bool GetTag_Short ( XMP_Uns8 ifd, XMP_Uns16 id, XMP_Uns16* data ) const; + bool GetTag_SShort ( XMP_Uns8 ifd, XMP_Uns16 id, XMP_Int16* data ) const; + bool GetTag_Long ( XMP_Uns8 ifd, XMP_Uns16 id, XMP_Uns32* data ) const; + bool GetTag_SLong ( XMP_Uns8 ifd, XMP_Uns16 id, XMP_Int32* data ) const; + + bool GetTag_Rational ( XMP_Uns8 ifd, XMP_Uns16 id, Rational* data ) const; + bool GetTag_SRational ( XMP_Uns8 ifd, XMP_Uns16 id, SRational* data ) const; + + bool GetTag_Float ( XMP_Uns8 ifd, XMP_Uns16 id, float* data ) const; + bool GetTag_Double ( XMP_Uns8 ifd, XMP_Uns16 id, double* data ) const; + + bool GetTag_ASCII ( XMP_Uns8 ifd, XMP_Uns16 id, XMP_StringPtr* dataPtr, XMP_StringLen* dataLen ) const; + + bool GetTag_EncodedString ( XMP_Uns8 ifd, XMP_Uns16 id, std::string* utf8Str ) const; + + void SetTag_EncodedString ( XMP_Uns8 ifd, XMP_Uns16 id, const std::string& utf8Str, XMP_Uns8 encoding ) { NotAppropriate(); }; + + bool IsChanged() { return false; }; + bool IsLegacyChanged() { return false; }; + + // isAlredyLittle is provided for case when data contain no information about Endianess, So need not to check for header + void ParseMemoryStream ( const void* data, XMP_Uns32 length, bool copyData = true, bool isAlreadyLittle = false ); + void ParseFileStream ( XMP_IO* fileRef ) { NotAppropriate(); }; + + void IntegrateFromPShop6 ( const void * buriedPtr, size_t buriedLen ) { NotAppropriate(); }; + + XMP_Uns32 UpdateMemoryStream ( void** dataPtr, bool condenseStream = false ) { if ( dataPtr != 0 ) *dataPtr = tiffStream; return tiffLength; }; + void UpdateFileStream ( XMP_IO* fileRef, XMP_ProgressTracker* progressTracker ) { NotAppropriate(); }; + + TIFF_MemoryReader() : ownedStream(false), tiffStream(0), tiffLength(0) {}; + + virtual ~TIFF_MemoryReader() { if ( this->ownedStream ) free ( this->tiffStream ); }; + +private: + + bool ownedStream; + + XMP_Uns8* tiffStream; + XMP_Uns32 tiffLength; + + // Memory usage notes: TIFF_MemoryReader is for memory-based read-only usage (both apply). There + // is no need to ever allocate separate blocks of memory, everything is used directly from the + // TIFF stream. Data pointers are computed on the fly, the offset field is 4 bytes and pointers + // will be 8 bytes for 64-bit platforms. + + struct TweakedIFDEntry { // ! Most fields are in native byte order, dataOrPos is for offsets only. + XMP_Uns16 id; + XMP_Uns16 type; + XMP_Uns32 bytes; + XMP_Uns32 dataOrPos; + TweakedIFDEntry() : id(0), type(0), bytes(0), dataOrPos(0) {}; + }; + + struct TweakedIFDInfo { + XMP_Uns16 count; + TweakedIFDEntry* entries; + TweakedIFDInfo() : count(0), entries(0) {}; + }; + + TweakedIFDInfo containedIFDs[kTIFF_KnownIFDCount]; + + static void SortIFD ( TweakedIFDInfo* thisIFD ); + + // ModifiedInitialCheck is provided for case when data contain no header + XMP_Uns32 ProcessOneIFD ( XMP_Uns32 ifdOffset, XMP_Uns8 ifd, bool ModifiedInitialCheck = false ); + + const TweakedIFDEntry* FindTagInIFD ( XMP_Uns8 ifd, XMP_Uns16 id ) const; + + const inline void* GetDataPtr ( const TweakedIFDEntry* tifdEntry ) const + { if ( GetUns32AsIs(&tifdEntry->bytes) <= 4 ) { + return &tifdEntry->dataOrPos; + } else { + return (this->tiffStream + GetUns32AsIs(&tifdEntry->dataOrPos)); + } + } + + static inline void NotAppropriate() { XMP_Throw ( "Not appropriate for TIFF_Reader", kXMPErr_InternalFailure ); }; + +}; // TIFF_MemoryReader + + +// ================================================================================================= +// ================================================================================================= + + +// ================================================================================================= +// TIFF_FileWriter +// =============== + +class TIFF_FileWriter : public TIFF_Manager { // The derived class for file-based or read-write access. +public: + + bool HasExifIFD() const { return this->containedIFDs[kTIFF_ExifIFD].tagMap.size() != 0; }; + bool HasGPSInfoIFD() const { return this->containedIFDs[kTIFF_GPSInfoIFD].tagMap.size() != 0; }; + + bool GetIFD ( XMP_Uns8 ifd, TagInfoMap* ifdMap ) const; + + bool GetTag ( XMP_Uns8 ifd, XMP_Uns16 id, TagInfo* info ) const; + + void SetTag ( XMP_Uns8 ifd, XMP_Uns16 id, XMP_Uns16 type, XMP_Uns32 count, const void* dataPtr ); + + void DeleteTag ( XMP_Uns8 ifd, XMP_Uns16 id ); + + XMP_Uns32 GetValueOffset ( XMP_Uns8 ifd, XMP_Uns16 id ) const; + + bool GetTag_Integer ( XMP_Uns8 ifd, XMP_Uns16 id, XMP_Uns32* data ) const; + + bool GetTag_Byte ( XMP_Uns8 ifd, XMP_Uns16 id, XMP_Uns8* data ) const; + bool GetTag_SByte ( XMP_Uns8 ifd, XMP_Uns16 id, XMP_Int8* data ) const; + bool GetTag_Short ( XMP_Uns8 ifd, XMP_Uns16 id, XMP_Uns16* data ) const; + bool GetTag_SShort ( XMP_Uns8 ifd, XMP_Uns16 id, XMP_Int16* data ) const; + bool GetTag_Long ( XMP_Uns8 ifd, XMP_Uns16 id, XMP_Uns32* data ) const; + bool GetTag_SLong ( XMP_Uns8 ifd, XMP_Uns16 id, XMP_Int32* data ) const; + + bool GetTag_Rational ( XMP_Uns8 ifd, XMP_Uns16 id, Rational* data ) const; + bool GetTag_SRational ( XMP_Uns8 ifd, XMP_Uns16 id, SRational* data ) const; + + bool GetTag_Float ( XMP_Uns8 ifd, XMP_Uns16 id, float* data ) const; + bool GetTag_Double ( XMP_Uns8 ifd, XMP_Uns16 id, double* data ) const; + + bool GetTag_ASCII ( XMP_Uns8 ifd, XMP_Uns16 id, XMP_StringPtr* dataPtr, XMP_StringLen* dataLen ) const; + + bool GetTag_EncodedString ( XMP_Uns8 ifd, XMP_Uns16 id, std::string* utf8Str ) const; + + void SetTag_EncodedString ( XMP_Uns8 ifd, XMP_Uns16 id, const std::string& utf8Str, XMP_Uns8 encoding ); + + bool IsChanged() { return this->changed; }; + + bool IsLegacyChanged(); + + enum { kDoNotCopyData = false }; + + // isAlredyLittle is provided for case when data contain no information about Endianess, So need not to check for header + void ParseMemoryStream ( const void* data, XMP_Uns32 length, bool copyData = true, bool isAlreadyLittle = false ); + void ParseFileStream ( XMP_IO* fileRef ); + + void IntegrateFromPShop6 ( const void * buriedPtr, size_t buriedLen ); + + XMP_Uns32 UpdateMemoryStream ( void** dataPtr, bool condenseStream = false ); + void UpdateFileStream ( XMP_IO* fileRef, XMP_ProgressTracker* progressTracker ); + + TIFF_FileWriter(); + + virtual ~TIFF_FileWriter(); + +private: + + bool changed, legacyDeleted; + bool memParsed, fileParsed; + bool ownedStream; + + XMP_Uns8* memStream; + XMP_Uns32 tiffLength; + + // Memory usage notes: TIFF_FileWriter is for file-based OR read/write usage. For memory-based + // streams the dataPtr is initially into the stream, regardless of size. For file-based streams + // the dataPtr is initially a separate allocation for large values (over 4 bytes), and points to + // the smallValue field for small values. This is also the usage when a tag is changed (for both + // memory and file cases), the dataPtr is a separate allocation for large values (over 4 bytes), + // and points to the smallValue field for small values. + + // ! The working data values are always stream endian, no matter where stored. They are flipped + // ! as necessary by GetTag and SetTag. + + static const bool kIsFileBased = true; // For use in the InternalTagInfo constructor. + static const bool kIsMemoryBased = false; + + class InternalTagInfo { + public: + + XMP_Uns16 id; + XMP_Uns16 type; + XMP_Uns32 count; + XMP_Uns32 dataLen; + XMP_Uns32 smallValue; // Small value in stream endianness, but "left" justified. + XMP_Uns8* dataPtr; // Parsing captures all small values, only large ones that we care about. + XMP_Uns32 origDataLen; // The original (parse time) data length in bytes. + XMP_Uns32 origDataOffset; // The original data offset, regardless of length. + bool changed; + bool fileBased; + + inline void FreeData() { + if ( this->fileBased || this->changed ) { + if ( (this->dataLen > 4) && (this->dataPtr != 0) ) { free ( this->dataPtr ); this->dataPtr = 0; } + } + } + + InternalTagInfo ( XMP_Uns16 _id, XMP_Uns16 _type, XMP_Uns32 _count, bool _fileBased ) + : id(_id), type(_type), count(_count), dataLen(0), smallValue(0), dataPtr(0), + origDataLen(0), origDataOffset(0), changed(false), fileBased(_fileBased) {}; + ~InternalTagInfo() { this->FreeData(); }; + + void operator= ( const InternalTagInfo & in ) + { + // ! Gag! Transfer ownership of the dataPtr! + this->FreeData(); + memcpy ( this, &in, sizeof(*this) ); // AUDIT: Use of sizeof(InternalTagInfo) is safe. + if ( this->dataLen <= 4 ) { + this->dataPtr = (XMP_Uns8*) &this->smallValue; // Don't use the copied pointer. + } else { + *((XMP_Uns8**)&in.dataPtr) = 0; // The pointer is now owned by "this". + } + }; + + private: + + InternalTagInfo() // Hidden on purpose, fileBased must be properly set. + : id(0), type(0), count(0), dataLen(0), smallValue(0), dataPtr(0), + origDataLen(0), origDataOffset(0), changed(false), fileBased(false) {}; + + }; + + typedef std::map InternalTagMap; + + struct InternalIFDInfo { + bool changed; + XMP_Uns16 origCount; // Original number of IFD entries. + XMP_Uns32 origIFDOffset; // Original stream offset of the IFD. + XMP_Uns32 origNextIFD; // Original stream offset of the following IFD. + InternalTagMap tagMap; + InternalIFDInfo() : changed(false), origCount(0), origIFDOffset(0), origNextIFD(0) {}; + inline void clear() + { + this->changed = false; + this->origCount = 0; + this->origIFDOffset = this->origNextIFD = 0; + this->tagMap.clear(); + }; + }; + + InternalIFDInfo containedIFDs[kTIFF_KnownIFDCount]; + + static XMP_Uns8 PickIFD ( XMP_Uns8 ifd, XMP_Uns16 id ); + const InternalTagInfo* FindTagInIFD ( XMP_Uns8 ifd, XMP_Uns16 id ) const; + + void DeleteExistingInfo(); + + XMP_Uns32 ProcessMemoryIFD ( XMP_Uns32 ifdOffset, XMP_Uns8 ifd ); + XMP_Uns32 ProcessFileIFD ( XMP_Uns8 ifd, XMP_Uns32 ifdOffset, XMP_IO* fileRef ); + + void ProcessPShop6IFD ( const TIFF_MemoryReader& buriedExif, XMP_Uns8 ifd ); + + void* CopyTagToMasterIFD ( const TagInfo& ps6Tag, InternalIFDInfo* masterIFD ); + + void PreflightIFDLinkage(); + + XMP_Uns32 DetermineVisibleLength(); + + XMP_Uns32 DetermineAppendInfo ( XMP_Uns32 appendedOrigin, + bool appendedIFDs[kTIFF_KnownIFDCount], + XMP_Uns32 newIFDOffsets[kTIFF_KnownIFDCount], + bool appendAll = false ); + + void UpdateMemByAppend ( XMP_Uns8** newStream_out, XMP_Uns32* newLength_out, + bool appendAll = false, XMP_Uns32 extraSpace = 0 ); + void UpdateMemByRewrite ( XMP_Uns8** newStream_out, XMP_Uns32* newLength_out ); + + void WriteFileIFD ( XMP_IO* fileRef, InternalIFDInfo & thisIFD ); + +}; // TIFF_FileWriter + +XMP_Bool IsOffsetValid( XMP_Uns32 offset, XMP_Uns32 lowerBound, XMP_Uns32 upperBound ); + +// ================================================================================================= + +#endif // __TIFF_Support_hpp__ diff --git a/core/libs/dngwriter/extra/xmp_sdk/XMPFiles/source/FormatSupport/TimeConversionUtils.cpp b/core/libs/dngwriter/extra/xmp_sdk/XMPFiles/source/FormatSupport/TimeConversionUtils.cpp new file mode 100644 index 0000000000..2dbb4594f2 --- /dev/null +++ b/core/libs/dngwriter/extra/xmp_sdk/XMPFiles/source/FormatSupport/TimeConversionUtils.cpp @@ -0,0 +1,599 @@ +// ================================================================================================= +// ADOBE SYSTEMS INCORPORATED +// Copyright 2014 Adobe Systems Incorporated +// All Rights Reserved +// +// NOTICE: Adobe permits you to use, modify, and distribute this file in accordance with the terms +// of the Adobe license agreement accompanying it. +// ================================================================================================= + +#include "public/include/XMP_Environment.h" // ! XMP_Environment.h must be the first included header. +#include "public/include/XMP_Const.h" + +#include "XMPFiles/source/FormatSupport/TimeConversionUtils.hpp" + +#include +#include +#include +#include + +namespace TimeConversionUtils { + + void DropFrameToHMSF( + XMP_Int64 inFrames, + XMP_Int64 inTimecodeFPS, + XMP_Uns32& outHours, + XMP_Uns32& outMinutes, + XMP_Uns32& outSeconds, + XMP_Uns32& outFrames) + { + XMP_Assert((inTimecodeFPS == 30) || (inTimecodeFPS == 60)); // No other drop frame rates are known at this time. + + XMP_Int64 rateAdjustmentFactor = inTimecodeFPS / 30; + XMP_Int64 framesPerHour = (30 * 3600 - 108) * rateAdjustmentFactor; + XMP_Int64 framesPer10Minutes = (30 * 600 - 18) * rateAdjustmentFactor; + XMP_Int64 framesPerMinute = 30 * 60 * rateAdjustmentFactor; + XMP_Int64 framesPerSecond = 30 * rateAdjustmentFactor; + XMP_Int64 dropsPerMinute = 2 * rateAdjustmentFactor; + + XMP_Int64 currentFrames = inFrames; + XMP_Int64 framesLeft = currentFrames; + if (currentFrames < 0) + { + framesLeft = -currentFrames; + } + if (framesLeft >= framesPerHour) + { + outHours = static_cast(framesLeft / framesPerHour); + framesLeft = framesLeft % framesPerHour; + } + if (framesLeft >= framesPer10Minutes) + { + outMinutes = static_cast(framesLeft / framesPer10Minutes) * 10; + framesLeft = framesLeft % framesPer10Minutes; + } + if (framesLeft >= framesPerMinute) + { + XMP_Int64 remainingDropMinutes = static_cast((framesLeft - framesPerMinute) / + (framesPerMinute - dropsPerMinute)); + ++remainingDropMinutes; + + outMinutes += static_cast(remainingDropMinutes); + framesLeft -= ((framesPerMinute - dropsPerMinute) * remainingDropMinutes); + } + if (framesLeft >= framesPerSecond) + { + outSeconds = static_cast(framesLeft / framesPerSecond); + } + outFrames = static_cast(framesLeft % framesPerSecond); + } + + bool ConvertSamplesToTimecode( + std::string & outTimecode, + XMP_Int64 inSamples, + XMP_Uns64 inSampleRate, + XMP_Int64 inTimecodeFPS, + bool inIsDrop, + bool inIsNoDrop, + bool inShowOnlyFrames = false, + bool inOnlyShowSeconds = false , + bool inNoZeroPrefix = false , + bool inShowFractional = false , + bool inNoHours = false ) + { + if (!(inIsDrop ? !inIsNoDrop : true)) + { + XMP_Assert( !(inIsDrop ? !inIsNoDrop : true) ); + return false; + } + + if (inSampleRate == 0) + { + outTimecode = "00:00:00:00"; + return true; + } + + std::string possibleNegStr; + if (inSamples < 0) + { + inSamples *= -1; + possibleNegStr = "-"; + } + + XMP_Int64 rateNumerator = inTimecodeFPS; + XMP_Int64 rateDenominator = 1; + if (inIsDrop || inIsNoDrop) + { + rateNumerator = 1000 * inTimecodeFPS; + rateDenominator = 1001; + } + + XMP_Int64 frameNumber = (inSamples * rateNumerator) / (inSampleRate * rateDenominator); + XMP_Int64 hundredthsOfFrames = ((inSamples * rateNumerator * 100) / (inSampleRate * rateDenominator)) % 100; + + std::stringstream stream; + double fSamples = static_cast(inSamples); + double fSampleRate = static_cast(inSampleRate); + + if (inIsDrop) + { + if (inShowOnlyFrames) + { + double fAdjustmentFactor = static_cast(inTimecodeFPS) / 30.0; + double fCorrectionRatio = (600.0 * static_cast(inTimecodeFPS) / 1.001) / (17982.0 * fAdjustmentFactor); + double fValue = fSamples * fCorrectionRatio / fSampleRate; + + // "%ld" + stream << static_cast(fValue * 29.97 * fAdjustmentFactor); + } + else + { + XMP_Uns32 hours = 0; + XMP_Uns32 minutes = 0; + XMP_Uns32 seconds = 0; + XMP_Uns32 frames = 0; + + DropFrameToHMSF( + frameNumber, + inTimecodeFPS, + hours, + minutes, + seconds, + frames); + + hours = hours % 24; + // "%02d;%02d;%02d;%02d" + stream << possibleNegStr << std::setfill('0') << std::setw(2) << static_cast(hours) + << ";" + << std::setfill('0') << std::setw(2) << static_cast(minutes) + << ";" + << std::setfill('0') << std::setw(2) << static_cast(seconds) + << ";" + << std::setfill('0') << std::setw(2) << static_cast(frames); + possibleNegStr.clear(); + } + } + else + { + if (inShowOnlyFrames) + { + // "%ld" + stream << static_cast(frameNumber); + } + else + { + XMP_Int64 framesPerMinute = inTimecodeFPS * 60; + XMP_Int64 framesPerHour = framesPerMinute * 60; + + XMP_Int64 iHours = frameNumber / framesPerHour; + frameNumber %= framesPerHour; + XMP_Int64 mins = frameNumber / framesPerMinute; + frameNumber %= framesPerMinute; + XMP_Int64 seconds = frameNumber / inTimecodeFPS; + XMP_Int64 ss = frameNumber % inTimecodeFPS; + XMP_Int64 s = seconds; + + if (inNoHours) + { + mins += iHours * 60; + iHours = 0; + } + + if (((iHours) || (!inNoZeroPrefix)) && (!inNoHours)) + { + iHours = iHours % 24; + // "%02ld:" + stream << possibleNegStr << std::setfill('0') << std::setw(2) << static_cast(iHours) + << ":"; + possibleNegStr.clear(); + } + + if ((iHours) || (!inNoZeroPrefix)) + { + // "%02ld:" + stream << possibleNegStr << std::setfill('0') << std::setw(2) << static_cast(mins) + << ":"; + possibleNegStr.clear(); + } + else if (mins) + { + // "%ld:" + stream << possibleNegStr << static_cast(mins) + << ":"; + possibleNegStr.clear(); + } + + if (inOnlyShowSeconds) + { + // "%02ld" + stream << possibleNegStr << std::setfill('0') << std::setw(2) << static_cast(s); + possibleNegStr.clear(); + } + else + { + if ((iHours) || (mins) || (!inNoZeroPrefix)) + { + // "%02ld:" + stream << possibleNegStr << std::setfill('0') << std::setw(2) << static_cast(s) + << ":"; + possibleNegStr.clear(); + } + else if (s) + { + // "%ld:" + stream << possibleNegStr << static_cast(s) + << ":"; + possibleNegStr.clear(); + } + + if ((iHours) || (mins) || (s) || (!inNoZeroPrefix)) + { + if (inTimecodeFPS <= 10) + { + // "%01ld" + stream << possibleNegStr << std::setfill('0') << std::setw(1) << static_cast(ss); + possibleNegStr.clear(); + } + else if ((inTimecodeFPS <= 100)) + { + // "%02ld" + stream << possibleNegStr << std::setfill('0') << std::setw(2) << static_cast(ss); + possibleNegStr.clear(); + } + else if (inTimecodeFPS <= 1000) + { + // "%03ld" + stream << possibleNegStr << std::setfill('0') << std::setw(3) << static_cast(ss); + possibleNegStr.clear(); + } + else + { + // "%04ld" + stream << possibleNegStr << std::setfill('0') << std::setw(4) << static_cast(ss); + possibleNegStr.clear(); + } + } + else + { + // "%ld" + stream << possibleNegStr << static_cast(ss); + possibleNegStr.clear(); + } + + if (inShowFractional) + { + // ".%02d" + stream << possibleNegStr << "." + << std::setfill('0') << std::setw(2) << static_cast(hundredthsOfFrames); + possibleNegStr.clear(); + } + } + } + } + + outTimecode = stream.str(); + + return true; + } + + bool ConvertSamplesToSMPTETimecode( + std::string & outTimecode, + XMP_Int64 inSamples, + XMP_Uns64 inSampleRate, + const std::string & inTimecodeFormat ) + { + bool result = false; + + if ( inTimecodeFormat.compare( "24Timecode" ) == 0 ) { + result = ConvertSamplesToTimecode( outTimecode, inSamples, inSampleRate, 24, false, false ); + } else if ( inTimecodeFormat.compare( "25Timecode" ) == 0 ) { + result = ConvertSamplesToTimecode( outTimecode, inSamples, inSampleRate, 25, false, false ); + } else if ( inTimecodeFormat.compare( "2997DropTimecode") == 0 ) { + result = ConvertSamplesToTimecode( outTimecode, inSamples, inSampleRate, 30, true, false ); + } else if ( inTimecodeFormat.compare( "2997NonDropTimecode") == 0 ) { + result = ConvertSamplesToTimecode( outTimecode, inSamples, inSampleRate, 30, false, true ); + } else if ( inTimecodeFormat.compare( "30Timecode" ) == 0 ) { + result = ConvertSamplesToTimecode( outTimecode, inSamples, inSampleRate, 30, false, false ); + } else if ( inTimecodeFormat.compare( "50Timecode" ) == 0 ) { + result = ConvertSamplesToTimecode( outTimecode, inSamples, inSampleRate, 50, false, false ); + } else if ( inTimecodeFormat.compare( "5994DropTimecode" ) == 0 ) { + result = ConvertSamplesToTimecode( outTimecode, inSamples, inSampleRate, 60, true, false ); + } else if ( inTimecodeFormat.compare( "5994NonDropTimecode" ) == 0 ) { + result = ConvertSamplesToTimecode( outTimecode, inSamples, inSampleRate, 60, false, true ); + } else if ( inTimecodeFormat.compare( "60Timecode" ) == 0 ) { + result = ConvertSamplesToTimecode( outTimecode, inSamples, inSampleRate, 60, false, false ); + } else if ( inTimecodeFormat.compare( "23976Timecode" ) == 0 ) { + result = ConvertSamplesToTimecode(outTimecode, inSamples, inSampleRate, 24, false, true); + } + return result; + } + + bool StringToNumber( + XMP_Int32 & outNumber, + const std::string & inString ) + { + bool numberFound = false; + outNumber = 0; + for ( size_t i = 0, endIndex = inString.size(); i < endIndex; i++ ) { + XMP_Int32 digit = inString[i] - '0'; + if ( digit >= 0 && digit <= 9 ) { + outNumber *= 10; + outNumber += digit; + numberFound = true; + } else { + return numberFound; + } + } + return numberFound; + } + + void ParseTimeCodeString( + const std::string & inTimecode, + XMP_Int32 & outHours, + XMP_Int32 & outMinutes, + XMP_Int32 & outSeconds, + XMP_Int32 & outFrames, + XMP_Int32 & outFractionalFrameNumerator, + XMP_Int32 & outFractionalFrameDenominator ) + { + XMP_Int32 m1 = 0; + XMP_Int32 m2 = 0; + XMP_Int32 m3 = 0; + XMP_Int32 m4 = 0; + XMP_Int32 m5 = 0; + bool hasFoundDecimal = false; + XMP_Int32 digitCount = 0; + + outFractionalFrameNumerator = 0; + outFractionalFrameDenominator = 1; + + std::string::const_iterator iter = inTimecode.begin(); + std::string::const_iterator iterEnd = inTimecode.end(); + + while (1) + { // Skip leading white space + while ( iter != iterEnd && (*iter < '0' || *iter > '9') ) + { + if (*iter == '.') + hasFoundDecimal = true; + iter++; + } // hh:mm:ss:ff.ddd + if (iter == iterEnd) + break; + + if (!hasFoundDecimal) + { + // get MSB digits + StringToNumber(m1, std::string(iter, iterEnd)); + + // Skip the digits + while (iter != iterEnd && (*iter >= '0' && *iter <= '9')) + iter++; + + // Skip the white space, note if "." or ":" ("." signifies decimal portion of frame) + while ( iter != iterEnd && (*iter < '0' || *iter > '9')) + { + if (*iter == '.') + hasFoundDecimal = true; + iter++; + } + + if (iter == iterEnd) + break; + } + // shift and scan next MSB digits + + if (!hasFoundDecimal) + { + m2 = m1; + digitCount = static_cast< XMP_Int32 >( iterEnd - iter ); + StringToNumber(m1, std::string(iter, iterEnd)); + + // Skip the digits + while ( iter != iterEnd && (*iter >= '0' && *iter <= '9') ) + iter++; + + // Skip the white space, note if "." or ":" ("." signifies + // decimal portion of frame) + while (iter != iterEnd && (*iter < '0' || *iter > '9')) + { + if (*iter == '.') + hasFoundDecimal = true; + iter++; + } + } + + if (iter == iterEnd) + break; + + m3 = m2; + m2 = m1; + digitCount = static_cast< XMP_Int32 >( iterEnd - iter ); + StringToNumber(m1, std::string(iter, iterEnd)); + if (hasFoundDecimal) + break; + + while (iter != iterEnd && (*iter >= '0' && *iter <= '9')) + iter++; + + // Skip the white space, note if "." or ":" ("." signifies decimal portion of frame) + while (iter != iterEnd && (*iter < '0' || *iter > '9')) + { + if (*iter == '.') + hasFoundDecimal = true; + iter++; + } + if (iter == iterEnd) + break; + + m4 = m3; + m3 = m2; + m2 = m1; + digitCount = static_cast< XMP_Int32 >( iterEnd - iter ); + StringToNumber(m1, std::string(iter, iterEnd)); + if (hasFoundDecimal) + break; + + while (iter != iterEnd && (*iter >= '0' && *iter <= '9')) + { + iter++; + } + + // Skip the white space, note if "." or ":" ("." signifies decimal portion of frame) + while (iter != iterEnd && (*iter < '0' || *iter > '9')) + { + if (*iter == '.') + hasFoundDecimal = true; + iter++; + } + + if (iter == iterEnd) + break; + + m5 = m4; + m4 = m3; + m3 = m2; + m2 = m1; + digitCount = static_cast< XMP_Int32 >( iterEnd - iter ); + StringToNumber(m1, std::string(iter, iterEnd)); + break; + } + + if (hasFoundDecimal) + { + outFractionalFrameDenominator = static_cast(pow(10.0, digitCount) + 0.5); + outFractionalFrameNumerator = m1; + m1 = m2; + m2 = m3; + m3 = m4; + m4 = m5; + m5 = 0; + } + outHours = m4; + outMinutes = m3; + outSeconds = m2; + outFrames = m1; + } + + bool ConvertTimecodeToSamples( + XMP_Int64 & outSamples, + const std::string & inTimecode, + XMP_Uns64 inSampleRate, + XMP_Int64 inTimecodeFPS, + bool inNTSC, + bool inDropFrame) + { + /// @TODO: Ensure that negative and >64-bit values are OK and work as expected. + + if (inTimecode.empty()) + { + outSamples = static_cast(-1); + return true; + } + + XMP_Int32 hours; + XMP_Int32 minutes; + XMP_Int32 seconds; + XMP_Int32 frames; + XMP_Int32 fractionalFrameNumerator; + XMP_Int32 fractionalFrameDenominator; + + ParseTimeCodeString(inTimecode, hours, minutes, seconds, frames, fractionalFrameNumerator, fractionalFrameDenominator); + + XMP_Int64 framesPerSecond = inTimecodeFPS; + XMP_Int64 framesPerMinute = framesPerSecond * 60; + XMP_Int64 framesPerHour = framesPerMinute * 60; + XMP_Int64 wholeFrames = 0; + XMP_Int64 frameRateNumerator = inTimecodeFPS; + XMP_Int64 frameRateDenominator = 1; + + if (inNTSC) + { + frameRateNumerator = 1000 * inTimecodeFPS; + frameRateDenominator = 1001; + } + + if (inDropFrame) + { + XMP_Int64 frameGroupDropped = 2 * inTimecodeFPS / 30; // 2 or 4 frames dropped at a time. + XMP_Int64 framesPerHourDropped = 108 * inTimecodeFPS / 30; + framesPerHour -= framesPerHourDropped; + XMP_Int64 framesPerTenMinutes = framesPerHour / 6; + XMP_Assert( framesPerHour % 6 == 0 ); //, "Drop frame not supported on the given frame rate." + XMP_Int64 framesDroppedWithinTheLeastTenMinutes = 0; + if (minutes % 10 != 0) + { + if ((seconds == 0) && (frames < frameGroupDropped)) + { + frames = static_cast(frameGroupDropped); // Make sure invalid strings snap to the next higher valid frame. + } + framesDroppedWithinTheLeastTenMinutes = (minutes % 10) * frameGroupDropped; + } + wholeFrames = hours * framesPerHour + (minutes / 10) * framesPerTenMinutes + (minutes % 10) * framesPerMinute + seconds * framesPerSecond + frames - framesDroppedWithinTheLeastTenMinutes; + } + else + { + wholeFrames = hours * framesPerHour + minutes * framesPerMinute + seconds * framesPerSecond + frames; + } + + if (frameRateNumerator * fractionalFrameDenominator == 0) + { + XMP_Assert( "Divide by zero in ConvertTimecodeToSamples" ); + outSamples = 0; + return true; + } + + // + // (frame count / frames per second) * samples per second = sample count. + // + // ((frame count + fractionalFrameNumerator / fractionalFrameDenominator) * samples per second / frames per second) = sample count. + // or in integer math: + // ((frame count * fractionalFrameDenominator + fractionalFrameNumerator) * samples per second / (frames per second * fractionalFrameDenominator)) = sample count. + // with rounding correction to give us the first sample contained entirely in the frame: + + // There is a non-zero probability of rolling over this integer arithmetic. + double integerFailsafeNumerator = ((static_cast(wholeFrames) * static_cast(fractionalFrameDenominator) + fractionalFrameNumerator) * static_cast(frameRateDenominator) * static_cast(inSampleRate) + (frameRateNumerator * fractionalFrameDenominator - 1)); + if (integerFailsafeNumerator > static_cast(0x7000000000000000LL)) + { + outSamples = static_cast(integerFailsafeNumerator / (static_cast(frameRateNumerator) * static_cast(fractionalFrameDenominator))); + } + else + { + outSamples = ((wholeFrames * fractionalFrameDenominator + fractionalFrameNumerator) * frameRateDenominator * inSampleRate + (frameRateNumerator * fractionalFrameDenominator - 1)) / (frameRateNumerator * fractionalFrameDenominator); + } + return true; + } + + bool ConvertSMPTETimecodeToSamples( + XMP_Int64 & outSamples, + const std::string & inTimecode, + XMP_Uns64 inSampleRate, + const std::string & inTimecodeFormat ) + { + bool result = false; + + if ( inTimecodeFormat.compare( "24Timecode" ) == 0 ) { + result = ConvertTimecodeToSamples( outSamples, inTimecode, inSampleRate, 24, false, false ); + } else if ( inTimecodeFormat.compare( "25Timecode" ) == 0 ) { + result = ConvertTimecodeToSamples( outSamples, inTimecode, inSampleRate, 25, false, false ); + } else if ( inTimecodeFormat.compare( "2997DropTimecode") == 0 ) { + result = ConvertTimecodeToSamples( outSamples, inTimecode, inSampleRate, 30, true, true ); + } else if ( inTimecodeFormat.compare( "2997NonDropTimecode") == 0 ) { + result = ConvertTimecodeToSamples( outSamples, inTimecode, inSampleRate, 30, true, false ); + } else if ( inTimecodeFormat.compare( "30Timecode" ) == 0 ) { + result = ConvertTimecodeToSamples( outSamples, inTimecode, inSampleRate, 30, false, false ); + } else if ( inTimecodeFormat.compare( "50Timecode" ) == 0 ) { + result = ConvertTimecodeToSamples( outSamples, inTimecode, inSampleRate, 50, false, false ); + } else if ( inTimecodeFormat.compare( "5994DropTimecode" ) == 0 ) { + result = ConvertTimecodeToSamples( outSamples, inTimecode, inSampleRate, 60, true, true ); + } else if ( inTimecodeFormat.compare( "5994NonDropTimecode" ) == 0 ) { + result = ConvertTimecodeToSamples( outSamples, inTimecode, inSampleRate, 60, true, false ); + } else if ( inTimecodeFormat.compare( "60Timecode" ) == 0 ) { + result = ConvertTimecodeToSamples( outSamples, inTimecode, inSampleRate, 60, false, false ); + } else if ( inTimecodeFormat.compare( "23976Timecode" ) == 0 ) { + result = ConvertTimecodeToSamples( outSamples, inTimecode, inSampleRate, 24, true, false ); + } + return result; + } + +} diff --git a/core/libs/dngwriter/extra/xmp_sdk/XMPFiles/source/FormatSupport/TimeConversionUtils.hpp b/core/libs/dngwriter/extra/xmp_sdk/XMPFiles/source/FormatSupport/TimeConversionUtils.hpp new file mode 100644 index 0000000000..73ba9ffbb6 --- /dev/null +++ b/core/libs/dngwriter/extra/xmp_sdk/XMPFiles/source/FormatSupport/TimeConversionUtils.hpp @@ -0,0 +1,38 @@ +#ifndef TimeConversionUtils_h__ +#define TimeConversionUtils_h__ 1 + +// ================================================================================================= +// ADOBE SYSTEMS INCORPORATED +// Copyright 2014 Adobe Systems Incorporated +// All Rights Reserved +// +// NOTICE: Adobe permits you to use, modify, and distribute this file in accordance with the terms +// of the Adobe license agreement accompanying it. +// ================================================================================================= + +#include "public/include/XMP_Environment.h" // ! XMP_Environment.h must be the first included header. +#include "public/include/XMP_Const.h" +#include +#include +#include +#include +#include "XMPFiles/source/XMPFiles_Impl.hpp" + +namespace TimeConversionUtils { + bool ConvertSamplesToSMPTETimecode( + std::string & outTimecode, + XMP_Int64 inSamples, + XMP_Uns64 inSampleRate, + const std::string & inTimecodeFormat ); + + bool ConvertSMPTETimecodeToSamples( + XMP_Int64 & outSamples, + const std::string & inTimecode, + XMP_Uns64 inSampleRate, + const std::string & inTimecodeFormat + ); + + +}; + +#endif // TimeConversionUtils_h__ diff --git a/core/libs/dngwriter/extra/xmp_sdk/XMPFiles/source/FormatSupport/WAVE/BEXTMetadata.cpp b/core/libs/dngwriter/extra/xmp_sdk/XMPFiles/source/FormatSupport/WAVE/BEXTMetadata.cpp new file mode 100644 index 0000000000..5b98ba0a65 --- /dev/null +++ b/core/libs/dngwriter/extra/xmp_sdk/XMPFiles/source/FormatSupport/WAVE/BEXTMetadata.cpp @@ -0,0 +1,355 @@ +// ================================================================================================= +// ADOBE SYSTEMS INCORPORATED +// Copyright 2010 Adobe Systems Incorporated +// All Rights Reserved +// +// NOTICE: Adobe permits you to use, modify, and distribute this file in accordance with the terms +// of the Adobe license agreement accompanying it. +// ================================================================================================= + +#include + +#include "public/include/XMP_Environment.h" // ! XMP_Environment.h must be the first included header. +#include "public/include/XMP_Const.h" + +#include "XMPFiles/source/FormatSupport/WAVE/BEXTMetadata.h" +#include "source/Endian.h" + +using namespace IFF_RIFF; + +static const XMP_Uns32 kBEXTSizeMin = 602; // at minimum 602 bytes + +static const XMP_Uns32 kSizeDescription = 256; +static const XMP_Uns32 kSizeOriginator = 32; +static const XMP_Uns32 kSizeOriginatorReference = 32; +static const XMP_Uns32 kSizeOriginationDate = 10; +static const XMP_Uns32 kSizeOriginationTime = 8; + +// Needed to be able to memcpy directly to this struct. +#if SUNOS_SPARC || SUNOS_X86 +#pragma pack ( 1 ) +#else +#pragma pack ( push, 1 ) +#endif //#if SUNOS_SPARC || SUNOS_X86 + struct BEXT + { + char mDescription[256]; + char mOriginator[32]; + char mOriginatorReference[32]; + char mOriginationDate[10]; + char mOriginationTime[8]; + XMP_Uns32 mTimeReferenceLow; + XMP_Uns32 mTimeReferenceHigh; + XMP_Uns16 mVersion; + XMP_Uns8 mUMID[64]; + XMP_Uns8 mReserved[190]; + }; +#if SUNOS_SPARC || SUNOS_X86 +#pragma pack ( ) +#else +#pragma pack ( pop ) +#endif //#if SUNOS_SPARC || SUNOS_X86 + +//----------------------------------------------------------------------------- +// +// [static] convertLF(...) +// +// Purpose: Convert Mac/Unix line feeds to CR/LF +// +//----------------------------------------------------------------------------- + +void BEXTMetadata::NormalizeLF( std::string& str ) +{ + XMP_Uns32 i = 0; + while( i < str.length() ) + { + char ch = str[i]; + + if( ch == 0x0d ) + { + // + // possible Mac lf + // + if( i+1 < str.length() ) + { + if( str[i+1] != 0x0a ) + { + // + // insert missing LF character + // + str.insert( i+1, 1, 0x0a ); + } + + i += 2; + } + else + { + str.push_back( 0x0a ); + } + } + else if( ch == 0x0a ) + { + // + // possible Unix LF + // + if( i == 0 || str[i-1] != 0x0d ) + { + // + // insert missing CR character + // + str.insert( i, 1, 0x0d ); + i += 2; + } + else + { + i++; + } + } + else + { + i++; + } + } +} + +//----------------------------------------------------------------------------- +// +// BEXTMetadata::BEXTMetadata(...) +// +// Purpose: ctor/dtor +// +//----------------------------------------------------------------------------- + +BEXTMetadata::BEXTMetadata() +{ +} + +BEXTMetadata::~BEXTMetadata() +{ +} + +//----------------------------------------------------------------------------- +// +// BEXTMetadata::parse(...) +// +// Purpose: Parses the given memory block and creates a data model representation +// The implementation expects that the memory block is the data area of +// the BEXT chunk and its size is at least as big as the minimum size +// of a BEXT data block. +// Throws exceptions if parsing is not possible +// +//----------------------------------------------------------------------------- + +void BEXTMetadata::parse( const XMP_Uns8* chunkData, XMP_Uns64 size ) +{ + if( size >= kBEXTSizeMin ) + { + const LittleEndian& LE = LittleEndian::getInstance(); + + BEXT bext; + memset( &bext, 0, kBEXTSizeMin ); + + // + // copy input data into BEXT block (except CodingHistory field) + // Safe as fixed size matches size of struct that is #pragma packed(1) + // + memcpy( &bext, chunkData, kBEXTSizeMin ); + + // + // copy CodingHistory + // + if( size > kBEXTSizeMin ) + { + this->setValue( kCodingHistory, std::string( reinterpret_cast(&chunkData[kBEXTSizeMin]), static_cast(size - kBEXTSizeMin) ) ); + } + + // + // copy values to map + // + this->setValue( kDescription, std::string( bext.mDescription, kSizeDescription ) ); + this->setValue( kOriginator, std::string( bext.mOriginator, kSizeOriginator ) ); + this->setValue( kOriginatorReference, std::string( bext.mOriginatorReference, kSizeOriginatorReference ) ); + this->setValue( kOriginationDate, std::string( bext.mOriginationDate, kSizeOriginationDate ) ); + this->setValue( kOriginationTime, std::string( bext.mOriginationTime, kSizeOriginationTime ) ); + + this->setValue( kTimeReference, LE.getUns64( &bext.mTimeReferenceLow ) ); + this->setValue( kVersion, LE.getUns16( &bext.mVersion ) ); + + this->setArray( kUMID, bext.mUMID, 64 ); + + this->resetChanges(); + } + else + { + XMP_Throw ( "Not a valid BEXT chunk", kXMPErr_BadFileFormat ); + } +} + +//----------------------------------------------------------------------------- +// +// BEXTMetadata::serialize(...) +// +// Purpose: Serializes the data model to a memory block. +// The memory block will be the data area of a BEXT chunk. +// Throws exceptions if serializing is not possible +// +//----------------------------------------------------------------------------- + +XMP_Uns64 BEXTMetadata::serialize( XMP_Uns8** outBuffer ) +{ + XMP_Uns64 size = 0; + + if( outBuffer != NULL ) + { + const LittleEndian& LE = LittleEndian::getInstance(); + + size = kBEXTSizeMin; + + std::string codingHistory; + + if( this->valueExists( kCodingHistory ) ) + { + codingHistory = this->getValue( kCodingHistory ); + NormalizeLF( codingHistory ); + + size += codingHistory.length(); + } + + // + // setup buffer + // + XMP_Uns8* buffer = new XMP_Uns8[static_cast(size)]; + + // + // copy values and strings back to BEXT block + // + // ! Safe use of strncpy as the fixed size is consistent with the size of the destination buffer + // But it is intentional here that the string might not be null terminated if + // the size of the source is equal to the fixed size of the destination + // + BEXT bext; + memset( &bext, 0, kBEXTSizeMin ); + + if( this->valueExists( kDescription ) ) + { + strncpy( bext.mDescription, this->getValue( kDescription ).c_str(), kSizeDescription ); + } + if( this->valueExists( kOriginator ) ) + { + strncpy( bext.mOriginator, this->getValue( kOriginator ).c_str(), kSizeOriginator ); + } + if( this->valueExists( kOriginatorReference ) ) + { + strncpy( bext.mOriginatorReference, this->getValue( kOriginatorReference ).c_str(), kSizeOriginatorReference ); + } + if( this->valueExists( kOriginationDate ) ) + { + strncpy( bext.mOriginationDate, this->getValue( kOriginationDate ).c_str(), kSizeOriginationDate ); + } + if( this->valueExists( kOriginationTime ) ) + { + strncpy( bext.mOriginationTime, this->getValue( kOriginationTime ).c_str(), kSizeOriginationTime ); + } + + if( this->valueExists( kTimeReference ) ) + { + LE.putUns64( this->getValue( kTimeReference ), &bext.mTimeReferenceLow ); + } + + if( this->valueExists( kVersion ) ) + { + LE.putUns16( this->getValue( kVersion ), &bext.mVersion ); + } + else // Special case: If no value is given, a value of "1" is the default! + { + LE.putUns16( 1, &bext.mVersion ); + } + + if( this->valueExists( kUMID ) ) + { + XMP_Uns32 muidSize = 0; + const XMP_Uns8* const muid = this->getArray( kUMID, muidSize ); + + // Make sure to copy 64 bytes max. + muidSize = muidSize > 64 ? 64 : muidSize; + memcpy( bext.mUMID, muid, muidSize ); + } + // + // set input buffer to zero + // + memset( buffer, 0, static_cast(size) ); + + // + // copy BEXT block into buffer (except CodingHistory field) + // + memcpy( buffer, &bext, kBEXTSizeMin ); + + // + // copy CodingHistory field into buffer + // + if( ! codingHistory.empty() ) + { + memcpy( buffer + kBEXTSizeMin, codingHistory.c_str(), static_cast(size - kBEXTSizeMin) ); + } + + *outBuffer = buffer; + } + else + { + XMP_Throw ( "Invalid buffer", kXMPErr_InternalFailure ); + } + + return size; +} + +//----------------------------------------------------------------------------- +// +// BEXTMetadata::isEmptyValue(...) +// +// Purpose: Is the value of the passed ValueObject and its id "empty"? +// +//----------------------------------------------------------------------------- + +bool BEXTMetadata::isEmptyValue( XMP_Uns32 id, ValueObject& valueObj ) +{ + bool ret = true; + + switch( id ) + { + case kDescription: + case kOriginator: + case kOriginatorReference: + case kOriginationDate: + case kOriginationTime: + case kCodingHistory: + { + TValueObject* strObj = dynamic_cast*>(&valueObj); + + ret = ( strObj == NULL || ( strObj != NULL && strObj->getValue().empty() ) ); + } + break; + + case kTimeReference: + case kVersion: + ret = false; + break; + case kUMID: + { + TArrayObject* obj = dynamic_cast*>(&valueObj); + + if( obj != NULL ) + { + XMP_Uns32 size = 0; + const XMP_Uns8* const buffer = obj->getArray( size ); + + ret = ( size == 0 ); + } + } + break; + + default: + ret = true; + } + + return ret; +} diff --git a/core/libs/dngwriter/extra/xmp_sdk/XMPFiles/source/FormatSupport/WAVE/BEXTMetadata.h b/core/libs/dngwriter/extra/xmp_sdk/XMPFiles/source/FormatSupport/WAVE/BEXTMetadata.h new file mode 100644 index 0000000000..0f572469b6 --- /dev/null +++ b/core/libs/dngwriter/extra/xmp_sdk/XMPFiles/source/FormatSupport/WAVE/BEXTMetadata.h @@ -0,0 +1,99 @@ +// ================================================================================================= +// ADOBE SYSTEMS INCORPORATED +// Copyright 2010 Adobe Systems Incorporated +// All Rights Reserved +// +// NOTICE: Adobe permits you to use, modify, and distribute this file in accordance with the terms +// of the Adobe license agreement accompanying it. +// ================================================================================================= + +#ifndef _BEXTMetadata_h_ +#define _BEXTMetadata_h_ + +#include "public/include/XMP_Environment.h" // ! XMP_Environment.h must be the first included header. +#include "public/include/XMP_Const.h" +#include "public/include/XMP_IO.hpp" + +#include "XMPFiles/source/NativeMetadataSupport/IMetadata.h" + +namespace IFF_RIFF +{ + +/** + * BEXT Metadata model. + * Implements the IMetadata interface + */ +class BEXTMetadata : public IMetadata +{ +public: + enum + { + kDescription, // std::string + kOriginator, // std::string + kOriginatorReference, // std::string + kOriginationDate, // std::string + kOriginationTime, // std::string + kTimeReference, // XMP_Uns64 + kVersion, // XMP_Uns16 + kUMID, // XMP_Uns8[64] + kCodingHistory // std::string + }; + +public: + /** + *ctor/dtor + */ + BEXTMetadata(); + ~BEXTMetadata(); + + /** + * Parses the given memory block and creates a data model representation + * The implementation expects that the memory block is the data area of + * the BEXT chunk. + * Throws exceptions if parsing is not possible + * + * @param input The byte buffer to parse + * @param size Size of the given byte buffer + */ + void parse( const XMP_Uns8* chunkData, XMP_Uns64 size ); + + /** + * See IMetadata::parse( const LFA_FileRef input ) + */ + void parse( XMP_IO* input ) { IMetadata::parse( input ); } + + /** + * Serializes the data model to a memory block. + * The memory block will be the data area of a BEXT chunk. + * Throws exceptions if serializing is not possible + * + * @param buffer Buffer that gets filled with serialized data + * @param size Size of passed in buffer + * + * @return Size of serialzed data (might be smaller than buffer size) + */ + XMP_Uns64 serialize( XMP_Uns8** buffer ); + +protected: + /** + * @see IMetadata::isEmptyValue + */ + virtual bool isEmptyValue( XMP_Uns32 id, ValueObject& valueObj ); + + /** + * Normalize line feeds to \CR\LF + * Mac