From 5bf3ee8d0bab7568c4199df4a4af9cfe26272917 Mon Sep 17 00:00:00 2001 From: Packit Date: Sep 18 2020 12:29:37 +0000 Subject: exempi-2.4.5 base --- diff --git a/AUTHORS b/AUTHORS new file mode 100644 index 0000000..482c2f5 --- /dev/null +++ b/AUTHORS @@ -0,0 +1,14 @@ +The package is based on Adobe XMP SDK CS6 + +Maintainer: +Hubert Figuiere + +Original XMP code by: +Adobe Systems Incorporated (XMP SDK) + +Contributions by: +Ian Jacobi +Jason Kivlighn +Michael Biebl +Tim Mooney +Misty De Meo diff --git a/COPYING b/COPYING new file mode 100644 index 0000000..e7439c9 --- /dev/null +++ b/COPYING @@ -0,0 +1,32 @@ +This package is based off Adobe XMP SDK CD6, distributed +under the BSD license reproduced thereafter for convenience +and available in BSD-License.txt + +This package is licensed under the same license. + + +The BSD License + +Copyright (c) 1999 - 2010, Adobe Systems Incorporated +All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: + +* Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. + +* Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. + +* Neither the name of Adobe Systems Incorporated, nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR +CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, +EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR +PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + diff --git a/ChangeLog b/ChangeLog new file mode 100644 index 0000000..047887b --- /dev/null +++ b/ChangeLog @@ -0,0 +1,339 @@ +2009-05-26 Hubert Figuiere + + * source/XMPFiles/FileHandlers/UCF_Handler.cpp (UCF_CheckFormat): + Mismatched new [] / delete. (Closes bug #21934) + +2009-05-15 Hubert Figuiere + + * exempi/exempi.cpp: + Catch unhandled exceptions in xmp_files_can_put_xmp() + (Closes bug #20622) + +2009-03-11 Hubert Figuiere + + * configure.ac: + * exempi/Makefile.am: + * m4/ax_ld_check_flag.m4: + Detect proper ldflags. (Closes bug #20554) + + * configure.ac: Bump to version 2.1.1 + +2009-03-11 Lars Holm Nielsen + + * source/XMPFiles/FileHandlers/Makefile.am: + * source/XMPFiles/FormatSupport/Makefile.am: + * configure.ac: + Some files missing when building on Mac + (Closes bug #20554) + +2009-02-24 Hubert Figuiere + + * exempi/exempi.cpp (xmp_set_property): + If the value is struct or array, if the property value + is the empty string pass NULL. + (Closes bug #16030) + +2009-02-22 Hubert Figuiere + + * source/common/XMLParserAdapter.hpp: + Remove an annoying warning. + +2009-02-20 Hubert Figuiere + + * configure.in: + * m4/shave.m4: + * shave-libtool.in: + * shave.in: + Add shave to make the build output saner. + +2008-12-28 Michael Biebl + + * tests/utils.cpp + * samples/source/XMPFilesCoverage.cpp + * source/common/XML_Node.cpp + Fix build on gcc 4.4 + +2008-12-24 Hubert Figuiere + + * source/XMPFiles/FileHandlers/P2_Handler.cpp (SetStartTimecodeFromLegacyXML): + Don't compare string pointers. (Closes: #19312) + +=== 2.1.0 === + +2008-12-24 Hubert Figuiere + + * exempi/tests/test-bgo.cpp + * exempi/tests/test-xmpfiles.cpp + Remove serious warnings. + +2008-12-21 Hubert Figuiere + + * exempi/tests/Makefile.am + * exempi/tests/test-bgo.cpp + * exempi/tests/test-exempi-core.cpp + * exempi/tests/test-serialise.cpp + * exempi/tests/test-tiff-leak.cpp + * exempi/tests/test-write-new-prop.cpp + * exempi/tests/test-xmpfiles-write.cpp + * exempi/tests/test-xmpfiles.cpp + * exempi/tests/test3.cpp + * exempi/tests/testinit.cpp + Migrated to use boost/test/minimal + + * exempi/tests/test1.cpp + * exempi/tests/test2.cpp + Split out for the above change. + +2008-12-19 Hubert Figuiere + + * exempi/Makefile.am (EXTRA_DIST): Added libexempi.sym + +2008-11-26 Hubert Figuiere + + * exempi/tests/Makefile.am (TESTS_ENVIRONMENT): + BOOST_TEST_CATCH_SYSTEM_ERRORS=no is set before running + the test. This avoid a failure because of system() on + boost 1.35 and later. + See https://svn.boost.org/trac/boost/ticket/1723 + +2008-11-24 Hubert Figuiere + + * source/XMPFiles/FormatSupport/ReconcileTIFF.cpp (ImportSingleTIFF_ASCII): + Re-enable the conversion for UNIX. Disabled in the original SDK. + + * exempi/tests/Makefile.am: + * exempi/tests/fdo18635.jpg: + * exempi/tests/test-bgo.cpp + Don't crash on not so valid Exif. (Closes #18635) + And test case. + +2008-11-18 Hubert Figuiere + + * exempi/libexempi.sym, exempi/Makefile.am (libexempi_la_LDFLAGS): + Use symbol file. (Closes #16139) + + * samples/testfiles/BlueSquare.gif, + samples/testfiles/Makefile.am: Forgotten files. + + * NEWS, configure.ac, exempi/Makefile.am, + exempi/tests/test2.cpp, exempi/tests/testcore.sh, + samples/Makefile.am, samples/source/Makefile.am, + source/XMPFiles/FileHandlers/Makefile.am, + source/XMPFiles/FormatSupport/Makefile.am, + source/XMPCore/Makefile.am, source/common/Makefile.am: + Fix the build system. + * source/XMPFiles/FileHandlers/AVCHD_Handler.cpp + source/XMPFiles/FileHandlers/MP3_Handler.cpp, + source/XMPFiles/FileHandlers/MP3_Handler.hpp, + source/XMPFiles/FileHandlers/P2_Handler.cpp, + source/XMPFiles/FileHandlers/SonyHDV_Handler.cpp, + source/XMPFiles/FileHandlers/WAV_Handler.cpp, + source/XMPFiles/FileHandlers/WAV_Handler.hpp, + source/XMPFiles/FileHandlers/XDCAMEX_Handler.cpp, + source/XMPFiles/FileHandlers/XDCAM_Handler.cpp, + source/XMPFiles/FormatSupport/ID3_Support.cpp, + source/XMPFiles/FormatSupport/ID3_Support.hpp, + source/XMPFiles/FormatSupport/RIFF_Support.cpp, + source/XMPFiles/FormatSupport/RIFF_Support.hpp, + source/XMPFiles/FormatSupport/ReconcileTIFF.cpp, + source/XMPFiles/FormatSupport/Reconcile_Impl.cpp, + source/XMPFiles/FormatSupport/Reconcile_Impl.hpp, + source/XMPFiles/FormatSupport/TIFF_Support.hpp: + Enable all the file formats that Adobe disabled on + purpose. + * samples/source/ModifyingXMP.cpp, + source/common/EndianUtils.hpp, + source/common/LargeFileAccess.hpp, + source/common/XML_Node.cpp: + Other misc merge fixes. + + * Merge the XPM SDK 4.4.2 + +2008-05-29 Hubert Figuiere + + * exempi/exempi.cpp, exempi/Makefile.am: don't define + UNIX_ENV in the source code but use the CPPFLAGS. + + * exempi/exempi.cpp (xmp_files_get_xmp): CHECK_PTR should + return false and not NULL. + +2008-04-06 Hubert Figuiere + + * exempi/xmp.h, exempi/exempi.cpp: add new API + xmp_namespace_prefix(), xmp_prefix_namespace_uri(). + * exempi/tests/test1.cpp: corresponding test. + (Closes #14962) + +2008-04-04 Hubert Figuiere + + * Start 2.1.0 + +=== 2.0.2 === + +2008-08-12 Hubert Figuiere + + * exempi/exempi.cpp (set_error): Use pthread for the TLS + if there is no compiler support. + (Closes #16598) + + * configure.ac: + * m4/ax_tls.m4: + Check for TLS. + +=== 2.0.1 === + +2008-04-28 Hubert Figuiere + + * exempi/tests/test1.cpp: Check that errors are unset. + +2008-04-24 Hubert Figuiere + + * exempi/exempi.cpp: RESET_ERROR is called + upon entry of any functions. + error code is local thread storage. + +2008-04-05 Hubert Figuiere + + * source/XMPFiles/FormatSupport/Reconcile_Impl.cpp, + configure.ac: Check for iconv constness. (Closes #14613) + +2008-04-04 Hubert Figuiere + + * exempi/xmp.h: no stdbool.h on Sun compilers + (Closes #14612) + +=== 2.0.0 === + +2008-04-01 Hubert Figuiere + + * configure.ac: This is really 2.0.0 + + * exempi/tests/test1.cpp, exempi/tests/test1.xmp: + Add a test on "Rating:". + +2008-03-30 Hubert Figuiere + + * source/XMPFiles/FormatSupport/EndianUtils.hpp, + configure.ac: Fix the endian detection. (Closes #15263) + +2008-02-22 Hubert Figuiere + + * source/XMPCore/XMPMeta.cpp, + source/XMPFiles/FormatSupport/GIF_Support.cpp, + source/XMPFiles/FormatSupport/ID3_Support.cpp, + source/XMPFiles/FormatSupport/PNG_Support.cpp, + source/XMPFiles/FormatSupport/PSIR_FileWriter.cpp, + source/XMPFiles/FormatSupport/PSIR_MemoryReader.cpp, + source/XMPFiles/FormatSupport/ReconcileIPTC.cpp, + source/XMPFiles/FormatSupport/TIFF_Support.hpp: + Add some missing includes. (Closes #14615) + + * source/XMPFiles/FormatSupport/EndianUtils.hpp: endian + detection on Solaris. (Closes #14614) + +=== 1.99.9 === + +2008-01-30 Hubert Figuiere + + * exempi/xmp.h (XMP_OPEN_OPNLYXMP): Bad API breakage fix (introduced + in 1.99.8) + +=== 1.99.8 === + +2008-01-23 Hubert Figuiere + + * source/XMPFiles/FormatSupport/GIF_Support.cpp (ReadHeader): Fix + a nasty buffer overflow. Closes Debian #454297. Closes Gnome #484105 + Patch by Michael Biebl + (ReadBlock): Fix another possible overflow similar to the one in + ReadHeader() + +2008-01-22 Hubert Figuiere + + * exempi/xmp.h: Fix a typo. (Closes #14200) + +2008-01-18 Hubert Figuiere + + * source/XMPFiles/Makefile.am: Disable strict aliasing. This fix + some nasty warning. + +2008-01-13 Hubert Figuiere + + * configure.ac: Allow disabling unittest if you + don't have boost (Closes #13712) + + * autogen.sh: Don't run autoheader. (Closes #14049) + + * autogen.sh: re-order commands in autogens.sh + +=== 1.99.7 === + +2008-01-12 Hubert Figuiere + + * configure.ac: Fix the soversion. A mix-up occured in the last + release. + +=== 1.99.6 === + +2008-01-11 Hubert Figuiere + + * exempi/tests/test2.cpp (test_tiff_leak): Ensure that the file is + writable. + +2007-12-22 Hubert Figuiere + + * configure.ac: make boost optional as it is only for + the unit tests. (Closes #13712) + + * configure.ac, m4/boost.m4, exempi/test/Makefile.am: + Change the BOOST detection macros. + (Closes #13713) + +2007-12-20 Hubert Figuiere + + * source/XMPCore/XMPUtils.cpp (ConvertFromInt64): Fix format + for long long int that was causing a failure on ppc (big endian). + +2007-12-19 Hubert Figuiere + + * exempi/exempi.cpp, exempi/tests/test1.cpp, + exempi/tests/test1.xmp, exempi/xmp.h: + Add setter/getter for int32, int64, bool, float. + + * exempi/xmpconsts.h, exempi/exempi.cpp: add ACR schema namespace + + * public/include/TXMPMeta.hpp, public/include/client-glue/TXMPMeta.incl_cpp, + samples/source/XMPCoreCoverage.cpp, + source/XMPFiles/FormatSupport/ReconcileTIFF.cpp: + Some APIs use long and long long. Change that. + + * source/XMPFiles/FormatSupport/TIFF_FileWriter.cpp, exempi/tests/*: + Fix a memory leak. See http://www.adobeforums.com/webx/.3bc42b73 + And test case. + + * exempi/tests/*, configure.ac: detect valgrind. + Add leak detection support. + +2007-12-17 Hubert Figuiere + + * autogen.sh: added for convenience for non-tarball builders. + (Closes #13707) + +2007-12-16 Hubert Figuiere + + * exempi/*: added new API for get/set datetime. + + * exempi/tests/*: refactor the test preparation. + +2007-11-13 Hubert Figuiere + + * exempi/tests/*.c: Add a bunch of include deemed necessary + by suse factory. + * Add a bunch of include deemed necessary by suse factory. + +2007-11-08 Hubert Figuiere + + * source/XMPFiles/FormatSupport/Makefile.am: add + -Wno-multichar to remove warnings. + QuickTime_Support.cpp is in the dist as a noinst_HEADER + diff --git a/INSTALL b/INSTALL new file mode 100644 index 0000000..5458714 --- /dev/null +++ b/INSTALL @@ -0,0 +1,234 @@ +Installation Instructions +************************* + +Copyright (C) 1994, 1995, 1996, 1999, 2000, 2001, 2002, 2004, 2005, +2006 Free Software Foundation, Inc. + +This file is free documentation; the Free Software Foundation gives +unlimited permission to copy, distribute and modify it. + +Basic Installation +================== + +Briefly, the shell commands `./configure; make; make install' should +configure, build, and install this package. The following +more-detailed instructions are generic; see the `README' file for +instructions specific to this package. + + The `configure' shell script attempts to guess correct values for +various system-dependent variables used during compilation. It uses +those values to create a `Makefile' in each directory of the package. +It may also create one or more `.h' files containing system-dependent +definitions. Finally, it creates a shell script `config.status' that +you can run in the future to recreate the current configuration, and a +file `config.log' containing compiler output (useful mainly for +debugging `configure'). + + It can also use an optional file (typically called `config.cache' +and enabled with `--cache-file=config.cache' or simply `-C') that saves +the results of its tests to speed up reconfiguring. Caching is +disabled by default to prevent problems with accidental use of stale +cache files. + + If you need to do unusual things to compile the package, please try +to figure out how `configure' could check whether to do them, and mail +diffs or instructions to the address given in the `README' so they can +be considered for the next release. If you are using the cache, and at +some point `config.cache' contains results you don't want to keep, you +may remove or edit it. + + The file `configure.ac' (or `configure.in') is used to create +`configure' by a program called `autoconf'. You need `configure.ac' if +you want to change it or regenerate `configure' using a newer version +of `autoconf'. + +The simplest way to compile this package is: + + 1. `cd' to the directory containing the package's source code and type + `./configure' to configure the package for your system. + + Running `configure' might take a while. While running, it prints + some messages telling which features it is checking for. + + 2. Type `make' to compile the package. + + 3. Optionally, type `make check' to run any self-tests that come with + the package. + + 4. Type `make install' to install the programs and any data files and + documentation. + + 5. You can remove the program binaries and object files from the + source code directory by typing `make clean'. To also remove the + files that `configure' created (so you can compile the package for + a different kind of computer), type `make distclean'. There is + also a `make maintainer-clean' target, but that is intended mainly + for the package's developers. If you use it, you may have to get + all sorts of other programs in order to regenerate files that came + with the distribution. + +Compilers and Options +===================== + +Some systems require unusual options for compilation or linking that the +`configure' script does not know about. Run `./configure --help' for +details on some of the pertinent environment variables. + + You can give `configure' initial values for configuration parameters +by setting variables in the command line or in the environment. Here +is an example: + + ./configure CC=c99 CFLAGS=-g LIBS=-lposix + + *Note Defining Variables::, for more details. + +Compiling For Multiple Architectures +==================================== + +You can compile the package for more than one kind of computer at the +same time, by placing the object files for each architecture in their +own directory. To do this, you can use GNU `make'. `cd' to the +directory where you want the object files and executables to go and run +the `configure' script. `configure' automatically checks for the +source code in the directory that `configure' is in and in `..'. + + With a non-GNU `make', it is safer to compile the package for one +architecture at a time in the source code directory. After you have +installed the package for one architecture, use `make distclean' before +reconfiguring for another architecture. + +Installation Names +================== + +By default, `make install' installs the package's commands under +`/usr/local/bin', include files under `/usr/local/include', etc. You +can specify an installation prefix other than `/usr/local' by giving +`configure' the option `--prefix=PREFIX'. + + You can specify separate installation prefixes for +architecture-specific files and architecture-independent files. If you +pass the option `--exec-prefix=PREFIX' to `configure', the package uses +PREFIX as the prefix for installing programs and libraries. +Documentation and other data files still use the regular prefix. + + In addition, if you use an unusual directory layout you can give +options like `--bindir=DIR' to specify different values for particular +kinds of files. Run `configure --help' for a list of the directories +you can set and what kinds of files go in them. + + If the package supports it, you can cause programs to be installed +with an extra prefix or suffix on their names by giving `configure' the +option `--program-prefix=PREFIX' or `--program-suffix=SUFFIX'. + +Optional Features +================= + +Some packages pay attention to `--enable-FEATURE' options to +`configure', where FEATURE indicates an optional part of the package. +They may also pay attention to `--with-PACKAGE' options, where PACKAGE +is something like `gnu-as' or `x' (for the X Window System). The +`README' should mention any `--enable-' and `--with-' options that the +package recognizes. + + For packages that use the X Window System, `configure' can usually +find the X include and library files automatically, but if it doesn't, +you can use the `configure' options `--x-includes=DIR' and +`--x-libraries=DIR' to specify their locations. + +Specifying the System Type +========================== + +There may be some features `configure' cannot figure out automatically, +but needs to determine by the type of machine the package will run on. +Usually, assuming the package is built to be run on the _same_ +architectures, `configure' can figure that out, but if it prints a +message saying it cannot guess the machine type, give it the +`--build=TYPE' option. TYPE can either be a short name for the system +type, such as `sun4', or a canonical name which has the form: + + CPU-COMPANY-SYSTEM + +where SYSTEM can have one of these forms: + + OS KERNEL-OS + + See the file `config.sub' for the possible values of each field. If +`config.sub' isn't included in this package, then this package doesn't +need to know the machine type. + + If you are _building_ compiler tools for cross-compiling, you should +use the option `--target=TYPE' to select the type of system they will +produce code for. + + If you want to _use_ a cross compiler, that generates code for a +platform different from the build platform, you should specify the +"host" platform (i.e., that on which the generated programs will +eventually be run) with `--host=TYPE'. + +Sharing Defaults +================ + +If you want to set default values for `configure' scripts to share, you +can create a site shell script called `config.site' that gives default +values for variables like `CC', `cache_file', and `prefix'. +`configure' looks for `PREFIX/share/config.site' if it exists, then +`PREFIX/etc/config.site' if it exists. Or, you can set the +`CONFIG_SITE' environment variable to the location of the site script. +A warning: not all `configure' scripts look for a site script. + +Defining Variables +================== + +Variables not defined in a site shell script can be set in the +environment passed to `configure'. However, some packages may run +configure again during the build, and the customized values of these +variables may be lost. In order to avoid this problem, you should set +them in the `configure' command line, using `VAR=value'. For example: + + ./configure CC=/usr/local2/bin/gcc + +causes the specified `gcc' to be used as the C compiler (unless it is +overridden in the site shell script). + +Unfortunately, this technique does not work for `CONFIG_SHELL' due to +an Autoconf bug. Until the bug is fixed you can use this workaround: + + CONFIG_SHELL=/bin/bash /bin/bash ./configure CONFIG_SHELL=/bin/bash + +`configure' Invocation +====================== + +`configure' recognizes the following options to control how it operates. + +`--help' +`-h' + Print a summary of the options to `configure', and exit. + +`--version' +`-V' + Print the version of Autoconf used to generate the `configure' + script, and exit. + +`--cache-file=FILE' + Enable the cache: use and save the results of the tests in FILE, + traditionally `config.cache'. FILE defaults to `/dev/null' to + disable caching. + +`--config-cache' +`-C' + Alias for `--cache-file=config.cache'. + +`--quiet' +`--silent' +`-q' + Do not print messages saying which checks are being made. To + suppress all normal output, redirect it to `/dev/null' (any error + messages will still be shown). + +`--srcdir=DIR' + Look for the package's source code in directory DIR. Usually + `configure' can determine that directory automatically. + +`configure' also accepts some other, not widely useful, options. Run +`configure --help' for more details. + diff --git a/Makefile.am b/Makefile.am new file mode 100644 index 0000000..2fdee08 --- /dev/null +++ b/Makefile.am @@ -0,0 +1,42 @@ +# +# exempi - Makefile.am +# +# Copyright (C) 2007-2013 Hubert Figuiere +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions +# are met: +# +# 1 Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# +# 2 Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in the +# documentation and/or other materials provided with the +# distribution. +# +# 3 Neither the name of the Authors, nor the names of its +# contributors may be used to endorse or promote products derived +# from this software wit hout specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS +# FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE +# COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, +# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, +# STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED +# OF THE POSSIBILITY OF SUCH DAMAGE. +# + +ACLOCAL_AMFLAGS = -I m4 + +SUBDIRS = third-party source XMPCore XMPFiles samples exempi +DIST_SUBDIRS = build third-party source XMPCore XMPFiles XMPFilesPlugins samples exempi public + +EXTRA_DIST = autogen.sh diff --git a/Makefile.in b/Makefile.in new file mode 100644 index 0000000..71f3852 --- /dev/null +++ b/Makefile.in @@ -0,0 +1,855 @@ +# Makefile.in generated by automake 1.15.1 from Makefile.am. +# @configure_input@ + +# Copyright (C) 1994-2017 Free Software Foundation, Inc. + +# This Makefile.in is free software; the Free Software Foundation +# gives unlimited permission to copy and/or distribute it, +# with or without modifications, as long as this notice is preserved. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY, to the extent permitted by law; without +# even the implied warranty of MERCHANTABILITY or FITNESS FOR A +# PARTICULAR PURPOSE. + +@SET_MAKE@ + +# +# exempi - Makefile.am +# +# Copyright (C) 2007-2013 Hubert Figuiere +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions +# are met: +# +# 1 Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# +# 2 Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in the +# documentation and/or other materials provided with the +# distribution. +# +# 3 Neither the name of the Authors, nor the names of its +# contributors may be used to endorse or promote products derived +# from this software wit hout specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS +# FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE +# COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, +# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, +# STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED +# OF THE POSSIBILITY OF SUCH DAMAGE. +# +VPATH = @srcdir@ +am__is_gnu_make = { \ + if test -z '$(MAKELEVEL)'; then \ + false; \ + elif test -n '$(MAKE_HOST)'; then \ + true; \ + elif test -n '$(MAKE_VERSION)' && test -n '$(CURDIR)'; then \ + true; \ + else \ + false; \ + fi; \ +} +am__make_running_with_option = \ + case $${target_option-} in \ + ?) ;; \ + *) echo "am__make_running_with_option: internal error: invalid" \ + "target option '$${target_option-}' specified" >&2; \ + exit 1;; \ + esac; \ + has_opt=no; \ + sane_makeflags=$$MAKEFLAGS; \ + if $(am__is_gnu_make); then \ + sane_makeflags=$$MFLAGS; \ + else \ + case $$MAKEFLAGS in \ + *\\[\ \ ]*) \ + bs=\\; \ + sane_makeflags=`printf '%s\n' "$$MAKEFLAGS" \ + | sed "s/$$bs$$bs[$$bs $$bs ]*//g"`;; \ + esac; \ + fi; \ + skip_next=no; \ + strip_trailopt () \ + { \ + flg=`printf '%s\n' "$$flg" | sed "s/$$1.*$$//"`; \ + }; \ + for flg in $$sane_makeflags; do \ + test $$skip_next = yes && { skip_next=no; continue; }; \ + case $$flg in \ + *=*|--*) continue;; \ + -*I) strip_trailopt 'I'; skip_next=yes;; \ + -*I?*) strip_trailopt 'I';; \ + -*O) strip_trailopt 'O'; skip_next=yes;; \ + -*O?*) strip_trailopt 'O';; \ + -*l) strip_trailopt 'l'; skip_next=yes;; \ + -*l?*) strip_trailopt 'l';; \ + -[dEDm]) skip_next=yes;; \ + -[JT]) skip_next=yes;; \ + esac; \ + case $$flg in \ + *$$target_option*) has_opt=yes; break;; \ + esac; \ + done; \ + test $$has_opt = yes +am__make_dryrun = (target_option=n; $(am__make_running_with_option)) +am__make_keepgoing = (target_option=k; $(am__make_running_with_option)) +pkgdatadir = $(datadir)/@PACKAGE@ +pkgincludedir = $(includedir)/@PACKAGE@ +pkglibdir = $(libdir)/@PACKAGE@ +pkglibexecdir = $(libexecdir)/@PACKAGE@ +am__cd = CDPATH="$${ZSH_VERSION+.}$(PATH_SEPARATOR)" && cd +install_sh_DATA = $(install_sh) -c -m 644 +install_sh_PROGRAM = $(install_sh) -c +install_sh_SCRIPT = $(install_sh) -c +INSTALL_HEADER = $(INSTALL_DATA) +transform = $(program_transform_name) +NORMAL_INSTALL = : +PRE_INSTALL = : +POST_INSTALL = : +NORMAL_UNINSTALL = : +PRE_UNINSTALL = : +POST_UNINSTALL = : +build_triplet = @build@ +host_triplet = @host@ +subdir = . +ACLOCAL_M4 = $(top_srcdir)/aclocal.m4 +am__aclocal_m4_deps = $(top_srcdir)/m4/ax_cflags_gcc_option.m4 \ + $(top_srcdir)/m4/ax_cxx_compile_stdcxx_11.m4 \ + $(top_srcdir)/m4/ax_ld_check_flag.m4 \ + $(top_srcdir)/m4/ax_tls.m4 $(top_srcdir)/m4/boost.m4 \ + $(top_srcdir)/m4/libtool.m4 $(top_srcdir)/m4/ltoptions.m4 \ + $(top_srcdir)/m4/ltsugar.m4 $(top_srcdir)/m4/ltversion.m4 \ + $(top_srcdir)/m4/lt~obsolete.m4 $(top_srcdir)/configure.ac +am__configure_deps = $(am__aclocal_m4_deps) $(CONFIGURE_DEPENDENCIES) \ + $(ACLOCAL_M4) +DIST_COMMON = $(srcdir)/Makefile.am $(top_srcdir)/configure \ + $(am__configure_deps) $(am__DIST_COMMON) +am__CONFIG_DISTCLEAN_FILES = config.status config.cache config.log \ + configure.lineno config.status.lineno +mkinstalldirs = $(install_sh) -d +CONFIG_CLEAN_FILES = +CONFIG_CLEAN_VPATH_FILES = +AM_V_P = $(am__v_P_@AM_V@) +am__v_P_ = $(am__v_P_@AM_DEFAULT_V@) +am__v_P_0 = false +am__v_P_1 = : +AM_V_GEN = $(am__v_GEN_@AM_V@) +am__v_GEN_ = $(am__v_GEN_@AM_DEFAULT_V@) +am__v_GEN_0 = @echo " GEN " $@; +am__v_GEN_1 = +AM_V_at = $(am__v_at_@AM_V@) +am__v_at_ = $(am__v_at_@AM_DEFAULT_V@) +am__v_at_0 = @ +am__v_at_1 = +SOURCES = +DIST_SOURCES = +RECURSIVE_TARGETS = all-recursive check-recursive cscopelist-recursive \ + ctags-recursive dvi-recursive html-recursive info-recursive \ + install-data-recursive install-dvi-recursive \ + install-exec-recursive install-html-recursive \ + install-info-recursive install-pdf-recursive \ + install-ps-recursive install-recursive installcheck-recursive \ + installdirs-recursive pdf-recursive ps-recursive \ + tags-recursive uninstall-recursive +am__can_run_installinfo = \ + case $$AM_UPDATE_INFO_DIR in \ + n|no|NO) false;; \ + *) (install-info --version) >/dev/null 2>&1;; \ + esac +RECURSIVE_CLEAN_TARGETS = mostlyclean-recursive clean-recursive \ + distclean-recursive maintainer-clean-recursive +am__recursive_targets = \ + $(RECURSIVE_TARGETS) \ + $(RECURSIVE_CLEAN_TARGETS) \ + $(am__extra_recursive_targets) +AM_RECURSIVE_TARGETS = $(am__recursive_targets:-recursive=) TAGS CTAGS \ + cscope distdir dist dist-all distcheck +am__tagged_files = $(HEADERS) $(SOURCES) $(TAGS_FILES) $(LISP) +# Read a list of newline-separated strings from the standard input, +# and print each of them once, without duplicates. Input order is +# *not* preserved. +am__uniquify_input = $(AWK) '\ + BEGIN { nonempty = 0; } \ + { items[$$0] = 1; nonempty = 1; } \ + END { if (nonempty) { for (i in items) print i; }; } \ +' +# Make sure the list of sources is unique. This is necessary because, +# e.g., the same source file might be shared among _SOURCES variables +# for different programs/libraries. +am__define_uniq_tagged_files = \ + list='$(am__tagged_files)'; \ + unique=`for i in $$list; do \ + if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \ + done | $(am__uniquify_input)` +ETAGS = etags +CTAGS = ctags +CSCOPE = cscope +am__DIST_COMMON = $(srcdir)/Makefile.in AUTHORS COPYING ChangeLog \ + INSTALL NEWS README TODO compile config.guess config.sub \ + depcomp install-sh ltmain.sh missing +DISTFILES = $(DIST_COMMON) $(DIST_SOURCES) $(TEXINFOS) $(EXTRA_DIST) +distdir = $(PACKAGE)-$(VERSION) +top_distdir = $(distdir) +am__remove_distdir = \ + if test -d "$(distdir)"; then \ + find "$(distdir)" -type d ! -perm -200 -exec chmod u+w {} ';' \ + && rm -rf "$(distdir)" \ + || { sleep 5 && rm -rf "$(distdir)"; }; \ + else :; fi +am__post_remove_distdir = $(am__remove_distdir) +am__relativize = \ + dir0=`pwd`; \ + sed_first='s,^\([^/]*\)/.*$$,\1,'; \ + sed_rest='s,^[^/]*/*,,'; \ + sed_last='s,^.*/\([^/]*\)$$,\1,'; \ + sed_butlast='s,/*[^/]*$$,,'; \ + while test -n "$$dir1"; do \ + first=`echo "$$dir1" | sed -e "$$sed_first"`; \ + if test "$$first" != "."; then \ + if test "$$first" = ".."; then \ + dir2=`echo "$$dir0" | sed -e "$$sed_last"`/"$$dir2"; \ + dir0=`echo "$$dir0" | sed -e "$$sed_butlast"`; \ + else \ + first2=`echo "$$dir2" | sed -e "$$sed_first"`; \ + if test "$$first2" = "$$first"; then \ + dir2=`echo "$$dir2" | sed -e "$$sed_rest"`; \ + else \ + dir2="../$$dir2"; \ + fi; \ + dir0="$$dir0"/"$$first"; \ + fi; \ + fi; \ + dir1=`echo "$$dir1" | sed -e "$$sed_rest"`; \ + done; \ + reldir="$$dir2" +DIST_ARCHIVES = $(distdir).tar.gz $(distdir).tar.bz2 +GZIP_ENV = --best +DIST_TARGETS = dist-bzip2 dist-gzip +distuninstallcheck_listfiles = find . -type f -print +am__distuninstallcheck_listfiles = $(distuninstallcheck_listfiles) \ + | sed 's|^\./|$(prefix)/|' | grep -v '$(infodir)/dir$$' +distcleancheck_listfiles = find . -type f -print +ACLOCAL = @ACLOCAL@ +AMTAR = @AMTAR@ +AM_DEFAULT_VERBOSITY = @AM_DEFAULT_VERBOSITY@ +AR = @AR@ +AUTOCONF = @AUTOCONF@ +AUTOHEADER = @AUTOHEADER@ +AUTOMAKE = @AUTOMAKE@ +AWK = @AWK@ +BOOST_CPPFLAGS = @BOOST_CPPFLAGS@ +BOOST_LDPATH = @BOOST_LDPATH@ +BOOST_ROOT = @BOOST_ROOT@ +BOOST_UNIT_TEST_FRAMEWORK_LDFLAGS = @BOOST_UNIT_TEST_FRAMEWORK_LDFLAGS@ +BOOST_UNIT_TEST_FRAMEWORK_LDPATH = @BOOST_UNIT_TEST_FRAMEWORK_LDPATH@ +BOOST_UNIT_TEST_FRAMEWORK_LIBS = @BOOST_UNIT_TEST_FRAMEWORK_LIBS@ +CC = @CC@ +CCDEPMODE = @CCDEPMODE@ +CFLAGS = @CFLAGS@ +CPP = @CPP@ +CPPFLAGS = @CPPFLAGS@ +CXX = @CXX@ +CXXCPP = @CXXCPP@ +CXXDEPMODE = @CXXDEPMODE@ +CXXFLAGS = @CXXFLAGS@ +CYGPATH_W = @CYGPATH_W@ +DEFS = @DEFS@ +DEPDIR = @DEPDIR@ +DISTCHECK_CONFIGURE_FLAGS = @DISTCHECK_CONFIGURE_FLAGS@ +DLLTOOL = @DLLTOOL@ +DSYMUTIL = @DSYMUTIL@ +DUMPBIN = @DUMPBIN@ +ECHO_C = @ECHO_C@ +ECHO_N = @ECHO_N@ +ECHO_T = @ECHO_T@ +EGREP = @EGREP@ +EXEEXT = @EXEEXT@ +EXEMPI_AGE = @EXEMPI_AGE@ +EXEMPI_CURRENT = @EXEMPI_CURRENT@ +EXEMPI_CURRENT_MIN = @EXEMPI_CURRENT_MIN@ +EXEMPI_INCLUDE_BASE = @EXEMPI_INCLUDE_BASE@ +EXEMPI_MAJOR_VERSION = @EXEMPI_MAJOR_VERSION@ +EXEMPI_PLATFORM_DEF = @EXEMPI_PLATFORM_DEF@ +EXEMPI_REVISION = @EXEMPI_REVISION@ +EXEMPI_VERSION_INFO = @EXEMPI_VERSION_INFO@ +FGREP = @FGREP@ +GREP = @GREP@ +HAVE_CXX11 = @HAVE_CXX11@ +INSTALL = @INSTALL@ +INSTALL_DATA = @INSTALL_DATA@ +INSTALL_PROGRAM = @INSTALL_PROGRAM@ +INSTALL_SCRIPT = @INSTALL_SCRIPT@ +INSTALL_STRIP_PROGRAM = @INSTALL_STRIP_PROGRAM@ +LD = @LD@ +LDFLAGS = @LDFLAGS@ +LIBOBJS = @LIBOBJS@ +LIBS = @LIBS@ +LIBTOOL = @LIBTOOL@ +LIPO = @LIPO@ +LN_S = @LN_S@ +LTLIBOBJS = @LTLIBOBJS@ +LT_SYS_LIBRARY_PATH = @LT_SYS_LIBRARY_PATH@ +MAINT = @MAINT@ +MAKEINFO = @MAKEINFO@ +MANIFEST_TOOL = @MANIFEST_TOOL@ +MKDIR_P = @MKDIR_P@ +NM = @NM@ +NMEDIT = @NMEDIT@ +OBJDUMP = @OBJDUMP@ +OBJEXT = @OBJEXT@ +OTOOL = @OTOOL@ +OTOOL64 = @OTOOL64@ +PACKAGE = @PACKAGE@ +PACKAGE_BUGREPORT = @PACKAGE_BUGREPORT@ +PACKAGE_NAME = @PACKAGE_NAME@ +PACKAGE_STRING = @PACKAGE_STRING@ +PACKAGE_TARNAME = @PACKAGE_TARNAME@ +PACKAGE_URL = @PACKAGE_URL@ +PACKAGE_VERSION = @PACKAGE_VERSION@ +PATH_SEPARATOR = @PATH_SEPARATOR@ +RANLIB = @RANLIB@ +SED = @SED@ +SET_MAKE = @SET_MAKE@ +SHELL = @SHELL@ +STRIP = @STRIP@ +VALGRIND = @VALGRIND@ +VERSION = @VERSION@ +XMPCORE_CPPFLAGS = @XMPCORE_CPPFLAGS@ +abs_builddir = @abs_builddir@ +abs_srcdir = @abs_srcdir@ +abs_top_builddir = @abs_top_builddir@ +abs_top_srcdir = @abs_top_srcdir@ +ac_ct_AR = @ac_ct_AR@ +ac_ct_CC = @ac_ct_CC@ +ac_ct_CXX = @ac_ct_CXX@ +ac_ct_DUMPBIN = @ac_ct_DUMPBIN@ +am__include = @am__include@ +am__leading_dot = @am__leading_dot@ +am__quote = @am__quote@ +am__tar = @am__tar@ +am__untar = @am__untar@ +bindir = @bindir@ +build = @build@ +build_alias = @build_alias@ +build_cpu = @build_cpu@ +build_os = @build_os@ +build_vendor = @build_vendor@ +builddir = @builddir@ +datadir = @datadir@ +datarootdir = @datarootdir@ +docdir = @docdir@ +dvidir = @dvidir@ +exec_prefix = @exec_prefix@ +host = @host@ +host_alias = @host_alias@ +host_cpu = @host_cpu@ +host_os = @host_os@ +host_vendor = @host_vendor@ +htmldir = @htmldir@ +includedir = @includedir@ +infodir = @infodir@ +install_sh = @install_sh@ +libdir = @libdir@ +libexecdir = @libexecdir@ +localedir = @localedir@ +localstatedir = @localstatedir@ +mandir = @mandir@ +mkdir_p = @mkdir_p@ +oldincludedir = @oldincludedir@ +pdfdir = @pdfdir@ +pkgconfigdir = @pkgconfigdir@ +prefix = @prefix@ +program_transform_name = @program_transform_name@ +psdir = @psdir@ +sbindir = @sbindir@ +sharedstatedir = @sharedstatedir@ +srcdir = @srcdir@ +sysconfdir = @sysconfdir@ +target_alias = @target_alias@ +top_build_prefix = @top_build_prefix@ +top_builddir = @top_builddir@ +top_srcdir = @top_srcdir@ +ACLOCAL_AMFLAGS = -I m4 +SUBDIRS = third-party source XMPCore XMPFiles samples exempi +DIST_SUBDIRS = build third-party source XMPCore XMPFiles XMPFilesPlugins samples exempi public +EXTRA_DIST = autogen.sh +all: all-recursive + +.SUFFIXES: +am--refresh: Makefile + @: +$(srcdir)/Makefile.in: @MAINTAINER_MODE_TRUE@ $(srcdir)/Makefile.am $(am__configure_deps) + @for dep in $?; do \ + case '$(am__configure_deps)' in \ + *$$dep*) \ + echo ' cd $(srcdir) && $(AUTOMAKE) --foreign'; \ + $(am__cd) $(srcdir) && $(AUTOMAKE) --foreign \ + && exit 0; \ + exit 1;; \ + esac; \ + done; \ + echo ' cd $(top_srcdir) && $(AUTOMAKE) --foreign Makefile'; \ + $(am__cd) $(top_srcdir) && \ + $(AUTOMAKE) --foreign Makefile +Makefile: $(srcdir)/Makefile.in $(top_builddir)/config.status + @case '$?' in \ + *config.status*) \ + echo ' $(SHELL) ./config.status'; \ + $(SHELL) ./config.status;; \ + *) \ + echo ' cd $(top_builddir) && $(SHELL) ./config.status $@ $(am__depfiles_maybe)'; \ + cd $(top_builddir) && $(SHELL) ./config.status $@ $(am__depfiles_maybe);; \ + esac; + +$(top_builddir)/config.status: $(top_srcdir)/configure $(CONFIG_STATUS_DEPENDENCIES) + $(SHELL) ./config.status --recheck + +$(top_srcdir)/configure: @MAINTAINER_MODE_TRUE@ $(am__configure_deps) + $(am__cd) $(srcdir) && $(AUTOCONF) +$(ACLOCAL_M4): @MAINTAINER_MODE_TRUE@ $(am__aclocal_m4_deps) + $(am__cd) $(srcdir) && $(ACLOCAL) $(ACLOCAL_AMFLAGS) +$(am__aclocal_m4_deps): + +mostlyclean-libtool: + -rm -f *.lo + +clean-libtool: + -rm -rf .libs _libs + +distclean-libtool: + -rm -f libtool config.lt + +# This directory's subdirectories are mostly independent; you can cd +# into them and run 'make' without going through this Makefile. +# To change the values of 'make' variables: instead of editing Makefiles, +# (1) if the variable is set in 'config.status', edit 'config.status' +# (which will cause the Makefiles to be regenerated when you run 'make'); +# (2) otherwise, pass the desired values on the 'make' command line. +$(am__recursive_targets): + @fail=; \ + if $(am__make_keepgoing); then \ + failcom='fail=yes'; \ + else \ + failcom='exit 1'; \ + fi; \ + dot_seen=no; \ + target=`echo $@ | sed s/-recursive//`; \ + case "$@" in \ + distclean-* | maintainer-clean-*) list='$(DIST_SUBDIRS)' ;; \ + *) list='$(SUBDIRS)' ;; \ + esac; \ + for subdir in $$list; do \ + echo "Making $$target in $$subdir"; \ + if test "$$subdir" = "."; then \ + dot_seen=yes; \ + local_target="$$target-am"; \ + else \ + local_target="$$target"; \ + fi; \ + ($(am__cd) $$subdir && $(MAKE) $(AM_MAKEFLAGS) $$local_target) \ + || eval $$failcom; \ + done; \ + if test "$$dot_seen" = "no"; then \ + $(MAKE) $(AM_MAKEFLAGS) "$$target-am" || exit 1; \ + fi; test -z "$$fail" + +ID: $(am__tagged_files) + $(am__define_uniq_tagged_files); mkid -fID $$unique +tags: tags-recursive +TAGS: tags + +tags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files) + set x; \ + here=`pwd`; \ + if ($(ETAGS) --etags-include --version) >/dev/null 2>&1; then \ + include_option=--etags-include; \ + empty_fix=.; \ + else \ + include_option=--include; \ + empty_fix=; \ + fi; \ + list='$(SUBDIRS)'; for subdir in $$list; do \ + if test "$$subdir" = .; then :; else \ + test ! -f $$subdir/TAGS || \ + set "$$@" "$$include_option=$$here/$$subdir/TAGS"; \ + fi; \ + done; \ + $(am__define_uniq_tagged_files); \ + shift; \ + if test -z "$(ETAGS_ARGS)$$*$$unique"; then :; else \ + test -n "$$unique" || unique=$$empty_fix; \ + if test $$# -gt 0; then \ + $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \ + "$$@" $$unique; \ + else \ + $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \ + $$unique; \ + fi; \ + fi +ctags: ctags-recursive + +CTAGS: ctags +ctags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files) + $(am__define_uniq_tagged_files); \ + test -z "$(CTAGS_ARGS)$$unique" \ + || $(CTAGS) $(CTAGSFLAGS) $(AM_CTAGSFLAGS) $(CTAGS_ARGS) \ + $$unique + +GTAGS: + here=`$(am__cd) $(top_builddir) && pwd` \ + && $(am__cd) $(top_srcdir) \ + && gtags -i $(GTAGS_ARGS) "$$here" +cscope: cscope.files + test ! -s cscope.files \ + || $(CSCOPE) -b -q $(AM_CSCOPEFLAGS) $(CSCOPEFLAGS) -i cscope.files $(CSCOPE_ARGS) +clean-cscope: + -rm -f cscope.files +cscope.files: clean-cscope cscopelist +cscopelist: cscopelist-recursive + +cscopelist-am: $(am__tagged_files) + list='$(am__tagged_files)'; \ + case "$(srcdir)" in \ + [\\/]* | ?:[\\/]*) sdir="$(srcdir)" ;; \ + *) sdir=$(subdir)/$(srcdir) ;; \ + esac; \ + for i in $$list; do \ + if test -f "$$i"; then \ + echo "$(subdir)/$$i"; \ + else \ + echo "$$sdir/$$i"; \ + fi; \ + done >> $(top_builddir)/cscope.files + +distclean-tags: + -rm -f TAGS ID GTAGS GRTAGS GSYMS GPATH tags + -rm -f cscope.out cscope.in.out cscope.po.out cscope.files + +distdir: $(DISTFILES) + $(am__remove_distdir) + test -d "$(distdir)" || mkdir "$(distdir)" + @srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \ + topsrcdirstrip=`echo "$(top_srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \ + list='$(DISTFILES)'; \ + dist_files=`for file in $$list; do echo $$file; done | \ + sed -e "s|^$$srcdirstrip/||;t" \ + -e "s|^$$topsrcdirstrip/|$(top_builddir)/|;t"`; \ + case $$dist_files in \ + */*) $(MKDIR_P) `echo "$$dist_files" | \ + sed '/\//!d;s|^|$(distdir)/|;s,/[^/]*$$,,' | \ + sort -u` ;; \ + esac; \ + for file in $$dist_files; do \ + if test -f $$file || test -d $$file; then d=.; else d=$(srcdir); fi; \ + if test -d $$d/$$file; then \ + dir=`echo "/$$file" | sed -e 's,/[^/]*$$,,'`; \ + if test -d "$(distdir)/$$file"; then \ + find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \ + fi; \ + if test -d $(srcdir)/$$file && test $$d != $(srcdir); then \ + cp -fpR $(srcdir)/$$file "$(distdir)$$dir" || exit 1; \ + find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \ + fi; \ + cp -fpR $$d/$$file "$(distdir)$$dir" || exit 1; \ + else \ + test -f "$(distdir)/$$file" \ + || cp -p $$d/$$file "$(distdir)/$$file" \ + || exit 1; \ + fi; \ + done + @list='$(DIST_SUBDIRS)'; for subdir in $$list; do \ + if test "$$subdir" = .; then :; else \ + $(am__make_dryrun) \ + || test -d "$(distdir)/$$subdir" \ + || $(MKDIR_P) "$(distdir)/$$subdir" \ + || exit 1; \ + dir1=$$subdir; dir2="$(distdir)/$$subdir"; \ + $(am__relativize); \ + new_distdir=$$reldir; \ + dir1=$$subdir; dir2="$(top_distdir)"; \ + $(am__relativize); \ + new_top_distdir=$$reldir; \ + echo " (cd $$subdir && $(MAKE) $(AM_MAKEFLAGS) top_distdir="$$new_top_distdir" distdir="$$new_distdir" \\"; \ + echo " am__remove_distdir=: am__skip_length_check=: am__skip_mode_fix=: distdir)"; \ + ($(am__cd) $$subdir && \ + $(MAKE) $(AM_MAKEFLAGS) \ + top_distdir="$$new_top_distdir" \ + distdir="$$new_distdir" \ + am__remove_distdir=: \ + am__skip_length_check=: \ + am__skip_mode_fix=: \ + distdir) \ + || exit 1; \ + fi; \ + done + -test -n "$(am__skip_mode_fix)" \ + || find "$(distdir)" -type d ! -perm -755 \ + -exec chmod u+rwx,go+rx {} \; -o \ + ! -type d ! -perm -444 -links 1 -exec chmod a+r {} \; -o \ + ! -type d ! -perm -400 -exec chmod a+r {} \; -o \ + ! -type d ! -perm -444 -exec $(install_sh) -c -m a+r {} {} \; \ + || chmod -R a+r "$(distdir)" +dist-gzip: distdir + tardir=$(distdir) && $(am__tar) | eval GZIP= gzip $(GZIP_ENV) -c >$(distdir).tar.gz + $(am__post_remove_distdir) +dist-bzip2: distdir + tardir=$(distdir) && $(am__tar) | BZIP2=$${BZIP2--9} bzip2 -c >$(distdir).tar.bz2 + $(am__post_remove_distdir) + +dist-lzip: distdir + tardir=$(distdir) && $(am__tar) | lzip -c $${LZIP_OPT--9} >$(distdir).tar.lz + $(am__post_remove_distdir) + +dist-xz: distdir + tardir=$(distdir) && $(am__tar) | XZ_OPT=$${XZ_OPT--e} xz -c >$(distdir).tar.xz + $(am__post_remove_distdir) + +dist-tarZ: distdir + @echo WARNING: "Support for distribution archives compressed with" \ + "legacy program 'compress' is deprecated." >&2 + @echo WARNING: "It will be removed altogether in Automake 2.0" >&2 + tardir=$(distdir) && $(am__tar) | compress -c >$(distdir).tar.Z + $(am__post_remove_distdir) + +dist-shar: distdir + @echo WARNING: "Support for shar distribution archives is" \ + "deprecated." >&2 + @echo WARNING: "It will be removed altogether in Automake 2.0" >&2 + shar $(distdir) | eval GZIP= gzip $(GZIP_ENV) -c >$(distdir).shar.gz + $(am__post_remove_distdir) + +dist-zip: distdir + -rm -f $(distdir).zip + zip -rq $(distdir).zip $(distdir) + $(am__post_remove_distdir) + +dist dist-all: + $(MAKE) $(AM_MAKEFLAGS) $(DIST_TARGETS) am__post_remove_distdir='@:' + $(am__post_remove_distdir) + +# This target untars the dist file and tries a VPATH configuration. Then +# it guarantees that the distribution is self-contained by making another +# tarfile. +distcheck: dist + case '$(DIST_ARCHIVES)' in \ + *.tar.gz*) \ + eval GZIP= gzip $(GZIP_ENV) -dc $(distdir).tar.gz | $(am__untar) ;;\ + *.tar.bz2*) \ + bzip2 -dc $(distdir).tar.bz2 | $(am__untar) ;;\ + *.tar.lz*) \ + lzip -dc $(distdir).tar.lz | $(am__untar) ;;\ + *.tar.xz*) \ + xz -dc $(distdir).tar.xz | $(am__untar) ;;\ + *.tar.Z*) \ + uncompress -c $(distdir).tar.Z | $(am__untar) ;;\ + *.shar.gz*) \ + eval GZIP= gzip $(GZIP_ENV) -dc $(distdir).shar.gz | unshar ;;\ + *.zip*) \ + unzip $(distdir).zip ;;\ + esac + chmod -R a-w $(distdir) + chmod u+w $(distdir) + mkdir $(distdir)/_build $(distdir)/_build/sub $(distdir)/_inst + chmod a-w $(distdir) + test -d $(distdir)/_build || exit 0; \ + dc_install_base=`$(am__cd) $(distdir)/_inst && pwd | sed -e 's,^[^:\\/]:[\\/],/,'` \ + && dc_destdir="$${TMPDIR-/tmp}/am-dc-$$$$/" \ + && am__cwd=`pwd` \ + && $(am__cd) $(distdir)/_build/sub \ + && ../../configure \ + $(AM_DISTCHECK_CONFIGURE_FLAGS) \ + $(DISTCHECK_CONFIGURE_FLAGS) \ + --srcdir=../.. --prefix="$$dc_install_base" \ + && $(MAKE) $(AM_MAKEFLAGS) \ + && $(MAKE) $(AM_MAKEFLAGS) dvi \ + && $(MAKE) $(AM_MAKEFLAGS) check \ + && $(MAKE) $(AM_MAKEFLAGS) install \ + && $(MAKE) $(AM_MAKEFLAGS) installcheck \ + && $(MAKE) $(AM_MAKEFLAGS) uninstall \ + && $(MAKE) $(AM_MAKEFLAGS) distuninstallcheck_dir="$$dc_install_base" \ + distuninstallcheck \ + && chmod -R a-w "$$dc_install_base" \ + && ({ \ + (cd ../.. && umask 077 && mkdir "$$dc_destdir") \ + && $(MAKE) $(AM_MAKEFLAGS) DESTDIR="$$dc_destdir" install \ + && $(MAKE) $(AM_MAKEFLAGS) DESTDIR="$$dc_destdir" uninstall \ + && $(MAKE) $(AM_MAKEFLAGS) DESTDIR="$$dc_destdir" \ + distuninstallcheck_dir="$$dc_destdir" distuninstallcheck; \ + } || { rm -rf "$$dc_destdir"; exit 1; }) \ + && rm -rf "$$dc_destdir" \ + && $(MAKE) $(AM_MAKEFLAGS) dist \ + && rm -rf $(DIST_ARCHIVES) \ + && $(MAKE) $(AM_MAKEFLAGS) distcleancheck \ + && cd "$$am__cwd" \ + || exit 1 + $(am__post_remove_distdir) + @(echo "$(distdir) archives ready for distribution: "; \ + list='$(DIST_ARCHIVES)'; for i in $$list; do echo $$i; done) | \ + sed -e 1h -e 1s/./=/g -e 1p -e 1x -e '$$p' -e '$$x' +distuninstallcheck: + @test -n '$(distuninstallcheck_dir)' || { \ + echo 'ERROR: trying to run $@ with an empty' \ + '$$(distuninstallcheck_dir)' >&2; \ + exit 1; \ + }; \ + $(am__cd) '$(distuninstallcheck_dir)' || { \ + echo 'ERROR: cannot chdir into $(distuninstallcheck_dir)' >&2; \ + exit 1; \ + }; \ + test `$(am__distuninstallcheck_listfiles) | wc -l` -eq 0 \ + || { echo "ERROR: files left after uninstall:" ; \ + if test -n "$(DESTDIR)"; then \ + echo " (check DESTDIR support)"; \ + fi ; \ + $(distuninstallcheck_listfiles) ; \ + exit 1; } >&2 +distcleancheck: distclean + @if test '$(srcdir)' = . ; then \ + echo "ERROR: distcleancheck can only run from a VPATH build" ; \ + exit 1 ; \ + fi + @test `$(distcleancheck_listfiles) | wc -l` -eq 0 \ + || { echo "ERROR: files left in build directory after distclean:" ; \ + $(distcleancheck_listfiles) ; \ + exit 1; } >&2 +check-am: all-am +check: check-recursive +all-am: Makefile +installdirs: installdirs-recursive +installdirs-am: +install: install-recursive +install-exec: install-exec-recursive +install-data: install-data-recursive +uninstall: uninstall-recursive + +install-am: all-am + @$(MAKE) $(AM_MAKEFLAGS) install-exec-am install-data-am + +installcheck: installcheck-recursive +install-strip: + if test -z '$(STRIP)'; then \ + $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \ + install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \ + install; \ + else \ + $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \ + install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \ + "INSTALL_PROGRAM_ENV=STRIPPROG='$(STRIP)'" install; \ + fi +mostlyclean-generic: + +clean-generic: + +distclean-generic: + -test -z "$(CONFIG_CLEAN_FILES)" || rm -f $(CONFIG_CLEAN_FILES) + -test . = "$(srcdir)" || test -z "$(CONFIG_CLEAN_VPATH_FILES)" || rm -f $(CONFIG_CLEAN_VPATH_FILES) + +maintainer-clean-generic: + @echo "This command is intended for maintainers to use" + @echo "it deletes files that may require special tools to rebuild." +clean: clean-recursive + +clean-am: clean-generic clean-libtool mostlyclean-am + +distclean: distclean-recursive + -rm -f $(am__CONFIG_DISTCLEAN_FILES) + -rm -f Makefile +distclean-am: clean-am distclean-generic distclean-libtool \ + distclean-tags + +dvi: dvi-recursive + +dvi-am: + +html: html-recursive + +html-am: + +info: info-recursive + +info-am: + +install-data-am: + +install-dvi: install-dvi-recursive + +install-dvi-am: + +install-exec-am: + +install-html: install-html-recursive + +install-html-am: + +install-info: install-info-recursive + +install-info-am: + +install-man: + +install-pdf: install-pdf-recursive + +install-pdf-am: + +install-ps: install-ps-recursive + +install-ps-am: + +installcheck-am: + +maintainer-clean: maintainer-clean-recursive + -rm -f $(am__CONFIG_DISTCLEAN_FILES) + -rm -rf $(top_srcdir)/autom4te.cache + -rm -f Makefile +maintainer-clean-am: distclean-am maintainer-clean-generic + +mostlyclean: mostlyclean-recursive + +mostlyclean-am: mostlyclean-generic mostlyclean-libtool + +pdf: pdf-recursive + +pdf-am: + +ps: ps-recursive + +ps-am: + +uninstall-am: + +.MAKE: $(am__recursive_targets) install-am install-strip + +.PHONY: $(am__recursive_targets) CTAGS GTAGS TAGS all all-am \ + am--refresh check check-am clean clean-cscope clean-generic \ + clean-libtool cscope cscopelist-am ctags ctags-am dist \ + dist-all dist-bzip2 dist-gzip dist-lzip dist-shar dist-tarZ \ + dist-xz dist-zip distcheck distclean distclean-generic \ + distclean-libtool distclean-tags distcleancheck distdir \ + distuninstallcheck dvi dvi-am html html-am info info-am \ + install install-am install-data install-data-am install-dvi \ + install-dvi-am install-exec install-exec-am install-html \ + install-html-am install-info install-info-am install-man \ + install-pdf install-pdf-am install-ps install-ps-am \ + install-strip installcheck installcheck-am installdirs \ + installdirs-am maintainer-clean maintainer-clean-generic \ + mostlyclean mostlyclean-generic mostlyclean-libtool pdf pdf-am \ + ps ps-am tags tags-am uninstall uninstall-am + +.PRECIOUS: Makefile + + +# Tell versions [3.59,3.63) of GNU make to not export all variables. +# Otherwise a system limit (for SysV at least) may be exceeded. +.NOEXPORT: diff --git a/NEWS b/NEWS new file mode 100644 index 0000000..dd45381 --- /dev/null +++ b/NEWS @@ -0,0 +1,262 @@ +2.4.5 - 2018/03/07 + +- Bug #105204: [CVE-2018-7730] fix a buffer overflow in the PSD parser. +- Bug #105205: [CVE-2018-7728] fix a buffer overflow in the TIFF parser. +- Bug #105206: [CVE-2018-7729] fix a buffer overflow in PostScript parser. +- Bug #105247: [CVE-2018-7731] fix a null dereference in WEBP parser. + +2.4.4 - 2018/02/04 + +- Bug #102197: Properly initialize pointers in WEBP. +- Bug #102151: Fix an infinite loop in RIFF parser. +- Bug #102483: Fix an infinite loop in QuickTime parser. +- Bug #102484: Fix an infinite loop in ASF parser. +- Bug #104885: Adjust minimum version for gcc in documentation. + +2.4.3 - 2017/08/03 + +- Bug #100397: Fix a buffer overrun, memcpy() on overlapping + regions, use after free in the exception handling. +- Bug #101913: Fix a fatal assert with corrupt WEBP. +- Bug #101914: Fix a crash on a corrupt file. + +2.4.2 - 2017/01/29 + +- Properly define BanAllEntityUsage. + See https://bugzilla.redhat.com/show_bug.cgi?id=888765 + +2.4.1 - 2017/01/23 + +- Bug #99494: Restore error reporting. + +Internal: + +- Added test for xmp_parse() +- Renamed test3 to testiterator and more comprehensive test for + iterator to detect thing like bug 99480 + +Release notes: + +- In 2.4.0 the XMP iterator corrected behaviour when used for + XMP_ITER_JUSTLEAFNAME: The returned values are now set to they + actual schema NS instead of the top level one. + This required fixed in third party packages: + https://github.com/python-xmp-toolkit/python-xmp-toolkit/issues/67 + This changes is the result of a bug fix in Adobe SDK. + See bug #99480 + +2.4.0 - 2017/01/07 + +- Bug #89449: Upgrade XMPCore to Adobe XMP CC 2014.12. + - New flag to optimize layout on MPEG4 files. + - GoPro MPEG4 video files support. + - Improved JPEG support. + - iXML support in WAVE files. + - Several bugs and memory leaks fixes. + - Changes from Adobe XMP CC 2013.06. + - Pluggable file handlers (not exposed yet in Exempi) + - Support for Exif 2.3 properties + - New RIFF file handler + - Better Postscript support. + - Lot of bug fixes. +- New API: added XMP_OPEN_OPTIMIZEFILELAYOUT for new SDK. +- Now require (partial) C++11 support to compile (gcc 4.4.7 tested) +- New: WebP format handler (contributed: Frankie Dintino, The Atlantic) + +Internal: + +- Exempi is now automatically build and the test run by Travis CI. + +2.3.0 - 2016/03/15 + +- New: API xmp_datetime_compare(). +- New: API xmp_string_len() to get the length of the XmpString. +- Bug #94065: + - New: API xmp_files_can_put_xmp_xmpstring() and xmp_files_can_put_xmp_cstr() + variants. + - New: API xmp_files_put_xmp_xmpstring() and xmp_files_put_xmp_cstr() + variants. + - New: API xmp_files_get_xmp_xmpstring() variant. + - Test: check the status of the PDF handler. +- Bug #90380: Fix potential crash with corrupt TIFF file. +- Bug #14612: Better Solaris compilation fix. +- Fix header to pass -Wstrict-prototypes + +2.2.2 - 2014/08/31 + +- Public header cleanup and documentation update. +- Update Doxygen config. +- Bug #73058: Add missing include for MacOS. +- Bug #72810: Fix typo in date test in MP3 handler. +- Bug #83313: Fix crash on invalid Exif (from Samsung) +- Fix valgrind testing for xmpcore.sh +- Fix delete / delete[] mismatch in ID3_Support.hpp + +2.2.1 - 2013/06/29 + +- Bug #54011: Use POSIX API for files on MacOS. (Misty De Meo) +- Bug #58175: Replace OS X FlatCarbon headers. (Misty De Meo) +- Added a manpage for exempi(1). +- Added the -n option to the command line for arbitrary namespaces. + +2.2.0 - 2012/02/21 + +- New 'exempi' command line tool. +- Upgrade XMPCore to Adobe XMP 5.1.2 + - Quicktime support now works without Quicktime. + - Reconciliation with ID3v2. + - "Blessed" 64-bits support (we already had it in exempi). + - Slight change in the way XMP are written for MWG compliance. + - Fixed a serious bug with RIFF. + - Change in the way local text encoding is dealt with. + - Alternative languages behave slightly differently by changing + how the default language property is managed. + - Probably a bunch of bugs fixed that I don't know about. +- Update unit tests. + - Refactor the fixtures. +- Use automake silent rules instead of shave. (build only) +- "make dist" generate a bzip2 archive as well. (build only) +- Remove some obsolete warning flags. (build only) +- Build xmpcommandtool +- New: API xmp_files_get_format_info(). +- New: API xmp_files_check_file_format(). +- New: API xmp_files_get_file_info(). +- New: API XMP_PROP_ARRAY_INSERT_BEFORE, XMP_PROP_ARRAY_INSERT_AFTER array options. +- New: C++ helpers in xmp++.hpp. + +Bug fixes: + +- Bug #37747: mismatch delete/delete[] and new/new[] (from Meego + https://bugs.meego.com/show_bug.cgi?id=14661) + +2.1.1 - 2009/06/30 + +New features: + +- Added shave to the build system for sane output. (build only) + +Bug fixes: + +- Bug #16030: if the property is an array or struct allow "" to be passed + as a value. +- Bug #19312: source/XMPFiles/FileHandlers/P2_Handler.cpp was using + an improper string comparison. (made rpmlint unhappy). +- Bug #20554: Missing file on MacOS X. +- Bug #20554: Detect ldflags properly. +- Bug #20622: Catch unhandled exceptions in xmp_files_can_put_xmp(). +- Bug #21934: Mismatched new [] / delete. +- Bug #22554: Fix a SIGFPE encountered on some invalid files. (Bug GNOME #586720) + +2.1.0 + +- Upgrade XMPCore to Adobe XMP 4.4.2 + - Handlers for additional file formats, including ASF (WMA, WMV), FLV; + MPEG4; SWF; folder-based video formats AVCHD, P2, SonyHDV, and XDCAM; UCF + - Additional schemas to support document histories, composed documents, + and temporal metadata +- New: NS_PDF namespace for PDF. +- New: API xmp_prefix_namespace_uri() and xmp_namespace_prefix(). Bug #14962. +- Bug: make sure boost >1.35 does not fail test with system(). +- Bug: unit test now use boost/test/minimal.hpp to work with more boost + install. (known boost.test bug) +- Bug: fix a typo in a CHECK_PTR call causing warnings on gcc < 4. +- Bug: no longer define UNIX_ENV in exempi.cpp and let CPPFLAGS do it. +- Bug #16139: the list of exported symbols was too large. +- Bug #18635: fix crasher. + +2.0.2 + +- Bug #16598: address the lack of TLS for some platforms. + +2.0.1 + +- Bug #14612: no stdbool.h for Sun compilers. +- Bug #14613: check for iconv() const-ness. +- Make the error checking more robust. +- Make error code thread-safe (ie local to the thread). + +2.0.0 + +- Bug #14614, Bug #15263: endian detection in configure. +- Bug #14615: missing includes for Solaris. + +1.99.9 + +- Bug: fixed an API breakage introduced in 1.99.8 + +1.99.8 + +- Bug #14049: don't run autoheader. +- Bug #13712: add --enable-unittest to disable tests. +- Bug: Disable strict aliasing in XMPFiles due to bad casting. +- Bug #14200: fix a typo. +- Bug: fix a couple of buffer overflows in GIF support. Closes Debian #454297. + Closes Gnome #484105 + +1.99.7 + +- Bug: fix soversion. + +1.99.6 + +- New: API xmp_get_property_date() / xmp_set_property_date() with tests. +- New: API xmp_{get,set]_property_{float,bool,int32,int64}() with tests. +- New: API add ACR schema namespaces. +- Test: refactor a the tests preparation. +- Test: add a test for multiple initializations. +- Test: use valgrind is available. +- Bug: fix configure to allow building on MacOS X. Closes bug #13596 +- Bug #13707: add autogen.sh +- Bug #13713: fix boost macros to link boost.test statically (for 1.34). +- Bug #13712: skip tests if no boost. + +1.99.5 + +- ABI breakage: soversion is now 3 +- Change: API xmp_files_close(), xmp_files_put_xmp(), xmp_files_free(), + xmp_free(), xmp_iterator_free(), xmp_iterator_skip() now return bool. +- Change: API xmp_get_property_and_bits() renamed xmp_get_property(). +- Change: API xmp_set_property2() renamed xmp_set_property(). +- Bug: all API should check about input and return an error if + passed NULL. +- Bug: more exception handling and refactor set_error() use. +- Bug: add AC_CONFIG_MACRO_DIR to configure. +- Bug: Lower requirement for libboost to 1.33.0. +- New: Doxygen API doc generation. + +1.99.4 + +- New: GIF Files smart handlers +- New: API xmp_append_array_item(), xmp_delete_property(), + xmp_has_property(), xmp_get_localized_text(), + xmp_set_localized_text() +- New: API xmp_delete_localized_text() +- New: Exempi will be visible in the "generator" string of the XMP packet. +- Bug: xmp_files_open_new() will handle exceptions properly + +1.99.3 + +- New: API xmp_serialize{,_and_format}() and the + corresponding option bits +- New API xmp_get_array_item() + +1.99.2 + +- New: API xmp_copy() +- New: API xmp_set_property2(), xmp_set_array_item() + and xmp_get_error() +- New: API xmp_get_property_and_bits() +- Bug: xmp_files_get_xmp() handle exceptions properly. + +1.99.1 + +- Bug: store the TIFF tag as BYTE and not UNDEFINED + to comply with the spec. +- Bug: more exception catched. +- New: added NS_CC namespace +- New: API xmp_register_namespace() + +1.99.0 + +Initial release of the 2.0 series. Based on Adobe XMP SDK 4.1.1 + diff --git a/README b/README new file mode 100644 index 0000000..97a8f53 --- /dev/null +++ b/README @@ -0,0 +1,16 @@ +exempi is a port of Adobe XMP SDK to work on UNIX and to be build with +GNU automake. + +It includes XMPCore and XMPFiles, libexempi, a C-based API and exempi +a command line tool. + +It require at least the gcc 4.6.0 C++ compiler or clang++ with some +support for C++11. + +It is maintained by Hubert Figuiere + +Contributors: + Frankie Dintino + + +Homepage: http://libopenraw.freedesktop.org/wiki/Exempi diff --git a/TODO b/TODO new file mode 100644 index 0000000..aa03630 --- /dev/null +++ b/TODO @@ -0,0 +1,9 @@ +Things to do: + +- allow providing your own IO +- add handler for Ogg (need to be defined) +- reconcile CC license from Ogg to XMP. Check for other media +format like MP3 +- add support for QuickTime without having quicktime +- bug gna #10110 +- add support for PDF (using PoDoFo?) diff --git a/XMPCore/Makefile.am b/XMPCore/Makefile.am new file mode 100644 index 0000000..525e2e5 --- /dev/null +++ b/XMPCore/Makefile.am @@ -0,0 +1,3 @@ + + +SUBDIRS=source diff --git a/XMPCore/Makefile.in b/XMPCore/Makefile.in new file mode 100644 index 0000000..74de8ac --- /dev/null +++ b/XMPCore/Makefile.in @@ -0,0 +1,642 @@ +# Makefile.in generated by automake 1.15.1 from Makefile.am. +# @configure_input@ + +# Copyright (C) 1994-2017 Free Software Foundation, Inc. + +# This Makefile.in is free software; the Free Software Foundation +# gives unlimited permission to copy and/or distribute it, +# with or without modifications, as long as this notice is preserved. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY, to the extent permitted by law; without +# even the implied warranty of MERCHANTABILITY or FITNESS FOR A +# PARTICULAR PURPOSE. + +@SET_MAKE@ +VPATH = @srcdir@ +am__is_gnu_make = { \ + if test -z '$(MAKELEVEL)'; then \ + false; \ + elif test -n '$(MAKE_HOST)'; then \ + true; \ + elif test -n '$(MAKE_VERSION)' && test -n '$(CURDIR)'; then \ + true; \ + else \ + false; \ + fi; \ +} +am__make_running_with_option = \ + case $${target_option-} in \ + ?) ;; \ + *) echo "am__make_running_with_option: internal error: invalid" \ + "target option '$${target_option-}' specified" >&2; \ + exit 1;; \ + esac; \ + has_opt=no; \ + sane_makeflags=$$MAKEFLAGS; \ + if $(am__is_gnu_make); then \ + sane_makeflags=$$MFLAGS; \ + else \ + case $$MAKEFLAGS in \ + *\\[\ \ ]*) \ + bs=\\; \ + sane_makeflags=`printf '%s\n' "$$MAKEFLAGS" \ + | sed "s/$$bs$$bs[$$bs $$bs ]*//g"`;; \ + esac; \ + fi; \ + skip_next=no; \ + strip_trailopt () \ + { \ + flg=`printf '%s\n' "$$flg" | sed "s/$$1.*$$//"`; \ + }; \ + for flg in $$sane_makeflags; do \ + test $$skip_next = yes && { skip_next=no; continue; }; \ + case $$flg in \ + *=*|--*) continue;; \ + -*I) strip_trailopt 'I'; skip_next=yes;; \ + -*I?*) strip_trailopt 'I';; \ + -*O) strip_trailopt 'O'; skip_next=yes;; \ + -*O?*) strip_trailopt 'O';; \ + -*l) strip_trailopt 'l'; skip_next=yes;; \ + -*l?*) strip_trailopt 'l';; \ + -[dEDm]) skip_next=yes;; \ + -[JT]) skip_next=yes;; \ + esac; \ + case $$flg in \ + *$$target_option*) has_opt=yes; break;; \ + esac; \ + done; \ + test $$has_opt = yes +am__make_dryrun = (target_option=n; $(am__make_running_with_option)) +am__make_keepgoing = (target_option=k; $(am__make_running_with_option)) +pkgdatadir = $(datadir)/@PACKAGE@ +pkgincludedir = $(includedir)/@PACKAGE@ +pkglibdir = $(libdir)/@PACKAGE@ +pkglibexecdir = $(libexecdir)/@PACKAGE@ +am__cd = CDPATH="$${ZSH_VERSION+.}$(PATH_SEPARATOR)" && cd +install_sh_DATA = $(install_sh) -c -m 644 +install_sh_PROGRAM = $(install_sh) -c +install_sh_SCRIPT = $(install_sh) -c +INSTALL_HEADER = $(INSTALL_DATA) +transform = $(program_transform_name) +NORMAL_INSTALL = : +PRE_INSTALL = : +POST_INSTALL = : +NORMAL_UNINSTALL = : +PRE_UNINSTALL = : +POST_UNINSTALL = : +build_triplet = @build@ +host_triplet = @host@ +subdir = XMPCore +ACLOCAL_M4 = $(top_srcdir)/aclocal.m4 +am__aclocal_m4_deps = $(top_srcdir)/m4/ax_cflags_gcc_option.m4 \ + $(top_srcdir)/m4/ax_cxx_compile_stdcxx_11.m4 \ + $(top_srcdir)/m4/ax_ld_check_flag.m4 \ + $(top_srcdir)/m4/ax_tls.m4 $(top_srcdir)/m4/boost.m4 \ + $(top_srcdir)/m4/libtool.m4 $(top_srcdir)/m4/ltoptions.m4 \ + $(top_srcdir)/m4/ltsugar.m4 $(top_srcdir)/m4/ltversion.m4 \ + $(top_srcdir)/m4/lt~obsolete.m4 $(top_srcdir)/configure.ac +am__configure_deps = $(am__aclocal_m4_deps) $(CONFIGURE_DEPENDENCIES) \ + $(ACLOCAL_M4) +DIST_COMMON = $(srcdir)/Makefile.am $(am__DIST_COMMON) +mkinstalldirs = $(install_sh) -d +CONFIG_CLEAN_FILES = +CONFIG_CLEAN_VPATH_FILES = +AM_V_P = $(am__v_P_@AM_V@) +am__v_P_ = $(am__v_P_@AM_DEFAULT_V@) +am__v_P_0 = false +am__v_P_1 = : +AM_V_GEN = $(am__v_GEN_@AM_V@) +am__v_GEN_ = $(am__v_GEN_@AM_DEFAULT_V@) +am__v_GEN_0 = @echo " GEN " $@; +am__v_GEN_1 = +AM_V_at = $(am__v_at_@AM_V@) +am__v_at_ = $(am__v_at_@AM_DEFAULT_V@) +am__v_at_0 = @ +am__v_at_1 = +SOURCES = +DIST_SOURCES = +RECURSIVE_TARGETS = all-recursive check-recursive cscopelist-recursive \ + ctags-recursive dvi-recursive html-recursive info-recursive \ + install-data-recursive install-dvi-recursive \ + install-exec-recursive install-html-recursive \ + install-info-recursive install-pdf-recursive \ + install-ps-recursive install-recursive installcheck-recursive \ + installdirs-recursive pdf-recursive ps-recursive \ + tags-recursive uninstall-recursive +am__can_run_installinfo = \ + case $$AM_UPDATE_INFO_DIR in \ + n|no|NO) false;; \ + *) (install-info --version) >/dev/null 2>&1;; \ + esac +RECURSIVE_CLEAN_TARGETS = mostlyclean-recursive clean-recursive \ + distclean-recursive maintainer-clean-recursive +am__recursive_targets = \ + $(RECURSIVE_TARGETS) \ + $(RECURSIVE_CLEAN_TARGETS) \ + $(am__extra_recursive_targets) +AM_RECURSIVE_TARGETS = $(am__recursive_targets:-recursive=) TAGS CTAGS \ + distdir +am__tagged_files = $(HEADERS) $(SOURCES) $(TAGS_FILES) $(LISP) +# Read a list of newline-separated strings from the standard input, +# and print each of them once, without duplicates. Input order is +# *not* preserved. +am__uniquify_input = $(AWK) '\ + BEGIN { nonempty = 0; } \ + { items[$$0] = 1; nonempty = 1; } \ + END { if (nonempty) { for (i in items) print i; }; } \ +' +# Make sure the list of sources is unique. This is necessary because, +# e.g., the same source file might be shared among _SOURCES variables +# for different programs/libraries. +am__define_uniq_tagged_files = \ + list='$(am__tagged_files)'; \ + unique=`for i in $$list; do \ + if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \ + done | $(am__uniquify_input)` +ETAGS = etags +CTAGS = ctags +DIST_SUBDIRS = $(SUBDIRS) +am__DIST_COMMON = $(srcdir)/Makefile.in +DISTFILES = $(DIST_COMMON) $(DIST_SOURCES) $(TEXINFOS) $(EXTRA_DIST) +am__relativize = \ + dir0=`pwd`; \ + sed_first='s,^\([^/]*\)/.*$$,\1,'; \ + sed_rest='s,^[^/]*/*,,'; \ + sed_last='s,^.*/\([^/]*\)$$,\1,'; \ + sed_butlast='s,/*[^/]*$$,,'; \ + while test -n "$$dir1"; do \ + first=`echo "$$dir1" | sed -e "$$sed_first"`; \ + if test "$$first" != "."; then \ + if test "$$first" = ".."; then \ + dir2=`echo "$$dir0" | sed -e "$$sed_last"`/"$$dir2"; \ + dir0=`echo "$$dir0" | sed -e "$$sed_butlast"`; \ + else \ + first2=`echo "$$dir2" | sed -e "$$sed_first"`; \ + if test "$$first2" = "$$first"; then \ + dir2=`echo "$$dir2" | sed -e "$$sed_rest"`; \ + else \ + dir2="../$$dir2"; \ + fi; \ + dir0="$$dir0"/"$$first"; \ + fi; \ + fi; \ + dir1=`echo "$$dir1" | sed -e "$$sed_rest"`; \ + done; \ + reldir="$$dir2" +ACLOCAL = @ACLOCAL@ +AMTAR = @AMTAR@ +AM_DEFAULT_VERBOSITY = @AM_DEFAULT_VERBOSITY@ +AR = @AR@ +AUTOCONF = @AUTOCONF@ +AUTOHEADER = @AUTOHEADER@ +AUTOMAKE = @AUTOMAKE@ +AWK = @AWK@ +BOOST_CPPFLAGS = @BOOST_CPPFLAGS@ +BOOST_LDPATH = @BOOST_LDPATH@ +BOOST_ROOT = @BOOST_ROOT@ +BOOST_UNIT_TEST_FRAMEWORK_LDFLAGS = @BOOST_UNIT_TEST_FRAMEWORK_LDFLAGS@ +BOOST_UNIT_TEST_FRAMEWORK_LDPATH = @BOOST_UNIT_TEST_FRAMEWORK_LDPATH@ +BOOST_UNIT_TEST_FRAMEWORK_LIBS = @BOOST_UNIT_TEST_FRAMEWORK_LIBS@ +CC = @CC@ +CCDEPMODE = @CCDEPMODE@ +CFLAGS = @CFLAGS@ +CPP = @CPP@ +CPPFLAGS = @CPPFLAGS@ +CXX = @CXX@ +CXXCPP = @CXXCPP@ +CXXDEPMODE = @CXXDEPMODE@ +CXXFLAGS = @CXXFLAGS@ +CYGPATH_W = @CYGPATH_W@ +DEFS = @DEFS@ +DEPDIR = @DEPDIR@ +DISTCHECK_CONFIGURE_FLAGS = @DISTCHECK_CONFIGURE_FLAGS@ +DLLTOOL = @DLLTOOL@ +DSYMUTIL = @DSYMUTIL@ +DUMPBIN = @DUMPBIN@ +ECHO_C = @ECHO_C@ +ECHO_N = @ECHO_N@ +ECHO_T = @ECHO_T@ +EGREP = @EGREP@ +EXEEXT = @EXEEXT@ +EXEMPI_AGE = @EXEMPI_AGE@ +EXEMPI_CURRENT = @EXEMPI_CURRENT@ +EXEMPI_CURRENT_MIN = @EXEMPI_CURRENT_MIN@ +EXEMPI_INCLUDE_BASE = @EXEMPI_INCLUDE_BASE@ +EXEMPI_MAJOR_VERSION = @EXEMPI_MAJOR_VERSION@ +EXEMPI_PLATFORM_DEF = @EXEMPI_PLATFORM_DEF@ +EXEMPI_REVISION = @EXEMPI_REVISION@ +EXEMPI_VERSION_INFO = @EXEMPI_VERSION_INFO@ +FGREP = @FGREP@ +GREP = @GREP@ +HAVE_CXX11 = @HAVE_CXX11@ +INSTALL = @INSTALL@ +INSTALL_DATA = @INSTALL_DATA@ +INSTALL_PROGRAM = @INSTALL_PROGRAM@ +INSTALL_SCRIPT = @INSTALL_SCRIPT@ +INSTALL_STRIP_PROGRAM = @INSTALL_STRIP_PROGRAM@ +LD = @LD@ +LDFLAGS = @LDFLAGS@ +LIBOBJS = @LIBOBJS@ +LIBS = @LIBS@ +LIBTOOL = @LIBTOOL@ +LIPO = @LIPO@ +LN_S = @LN_S@ +LTLIBOBJS = @LTLIBOBJS@ +LT_SYS_LIBRARY_PATH = @LT_SYS_LIBRARY_PATH@ +MAINT = @MAINT@ +MAKEINFO = @MAKEINFO@ +MANIFEST_TOOL = @MANIFEST_TOOL@ +MKDIR_P = @MKDIR_P@ +NM = @NM@ +NMEDIT = @NMEDIT@ +OBJDUMP = @OBJDUMP@ +OBJEXT = @OBJEXT@ +OTOOL = @OTOOL@ +OTOOL64 = @OTOOL64@ +PACKAGE = @PACKAGE@ +PACKAGE_BUGREPORT = @PACKAGE_BUGREPORT@ +PACKAGE_NAME = @PACKAGE_NAME@ +PACKAGE_STRING = @PACKAGE_STRING@ +PACKAGE_TARNAME = @PACKAGE_TARNAME@ +PACKAGE_URL = @PACKAGE_URL@ +PACKAGE_VERSION = @PACKAGE_VERSION@ +PATH_SEPARATOR = @PATH_SEPARATOR@ +RANLIB = @RANLIB@ +SED = @SED@ +SET_MAKE = @SET_MAKE@ +SHELL = @SHELL@ +STRIP = @STRIP@ +VALGRIND = @VALGRIND@ +VERSION = @VERSION@ +XMPCORE_CPPFLAGS = @XMPCORE_CPPFLAGS@ +abs_builddir = @abs_builddir@ +abs_srcdir = @abs_srcdir@ +abs_top_builddir = @abs_top_builddir@ +abs_top_srcdir = @abs_top_srcdir@ +ac_ct_AR = @ac_ct_AR@ +ac_ct_CC = @ac_ct_CC@ +ac_ct_CXX = @ac_ct_CXX@ +ac_ct_DUMPBIN = @ac_ct_DUMPBIN@ +am__include = @am__include@ +am__leading_dot = @am__leading_dot@ +am__quote = @am__quote@ +am__tar = @am__tar@ +am__untar = @am__untar@ +bindir = @bindir@ +build = @build@ +build_alias = @build_alias@ +build_cpu = @build_cpu@ +build_os = @build_os@ +build_vendor = @build_vendor@ +builddir = @builddir@ +datadir = @datadir@ +datarootdir = @datarootdir@ +docdir = @docdir@ +dvidir = @dvidir@ +exec_prefix = @exec_prefix@ +host = @host@ +host_alias = @host_alias@ +host_cpu = @host_cpu@ +host_os = @host_os@ +host_vendor = @host_vendor@ +htmldir = @htmldir@ +includedir = @includedir@ +infodir = @infodir@ +install_sh = @install_sh@ +libdir = @libdir@ +libexecdir = @libexecdir@ +localedir = @localedir@ +localstatedir = @localstatedir@ +mandir = @mandir@ +mkdir_p = @mkdir_p@ +oldincludedir = @oldincludedir@ +pdfdir = @pdfdir@ +pkgconfigdir = @pkgconfigdir@ +prefix = @prefix@ +program_transform_name = @program_transform_name@ +psdir = @psdir@ +sbindir = @sbindir@ +sharedstatedir = @sharedstatedir@ +srcdir = @srcdir@ +sysconfdir = @sysconfdir@ +target_alias = @target_alias@ +top_build_prefix = @top_build_prefix@ +top_builddir = @top_builddir@ +top_srcdir = @top_srcdir@ +SUBDIRS = source +all: all-recursive + +.SUFFIXES: +$(srcdir)/Makefile.in: @MAINTAINER_MODE_TRUE@ $(srcdir)/Makefile.am $(am__configure_deps) + @for dep in $?; do \ + case '$(am__configure_deps)' in \ + *$$dep*) \ + ( cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh ) \ + && { if test -f $@; then exit 0; else break; fi; }; \ + exit 1;; \ + esac; \ + done; \ + echo ' cd $(top_srcdir) && $(AUTOMAKE) --foreign XMPCore/Makefile'; \ + $(am__cd) $(top_srcdir) && \ + $(AUTOMAKE) --foreign XMPCore/Makefile +Makefile: $(srcdir)/Makefile.in $(top_builddir)/config.status + @case '$?' in \ + *config.status*) \ + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh;; \ + *) \ + echo ' cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__depfiles_maybe)'; \ + cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__depfiles_maybe);; \ + esac; + +$(top_builddir)/config.status: $(top_srcdir)/configure $(CONFIG_STATUS_DEPENDENCIES) + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh + +$(top_srcdir)/configure: @MAINTAINER_MODE_TRUE@ $(am__configure_deps) + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh +$(ACLOCAL_M4): @MAINTAINER_MODE_TRUE@ $(am__aclocal_m4_deps) + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh +$(am__aclocal_m4_deps): + +mostlyclean-libtool: + -rm -f *.lo + +clean-libtool: + -rm -rf .libs _libs + +# This directory's subdirectories are mostly independent; you can cd +# into them and run 'make' without going through this Makefile. +# To change the values of 'make' variables: instead of editing Makefiles, +# (1) if the variable is set in 'config.status', edit 'config.status' +# (which will cause the Makefiles to be regenerated when you run 'make'); +# (2) otherwise, pass the desired values on the 'make' command line. +$(am__recursive_targets): + @fail=; \ + if $(am__make_keepgoing); then \ + failcom='fail=yes'; \ + else \ + failcom='exit 1'; \ + fi; \ + dot_seen=no; \ + target=`echo $@ | sed s/-recursive//`; \ + case "$@" in \ + distclean-* | maintainer-clean-*) list='$(DIST_SUBDIRS)' ;; \ + *) list='$(SUBDIRS)' ;; \ + esac; \ + for subdir in $$list; do \ + echo "Making $$target in $$subdir"; \ + if test "$$subdir" = "."; then \ + dot_seen=yes; \ + local_target="$$target-am"; \ + else \ + local_target="$$target"; \ + fi; \ + ($(am__cd) $$subdir && $(MAKE) $(AM_MAKEFLAGS) $$local_target) \ + || eval $$failcom; \ + done; \ + if test "$$dot_seen" = "no"; then \ + $(MAKE) $(AM_MAKEFLAGS) "$$target-am" || exit 1; \ + fi; test -z "$$fail" + +ID: $(am__tagged_files) + $(am__define_uniq_tagged_files); mkid -fID $$unique +tags: tags-recursive +TAGS: tags + +tags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files) + set x; \ + here=`pwd`; \ + if ($(ETAGS) --etags-include --version) >/dev/null 2>&1; then \ + include_option=--etags-include; \ + empty_fix=.; \ + else \ + include_option=--include; \ + empty_fix=; \ + fi; \ + list='$(SUBDIRS)'; for subdir in $$list; do \ + if test "$$subdir" = .; then :; else \ + test ! -f $$subdir/TAGS || \ + set "$$@" "$$include_option=$$here/$$subdir/TAGS"; \ + fi; \ + done; \ + $(am__define_uniq_tagged_files); \ + shift; \ + if test -z "$(ETAGS_ARGS)$$*$$unique"; then :; else \ + test -n "$$unique" || unique=$$empty_fix; \ + if test $$# -gt 0; then \ + $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \ + "$$@" $$unique; \ + else \ + $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \ + $$unique; \ + fi; \ + fi +ctags: ctags-recursive + +CTAGS: ctags +ctags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files) + $(am__define_uniq_tagged_files); \ + test -z "$(CTAGS_ARGS)$$unique" \ + || $(CTAGS) $(CTAGSFLAGS) $(AM_CTAGSFLAGS) $(CTAGS_ARGS) \ + $$unique + +GTAGS: + here=`$(am__cd) $(top_builddir) && pwd` \ + && $(am__cd) $(top_srcdir) \ + && gtags -i $(GTAGS_ARGS) "$$here" +cscopelist: cscopelist-recursive + +cscopelist-am: $(am__tagged_files) + list='$(am__tagged_files)'; \ + case "$(srcdir)" in \ + [\\/]* | ?:[\\/]*) sdir="$(srcdir)" ;; \ + *) sdir=$(subdir)/$(srcdir) ;; \ + esac; \ + for i in $$list; do \ + if test -f "$$i"; then \ + echo "$(subdir)/$$i"; \ + else \ + echo "$$sdir/$$i"; \ + fi; \ + done >> $(top_builddir)/cscope.files + +distclean-tags: + -rm -f TAGS ID GTAGS GRTAGS GSYMS GPATH tags + +distdir: $(DISTFILES) + @srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \ + topsrcdirstrip=`echo "$(top_srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \ + list='$(DISTFILES)'; \ + dist_files=`for file in $$list; do echo $$file; done | \ + sed -e "s|^$$srcdirstrip/||;t" \ + -e "s|^$$topsrcdirstrip/|$(top_builddir)/|;t"`; \ + case $$dist_files in \ + */*) $(MKDIR_P) `echo "$$dist_files" | \ + sed '/\//!d;s|^|$(distdir)/|;s,/[^/]*$$,,' | \ + sort -u` ;; \ + esac; \ + for file in $$dist_files; do \ + if test -f $$file || test -d $$file; then d=.; else d=$(srcdir); fi; \ + if test -d $$d/$$file; then \ + dir=`echo "/$$file" | sed -e 's,/[^/]*$$,,'`; \ + if test -d "$(distdir)/$$file"; then \ + find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \ + fi; \ + if test -d $(srcdir)/$$file && test $$d != $(srcdir); then \ + cp -fpR $(srcdir)/$$file "$(distdir)$$dir" || exit 1; \ + find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \ + fi; \ + cp -fpR $$d/$$file "$(distdir)$$dir" || exit 1; \ + else \ + test -f "$(distdir)/$$file" \ + || cp -p $$d/$$file "$(distdir)/$$file" \ + || exit 1; \ + fi; \ + done + @list='$(DIST_SUBDIRS)'; for subdir in $$list; do \ + if test "$$subdir" = .; then :; else \ + $(am__make_dryrun) \ + || test -d "$(distdir)/$$subdir" \ + || $(MKDIR_P) "$(distdir)/$$subdir" \ + || exit 1; \ + dir1=$$subdir; dir2="$(distdir)/$$subdir"; \ + $(am__relativize); \ + new_distdir=$$reldir; \ + dir1=$$subdir; dir2="$(top_distdir)"; \ + $(am__relativize); \ + new_top_distdir=$$reldir; \ + echo " (cd $$subdir && $(MAKE) $(AM_MAKEFLAGS) top_distdir="$$new_top_distdir" distdir="$$new_distdir" \\"; \ + echo " am__remove_distdir=: am__skip_length_check=: am__skip_mode_fix=: distdir)"; \ + ($(am__cd) $$subdir && \ + $(MAKE) $(AM_MAKEFLAGS) \ + top_distdir="$$new_top_distdir" \ + distdir="$$new_distdir" \ + am__remove_distdir=: \ + am__skip_length_check=: \ + am__skip_mode_fix=: \ + distdir) \ + || exit 1; \ + fi; \ + done +check-am: all-am +check: check-recursive +all-am: Makefile +installdirs: installdirs-recursive +installdirs-am: +install: install-recursive +install-exec: install-exec-recursive +install-data: install-data-recursive +uninstall: uninstall-recursive + +install-am: all-am + @$(MAKE) $(AM_MAKEFLAGS) install-exec-am install-data-am + +installcheck: installcheck-recursive +install-strip: + if test -z '$(STRIP)'; then \ + $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \ + install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \ + install; \ + else \ + $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \ + install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \ + "INSTALL_PROGRAM_ENV=STRIPPROG='$(STRIP)'" install; \ + fi +mostlyclean-generic: + +clean-generic: + +distclean-generic: + -test -z "$(CONFIG_CLEAN_FILES)" || rm -f $(CONFIG_CLEAN_FILES) + -test . = "$(srcdir)" || test -z "$(CONFIG_CLEAN_VPATH_FILES)" || rm -f $(CONFIG_CLEAN_VPATH_FILES) + +maintainer-clean-generic: + @echo "This command is intended for maintainers to use" + @echo "it deletes files that may require special tools to rebuild." +clean: clean-recursive + +clean-am: clean-generic clean-libtool mostlyclean-am + +distclean: distclean-recursive + -rm -f Makefile +distclean-am: clean-am distclean-generic distclean-tags + +dvi: dvi-recursive + +dvi-am: + +html: html-recursive + +html-am: + +info: info-recursive + +info-am: + +install-data-am: + +install-dvi: install-dvi-recursive + +install-dvi-am: + +install-exec-am: + +install-html: install-html-recursive + +install-html-am: + +install-info: install-info-recursive + +install-info-am: + +install-man: + +install-pdf: install-pdf-recursive + +install-pdf-am: + +install-ps: install-ps-recursive + +install-ps-am: + +installcheck-am: + +maintainer-clean: maintainer-clean-recursive + -rm -f Makefile +maintainer-clean-am: distclean-am maintainer-clean-generic + +mostlyclean: mostlyclean-recursive + +mostlyclean-am: mostlyclean-generic mostlyclean-libtool + +pdf: pdf-recursive + +pdf-am: + +ps: ps-recursive + +ps-am: + +uninstall-am: + +.MAKE: $(am__recursive_targets) install-am install-strip + +.PHONY: $(am__recursive_targets) CTAGS GTAGS TAGS all all-am check \ + check-am clean clean-generic clean-libtool cscopelist-am ctags \ + ctags-am distclean distclean-generic distclean-libtool \ + distclean-tags distdir dvi dvi-am html html-am info info-am \ + install install-am install-data install-data-am install-dvi \ + install-dvi-am install-exec install-exec-am install-html \ + install-html-am install-info install-info-am install-man \ + install-pdf install-pdf-am install-ps install-ps-am \ + install-strip installcheck installcheck-am installdirs \ + installdirs-am maintainer-clean maintainer-clean-generic \ + mostlyclean mostlyclean-generic mostlyclean-libtool pdf pdf-am \ + ps ps-am tags tags-am uninstall uninstall-am + +.PRECIOUS: Makefile + + +# Tell versions [3.59,3.63) of GNU make to not export all variables. +# Otherwise a system limit (for SysV at least) may be exceeded. +.NOEXPORT: diff --git a/XMPCore/source/ExpatAdapter.cpp b/XMPCore/source/ExpatAdapter.cpp new file mode 100644 index 0000000..0951079 --- /dev/null +++ b/XMPCore/source/ExpatAdapter.cpp @@ -0,0 +1,522 @@ +// ================================================================================================= +// 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" // ! Must be the first #include! +#include "XMPCore/source/XMPCore_Impl.hpp" + +#include "source/ExpatAdapter.hpp" +#include "XMPCore/source/XMPMeta.hpp" + +#include "third-party/expat/lib/expat.h" +#include + +using namespace std; + +#if XMP_WinBuild + #pragma warning ( disable : 4996 ) // '...' was declared deprecated +#endif + +// *** Set memory handlers. + +#ifndef DumpXMLParseEvents + #define DumpXMLParseEvents 0 +#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 StartCdataSectionHandler ( void * userData ); +static void EndCdataSectionHandler ( void * userData ); + +static void ProcessingInstructionHandler ( void * userData, XMP_StringPtr target, XMP_StringPtr data ); +static void CommentHandler ( void * userData, XMP_StringPtr comment ); + +#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 + +// ================================================================================================= + +extern "C" ExpatAdapter * XMP_NewExpatAdapter ( bool useGlobalNamespaces ) +{ + + return new ExpatAdapter ( useGlobalNamespaces ); + +} // XMP_NewExpatAdapter + +// ================================================================================================= + +ExpatAdapter::ExpatAdapter ( bool useGlobalNamespaces ) : parser(0), registeredNamespaces(0) +{ + + #if XMP_DebugBuild + this->elemNesting = 0; + #if DumpXMLParseEvents + if ( this->parseLog == 0 ) this->parseLog = stdout; + #endif + #endif + + 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{ + if ( useGlobalNamespaces ) { + this->registeredNamespaces = sRegisteredNamespaces; + } else { + this->registeredNamespaces = new XMP_NamespaceTable ( *sRegisteredNamespaces ); + } + + XML_SetUserData ( this->parser, this ); + + XML_SetNamespaceDeclHandler ( this->parser, StartNamespaceDeclHandler, EndNamespaceDeclHandler ); + XML_SetElementHandler ( this->parser, StartElementHandler, EndElementHandler ); + + XML_SetCharacterDataHandler ( this->parser, CharacterDataHandler ); + XML_SetCdataSectionHandler ( this->parser, StartCdataSectionHandler, EndCdataSectionHandler ); + + XML_SetProcessingInstructionHandler ( this->parser, ProcessingInstructionHandler ); + XML_SetCommentHandler ( this->parser, CommentHandler ); + + #if BanAllEntityUsage + XML_SetStartDoctypeDeclHandler ( this->parser, StartDoctypeDeclHandler ); + isAborted = false; + #endif + + this->parseStack.push_back ( &this->tree ); // Push the XML root node. + } +} // ExpatAdapter::ExpatAdapter + +// ================================================================================================= + +ExpatAdapter::~ExpatAdapter() +{ + + if ( this->parser != 0 ) XML_ParserFree ( this->parser ); + this->parser = 0; + + if ( this->registeredNamespaces != sRegisteredNamespaces ) delete ( this->registeredNamespaces ); + this->registeredNamespaces = 0; + +} // ExpatAdapter::~ExpatAdapter + +// ================================================================================================= + +#if XMP_DebugBuild + static XMP_VarString sExpatMessage; +#endif + +static const char * kOneSpace = " "; + +void ExpatAdapter::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; + buffer = kOneSpace; + length = 1; + } + + status = XML_Parse ( this->parser, (const char *)buffer, 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_StringPtr errMsg = "XML parsing failure"; + + #if 0 // XMP_DebugBuild // Disable for now to make test output uniform. Restore later with thread safety. + + // *** This is a good candidate for a callback error notification mechanism. + // *** This code is not thread safe, the sExpatMessage isn't locked. But that's OK for debug usage. + + enum XML_Error expatErr = XML_GetErrorCode ( this->parser ); + const char * expatMsg = XML_ErrorString ( expatErr ); + int errLine = XML_GetCurrentLineNumber ( this->parser ); + + char msgBuffer[1000]; + // AUDIT: Use of sizeof(msgBuffer) for snprintf length is safe. + snprintf ( msgBuffer, sizeof(msgBuffer), "# Expat error %d at line %d, \"%s\"", expatErr, errLine, expatMsg ); + sExpatMessage = msgBuffer; + errMsg = sExpatMessage.c_str(); + + #if DumpXMLParseEvents + if ( this->parseLog != 0 ) fprintf ( this->parseLog, "%s\n", errMsg, expatErr, errLine, expatMsg ); + #endif + + #endif + + XMP_Error error(kXMPErr_BadXML, errMsg); + this->NotifyClient ( kXMPErrSev_Recoverable, error ); + + } + +} // ExpatAdapter::ParseBuffer + +// ================================================================================================= +// ================================================================================================= + +#if XMP_DebugBuild & DumpXMLParseEvents + + static inline void PrintIndent ( FILE * file, size_t count ) + { + for ( ; count > 0; --count ) fprintf ( file, " " ); + } + +#endif + +// ================================================================================================= + +static void SetQualName ( ExpatAdapter * thiz, XMP_StringPtr fullName, XML_Node * node ) +{ + // Expat delivers the full name as a catenation of namespace URI, separator, and local name. + + // As a compatibility hack, an "about" or "ID" attribute of an rdf:Description element is + // changed to "rdf:about" or rdf:ID. Easier done here than in the RDF recognizer. + + // As a bug fix hack, change a URI of "http://purl.org/dc/1.1/" to ""http://purl.org/dc/elements/1.1/. + // Early versions of Flash that put XMP in SWF used a bad URI for the dc: namespace. + + // ! This code presumes the RDF namespace prefix is "rdf". + + size_t sepPos = strlen(fullName); + for ( --sepPos; sepPos > 0; --sepPos ) { + if ( fullName[sepPos] == FullNameSeparator ) break; + } + + if ( fullName[sepPos] == FullNameSeparator ) { + + XMP_StringPtr prefix; + XMP_StringLen prefixLen; + XMP_StringPtr localPart = fullName + sepPos + 1; + + node->ns.assign ( fullName, sepPos ); + if ( node->ns == "http://purl.org/dc/1.1/" ) node->ns = "http://purl.org/dc/elements/1.1/"; + + bool found = thiz->registeredNamespaces->GetPrefix ( node->ns.c_str(), &prefix, &prefixLen ); + if ( ! found ) { + XMP_Error error(kXMPErr_ExternalFailure, "Unknown URI in Expat full name" ); + thiz->NotifyClient ( kXMPErrSev_OperationFatal, error ); + } + node->nsPrefixLen = prefixLen; // ! Includes the ':'. + + node->name = prefix; + node->name += localPart; + + } else { + + node->name = fullName; // The name is not in a namespace. + + if ( node->parent->name == "rdf:Description" ) { + if ( node->name == "about" ) { + node->ns = kXMP_NS_RDF; + node->name = "rdf:about"; + node->nsPrefixLen = 4; // ! Include the ':'. + } else if ( node->name == "ID" ) { + node->ns = kXMP_NS_RDF; + node->name = "rdf:ID"; + node->nsPrefixLen = 4; // ! Include the ':'. + } + } + + } + +} // SetQualName + +// ================================================================================================= + +static void StartNamespaceDeclHandler ( void * userData, XMP_StringPtr prefix, XMP_StringPtr uri ) +{ + IgnoreParam(userData); + + // As a bug fix hack, change a URI of "http://purl.org/dc/1.1/" to ""http://purl.org/dc/elements/1.1/. + // Early versions of Flash that put XMP in SWF used a bad URI for the dc: namespace. + + ExpatAdapter * thiz = (ExpatAdapter*)userData; + + if ( prefix == 0 ) prefix = "_dflt_"; // Have default namespace. + if ( uri == 0 ) return; // Ignore, have xmlns:pre="", no URI to register. + + #if XMP_DebugBuild & DumpXMLParseEvents + if ( thiz->parseLog != 0 ) { + PrintIndent ( thiz->parseLog, thiz->elemNesting ); + fprintf ( thiz->parseLog, "StartNamespace: %s - \"%s\"\n", prefix, uri ); + } + #endif + + if ( XMP_LitMatch ( uri, "http://purl.org/dc/1.1/" ) ) uri = "http://purl.org/dc/elements/1.1/"; + (void) thiz->registeredNamespaces->Define ( uri, prefix, 0, 0 ); + +} // StartNamespaceDeclHandler + +// ================================================================================================= + +static void EndNamespaceDeclHandler ( void * userData, XMP_StringPtr prefix ) +{ + IgnoreParam(userData); + + #if XMP_DebugBuild & DumpXMLParseEvents // Avoid unused variable warning. + ExpatAdapter * thiz = (ExpatAdapter*)userData; + #endif + + if ( prefix == 0 ) prefix = "_dflt_"; // Have default namespace. + + #if XMP_DebugBuild & DumpXMLParseEvents + if ( thiz->parseLog != 0 ) { + PrintIndent ( thiz->parseLog, thiz->elemNesting ); + fprintf ( thiz->parseLog, "EndNamespace: %s\n", prefix ); + } + #endif + + // ! Nothing to do, Expat has done all of the XML processing. + +} // EndNamespaceDeclHandler + +// ================================================================================================= + +static void StartElementHandler ( void * userData, XMP_StringPtr name, XMP_StringPtr* attrs ) +{ + XMP_Assert ( attrs != 0 ); + ExpatAdapter * thiz = (ExpatAdapter*)userData; + + size_t attrCount = 0; + for ( XMP_StringPtr* a = attrs; *a != 0; ++a ) ++attrCount; + if ( (attrCount & 1) != 0 ) { + XMP_Error error(kXMPErr_ExternalFailure, "Expat attribute info has odd length"); + thiz->NotifyClient ( kXMPErrSev_OperationFatal, error ); + } + attrCount = attrCount/2; // They are name/value pairs. + + #if XMP_DebugBuild & DumpXMLParseEvents + if ( thiz->parseLog != 0 ) { + PrintIndent ( thiz->parseLog, thiz->elemNesting ); + fprintf ( thiz->parseLog, "StartElement: %s, %d attrs", name, attrCount ); + for ( XMP_StringPtr* attr = attrs; *attr != 0; attr += 2 ) { + XMP_StringPtr attrName = *attr; + XMP_StringPtr attrValue = *(attr+1); + fprintf ( thiz->parseLog, ", %s = \"%s\"", attrName, attrValue ); + } + fprintf ( thiz->parseLog, "\n" ); + } + #endif + + XML_Node * parentNode = thiz->parseStack.back(); + XML_Node * elemNode = new XML_Node ( parentNode, "", kElemNode ); + + SetQualName ( thiz, name, elemNode ); + + for ( XMP_StringPtr* attr = attrs; *attr != 0; attr += 2 ) { + + XMP_StringPtr attrName = *attr; + XMP_StringPtr attrValue = *(attr+1); + XML_Node * attrNode = new XML_Node ( elemNode, "", kAttrNode ); + + SetQualName ( thiz, attrName, attrNode ); + attrNode->value = attrValue; + if ( attrNode->name == "xml:lang" ) NormalizeLangValue ( &attrNode->value ); + elemNode->attrs.push_back ( attrNode ); + + } + + parentNode->content.push_back ( elemNode ); + thiz->parseStack.push_back ( elemNode ); + + if ( elemNode->name == "rdf:RDF" ) { + thiz->rootNode = elemNode; + ++thiz->rootCount; + } + #if XMP_DebugBuild + ++thiz->elemNesting; + #endif + +} // StartElementHandler + +// ================================================================================================= + +static void EndElementHandler ( void * userData, XMP_StringPtr name ) +{ + IgnoreParam(name); + + ExpatAdapter * thiz = (ExpatAdapter*)userData; + + #if XMP_DebugBuild + --thiz->elemNesting; + #endif + (void) thiz->parseStack.pop_back(); + + #if XMP_DebugBuild & DumpXMLParseEvents + if ( thiz->parseLog != 0 ) { + PrintIndent ( thiz->parseLog, thiz->elemNesting ); + fprintf ( thiz->parseLog, "EndElement: %s\n", name ); + } + #endif + +} // EndElementHandler + +// ================================================================================================= + +static void CharacterDataHandler ( void * userData, XMP_StringPtr cData, int len ) +{ + ExpatAdapter * thiz = (ExpatAdapter*)userData; + + if ( (cData == 0) || (len == 0) ) { cData = ""; len = 0; } + + #if XMP_DebugBuild & DumpXMLParseEvents + if ( thiz->parseLog != 0 ) { + PrintIndent ( thiz->parseLog, thiz->elemNesting ); + fprintf ( thiz->parseLog, "CharContent: \"" ); + for ( int i = 0; i < len; ++i ) fprintf ( thiz->parseLog, "%c", cData[i] ); + fprintf ( thiz->parseLog, "\"\n" ); + } + #endif + + 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 StartCdataSectionHandler ( void * userData ) +{ + IgnoreParam(userData); + + #if XMP_DebugBuild & DumpXMLParseEvents // Avoid unused variable warning. + ExpatAdapter * thiz = (ExpatAdapter*)userData; + #endif + + #if XMP_DebugBuild & DumpXMLParseEvents + if ( thiz->parseLog != 0 ) { + PrintIndent ( thiz->parseLog, thiz->elemNesting ); + fprintf ( thiz->parseLog, "StartCDATA\n" ); + } + #endif + + // *** Since markup isn't recognized inside CDATA, this affects XMP's double escaping. + +} // StartCdataSectionHandler + +// ================================================================================================= + +static void EndCdataSectionHandler ( void * userData ) +{ + IgnoreParam(userData); + + #if XMP_DebugBuild & DumpXMLParseEvents // Avoid unused variable warning. + ExpatAdapter * thiz = (ExpatAdapter*)userData; + #endif + + #if XMP_DebugBuild & DumpXMLParseEvents + if ( thiz->parseLog != 0 ) { + PrintIndent ( thiz->parseLog, thiz->elemNesting ); + fprintf ( thiz->parseLog, "EndCDATA\n" ); + } + #endif + +} // EndCdataSectionHandler + +// ================================================================================================= + +static void ProcessingInstructionHandler ( void * userData, XMP_StringPtr target, XMP_StringPtr data ) +{ + XMP_Assert ( target != 0 ); + ExpatAdapter * thiz = (ExpatAdapter*)userData; + + if ( ! XMP_LitMatch ( target, "xpacket" ) ) return; // Ignore all PIs except the XMP packet wrapper. + if ( data == 0 ) data = ""; + + #if XMP_DebugBuild & DumpXMLParseEvents + if ( thiz->parseLog != 0 ) { + PrintIndent ( thiz->parseLog, thiz->elemNesting ); + fprintf ( thiz->parseLog, "PI: %s - \"%s\"\n", target, data ); + } + #endif + + XML_Node * parentNode = thiz->parseStack.back(); + XML_Node * piNode = new XML_Node ( parentNode, target, kPINode ); + + piNode->value.assign ( data ); + parentNode->content.push_back ( piNode ); + +} // ProcessingInstructionHandler + +// ================================================================================================= + +static void CommentHandler ( void * userData, XMP_StringPtr comment ) +{ + IgnoreParam(userData); + + #if XMP_DebugBuild & DumpXMLParseEvents // Avoid unused variable warning. + ExpatAdapter * thiz = (ExpatAdapter*)userData; + #endif + + if ( comment == 0 ) comment = ""; + + #if XMP_DebugBuild & DumpXMLParseEvents + if ( thiz->parseLog != 0 ) { + PrintIndent ( thiz->parseLog, thiz->elemNesting ); + fprintf ( thiz->parseLog, "Comment: \"%s\"\n", comment ); + } + #endif + + // ! Comments are ignored. + +} // CommentHandler + +// ================================================================================================= + +#if BanAllEntityUsage +static void StartDoctypeDeclHandler ( void * userData, XMP_StringPtr doctypeName, + XMP_StringPtr sysid, XMP_StringPtr pubid, int has_internal_subset ) +{ + IgnoreParam(userData); + + ExpatAdapter * thiz = (ExpatAdapter*)userData; + + #if XMP_DebugBuild & DumpXMLParseEvents // Avoid unused variable warning. + if ( thiz->parseLog != 0 ) { + PrintIndent ( thiz->parseLog, thiz->elemNesting ); + fprintf ( thiz->parseLog, "DocType: \"%s\"\n", doctypeName ); + } + #endif + + 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/XMPCore/source/Makefile.am b/XMPCore/source/Makefile.am new file mode 100644 index 0000000..309d255 --- /dev/null +++ b/XMPCore/source/Makefile.am @@ -0,0 +1,64 @@ +# +# exempi - Makefile.am +# +# Copyright (C) 2007-2013 Hubert Figuiere +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions +# are met: +# +# 1 Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# +# 2 Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in the +# documentation and/or other materials provided with the +# distribution. +# +# 3 Neither the name of the Authors, nor the names of its +# contributors may be used to endorse or promote products derived +# from this software wit hout specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS +# FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE +# COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, +# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, +# STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED +# OF THE POSSIBILITY OF SUCH DAMAGE. +# + + + +noinst_LTLIBRARIES = libXMPCore.la + +AM_CXXFLAGS = -Wno-multichar -Wno-ctor-dtor-privacy \ + -funsigned-char -fexceptions +AM_CPPFLAGS = -I$(top_srcdir) -I$(top_srcdir)/public/include \ + -Wall @XMPCORE_CPPFLAGS@ + +noinst_HEADERS = \ + XMPCore_Impl.hpp XMPIterator.hpp \ + XMPMeta.hpp XMPUtils.hpp + + +libXMPCore_la_SOURCES = ExpatAdapter.cpp \ + WXMPIterator.cpp \ + WXMPUtils.cpp \ + XMPIterator.cpp \ + XMPMeta-GetSet.cpp \ + XMPMeta-Serialize.cpp \ + XMPUtils-FileInfo.cpp \ + ParseRDF.cpp \ + WXMPMeta.cpp \ + XMPCore_Impl.cpp \ + XMPMeta.cpp \ + XMPMeta-Parse.cpp \ + XMPUtils.cpp + diff --git a/XMPCore/source/Makefile.in b/XMPCore/source/Makefile.in new file mode 100644 index 0000000..a90aa76 --- /dev/null +++ b/XMPCore/source/Makefile.in @@ -0,0 +1,690 @@ +# Makefile.in generated by automake 1.15.1 from Makefile.am. +# @configure_input@ + +# Copyright (C) 1994-2017 Free Software Foundation, Inc. + +# This Makefile.in is free software; the Free Software Foundation +# gives unlimited permission to copy and/or distribute it, +# with or without modifications, as long as this notice is preserved. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY, to the extent permitted by law; without +# even the implied warranty of MERCHANTABILITY or FITNESS FOR A +# PARTICULAR PURPOSE. + +@SET_MAKE@ + +# +# exempi - Makefile.am +# +# Copyright (C) 2007-2013 Hubert Figuiere +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions +# are met: +# +# 1 Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# +# 2 Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in the +# documentation and/or other materials provided with the +# distribution. +# +# 3 Neither the name of the Authors, nor the names of its +# contributors may be used to endorse or promote products derived +# from this software wit hout specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS +# FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE +# COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, +# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, +# STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED +# OF THE POSSIBILITY OF SUCH DAMAGE. +# + + +VPATH = @srcdir@ +am__is_gnu_make = { \ + if test -z '$(MAKELEVEL)'; then \ + false; \ + elif test -n '$(MAKE_HOST)'; then \ + true; \ + elif test -n '$(MAKE_VERSION)' && test -n '$(CURDIR)'; then \ + true; \ + else \ + false; \ + fi; \ +} +am__make_running_with_option = \ + case $${target_option-} in \ + ?) ;; \ + *) echo "am__make_running_with_option: internal error: invalid" \ + "target option '$${target_option-}' specified" >&2; \ + exit 1;; \ + esac; \ + has_opt=no; \ + sane_makeflags=$$MAKEFLAGS; \ + if $(am__is_gnu_make); then \ + sane_makeflags=$$MFLAGS; \ + else \ + case $$MAKEFLAGS in \ + *\\[\ \ ]*) \ + bs=\\; \ + sane_makeflags=`printf '%s\n' "$$MAKEFLAGS" \ + | sed "s/$$bs$$bs[$$bs $$bs ]*//g"`;; \ + esac; \ + fi; \ + skip_next=no; \ + strip_trailopt () \ + { \ + flg=`printf '%s\n' "$$flg" | sed "s/$$1.*$$//"`; \ + }; \ + for flg in $$sane_makeflags; do \ + test $$skip_next = yes && { skip_next=no; continue; }; \ + case $$flg in \ + *=*|--*) continue;; \ + -*I) strip_trailopt 'I'; skip_next=yes;; \ + -*I?*) strip_trailopt 'I';; \ + -*O) strip_trailopt 'O'; skip_next=yes;; \ + -*O?*) strip_trailopt 'O';; \ + -*l) strip_trailopt 'l'; skip_next=yes;; \ + -*l?*) strip_trailopt 'l';; \ + -[dEDm]) skip_next=yes;; \ + -[JT]) skip_next=yes;; \ + esac; \ + case $$flg in \ + *$$target_option*) has_opt=yes; break;; \ + esac; \ + done; \ + test $$has_opt = yes +am__make_dryrun = (target_option=n; $(am__make_running_with_option)) +am__make_keepgoing = (target_option=k; $(am__make_running_with_option)) +pkgdatadir = $(datadir)/@PACKAGE@ +pkgincludedir = $(includedir)/@PACKAGE@ +pkglibdir = $(libdir)/@PACKAGE@ +pkglibexecdir = $(libexecdir)/@PACKAGE@ +am__cd = CDPATH="$${ZSH_VERSION+.}$(PATH_SEPARATOR)" && cd +install_sh_DATA = $(install_sh) -c -m 644 +install_sh_PROGRAM = $(install_sh) -c +install_sh_SCRIPT = $(install_sh) -c +INSTALL_HEADER = $(INSTALL_DATA) +transform = $(program_transform_name) +NORMAL_INSTALL = : +PRE_INSTALL = : +POST_INSTALL = : +NORMAL_UNINSTALL = : +PRE_UNINSTALL = : +POST_UNINSTALL = : +build_triplet = @build@ +host_triplet = @host@ +subdir = XMPCore/source +ACLOCAL_M4 = $(top_srcdir)/aclocal.m4 +am__aclocal_m4_deps = $(top_srcdir)/m4/ax_cflags_gcc_option.m4 \ + $(top_srcdir)/m4/ax_cxx_compile_stdcxx_11.m4 \ + $(top_srcdir)/m4/ax_ld_check_flag.m4 \ + $(top_srcdir)/m4/ax_tls.m4 $(top_srcdir)/m4/boost.m4 \ + $(top_srcdir)/m4/libtool.m4 $(top_srcdir)/m4/ltoptions.m4 \ + $(top_srcdir)/m4/ltsugar.m4 $(top_srcdir)/m4/ltversion.m4 \ + $(top_srcdir)/m4/lt~obsolete.m4 $(top_srcdir)/configure.ac +am__configure_deps = $(am__aclocal_m4_deps) $(CONFIGURE_DEPENDENCIES) \ + $(ACLOCAL_M4) +DIST_COMMON = $(srcdir)/Makefile.am $(noinst_HEADERS) \ + $(am__DIST_COMMON) +mkinstalldirs = $(install_sh) -d +CONFIG_CLEAN_FILES = +CONFIG_CLEAN_VPATH_FILES = +LTLIBRARIES = $(noinst_LTLIBRARIES) +libXMPCore_la_LIBADD = +am_libXMPCore_la_OBJECTS = ExpatAdapter.lo WXMPIterator.lo \ + WXMPUtils.lo XMPIterator.lo XMPMeta-GetSet.lo \ + XMPMeta-Serialize.lo XMPUtils-FileInfo.lo ParseRDF.lo \ + WXMPMeta.lo XMPCore_Impl.lo XMPMeta.lo XMPMeta-Parse.lo \ + XMPUtils.lo +libXMPCore_la_OBJECTS = $(am_libXMPCore_la_OBJECTS) +AM_V_lt = $(am__v_lt_@AM_V@) +am__v_lt_ = $(am__v_lt_@AM_DEFAULT_V@) +am__v_lt_0 = --silent +am__v_lt_1 = +AM_V_P = $(am__v_P_@AM_V@) +am__v_P_ = $(am__v_P_@AM_DEFAULT_V@) +am__v_P_0 = false +am__v_P_1 = : +AM_V_GEN = $(am__v_GEN_@AM_V@) +am__v_GEN_ = $(am__v_GEN_@AM_DEFAULT_V@) +am__v_GEN_0 = @echo " GEN " $@; +am__v_GEN_1 = +AM_V_at = $(am__v_at_@AM_V@) +am__v_at_ = $(am__v_at_@AM_DEFAULT_V@) +am__v_at_0 = @ +am__v_at_1 = +DEFAULT_INCLUDES = -I.@am__isrc@ +depcomp = $(SHELL) $(top_srcdir)/depcomp +am__depfiles_maybe = depfiles +am__mv = mv -f +CXXCOMPILE = $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) \ + $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) +LTCXXCOMPILE = $(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) \ + $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) \ + $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) \ + $(AM_CXXFLAGS) $(CXXFLAGS) +AM_V_CXX = $(am__v_CXX_@AM_V@) +am__v_CXX_ = $(am__v_CXX_@AM_DEFAULT_V@) +am__v_CXX_0 = @echo " CXX " $@; +am__v_CXX_1 = +CXXLD = $(CXX) +CXXLINK = $(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) \ + $(LIBTOOLFLAGS) --mode=link $(CXXLD) $(AM_CXXFLAGS) \ + $(CXXFLAGS) $(AM_LDFLAGS) $(LDFLAGS) -o $@ +AM_V_CXXLD = $(am__v_CXXLD_@AM_V@) +am__v_CXXLD_ = $(am__v_CXXLD_@AM_DEFAULT_V@) +am__v_CXXLD_0 = @echo " CXXLD " $@; +am__v_CXXLD_1 = +SOURCES = $(libXMPCore_la_SOURCES) +DIST_SOURCES = $(libXMPCore_la_SOURCES) +am__can_run_installinfo = \ + case $$AM_UPDATE_INFO_DIR in \ + n|no|NO) false;; \ + *) (install-info --version) >/dev/null 2>&1;; \ + esac +HEADERS = $(noinst_HEADERS) +am__tagged_files = $(HEADERS) $(SOURCES) $(TAGS_FILES) $(LISP) +# Read a list of newline-separated strings from the standard input, +# and print each of them once, without duplicates. Input order is +# *not* preserved. +am__uniquify_input = $(AWK) '\ + BEGIN { nonempty = 0; } \ + { items[$$0] = 1; nonempty = 1; } \ + END { if (nonempty) { for (i in items) print i; }; } \ +' +# Make sure the list of sources is unique. This is necessary because, +# e.g., the same source file might be shared among _SOURCES variables +# for different programs/libraries. +am__define_uniq_tagged_files = \ + list='$(am__tagged_files)'; \ + unique=`for i in $$list; do \ + if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \ + done | $(am__uniquify_input)` +ETAGS = etags +CTAGS = ctags +am__DIST_COMMON = $(srcdir)/Makefile.in $(top_srcdir)/depcomp +DISTFILES = $(DIST_COMMON) $(DIST_SOURCES) $(TEXINFOS) $(EXTRA_DIST) +ACLOCAL = @ACLOCAL@ +AMTAR = @AMTAR@ +AM_DEFAULT_VERBOSITY = @AM_DEFAULT_VERBOSITY@ +AR = @AR@ +AUTOCONF = @AUTOCONF@ +AUTOHEADER = @AUTOHEADER@ +AUTOMAKE = @AUTOMAKE@ +AWK = @AWK@ +BOOST_CPPFLAGS = @BOOST_CPPFLAGS@ +BOOST_LDPATH = @BOOST_LDPATH@ +BOOST_ROOT = @BOOST_ROOT@ +BOOST_UNIT_TEST_FRAMEWORK_LDFLAGS = @BOOST_UNIT_TEST_FRAMEWORK_LDFLAGS@ +BOOST_UNIT_TEST_FRAMEWORK_LDPATH = @BOOST_UNIT_TEST_FRAMEWORK_LDPATH@ +BOOST_UNIT_TEST_FRAMEWORK_LIBS = @BOOST_UNIT_TEST_FRAMEWORK_LIBS@ +CC = @CC@ +CCDEPMODE = @CCDEPMODE@ +CFLAGS = @CFLAGS@ +CPP = @CPP@ +CPPFLAGS = @CPPFLAGS@ +CXX = @CXX@ +CXXCPP = @CXXCPP@ +CXXDEPMODE = @CXXDEPMODE@ +CXXFLAGS = @CXXFLAGS@ +CYGPATH_W = @CYGPATH_W@ +DEFS = @DEFS@ +DEPDIR = @DEPDIR@ +DISTCHECK_CONFIGURE_FLAGS = @DISTCHECK_CONFIGURE_FLAGS@ +DLLTOOL = @DLLTOOL@ +DSYMUTIL = @DSYMUTIL@ +DUMPBIN = @DUMPBIN@ +ECHO_C = @ECHO_C@ +ECHO_N = @ECHO_N@ +ECHO_T = @ECHO_T@ +EGREP = @EGREP@ +EXEEXT = @EXEEXT@ +EXEMPI_AGE = @EXEMPI_AGE@ +EXEMPI_CURRENT = @EXEMPI_CURRENT@ +EXEMPI_CURRENT_MIN = @EXEMPI_CURRENT_MIN@ +EXEMPI_INCLUDE_BASE = @EXEMPI_INCLUDE_BASE@ +EXEMPI_MAJOR_VERSION = @EXEMPI_MAJOR_VERSION@ +EXEMPI_PLATFORM_DEF = @EXEMPI_PLATFORM_DEF@ +EXEMPI_REVISION = @EXEMPI_REVISION@ +EXEMPI_VERSION_INFO = @EXEMPI_VERSION_INFO@ +FGREP = @FGREP@ +GREP = @GREP@ +HAVE_CXX11 = @HAVE_CXX11@ +INSTALL = @INSTALL@ +INSTALL_DATA = @INSTALL_DATA@ +INSTALL_PROGRAM = @INSTALL_PROGRAM@ +INSTALL_SCRIPT = @INSTALL_SCRIPT@ +INSTALL_STRIP_PROGRAM = @INSTALL_STRIP_PROGRAM@ +LD = @LD@ +LDFLAGS = @LDFLAGS@ +LIBOBJS = @LIBOBJS@ +LIBS = @LIBS@ +LIBTOOL = @LIBTOOL@ +LIPO = @LIPO@ +LN_S = @LN_S@ +LTLIBOBJS = @LTLIBOBJS@ +LT_SYS_LIBRARY_PATH = @LT_SYS_LIBRARY_PATH@ +MAINT = @MAINT@ +MAKEINFO = @MAKEINFO@ +MANIFEST_TOOL = @MANIFEST_TOOL@ +MKDIR_P = @MKDIR_P@ +NM = @NM@ +NMEDIT = @NMEDIT@ +OBJDUMP = @OBJDUMP@ +OBJEXT = @OBJEXT@ +OTOOL = @OTOOL@ +OTOOL64 = @OTOOL64@ +PACKAGE = @PACKAGE@ +PACKAGE_BUGREPORT = @PACKAGE_BUGREPORT@ +PACKAGE_NAME = @PACKAGE_NAME@ +PACKAGE_STRING = @PACKAGE_STRING@ +PACKAGE_TARNAME = @PACKAGE_TARNAME@ +PACKAGE_URL = @PACKAGE_URL@ +PACKAGE_VERSION = @PACKAGE_VERSION@ +PATH_SEPARATOR = @PATH_SEPARATOR@ +RANLIB = @RANLIB@ +SED = @SED@ +SET_MAKE = @SET_MAKE@ +SHELL = @SHELL@ +STRIP = @STRIP@ +VALGRIND = @VALGRIND@ +VERSION = @VERSION@ +XMPCORE_CPPFLAGS = @XMPCORE_CPPFLAGS@ +abs_builddir = @abs_builddir@ +abs_srcdir = @abs_srcdir@ +abs_top_builddir = @abs_top_builddir@ +abs_top_srcdir = @abs_top_srcdir@ +ac_ct_AR = @ac_ct_AR@ +ac_ct_CC = @ac_ct_CC@ +ac_ct_CXX = @ac_ct_CXX@ +ac_ct_DUMPBIN = @ac_ct_DUMPBIN@ +am__include = @am__include@ +am__leading_dot = @am__leading_dot@ +am__quote = @am__quote@ +am__tar = @am__tar@ +am__untar = @am__untar@ +bindir = @bindir@ +build = @build@ +build_alias = @build_alias@ +build_cpu = @build_cpu@ +build_os = @build_os@ +build_vendor = @build_vendor@ +builddir = @builddir@ +datadir = @datadir@ +datarootdir = @datarootdir@ +docdir = @docdir@ +dvidir = @dvidir@ +exec_prefix = @exec_prefix@ +host = @host@ +host_alias = @host_alias@ +host_cpu = @host_cpu@ +host_os = @host_os@ +host_vendor = @host_vendor@ +htmldir = @htmldir@ +includedir = @includedir@ +infodir = @infodir@ +install_sh = @install_sh@ +libdir = @libdir@ +libexecdir = @libexecdir@ +localedir = @localedir@ +localstatedir = @localstatedir@ +mandir = @mandir@ +mkdir_p = @mkdir_p@ +oldincludedir = @oldincludedir@ +pdfdir = @pdfdir@ +pkgconfigdir = @pkgconfigdir@ +prefix = @prefix@ +program_transform_name = @program_transform_name@ +psdir = @psdir@ +sbindir = @sbindir@ +sharedstatedir = @sharedstatedir@ +srcdir = @srcdir@ +sysconfdir = @sysconfdir@ +target_alias = @target_alias@ +top_build_prefix = @top_build_prefix@ +top_builddir = @top_builddir@ +top_srcdir = @top_srcdir@ +noinst_LTLIBRARIES = libXMPCore.la +AM_CXXFLAGS = -Wno-multichar -Wno-ctor-dtor-privacy \ + -funsigned-char -fexceptions + +AM_CPPFLAGS = -I$(top_srcdir) -I$(top_srcdir)/public/include \ + -Wall @XMPCORE_CPPFLAGS@ + +noinst_HEADERS = \ + XMPCore_Impl.hpp XMPIterator.hpp \ + XMPMeta.hpp XMPUtils.hpp + +libXMPCore_la_SOURCES = ExpatAdapter.cpp \ + WXMPIterator.cpp \ + WXMPUtils.cpp \ + XMPIterator.cpp \ + XMPMeta-GetSet.cpp \ + XMPMeta-Serialize.cpp \ + XMPUtils-FileInfo.cpp \ + ParseRDF.cpp \ + WXMPMeta.cpp \ + XMPCore_Impl.cpp \ + XMPMeta.cpp \ + XMPMeta-Parse.cpp \ + XMPUtils.cpp + +all: all-am + +.SUFFIXES: +.SUFFIXES: .cpp .lo .o .obj +$(srcdir)/Makefile.in: @MAINTAINER_MODE_TRUE@ $(srcdir)/Makefile.am $(am__configure_deps) + @for dep in $?; do \ + case '$(am__configure_deps)' in \ + *$$dep*) \ + ( cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh ) \ + && { if test -f $@; then exit 0; else break; fi; }; \ + exit 1;; \ + esac; \ + done; \ + echo ' cd $(top_srcdir) && $(AUTOMAKE) --foreign XMPCore/source/Makefile'; \ + $(am__cd) $(top_srcdir) && \ + $(AUTOMAKE) --foreign XMPCore/source/Makefile +Makefile: $(srcdir)/Makefile.in $(top_builddir)/config.status + @case '$?' in \ + *config.status*) \ + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh;; \ + *) \ + echo ' cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__depfiles_maybe)'; \ + cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__depfiles_maybe);; \ + esac; + +$(top_builddir)/config.status: $(top_srcdir)/configure $(CONFIG_STATUS_DEPENDENCIES) + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh + +$(top_srcdir)/configure: @MAINTAINER_MODE_TRUE@ $(am__configure_deps) + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh +$(ACLOCAL_M4): @MAINTAINER_MODE_TRUE@ $(am__aclocal_m4_deps) + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh +$(am__aclocal_m4_deps): + +clean-noinstLTLIBRARIES: + -test -z "$(noinst_LTLIBRARIES)" || rm -f $(noinst_LTLIBRARIES) + @list='$(noinst_LTLIBRARIES)'; \ + locs=`for p in $$list; do echo $$p; done | \ + sed 's|^[^/]*$$|.|; s|/[^/]*$$||; s|$$|/so_locations|' | \ + sort -u`; \ + test -z "$$locs" || { \ + echo rm -f $${locs}; \ + rm -f $${locs}; \ + } + +libXMPCore.la: $(libXMPCore_la_OBJECTS) $(libXMPCore_la_DEPENDENCIES) $(EXTRA_libXMPCore_la_DEPENDENCIES) + $(AM_V_CXXLD)$(CXXLINK) $(libXMPCore_la_OBJECTS) $(libXMPCore_la_LIBADD) $(LIBS) + +mostlyclean-compile: + -rm -f *.$(OBJEXT) + +distclean-compile: + -rm -f *.tab.c + +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/ExpatAdapter.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/ParseRDF.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/WXMPIterator.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/WXMPMeta.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/WXMPUtils.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/XMPCore_Impl.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/XMPIterator.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/XMPMeta-GetSet.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/XMPMeta-Parse.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/XMPMeta-Serialize.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/XMPMeta.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/XMPUtils-FileInfo.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/XMPUtils.Plo@am__quote@ + +.cpp.o: +@am__fastdepCXX_TRUE@ $(AM_V_CXX)depbase=`echo $@ | sed 's|[^/]*$$|$(DEPDIR)/&|;s|\.o$$||'`;\ +@am__fastdepCXX_TRUE@ $(CXXCOMPILE) -MT $@ -MD -MP -MF $$depbase.Tpo -c -o $@ $< &&\ +@am__fastdepCXX_TRUE@ $(am__mv) $$depbase.Tpo $$depbase.Po +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXXCOMPILE) -c -o $@ $< + +.cpp.obj: +@am__fastdepCXX_TRUE@ $(AM_V_CXX)depbase=`echo $@ | sed 's|[^/]*$$|$(DEPDIR)/&|;s|\.obj$$||'`;\ +@am__fastdepCXX_TRUE@ $(CXXCOMPILE) -MT $@ -MD -MP -MF $$depbase.Tpo -c -o $@ `$(CYGPATH_W) '$<'` &&\ +@am__fastdepCXX_TRUE@ $(am__mv) $$depbase.Tpo $$depbase.Po +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXXCOMPILE) -c -o $@ `$(CYGPATH_W) '$<'` + +.cpp.lo: +@am__fastdepCXX_TRUE@ $(AM_V_CXX)depbase=`echo $@ | sed 's|[^/]*$$|$(DEPDIR)/&|;s|\.lo$$||'`;\ +@am__fastdepCXX_TRUE@ $(LTCXXCOMPILE) -MT $@ -MD -MP -MF $$depbase.Tpo -c -o $@ $< &&\ +@am__fastdepCXX_TRUE@ $(am__mv) $$depbase.Tpo $$depbase.Plo +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='$<' object='$@' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(LTCXXCOMPILE) -c -o $@ $< + +mostlyclean-libtool: + -rm -f *.lo + +clean-libtool: + -rm -rf .libs _libs + +ID: $(am__tagged_files) + $(am__define_uniq_tagged_files); mkid -fID $$unique +tags: tags-am +TAGS: tags + +tags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files) + set x; \ + here=`pwd`; \ + $(am__define_uniq_tagged_files); \ + shift; \ + if test -z "$(ETAGS_ARGS)$$*$$unique"; then :; else \ + test -n "$$unique" || unique=$$empty_fix; \ + if test $$# -gt 0; then \ + $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \ + "$$@" $$unique; \ + else \ + $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \ + $$unique; \ + fi; \ + fi +ctags: ctags-am + +CTAGS: ctags +ctags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files) + $(am__define_uniq_tagged_files); \ + test -z "$(CTAGS_ARGS)$$unique" \ + || $(CTAGS) $(CTAGSFLAGS) $(AM_CTAGSFLAGS) $(CTAGS_ARGS) \ + $$unique + +GTAGS: + here=`$(am__cd) $(top_builddir) && pwd` \ + && $(am__cd) $(top_srcdir) \ + && gtags -i $(GTAGS_ARGS) "$$here" +cscopelist: cscopelist-am + +cscopelist-am: $(am__tagged_files) + list='$(am__tagged_files)'; \ + case "$(srcdir)" in \ + [\\/]* | ?:[\\/]*) sdir="$(srcdir)" ;; \ + *) sdir=$(subdir)/$(srcdir) ;; \ + esac; \ + for i in $$list; do \ + if test -f "$$i"; then \ + echo "$(subdir)/$$i"; \ + else \ + echo "$$sdir/$$i"; \ + fi; \ + done >> $(top_builddir)/cscope.files + +distclean-tags: + -rm -f TAGS ID GTAGS GRTAGS GSYMS GPATH tags + +distdir: $(DISTFILES) + @srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \ + topsrcdirstrip=`echo "$(top_srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \ + list='$(DISTFILES)'; \ + dist_files=`for file in $$list; do echo $$file; done | \ + sed -e "s|^$$srcdirstrip/||;t" \ + -e "s|^$$topsrcdirstrip/|$(top_builddir)/|;t"`; \ + case $$dist_files in \ + */*) $(MKDIR_P) `echo "$$dist_files" | \ + sed '/\//!d;s|^|$(distdir)/|;s,/[^/]*$$,,' | \ + sort -u` ;; \ + esac; \ + for file in $$dist_files; do \ + if test -f $$file || test -d $$file; then d=.; else d=$(srcdir); fi; \ + if test -d $$d/$$file; then \ + dir=`echo "/$$file" | sed -e 's,/[^/]*$$,,'`; \ + if test -d "$(distdir)/$$file"; then \ + find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \ + fi; \ + if test -d $(srcdir)/$$file && test $$d != $(srcdir); then \ + cp -fpR $(srcdir)/$$file "$(distdir)$$dir" || exit 1; \ + find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \ + fi; \ + cp -fpR $$d/$$file "$(distdir)$$dir" || exit 1; \ + else \ + test -f "$(distdir)/$$file" \ + || cp -p $$d/$$file "$(distdir)/$$file" \ + || exit 1; \ + fi; \ + done +check-am: all-am +check: check-am +all-am: Makefile $(LTLIBRARIES) $(HEADERS) +installdirs: +install: install-am +install-exec: install-exec-am +install-data: install-data-am +uninstall: uninstall-am + +install-am: all-am + @$(MAKE) $(AM_MAKEFLAGS) install-exec-am install-data-am + +installcheck: installcheck-am +install-strip: + if test -z '$(STRIP)'; then \ + $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \ + install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \ + install; \ + else \ + $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \ + install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \ + "INSTALL_PROGRAM_ENV=STRIPPROG='$(STRIP)'" install; \ + fi +mostlyclean-generic: + +clean-generic: + +distclean-generic: + -test -z "$(CONFIG_CLEAN_FILES)" || rm -f $(CONFIG_CLEAN_FILES) + -test . = "$(srcdir)" || test -z "$(CONFIG_CLEAN_VPATH_FILES)" || rm -f $(CONFIG_CLEAN_VPATH_FILES) + +maintainer-clean-generic: + @echo "This command is intended for maintainers to use" + @echo "it deletes files that may require special tools to rebuild." +clean: clean-am + +clean-am: clean-generic clean-libtool clean-noinstLTLIBRARIES \ + mostlyclean-am + +distclean: distclean-am + -rm -rf ./$(DEPDIR) + -rm -f Makefile +distclean-am: clean-am distclean-compile distclean-generic \ + distclean-tags + +dvi: dvi-am + +dvi-am: + +html: html-am + +html-am: + +info: info-am + +info-am: + +install-data-am: + +install-dvi: install-dvi-am + +install-dvi-am: + +install-exec-am: + +install-html: install-html-am + +install-html-am: + +install-info: install-info-am + +install-info-am: + +install-man: + +install-pdf: install-pdf-am + +install-pdf-am: + +install-ps: install-ps-am + +install-ps-am: + +installcheck-am: + +maintainer-clean: maintainer-clean-am + -rm -rf ./$(DEPDIR) + -rm -f Makefile +maintainer-clean-am: distclean-am maintainer-clean-generic + +mostlyclean: mostlyclean-am + +mostlyclean-am: mostlyclean-compile mostlyclean-generic \ + mostlyclean-libtool + +pdf: pdf-am + +pdf-am: + +ps: ps-am + +ps-am: + +uninstall-am: + +.MAKE: install-am install-strip + +.PHONY: CTAGS GTAGS TAGS all all-am check check-am clean clean-generic \ + clean-libtool clean-noinstLTLIBRARIES cscopelist-am ctags \ + ctags-am distclean distclean-compile distclean-generic \ + distclean-libtool distclean-tags distdir dvi dvi-am html \ + html-am info info-am install install-am install-data \ + install-data-am install-dvi install-dvi-am install-exec \ + install-exec-am install-html install-html-am install-info \ + install-info-am install-man install-pdf install-pdf-am \ + install-ps install-ps-am install-strip installcheck \ + installcheck-am installdirs maintainer-clean \ + maintainer-clean-generic mostlyclean mostlyclean-compile \ + mostlyclean-generic mostlyclean-libtool pdf pdf-am ps ps-am \ + tags tags-am uninstall uninstall-am + +.PRECIOUS: Makefile + + +# Tell versions [3.59,3.63) of GNU make to not export all variables. +# Otherwise a system limit (for SysV at least) may be exceeded. +.NOEXPORT: diff --git a/XMPCore/source/ParseRDF.cpp b/XMPCore/source/ParseRDF.cpp new file mode 100644 index 0000000..8ec0e87 --- /dev/null +++ b/XMPCore/source/ParseRDF.cpp @@ -0,0 +1,1407 @@ +// ================================================================================================= +// 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 "XMPCore/source/XMPCore_Impl.hpp" +#include "XMPCore/source/XMPMeta.hpp" +#include "source/ExpatAdapter.hpp" + +#include + +#if DEBUG + #include +#endif + +using namespace std; + +#if XMP_WinBuild + #pragma warning ( disable : 4189 ) // local variable is initialized but not referenced + #pragma warning ( disable : 4505 ) // unreferenced local function has been removed +#endif + +// ================================================================================================= + +// *** This might be faster and use less memory as a state machine. A big advantage of building an +// *** XML tree though is easy lookahead during the recursive descent processing. + +// *** It would be nice to give a line number or byte offset in the exception messages. + + +// 7 RDF/XML Grammar (from http://www.w3.org/TR/rdf-syntax-grammar/#section-Infoset-Grammar) +// +// 7.1 Grammar summary +// +// 7.2.2 coreSyntaxTerms +// rdf:RDF | rdf:ID | rdf:about | rdf:parseType | rdf:resource | rdf:nodeID | rdf:datatype +// +// 7.2.3 syntaxTerms +// coreSyntaxTerms | rdf:Description | rdf:li +// +// 7.2.4 oldTerms +// rdf:aboutEach | rdf:aboutEachPrefix | rdf:bagID +// +// 7.2.5 nodeElementURIs +// anyURI - ( coreSyntaxTerms | rdf:li | oldTerms ) +// +// 7.2.6 propertyElementURIs +// anyURI - ( coreSyntaxTerms | rdf:Description | oldTerms ) +// +// 7.2.7 propertyAttributeURIs +// anyURI - ( coreSyntaxTerms | rdf:Description | rdf:li | oldTerms ) +// +// 7.2.8 doc +// root ( document-element == RDF, children == list ( RDF ) ) +// +// 7.2.9 RDF +// start-element ( URI == rdf:RDF, attributes == set() ) +// nodeElementList +// end-element() +// +// 7.2.10 nodeElementList +// ws* ( nodeElement ws* )* +// +// 7.2.11 nodeElement +// start-element ( URI == nodeElementURIs, +// attributes == set ( ( idAttr | nodeIdAttr | aboutAttr )?, propertyAttr* ) ) +// propertyEltList +// end-element() +// +// 7.2.12 ws +// A text event matching white space defined by [XML] definition White Space Rule [3] S in section Common Syntactic Constructs. +// +// 7.2.13 propertyEltList +// ws* ( propertyElt ws* )* +// +// 7.2.14 propertyElt +// resourcePropertyElt | literalPropertyElt | parseTypeLiteralPropertyElt | +// parseTypeResourcePropertyElt | parseTypeCollectionPropertyElt | parseTypeOtherPropertyElt | emptyPropertyElt +// +// 7.2.15 resourcePropertyElt +// start-element ( URI == propertyElementURIs, attributes == set ( idAttr? ) ) +// ws* nodeElement ws* +// end-element() +// +// 7.2.16 literalPropertyElt +// start-element ( URI == propertyElementURIs, attributes == set ( idAttr?, datatypeAttr?) ) +// text() +// end-element() +// +// 7.2.17 parseTypeLiteralPropertyElt +// start-element ( URI == propertyElementURIs, attributes == set ( idAttr?, parseLiteral ) ) +// literal +// end-element() +// +// 7.2.18 parseTypeResourcePropertyElt +// start-element ( URI == propertyElementURIs, attributes == set ( idAttr?, parseResource ) ) +// propertyEltList +// end-element() +// +// 7.2.19 parseTypeCollectionPropertyElt +// start-element ( URI == propertyElementURIs, attributes == set ( idAttr?, parseCollection ) ) +// nodeElementList +// end-element() +// +// 7.2.20 parseTypeOtherPropertyElt +// start-element ( URI == propertyElementURIs, attributes == set ( idAttr?, parseOther ) ) +// propertyEltList +// end-element() +// +// 7.2.21 emptyPropertyElt +// start-element ( URI == propertyElementURIs, +// attributes == set ( idAttr?, ( resourceAttr | nodeIdAttr )?, propertyAttr* ) ) +// end-element() +// +// 7.2.22 idAttr +// attribute ( URI == rdf:ID, string-value == rdf-id ) +// +// 7.2.23 nodeIdAttr +// attribute ( URI == rdf:nodeID, string-value == rdf-id ) +// +// 7.2.24 aboutAttr +// attribute ( URI == rdf:about, string-value == URI-reference ) +// +// 7.2.25 propertyAttr +// attribute ( URI == propertyAttributeURIs, string-value == anyString ) +// +// 7.2.26 resourceAttr +// attribute ( URI == rdf:resource, string-value == URI-reference ) +// +// 7.2.27 datatypeAttr +// attribute ( URI == rdf:datatype, string-value == URI-reference ) +// +// 7.2.28 parseLiteral +// attribute ( URI == rdf:parseType, string-value == "Literal") +// +// 7.2.29 parseResource +// attribute ( URI == rdf:parseType, string-value == "Resource") +// +// 7.2.30 parseCollection +// attribute ( URI == rdf:parseType, string-value == "Collection") +// +// 7.2.31 parseOther +// attribute ( URI == rdf:parseType, string-value == anyString - ("Resource" | "Literal" | "Collection") ) +// +// 7.2.32 URI-reference +// An RDF URI Reference. +// +// 7.2.33 literal +// Any XML element content that is allowed according to [XML] definition Content of Elements Rule [43] content +// in section 3.1 Start-Tags, End-Tags, and Empty-Element Tags. +// +// 7.2.34 rdf-id +// An attribute string-value matching any legal [XML-NS] token NCName. + + +// ================================================================================================= +// Primary Parsing Functions +// ========================= +// +// Each of these is responsible for recognizing an RDF syntax production and adding the appropriate +// structure to the XMP tree. They simply return for success, failures will throw an exception. The +// class exists only to provide access to the error notification object. + +class RDF_Parser { +public: + + void RDF ( XMP_Node * xmpTree, const XML_Node & xmlNode ); + + void NodeElementList ( XMP_Node * xmpParent, const XML_Node & xmlParent, bool isTopLevel ); + + void NodeElement ( XMP_Node * xmpParent, const XML_Node & xmlNode, bool isTopLevel ); + + void NodeElementAttrs ( XMP_Node * xmpParent, const XML_Node & xmlNode, bool isTopLevel ); + + void PropertyElementList ( XMP_Node * xmpParent, const XML_Node & xmlParent, bool isTopLevel ); + + void PropertyElement ( XMP_Node * xmpParent, const XML_Node & xmlNode, bool isTopLevel ); + + void ResourcePropertyElement ( XMP_Node * xmpParent, const XML_Node & xmlNode, bool isTopLevel ); + + void LiteralPropertyElement ( XMP_Node * xmpParent, const XML_Node & xmlNode, bool isTopLevel ); + + void ParseTypeLiteralPropertyElement ( XMP_Node * xmpParent, const XML_Node & xmlNode, bool isTopLevel ); + + void ParseTypeResourcePropertyElement ( XMP_Node * xmpParent, const XML_Node & xmlNode, bool isTopLevel ); + + void ParseTypeCollectionPropertyElement ( XMP_Node * xmpParent, const XML_Node & xmlNode, bool isTopLevel ); + + void ParseTypeOtherPropertyElement ( XMP_Node * xmpParent, const XML_Node & xmlNode, bool isTopLevel ); + + void EmptyPropertyElement ( XMP_Node * xmpParent, const XML_Node & xmlNode, bool isTopLevel ); + + RDF_Parser ( XMPMeta::ErrorCallbackInfo * ec ) : errorCallback(ec) {}; + +private: + + RDF_Parser() { + + errorCallback = NULL; + + }; // Hidden on purpose. + + XMPMeta::ErrorCallbackInfo * errorCallback; + + XMP_Node * AddChildNode ( XMP_Node * xmpParent, const XML_Node & xmlNode, const XMP_StringPtr value, bool isTopLevel ); + + XMP_Node * AddQualifierNode ( XMP_Node * xmpParent, const XMP_VarString & name, const XMP_VarString & value ); + + XMP_Node * AddQualifierNode ( XMP_Node * xmpParent, const XML_Node & attr ); + + void FixupQualifiedNode ( XMP_Node * xmpParent ); + +}; + +enum { kIsTopLevel = true, kNotTopLevel = false }; + +// ================================================================================================= + +typedef XMP_Uns8 RDFTermKind; + +// *** Logic might be safer with just masks. + +enum { + kRDFTerm_Other = 0, + kRDFTerm_RDF = 1, // Start of coreSyntaxTerms. + kRDFTerm_ID = 2, + kRDFTerm_about = 3, + kRDFTerm_parseType = 4, + kRDFTerm_resource = 5, + kRDFTerm_nodeID = 6, + kRDFTerm_datatype = 7, // End of coreSyntaxTerms. + kRDFTerm_Description = 8, // Start of additions for syntaxTerms. + kRDFTerm_li = 9, // End of of additions for syntaxTerms. + kRDFTerm_aboutEach = 10, // Start of oldTerms. + kRDFTerm_aboutEachPrefix = 11, + kRDFTerm_bagID = 12, // End of oldTerms. + + kRDFTerm_FirstCore = kRDFTerm_RDF, + kRDFTerm_LastCore = kRDFTerm_datatype, + kRDFTerm_FirstSyntax = kRDFTerm_FirstCore, // ! Yes, the syntax terms include the core terms. + kRDFTerm_LastSyntax = kRDFTerm_li, + kRDFTerm_FirstOld = kRDFTerm_aboutEach, + kRDFTerm_LastOld = kRDFTerm_bagID +}; + +enum { + kRDFMask_Other = 1 << kRDFTerm_Other, + kRDFMask_RDF = 1 << kRDFTerm_RDF, + kRDFMask_ID = 1 << kRDFTerm_ID, + kRDFMask_about = 1 << kRDFTerm_about, + kRDFMask_parseType = 1 << kRDFTerm_parseType, + kRDFMask_resource = 1 << kRDFTerm_resource, + kRDFMask_nodeID = 1 << kRDFTerm_nodeID, + kRDFMask_datatype = 1 << kRDFTerm_datatype, + kRDFMask_Description = 1 << kRDFTerm_Description, + kRDFMask_li = 1 << kRDFTerm_li, + kRDFMask_aboutEach = 1 << kRDFTerm_aboutEach, + kRDFMask_aboutEachPrefix = 1 << kRDFTerm_aboutEachPrefix, + kRDFMask_bagID = 1 << kRDFTerm_bagID +}; + +enum { + kRDF_HasValueElem = 0x10000000UL // ! Contains rdf:value child. Must fit within kXMP_ImplReservedMask! +}; + +// ------------------------------------------------------------------------------------------------- +// GetRDFTermKind +// -------------- + +static RDFTermKind +GetRDFTermKind ( const XMP_VarString & name ) +{ + RDFTermKind term = kRDFTerm_Other; + + // Arranged to hopefully minimize the parse time for large XMP. + + if ( (name.size() > 4) && (strncmp ( name.c_str(), "rdf:", 4 ) == 0) ) { + + if ( name == "rdf:li" ) { + term = kRDFTerm_li; + } else if ( name == "rdf:parseType" ) { + term = kRDFTerm_parseType; + } else if ( name == "rdf:Description" ) { + term = kRDFTerm_Description; + } else if ( name == "rdf:about" ) { + term = kRDFTerm_about; + } else if ( name == "rdf:resource" ) { + term = kRDFTerm_resource; + } else if ( name == "rdf:RDF" ) { + term = kRDFTerm_RDF; + } else if ( name == "rdf:ID" ) { + term = kRDFTerm_ID; + } else if ( name == "rdf:nodeID" ) { + term = kRDFTerm_nodeID; + } else if ( name == "rdf:datatype" ) { + term = kRDFTerm_datatype; + } else if ( name == "rdf:aboutEach" ) { + term = kRDFTerm_aboutEach; + } else if ( name == "rdf:aboutEachPrefix" ) { + term = kRDFTerm_aboutEachPrefix; + } else if ( name == "rdf:bagID" ) { + term = kRDFTerm_bagID; + } + + } + + return term; + +} // GetRDFTermKind + +// ================================================================================================= + +static void +RemoveQualifier ( XMP_Node * xmpParent, size_t index ) +{ + XMP_Node * qualifier = xmpParent->qualifiers[index]; + xmpParent->qualifiers.erase ( xmpParent->qualifiers.begin() + index ); + delete qualifier; +} + +// ------------------------------------------------------------------------------------------------- + +static void +RemoveQualifier ( XMP_Node * xmpParent, XMP_NodePtrPos pos ) +{ + XMP_Node * qualifier = *pos; + xmpParent->qualifiers.erase ( pos ); + delete qualifier; +} + +// ================================================================================================= + +// ------------------------------------------------------------------------------------------------- +// IsCoreSyntaxTerm +// ---------------- +// +// 7.2.2 coreSyntaxTerms +// rdf:RDF | rdf:ID | rdf:about | rdf:parseType | rdf:resource | rdf:nodeID | rdf:datatype + +static bool +IsCoreSyntaxTerm ( RDFTermKind term ) +{ + if ( (kRDFTerm_FirstCore <= term) && (term <= kRDFTerm_LastCore) ) return true; + return false; +} + +// ------------------------------------------------------------------------------------------------- +// IsOldTerm +// --------- +// +// 7.2.4 oldTerms +// rdf:aboutEach | rdf:aboutEachPrefix | rdf:bagID + +static bool +IsOldTerm ( RDFTermKind term ) +{ + if ( (kRDFTerm_FirstOld <= term) && (term <= kRDFTerm_LastOld) ) return true; + return false; +} + +// ------------------------------------------------------------------------------------------------- +// IsPropertyElementName +// --------------------- +// +// 7.2.6 propertyElementURIs +// anyURI - ( coreSyntaxTerms | rdf:Description | oldTerms ) + +static bool +IsPropertyElementName ( RDFTermKind term ) +{ + if ( (term == kRDFTerm_Description) || IsOldTerm ( term ) ) return false; + return (! IsCoreSyntaxTerm ( term )); +} + +// ------------------------------------------------------------------------------------------------- +// IsNumberedArrayItemName +// ----------------------- +// +// Return true for a name of the form "rdf:_n", where n is a decimal integer. We're not strict about +// the integer part, it just has to be characters in the range '0'..'9'. + +static bool +IsNumberedArrayItemName ( const std::string & name ) +{ + if ( name.size() <= 5 ) return false; + if ( strncmp ( name.c_str(), "rdf:_", 5 ) != 0 ) return false; + for ( size_t i = 5; i < name.size(); ++i ) { + if ( (name[i] < '0') | (name[i] > '9') ) return false; + } + return true; +} + +// ================================================================================================= +// RDF_Parser::AddChildNode +// ======================== + +XMP_Node * RDF_Parser::AddChildNode ( XMP_Node * xmpParent, const XML_Node & xmlNode, const XMP_StringPtr value, bool isTopLevel ) +{ + + if ( xmlNode.ns.empty() ) { + XMP_Error error ( kXMPErr_BadRDF, "XML namespace required for all elements and attributes" ); + this->errorCallback->NotifyClient ( kXMPErrSev_Recoverable, error ); + return 0; + } + + bool isArrayParent = (xmpParent->options & kXMP_PropValueIsArray) !=0; + bool isArrayItem = (xmlNode.name == "rdf:li"); + bool isValueNode = (xmlNode.name == "rdf:value"); + XMP_OptionBits childOptions = 0; + XMP_StringPtr childName = xmlNode.name.c_str(); + + if ( isTopLevel ) { + + // Lookup the schema node, adjust the XMP parent pointer. + XMP_Assert ( xmpParent->parent == 0 ); // Incoming parent must be the tree root. + XMP_Node * schemaNode = FindSchemaNode ( xmpParent, xmlNode.ns.c_str(), kXMP_CreateNodes ); + if ( schemaNode->options & kXMP_NewImplicitNode ) schemaNode->options ^= kXMP_NewImplicitNode; // Clear the implicit node bit. + // *** Should use "opt &= ~flag" (no conditional), need runtime check for proper 32 bit code. + xmpParent = schemaNode; + + // If this is an alias set the isAlias flag in the node and the hasAliases flag in the tree. + if ( sRegisteredAliasMap->find ( xmlNode.name ) != sRegisteredAliasMap->end() ) { + childOptions |= kXMP_PropIsAlias; + schemaNode->parent->options |= kXMP_PropHasAliases; + } + + } + + // Check use of rdf:li and rdf:_n names. Must be done before calling FindChildNode! + if ( isArrayItem ) { + + // rdf:li can only be used for array children. + if ( ! isArrayParent ) { + XMP_Error error ( kXMPErr_BadRDF, "Misplaced rdf:li element" ); + this->errorCallback->NotifyClient ( kXMPErrSev_Recoverable, error ); + return 0; + } + childName = kXMP_ArrayItemName; + + } else if ( isArrayParent ) { + + // Tolerate use of rdf:_n, don't verify order. + if ( IsNumberedArrayItemName ( xmlNode.name ) ) { + childName = kXMP_ArrayItemName; + isArrayItem = true; + } else { + XMP_Error error ( kXMPErr_BadRDF, "Array items cannot have arbitrary child names" ); + this->errorCallback->NotifyClient ( kXMPErrSev_Recoverable, error ); + return 0; + } + + } + + // Make sure that this is not a duplicate of a named node. + if ( ! (isArrayItem | isValueNode) ) { + if ( FindChildNode ( xmpParent, childName, kXMP_ExistingOnly ) != 0 ) { + XMP_Error error ( kXMPErr_BadXMP, "Duplicate property or field node" ); + this->errorCallback->NotifyClient ( kXMPErrSev_Recoverable, error ); + return 0; + } + } + + // Make sure an rdf:value node is used properly. + if ( isValueNode ) { + if ( isTopLevel || (! (xmpParent->options & kXMP_PropValueIsStruct)) ) { + XMP_Error error ( kXMPErr_BadRDF, "Misplaced rdf:value element" ); + this->errorCallback->NotifyClient ( kXMPErrSev_Recoverable, error ); + return 0; + } + xmpParent->options |= kRDF_HasValueElem; + } + + // Add the new child to the XMP parent node. + XMP_Node * newChild = new XMP_Node ( xmpParent, childName, value, childOptions ); + if ( (! isValueNode) || xmpParent->children.empty() ) { + xmpParent->children.push_back ( newChild ); + } else { + xmpParent->children.insert ( xmpParent->children.begin(), newChild ); + } + + return newChild; + +} // RDF_Parser::AddChildNode + +// ================================================================================================= +// RDF_Parser::AddQualifierNode +// ============================ + +XMP_Node * RDF_Parser::AddQualifierNode ( XMP_Node * xmpParent, const XMP_VarString & name, const XMP_VarString & value ) +{ + + const bool isLang = (name == "xml:lang"); + const bool isType = (name == "rdf:type"); + + XMP_Node * newQual = 0; + + newQual = new XMP_Node ( xmpParent, name, value, kXMP_PropIsQualifier ); + + if ( ! (isLang | isType) ) { + xmpParent->qualifiers.push_back ( newQual ); + } else if ( isLang ) { + if ( xmpParent->qualifiers.empty() ) { + xmpParent->qualifiers.push_back ( newQual ); + } else { + xmpParent->qualifiers.insert ( xmpParent->qualifiers.begin(), newQual ); + } + xmpParent->options |= kXMP_PropHasLang; + } else { + XMP_Assert ( isType ); + if ( xmpParent->qualifiers.empty() ) { + xmpParent->qualifiers.push_back ( newQual ); + } else { + size_t offset = 0; + if ( XMP_PropHasLang ( xmpParent->options ) ) offset = 1; + xmpParent->qualifiers.insert ( xmpParent->qualifiers.begin()+offset, newQual ); + } + xmpParent->options |= kXMP_PropHasType; + } + + xmpParent->options |= kXMP_PropHasQualifiers; + + return newQual; + +} // RDF_Parser::AddQualifierNode + +// ================================================================================================= +// RDF_Parser::AddQualifierNode +// ============================ + +XMP_Node * RDF_Parser::AddQualifierNode ( XMP_Node * xmpParent, const XML_Node & attr ) +{ + if ( attr.ns.empty() ) { + XMP_Error error ( kXMPErr_BadRDF, "XML namespace required for all elements and attributes" ); + this->errorCallback->NotifyClient ( kXMPErrSev_Recoverable, error ); + return 0; + } + + return this->AddQualifierNode ( xmpParent, attr.name, attr.value ); + +} // RDF_Parser::AddQualifierNode + +// ================================================================================================= +// RDF_Parser::FixupQualifiedNode +// ============================== +// +// The parent is an RDF pseudo-struct containing an rdf:value field. Fix the XMP data model. The +// rdf:value node must be the first child, the other children are qualifiers. The form, value, and +// children of the rdf:value node are the real ones. The rdf:value node's qualifiers must be added +// to the others. + +void RDF_Parser::FixupQualifiedNode ( XMP_Node * xmpParent ) +{ + size_t qualNum, qualLim; + size_t childNum, childLim; + + XMP_Enforce ( (xmpParent->options & kXMP_PropValueIsStruct) && (! xmpParent->children.empty()) ); + + XMP_Node * valueNode = xmpParent->children[0]; + XMP_Enforce ( valueNode->name == "rdf:value" ); + + xmpParent->qualifiers.reserve ( xmpParent->qualifiers.size() + xmpParent->children.size() + valueNode->qualifiers.size() ); + + // Move the qualifiers on the value node to the parent. Make sure an xml:lang qualifier stays at + // the front. + + qualNum = 0; + qualLim = valueNode->qualifiers.size(); + + if ( valueNode->options & kXMP_PropHasLang ) { + + if ( xmpParent->options & kXMP_PropHasLang ) { + XMP_Error error ( kXMPErr_BadXMP, "Duplicate xml:lang for rdf:value element" ); + this->errorCallback->NotifyClient ( kXMPErrSev_Recoverable, error ); + XMP_Assert ( xmpParent->qualifiers[0]->name == "xml:lang" ); + RemoveQualifier ( xmpParent, 0 ); // Use the rdf:value node's language. + } + + XMP_Node * langQual = valueNode->qualifiers[0]; + + XMP_Assert ( langQual->name == "xml:lang" ); + langQual->parent = xmpParent; + xmpParent->options |= kXMP_PropHasLang; + XMP_ClearOption ( valueNode->options, kXMP_PropHasLang ); + + if ( xmpParent->qualifiers.empty() ) { + xmpParent->qualifiers.push_back ( langQual ); // *** Should use utilities to add qual & set parent. + } else { + xmpParent->qualifiers.insert ( xmpParent->qualifiers.begin(), langQual ); + } + valueNode->qualifiers[0] = 0; // We just moved it to the parent. + + qualNum = 1; // Start the remaining copy after the xml:lang qualifier. + + } + + for ( ; qualNum != qualLim; ++qualNum ) { + + XMP_Node * currQual = valueNode->qualifiers[qualNum]; + XMP_NodePtrPos existingPos; + XMP_Node * existingQual = FindQualifierNode ( xmpParent, currQual->name.c_str(), kXMP_ExistingOnly, &existingPos ); + + if ( existingQual != 0 ) { + XMP_Error error ( kXMPErr_BadXMP, "Duplicate qualifier node" ); + this->errorCallback->NotifyClient ( kXMPErrSev_Recoverable, error ); + RemoveQualifier ( xmpParent, existingPos ); // Use the rdf:value node's qualifier. + } + + currQual->parent = xmpParent; + xmpParent->qualifiers.push_back ( currQual ); + valueNode->qualifiers[qualNum] = 0; // We just moved it to the parent. + + } + + valueNode->qualifiers.clear(); // ! There should be nothing but null pointers. + + // Change the parent's other children into qualifiers. This loop starts at 1, child 0 is the + // rdf:value node. Put xml:lang at the front, append all others. + + for ( childNum = 1, childLim = xmpParent->children.size(); childNum != childLim; ++childNum ) { + + XMP_Node * currQual = xmpParent->children[childNum]; + bool isLang = (currQual->name == "xml:lang"); + + if ( FindQualifierNode ( xmpParent, currQual->name.c_str(), kXMP_ExistingOnly ) != 0 ) { + XMP_Error error ( kXMPErr_BadXMP, "Duplicate qualifier" ); + this->errorCallback->NotifyClient ( kXMPErrSev_Recoverable, error ); + delete currQual; + + } else { + + currQual->options |= kXMP_PropIsQualifier; + currQual->parent = xmpParent; + + if ( isLang ) { + xmpParent->options |= kXMP_PropHasLang; + } else if ( currQual->name == "rdf:type" ) { + xmpParent->options |= kXMP_PropHasType; + } + + if ( (! isLang) || xmpParent->qualifiers.empty() ) { + xmpParent->qualifiers.push_back ( currQual ); + } else { + xmpParent->qualifiers.insert ( xmpParent->qualifiers.begin(), currQual ); + } + + } + + xmpParent->children[childNum] = 0; // We just moved it to the qualifers, or ignored it. + + } + + if ( ! xmpParent->qualifiers.empty() ) xmpParent->options |= kXMP_PropHasQualifiers; + + // Move the options and value last, other checks need the parent's original options. Move the + // value node's children to be the parent's children. Delete the now useless value node. + + XMP_Assert ( xmpParent->options & (kXMP_PropValueIsStruct | kRDF_HasValueElem) ); + xmpParent->options &= ~ (kXMP_PropValueIsStruct | kRDF_HasValueElem); + xmpParent->options |= valueNode->options; + + xmpParent->value.swap ( valueNode->value ); + + xmpParent->children[0] = 0; // ! Remove the value node itself before the swap. + xmpParent->children.swap ( valueNode->children ); + + for ( childNum = 0, childLim = xmpParent->children.size(); childNum != childLim; ++childNum ) { + XMP_Node * currChild = xmpParent->children[childNum]; + currChild->parent = xmpParent; + } + + delete valueNode; + +} // RDF_Parser::FixupQualifiedNode + +// ================================================================================================= +// RDF_Parser::RDF +// =============== +// +// 7.2.9 RDF +// start-element ( URI == rdf:RDF, attributes == set() ) +// nodeElementList +// end-element() +// +// The top level rdf:RDF node. It can only have xmlns attributes, which have already been removed +// during construction of the XML tree. + +void RDF_Parser::RDF ( XMP_Node * xmpTree, const XML_Node & xmlNode ) +{ + + if ( ! xmlNode.attrs.empty() ) { + XMP_Error error ( kXMPErr_BadRDF, "Invalid attributes of rdf:RDF element" ); + this->errorCallback->NotifyClient ( kXMPErrSev_Recoverable, error ); + } + this->NodeElementList ( xmpTree, xmlNode, kIsTopLevel ); // ! Attributes are ignored. + +} // RDF_Parser::RDF + +// ================================================================================================= +// RDF_Parser::NodeElementList +// =========================== +// +// 7.2.10 nodeElementList +// ws* ( nodeElement ws* )* + +void RDF_Parser::NodeElementList ( XMP_Node * xmpParent, const XML_Node & xmlParent, bool isTopLevel ) +{ + XMP_Assert ( isTopLevel ); + + XML_cNodePos currChild = xmlParent.content.begin(); // *** Change these loops to the indexed pattern. + XML_cNodePos endChild = xmlParent.content.end(); + + for ( ; currChild != endChild; ++currChild ) { + if ( (*currChild)->IsWhitespaceNode() ) continue; + this->NodeElement ( xmpParent, **currChild, isTopLevel ); + } + +} // RDF_Parser::NodeElementList + +// ================================================================================================= +// RDF_Parser::NodeElement +// ======================= +// +// 7.2.5 nodeElementURIs +// anyURI - ( coreSyntaxTerms | rdf:li | oldTerms ) +// +// 7.2.11 nodeElement +// start-element ( URI == nodeElementURIs, +// attributes == set ( ( idAttr | nodeIdAttr | aboutAttr )?, propertyAttr* ) ) +// propertyEltList +// end-element() +// +// A node element URI is rdf:Description or anything else that is not an RDF term. + +void RDF_Parser::NodeElement ( XMP_Node * xmpParent, const XML_Node & xmlNode, bool isTopLevel ) +{ + RDFTermKind nodeTerm = GetRDFTermKind ( xmlNode.name ); + if ( (nodeTerm != kRDFTerm_Description) && (nodeTerm != kRDFTerm_Other) ) { + XMP_Error error ( kXMPErr_BadRDF, "Node element must be rdf:Description or typedNode" ); + this->errorCallback->NotifyClient ( kXMPErrSev_Recoverable, error ); + } else if ( isTopLevel && (nodeTerm == kRDFTerm_Other) ) { + XMP_Error error ( kXMPErr_BadXMP, "Top level typedNode not allowed" ); + this->errorCallback->NotifyClient ( kXMPErrSev_Recoverable, error ); + } else { + this->NodeElementAttrs ( xmpParent, xmlNode, isTopLevel ); + this->PropertyElementList ( xmpParent, xmlNode, isTopLevel ); + } + +} // RDF_Parser::NodeElement + +// ================================================================================================= +// RDF_Parser::NodeElementAttrs +// ============================ +// +// 7.2.7 propertyAttributeURIs +// anyURI - ( coreSyntaxTerms | rdf:Description | rdf:li | oldTerms ) +// +// 7.2.11 nodeElement +// start-element ( URI == nodeElementURIs, +// attributes == set ( ( idAttr | nodeIdAttr | aboutAttr )?, propertyAttr* ) ) +// propertyEltList +// end-element() +// +// Process the attribute list for an RDF node element. A property attribute URI is anything other +// than an RDF term. The rdf:ID and rdf:nodeID attributes are simply ignored, as are rdf:about +// attributes on inner nodes. + +static const XMP_OptionBits kExclusiveAttrMask = (kRDFMask_ID | kRDFMask_nodeID | kRDFMask_about); + +void RDF_Parser::NodeElementAttrs ( XMP_Node * xmpParent, const XML_Node & xmlNode, bool isTopLevel ) +{ + XMP_OptionBits exclusiveAttrs = 0; // Used to detect attributes that are mutually exclusive. + + XML_cNodePos currAttr = xmlNode.attrs.begin(); + XML_cNodePos endAttr = xmlNode.attrs.end(); + + for ( ; currAttr != endAttr; ++currAttr ) { + + RDFTermKind attrTerm = GetRDFTermKind ( (*currAttr)->name ); + + switch ( attrTerm ) { + + case kRDFTerm_ID : + case kRDFTerm_nodeID : + case kRDFTerm_about : + + if ( exclusiveAttrs & kExclusiveAttrMask ) { + XMP_Error error ( kXMPErr_BadRDF, "Mutally exclusive about, ID, nodeID attributes" ); + this->errorCallback->NotifyClient ( kXMPErrSev_Recoverable, error ); + continue; // Skip the later mutually exclusive attributes. + } + exclusiveAttrs |= (1 << attrTerm); + + if ( isTopLevel && (attrTerm == kRDFTerm_about) ) { + // This is the rdf:about attribute on a top level node. Set the XMP tree name if + // it doesn't have a name yet. Make sure this name matches the XMP tree name. + XMP_Assert ( xmpParent->parent == 0 ); // Must be the tree root node. + if ( xmpParent->name.empty() ) { + xmpParent->name = (*currAttr)->value; + } else if ( ! (*currAttr)->value.empty() ) { + if ( xmpParent->name != (*currAttr)->value ) { + XMP_Error error ( kXMPErr_BadXMP, "Mismatched top level rdf:about values" ); + this->errorCallback->NotifyClient ( kXMPErrSev_Recoverable, error ); + } + } + } + + break; + + case kRDFTerm_Other : + this->AddChildNode ( xmpParent, **currAttr, (*currAttr)->value.c_str(), isTopLevel ); + break; + + default : + { + XMP_Error error ( kXMPErr_BadRDF, "Invalid nodeElement attribute" ); + this->errorCallback->NotifyClient ( kXMPErrSev_Recoverable, error ); + } + continue; + + } + + } + +} // RDF_Parser::NodeElementAttrs + +// ================================================================================================= +// RDF_Parser::PropertyElementList +// =============================== +// +// 7.2.13 propertyEltList +// ws* ( propertyElt ws* )* + +void RDF_Parser::PropertyElementList ( XMP_Node * xmpParent, const XML_Node & xmlParent, bool isTopLevel ) +{ + XML_cNodePos currChild = xmlParent.content.begin(); + XML_cNodePos endChild = xmlParent.content.end(); + + for ( ; currChild != endChild; ++currChild ) { + if ( (*currChild)->IsWhitespaceNode() ) continue; + if ( (*currChild)->kind != kElemNode ) { + XMP_Error error ( kXMPErr_BadRDF, "Expected property element node not found" ); + this->errorCallback->NotifyClient ( kXMPErrSev_Recoverable, error ); + continue; + } + this->PropertyElement ( xmpParent, **currChild, isTopLevel ); + } + +} // RDF_Parser::PropertyElementList + +// ================================================================================================= +// RDF_Parser::PropertyElement +// =========================== +// +// 7.2.14 propertyElt +// resourcePropertyElt | literalPropertyElt | parseTypeLiteralPropertyElt | +// parseTypeResourcePropertyElt | parseTypeCollectionPropertyElt | parseTypeOtherPropertyElt | emptyPropertyElt +// +// 7.2.15 resourcePropertyElt +// start-element ( URI == propertyElementURIs, attributes == set ( idAttr? ) ) +// ws* nodeElement ws* +// end-element() +// +// 7.2.16 literalPropertyElt +// start-element ( URI == propertyElementURIs, attributes == set ( idAttr?, datatypeAttr?) ) +// text() +// end-element() +// +// 7.2.17 parseTypeLiteralPropertyElt +// start-element ( URI == propertyElementURIs, attributes == set ( idAttr?, parseLiteral ) ) +// literal +// end-element() +// +// 7.2.18 parseTypeResourcePropertyElt +// start-element ( URI == propertyElementURIs, attributes == set ( idAttr?, parseResource ) ) +// propertyEltList +// end-element() +// +// 7.2.19 parseTypeCollectionPropertyElt +// start-element ( URI == propertyElementURIs, attributes == set ( idAttr?, parseCollection ) ) +// nodeElementList +// end-element() +// +// 7.2.20 parseTypeOtherPropertyElt +// start-element ( URI == propertyElementURIs, attributes == set ( idAttr?, parseOther ) ) +// propertyEltList +// end-element() +// +// 7.2.21 emptyPropertyElt +// start-element ( URI == propertyElementURIs, +// attributes == set ( idAttr?, ( resourceAttr | nodeIdAttr )?, propertyAttr* ) ) +// end-element() +// +// The various property element forms are not distinguished by the XML element name, but by their +// attributes for the most part. The exceptions are resourcePropertyElt and literalPropertyElt. They +// are distinguished by their XML element content. +// +// NOTE: The RDF syntax does not explicitly include the xml:lang attribute although it can appear in +// many of these. We have to allow for it in the attibute counts below. + +void RDF_Parser::PropertyElement ( XMP_Node * xmpParent, const XML_Node & xmlNode, bool isTopLevel ) +{ + RDFTermKind nodeTerm = GetRDFTermKind ( xmlNode.name ); + if ( ! IsPropertyElementName ( nodeTerm ) ) { + XMP_Error error ( kXMPErr_BadRDF, "Invalid property element name" ); + this->errorCallback->NotifyClient ( kXMPErrSev_Recoverable, error ); + return; + } + + if ( xmlNode.attrs.size() > 3 ) { + + // Only an emptyPropertyElt can have more than 3 attributes. + this->EmptyPropertyElement ( xmpParent, xmlNode, isTopLevel ); + + } else { + + // Look through the attributes for one that isn't rdf:ID or xml:lang, it will usually tell + // what we should be dealing with. The called routines must verify their specific syntax! + + XML_cNodePos currAttr = xmlNode.attrs.begin(); + XML_cNodePos endAttr = xmlNode.attrs.end(); + XMP_VarString * attrName = 0; + + for ( ; currAttr != endAttr; ++currAttr ) { + attrName = &((*currAttr)->name); + if ( (*attrName != "xml:lang") && (*attrName != "rdf:ID") ) break; + } + + if ( currAttr != endAttr ) { + + XMP_Assert ( attrName != 0 ); + XMP_VarString& attrValue = (*currAttr)->value; + + if ( *attrName == "rdf:datatype" ) { + this->LiteralPropertyElement ( xmpParent, xmlNode, isTopLevel ); + } else if ( *attrName != "rdf:parseType" ) { + this->EmptyPropertyElement ( xmpParent, xmlNode, isTopLevel ); + } else if ( attrValue == "Literal" ) { + this->ParseTypeLiteralPropertyElement ( xmpParent, xmlNode, isTopLevel ); + } else if ( attrValue == "Resource" ) { + this->ParseTypeResourcePropertyElement ( xmpParent, xmlNode, isTopLevel ); + } else if ( attrValue == "Collection" ) { + this->ParseTypeCollectionPropertyElement ( xmpParent, xmlNode, isTopLevel ); + } else { + this->ParseTypeOtherPropertyElement ( xmpParent, xmlNode, isTopLevel ); + } + + } else { + + // Only rdf:ID and xml:lang, could be a resourcePropertyElt, a literalPropertyElt, or an. + // emptyPropertyElt. Look at the child XML nodes to decide which. + + if ( xmlNode.content.empty() ) { + + this->EmptyPropertyElement ( xmpParent, xmlNode, isTopLevel ); + + } else { + + XML_cNodePos currChild = xmlNode.content.begin(); + XML_cNodePos endChild = xmlNode.content.end(); + + for ( ; currChild != endChild; ++currChild ) { + if ( (*currChild)->kind != kCDataNode ) break; + } + + if ( currChild == endChild ) { + this->LiteralPropertyElement ( xmpParent, xmlNode, isTopLevel ); + } else { + this->ResourcePropertyElement ( xmpParent, xmlNode, isTopLevel ); + } + + } + + } + + } + +} // RDF_Parser::PropertyElement + +// ================================================================================================= +// RDF_Parser::ResourcePropertyElement +// =================================== +// +// 7.2.15 resourcePropertyElt +// start-element ( URI == propertyElementURIs, attributes == set ( idAttr? ) ) +// ws* nodeElement ws* +// end-element() +// +// This handles structs using an rdf:Description node, arrays using rdf:Bag/Seq/Alt, and Typed Nodes. +// It also catches and cleans up qualified properties written with rdf:Description and rdf:value. + +void RDF_Parser::ResourcePropertyElement ( XMP_Node * xmpParent, const XML_Node & xmlNode, bool isTopLevel ) +{ + if ( isTopLevel && (xmlNode.name == "iX:changes") ) return; // Strip old "punchcard" chaff. + + XMP_Node * newCompound = this->AddChildNode ( xmpParent, xmlNode, "", isTopLevel ); + if ( newCompound == 0 ) return; // Ignore lower level errors. + + XML_cNodePos currAttr = xmlNode.attrs.begin(); + XML_cNodePos endAttr = xmlNode.attrs.end(); + + for ( ; currAttr != endAttr; ++currAttr ) { + XMP_VarString & attrName = (*currAttr)->name; + if ( attrName == "xml:lang" ) { + this->AddQualifierNode ( newCompound, **currAttr ); + } else if ( attrName == "rdf:ID" ) { + continue; // Ignore all rdf:ID attributes. + } else { + XMP_Error error ( kXMPErr_BadRDF, "Invalid attribute for resource property element" ); + this->errorCallback->NotifyClient ( kXMPErrSev_Recoverable, error ); + continue; + } + } + + XML_cNodePos currChild = xmlNode.content.begin(); + XML_cNodePos endChild = xmlNode.content.end(); + + for ( ; currChild != endChild; ++currChild ) { + if ( ! (*currChild)->IsWhitespaceNode() ) break; + } + if ( currChild == endChild ) { + XMP_Error error ( kXMPErr_BadRDF, "Missing child of resource property element" ); + this->errorCallback->NotifyClient ( kXMPErrSev_Recoverable, error ); + return; + } + if ( (*currChild)->kind != kElemNode ) { + XMP_Error error ( kXMPErr_BadRDF, "Children of resource property element must be XML elements" ); + this->errorCallback->NotifyClient ( kXMPErrSev_Recoverable, error ); + return; + } + + if ( (*currChild)->name == "rdf:Bag" ) { + newCompound->options |= kXMP_PropValueIsArray; + } else if ( (*currChild)->name == "rdf:Seq" ) { + newCompound->options |= kXMP_PropValueIsArray | kXMP_PropArrayIsOrdered; + } else if ( (*currChild)->name == "rdf:Alt" ) { + newCompound->options |= kXMP_PropValueIsArray | kXMP_PropArrayIsOrdered | kXMP_PropArrayIsAlternate; + } else { + // This is the Typed Node case. Add an rdf:type qualifier with a URI value. + if ( (*currChild)->name != "rdf:Description" ) { + XMP_VarString typeName ( (*currChild)->ns ); + size_t colonPos = (*currChild)->name.find_first_of(':'); + if ( colonPos == XMP_VarString::npos ) { + XMP_Error error ( kXMPErr_BadXMP, "All XML elements must be in a namespace" ); + this->errorCallback->NotifyClient ( kXMPErrSev_Recoverable, error ); + return; + } + typeName.append ( (*currChild)->name, colonPos+1, XMP_VarString::npos ); // Append just the local name. + XMP_Node * typeQual = this->AddQualifierNode ( newCompound, XMP_VarString("rdf:type"), typeName ); + if ( typeQual != 0 ) typeQual->options |= kXMP_PropValueIsURI; + } + newCompound->options |= kXMP_PropValueIsStruct; + } + + this->NodeElement ( newCompound, **currChild, kNotTopLevel ); + if ( newCompound->options & kRDF_HasValueElem ) { + this->FixupQualifiedNode ( newCompound ); + } else if ( newCompound->options & kXMP_PropArrayIsAlternate ) { + DetectAltText ( newCompound ); + } + + for ( ++currChild; currChild != endChild; ++currChild ) { + if ( ! (*currChild)->IsWhitespaceNode() ) { + XMP_Error error ( kXMPErr_BadRDF, "Invalid child of resource property element" ); + this->errorCallback->NotifyClient ( kXMPErrSev_Recoverable, error ); + break; // Don't bother looking for more trailing errors. + } + } + +} // RDF_Parser::ResourcePropertyElement + +// ================================================================================================= +// RDF_Parser::LiteralPropertyElement +// ================================== +// +// 7.2.16 literalPropertyElt +// start-element ( URI == propertyElementURIs, attributes == set ( idAttr?, datatypeAttr?) ) +// text() +// end-element() +// +// Add a leaf node with the text value and qualifiers for the attributes. + +void RDF_Parser::LiteralPropertyElement ( XMP_Node * xmpParent, const XML_Node & xmlNode, bool isTopLevel ) +{ + XMP_Node * newChild = this->AddChildNode ( xmpParent, xmlNode, "", isTopLevel ); + if ( newChild == 0 ) return; // Ignore lower level errors. + + XML_cNodePos currAttr = xmlNode.attrs.begin(); + XML_cNodePos endAttr = xmlNode.attrs.end(); + + for ( ; currAttr != endAttr; ++currAttr ) { + XMP_VarString & attrName = (*currAttr)->name; + if ( attrName == "xml:lang" ) { + this->AddQualifierNode ( newChild, **currAttr ); + } else if ( (attrName == "rdf:ID") || (attrName == "rdf:datatype") ) { + continue; // Ignore all rdf:ID and rdf:datatype attributes. + } else { + XMP_Error error ( kXMPErr_BadRDF, "Invalid attribute for literal property element" ); + this->errorCallback->NotifyClient ( kXMPErrSev_Recoverable, error ); + continue; + } + } + + XML_cNodePos currChild = xmlNode.content.begin(); + XML_cNodePos endChild = xmlNode.content.end(); + size_t textSize = 0; + + for ( ; currChild != endChild; ++currChild ) { + if ( (*currChild)->kind == kCDataNode ) { + textSize += (*currChild)->value.size(); + } else { + XMP_Error error ( kXMPErr_BadRDF, "Invalid child of literal property element" ); + this->errorCallback->NotifyClient ( kXMPErrSev_Recoverable, error ); + } + } + + newChild->value.reserve ( textSize ); + + for ( currChild = xmlNode.content.begin(); currChild != endChild; ++currChild ) { + newChild->value += (*currChild)->value; + } + +} // RDF_Parser::LiteralPropertyElement + +// ================================================================================================= +// RDF_Parser::ParseTypeLiteralPropertyElement +// =========================================== +// +// 7.2.17 parseTypeLiteralPropertyElt +// start-element ( URI == propertyElementURIs, attributes == set ( idAttr?, parseLiteral ) ) +// literal +// end-element() + +void RDF_Parser::ParseTypeLiteralPropertyElement ( XMP_Node * xmpParent, const XML_Node & xmlNode, bool isTopLevel ) +{ + IgnoreParam(xmpParent); IgnoreParam(xmlNode); IgnoreParam(isTopLevel); + XMP_Error error ( kXMPErr_BadXMP, "ParseTypeLiteral property element not allowed" ); + this->errorCallback->NotifyClient ( kXMPErrSev_Recoverable, error ); + +} // RDF_Parser::ParseTypeLiteralPropertyElement + +// ================================================================================================= +// RDF_Parser::ParseTypeResourcePropertyElement +// ============================================ +// +// 7.2.18 parseTypeResourcePropertyElt +// start-element ( URI == propertyElementURIs, attributes == set ( idAttr?, parseResource ) ) +// propertyEltList +// end-element() +// +// Add a new struct node with a qualifier for the possible rdf:ID attribute. Then process the XML +// child nodes to get the struct fields. + +void RDF_Parser::ParseTypeResourcePropertyElement ( XMP_Node * xmpParent, const XML_Node & xmlNode, bool isTopLevel ) +{ + XMP_Node * newStruct = this->AddChildNode ( xmpParent, xmlNode, "", isTopLevel ); + if ( newStruct == 0 ) return; // Ignore lower level errors. + newStruct->options |= kXMP_PropValueIsStruct; + + XML_cNodePos currAttr = xmlNode.attrs.begin(); + XML_cNodePos endAttr = xmlNode.attrs.end(); + + for ( ; currAttr != endAttr; ++currAttr ) { + XMP_VarString & attrName = (*currAttr)->name; + if ( attrName == "rdf:parseType" ) { + continue; // ! The caller ensured the value is "Resource". + } else if ( attrName == "xml:lang" ) { + this->AddQualifierNode ( newStruct, **currAttr ); + } else if ( attrName == "rdf:ID" ) { + continue; // Ignore all rdf:ID attributes. + } else { + XMP_Error error ( kXMPErr_BadRDF, "Invalid attribute for ParseTypeResource property element" ); + this->errorCallback->NotifyClient ( kXMPErrSev_Recoverable, error ); + continue; + } + } + + this->PropertyElementList ( newStruct, xmlNode, kNotTopLevel ); + + if ( newStruct->options & kRDF_HasValueElem ) this->FixupQualifiedNode ( newStruct ); + + // *** Need to look for arrays using rdf:Description and rdf:type. + +} // RDF_Parser::ParseTypeResourcePropertyElement + +// ================================================================================================= +// RDF_Parser::ParseTypeCollectionPropertyElement +// ============================================== +// +// 7.2.19 parseTypeCollectionPropertyElt +// start-element ( URI == propertyElementURIs, attributes == set ( idAttr?, parseCollection ) ) +// nodeElementList +// end-element() + +void RDF_Parser::ParseTypeCollectionPropertyElement ( XMP_Node * xmpParent, const XML_Node & xmlNode, bool isTopLevel ) +{ + IgnoreParam(xmpParent); IgnoreParam(xmlNode); IgnoreParam(isTopLevel); + XMP_Error error ( kXMPErr_BadXMP, "ParseTypeCollection property element not allowed" ); + this->errorCallback->NotifyClient ( kXMPErrSev_Recoverable, error ); + +} // RDF_Parser::ParseTypeCollectionPropertyElement + +// ================================================================================================= +// RDF_Parser::ParseTypeOtherPropertyElement +// ========================================= +// +// 7.2.20 parseTypeOtherPropertyElt +// start-element ( URI == propertyElementURIs, attributes == set ( idAttr?, parseOther ) ) +// propertyEltList +// end-element() + +void RDF_Parser::ParseTypeOtherPropertyElement ( XMP_Node * xmpParent, const XML_Node & xmlNode, bool isTopLevel ) +{ + IgnoreParam(xmpParent); IgnoreParam(xmlNode); IgnoreParam(isTopLevel); + XMP_Error error ( kXMPErr_BadXMP, "ParseTypeOther property element not allowed" ); + this->errorCallback->NotifyClient ( kXMPErrSev_Recoverable, error ); + +} // RDF_Parser::ParseTypeOtherPropertyElement + +// ================================================================================================= +// RDF_Parser::EmptyPropertyElement +// ================================ +// +// 7.2.21 emptyPropertyElt +// start-element ( URI == propertyElementURIs, +// attributes == set ( idAttr?, ( resourceAttr | nodeIdAttr )?, propertyAttr* ) ) +// end-element() +// +// +// +// +// +// +// An emptyPropertyElt is an element with no contained content, just a possibly empty set of +// attributes. An emptyPropertyElt can represent three special cases of simple XMP properties: a +// simple property with an empty value (ns:Prop1), a simple property whose value is a URI +// (ns:Prop2), or a simple property with simple qualifiers (ns:Prop3). An emptyPropertyElt can also +// represent an XMP struct whose fields are all simple and unqualified (ns:Prop4). +// +// It is an error to use both rdf:value and rdf:resource - that can lead to invalid RDF in the +// verbose form written using a literalPropertyElt. +// +// The XMP mapping for an emptyPropertyElt is a bit different from generic RDF, partly for +// design reasons and partly for historical reasons. The XMP mapping rules are: +// 1. If there is an rdf:value attribute then this is a simple property with a text value. +// All other attributes are qualifiers. +// 2. If there is an rdf:resource attribute then this is a simple property with a URI value. +// All other attributes are qualifiers. +// 3. If there are no attributes other than xml:lang, rdf:ID, or rdf:nodeID then this is a simple +// property with an empty value. +// 4. Otherwise this is a struct, the attributes other than xml:lang, rdf:ID, or rdf:nodeID are fields. + +void RDF_Parser::EmptyPropertyElement ( XMP_Node * xmpParent, const XML_Node & xmlNode, bool isTopLevel ) +{ + bool hasPropertyAttrs = false; + bool hasResourceAttr = false; + bool hasNodeIDAttr = false; + bool hasValueAttr = false; + + const XML_Node * valueNode = 0; // ! Can come from rdf:value or rdf:resource. + + if ( ! xmlNode.content.empty() ) { + XMP_Error error ( kXMPErr_BadRDF, "Nested content not allowed with rdf:resource or property attributes" ); + this->errorCallback->NotifyClient ( kXMPErrSev_Recoverable, error ); + return; + } + + // First figure out what XMP this maps to and remember the XML node for a simple value. + + XML_cNodePos currAttr = xmlNode.attrs.begin(); + XML_cNodePos endAttr = xmlNode.attrs.end(); + + for ( ; currAttr != endAttr; ++currAttr ) { + + RDFTermKind attrTerm = GetRDFTermKind ( (*currAttr)->name ); + + switch ( attrTerm ) { + + case kRDFTerm_ID : + // Nothing to do. + break; + + case kRDFTerm_resource : + if ( hasNodeIDAttr ) { + XMP_Error error ( kXMPErr_BadRDF, "Empty property element can't have both rdf:resource and rdf:nodeID" ); + this->errorCallback->NotifyClient ( kXMPErrSev_Recoverable, error ); + return; + } + if ( hasValueAttr ) { + XMP_Error error ( kXMPErr_BadXMP, "Empty property element can't have both rdf:value and rdf:resource" ); + this->errorCallback->NotifyClient ( kXMPErrSev_Recoverable, error ); + return; + } + hasResourceAttr = true; + if ( ! hasValueAttr ) valueNode = *currAttr; + break; + + case kRDFTerm_nodeID : + if ( hasResourceAttr ) { + XMP_Error error ( kXMPErr_BadRDF, "Empty property element can't have both rdf:resource and rdf:nodeID" ); + this->errorCallback->NotifyClient ( kXMPErrSev_Recoverable, error ); + return; + } + hasNodeIDAttr = true; + break; + + case kRDFTerm_Other : + if ( (*currAttr)->name == "rdf:value" ) { + if ( hasResourceAttr ) { + XMP_Error error ( kXMPErr_BadXMP, "Empty property element can't have both rdf:value and rdf:resource" ); + this->errorCallback->NotifyClient ( kXMPErrSev_Recoverable, error ); + return; + } + hasValueAttr = true; + valueNode = *currAttr; + } else if ( (*currAttr)->name != "xml:lang" ) { + hasPropertyAttrs = true; + } + break; + + default : + { + XMP_Error error ( kXMPErr_BadRDF, "Unrecognized attribute of empty property element" ); + this->errorCallback->NotifyClient ( kXMPErrSev_Recoverable, error ); + } + + return; + + } + + } + + // Create the right kind of child node and visit the attributes again to add the fields or qualifiers. + // ! Because of implementation vagaries, the xmpParent is the tree root for top level properties. + // ! The schema is found, created if necessary, by AddChildNode. + + XMP_Node * childNode = this->AddChildNode ( xmpParent, xmlNode, "", isTopLevel ); + if ( childNode == 0 ) return; // Ignore lower level errors. + bool childIsStruct = false; + + if ( hasValueAttr | hasResourceAttr ) { + childNode->value = valueNode->value; + if ( ! hasValueAttr ) childNode->options |= kXMP_PropValueIsURI; // ! Might have both rdf:value and rdf:resource. + } else if ( hasPropertyAttrs ) { + childNode->options |= kXMP_PropValueIsStruct; + childIsStruct = true; + } + + currAttr = xmlNode.attrs.begin(); + endAttr = xmlNode.attrs.end(); + + for ( ; currAttr != endAttr; ++currAttr ) { + + if ( *currAttr == valueNode ) continue; // Skip the rdf:value or rdf:resource attribute holding the value. + RDFTermKind attrTerm = GetRDFTermKind ( (*currAttr)->name ); + + switch ( attrTerm ) { + + case kRDFTerm_ID : + case kRDFTerm_nodeID : + break; // Ignore all rdf:ID and rdf:nodeID attributes. + + case kRDFTerm_resource : + this->AddQualifierNode ( childNode, **currAttr ); + break; + + case kRDFTerm_Other : + if ( (! childIsStruct) || (*currAttr)->name == "xml:lang" ) { + this->AddQualifierNode ( childNode, **currAttr ); + } else { + this->AddChildNode ( childNode, **currAttr, (*currAttr)->value.c_str(), false ); + } + break; + + default : + { + XMP_Error error ( kXMPErr_BadRDF, "Unrecognized attribute of empty property element" ); + this->errorCallback->NotifyClient ( kXMPErrSev_Recoverable, error ); + } + continue; + + } + + } + +} // RDF_Parser::EmptyPropertyElement + +// ================================================================================================= +// XMPMeta::ProcessRDF +// =================== +// +// Parse the XML tree of the RDF and build the corresponding XMP tree. + +void XMPMeta::ProcessRDF ( const XML_Node & rdfNode, XMP_OptionBits options ) +{ + IgnoreParam(options); + + RDF_Parser parser ( &this->errorCallback ); + + parser.RDF ( &this->tree, rdfNode ); + +} // XMPMeta::ProcessRDF + +// ================================================================================================= diff --git a/XMPCore/source/WXMPIterator.cpp b/XMPCore/source/WXMPIterator.cpp new file mode 100644 index 0000000..401b468 --- /dev/null +++ b/XMPCore/source/WXMPIterator.cpp @@ -0,0 +1,170 @@ +// ================================================================================================= +// 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/client-glue/WXMPIterator.hpp" + +#include "XMPCore/source/XMPCore_Impl.hpp" +#include "XMPCore/source/XMPIterator.hpp" + +#if XMP_WinBuild + #pragma warning ( disable : 4101 ) // unreferenced local variable + #pragma warning ( disable : 4189 ) // local variable is initialized but not referenced + #pragma warning ( disable : 4800 ) // forcing value to bool 'true' or 'false' (performance warning) + #if XMP_DebugBuild + #pragma warning ( disable : 4297 ) // function assumed not to throw an exception but does + #endif +#endif + +#if __cplusplus +extern "C" { +#endif + +// ================================================================================================= +// CTor/DTor Wrappers +// ================== + +void +WXMPIterator_PropCTor_1 ( XMPMetaRef xmpRef, + XMP_StringPtr schemaNS, + XMP_StringPtr propName, + XMP_OptionBits options, + WXMP_Result * wResult ) +{ + XMP_ENTER_Static ( "WXMPIterator_PropCTor_1" ) // No lib object yet, use the static entry. + + if ( schemaNS == 0 ) schemaNS = ""; + if ( propName == 0 ) propName = ""; + + const XMPMeta & xmpObj = WtoXMPMeta_Ref ( xmpRef ); + XMP_AutoLock metaLock ( &xmpObj.lock, kXMP_ReadLock ); + + XMPIterator * iter = new XMPIterator ( xmpObj, schemaNS, propName, options ); + ++iter->clientRefs; + XMP_Assert ( iter->clientRefs == 1 ); + wResult->ptrResult = XMPIteratorRef ( iter ); + + XMP_EXIT +} + +// ------------------------------------------------------------------------------------------------- + +void +WXMPIterator_TableCTor_1 ( XMP_StringPtr schemaNS, + XMP_StringPtr propName, + XMP_OptionBits options, + WXMP_Result * wResult ) +{ + XMP_ENTER_Static ( "WXMPIterator_TableCTor_1" ) // No lib object yet, use the static entry. + + if ( schemaNS == 0 ) schemaNS = ""; + if ( propName == 0 ) propName = ""; + + XMPIterator * iter = new XMPIterator ( schemaNS, propName, options ); + ++iter->clientRefs; + XMP_Assert ( iter->clientRefs == 1 ); + wResult->ptrResult = XMPIteratorRef ( iter ); + + XMP_EXIT +} + +// ------------------------------------------------------------------------------------------------- + +void +WXMPIterator_IncrementRefCount_1 ( XMPIteratorRef xmpObjRef ) +{ + WXMP_Result * wResult = &void_wResult; // ! Needed to "fool" the EnterWrapper macro. + XMP_ENTER_ObjWrite ( XMPIterator, "WXMPIterator_IncrementRefCount_1" ) + + ++thiz->clientRefs; + XMP_Assert ( thiz->clientRefs > 1 ); + + XMP_EXIT_NoThrow +} + +// ------------------------------------------------------------------------------------------------- + +void +WXMPIterator_DecrementRefCount_1 ( XMPIteratorRef xmpObjRef ) +{ + WXMP_Result * wResult = &void_wResult; // ! Needed to "fool" the EnterWrapper macro. + XMP_ENTER_ObjWrite ( XMPIterator, "WXMPIterator_DecrementRefCount_1" ) + + XMP_Assert ( thiz->clientRefs > 0 ); + --thiz->clientRefs; + if ( thiz->clientRefs <= 0 ) { + objLock.Release(); + delete ( thiz ); + } + + XMP_EXIT_NoThrow +} + +// ================================================================================================= +// Class Method Wrappers +// ===================== + +void +WXMPIterator_Next_1 ( XMPIteratorRef xmpObjRef, + void * schemaNS, + void * propPath, + void * propValue, + XMP_OptionBits * propOptions, + SetClientStringProc SetClientString, + WXMP_Result * wResult ) +{ + XMP_ENTER_ObjWrite ( XMPIterator, "WXMPIterator_Next_1" ) + + XMP_StringPtr schemaPtr = 0; + XMP_StringLen schemaLen = 0; + XMP_StringPtr pathPtr = 0; + XMP_StringLen pathLen = 0; + XMP_StringPtr valuePtr = 0; + XMP_StringLen valueLen = 0; + + if ( propOptions == 0 ) propOptions = &voidOptionBits; + + XMP_Assert( thiz->info.xmpObj != NULL ); + XMP_AutoLock metaLock ( &thiz->info.xmpObj->lock, kXMP_ReadLock, (thiz->info.xmpObj != 0) ); + + XMP_Bool found = thiz->Next ( &schemaPtr, &schemaLen, &pathPtr, &pathLen, &valuePtr, &valueLen, propOptions ); + wResult->int32Result = found; + + if ( found ) { + if ( schemaNS != 0 ) (*SetClientString) ( schemaNS, schemaPtr, schemaLen ); + if ( propPath != 0 ) (*SetClientString) ( propPath, pathPtr, pathLen ); + if ( propValue != 0 ) (*SetClientString) ( propValue, valuePtr, valueLen ); + } + + XMP_EXIT +} + +// ------------------------------------------------------------------------------------------------- + +void +WXMPIterator_Skip_1 ( XMPIteratorRef xmpObjRef, + XMP_OptionBits options, + WXMP_Result * wResult ) +{ + XMP_ENTER_ObjWrite ( XMPIterator, "WXMPIterator_Skip_1" ) + + XMP_Assert( thiz->info.xmpObj != NULL ); + XMP_AutoLock metaLock ( &thiz->info.xmpObj->lock, kXMP_ReadLock, (thiz->info.xmpObj != 0) ); + + thiz->Skip ( options ); + + XMP_EXIT +} + +// ================================================================================================= + +#if __cplusplus +} /* extern "C" */ +#endif diff --git a/XMPCore/source/WXMPMeta.cpp b/XMPCore/source/WXMPMeta.cpp new file mode 100644 index 0000000..61f13f1 --- /dev/null +++ b/XMPCore/source/WXMPMeta.cpp @@ -0,0 +1,1191 @@ +// ================================================================================================= +// 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/client-glue/WXMPMeta.hpp" + +#include "XMPCore/source/XMPCore_Impl.hpp" +#include "XMPCore/source/XMPMeta.hpp" + +#if XMP_WinBuild + #pragma warning ( disable : 4101 ) // unreferenced local variable + #pragma warning ( disable : 4189 ) // local variable is initialized but not referenced + #pragma warning ( disable : 4702 ) // unreachable code + #pragma warning ( disable : 4800 ) // forcing value to bool 'true' or 'false' (performance warning) + #if XMP_DebugBuild + #pragma warning ( disable : 4297 ) // function assumed not to throw an exception but does + #endif +#endif + +#if __cplusplus +extern "C" { +#endif + +// ================================================================================================= +// Init/Term Wrappers +// ================== + +/* class static */ void +WXMPMeta_GetVersionInfo_1 ( XMP_VersionInfo * info ) +{ + WXMP_Result * wResult = &void_wResult; // ! Needed to "fool" the EnterWrapper macro. + XMP_ENTER_NoLock ( "WXMPMeta_GetVersionInfo_1" ) + + XMPMeta::GetVersionInfo ( info ); + + XMP_EXIT_NoThrow +} + +// ------------------------------------------------------------------------------------------------- + +/* class static */ void +WXMPMeta_Initialize_1 ( WXMP_Result * wResult ) +{ + XMP_ENTER_NoLock ( "WXMPMeta_Initialize_1" ) + + wResult->int32Result = XMPMeta::Initialize(); + + XMP_EXIT +} +// ------------------------------------------------------------------------------------------------- + +/* class static */ void +WXMPMeta_Terminate_1() +{ + WXMP_Result * wResult = &void_wResult; // ! Needed to "fool" the EnterWrapper macro. + XMP_ENTER_NoLock ( "WXMPMeta_Terminate_1" ) + + XMPMeta::Terminate(); + + XMP_EXIT_NoThrow +} + +// ================================================================================================= +// CTor/DTor Wrappers +// ================== + +void +WXMPMeta_CTor_1 ( WXMP_Result * wResult ) +{ + XMP_ENTER_Static ( "WXMPMeta_CTor_1" ) // No lib object yet, use the static entry. + + XMPMeta * xmpObj = new XMPMeta(); + ++xmpObj->clientRefs; + XMP_Assert ( xmpObj->clientRefs == 1 ); + wResult->ptrResult = XMPMetaRef ( xmpObj ); + + XMP_EXIT +} + +// ------------------------------------------------------------------------------------------------- + +void +WXMPMeta_IncrementRefCount_1 ( XMPMetaRef xmpObjRef ) +{ + WXMP_Result * wResult = &void_wResult; // ! Needed to "fool" the EnterWrapper macro. + XMP_ENTER_ObjWrite ( XMPMeta, "WXMPMeta_IncrementRefCount_1" ) + + ++thiz->clientRefs; + XMP_Assert ( thiz->clientRefs > 0 ); + + XMP_EXIT_NoThrow +} + +// ------------------------------------------------------------------------------------------------- + +void +WXMPMeta_DecrementRefCount_1 ( XMPMetaRef xmpObjRef ) +{ + WXMP_Result * wResult = &void_wResult; // ! Needed to "fool" the EnterWrapper macro. + XMP_ENTER_ObjWrite ( XMPMeta, "WXMPMeta_DecrementRefCount_1" ) + + XMP_Assert ( thiz->clientRefs > 0 ); + --thiz->clientRefs; + if ( thiz->clientRefs <= 0 ) { + objLock.Release(); + delete ( thiz ); + } + + XMP_EXIT_NoThrow +} + +// ================================================================================================= +// Class Static Wrappers +// ===================== + +/* class static */ void +WXMPMeta_GetGlobalOptions_1 ( WXMP_Result * wResult ) +{ + XMP_ENTER_Static ( "WXMPMeta_GetGlobalOptions_1" ) + + XMP_OptionBits options = XMPMeta::GetGlobalOptions(); + wResult->int32Result = options; + + XMP_EXIT +} + +// ------------------------------------------------------------------------------------------------- + +/* class static */ void +WXMPMeta_SetGlobalOptions_1 ( XMP_OptionBits options, + WXMP_Result * wResult ) +{ + XMP_ENTER_Static ( "WXMPMeta_SetGlobalOptions_1" ) + + XMPMeta::SetGlobalOptions ( options ); + + XMP_EXIT +} +// ------------------------------------------------------------------------------------------------- + +/* class static */ void +WXMPMeta_DumpNamespaces_1 ( XMP_TextOutputProc outProc, + void * refCon, + WXMP_Result * wResult ) +{ + XMP_ENTER_Static ( "WXMPMeta_DumpNamespaces_1" ) + + if ( outProc == 0 ) XMP_Throw ( "Null client output routine", kXMPErr_BadParam ); + + XMP_Status status = XMPMeta::DumpNamespaces ( outProc, refCon ); + wResult->int32Result = status; + + XMP_EXIT +} + +// ------------------------------------------------------------------------------------------------- + +/* class static */ void +WXMPMeta_RegisterNamespace_1 ( XMP_StringPtr namespaceURI, + XMP_StringPtr suggestedPrefix, + void * actualPrefix, + SetClientStringProc SetClientString, + WXMP_Result * wResult ) +{ + XMP_ENTER_Static ( "WXMPMeta_RegisterNamespace_1" ) + + if ( (namespaceURI == 0) || (*namespaceURI == 0) ) XMP_Throw ( "Empty namespace URI", kXMPErr_BadSchema ); + if ( (suggestedPrefix == 0) || (*suggestedPrefix == 0) ) XMP_Throw ( "Empty suggested prefix", kXMPErr_BadSchema ); + + XMP_StringPtr prefixPtr = 0; + XMP_StringLen prefixSize = 0; + + bool prefixMatch = XMPMeta::RegisterNamespace ( namespaceURI, suggestedPrefix, &prefixPtr, &prefixSize ); + wResult->int32Result = prefixMatch; + + if ( actualPrefix != 0 ) (*SetClientString) ( actualPrefix, prefixPtr, prefixSize ); + + XMP_EXIT +} + +// ------------------------------------------------------------------------------------------------- + +/* class static */ void +WXMPMeta_GetNamespacePrefix_1 ( XMP_StringPtr namespaceURI, + void * namespacePrefix, + SetClientStringProc SetClientString, + WXMP_Result * wResult ) +{ + XMP_ENTER_Static ( "WXMPMeta_GetNamespacePrefix_1" ) + + if ( (namespaceURI == 0) || (*namespaceURI == 0) ) XMP_Throw ( "Empty namespace URI", kXMPErr_BadSchema ); + + XMP_StringPtr prefixPtr = 0; + XMP_StringLen prefixSize = 0; + + bool found = XMPMeta::GetNamespacePrefix ( namespaceURI, &prefixPtr, &prefixSize ); + wResult->int32Result = found; + + if ( found && (namespacePrefix != 0) ) (*SetClientString) ( namespacePrefix, prefixPtr, prefixSize ); + + XMP_EXIT +} + +// ------------------------------------------------------------------------------------------------- + +/* class static */ void +WXMPMeta_GetNamespaceURI_1 ( XMP_StringPtr namespacePrefix, + void * namespaceURI, + SetClientStringProc SetClientString, + WXMP_Result * wResult ) +{ + XMP_ENTER_Static ( "WXMPMeta_GetNamespaceURI_1" ) + + if ( (namespacePrefix == 0) || (*namespacePrefix == 0) ) XMP_Throw ( "Empty namespace prefix", kXMPErr_BadSchema ); + + XMP_StringPtr uriPtr = 0; + XMP_StringLen uriSize = 0; + + bool found = XMPMeta::GetNamespaceURI ( namespacePrefix, &uriPtr, &uriSize ); + wResult->int32Result = found; + + if ( found && (namespaceURI != 0) ) (*SetClientString) ( namespaceURI, uriPtr, uriSize ); + + XMP_EXIT +} + +// ------------------------------------------------------------------------------------------------- + +/* class static */ void +WXMPMeta_DeleteNamespace_1 ( XMP_StringPtr namespaceURI, + WXMP_Result * wResult ) +{ + XMP_ENTER_Static ( "WXMPMeta_DeleteNamespace_1" ) + + if ( (namespaceURI == 0) || (*namespaceURI == 0) ) XMP_Throw ( "Empty namespace URI", kXMPErr_BadSchema ); + + XMPMeta::DeleteNamespace ( namespaceURI ); + + XMP_EXIT +} + +// ================================================================================================= +// Class Method Wrappers +// ===================== + +void +WXMPMeta_GetProperty_1 ( XMPMetaRef xmpObjRef, + XMP_StringPtr schemaNS, + XMP_StringPtr propName, + void * propValue, + XMP_OptionBits * options, + SetClientStringProc SetClientString, + WXMP_Result * wResult ) /* const */ +{ + XMP_ENTER_ObjRead ( XMPMeta, "WXMPMeta_GetProperty_1" ) + + if ( (schemaNS == 0) || (*schemaNS == 0) ) XMP_Throw ( "Empty schema namespace URI", kXMPErr_BadSchema ); + if ( (propName == 0) || (*propName == 0) ) XMP_Throw ( "Empty property name", kXMPErr_BadXPath ); + + XMP_StringPtr valuePtr = 0; + XMP_StringLen valueSize = 0; + if ( options == 0 ) options = &voidOptionBits; + + bool found = thiz.GetProperty ( schemaNS, propName, &valuePtr, &valueSize, options ); + wResult->int32Result = found; + + if ( found && (propValue != 0) ) (*SetClientString) ( propValue, valuePtr, valueSize ); + + XMP_EXIT +} + +// ------------------------------------------------------------------------------------------------- + +void +WXMPMeta_GetArrayItem_1 ( XMPMetaRef xmpObjRef, + XMP_StringPtr schemaNS, + XMP_StringPtr arrayName, + XMP_Index itemIndex, + void * itemValue, + XMP_OptionBits * options, + SetClientStringProc SetClientString, + WXMP_Result * wResult ) /* const */ +{ + XMP_ENTER_ObjRead ( XMPMeta, "WXMPMeta_GetArrayItem_1" ) + + if ( (schemaNS == 0) || (*schemaNS == 0) ) XMP_Throw ( "Empty schema namespace URI", kXMPErr_BadSchema ); + if ( (arrayName == 0) || (*arrayName == 0) ) XMP_Throw ( "Empty array name", kXMPErr_BadXPath ); + + XMP_StringPtr valuePtr = 0; + XMP_StringLen valueSize = 0; + if ( options == 0 ) options = &voidOptionBits; + + bool found = thiz.GetArrayItem ( schemaNS, arrayName, itemIndex, &valuePtr, &valueSize, options ); + wResult->int32Result = found; + + if ( found && (itemValue != 0) ) (*SetClientString) ( itemValue, valuePtr, valueSize ); + + XMP_EXIT +} + +// ------------------------------------------------------------------------------------------------- + +void +WXMPMeta_GetStructField_1 ( XMPMetaRef xmpObjRef, + XMP_StringPtr schemaNS, + XMP_StringPtr structName, + XMP_StringPtr fieldNS, + XMP_StringPtr fieldName, + void * fieldValue, + XMP_OptionBits * options, + SetClientStringProc SetClientString, + WXMP_Result * wResult ) /* const */ +{ + XMP_ENTER_ObjRead ( XMPMeta, "WXMPMeta_GetStructField_1" ) + + if ( (schemaNS == 0) || (*schemaNS == 0) ) XMP_Throw ( "Empty schema namespace URI", kXMPErr_BadSchema ); + if ( (structName == 0) || (*structName == 0) ) XMP_Throw ( "Empty struct name", kXMPErr_BadXPath ); + if ( (fieldNS == 0) || (*fieldNS == 0) ) XMP_Throw ( "Empty field namespace URI", kXMPErr_BadSchema ); + if ( (fieldName == 0) || (*fieldName == 0) ) XMP_Throw ( "Empty field name", kXMPErr_BadXPath ); + + XMP_StringPtr valuePtr = 0; + XMP_StringLen valueSize = 0; + if ( options == 0 ) options = &voidOptionBits; + + bool found = thiz.GetStructField ( schemaNS, structName, fieldNS, fieldName, &valuePtr, &valueSize, options ); + wResult->int32Result = found; + + if ( found && (fieldValue != 0) ) (*SetClientString) ( fieldValue, valuePtr, valueSize ); + + XMP_EXIT +} + +// ------------------------------------------------------------------------------------------------- + +void +WXMPMeta_GetQualifier_1 ( XMPMetaRef xmpObjRef, + XMP_StringPtr schemaNS, + XMP_StringPtr propName, + XMP_StringPtr qualNS, + XMP_StringPtr qualName, + void * qualValue, + XMP_OptionBits * options, + SetClientStringProc SetClientString, + WXMP_Result * wResult ) /* const */ +{ + XMP_ENTER_ObjRead ( XMPMeta, "WXMPMeta_GetQualifier_1" ) + + if ( (schemaNS == 0) || (*schemaNS == 0) ) XMP_Throw ( "Empty schema namespace URI", kXMPErr_BadSchema ); + if ( (propName == 0) || (*propName == 0) ) XMP_Throw ( "Empty property name", kXMPErr_BadXPath ); + if ( (qualNS == 0) || (*qualNS == 0) ) XMP_Throw ( "Empty qualifier namespace URI", kXMPErr_BadSchema ); + if ( (qualName == 0) || (*qualName == 0) ) XMP_Throw ( "Empty qualifier name", kXMPErr_BadXPath ); + + XMP_StringPtr valuePtr = 0; + XMP_StringLen valueSize = 0; + if ( options == 0 ) options = &voidOptionBits; + + bool found = thiz.GetQualifier ( schemaNS, propName, qualNS, qualName, &valuePtr, &valueSize, options ); + wResult->int32Result = found; + + if ( found && (qualValue != 0) ) (*SetClientString) ( qualValue, valuePtr, valueSize ); + + XMP_EXIT +} + +// ------------------------------------------------------------------------------------------------- + +void +WXMPMeta_SetProperty_1 ( XMPMetaRef xmpObjRef, + XMP_StringPtr schemaNS, + XMP_StringPtr propName, + XMP_StringPtr propValue, + XMP_OptionBits options, + WXMP_Result * wResult ) +{ + XMP_ENTER_ObjWrite ( XMPMeta, "WXMPMeta_SetProperty_1" ) + + if ( (schemaNS == 0) || (*schemaNS == 0) ) XMP_Throw ( "Empty schema namespace URI", kXMPErr_BadSchema ); + if ( (propName == 0) || (*propName == 0) ) XMP_Throw ( "Empty property name", kXMPErr_BadXPath ); + + thiz->SetProperty ( schemaNS, propName, propValue, options ); + + XMP_EXIT +} + +// ------------------------------------------------------------------------------------------------- + +void +WXMPMeta_SetArrayItem_1 ( XMPMetaRef xmpObjRef, + XMP_StringPtr schemaNS, + XMP_StringPtr arrayName, + XMP_Index itemIndex, + XMP_StringPtr itemValue, + XMP_OptionBits options, + WXMP_Result * wResult ) +{ + XMP_ENTER_ObjWrite ( XMPMeta, "WXMPMeta_SetArrayItem_1" ) + + if ( (schemaNS == 0) || (*schemaNS == 0) ) XMP_Throw ( "Empty schema namespace URI", kXMPErr_BadSchema ); + if ( (arrayName == 0) || (*arrayName == 0) ) XMP_Throw ( "Empty array name", kXMPErr_BadXPath ); + + thiz->SetArrayItem ( schemaNS, arrayName, itemIndex, itemValue, options ); + + XMP_EXIT +} + +// ------------------------------------------------------------------------------------------------- + +void +WXMPMeta_AppendArrayItem_1 ( XMPMetaRef xmpObjRef, + XMP_StringPtr schemaNS, + XMP_StringPtr arrayName, + XMP_OptionBits arrayOptions, + XMP_StringPtr itemValue, + XMP_OptionBits options, + WXMP_Result * wResult ) +{ + XMP_ENTER_ObjWrite ( XMPMeta, "WXMPMeta_AppendArrayItem_1" ) + + if ( (schemaNS == 0) || (*schemaNS == 0) ) XMP_Throw ( "Empty schema namespace URI", kXMPErr_BadSchema ); + if ( (arrayName == 0) || (*arrayName == 0) ) XMP_Throw ( "Empty array name", kXMPErr_BadXPath ); + + thiz->AppendArrayItem ( schemaNS, arrayName, arrayOptions, itemValue, options ); + + XMP_EXIT +} + +// ------------------------------------------------------------------------------------------------- + +void +WXMPMeta_SetStructField_1 ( XMPMetaRef xmpObjRef, + XMP_StringPtr schemaNS, + XMP_StringPtr structName, + XMP_StringPtr fieldNS, + XMP_StringPtr fieldName, + XMP_StringPtr fieldValue, + XMP_OptionBits options, + WXMP_Result * wResult ) +{ + XMP_ENTER_ObjWrite ( XMPMeta, "WXMPMeta_SetStructField_1" ) + + if ( (schemaNS == 0) || (*schemaNS == 0) ) XMP_Throw ( "Empty schema namespace URI", kXMPErr_BadSchema ); + if ( (structName == 0) || (*structName == 0) ) XMP_Throw ( "Empty struct name", kXMPErr_BadXPath ); + if ( (fieldNS == 0) || (*fieldNS == 0) ) XMP_Throw ( "Empty field namespace URI", kXMPErr_BadSchema ); + if ( (fieldName == 0) || (*fieldName == 0) ) XMP_Throw ( "Empty field name", kXMPErr_BadXPath ); + + thiz->SetStructField ( schemaNS, structName, fieldNS, fieldName, fieldValue, options ); + + XMP_EXIT +} + +// ------------------------------------------------------------------------------------------------- + +void +WXMPMeta_SetQualifier_1 ( XMPMetaRef xmpObjRef, + XMP_StringPtr schemaNS, + XMP_StringPtr propName, + XMP_StringPtr qualNS, + XMP_StringPtr qualName, + XMP_StringPtr qualValue, + XMP_OptionBits options, + WXMP_Result * wResult ) +{ + XMP_ENTER_ObjWrite ( XMPMeta, "WXMPMeta_SetQualifier_1" ) + + if ( (schemaNS == 0) || (*schemaNS == 0) ) XMP_Throw ( "Empty schema namespace URI", kXMPErr_BadSchema ); + if ( (propName == 0) || (*propName == 0) ) XMP_Throw ( "Empty property name", kXMPErr_BadXPath ); + if ( (qualNS == 0) || (*qualNS == 0) ) XMP_Throw ( "Empty qualifier namespace URI", kXMPErr_BadSchema ); + if ( (qualName == 0) || (*qualName == 0) ) XMP_Throw ( "Empty qualifier name", kXMPErr_BadXPath ); + + thiz->SetQualifier ( schemaNS, propName, qualNS, qualName, qualValue, options ); + + XMP_EXIT +} + +// ------------------------------------------------------------------------------------------------- + +void +WXMPMeta_DeleteProperty_1 ( XMPMetaRef xmpObjRef, + XMP_StringPtr schemaNS, + XMP_StringPtr propName, + WXMP_Result * wResult ) +{ + XMP_ENTER_ObjWrite ( XMPMeta, "WXMPMeta_DeleteProperty_1" ) + + if ( (schemaNS == 0) || (*schemaNS == 0) ) XMP_Throw ( "Empty schema namespace URI", kXMPErr_BadSchema ); + if ( (propName == 0) || (*propName == 0) ) XMP_Throw ( "Empty property name", kXMPErr_BadXPath ); + + thiz->DeleteProperty ( schemaNS, propName ); + + XMP_EXIT +} + +// ------------------------------------------------------------------------------------------------- + +void +WXMPMeta_DeleteArrayItem_1 ( XMPMetaRef xmpObjRef, + XMP_StringPtr schemaNS, + XMP_StringPtr arrayName, + XMP_Index itemIndex, + WXMP_Result * wResult ) +{ + XMP_ENTER_ObjWrite ( XMPMeta, "WXMPMeta_DeleteArrayItem_1" ) + + if ( (schemaNS == 0) || (*schemaNS == 0) ) XMP_Throw ( "Empty schema namespace URI", kXMPErr_BadSchema ); + if ( (arrayName == 0) || (*arrayName == 0) ) XMP_Throw ( "Empty array name", kXMPErr_BadXPath ); + + thiz->DeleteArrayItem ( schemaNS, arrayName, itemIndex ); + + XMP_EXIT +} + +// ------------------------------------------------------------------------------------------------- + +void +WXMPMeta_DeleteStructField_1 ( XMPMetaRef xmpObjRef, + XMP_StringPtr schemaNS, + XMP_StringPtr structName, + XMP_StringPtr fieldNS, + XMP_StringPtr fieldName, + WXMP_Result * wResult ) +{ + XMP_ENTER_ObjWrite ( XMPMeta, "WXMPMeta_DeleteStructField_1" ) + + if ( (schemaNS == 0) || (*schemaNS == 0) ) XMP_Throw ( "Empty schema namespace URI", kXMPErr_BadSchema ); + if ( (structName == 0) || (*structName == 0) ) XMP_Throw ( "Empty struct name", kXMPErr_BadXPath ); + if ( (fieldNS == 0) || (*fieldNS == 0) ) XMP_Throw ( "Empty field namespace URI", kXMPErr_BadSchema ); + if ( (fieldName == 0) || (*fieldName == 0) ) XMP_Throw ( "Empty field name", kXMPErr_BadXPath ); + + thiz->DeleteStructField ( schemaNS, structName, fieldNS, fieldName ); + + XMP_EXIT +} + +// ------------------------------------------------------------------------------------------------- + +void +WXMPMeta_DeleteQualifier_1 ( XMPMetaRef xmpObjRef, + XMP_StringPtr schemaNS, + XMP_StringPtr propName, + XMP_StringPtr qualNS, + XMP_StringPtr qualName, + WXMP_Result * wResult ) +{ + XMP_ENTER_ObjWrite ( XMPMeta, "WXMPMeta_DeleteQualifier_1" ) + + if ( (schemaNS == 0) || (*schemaNS == 0) ) XMP_Throw ( "Empty schema namespace URI", kXMPErr_BadSchema ); + if ( (propName == 0) || (*propName == 0) ) XMP_Throw ( "Empty property name", kXMPErr_BadXPath ); + if ( (qualNS == 0) || (*qualNS == 0) ) XMP_Throw ( "Empty qualifier namespace URI", kXMPErr_BadSchema ); + if ( (qualName == 0) || (*qualName == 0) ) XMP_Throw ( "Empty qualifier name", kXMPErr_BadXPath ); + + thiz->DeleteQualifier ( schemaNS, propName, qualNS, qualName ); + + XMP_EXIT +} + +// ------------------------------------------------------------------------------------------------- + +void +WXMPMeta_DoesPropertyExist_1 ( XMPMetaRef xmpObjRef, + XMP_StringPtr schemaNS, + XMP_StringPtr propName, + WXMP_Result * wResult ) /* const */ +{ + XMP_ENTER_ObjRead ( XMPMeta, "WXMPMeta_DoesPropertyExist_1" ) + + if ( (schemaNS == 0) || (*schemaNS == 0) ) XMP_Throw ( "Empty schema namespace URI", kXMPErr_BadSchema ); + if ( (propName == 0) || (*propName == 0) ) XMP_Throw ( "Empty property name", kXMPErr_BadXPath ); + + bool found = thiz.DoesPropertyExist ( schemaNS, propName ); + wResult->int32Result = found; + + XMP_EXIT +} + +// ------------------------------------------------------------------------------------------------- + +void +WXMPMeta_DoesArrayItemExist_1 ( XMPMetaRef xmpObjRef, + XMP_StringPtr schemaNS, + XMP_StringPtr arrayName, + XMP_Index itemIndex, + WXMP_Result * wResult ) /* const */ +{ + XMP_ENTER_ObjRead ( XMPMeta, "WXMPMeta_DoesArrayItemExist_1" ) + + if ( (schemaNS == 0) || (*schemaNS == 0) ) XMP_Throw ( "Empty schema namespace URI", kXMPErr_BadSchema ); + if ( (arrayName == 0) || (*arrayName == 0) ) XMP_Throw ( "Empty array name", kXMPErr_BadXPath ); + + bool found = thiz.DoesArrayItemExist ( schemaNS, arrayName, itemIndex ); + wResult->int32Result = found; + + XMP_EXIT +} + +// ------------------------------------------------------------------------------------------------- + +void +WXMPMeta_DoesStructFieldExist_1 ( XMPMetaRef xmpObjRef, + XMP_StringPtr schemaNS, + XMP_StringPtr structName, + XMP_StringPtr fieldNS, + XMP_StringPtr fieldName, + WXMP_Result * wResult ) /* const */ +{ + XMP_ENTER_ObjRead ( XMPMeta, "WXMPMeta_DoesStructFieldExist_1" ) + + if ( (schemaNS == 0) || (*schemaNS == 0) ) XMP_Throw ( "Empty schema namespace URI", kXMPErr_BadSchema ); + if ( (structName == 0) || (*structName == 0) ) XMP_Throw ( "Empty struct name", kXMPErr_BadXPath ); + if ( (fieldNS == 0) || (*fieldNS == 0) ) XMP_Throw ( "Empty field namespace URI", kXMPErr_BadSchema ); + if ( (fieldName == 0) || (*fieldName == 0) ) XMP_Throw ( "Empty field name", kXMPErr_BadXPath ); + + bool found = thiz.DoesStructFieldExist ( schemaNS, structName, fieldNS, fieldName ); + wResult->int32Result = found; + + XMP_EXIT +} + +// ------------------------------------------------------------------------------------------------- + +void +WXMPMeta_DoesQualifierExist_1 ( XMPMetaRef xmpObjRef, + XMP_StringPtr schemaNS, + XMP_StringPtr propName, + XMP_StringPtr qualNS, + XMP_StringPtr qualName, + WXMP_Result * wResult ) /* const */ +{ + XMP_ENTER_ObjRead ( XMPMeta, "WXMPMeta_DoesQualifierExist_1" ) + + if ( (schemaNS == 0) || (*schemaNS == 0) ) XMP_Throw ( "Empty schema namespace URI", kXMPErr_BadSchema ); + if ( (propName == 0) || (*propName == 0) ) XMP_Throw ( "Empty property name", kXMPErr_BadXPath ); + if ( (qualNS == 0) || (*qualNS == 0) ) XMP_Throw ( "Empty qualifier namespace URI", kXMPErr_BadSchema ); + if ( (qualName == 0) || (*qualName == 0) ) XMP_Throw ( "Empty qualifier name", kXMPErr_BadXPath ); + + bool found = thiz.DoesQualifierExist ( schemaNS, propName, qualNS, qualName ); + wResult->int32Result = found; + + XMP_EXIT +} + +// ------------------------------------------------------------------------------------------------- + +void +WXMPMeta_GetLocalizedText_1 ( XMPMetaRef xmpObjRef, + XMP_StringPtr schemaNS, + XMP_StringPtr arrayName, + XMP_StringPtr genericLang, + XMP_StringPtr specificLang, + void * actualLang, + void * itemValue, + XMP_OptionBits * options, + SetClientStringProc SetClientString, + WXMP_Result * wResult ) /* const */ +{ + XMP_ENTER_ObjRead ( XMPMeta, "WXMPMeta_GetLocalizedText_1" ) + + if ( (schemaNS == 0) || (*schemaNS == 0) ) XMP_Throw ( "Empty schema namespace URI", kXMPErr_BadSchema ); + if ( (arrayName == 0) || (*arrayName == 0) ) XMP_Throw ( "Empty array name", kXMPErr_BadXPath ); + if ( genericLang == 0 ) genericLang = ""; + if ( (specificLang == 0) ||(*specificLang == 0) ) XMP_Throw ( "Empty specific language", kXMPErr_BadParam ); + + XMP_StringPtr langPtr = 0; + XMP_StringLen langSize = 0; + XMP_StringPtr valuePtr = 0; + XMP_StringLen valueSize = 0; + if ( options == 0 ) options = &voidOptionBits; + + bool found = thiz.GetLocalizedText ( schemaNS, arrayName, genericLang, specificLang, + &langPtr, &langSize, &valuePtr, &valueSize, options ); + wResult->int32Result = found; + + if ( found ) { + if ( actualLang != 0 ) (*SetClientString) ( actualLang, langPtr, langSize ); + if ( itemValue != 0 ) (*SetClientString) ( itemValue, valuePtr, valueSize ); + } + + XMP_EXIT +} + +// ------------------------------------------------------------------------------------------------- + +void +WXMPMeta_SetLocalizedText_1 ( XMPMetaRef xmpObjRef, + XMP_StringPtr schemaNS, + XMP_StringPtr arrayName, + XMP_StringPtr genericLang, + XMP_StringPtr specificLang, + XMP_StringPtr itemValue, + XMP_OptionBits options, + WXMP_Result * wResult ) +{ + XMP_ENTER_ObjWrite ( XMPMeta, "WXMPMeta_SetLocalizedText_1" ) + + if ( (schemaNS == 0) || (*schemaNS == 0) ) XMP_Throw ( "Empty schema namespace URI", kXMPErr_BadSchema ); + if ( (arrayName == 0) || (*arrayName == 0) ) XMP_Throw ( "Empty array name", kXMPErr_BadXPath ); + if ( genericLang == 0 ) genericLang = ""; + if ( (specificLang == 0) ||(*specificLang == 0) ) XMP_Throw ( "Empty specific language", kXMPErr_BadParam ); + if ( itemValue == 0 ) itemValue = ""; + + thiz->SetLocalizedText ( schemaNS, arrayName, genericLang, specificLang, itemValue, options ); + + XMP_EXIT +} + +void +WXMPMeta_DeleteLocalizedText_1 ( XMPMetaRef xmpObjRef, + XMP_StringPtr schemaNS, + XMP_StringPtr arrayName, + XMP_StringPtr genericLang, + XMP_StringPtr specificLang, + WXMP_Result * wResult ) +{ + XMP_ENTER_ObjWrite ( XMPMeta, "WXMPMeta_DeleteLocalizedText_1" ) + + if ( (schemaNS == 0) || (*schemaNS == 0) ) XMP_Throw ( "Empty schema namespace URI", kXMPErr_BadSchema ); + if ( (arrayName == 0) || (*arrayName == 0) ) XMP_Throw ( "Empty array name", kXMPErr_BadXPath ); + if ( genericLang == 0 ) genericLang = ""; + if ( (specificLang == 0) ||(*specificLang == 0) ) XMP_Throw ( "Empty specific language", kXMPErr_BadParam ); + + thiz->DeleteLocalizedText ( schemaNS, arrayName, genericLang, specificLang ); + + XMP_EXIT +} + + +// ------------------------------------------------------------------------------------------------- + +void +WXMPMeta_GetProperty_Bool_1 ( XMPMetaRef xmpObjRef, + XMP_StringPtr schemaNS, + XMP_StringPtr propName, + XMP_Bool * propValue, + XMP_OptionBits * options, + WXMP_Result * wResult ) /* const */ +{ + XMP_ENTER_ObjRead ( XMPMeta, "WXMPMeta_GetProperty_Bool_1" ) + + if ( (schemaNS == 0) || (*schemaNS == 0) ) XMP_Throw ( "Empty schema namespace URI", kXMPErr_BadSchema ); + if ( (propName == 0) || (*propName == 0) ) XMP_Throw ( "Empty property name", kXMPErr_BadXPath ); + + if ( propValue == 0 ) propValue = &voidByte; + if ( options == 0 ) options = &voidOptionBits; + + bool value; + bool found = thiz.GetProperty_Bool ( schemaNS, propName, &value, options ); + if ( propValue != 0 ) *propValue = value; + wResult->int32Result = found; + + XMP_EXIT +} + +// ------------------------------------------------------------------------------------------------- + +void +WXMPMeta_GetProperty_Int_1 ( XMPMetaRef xmpObjRef, + XMP_StringPtr schemaNS, + XMP_StringPtr propName, + XMP_Int32 * propValue, + XMP_OptionBits * options, + WXMP_Result * wResult ) /* const */ +{ + XMP_ENTER_ObjRead ( XMPMeta, "WXMPMeta_GetProperty_Int_1" ) + + if ( (schemaNS == 0) || (*schemaNS == 0) ) XMP_Throw ( "Empty schema namespace URI", kXMPErr_BadSchema ); + if ( (propName == 0) || (*propName == 0) ) XMP_Throw ( "Empty property name", kXMPErr_BadXPath ); + + if ( propValue == 0 ) propValue = &voidInt32; + if ( options == 0 ) options = &voidOptionBits; + + bool found = thiz.GetProperty_Int ( schemaNS, propName, propValue, options ); + wResult->int32Result = found; + + XMP_EXIT +} + +// ------------------------------------------------------------------------------------------------- + +void +WXMPMeta_GetProperty_Int64_1 ( XMPMetaRef xmpObjRef, + XMP_StringPtr schemaNS, + XMP_StringPtr propName, + XMP_Int64 * propValue, + XMP_OptionBits * options, + WXMP_Result * wResult ) /* const */ +{ + XMP_ENTER_ObjRead ( XMPMeta, "WXMPMeta_GetProperty_Int64_1" ) + + if ( (schemaNS == 0) || (*schemaNS == 0) ) XMP_Throw ( "Empty schema namespace URI", kXMPErr_BadSchema ); + if ( (propName == 0) || (*propName == 0) ) XMP_Throw ( "Empty property name", kXMPErr_BadXPath ); + + if ( propValue == 0 ) propValue = &voidInt64; + if ( options == 0 ) options = &voidOptionBits; + + bool found = thiz.GetProperty_Int64 ( schemaNS, propName, propValue, options ); + wResult->int32Result = found; + + XMP_EXIT +} + +// ------------------------------------------------------------------------------------------------- + +void +WXMPMeta_GetProperty_Float_1 ( XMPMetaRef xmpObjRef, + XMP_StringPtr schemaNS, + XMP_StringPtr propName, + double * propValue, + XMP_OptionBits * options, + WXMP_Result * wResult ) /* const */ +{ + XMP_ENTER_ObjRead ( XMPMeta, "WXMPMeta_GetProperty_Float_1" ) + + if ( (schemaNS == 0) || (*schemaNS == 0) ) XMP_Throw ( "Empty schema namespace URI", kXMPErr_BadSchema ); + if ( (propName == 0) || (*propName == 0) ) XMP_Throw ( "Empty property name", kXMPErr_BadXPath ); + + if ( propValue == 0 ) propValue = &voidDouble; + if ( options == 0 ) options = &voidOptionBits; + + bool found = thiz.GetProperty_Float ( schemaNS, propName, propValue, options ); + wResult->int32Result = found; + + XMP_EXIT +} + +// ------------------------------------------------------------------------------------------------- + +void +WXMPMeta_GetProperty_Date_1 ( XMPMetaRef xmpObjRef, + XMP_StringPtr schemaNS, + XMP_StringPtr propName, + XMP_DateTime * propValue, + XMP_OptionBits * options, + WXMP_Result * wResult ) /* const */ +{ + XMP_ENTER_ObjRead ( XMPMeta, "WXMPMeta_GetProperty_Date_1" ) + + if ( (schemaNS == 0) || (*schemaNS == 0) ) XMP_Throw ( "Empty schema namespace URI", kXMPErr_BadSchema ); + if ( (propName == 0) || (*propName == 0) ) XMP_Throw ( "Empty property name", kXMPErr_BadXPath ); + + if ( propValue == 0 ) propValue = &voidDateTime; + if ( options == 0 ) options = &voidOptionBits; + + bool found = thiz.GetProperty_Date ( schemaNS, propName, propValue, options ); + wResult->int32Result = found; + + XMP_EXIT +} + +// ------------------------------------------------------------------------------------------------- + +void +WXMPMeta_SetProperty_Bool_1 ( XMPMetaRef xmpObjRef, + XMP_StringPtr schemaNS, + XMP_StringPtr propName, + XMP_Bool propValue, + XMP_OptionBits options, + WXMP_Result * wResult ) +{ + XMP_ENTER_ObjWrite ( XMPMeta, "WXMPMeta_SetProperty_Bool_1" ) + + if ( (schemaNS == 0) || (*schemaNS == 0) ) XMP_Throw ( "Empty schema namespace URI", kXMPErr_BadSchema ); + if ( (propName == 0) || (*propName == 0) ) XMP_Throw ( "Empty property name", kXMPErr_BadXPath ); + + thiz->SetProperty_Bool ( schemaNS, propName, propValue, options ); + + XMP_EXIT +} + +// ------------------------------------------------------------------------------------------------- + +void +WXMPMeta_SetProperty_Int_1 ( XMPMetaRef xmpObjRef, + XMP_StringPtr schemaNS, + XMP_StringPtr propName, + XMP_Int32 propValue, + XMP_OptionBits options, + WXMP_Result * wResult ) +{ + XMP_ENTER_ObjWrite ( XMPMeta, "WXMPMeta_SetProperty_Int_1" ) + + if ( (schemaNS == 0) || (*schemaNS == 0) ) XMP_Throw ( "Empty schema namespace URI", kXMPErr_BadSchema ); + if ( (propName == 0) || (*propName == 0) ) XMP_Throw ( "Empty property name", kXMPErr_BadXPath ); + + thiz->SetProperty_Int ( schemaNS, propName, propValue, options ); + + XMP_EXIT +} + +// ------------------------------------------------------------------------------------------------- + +void +WXMPMeta_SetProperty_Int64_1 ( XMPMetaRef xmpObjRef, + XMP_StringPtr schemaNS, + XMP_StringPtr propName, + XMP_Int64 propValue, + XMP_OptionBits options, + WXMP_Result * wResult ) +{ + XMP_ENTER_ObjWrite ( XMPMeta, "WXMPMeta_SetProperty_Int64_1" ) + + if ( (schemaNS == 0) || (*schemaNS == 0) ) XMP_Throw ( "Empty schema namespace URI", kXMPErr_BadSchema ); + if ( (propName == 0) || (*propName == 0) ) XMP_Throw ( "Empty property name", kXMPErr_BadXPath ); + + thiz->SetProperty_Int64 ( schemaNS, propName, propValue, options ); + + XMP_EXIT +} + +// ------------------------------------------------------------------------------------------------- + +void +WXMPMeta_SetProperty_Float_1 ( XMPMetaRef xmpObjRef, + XMP_StringPtr schemaNS, + XMP_StringPtr propName, + double propValue, + XMP_OptionBits options, + WXMP_Result * wResult ) +{ + XMP_ENTER_ObjWrite ( XMPMeta, "WXMPMeta_SetProperty_Float_1" ) + + if ( (schemaNS == 0) || (*schemaNS == 0) ) XMP_Throw ( "Empty schema namespace URI", kXMPErr_BadSchema ); + if ( (propName == 0) || (*propName == 0) ) XMP_Throw ( "Empty property name", kXMPErr_BadXPath ); + + thiz->SetProperty_Float ( schemaNS, propName, propValue, options ); + + XMP_EXIT +} + +// ------------------------------------------------------------------------------------------------- + +void +WXMPMeta_SetProperty_Date_1 ( XMPMetaRef xmpObjRef, + XMP_StringPtr schemaNS, + XMP_StringPtr propName, + const XMP_DateTime & propValue, + XMP_OptionBits options, + WXMP_Result * wResult ) +{ + XMP_ENTER_ObjWrite ( XMPMeta, "WXMPMeta_SetProperty_Date_1" ) + + if ( (schemaNS == 0) || (*schemaNS == 0) ) XMP_Throw ( "Empty schema namespace URI", kXMPErr_BadSchema ); + if ( (propName == 0) || (*propName == 0) ) XMP_Throw ( "Empty property name", kXMPErr_BadXPath ); + + thiz->SetProperty_Date ( schemaNS, propName, propValue, options ); + + XMP_EXIT +} + +// ------------------------------------------------------------------------------------------------- + +void +WXMPMeta_DumpObject_1 ( XMPMetaRef xmpObjRef, + XMP_TextOutputProc outProc, + void * refCon, + WXMP_Result * wResult ) /* const */ +{ + XMP_ENTER_ObjRead ( XMPMeta, "WXMPMeta_DumpObject_1" ) + + if ( outProc == 0 ) XMP_Throw ( "Null client output routine", kXMPErr_BadParam ); + + thiz.DumpObject ( outProc, refCon ); + wResult->int32Result = 0; + + XMP_EXIT +} + +// ------------------------------------------------------------------------------------------------- + +void +WXMPMeta_Sort_1 ( XMPMetaRef xmpObjRef, + WXMP_Result * wResult ) +{ + XMP_ENTER_ObjWrite ( XMPMeta, "WXMPMeta_Sort_1" ) + + thiz->Sort(); + + XMP_EXIT +} + +// ------------------------------------------------------------------------------------------------- + +void +WXMPMeta_Erase_1 ( XMPMetaRef xmpObjRef, + WXMP_Result * wResult ) +{ + XMP_ENTER_ObjWrite ( XMPMeta, "WXMPMeta_Erase_1" ) + + thiz->Erase(); + + XMP_EXIT +} + +// ------------------------------------------------------------------------------------------------- + +void +WXMPMeta_Clone_1 ( XMPMetaRef xmpObjRef, + XMP_OptionBits options, + WXMP_Result * wResult ) /* const */ +{ + XMP_ENTER_ObjRead ( XMPMeta, "WXMPMeta_Clone_1" ) + + XMPMeta * xClone = new XMPMeta; // ! Don't need an output lock, final ref assignment in client glue. + thiz.Clone ( xClone, options ); + XMP_Assert ( xClone->clientRefs == 0 ); // ! Gets incremented in TXMPMeta::Clone. + wResult->ptrResult = xClone; + + XMP_EXIT +} + +// ------------------------------------------------------------------------------------------------- + +void +WXMPMeta_CountArrayItems_1 ( XMPMetaRef xmpObjRef, + XMP_StringPtr schemaNS, + XMP_StringPtr arrayName, + WXMP_Result * wResult ) /* const */ +{ + XMP_ENTER_ObjRead ( XMPMeta, "WXMPMeta_CountArrayItems_1" ) + + if ( (schemaNS == 0) || (*schemaNS == 0) ) XMP_Throw ( "Empty schema namespace URI", kXMPErr_BadSchema ); + if ( (arrayName == 0) || (*arrayName == 0) ) XMP_Throw ( "Empty array name", kXMPErr_BadXPath ); + + XMP_Index count = thiz.CountArrayItems ( schemaNS, arrayName ); + wResult->int32Result = count; + + XMP_EXIT +} + +// ------------------------------------------------------------------------------------------------- + +void +WXMPMeta_GetObjectName_1 ( XMPMetaRef xmpObjRef, + void * objName, + SetClientStringProc SetClientString, + WXMP_Result * wResult ) /* const */ +{ + XMP_ENTER_ObjRead ( XMPMeta, "WXMPMeta_GetObjectName_1" ) + + XMP_StringPtr namePtr = 0; + XMP_StringLen nameSize = 0; + + thiz.GetObjectName ( &namePtr, &nameSize ); + if ( objName != 0 ) (*SetClientString) ( objName, namePtr, nameSize ); + + XMP_EXIT +} + +// ------------------------------------------------------------------------------------------------- + +void +WXMPMeta_SetObjectName_1 ( XMPMetaRef xmpObjRef, + XMP_StringPtr name, + WXMP_Result * wResult ) +{ + XMP_ENTER_ObjWrite ( XMPMeta, "WXMPMeta_SetObjectName_1" ) + + if ( name == 0 ) name = ""; + + thiz->SetObjectName ( name ); + + XMP_EXIT +} + +// ------------------------------------------------------------------------------------------------- + +void +WXMPMeta_GetObjectOptions_1 ( XMPMetaRef xmpObjRef, + WXMP_Result * wResult ) /* const */ +{ + XMP_ENTER_ObjRead ( XMPMeta, "WXMPMeta_GetObjectOptions_1" ) + + XMP_OptionBits options = thiz.GetObjectOptions(); + wResult->int32Result = options; + + XMP_EXIT +} + +// ------------------------------------------------------------------------------------------------- + +void +WXMPMeta_SetObjectOptions_1 ( XMPMetaRef xmpObjRef, + XMP_OptionBits options, + WXMP_Result * wResult ) +{ + XMP_ENTER_ObjWrite ( XMPMeta, "WXMPMeta_SetObjectOptions_1" ) + + thiz->SetObjectOptions ( options ); + + XMP_EXIT +} + +// ------------------------------------------------------------------------------------------------- + +void +WXMPMeta_ParseFromBuffer_1 ( XMPMetaRef xmpObjRef, + XMP_StringPtr buffer, + XMP_StringLen bufferSize, + XMP_OptionBits options, + WXMP_Result * wResult ) +{ + XMP_ENTER_ObjWrite ( XMPMeta, "WXMPMeta_ParseFromBuffer_1" ) + + thiz->ParseFromBuffer ( buffer, bufferSize, options ); + + XMP_EXIT +} + +// ------------------------------------------------------------------------------------------------- + +void +WXMPMeta_SerializeToBuffer_1 ( XMPMetaRef xmpObjRef, + void * pktString, + XMP_OptionBits options, + XMP_StringLen padding, + XMP_StringPtr newline, + XMP_StringPtr indent, + XMP_Index baseIndent, + SetClientStringProc SetClientString, + WXMP_Result * wResult ) /* const */ +{ + XMP_ENTER_ObjRead ( XMPMeta, "WXMPMeta_SerializeToBuffer_1" ) + + XMP_VarString localStr; + + if ( newline == 0 ) newline = ""; + if ( indent == 0 ) indent = ""; + + thiz.SerializeToBuffer ( &localStr, options, padding, newline, indent, baseIndent ); + if ( pktString != 0 ) (*SetClientString) ( pktString, localStr.c_str(), localStr.size() ); + + XMP_EXIT +} + +// ------------------------------------------------------------------------------------------------- + +void +WXMPMeta_SetDefaultErrorCallback_1 ( XMPMeta_ErrorCallbackWrapper wrapperProc, + XMPMeta_ErrorCallbackProc clientProc, + void * context, + XMP_Uns32 limit, + WXMP_Result * wResult ) +{ + XMP_ENTER_Static ( "WXMPMeta_SetDefaultErrorCallback_1" ) + + XMPMeta::SetDefaultErrorCallback ( wrapperProc, clientProc, context, limit ); + + XMP_EXIT +} + +// ------------------------------------------------------------------------------------------------- + +void +WXMPMeta_SetErrorCallback_1 ( XMPMetaRef xmpObjRef, + XMPMeta_ErrorCallbackWrapper wrapperProc, + XMPMeta_ErrorCallbackProc clientProc, + void * context, + XMP_Uns32 limit, + WXMP_Result * wResult ) +{ + XMP_ENTER_ObjWrite ( XMPMeta, "WXMPMeta_SetErrorCallback_1" ) + + thiz->SetErrorCallback ( wrapperProc, clientProc, context, limit ); + + XMP_EXIT +} + +// ------------------------------------------------------------------------------------------------- + +void +WXMPMeta_ResetErrorCallbackLimit_1 ( XMPMetaRef xmpObjRef, + XMP_Uns32 limit, + WXMP_Result * wResult ) +{ + XMP_ENTER_ObjWrite ( XMPMeta, "WXMPMeta_ResetErrorCallbackLimit_1" ) + + thiz->ResetErrorCallbackLimit ( limit ); + + XMP_EXIT +} + +// ================================================================================================= + +#if __cplusplus +} /* extern "C" */ +#endif diff --git a/XMPCore/source/WXMPUtils.cpp b/XMPCore/source/WXMPUtils.cpp new file mode 100644 index 0000000..6d8f995 --- /dev/null +++ b/XMPCore/source/WXMPUtils.cpp @@ -0,0 +1,634 @@ +// ================================================================================================= +// 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. +// ================================================================================================= + +// *** Should change "type * inParam" to "type & inParam" + +#include "public/include/XMP_Environment.h" // ! This must be the first include! +#include "public/include/XMP_Const.h" + +#include "public/include/client-glue/WXMPUtils.hpp" + +#include "XMPCore/source/XMPCore_Impl.hpp" +#include "XMPCore/source/XMPUtils.hpp" + +#if XMP_WinBuild + #pragma warning ( disable : 4101 ) // unreferenced local variable + #pragma warning ( disable : 4189 ) // local variable is initialized but not referenced + #pragma warning ( disable : 4800 ) // forcing value to bool 'true' or 'false' (performance warning) + #if XMP_DebugBuild + #pragma warning ( disable : 4297 ) // function assumed not to throw an exception but does + #endif +#endif + +#if __cplusplus +extern "C" { +#endif + +// ================================================================================================= +// Class Static Wrappers +// ===================== + +void +WXMPUtils_ComposeArrayItemPath_1 ( XMP_StringPtr schemaNS, + XMP_StringPtr arrayName, + XMP_Index itemIndex, + void * itemPath, + SetClientStringProc SetClientString, + WXMP_Result * wResult ) +{ + XMP_ENTER_Static ( "WXMPUtils_ComposeArrayItemPath_1" ) + + if ( (schemaNS == 0) || (*schemaNS == 0) ) XMP_Throw ( "Empty schema namespace URI", kXMPErr_BadSchema ); + if ( (arrayName == 0) || (*arrayName == 0) ) XMP_Throw ( "Empty array name", kXMPErr_BadXPath ); + + XMP_VarString localStr; + + XMPUtils::ComposeArrayItemPath ( schemaNS, arrayName, itemIndex, &localStr ); + if ( itemPath != 0 ) (*SetClientString) ( itemPath, localStr.c_str(), localStr.size() ); + + XMP_EXIT +} + +// ------------------------------------------------------------------------------------------------- + +void +WXMPUtils_ComposeStructFieldPath_1 ( XMP_StringPtr schemaNS, + XMP_StringPtr structName, + XMP_StringPtr fieldNS, + XMP_StringPtr fieldName, + void * fieldPath, + SetClientStringProc SetClientString, + WXMP_Result * wResult ) +{ + XMP_ENTER_Static ( "WXMPUtils_ComposeStructFieldPath_1" ) + + if ( (schemaNS == 0) || (*schemaNS == 0) ) XMP_Throw ( "Empty schema namespace URI", kXMPErr_BadSchema ); + if ( (structName == 0) || (*structName == 0) ) XMP_Throw ( "Empty struct name", kXMPErr_BadXPath ); + if ( (fieldNS == 0) || (*fieldNS == 0) ) XMP_Throw ( "Empty field namespace URI", kXMPErr_BadSchema ); + if ( (fieldName == 0) || (*fieldName == 0) ) XMP_Throw ( "Empty field name", kXMPErr_BadXPath ); + + XMP_VarString localStr; + + XMPUtils::ComposeStructFieldPath ( schemaNS, structName, fieldNS, fieldName, &localStr ); + if ( fieldPath != 0 ) (*SetClientString) ( fieldPath, localStr.c_str(), localStr.size() ); + + XMP_EXIT +} + +// ------------------------------------------------------------------------------------------------- + +void +WXMPUtils_ComposeQualifierPath_1 ( XMP_StringPtr schemaNS, + XMP_StringPtr propName, + XMP_StringPtr qualNS, + XMP_StringPtr qualName, + void * qualPath, + SetClientStringProc SetClientString, + WXMP_Result * wResult ) +{ + XMP_ENTER_Static ( "WXMPUtils_ComposeQualifierPath_1" ) + + if ( (schemaNS == 0) || (*schemaNS == 0) ) XMP_Throw ( "Empty schema namespace URI", kXMPErr_BadSchema ); + if ( (propName == 0) || (*propName == 0) ) XMP_Throw ( "Empty property name", kXMPErr_BadXPath ); + if ( (qualNS == 0) || (*qualNS == 0) ) XMP_Throw ( "Empty qualifier namespace URI", kXMPErr_BadSchema ); + if ( (qualName == 0) || (*qualName == 0) ) XMP_Throw ( "Empty qualifier name", kXMPErr_BadXPath ); + + XMP_VarString localStr; + + XMPUtils::ComposeQualifierPath ( schemaNS, propName, qualNS, qualName, &localStr ); + if ( qualPath != 0 ) (*SetClientString) ( qualPath, localStr.c_str(), localStr.size() ); + + XMP_EXIT +} + +// ------------------------------------------------------------------------------------------------- + +void +WXMPUtils_ComposeLangSelector_1 ( XMP_StringPtr schemaNS, + XMP_StringPtr arrayName, + XMP_StringPtr langName, + void * selPath, + SetClientStringProc SetClientString, + WXMP_Result * wResult ) +{ + XMP_ENTER_Static ( "WXMPUtils_ComposeLangSelector_1" ) + + if ( (schemaNS == 0) || (*schemaNS == 0) ) XMP_Throw ( "Empty schema namespace URI", kXMPErr_BadSchema ); + if ( (arrayName == 0) || (*arrayName == 0) ) XMP_Throw ( "Empty array name", kXMPErr_BadXPath ); + if ( (langName == 0) || (*langName == 0) ) XMP_Throw ( "Empty language name", kXMPErr_BadParam ); + + XMP_VarString localStr; + + XMPUtils::ComposeLangSelector ( schemaNS, arrayName, langName, &localStr ); + if ( selPath != 0 ) (*SetClientString) ( selPath, localStr.c_str(), localStr.size() ); + + XMP_EXIT +} + +// ------------------------------------------------------------------------------------------------- + +void +WXMPUtils_ComposeFieldSelector_1 ( XMP_StringPtr schemaNS, + XMP_StringPtr arrayName, + XMP_StringPtr fieldNS, + XMP_StringPtr fieldName, + XMP_StringPtr fieldValue, + void * selPath, + SetClientStringProc SetClientString, + WXMP_Result * wResult ) +{ + XMP_ENTER_Static ( "WXMPUtils_ComposeFieldSelector_1" ) + + if ( (schemaNS == 0) || (*schemaNS == 0) ) XMP_Throw ( "Empty schema namespace URI", kXMPErr_BadSchema ); + if ( (arrayName == 0) || (*arrayName == 0) ) XMP_Throw ( "Empty array name", kXMPErr_BadXPath ); + if ( (fieldNS == 0) || (*fieldNS == 0) ) XMP_Throw ( "Empty field namespace URI", kXMPErr_BadSchema ); + if ( (fieldName == 0) || (*fieldName == 0) ) XMP_Throw ( "Empty field name", kXMPErr_BadXPath ); + if ( fieldValue == 0 ) fieldValue = ""; + + XMP_VarString localStr; + + XMPUtils::ComposeFieldSelector ( schemaNS, arrayName, fieldNS, fieldName, fieldValue, &localStr ); + if ( selPath != 0 ) (*SetClientString) ( selPath, localStr.c_str(), localStr.size() ); + + XMP_EXIT +} + +// ================================================================================================= + +void +WXMPUtils_ConvertFromBool_1 ( XMP_Bool binValue, + void * strValue, + SetClientStringProc SetClientString, + WXMP_Result * wResult ) +{ + XMP_ENTER_Static ( "WXMPUtils_ConvertFromBool_1" ) + + XMP_VarString localStr; + + XMPUtils::ConvertFromBool ( binValue, &localStr ); + if ( strValue != 0 ) (*SetClientString) ( strValue, localStr.c_str(), localStr.size() ); + + XMP_EXIT +} + +// ------------------------------------------------------------------------------------------------- + +void +WXMPUtils_ConvertFromInt_1 ( XMP_Int32 binValue, + XMP_StringPtr format, + void * strValue, + SetClientStringProc SetClientString, + WXMP_Result * wResult ) +{ + XMP_ENTER_Static ( "WXMPUtils_ConvertFromInt_1" ) + + if ( format == 0 ) format = ""; + + XMP_VarString localStr; + + XMPUtils::ConvertFromInt ( binValue, format, &localStr ); + if ( strValue != 0 ) (*SetClientString) ( strValue, localStr.c_str(), localStr.size() ); + + XMP_EXIT +} + +// ------------------------------------------------------------------------------------------------- + +void +WXMPUtils_ConvertFromInt64_1 ( XMP_Int64 binValue, + XMP_StringPtr format, + void * strValue, + SetClientStringProc SetClientString, + WXMP_Result * wResult ) +{ + XMP_ENTER_Static ( "WXMPUtils_ConvertFromInt64_1" ) + + if ( format == 0 ) format = ""; + + XMP_VarString localStr; + + XMPUtils::ConvertFromInt64 ( binValue, format, &localStr ); + if ( strValue != 0 ) (*SetClientString) ( strValue, localStr.c_str(), localStr.size() ); + + XMP_EXIT +} + +// ------------------------------------------------------------------------------------------------- + +void +WXMPUtils_ConvertFromFloat_1 ( double binValue, + XMP_StringPtr format, + void * strValue, + SetClientStringProc SetClientString, + WXMP_Result * wResult ) +{ + XMP_ENTER_Static ( "WXMPUtils_ConvertFromFloat_1" ) + + if ( format == 0 ) format = ""; + + XMP_VarString localStr; + + XMPUtils::ConvertFromFloat ( binValue, format, &localStr ); + if ( strValue != 0 ) (*SetClientString) ( strValue, localStr.c_str(), localStr.size() ); + + XMP_EXIT +} + +// ------------------------------------------------------------------------------------------------- + +void +WXMPUtils_ConvertFromDate_1 ( const XMP_DateTime & binValue, + void * strValue, + SetClientStringProc SetClientString, + WXMP_Result * wResult ) +{ + XMP_ENTER_Static ( "WXMPUtils_ConvertFromDate_1" ) + + XMP_VarString localStr; + + XMPUtils::ConvertFromDate( binValue, &localStr ); + if ( strValue != 0 ) (*SetClientString) ( strValue, localStr.c_str(), localStr.size() ); + + XMP_EXIT +} + +// ================================================================================================= + +void +WXMPUtils_ConvertToBool_1 ( XMP_StringPtr strValue, + WXMP_Result * wResult ) +{ + XMP_ENTER_Static ( "WXMPUtils_ConvertToBool_1" ) + + if ( (strValue == 0) || (*strValue == 0) ) XMP_Throw ( "Empty string value", kXMPErr_BadParam); + XMP_Bool result = XMPUtils::ConvertToBool ( strValue ); + wResult->int32Result = result; + + XMP_EXIT +} + +// ------------------------------------------------------------------------------------------------- + +void +WXMPUtils_ConvertToInt_1 ( XMP_StringPtr strValue, + WXMP_Result * wResult ) +{ + XMP_ENTER_Static ( "WXMPUtils_ConvertToInt_1" ) + + if ( (strValue == 0) || (*strValue == 0) ) XMP_Throw ( "Empty string value", kXMPErr_BadParam); + XMP_Int32 result = XMPUtils::ConvertToInt ( strValue ); + wResult->int32Result = result; + + XMP_EXIT +} + +// ------------------------------------------------------------------------------------------------- + +void +WXMPUtils_ConvertToInt64_1 ( XMP_StringPtr strValue, + WXMP_Result * wResult ) +{ + XMP_ENTER_Static ( "WXMPUtils_ConvertToInt64_1" ) + + if ( (strValue == 0) || (*strValue == 0) ) XMP_Throw ( "Empty string value", kXMPErr_BadParam); + XMP_Int64 result = XMPUtils::ConvertToInt64 ( strValue ); + wResult->int64Result = result; + + XMP_EXIT +} + +// ------------------------------------------------------------------------------------------------- + +void +WXMPUtils_ConvertToFloat_1 ( XMP_StringPtr strValue, + WXMP_Result * wResult ) +{ + XMP_ENTER_Static ( "WXMPUtils_ConvertToFloat_1") + + if ( (strValue == 0) || (*strValue == 0) ) XMP_Throw ( "Empty string value", kXMPErr_BadParam); + double result = XMPUtils::ConvertToFloat ( strValue ); + wResult->floatResult = result; + + XMP_EXIT +} + +// ------------------------------------------------------------------------------------------------- + +void +WXMPUtils_ConvertToDate_1 ( XMP_StringPtr strValue, + XMP_DateTime * binValue, + WXMP_Result * wResult ) +{ + XMP_ENTER_Static ( "WXMPUtils_ConvertToDate_1" ) + + if ( binValue == 0 ) XMP_Throw ( "Null output date", kXMPErr_BadParam); // ! Pointer is from the client. + XMPUtils::ConvertToDate ( strValue, binValue ); + + XMP_EXIT +} + +// ================================================================================================= + +void +WXMPUtils_CurrentDateTime_1 ( XMP_DateTime * time, + WXMP_Result * wResult ) +{ + XMP_ENTER_Static ( "WXMPUtils_CurrentDateTime_1" ) + + if ( time == 0 ) XMP_Throw ( "Null output date", kXMPErr_BadParam); + XMPUtils::CurrentDateTime ( time ); + + XMP_EXIT +} + +// ------------------------------------------------------------------------------------------------- + +void +WXMPUtils_SetTimeZone_1 ( XMP_DateTime * time, + WXMP_Result * wResult ) +{ + XMP_ENTER_Static ( "WXMPUtils_SetTimeZone_1" ) + + if ( time == 0 ) XMP_Throw ( "Null output date", kXMPErr_BadParam); + XMPUtils::SetTimeZone ( time ); + + XMP_EXIT +} + +// ------------------------------------------------------------------------------------------------- + +void +WXMPUtils_ConvertToUTCTime_1 ( XMP_DateTime * time, + WXMP_Result * wResult ) +{ + XMP_ENTER_Static ( "WXMPUtils_ConvertToUTCTime_1" ) + + if ( time == 0 ) XMP_Throw ( "Null output date", kXMPErr_BadParam); + XMPUtils::ConvertToUTCTime ( time ); + + XMP_EXIT +} + +// ------------------------------------------------------------------------------------------------- + +void +WXMPUtils_ConvertToLocalTime_1 ( XMP_DateTime * time, + WXMP_Result * wResult ) +{ + XMP_ENTER_Static ( "WXMPUtils_ConvertToLocalTime_1" ) + + if ( time == 0 ) XMP_Throw ( "Null output date", kXMPErr_BadParam); + XMPUtils::ConvertToLocalTime ( time ); + + XMP_EXIT +} + +// ------------------------------------------------------------------------------------------------- + +void +WXMPUtils_CompareDateTime_1 ( const XMP_DateTime & left, + const XMP_DateTime & right, + WXMP_Result * wResult ) +{ + XMP_ENTER_Static ( "WXMPUtils_CompareDateTime_1" ) + + int result = XMPUtils::CompareDateTime ( left, right ); + wResult->int32Result = result; + + XMP_EXIT +} + +// ================================================================================================= + +void +WXMPUtils_EncodeToBase64_1 ( XMP_StringPtr rawStr, + XMP_StringLen rawLen, + void * encodedStr, + SetClientStringProc SetClientString, + WXMP_Result * wResult ) +{ + XMP_ENTER_Static ( "WXMPUtils_EncodeToBase64_1" ) + + XMP_VarString localStr; + + XMPUtils::EncodeToBase64 ( rawStr, rawLen, &localStr ); + if ( encodedStr != 0 ) (*SetClientString) ( encodedStr, localStr.c_str(), localStr.size() ); + + XMP_EXIT +} + +// ------------------------------------------------------------------------------------------------- + +void +WXMPUtils_DecodeFromBase64_1 ( XMP_StringPtr encodedStr, + XMP_StringLen encodedLen, + void * rawStr, + SetClientStringProc SetClientString, + WXMP_Result * wResult ) +{ + XMP_ENTER_Static ( "WXMPUtils_DecodeFromBase64_1" ) + + XMP_VarString localStr; + + XMPUtils::DecodeFromBase64 ( encodedStr, encodedLen, &localStr ); + if ( rawStr != 0 ) (*SetClientString) ( rawStr, localStr.c_str(), localStr.size() ); + + XMP_EXIT +} + +// ================================================================================================= + +void +WXMPUtils_PackageForJPEG_1 ( XMPMetaRef wxmpObj, + void * stdStr, + void * extStr, + void * digestStr, + SetClientStringProc SetClientString, + WXMP_Result * wResult ) +{ + XMP_ENTER_Static ( "WXMPUtils_PackageForJPEG_1" ) + + XMP_VarString localStdStr; + XMP_VarString localExtStr; + XMP_VarString localDigestStr; + + const XMPMeta & xmpObj = WtoXMPMeta_Ref ( wxmpObj ); + XMP_AutoLock metaLock ( &xmpObj.lock, kXMP_ReadLock ); + + XMPUtils::PackageForJPEG ( xmpObj, &localStdStr, &localExtStr, &localDigestStr ); + if ( stdStr != 0 ) (*SetClientString) ( stdStr, localStdStr.c_str(), localStdStr.size() ); + if ( extStr != 0 ) (*SetClientString) ( extStr, localExtStr.c_str(), localExtStr.size() ); + if ( digestStr != 0 ) (*SetClientString) ( digestStr, localDigestStr.c_str(), localDigestStr.size() ); + + XMP_EXIT +} + +// ------------------------------------------------------------------------------------------------- + +void +WXMPUtils_MergeFromJPEG_1 ( XMPMetaRef wfullXMP, + XMPMetaRef wextendedXMP, + WXMP_Result * wResult ) +{ + XMP_ENTER_Static ( "WXMPUtils_MergeFromJPEG_1" ) + + if ( wfullXMP == 0 ) XMP_Throw ( "Output XMP pointer is null", kXMPErr_BadParam ); + if ( wfullXMP == wextendedXMP ) XMP_Throw ( "Full and extended XMP pointers match", kXMPErr_BadParam ); + + XMPMeta * fullXMP = WtoXMPMeta_Ptr ( wfullXMP ); + XMP_AutoLock fullXMPLock ( &fullXMP->lock, kXMP_WriteLock ); + + const XMPMeta & extendedXMP = WtoXMPMeta_Ref ( wextendedXMP ); + XMP_AutoLock extendedXMPLock ( &extendedXMP.lock, kXMP_ReadLock ); + + XMPUtils::MergeFromJPEG ( fullXMP, extendedXMP ); + + XMP_EXIT +} + +// ================================================================================================= + +void +WXMPUtils_CatenateArrayItems_1 ( XMPMetaRef wxmpObj, + XMP_StringPtr schemaNS, + XMP_StringPtr arrayName, + XMP_StringPtr separator, + XMP_StringPtr quotes, + XMP_OptionBits options, + void * catedStr, + SetClientStringProc SetClientString, + WXMP_Result * wResult ) +{ + XMP_ENTER_Static ( "WXMPUtils_CatenateArrayItems_1" ) + + if ( (schemaNS == 0) || (*schemaNS == 0) ) XMP_Throw ( "Empty schema namespace URI", kXMPErr_BadSchema ); + if ( (arrayName == 0) || (*arrayName == 0) ) XMP_Throw ( "Empty array name", kXMPErr_BadXPath ); + + if ( separator == 0 ) separator = "; "; + if ( quotes == 0 ) quotes = "\""; + + XMP_VarString localStr; + + const XMPMeta & xmpObj = WtoXMPMeta_Ref ( wxmpObj ); + XMP_AutoLock metaLock ( &xmpObj.lock, kXMP_ReadLock ); + + XMPUtils::CatenateArrayItems ( xmpObj, schemaNS, arrayName, separator, quotes, options, &localStr ); + if ( catedStr != 0 ) (*SetClientString) ( catedStr, localStr.c_str(), localStr.size() ); + + XMP_EXIT +} + +// ------------------------------------------------------------------------------------------------- + +void +WXMPUtils_SeparateArrayItems_1 ( XMPMetaRef wxmpObj, + XMP_StringPtr schemaNS, + XMP_StringPtr arrayName, + XMP_OptionBits options, + XMP_StringPtr catedStr, + WXMP_Result * wResult ) +{ + XMP_ENTER_Static ( "WXMPUtils_SeparateArrayItems_1" ) + + if ( wxmpObj == 0 ) XMP_Throw ( "Output XMP pointer is null", kXMPErr_BadParam ); + if ( (schemaNS == 0) || (*schemaNS == 0) ) XMP_Throw ( "Empty schema namespace URI", kXMPErr_BadSchema ); + if ( (arrayName == 0) || (*arrayName == 0) ) XMP_Throw ( "Empty array name", kXMPErr_BadXPath ); + if ( catedStr == 0 ) catedStr = ""; + + XMPMeta * xmpObj = WtoXMPMeta_Ptr ( wxmpObj ); + XMP_AutoLock metaLock ( &xmpObj->lock, kXMP_WriteLock ); + + XMPUtils::SeparateArrayItems ( xmpObj, schemaNS, arrayName, options, catedStr ); + + XMP_EXIT +} + +// ------------------------------------------------------------------------------------------------- + +void +WXMPUtils_ApplyTemplate_1 ( XMPMetaRef wWorkingXMP, + XMPMetaRef wTemplateXMP, + XMP_OptionBits actions, + WXMP_Result * wResult ) +{ + XMP_ENTER_Static ( "WXMPUtils_ApplyTemplate_1" ) + + XMP_Assert ( (wWorkingXMP != 0) && (wTemplateXMP != 0) ); // Client glue enforced. + + XMPMeta * workingXMP = WtoXMPMeta_Ptr ( wWorkingXMP ); + XMP_AutoLock workingLock ( &workingXMP->lock, kXMP_WriteLock ); + + const XMPMeta & templateXMP = WtoXMPMeta_Ref ( wTemplateXMP ); + XMP_AutoLock templateLock ( &templateXMP.lock, kXMP_ReadLock ); + + XMPUtils::ApplyTemplate ( workingXMP, templateXMP, actions ); + + XMP_EXIT +} + +// ------------------------------------------------------------------------------------------------- + +void +WXMPUtils_RemoveProperties_1 ( XMPMetaRef wxmpObj, + XMP_StringPtr schemaNS, + XMP_StringPtr propName, + XMP_OptionBits options, + WXMP_Result * wResult ) +{ + XMP_ENTER_Static ( "WXMPUtils_RemoveProperties_1" ) + + if ( wxmpObj == 0 ) XMP_Throw ( "Output XMP pointer is null", kXMPErr_BadParam ); + if ( schemaNS == 0 ) schemaNS = ""; + if ( propName == 0 ) propName = ""; + + XMPMeta * xmpObj = WtoXMPMeta_Ptr ( wxmpObj ); + XMP_AutoLock metaLock ( &xmpObj->lock, kXMP_WriteLock ); + + XMPUtils::RemoveProperties ( xmpObj, schemaNS, propName, options ); + + XMP_EXIT +} + +// ------------------------------------------------------------------------------------------------- + +// ------------------------------------------------------------------------------------------------- + +void +WXMPUtils_DuplicateSubtree_1 ( XMPMetaRef wSource, + XMPMetaRef wDest, + XMP_StringPtr sourceNS, + XMP_StringPtr sourceRoot, + XMP_StringPtr destNS, + XMP_StringPtr destRoot, + XMP_OptionBits options, + WXMP_Result * wResult ) +{ + XMP_ENTER_Static ( "WXMPUtils_DuplicateSubtree_1" ) + + if ( wDest == 0 ) XMP_Throw ( "Output XMP pointer is null", kXMPErr_BadParam ); + if ( (sourceNS == 0) || (*sourceNS == 0) ) XMP_Throw ( "Empty source schema URI", kXMPErr_BadSchema ); + if ( (sourceRoot == 0) || (*sourceRoot == 0) ) XMP_Throw ( "Empty source root name", kXMPErr_BadXPath ); + if ( destNS == 0 ) destNS = sourceNS; + if ( destRoot == 0 ) destRoot = sourceRoot; + + const XMPMeta & source = WtoXMPMeta_Ref ( wSource ); + XMP_AutoLock sourceLock ( &source.lock, kXMP_ReadLock, (wSource != wDest) ); + + XMPMeta * dest = WtoXMPMeta_Ptr ( wDest ); + XMP_AutoLock destLock ( &dest->lock, kXMP_WriteLock ); + + XMPUtils::DuplicateSubtree ( source, dest, sourceNS, sourceRoot, destNS, destRoot, options ); + + XMP_EXIT +} + +// ================================================================================================= + +#if __cplusplus +} /* extern "C" */ +#endif diff --git a/XMPCore/source/XMPCore_Impl.cpp b/XMPCore/source/XMPCore_Impl.cpp new file mode 100644 index 0000000..4da130d --- /dev/null +++ b/XMPCore/source/XMPCore_Impl.cpp @@ -0,0 +1,1418 @@ +// ================================================================================================= +// 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_Version.h" +#include "XMPCore/source/XMPCore_Impl.hpp" +#include "XMPCore/source/XMPMeta.hpp" // *** For use of GetNamespacePrefix in FindSchemaNode. + +#include "source/UnicodeInlines.incl_cpp" + +#include + +using namespace std; + +#if XMP_WinBuild + #pragma warning ( disable : 4290 ) // C++ exception specification ignored except ... not __declspec(nothrow) + #pragma warning ( disable : 4800 ) // forcing value to bool 'true' or 'false' (performance warning) +#endif + +// *** Add debug codegen checks, e.g. that typical masking operations really work +// *** Make option constants 0x...UL. + +// Internal code should be using #if with XMP_MacBuild, XMP_WinBuild, XMP_UNIXBuild or XMP_iOSBuild. +// This is a sanity check in case of accidental use of *_ENV. Some clients use the poor +// practice of defining the *_ENV macro with an empty value. +#if defined ( MAC_ENV ) + #if ! MAC_ENV + #error "MAC_ENV must be defined so that \"#if MAC_ENV\" is true" + #endif +#elif defined ( WIN_ENV ) + #if ! WIN_ENV + #error "WIN_ENV must be defined so that \"#if WIN_ENV\" is true" + #endif +#elif defined ( UNIX_ENV ) + #if ! UNIX_ENV + #error "UNIX_ENV must be defined so that \"#if UNIX_ENV\" is true" + #endif +#elif defined ( IOS_ENV ) + #if ! IOS_ENV + #error "IOS_ENV must be defined so that \"#if IOS_ENV\" is true" + #endif +#endif + +// ================================================================================================= +// Static Variables +// ================ + +XMP_Int32 sXMP_InitCount = 0; + +XMP_NamespaceTable * sRegisteredNamespaces = 0; + +XMP_AliasMap * sRegisteredAliasMap = 0; + +void * voidVoidPtr = 0; // Used to backfill null output parameters. +XMP_StringPtr voidStringPtr = 0; +XMP_StringLen voidStringLen = 0; +XMP_OptionBits voidOptionBits = 0; +XMP_Uns8 voidByte = 0; +bool voidBool = 0; +XMP_Int32 voidInt32 = 0; +XMP_Int64 voidInt64 = 0; +double voidDouble = 0.0; +XMP_DateTime voidDateTime; +WXMP_Result void_wResult; + +// ================================================================================================= +// Local Utilities +// =============== + +// ------------------------------------------------------------------------------------------------- +// VerifyXPathRoot +// --------------- +// +// Set up the first 2 components of the expanded XPath. Normalizes the various cases of using the +// full schema URI and/or a qualified root property name. Returns true for normal processing. If +// allowUnknownSchemaNS is true and the schema namespace is not registered, false is returned. If +// allowUnknownSchemaNS is false and the schema namespace is not registered, an exception is thrown. + +// *** Should someday check the full syntax. + +static void +VerifyXPathRoot ( XMP_StringPtr schemaURI, + XMP_StringPtr propName, + XMP_ExpandedXPath * expandedXPath ) +{ + // Do some basic checks on the URI and name. Try to lookup the URI. See if the name is qualified. + + XMP_Assert ( (schemaURI != 0) && (propName != 0) && (*propName != 0) ); + XMP_Assert ( (expandedXPath != 0) && (expandedXPath->empty()) ); + + if ( *schemaURI == 0 ) XMP_Throw ( "Schema namespace URI is required", kXMPErr_BadSchema ); + + if ( (*propName == '?') || (*propName == '@') ) { + XMP_Throw ( "Top level name must not be a qualifier", kXMPErr_BadXPath ); + } + for ( XMP_StringPtr ch = propName; *ch != 0; ++ch ) { + if ( (*ch == '/') || (*ch == '[') ) { + XMP_Throw ( "Top level name must be simple", kXMPErr_BadXPath ); + } + } + + XMP_StringPtr schemaPrefix; + bool nsFound = sRegisteredNamespaces->GetPrefix ( schemaURI, &schemaPrefix, 0 ); + if ( ! nsFound ) XMP_Throw ( "Unregistered schema namespace URI", kXMPErr_BadSchema ); + + XMP_StringPtr colonPos = propName; + while ( (*colonPos != 0) && (*colonPos != ':') ) ++colonPos; + VerifySimpleXMLName ( propName, colonPos ); // Verify the part before any colon. + + // Verify the various URI and prefix combinations. Initialize the expanded XPath. + + if ( *colonPos == 0 ) { + + // The propName is unqualified, use the schemaURI and associated prefix. + + expandedXPath->push_back ( XPathStepInfo ( schemaURI, kXMP_SchemaNode ) ); + expandedXPath->push_back ( XPathStepInfo ( schemaPrefix, 0 ) ); + (*expandedXPath)[kRootPropStep].step += propName; + + } else { + + // The propName is qualified. Make sure the prefix is legit. Use the associated URI and qualified name. + + size_t prefixLen = colonPos - propName + 1; // ! Include the colon. + VerifySimpleXMLName ( colonPos+1, colonPos+strlen(colonPos) ); + + XMP_VarString prefix ( propName, prefixLen ); + if ( prefix != schemaPrefix ) XMP_Throw ( "Schema namespace URI and prefix mismatch", kXMPErr_BadSchema ); + + expandedXPath->push_back ( XPathStepInfo ( schemaURI, kXMP_SchemaNode ) ); + expandedXPath->push_back ( XPathStepInfo ( propName, 0 ) ); + + } + +} // VerifyXPathRoot + +// ------------------------------------------------------------------------------------------------- +// VerifyQualName +// -------------- + +static void +VerifyQualName ( XMP_StringPtr qualName, XMP_StringPtr nameEnd ) +{ + if ( qualName >= nameEnd ) XMP_Throw ( "Empty qualified name", kXMPErr_BadXPath ); + + XMP_StringPtr colonPos = qualName; + while ( (colonPos < nameEnd) && (*colonPos != ':') ) ++colonPos; + if ( (colonPos == qualName) || (colonPos >= nameEnd) ) XMP_Throw ( "Ill-formed qualified name", kXMPErr_BadXPath ); + + VerifySimpleXMLName ( qualName, colonPos ); + VerifySimpleXMLName ( colonPos+1, nameEnd ); + + size_t prefixLen = colonPos - qualName + 1; // ! Include the colon. + XMP_VarString prefix ( qualName, prefixLen ); + bool nsFound = sRegisteredNamespaces->GetURI ( prefix.c_str(), 0, 0 ); + if ( ! nsFound ) XMP_Throw ( "Unknown namespace prefix for qualified name", kXMPErr_BadXPath ); + +} // VerifyQualName + +// ------------------------------------------------------------------------------------------------- +// FindIndexedItem +// --------------- +// +// [index] An element of an array. +// +// Support the implicit creation of a new last item. + +static XMP_Index +FindIndexedItem ( XMP_Node * arrayNode, const XMP_VarString & indexStep, bool createNodes ) +{ + XMP_Index index = 0; + size_t chLim = indexStep.size() - 1; + + XMP_Assert ( (chLim >= 2) && (indexStep[0] == '[') && (indexStep[chLim] == ']') ); + + for ( size_t chNum = 1; chNum != chLim; ++chNum ) { + XMP_Assert ( ('0' <= indexStep[chNum]) && (indexStep[chNum] <= '9') ); + index = (index * 10) + (indexStep[chNum] - '0'); + if ( index < 0 ) { + XMP_Throw ( "Array index overflow", kXMPErr_BadXPath ); // ! Overflow, not truly negative. + } + } + + --index; // Change to a C-style, zero based index. + if ( index < 0 ) XMP_Throw ( "Array index must be larger than zero", kXMPErr_BadXPath ); + + if ( (index == (XMP_Index)arrayNode->children.size()) && createNodes ) { // Append a new last+1 node. + XMP_Node * newItem = new XMP_Node ( arrayNode, kXMP_ArrayItemName, kXMP_NewImplicitNode ); + arrayNode->children.push_back ( newItem ); + } + + // ! Don't throw here for a too large index. SetProperty will throw, GetProperty will not. + if ( index >= (XMP_Index)arrayNode->children.size() ) index = -1; + return index; + +} // FindIndexedItem + +// ------------------------------------------------------------------------------------------------- +// SplitNameAndValue +// ----------------- +// +// Split the name and value parts for field and qualifier selectors: +// +// [qualName="value"] An element in an array of structs, chosen by a field value. +// [?qualName="value"] An element in an array, chosen by a qualifier value. +// +// The value portion is a string quoted by ''' or '"'. The value may contain any character including +// a doubled quoting character. The value may be empty. + +static void +SplitNameAndValue ( const XMP_VarString & selStep, XMP_VarString * nameStr, XMP_VarString * valueStr ) +{ + XMP_StringPtr partBegin = selStep.c_str(); + XMP_StringPtr partEnd; + + const XMP_StringPtr valueEnd = partBegin + (selStep.size() - 2); + const char quote = *valueEnd; + + XMP_Assert ( (*partBegin == '[') && (*(valueEnd+1) == ']') ); + XMP_Assert ( (selStep.size() >= 6) && ((quote == '"') || (quote == '\'')) ); + + // Extract the name part. + + ++partBegin; // Skip the opening '['. + if ( *partBegin == '?' ) ++partBegin; + for ( partEnd = partBegin+1; *partEnd != '='; ++partEnd ) {}; + + nameStr->assign ( partBegin, (partEnd - partBegin) ); + + // Extract the value part, reducing doubled quotes. + + XMP_Assert ( *(partEnd+1) == quote ); + + partBegin = partEnd + 2; + valueStr->erase(); + valueStr->reserve ( valueEnd - partBegin ); // Maximum length, don't optimize doubled quotes. + + for ( partEnd = partBegin; partEnd < valueEnd; ++partEnd ) { + if ( (*partEnd == quote) && (*(partEnd+1) == quote) ) { + ++partEnd; + valueStr->append ( partBegin, (partEnd - partBegin) ); + partBegin = partEnd+1; // ! Loop will increment partEnd again. + } + } + + valueStr->append ( partBegin, (partEnd - partBegin) ); // ! The loop does not add the last part. + +} // SplitNameAndValue + +// ------------------------------------------------------------------------------------------------- +// LookupQualSelector +// ------------------ +// +// [?qualName="value"] An element in an array, chosen by a qualifier value. +// +// Note that we don't create implicit nodes for qualifier selectors, so no CreateNodes parameter. + +static XMP_Index +LookupQualSelector ( XMP_Node * arrayNode, const XMP_VarString & qualName, XMP_VarString & qualValue ) +{ + XMP_Index index; + + if ( qualName == "xml:lang" ) { + + // *** Should check that the value is legit RFC 1766/3066. + NormalizeLangValue ( &qualValue ); + index = LookupLangItem ( arrayNode, qualValue ) ; + + } else { + + XMP_Index itemLim; + for ( index = 0, itemLim = arrayNode->children.size(); index != itemLim; ++index ) { + + const XMP_Node * currItem = arrayNode->children[index]; + XMP_Assert ( currItem->parent == arrayNode ); + + size_t q, qualLim; + for ( q = 0, qualLim = currItem->qualifiers.size(); q != qualLim; ++q ) { + const XMP_Node * currQual = currItem->qualifiers[q]; + XMP_Assert ( currQual->parent == currItem ); + if ( currQual->name != qualName ) continue; + if ( currQual->value == qualValue ) break; // Exit qual loop. + } + if ( q != qualLim ) break; // Exit child loop, found an item with a matching qualifier. + + } + if ( index == itemLim ) index = -1; + + } + + return index; + +} // LookupQualSelector + +// ------------------------------------------------------------------------------------------------- +// FollowXPathStep +// --------------- +// +// After processing by ExpandXPath, a step can be of these forms: +// qualName A top level property or struct field. +// [index] An element of an array. +// [last()] The last element of an array. +// [qualName="value"] An element in an array of structs, chosen by a field value. +// [?qualName="value"] An element in an array, chosen by a qualifier value. +// ?qualName A general qualifier. +// +// Find the appropriate child node, resolving aliases, and optionally creating nodes. + +static XMP_Node * +FollowXPathStep ( XMP_Node * parentNode, + const XMP_ExpandedXPath & fullPath, + size_t stepNum, + bool createNodes, + XMP_NodePtrPos * ptrPos, + bool aliasedArrayItem = false ) +{ + XMP_Node * nextNode = 0; + const XPathStepInfo & nextStep = fullPath[stepNum]; + XMP_Index index = 0; + XMP_OptionBits stepKind = nextStep.options & kXMP_StepKindMask; + + XMP_Assert ( (kXMP_StructFieldStep <= stepKind) && (stepKind <= kXMP_FieldSelectorStep) ); + + if ( stepKind == kXMP_StructFieldStep ) { + + nextNode = FindChildNode ( parentNode, nextStep.step.c_str(), createNodes, ptrPos ); + + } else if ( stepKind == kXMP_QualifierStep ) { + + XMP_StringPtr qualStep = nextStep.step.c_str(); + XMP_Assert ( *qualStep == '?' ); + ++qualStep; + nextNode = FindQualifierNode ( parentNode, qualStep, createNodes, ptrPos ); + + } else { + + // This is an array indexing step. First get the index, then get the node. + + if ( ! (parentNode->options & kXMP_PropValueIsArray) ) { + XMP_Throw ( "Indexing applied to non-array", kXMPErr_BadXPath ); + } + + if ( stepKind == kXMP_ArrayIndexStep ) { + index = FindIndexedItem ( parentNode, nextStep.step, createNodes ); + } else if ( stepKind == kXMP_ArrayLastStep ) { + index = parentNode->children.size() - 1; + } else if ( stepKind == kXMP_FieldSelectorStep ) { + XMP_VarString fieldName, fieldValue; + SplitNameAndValue ( nextStep.step, &fieldName, &fieldValue ); + index = LookupFieldSelector ( parentNode, fieldName.c_str(), fieldValue.c_str() ); + } else if ( stepKind == kXMP_QualSelectorStep ) { + XMP_VarString qualName, qualValue; + SplitNameAndValue ( nextStep.step, &qualName, &qualValue ); + index = LookupQualSelector ( parentNode, qualName, qualValue ); + } else { + XMP_Throw ( "Unknown array indexing step in FollowXPathStep", kXMPErr_InternalFailure ); + } + + if ( (0 <= index) && (index <= (XMP_Index)parentNode->children.size()) ) nextNode = parentNode->children[index]; + + if ( (index == -1) && createNodes && aliasedArrayItem && (stepKind == kXMP_QualSelectorStep) ) { + + // An ugly special case without an obvious better place to be. We have an alias to the + // x-default item of an alt-text array. A simple reference via SetProperty must create + // the x-default item if it does not yet exist. + + XMP_Assert ( parentNode->options & kXMP_PropArrayIsAltText ); + XMP_Assert ( (stepNum == 2) && (nextStep.step == "[?xml:lang=\"x-default\"]") ); + + nextNode = new XMP_Node ( parentNode, kXMP_ArrayItemName, + (kXMP_PropHasQualifiers | kXMP_PropHasLang | kXMP_NewImplicitNode) ); + + XMP_Node * langQual = new XMP_Node ( nextNode, "xml:lang", "x-default", kXMP_PropIsQualifier ); + nextNode->qualifiers.push_back ( langQual ); + + if ( parentNode->children.empty() ) { + parentNode->children.push_back ( nextNode ); + } else { + parentNode->children.insert ( parentNode->children.begin(), nextNode ); + } + + index = 0; // ! C-style index! The x-default item is always first. + + } + + if ( (nextNode != 0) && (ptrPos != 0) ) *ptrPos = parentNode->children.begin() + index; + + } + + if ( (nextNode != 0) && (nextNode->options & kXMP_NewImplicitNode) ) { + nextNode->options |= (nextStep.options & kXMP_PropArrayFormMask); + } + + XMP_Assert ( (ptrPos == 0) || (nextNode == 0) || (nextNode == **ptrPos) ); + XMP_Assert ( (nextNode != 0) || (! createNodes) ); + return nextNode; + +} // FollowXPathStep + +// ------------------------------------------------------------------------------------------------- +// CheckImplicitStruct +// ------------------- + +static inline void +CheckImplicitStruct ( XMP_Node * node, + const XMP_ExpandedXPath & expandedXPath, + size_t stepNum, + size_t stepLim ) +{ + + if ( (stepNum < stepLim) && + ((node->options & kXMP_PropCompositeMask) == 0) && + (GetStepKind ( expandedXPath[stepNum].options ) == kXMP_StructFieldStep) ) { + + node->options |= kXMP_PropValueIsStruct; + + } + +} // CheckImplicitStruct + +// ------------------------------------------------------------------------------------------------- +// DeleteSubtree +// ------------- + +void +DeleteSubtree ( XMP_NodePtrPos rootNodePos ) +{ + XMP_Node * rootNode = *rootNodePos; + XMP_Node * rootParent = rootNode->parent; + + if ( ! (rootNode->options & kXMP_PropIsQualifier) ) { + + rootParent->children.erase ( rootNodePos ); + + } else { + + rootParent->qualifiers.erase ( rootNodePos ); + + XMP_Assert ( rootParent->options & kXMP_PropHasQualifiers); + if ( rootParent->qualifiers.empty() ) rootParent->options ^= kXMP_PropHasQualifiers; + + if ( rootNode->name == "xml:lang" ) { + XMP_Assert ( rootParent->options & kXMP_PropHasLang); + rootParent->options ^= kXMP_PropHasLang; + } else if ( rootNode->name == "rdf:type" ) { + XMP_Assert ( rootParent->options & kXMP_PropHasType); + rootParent->options ^= kXMP_PropHasType; + } + + } + + delete rootNode; + +} // DeleteSubtree + +// ================================================================================================= +// ================================================================================================= + +// ================================================================================================= +// VerifySetOptions +// ================ +// +// Normalize and verify the option flags for SetProperty and similar functions. The allowed options +// here are just those that apply to the property, that would be kept in the XMP_Node. Others that +// affect the selection of the node or other processing must be removed by now. These are: +// kXMP_InsertBeforeItem +// kXMP_InsertAfterItem +// kXMP_KeepQualifiers +// kXMPUtil_AllowCommas + +enum { + kXMP_AllSetOptionsMask = (kXMP_PropValueIsURI | + kXMP_PropValueIsStruct | + kXMP_PropValueIsArray | + kXMP_PropArrayIsOrdered | + kXMP_PropArrayIsAlternate | + kXMP_PropArrayIsAltText | + kXMP_DeleteExisting) +}; + +XMP_OptionBits +VerifySetOptions ( XMP_OptionBits options, XMP_StringPtr propValue ) +{ + + if ( options & kXMP_PropArrayIsAltText ) options |= kXMP_PropArrayIsAlternate; + if ( options & kXMP_PropArrayIsAlternate ) options |= kXMP_PropArrayIsOrdered; + if ( options & kXMP_PropArrayIsOrdered ) options |= kXMP_PropValueIsArray; + + if ( options & ~kXMP_AllSetOptionsMask ) { + XMP_Throw ( "Unrecognized option flags", kXMPErr_BadOptions ); + } + + if ( (options & kXMP_PropValueIsStruct) && (options & kXMP_PropValueIsArray) ) { + XMP_Throw ( "IsStruct and IsArray options are mutually exclusive", kXMPErr_BadOptions ); + } + + if ( (options & kXMP_PropValueOptionsMask) && (options & kXMP_PropCompositeMask) ) { + XMP_Throw ( "Structs and arrays can't have \"value\" options", kXMPErr_BadOptions ); + } + + if ( (propValue != 0) && (options & kXMP_PropCompositeMask) ) { + XMP_Throw ( "Structs and arrays can't have string values", kXMPErr_BadOptions ); + } + + return options; + +} // VerifySetOptions + +// ================================================================================================= +// ComposeXPath +// ============ +// +// Compose the canonical string form of an expanded XPath expression. + +extern void +ComposeXPath ( const XMP_ExpandedXPath & expandedXPath, + XMP_VarString * stringXPath ) +{ + *stringXPath = expandedXPath[kRootPropStep].step; + + for ( size_t index = kRootPropStep+1; index < expandedXPath.size(); ++index ) { + const XPathStepInfo & currStep = expandedXPath[index]; + + switch ( currStep.options & kXMP_StepKindMask ) { + + case kXMP_StructFieldStep : + case kXMP_QualifierStep : + *stringXPath += '/'; + *stringXPath += currStep.step; + break; + + case kXMP_ArrayIndexStep : + case kXMP_ArrayLastStep : + case kXMP_QualSelectorStep : + case kXMP_FieldSelectorStep : + *stringXPath += currStep.step; + break; + + default: + XMP_Throw ( "Unexpected", kXMPErr_InternalFailure ); + + } + + } + +} // ComposeXPath + +// ================================================================================================= +// ExpandXPath +// =========== +// +// Split an XPath expression apart at the conceptual steps, adding the root namespace prefix to the +// first property component. The schema URI is put in the first (0th) slot in the expanded XPath. +// Check if the top level component is an alias, but don't resolve it. +// +// In the most verbose case steps are separated by '/', and each step can be of these forms: +// +// qualName A top level property or struct field. +// *[index] An element of an array. +// *[last()] The last element of an array. +// *[fieldName="value"] An element in an array of structs, chosen by a field value. +// *[@xml:lang="value"] An element in an alt-text array, chosen by the xml:lang qualifier. +// *[?qualName="value"] An element in an array, chosen by a qualifier value. +// @xml:lang An xml:lang qualifier. +// ?qualName A general qualifier. +// +// The logic is complicated though by shorthand for arrays, the separating '/' and leading '*' +// are optional. These are all equivalent: array/*[2] array/[2] array*[2] array[2] +// All of these are broken into the 2 steps "array" and "[2]". +// +// The value portion in the array selector forms is a string quoted by ''' or '"'. The value +// may contain any character including a doubled quoting character. The value may be empty. +// +// The syntax isn't checked, but an XML name begins with a letter or '_', and contains letters, +// digits, '.', '-', '_', and a bunch of special non-ASCII Unicode characters. An XML qualified +// name is a pair of names separated by a colon. + +void +ExpandXPath ( XMP_StringPtr schemaNS, + XMP_StringPtr propPath, + XMP_ExpandedXPath * expandedXPath ) +{ + XMP_Assert ( (schemaNS != 0) && (propPath != 0) && (*propPath != 0) && (expandedXPath != 0) ); + + XMP_StringPtr stepBegin, stepEnd; + XMP_StringPtr qualName, nameEnd; + XMP_VarString currStep; + + qualName = nameEnd = NULL; + size_t resCount = 2; // Guess at the number of steps. At least 2, plus 1 for each '/' or '['. + for ( stepEnd = propPath; *stepEnd != 0; ++stepEnd ) { + if ( (*stepEnd == '/') || (*stepEnd == '[') ) ++resCount; + } + + expandedXPath->clear(); + expandedXPath->reserve ( resCount ); + + // ------------------------------------------------------------------------------------------- + // Pull out the first component and do some special processing on it: add the schema namespace + // prefix and see if it is an alias. The start must be a qualName. + + stepBegin = propPath; + stepEnd = stepBegin; + while ( (*stepEnd != 0) && (*stepEnd != '/') && (*stepEnd != '[') && (*stepEnd != '*') ) ++stepEnd; + if ( stepEnd == stepBegin ) XMP_Throw ( "Empty initial XPath step", kXMPErr_BadXPath ); + currStep.assign ( stepBegin, (stepEnd - stepBegin) ); + + VerifyXPathRoot ( schemaNS, currStep.c_str(), expandedXPath ); + + XMP_OptionBits stepFlags = kXMP_StructFieldStep; + if ( sRegisteredAliasMap->find ( (*expandedXPath)[kRootPropStep].step ) != sRegisteredAliasMap->end() ) { + stepFlags |= kXMP_StepIsAlias; + } + (*expandedXPath)[kRootPropStep].options |= stepFlags; + + // ----------------------------------------------------- + // Now continue to process the rest of the XPath string. + + while ( *stepEnd != 0 ) { + + stepBegin = stepEnd; + if ( *stepBegin == '/' ) ++stepBegin; + if ( *stepBegin == '*' ) { + ++stepBegin; + if ( *stepBegin != '[' ) XMP_Throw ( "Missing '[' after '*'", kXMPErr_BadXPath ); + } + stepEnd = stepBegin; + + if ( *stepBegin != '[' ) { + + // A struct field or qualifier. + qualName = stepBegin; + while ( (*stepEnd != 0) && (*stepEnd != '/') && (*stepEnd != '[') && (*stepEnd != '*') ) ++stepEnd; + nameEnd = stepEnd; + stepFlags = kXMP_StructFieldStep; // ! Touch up later, also changing '@' to '?'. + + } else { + + // One of the array forms. + + ++stepEnd; // Look at the character after the leading '['. + + if ( ('0' <= *stepEnd) && (*stepEnd <= '9') ) { + + // A numeric (decimal integer) array index. + while ( (*stepEnd != 0) && ('0' <= *stepEnd) && (*stepEnd <= '9') ) ++stepEnd; + if ( *stepEnd != ']' ) XMP_Throw ( "Missing ']' for integer array index", kXMPErr_BadXPath ); + stepFlags = kXMP_ArrayIndexStep; + + } else { + + // Could be "[last()]" or one of the selector forms. Find the ']' or '='. + + while ( (*stepEnd != 0) && (*stepEnd != ']') && (*stepEnd != '=') ) ++stepEnd; + if ( *stepEnd == 0 ) XMP_Throw ( "Missing ']' or '=' for array index", kXMPErr_BadXPath ); + + if ( *stepEnd == ']' ) { + + if ( strncmp ( "[last()", stepBegin, (stepEnd - stepBegin) ) != 0 ) { + XMP_Throw ( "Invalid non-numeric array index", kXMPErr_BadXPath ); + } + stepFlags = kXMP_ArrayLastStep; + + } else { + + qualName = stepBegin+1; + nameEnd = stepEnd; + ++stepEnd; // Absorb the '=', remember the quote. + const char quote = *stepEnd; + if ( (quote != '\'') && (quote != '"') ) { + XMP_Throw ( "Invalid quote in array selector", kXMPErr_BadXPath ); + } + + ++stepEnd; // Absorb the leading quote. + while ( *stepEnd != 0 ) { + if ( *stepEnd == quote ) { + if ( *(stepEnd+1) != quote ) break; + ++stepEnd; + } + ++stepEnd; + } + if ( *stepEnd == 0 ) { + XMP_Throw ( "No terminating quote for array selector", kXMPErr_BadXPath ); + } + ++stepEnd; // Absorb the trailing quote. + + stepFlags = kXMP_FieldSelectorStep; // ! Touch up later, also changing '@' to '?'. + + } + + } + + if ( *stepEnd != ']' ) XMP_Throw ( "Missing ']' for array index", kXMPErr_BadXPath ); + ++stepEnd; + + } + + if ( stepEnd == stepBegin ) XMP_Throw ( "Empty XPath step", kXMPErr_BadXPath ); + currStep.assign ( stepBegin, (stepEnd - stepBegin) ); + + if ( GetStepKind ( stepFlags ) == kXMP_StructFieldStep ) { + + if ( currStep[0] == '@' ) { + currStep[0] = '?'; + if ( currStep != "?xml:lang" ) XMP_Throw ( "Only xml:lang allowed with '@'", kXMPErr_BadXPath ); + } + if ( currStep[0] == '?' ) { + ++qualName; + stepFlags = kXMP_QualifierStep; + } + VerifyQualName ( qualName, nameEnd ); + + } else if ( GetStepKind ( stepFlags ) == kXMP_FieldSelectorStep ) { + + if ( currStep[1] == '@' ) { + currStep[1] = '?'; + if ( strncmp ( currStep.c_str(), "[?xml:lang=", 11 ) != 0 ) { + XMP_Throw ( "Only xml:lang allowed with '@'", kXMPErr_BadXPath ); + } + } + if ( currStep[1] == '?' ) { + ++qualName; + stepFlags = kXMP_QualSelectorStep; + } + VerifyQualName ( qualName, nameEnd ); + + } + + expandedXPath->push_back ( XPathStepInfo ( currStep, stepFlags ) ); + + } + +} // ExpandXPath + +// ================================================================================================= +// FindSchemaNode +// ============== +// +// Find or create a schema node. Returns a pointer to the node, and optionally an iterator for the +// node's position in the top level vector of schema nodes. The iterator is unchanged if no schema +// node (null) is returned. + +XMP_Node * +FindSchemaNode ( XMP_Node * xmpTree, + XMP_StringPtr nsURI, + bool createNodes, + XMP_NodePtrPos * ptrPos /* = 0 */ ) +{ + XMP_Node * schemaNode = 0; + + XMP_Assert ( xmpTree->parent == 0 ); + + for ( size_t schemaNum = 0, schemaLim = xmpTree->children.size(); schemaNum != schemaLim; ++schemaNum ) { + XMP_Node * currSchema = xmpTree->children[schemaNum]; + XMP_Assert ( currSchema->parent == xmpTree ); + if ( currSchema->name == nsURI ) { + schemaNode = currSchema; + if ( ptrPos != 0 ) *ptrPos = xmpTree->children.begin() + schemaNum; + break; + } + } + + if ( (schemaNode == 0) && createNodes ) { + + schemaNode = new XMP_Node ( xmpTree, nsURI, (kXMP_SchemaNode | kXMP_NewImplicitNode) ); + + try { + XMP_StringPtr prefixPtr; + XMP_StringLen prefixLen; +#if XMP_DebugBuild + bool found = +#endif + XMPMeta::GetNamespacePrefix ( nsURI, &prefixPtr, &prefixLen ); // *** Use map directly? + XMP_Assert ( found ); + schemaNode->value.assign ( prefixPtr, prefixLen ); + } catch (...) { // Don't leak schemaNode in case of an exception before adding it to the children vector. + delete schemaNode; + throw; + } + + xmpTree->children.push_back ( schemaNode ); + if ( ptrPos != 0 ) *ptrPos = xmpTree->children.end() - 1; + + #if 0 // *** XMP_DebugBuild + schemaNode->_valuePtr = schemaNode->value.c_str(); + #endif + + } + + XMP_Assert ( (ptrPos == 0) || (schemaNode == 0) || (schemaNode == **ptrPos) ); + XMP_Assert ( (schemaNode != 0) || (! createNodes) ); + return schemaNode; + +} // FindSchemaNode + +// ================================================================================================= +// FindChildNode +// ============= +// +// Find or create a child node under a given parent node. Returns a pointer to the child node, and +// optionally an iterator for the node's position in the parent's vector of children. The iterator +// is unchanged if no child node (null) is returned. + +XMP_Node * +FindChildNode ( XMP_Node * parent, + XMP_StringPtr childName, + bool createNodes, + XMP_NodePtrPos * ptrPos /* = 0 */ ) +{ + XMP_Node * childNode = 0; + + if ( ! (parent->options & (kXMP_SchemaNode | kXMP_PropValueIsStruct)) ) { + if ( ! (parent->options & kXMP_NewImplicitNode) ) { + XMP_Throw ( "Named children only allowed for schemas and structs", kXMPErr_BadXPath ); + } + if ( parent->options & kXMP_PropValueIsArray ) { + XMP_Throw ( "Named children not allowed for arrays", kXMPErr_BadXPath ); + } + if ( ! createNodes ) { // *** Should be assert? If !createNodes, why is the parent a new implicit node? + XMP_Throw ( "Parent is new implicit node, but createNodes is false", kXMPErr_InternalFailure ); + } + parent->options |= kXMP_PropValueIsStruct; + } + + for ( size_t childNum = 0, childLim = parent->children.size(); childNum != childLim; ++childNum ) { + XMP_Node * currChild = parent->children[childNum]; + XMP_Assert ( currChild->parent == parent ); + if ( currChild->name == childName ) { + childNode = currChild; + if ( ptrPos != 0 ) *ptrPos = parent->children.begin() + childNum; + break; + } + } + + if ( (childNode == 0) && createNodes ) { + childNode = new XMP_Node ( parent, childName, kXMP_NewImplicitNode ); + parent->children.push_back ( childNode ); + if ( ptrPos != 0 ) *ptrPos = parent->children.end() - 1; + } + + XMP_Assert ( (ptrPos == 0) || (childNode == 0) || (childNode == **ptrPos) ); + XMP_Assert ( (childNode != 0) || (! createNodes) ); + return childNode; + +} // FindChildNode + +// ================================================================================================= +// FindQualifierNode +// ================= +// +// Find or create a qualifier node under a given parent node. Returns a pointer to the qualifier node, +// and optionally an iterator for the node's position in the parent's vector of qualifiers. The iterator +// is unchanged if no qualifier node (null) is returned. +// +// ! On entry, the qualName parameter must not have the leading '?' from the XPath step. + +XMP_Node * +FindQualifierNode ( XMP_Node * parent, + XMP_StringPtr qualName, + bool createNodes, + XMP_NodePtrPos * ptrPos /* = 0 */ ) // *** Require ptrPos internally & remove checks? +{ + XMP_Node * qualNode = 0; + + XMP_Assert ( *qualName != '?' ); + + for ( size_t qualNum = 0, qualLim = parent->qualifiers.size(); qualNum != qualLim; ++qualNum ) { + XMP_Node * currQual = parent->qualifiers[qualNum]; + XMP_Assert ( currQual->parent == parent ); + if ( currQual->name == qualName ) { + qualNode = currQual; + if ( ptrPos != 0 ) *ptrPos = parent->qualifiers.begin() + qualNum; + break; + } + } + + if ( (qualNode == 0) && createNodes ) { + + qualNode = new XMP_Node ( parent, qualName, (kXMP_PropIsQualifier | kXMP_NewImplicitNode) ); + parent->options |= kXMP_PropHasQualifiers; + + const bool isLang = XMP_LitMatch ( qualName, "xml:lang" ); + const bool isType = XMP_LitMatch ( qualName, "rdf:type" ); + const bool isSpecial = isLang | isType; + + if ( isLang ) { + parent->options |= kXMP_PropHasLang; + } else if ( isType ) { + parent->options |= kXMP_PropHasType; + } + + if ( parent->qualifiers.empty() || (! isSpecial) ) { + parent->qualifiers.push_back ( qualNode ); + if ( ptrPos != 0 ) *ptrPos = parent->qualifiers.end() - 1; + } else { + XMP_NodePtrPos insertPos = parent->qualifiers.begin(); // ! Lang goes first, type after. + if ( isType && (parent->options & kXMP_PropHasLang) ) ++insertPos; // *** Does insert at end() work? + insertPos = parent->qualifiers.insert ( insertPos, qualNode ); + if ( ptrPos != 0 ) *ptrPos = insertPos; + } + + } + + XMP_Assert ( (ptrPos == 0) || (qualNode == 0) || (qualNode == **ptrPos) ); + XMP_Assert ( (qualNode != 0) || (! createNodes) ); + return qualNode; + +} // FindQualifierNode + +// ================================================================================================= +// LookupFieldSelector +// =================== +// +// [fieldName="value"] An element in an array of structs, chosen by a field value. +// +// Note that we don't create implicit nodes for field selectors, so no CreateNodes parameter. + +XMP_Index +LookupFieldSelector ( const XMP_Node * arrayNode, XMP_StringPtr fieldName, XMP_StringPtr fieldValue ) +{ + XMP_Index index, itemLim; + + for ( index = 0, itemLim = arrayNode->children.size(); index != itemLim; ++index ) { + + const XMP_Node * currItem = arrayNode->children[index]; + XMP_Assert ( currItem->parent == arrayNode ); + + if ( ! (currItem->options & kXMP_PropValueIsStruct) ) { + XMP_Throw ( "Field selector must be used on array of struct", kXMPErr_BadXPath ); + } + + size_t f, fieldLim; + for ( f = 0, fieldLim = currItem->children.size(); f != fieldLim; ++f ) { + const XMP_Node * currField = currItem->children[f]; + XMP_Assert ( currField->parent == currItem ); + if ( currField->name != fieldName ) continue; + if ( currField->value == fieldValue ) break; // Exit qual loop. + } + if ( f != fieldLim ) break; // Exit child loop, found an item with a matching qualifier. + + } + + if ( index == itemLim ) index = -1; + return index; + +} // LookupFieldSelector + +// ================================================================================================= +// LookupLangItem +// ============== +// +// ! Assumes that the language value is already normalized. + +XMP_Index +LookupLangItem ( const XMP_Node * arrayNode, XMP_VarString & lang ) +{ + if ( ! (arrayNode->options & kXMP_PropValueIsArray) ) { // *** Check for alt-text? + XMP_Throw ( "Language item must be used on array", kXMPErr_BadXPath ); + } + + XMP_Index index = 0; + XMP_Index itemLim = arrayNode->children.size(); + + for ( ; index != itemLim; ++index ) { + const XMP_Node * currItem = arrayNode->children[index]; + XMP_Assert ( currItem->parent == arrayNode ); + if ( currItem->qualifiers.empty() || (currItem->qualifiers[0]->name != "xml:lang") ) continue; + if ( currItem->qualifiers[0]->value == lang ) break; + } + + if ( index == itemLim ) index = -1; + return index; + +} // LookupLangItem + +// ================================================================================================= +// FindNode +// ======== +// +// Follow an expanded path expression to find or create a node. Returns a pointer to the node, and +// optionally an iterator for the node's position in the parent's vector of children or qualifiers. +// The iterator is unchanged if no child node (null) is returned. + +XMP_Node * +FindNode ( XMP_Node * xmpTree, + const XMP_ExpandedXPath & expandedXPath, + bool createNodes, + XMP_OptionBits leafOptions /* = 0 */, + XMP_NodePtrPos * ptrPos /* = 0 */ ) +{ + XMP_Node * currNode = 0; + XMP_NodePtrPos currPos; + XMP_NodePtrPos newSubPos; // Root of implicitly created subtree. Valid only if leaf is new. + bool leafIsNew = false; + + XMP_Assert ( (leafOptions == 0) || createNodes ); + + if ( expandedXPath.empty() ) XMP_Throw ( "Empty XPath", kXMPErr_BadXPath ); + + size_t stepNum = 1; // By default start calling FollowXPathStep for the top level property step. + size_t stepLim = expandedXPath.size(); + + // The start of processing deals with the schema node and top level alias. If the top level step + // is not an alias, lookup the expanded path's schema URI. Otherwise, lookup the expanded path + // for the actual. While tempting, don't substitute the actual's path into the local one, don't + // risk messing with the caller's use of that. Also don't call FindNode recursively, we need to + // keep track of the root of the implicitly created subtree as we move down the path. + + if ( ! (expandedXPath[kRootPropStep].options & kXMP_StepIsAlias) ) { + + currNode = FindSchemaNode ( xmpTree, expandedXPath[kSchemaStep].step.c_str(), createNodes, &currPos ); + if ( currNode == 0 ) return 0; + + if ( currNode->options & kXMP_NewImplicitNode ) { + currNode->options ^= kXMP_NewImplicitNode; // Clear the implicit node bit. + if ( ! leafIsNew ) newSubPos = currPos; // Save the top most implicit node. + leafIsNew = true; // If any parent is new, the leaf will be new also. + } + + } else { + + stepNum = 2; // ! Continue processing the original path at the second level step. + + XMP_AliasMapPos aliasPos = sRegisteredAliasMap->find ( expandedXPath[kRootPropStep].step ); + XMP_Assert ( aliasPos != sRegisteredAliasMap->end() ); + + currNode = FindSchemaNode ( xmpTree, aliasPos->second[kSchemaStep].step.c_str(), createNodes, &currPos ); + if ( currNode == 0 ) goto EXIT; + if ( currNode->options & kXMP_NewImplicitNode ) { + currNode->options ^= kXMP_NewImplicitNode; // Clear the implicit node bit. + if ( ! leafIsNew ) newSubPos = currPos; // Save the top most implicit node. + leafIsNew = true; // If any parent is new, the leaf will be new also. + } + + currNode = FollowXPathStep ( currNode, aliasPos->second, 1, createNodes, &currPos ); + if ( currNode == 0 ) goto EXIT; + if ( currNode->options & kXMP_NewImplicitNode ) { + currNode->options ^= kXMP_NewImplicitNode; // Clear the implicit node bit. + CheckImplicitStruct ( currNode, expandedXPath, 2, stepLim ); + if ( ! leafIsNew ) newSubPos = currPos; // Save the top most implicit node. + leafIsNew = true; // If any parent is new, the leaf will be new also. + } + + XMP_OptionBits arrayForm = aliasPos->second[kRootPropStep].options & kXMP_PropArrayFormMask; + XMP_Assert ( (arrayForm == 0) || (arrayForm & kXMP_PropValueIsArray) ); + XMP_Assert ( (arrayForm == 0) ? (aliasPos->second.size() == 2) : (aliasPos->second.size() == 3) ); + + if ( arrayForm != 0 ) { + currNode = FollowXPathStep ( currNode, aliasPos->second, 2, createNodes, &currPos, true ); + if ( currNode == 0 ) goto EXIT; + if ( currNode->options & kXMP_NewImplicitNode ) { + currNode->options ^= kXMP_NewImplicitNode; // Clear the implicit node bit. + CheckImplicitStruct ( currNode, expandedXPath, 2, stepLim ); + if ( ! leafIsNew ) newSubPos = currPos; // Save the top most implicit node. + leafIsNew = true; // If any parent is new, the leaf will be new also. + } + } + + } + + // Now follow the remaining steps of the original XPath. + + // *** ??? Change all the num/lim loops back to numoptions & kXMP_NewImplicitNode ) { + currNode->options ^= kXMP_NewImplicitNode; // Clear the implicit node bit. + CheckImplicitStruct ( currNode, expandedXPath, stepNum+1, stepLim ); + if ( ! leafIsNew ) newSubPos = currPos; // Save the top most implicit node. + leafIsNew = true; // If any parent is new, the leaf will be new also. + } + } + } catch ( ... ) { + if ( leafIsNew ) DeleteSubtree ( newSubPos ); + throw; + } + + // Done. Delete the implicitly created subtree if the eventual node was not found. + +EXIT: + + XMP_Assert ( (currNode == 0) || (currNode == *currPos) ); + XMP_Assert ( (currNode != 0) || (! createNodes) ); + + if ( leafIsNew ) { + if ( currNode != 0 ) { + currNode->options |= leafOptions; + } else { + DeleteSubtree ( newSubPos ); + } + } + + if ( (currNode != 0) && (ptrPos != 0) ) *ptrPos = currPos; + return currNode; + +} // FindNode + +// ================================================================================================= +// CloneOffspring +// ============== + +void +CloneOffspring ( const XMP_Node * origParent, XMP_Node * cloneParent, bool skipEmpty /* = false */ ) +{ + size_t qualCount = origParent->qualifiers.size(); + size_t childCount = origParent->children.size(); + + if ( qualCount > 0 ) { + + cloneParent->qualifiers.reserve ( qualCount ); + + for ( size_t qualNum = 0, qualLim = qualCount; qualNum != qualLim; ++qualNum ) { + const XMP_Node * origQual = origParent->qualifiers[qualNum]; + if ( skipEmpty && origQual->value.empty() && origQual->children.empty() ) continue; + XMP_Node * cloneQual = new XMP_Node ( cloneParent, origQual->name, origQual->value, origQual->options ); + CloneOffspring ( origQual, cloneQual, skipEmpty ); + if ( skipEmpty && cloneQual->value.empty() && cloneQual->children.empty() ) { + // Check again, might have had an array or struct with all empty children. + delete cloneQual; + continue; + } + cloneParent->qualifiers.push_back ( cloneQual ); + } + + } + + if ( childCount > 0 ) { + + cloneParent->children.reserve ( childCount ); + + for ( size_t childNum = 0, childLim = childCount; childNum != childLim; ++childNum ) { + const XMP_Node * origChild = origParent->children[childNum]; + if ( skipEmpty && origChild->value.empty() && origChild->children.empty() ) continue; + XMP_Node * cloneChild = new XMP_Node ( cloneParent, origChild->name, origChild->value, origChild->options ); + CloneOffspring ( origChild, cloneChild, skipEmpty ); + if ( skipEmpty && cloneChild->value.empty() && cloneChild->children.empty() ) { + // Check again, might have had an array or struct with all empty children. + delete cloneChild; + continue; + } + cloneParent->children.push_back ( cloneChild ); + } + + } + +} // CloneOffspring + +// ================================================================================================= +// CloneSubtree +// ============ + +XMP_Node * +CloneSubtree ( const XMP_Node * origRoot, XMP_Node * cloneParent, bool skipEmpty /* = false */ ) +{ + #if XMP_DebugBuild + if ( cloneParent->parent == 0 ) { + XMP_Assert ( origRoot->options & kXMP_SchemaNode ); + XMP_Assert ( FindConstSchema ( cloneParent, origRoot->name.c_str() ) == 0 ); + } else { + XMP_Assert ( ! (origRoot->options & kXMP_SchemaNode) ); + if ( cloneParent->options & kXMP_PropValueIsStruct ) { // Might be an array. + XMP_Assert ( FindConstChild ( cloneParent, origRoot->name.c_str() ) == 0 ); + } + } + #endif + + XMP_Node * cloneRoot = new XMP_Node ( cloneParent, origRoot->name, origRoot->value, origRoot->options ); + CloneOffspring ( origRoot, cloneRoot, skipEmpty ) ; + + if ( skipEmpty && cloneRoot->value.empty() && cloneRoot->children.empty() ) { + // ! Can't do earlier, CloneOffspring might be skipping empty children. + delete cloneRoot; + return 0; + } + + cloneParent->children.push_back ( cloneRoot ); + return cloneRoot; + +} // CloneSubtree + +// ================================================================================================= +// 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 ( const XMP_Node & leftNode, const XMP_Node & rightNode ) +{ + // Don't compare the names here, we want to allow the outermost roots to have different names. + if ( (leftNode.value != rightNode.value) || + (leftNode.options != rightNode.options) || + (leftNode.children.size() != rightNode.children.size()) || + (leftNode.qualifiers.size() != rightNode.qualifiers.size()) ) return false; + + // Compare the qualifiers, allowing them to be out of order. + for ( size_t qualNum = 0, qualLim = leftNode.qualifiers.size(); qualNum != qualLim; ++qualNum ) { + const XMP_Node * leftQual = leftNode.qualifiers[qualNum]; + const XMP_Node * rightQual = FindConstQualifier ( &rightNode, leftQual->name.c_str() ); + if ( (rightQual == 0) || (! CompareSubtrees ( *leftQual, *rightQual )) ) return false; + } + + if ( (leftNode.parent == 0) || (leftNode.options & (kXMP_SchemaNode | kXMP_PropValueIsStruct)) ) { + + // The parent node is a tree root, a schema, or a struct. + for ( size_t childNum = 0, childLim = leftNode.children.size(); childNum != childLim; ++childNum ) { + const XMP_Node * leftChild = leftNode.children[childNum]; + const XMP_Node * rightChild = FindConstChild ( &rightNode, leftChild->name.c_str() ); + if ( (rightChild == 0) || (! CompareSubtrees ( *leftChild, *rightChild )) ) return false; + } + + } else if ( leftNode.options & kXMP_PropArrayIsAltText ) { + + // The parent node is an alt-text array. + for ( size_t childNum = 0, childLim = leftNode.children.size(); childNum != childLim; ++childNum ) { + const XMP_Node * leftChild = leftNode.children[childNum]; + XMP_Assert ( (! leftChild->qualifiers.empty()) && (leftChild->qualifiers[0]->name == "xml:lang") ); + XMP_Index rightIndex = LookupLangItem ( &rightNode, leftChild->qualifiers[0]->value ); + if ( rightIndex == -1 ) return false; + const XMP_Node * rightChild = rightNode.children[rightIndex]; + if ( ! CompareSubtrees ( *leftChild, *rightChild ) ) return false; + } + + } else { + + // The parent must be simple or some other (not alt-text) kind of array. + XMP_Assert ( (! (leftNode.options & kXMP_PropCompositeMask)) || (leftNode.options & kXMP_PropValueIsArray) ); + for ( size_t childNum = 0, childLim = leftNode.children.size(); childNum != childLim; ++childNum ) { + const XMP_Node * leftChild = leftNode.children[childNum]; + const XMP_Node * rightChild = rightNode.children[childNum]; + if ( ! CompareSubtrees ( *leftChild, *rightChild ) ) return false; + } + + } + + return true; + +} // CompareSubtrees + +// ================================================================================================= +// DeleteEmptySchema +// ================= + +void +DeleteEmptySchema ( XMP_Node * schemaNode ) +{ + + if ( XMP_NodeIsSchema ( schemaNode->options ) && schemaNode->children.empty() ) { + + XMP_Node * xmpTree = schemaNode->parent; + + size_t schemaNum = 0; + size_t schemaLim = xmpTree->children.size(); + while ( (schemaNum < schemaLim) && (xmpTree->children[schemaNum] != schemaNode) ) ++schemaNum; + XMP_Assert ( schemaNum < schemaLim ); + + XMP_NodePtrPos schemaPos = xmpTree->children.begin() + schemaNum; + XMP_Assert ( *schemaPos == schemaNode ); + + xmpTree->children.erase ( schemaPos ); + delete schemaNode; + + } + +} // DeleteEmptySchema + +// ================================================================================================= +// NormalizeLangValue +// ================== +// +// Normalize an xml:lang value so that comparisons are effectively case insensitive as required by +// RFC 3066 (which superceeds RFC 1766). The normalization rules: +// +// - The primary subtag is lower case, the suggested practice of ISO 639. +// - All 2 letter secondary subtags are upper case, the suggested practice of ISO 3166. +// - All other subtags are lower case. + +void +NormalizeLangValue ( XMP_VarString * value ) +{ + char * tagStart; + char * tagEnd; + + // Find and process the primary subtag. + + tagStart = (char*) value->c_str(); + for ( tagEnd = tagStart; (*tagEnd != 0) && (*tagEnd != '-'); ++tagEnd ) { + if ( ('A' <= *tagEnd) && (*tagEnd <= 'Z') ) *tagEnd += 0x20; + } + + // Find and process the secondary subtag. + + tagStart = tagEnd; + if ( *tagStart == '-' ) ++tagStart; + for ( tagEnd = tagStart; (*tagEnd != 0) && (*tagEnd != '-'); ++tagEnd ) { + if ( ('A' <= *tagEnd) && (*tagEnd <= 'Z') ) *tagEnd += 0x20; + } + if ( tagEnd == tagStart+2 ) { + if ( ('a' <= *tagStart) && (*tagStart <= 'z') ) *tagStart -= 0x20; + ++tagStart; + if ( ('a' <= *tagStart) && (*tagStart <= 'z') ) *tagStart -= 0x20; + } + + // Find and process the remaining subtags. + + while ( true ) { + tagStart = tagEnd; + if ( *tagStart == '-' ) ++tagStart; + if ( *tagStart == 0 ) break; + for ( tagEnd = tagStart; (*tagEnd != 0) && (*tagEnd != '-'); ++tagEnd ) { + if ( ('A' <= *tagEnd) && (*tagEnd <= 'Z') ) *tagEnd += 0x20; + } + } + +} // NormalizeLangValue + +// ================================================================================================= +// NormalizeLangArray +// ================== +// +// Make sure the x-default item is first. Touch up "single value" arrays that have a default plus +// one real language. This case should have the same value for both items. Older Adobe apps were +// hardwired to only use the 'x-default' item, so we copy that value to the other item. + +void +NormalizeLangArray ( XMP_Node * array ) +{ + XMP_Assert ( XMP_ArrayIsAltText(array->options) ); + + size_t itemNum; + size_t itemLim = array->children.size(); + bool hasDefault = false; + + for ( itemNum = 0; itemNum < itemLim; ++itemNum ) { + + if ( array->children[itemNum]->qualifiers.empty() || + (array->children[itemNum]->qualifiers[0]->name != "xml:lang") ) { + XMP_Throw ( "AltText array items must have an xml:lang qualifier", kXMPErr_BadXMP ); + } + + if ( array->children[itemNum]->qualifiers[0]->value == "x-default" ) { + hasDefault = true; + break; + } + + } + + if ( hasDefault ) { + + if ( itemNum != 0 ) { + XMP_Node * temp = array->children[0]; + array->children[0] = array->children[itemNum]; + array->children[itemNum] = temp; + } + + if ( itemLim == 2 ) array->children[1]->value = array->children[0]->value; + + } + +} // NormalizeLangArray + +// ================================================================================================= +// DetectAltText +// ============= +// +// See if an array is an alt-text array. If so, make sure the x-default item is first. + +void +DetectAltText ( XMP_Node * xmpParent ) +{ + XMP_Assert ( XMP_ArrayIsAlternate(xmpParent->options) ); + + size_t itemNum, itemLim; + + for ( itemNum = 0, itemLim = xmpParent->children.size(); itemNum < itemLim; ++itemNum ) { + XMP_OptionBits currOptions = xmpParent->children[itemNum]->options; + if ( (currOptions & kXMP_PropCompositeMask) || (! (currOptions & kXMP_PropHasLang)) ) break; + } + + if ( (itemLim != 0) && (itemNum == itemLim) ) { + xmpParent->options |= kXMP_PropArrayIsAltText; + NormalizeLangArray ( xmpParent ); + } + +} // DetectAltText + +// ================================================================================================= +// SortNamedNodes +// ============== +// +// Sort the pointers in an XMP_NodeOffspring vector by name. + +static inline bool Compare ( const XMP_Node * left, const XMP_Node * right ) +{ + return (left->name < right->name); +} + +void +SortNamedNodes ( XMP_NodeOffspring & nodeVector ) +{ + sort ( nodeVector.begin(), nodeVector.end(), Compare ); +} // SortNamedNodes + +// ================================================================================================= diff --git a/XMPCore/source/XMPCore_Impl.hpp b/XMPCore/source/XMPCore_Impl.hpp new file mode 100644 index 0000000..c9fdec6 --- /dev/null +++ b/XMPCore/source/XMPCore_Impl.hpp @@ -0,0 +1,392 @@ +#ifndef __XMPCore_Impl_hpp__ +#define __XMPCore_Impl_hpp__ 1 + +// ================================================================================================= +// 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 "build/XMP_BuildInfo.h" +#include "source/XMP_LibUtils.hpp" + +// #include "XMPCore/source/XMPMeta.hpp" + +#include "public/include/client-glue/WXMP_Common.hpp" + +#include +#include +#include +#include +#include +#include + +#if XMP_WinBuild + #pragma warning ( disable : 4244 ) // possible loss of data (temporary for 64 bit builds) + #pragma warning ( disable : 4267 ) // possible loss of data (temporary for 64 bit builds) +#endif + +// ================================================================================================= +// Primary internal types + +class XMP_Node; +class XML_Node; +class XPathStepInfo; + +typedef XMP_Node * XMP_NodePtr; + +typedef std::vector XMP_NodeOffspring; +typedef XMP_NodeOffspring::iterator XMP_NodePtrPos; + +typedef XMP_VarString::iterator XMP_VarStringPos; +typedef XMP_VarString::const_iterator XMP_cVarStringPos; + +typedef std::vector < XPathStepInfo > XMP_ExpandedXPath; +typedef XMP_ExpandedXPath::iterator XMP_ExpandedXPathPos; +typedef XMP_ExpandedXPath::const_iterator XMP_cExpandedXPathPos; + +typedef std::map < XMP_VarString, XMP_ExpandedXPath > XMP_AliasMap; // Alias name to actual path. +typedef XMP_AliasMap::iterator XMP_AliasMapPos; +typedef XMP_AliasMap::const_iterator XMP_cAliasMapPos; + +// ================================================================================================= +// General global variables and macros + +extern XMP_Int32 sXMP_InitCount; + +extern XMP_NamespaceTable * sRegisteredNamespaces; + +extern XMP_AliasMap * sRegisteredAliasMap; + +#define WtoXMPMeta_Ref(xmpRef) (const XMPMeta &) (*((XMPMeta*)(xmpRef))) +#define WtoXMPMeta_Ptr(xmpRef) ((XMPMeta*)(xmpRef)) + +#define WtoXMPDocOps_Ptr(docRef) ((XMPDocOps*)(docRef)) + +extern void * voidVoidPtr; // Used to backfill null output parameters. +extern XMP_StringPtr voidStringPtr; +extern XMP_StringLen voidStringLen; +extern XMP_OptionBits voidOptionBits; +extern XMP_Bool voidByte; +extern bool voidBool; +extern XMP_Int32 voidInt32; +extern XMP_Int64 voidInt64; +extern double voidDouble; +extern XMP_DateTime voidDateTime; +extern WXMP_Result void_wResult; + +#define kHexDigits "0123456789ABCDEF" + +#define XMP_LitMatch(s,l) (strcmp((s),(l)) == 0) +#define XMP_LitNMatch(s,l,n) (strncmp((s),(l),(n)) == 0) + // *** Use the above macros! + +#if XMP_WinBuild + #define snprintf _snprintf +#endif + +// ================================================================================================= +// Version info + +#if XMP_DebugBuild + #define kXMPCore_DebugFlag 1 +#else + #define kXMPCore_DebugFlag 0 +#endif + +#define kXMPCore_VersionNumber ( (kXMPCore_DebugFlag << 31) | \ + (XMP_API_VERSION_MAJOR << 24) | \ + (XMP_API_VERSION_MINOR << 16) | \ + (XMP_API_VERSION_MICRO << 8) ) + + #define kXMPCoreName "Exempi + XMP Core" + #define kXMPCore_VersionMessage kXMPCoreName " " XMPCORE_API_VERSION_STRING +// ================================================================================================= +// Support for call tracing + +#ifndef XMP_TraceCoreCalls + #define XMP_TraceCoreCalls 0 + #define XMP_TraceCoreCallsToFile 0 +#endif + +#if XMP_TraceCoreCalls + + #undef AnnounceThrow + #undef AnnounceCatch + + #undef AnnounceEntry + #undef AnnounceNoLock + #undef AnnounceExit + + extern FILE * xmpCoreLog; + + #define AnnounceThrow(msg) \ + fprintf ( xmpCoreLog, "XMP_Throw: %s\n", msg ); fflush ( xmpCoreLog ) + #define AnnounceCatch(msg) \ + fprintf ( xmpCoreLog, "Catch in %s: %s\n", procName, msg ); fflush ( xmpCoreLog ) + + #define AnnounceEntry(proc) \ + const char * procName = proc; \ + fprintf ( xmpCoreLog, "Entering %s\n", procName ); fflush ( xmpCoreLog ) + #define AnnounceNoLock(proc) \ + const char * procName = proc; \ + fprintf ( xmpCoreLog, "Entering %s (no lock)\n", procName ); fflush ( xmpCoreLog ) + #define AnnounceExit() \ + fprintf ( xmpCoreLog, "Exiting %s\n", procName ); fflush ( xmpCoreLog ) + +#endif + +// ================================================================================================= +// ExpandXPath, FindNode, and related support + +// *** Normalize the use of "const xx &" for input params + +#define kXMP_ArrayItemName "[]" + +#define kXMP_CreateNodes true +#define kXMP_ExistingOnly false + +#define FindConstSchema(t,u) FindSchemaNode ( const_cast(t), u, kXMP_ExistingOnly, 0 ) +#define FindConstChild(p,c) FindChildNode ( const_cast(p), c, kXMP_ExistingOnly, 0 ) +#define FindConstQualifier(p,c) FindQualifierNode ( const_cast(p), c, kXMP_ExistingOnly, 0 ) +#define FindConstNode(t,p) FindNode ( const_cast(t), p, kXMP_ExistingOnly, 0 ) + +extern XMP_OptionBits +VerifySetOptions ( XMP_OptionBits options, XMP_StringPtr propValue ); + +extern void +ComposeXPath ( const XMP_ExpandedXPath & expandedXPath, + XMP_VarString * stringXPath ); + +extern void +ExpandXPath ( XMP_StringPtr schemaNS, + XMP_StringPtr propPath, + XMP_ExpandedXPath * expandedXPath ); + +extern XMP_Node * +FindSchemaNode ( XMP_Node * xmpTree, + XMP_StringPtr nsURI, + bool createNodes, + XMP_NodePtrPos * ptrPos = 0 ); + +extern XMP_Node * +FindChildNode ( XMP_Node * parent, + XMP_StringPtr childName, + bool createNodes, + XMP_NodePtrPos * ptrPos = 0 ); + +extern XMP_Node * +FindQualifierNode ( XMP_Node * parent, + XMP_StringPtr qualName, + bool createNodes, + XMP_NodePtrPos * ptrPos = 0 ); + +extern XMP_Node * +FindNode ( XMP_Node * xmpTree, + const XMP_ExpandedXPath & expandedXPath, + bool createNodes, + XMP_OptionBits leafOptions = 0, + XMP_NodePtrPos * ptrPos = 0 ); + +extern XMP_Index +LookupLangItem ( const XMP_Node * arrayNode, XMP_VarString & lang ); // ! Lang must be normalized! + +extern XMP_Index +LookupFieldSelector ( const XMP_Node * arrayNode, XMP_StringPtr fieldName, XMP_StringPtr fieldValue ); + +extern void +CloneOffspring ( const XMP_Node * origParent, XMP_Node * cloneParent, bool skipEmpty = false ); + +extern XMP_Node * +CloneSubtree ( const XMP_Node * origRoot, XMP_Node * cloneParent, bool skipEmpty = false ); + +extern bool +CompareSubtrees ( const XMP_Node & leftNode, const XMP_Node & rightNode ); + +extern void +DeleteSubtree ( XMP_NodePtrPos rootNodePos ); + +extern void +DeleteEmptySchema ( XMP_Node * schemaNode ); + +extern void +NormalizeLangValue ( XMP_VarString * value ); + +extern void +NormalizeLangArray ( XMP_Node * array ); + +extern void +DetectAltText ( XMP_Node * xmpParent ); + +extern void +SortNamedNodes ( XMP_NodeOffspring & nodeVector ); + +static inline bool +IsPathPrefix ( XMP_StringPtr fullPath, XMP_StringPtr prefix ) +{ + bool isPrefix = false; + XMP_StringLen prefixLen = strlen(prefix); + if ( XMP_LitNMatch ( prefix, fullPath, prefixLen ) ) { + char separator = fullPath[prefixLen]; + if ( (separator == 0) || (separator == '/') || + (separator == '[') || (separator == '*') ) isPrefix = true; + } + return isPrefix; +} + +// ------------------------------------------------------------------------------------------------- + +class XPathStepInfo { +public: + XMP_VarString step; + XMP_OptionBits options; + XPathStepInfo ( XMP_StringPtr _step, XMP_OptionBits _options ) : step(_step), options(_options) {}; + XPathStepInfo ( XMP_VarString _step, XMP_OptionBits _options ) : step(_step), options(_options) {}; +private: + XPathStepInfo() : options(0) {}; // ! Hide the default constructor. +}; + +enum { kSchemaStep = 0, kRootPropStep = 1, kAliasIndexStep = 2 }; + +enum { // Bits for XPathStepInfo options. // *** Add mask check to init code. + kXMP_StepKindMask = 0x0F, // ! The step kinds are mutually exclusive numbers. + kXMP_StructFieldStep = 0x01, // Also for top level nodes (schema "fields"). + kXMP_QualifierStep = 0x02, // ! Order is significant to separate struct/qual from array kinds! + kXMP_ArrayIndexStep = 0x03, // ! The kinds must not overlay array form bits! + kXMP_ArrayLastStep = 0x04, + kXMP_QualSelectorStep = 0x05, + kXMP_FieldSelectorStep = 0x06, + kXMP_StepIsAlias = 0x10 +}; + +#define GetStepKind(f) ((f) & kXMP_StepKindMask) + +#define kXMP_NewImplicitNode kXMP_InsertAfterItem + +// ================================================================================================= +// XMP_Node details + +#if 0 // Pattern for iterating over the children or qualifiers: + for ( size_t xxNum = 0, xxLim = _node_->_offspring_.size(); xxNum < xxLim; ++xxNum ) { + const XMP_Node * _curr_ = _node_->_offspring_[xxNum]; + } +#endif + +class XMP_Node { +public: + + XMP_OptionBits options; + XMP_VarString name, value; + XMP_Node * parent; + XMP_NodeOffspring children; + XMP_NodeOffspring qualifiers; + #if XMP_DebugBuild + // *** XMP_StringPtr _namePtr, _valuePtr; // *** Not working, need operator=? + #endif + + XMP_Node ( XMP_Node * _parent, XMP_StringPtr _name, XMP_OptionBits _options ) + : options(_options), name(_name), parent(_parent) + { + #if XMP_DebugBuild + XMP_Assert ( (name.find ( ':' ) != XMP_VarString::npos) || (name == kXMP_ArrayItemName) || + (options & kXMP_SchemaNode) || (parent == 0) ); + // *** _namePtr = name.c_str(); + // *** _valuePtr = value.c_str(); + #endif + }; + + XMP_Node ( XMP_Node * _parent, const XMP_VarString & _name, XMP_OptionBits _options ) + : options(_options), name(_name), parent(_parent) + { + #if XMP_DebugBuild + XMP_Assert ( (name.find ( ':' ) != XMP_VarString::npos) || (name == kXMP_ArrayItemName) || + (options & kXMP_SchemaNode) || (parent == 0) ); + // *** _namePtr = name.c_str(); + // *** _valuePtr = value.c_str(); + #endif + }; + + XMP_Node ( XMP_Node * _parent, XMP_StringPtr _name, XMP_StringPtr _value, XMP_OptionBits _options ) + : options(_options), name(_name), value(_value), parent(_parent) + { + #if XMP_DebugBuild + XMP_Assert ( (name.find ( ':' ) != XMP_VarString::npos) || (name == kXMP_ArrayItemName) || + (options & kXMP_SchemaNode) || (parent == 0) ); + // *** _namePtr = name.c_str(); + // *** _valuePtr = value.c_str(); + #endif + }; + + XMP_Node ( XMP_Node * _parent, const XMP_VarString & _name, const XMP_VarString & _value, XMP_OptionBits _options ) + : options(_options), name(_name), value(_value), parent(_parent) + { + #if XMP_DebugBuild + XMP_Assert ( (name.find ( ':' ) != XMP_VarString::npos) || (name == kXMP_ArrayItemName) || + (options & kXMP_SchemaNode) || (parent == 0) ); + // *** _namePtr = name.c_str(); + // *** _valuePtr = value.c_str(); + #endif + }; + + void GetLocalURI ( XMP_StringPtr * uriStr, XMP_StringLen * uriSize ) const; + + void RemoveChildren() + { + for ( size_t i = 0, vLim = children.size(); i < vLim; ++i ) { + if ( children[i] != 0 ) delete children[i]; + } + children.clear(); + } + + void RemoveQualifiers() + { + for ( size_t i = 0, vLim = qualifiers.size(); i < vLim; ++i ) { + if ( qualifiers[i] != 0 ) delete qualifiers[i]; + } + qualifiers.clear(); + } + + void ClearNode() + { + options = 0; + name.erase(); + value.erase(); + this->RemoveChildren(); + this->RemoveQualifiers(); + } + + virtual ~XMP_Node() { RemoveChildren(); RemoveQualifiers(); }; + +private: + XMP_Node() : options(0), parent(0) // ! Make sure parent pointer is always set. + { + #if XMP_DebugBuild + // *** _namePtr = name.c_str(); + // *** _valuePtr = value.c_str(); + #endif + }; + +}; + +class XMP_AutoNode { // Used to hold a child during subtree construction. +public: + XMP_Node * nodePtr; + XMP_AutoNode() : nodePtr(0) {}; + ~XMP_AutoNode() { if ( nodePtr != 0 ) delete ( nodePtr ); nodePtr = 0; }; + XMP_AutoNode ( XMP_Node * _parent, XMP_StringPtr _name, XMP_OptionBits _options ) + : nodePtr ( new XMP_Node ( _parent, _name, _options ) ) {}; + XMP_AutoNode ( XMP_Node * _parent, const XMP_VarString & _name, XMP_OptionBits _options ) + : nodePtr ( new XMP_Node ( _parent, _name, _options ) ) {}; + XMP_AutoNode ( XMP_Node * _parent, XMP_StringPtr _name, XMP_StringPtr _value, XMP_OptionBits _options ) + : nodePtr ( new XMP_Node ( _parent, _name, _value, _options ) ) {}; + XMP_AutoNode ( XMP_Node * _parent, const XMP_VarString & _name, const XMP_VarString & _value, XMP_OptionBits _options ) + : nodePtr ( new XMP_Node ( _parent, _name, _value, _options ) ) {}; +}; + +// ================================================================================================= + +#endif // __XMPCore_Impl_hpp__ diff --git a/XMPCore/source/XMPIterator.cpp b/XMPCore/source/XMPIterator.cpp new file mode 100644 index 0000000..b857365 --- /dev/null +++ b/XMPCore/source/XMPIterator.cpp @@ -0,0 +1,636 @@ +// ================================================================================================= +// 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/source/XMPIterator.hpp" + +#include +#include // For snprintf. + +#if XMP_WinBuild + #pragma warning ( disable : 4702 ) // unreachable code + #pragma warning ( disable : 4800 ) // forcing value to bool 'true' or 'false' (performance warning) + #pragma warning ( disable : 4996 ) // '...' was declared deprecated +#endif + +// ================================================================================================= +// Support Routines +// ================================================================================================= + + +#ifndef TraceIterators + #define TraceIterators 0 +#endif + +#if TraceIterators + static const char * sStageNames[] = { "before", "self", "qualifiers", "children" }; +#endif + +static XMP_Node * sDummySchema = 0; // ! Used for some ugliness with aliases. + +// ------------------------------------------------------------------------------------------------- +// AddSchemaProps +// -------------- +// +// Add the top level properties to the IterNode for a schema. + +static void +AddSchemaProps ( IterInfo & info, IterNode & iterSchema, const XMP_Node * xmpSchema ) +{ + #if TraceIterators + printf ( " Adding properties of %s\n", xmpSchema->name.c_str() ); + #endif + + for ( size_t propNum = 0, propLim = xmpSchema->children.size(); propNum != propLim; ++propNum ) { + const XMP_Node * xmpProp = xmpSchema->children[propNum]; + // *** set the has-aliases bit when appropriate + iterSchema.children.push_back ( IterNode ( xmpProp->options, xmpProp->name, 0 ) ); + #if TraceIterators + printf ( " %s\n", xmpProp->name.c_str() ); + #endif + } + +} // AddSchemaProps + +// ------------------------------------------------------------------------------------------------- +// AddNodeOffspring +// ---------------- +// +// Add the immediate children and qualifiers to an IterNode. + +static void +AddNodeOffspring ( IterInfo & info, IterNode & iterParent, const XMP_Node * xmpParent ) +{ + XMP_VarString currPath ( iterParent.fullPath ); + size_t leafOffset = iterParent.fullPath.size(); + + if ( (! xmpParent->qualifiers.empty()) && (! (info.options & kXMP_IterOmitQualifiers)) ) { + + #if TraceIterators + printf ( " Adding qualifiers of %s\n", currPath.c_str() ); + #endif + + currPath += "/?"; // All qualifiers are named and use paths like "Prop/?Qual". + leafOffset += 2; + + for ( size_t qualNum = 0, qualLim = xmpParent->qualifiers.size(); qualNum != qualLim; ++qualNum ) { + const XMP_Node * xmpQual = xmpParent->qualifiers[qualNum]; + currPath += xmpQual->name; + iterParent.qualifiers.push_back ( IterNode ( xmpQual->options, currPath, leafOffset ) ); + currPath.erase ( leafOffset ); + #if TraceIterators + printf ( " %s\n", xmpQual->name.c_str() ); + #endif + } + + leafOffset -= 2; + currPath.erase ( leafOffset ); + + } + + if ( ! xmpParent->children.empty() ) { + + #if TraceIterators + printf ( " Adding children of %s\n", currPath.c_str() ); + #endif + + XMP_Assert ( xmpParent->options & kXMP_PropCompositeMask ); + + if ( xmpParent->options & kXMP_PropValueIsStruct ) { + currPath += '/'; + leafOffset += 1; + } + + for ( size_t childNum = 0, childLim = xmpParent->children.size(); childNum != childLim; ++childNum ) { + const XMP_Node * xmpChild = xmpParent->children[childNum]; + if ( ! (xmpParent->options & kXMP_PropValueIsArray) ) { + currPath += xmpChild->name; + } else { + char buffer [32]; // AUDIT: Using sizeof(buffer) below for snprintf length is safe. + snprintf ( buffer, sizeof(buffer), "[%lu]", childNum+1 ); // ! XPath indices are one-based. + currPath += buffer; + } + iterParent.children.push_back ( IterNode ( xmpChild->options, currPath, leafOffset ) ); + currPath.erase ( leafOffset ); + #if TraceIterators + printf ( " %s\n", (iterParent.children.back().fullPath.c_str() + leafOffset) ); + #endif + } + + } + +} // AddNodeOffspring + +// ------------------------------------------------------------------------------------------------- +// SetCurrSchema +// ------------- + +static inline void +SetCurrSchema ( IterInfo & info, XMP_StringPtr schemaName ) +{ + + info.currSchema = schemaName; + #if 0 // *** XMP_DebugBuild + info._schemaPtr = info.currSchema.c_str(); + #endif + +} // SetCurrSchema + +static inline void +SetCurrSchema ( IterInfo & info, XMP_VarString & schemaName ) +{ + + info.currSchema = schemaName; + #if 0 // *** XMP_DebugBuild + info._schemaPtr = info.currSchema.c_str(); + #endif + +} // SetCurrSchema + +// ------------------------------------------------------------------------------------------------- +// AdvanceIterPos +// -------------- +// +// Adjust currPos and possibly endPos for the next step in a pre-order depth-first traversal. The +// current node has just been visited, move on to its qualifiers, children, then siblings, or back +// up to an ancestor. AdvanceIterPos either moves to a property or qualifier node that can be +// visited, or to the end of the entire iteration. + +static void +AdvanceIterPos ( IterInfo & info ) +{ + // ------------------------------------------------------------------------------------------- + // Keep looking until we find a node to visit or the end of everything. The first time through + // the current node will exist, we just visited it. But we have to keep looking if the current + // node was the last of its siblings or is an empty schema. + + // ! It is possible that info.currPos == info.endPos on entry. Don't dereference info.currPos yet! + + while ( true ) { + + if ( info.currPos == info.endPos ) { + + // ------------------------------------------------------------------------------------ + // At the end of a set of siblings, move up to an ancestor. We've either just finished + // the qualifiers and will move to the children, or have just finished the children and + // will move on to the next sibling. + + if ( info.ancestors.empty() ) break; // We're at the end of the schema list. + + IterPosPair & parent = info.ancestors.back(); + info.currPos = parent.first; + info.endPos = parent.second; + info.ancestors.pop_back(); + + #if TraceIterators + printf ( " Moved up to %s, stage = %s\n", + info.currPos->fullPath.c_str(), sStageNames[info.currPos->visitStage] ); + #endif + + } else { + + // ------------------------------------------------------------------------------------------- + // Decide what to do with this iteration node based on its state. Don't use a switch statment, + // some of the cases want to break from the loop. A break in a switch just exits the case. + + #if TraceIterators + printf ( " Moving from %s, stage = %s\n", + info.currPos->fullPath.c_str(), sStageNames[info.currPos->visitStage] ); + #endif + + if ( info.currPos->visitStage == kIter_BeforeVisit ) { // Visit this node now. + if ( info.currPos->options & kXMP_SchemaNode ) SetCurrSchema ( info, info.currPos->fullPath ); + break; + } + + if ( info.currPos->visitStage == kIter_VisitSelf ) { // Just finished visiting the value portion. + info.currPos->visitStage = kIter_VisitQualifiers; // Start visiting the qualifiers. + if ( ! info.currPos->qualifiers.empty() ) { + info.ancestors.push_back ( IterPosPair ( info.currPos, info.endPos ) ); + info.endPos = info.currPos->qualifiers.end(); // ! Set the parent's endPos before changing currPos! + info.currPos = info.currPos->qualifiers.begin(); + break; + } + } + + if ( info.currPos->visitStage == kIter_VisitQualifiers ) { // Just finished visiting the qualifiers. + info.currPos->qualifiers.clear(); + info.currPos->visitStage = kIter_VisitChildren; // Start visiting the children. + if ( ! info.currPos->children.empty() ) { + info.ancestors.push_back ( IterPosPair ( info.currPos, info.endPos ) ); + info.endPos = info.currPos->children.end(); // ! Set the parent's endPos before changing currPos! + info.currPos = info.currPos->children.begin(); + break; + } + } + + if ( info.currPos->visitStage == kIter_VisitChildren ) { // Just finished visiting the children. + info.currPos->children.clear(); + ++info.currPos; // Move to the next sibling. + continue; + } + + #if TraceIterators + if ( info.currPos != info.endPos ) { + printf ( " Moved to %s, stage = %s\n", + info.currPos->fullPath.c_str(), sStageNames[info.currPos->visitStage] ); + } + #endif + + } + + } // Loop to find the next node. + + XMP_Assert ( (info.currPos == info.endPos) || (info.currPos->visitStage == kIter_BeforeVisit) ); + +} // AdvanceIterPos + +// ------------------------------------------------------------------------------------------------- +// GetNextXMPNode +// -------------- +// +// Used by XMPIterator::Next to obtain the next XMP node, ignoring the kXMP_IterJustLeafNodes flag. +// This isolates some messy code, allowing a clean loop in Next if kXMP_IterJustLeafNodes is set. + +static const XMP_Node * +GetNextXMPNode ( IterInfo & info ) +{ + const XMP_Node * xmpNode = 0; + + // ---------------------------------------------------------------------------------------------- + // On entry currPos points to an iteration node whose state is either before-visit or visit-self. + // If it is before-visit then we will return that node's value part now. If it is visit-self it + // means the previous iteration returned the value portion of that node, so we can advance to the + // next node in the iteration tree. Then we find the corresponding XMP node, allowing for the XMP + // tree to have been modified since that part of the iteration tree was constructed. + + // ! NOTE: Supporting aliases throws in some nastiness with schemas. There might not be any XMP + // ! node for the schema, but we still have to visit it because of possible aliases. The static + // ! sDummySchema is returned if there is no real schema node. + + if ( info.currPos->visitStage != kIter_BeforeVisit ) AdvanceIterPos ( info ); + + bool isSchemaNode = false; + XMP_ExpandedXPath expPath; // Keep outside the loop to avoid constant construct/destruct. + + while ( info.currPos != info.endPos ) { + + isSchemaNode = XMP_NodeIsSchema ( info.currPos->options ); + if ( isSchemaNode ) { + SetCurrSchema ( info, info.currPos->fullPath ); + xmpNode = FindConstSchema ( &info.xmpObj->tree, info.currPos->fullPath.c_str() ); + if ( xmpNode == 0 ) xmpNode = sDummySchema; + } else { + ExpandXPath ( info.currSchema.c_str(), info.currPos->fullPath.c_str(), &expPath ); + xmpNode = FindConstNode ( &info.xmpObj->tree, expPath ); + } + if ( xmpNode != 0 ) break; // Exit the loop, we found a live XMP node. + + info.currPos->visitStage = kIter_VisitChildren; // Make AdvanceIterPos move to the next sibling. + info.currPos->children.clear(); + info.currPos->qualifiers.clear(); + AdvanceIterPos ( info ); + + } + + if ( info.currPos == info.endPos ) return 0; + + // ------------------------------------------------------------------------------------------- + // Now we've got the iteration node and corresponding XMP node. Add the iteration children for + // structs and arrays. The children of schema were added when the iterator was constructed. + + XMP_Assert ( info.currPos->visitStage == kIter_BeforeVisit ); + + if ( info.currPos->visitStage == kIter_BeforeVisit ) { + if ( (! isSchemaNode) && (! (info.options & kXMP_IterJustChildren)) ) { + AddNodeOffspring ( info, *info.currPos, xmpNode ); + } + info.currPos->visitStage = kIter_VisitSelf; + } + + return xmpNode; + +} // GetNextXMPNode + +// ================================================================================================= +// Init/Term +// ================================================================================================= + +// ------------------------------------------------------------------------------------------------- +// Initialize +// ---------- + +/* class static */ bool +XMPIterator::Initialize() +{ + sDummySchema = new XMP_Node ( 0, "dummy:schema/", kXMP_SchemaNode); + return true; + +} // Initialize + +// ------------------------------------------------------------------------------------------------- +// Terminate +// ---------- + +/* class static */ void +XMPIterator::Terminate() RELEASE_NO_THROW +{ + delete ( sDummySchema ); + sDummySchema = 0; + return; + +} // Terminate + +// ================================================================================================= +// Constructors +// ================================================================================================= + +// ------------------------------------------------------------------------------------------------- +// XMPIterator +// ----------- +// +// Constructor for iterations over the nodes in an XMPMeta object. This builds a tree of iteration +// nodes that caches the existing node names of the XMPMeta object. The iteration tree is a partial +// replica of the XMPMeta tree. The initial iteration tree normally has just the root node, all of +// the schema nodes for a full object iteration. Lower level nodes (children and qualifiers) are +// added when the parent is visited. If the kXMP_IterJustChildren option is passed then the initial +// iterator includes the children and the parent is marked as done. The iteration tree nodes are +// pruned when they are no longer needed. + +XMPIterator::XMPIterator ( const XMPMeta & xmpObj, + XMP_StringPtr schemaNS, + XMP_StringPtr propName, + XMP_OptionBits options ) : clientRefs(0), info(IterInfo(options,&xmpObj)) +{ + if ( (options & kXMP_IterClassMask) != kXMP_IterProperties ) { + XMP_Throw ( "Unsupported iteration kind", kXMPErr_BadOptions ); + } + + // *** Lock the XMPMeta object if we ever stop using a full DLL lock. + + if ( *propName != 0 ) { + + // An iterator rooted at a specific node. + + #if TraceIterators + printf ( "\nNew XMP property iterator for \"%s\", options = %X\n Schema = %s, root = %s\n", + xmpObj.tree.name.c_str(), options, schemaNS, propName ); + #endif + + XMP_ExpandedXPath propPath; + ExpandXPath ( schemaNS, propName, &propPath ); + XMP_Node * propNode = FindConstNode ( &xmpObj.tree, propPath ); // If not found get empty iteration. + + if ( propNode != 0 ) { + + XMP_VarString rootName ( propPath[1].step ); // The schema is [0]. + for ( size_t i = 2; i < propPath.size(); ++i ) { + XMP_OptionBits stepKind = GetStepKind ( propPath[i].options ); + if ( stepKind <= kXMP_QualifierStep ) rootName += '/'; + rootName += propPath[i].step; + } + + propName = rootName.c_str(); + size_t leafOffset = rootName.size(); + while ( (leafOffset > 0) && (propName[leafOffset] != '/') && (propName[leafOffset] != '[') ) --leafOffset; + if ( propName[leafOffset] == '/' ) ++leafOffset; + + info.tree.children.push_back ( IterNode ( propNode->options, propName, leafOffset ) ); + SetCurrSchema ( info, propPath[kSchemaStep].step.c_str() ); + if ( info.options & kXMP_IterJustChildren ) { + AddNodeOffspring ( info, info.tree.children.back(), propNode ); + } + + } + + } else if ( *schemaNS != 0 ) { + + // An iterator for all properties in one schema. + + #if TraceIterators + printf ( "\nNew XMP schema iterator for \"%s\", options = %X\n Schema = %s\n", + xmpObj.tree.name.c_str(), options, schemaNS ); + #endif + + info.tree.children.push_back ( IterNode ( kXMP_SchemaNode, schemaNS, 0 ) ); + IterNode & iterSchema = info.tree.children.back(); + + XMP_Node * xmpSchema = FindConstSchema ( &xmpObj.tree, schemaNS ); + if ( xmpSchema != 0 ) AddSchemaProps ( info, iterSchema, xmpSchema ); + + if ( iterSchema.children.empty() ) { + info.tree.children.pop_back(); // No properties, remove the schema node. + } else { + SetCurrSchema ( info, schemaNS ); + } + + } else { + + // An iterator for all properties in all schema. First add schema that exist (have children), + // adding aliases from them if appropriate. Then add schema that have no actual properties + // but do have aliases to existing properties, if we're including aliases in the iteration. + + #if TraceIterators + printf ( "\nNew XMP tree iterator for \"%s\", options = %X\n", + xmpObj.tree.name.c_str(), options ); + #endif + + // First pick up the schema that exist. + + for ( size_t schemaNum = 0, schemaLim = xmpObj.tree.children.size(); schemaNum != schemaLim; ++schemaNum ) { + + const XMP_Node * xmpSchema = xmpObj.tree.children[schemaNum]; + info.tree.children.push_back ( IterNode ( kXMP_SchemaNode, xmpSchema->name, 0 ) ); + IterNode & iterSchema = info.tree.children.back(); + + if ( ! (info.options & kXMP_IterJustChildren) ) { + AddSchemaProps ( info, iterSchema, xmpSchema ); + if ( iterSchema.children.empty() ) info.tree.children.pop_back(); // No properties, remove the schema node. + } + + } + + } + + // Set the current iteration position to the first node to be visited. + + info.currPos = info.tree.children.begin(); + info.endPos = info.tree.children.end(); + + if ( (info.options & kXMP_IterJustChildren) && (info.currPos != info.endPos) && (*schemaNS != 0) ) { + info.currPos->visitStage = kIter_VisitSelf; + } + + #if TraceIterators + if ( info.currPos == info.endPos ) { + printf ( " ** Empty iteration **\n" ); + } else { + printf ( " Initial node %s, stage = %s, iterator @ %.8X\n", + info.currPos->fullPath.c_str(), sStageNames[info.currPos->visitStage], this ); + } + #endif + +} // XMPIterator for XMPMeta objects + +// ------------------------------------------------------------------------------------------------- +// XMPIterator +// ----------- +// +// Constructor for iterations over global tables such as registered namespaces or aliases. + +XMPIterator::XMPIterator ( XMP_StringPtr /*schemaNS*/, + XMP_StringPtr /*propName*/, + XMP_OptionBits options ) : clientRefs(0), info(IterInfo(options,0)) +{ + + XMP_Throw ( "Unimplemented XMPIterator constructor for global tables", kXMPErr_Unimplemented ); + +} // XMPIterator for global tables + +// ------------------------------------------------------------------------------------------------- +// ~XMPIterator +// ----------- + +XMPIterator::~XMPIterator() RELEASE_NO_THROW +{ + XMP_Assert ( this->clientRefs <= 0 ); + // Let everything else default. + +} // ~XMPIterator + +// ================================================================================================= +// Iteration Methods +// ================================================================================================= + +// ------------------------------------------------------------------------------------------------- +// Next +// ---- +// +// Do a preorder traversal of the cached nodes. + +// *** Need to document the relationships between currPos, endPos, and visitStage. + +bool +XMPIterator::Next ( XMP_StringPtr * schemaNS, + XMP_StringLen * nsSize, + XMP_StringPtr * propPath, + XMP_StringLen * pathSize, + XMP_StringPtr * propValue, + XMP_StringLen * valueSize, + XMP_OptionBits * propOptions ) +{ + // *** Lock the XMPMeta object if we ever stop using a full DLL lock. + + // ! NOTE: Supporting aliases throws in some nastiness with schemas. There might not be any XMP + // ! node for the schema, but we still have to visit it because of possible aliases. + + if ( info.currPos == info.endPos ) return false; // Happens at the start of an empty iteration. + + #if TraceIterators + printf ( "Next iteration from %s, stage = %s, iterator @ %.8X\n", + info.currPos->fullPath.c_str(), sStageNames[info.currPos->visitStage], this ); + #endif + + const XMP_Node * xmpNode = GetNextXMPNode ( info ); + if ( xmpNode == 0 ) return false; + bool isSchemaNode = XMP_NodeIsSchema ( info.currPos->options ); + + if ( info.options & kXMP_IterJustLeafNodes ) { + while ( isSchemaNode || (! xmpNode->children.empty()) ) { + info.currPos->visitStage = kIter_VisitQualifiers; // Skip to this node's children. + xmpNode = GetNextXMPNode ( info ); + if ( xmpNode == 0 ) return false; + isSchemaNode = XMP_NodeIsSchema ( info.currPos->options ); + } + } + + *schemaNS = info.currSchema.c_str(); + *nsSize = info.currSchema.size(); + + *propOptions = info.currPos->options; + + *propPath = ""; + *pathSize = 0; + *propValue = ""; + *valueSize = 0; + + if ( ! (*propOptions & kXMP_SchemaNode) ) { + + *propPath = info.currPos->fullPath.c_str(); + *pathSize = info.currPos->fullPath.size(); + + if ( info.options & kXMP_IterJustLeafName ) { + *propPath += info.currPos->leafOffset; + *pathSize -= info.currPos->leafOffset; + xmpNode->GetLocalURI ( schemaNS, nsSize ); // Use the leaf namespace, not the top namespace. + } + + if ( ! (*propOptions & kXMP_PropCompositeMask) ) { + *propValue = xmpNode->value.c_str(); + *valueSize = xmpNode->value.size(); + } + + } + + #if TraceIterators + printf ( " Next node %s, stage = %s\n", + info.currPos->fullPath.c_str(), sStageNames[info.currPos->visitStage] ); + #endif + + return true; + +} // Next + +// ------------------------------------------------------------------------------------------------- +// Skip +// ---- +// +// Skip some portion of the traversal related to the last visited node. We skip either that node's +// children, or those children and the previous node's siblings. The implementation might look a bit +// awkward because info.currNode always points to the next node to be visited. We might already have +// moved past the things to skip, e.g. if the previous node was simple and the last of its siblings. + +enum { + kXMP_ValidIterSkipOptions = kXMP_IterSkipSubtree | kXMP_IterSkipSiblings +}; + +void +XMPIterator::Skip ( XMP_OptionBits iterOptions ) +{ +// if ( (info.currPos == kIter_NullPos) ) XMP_Throw ( "No prior postion to skip from", kXMPErr_BadIterPosition ); + if ( iterOptions == 0 ) XMP_Throw ( "Must specify what to skip", kXMPErr_BadOptions ); + if ( (iterOptions & ~kXMP_ValidIterSkipOptions) != 0 ) XMP_Throw ( "Undefined options", kXMPErr_BadOptions ); + + #if TraceIterators + printf ( "Skipping from %s, stage = %s, iterator @ %.8X", + info.currPos->fullPath.c_str(), sStageNames[info.currPos->visitStage], this ); + #endif + + if ( iterOptions & kXMP_IterSkipSubtree ) { + #if TraceIterators + printf ( ", mode = subtree\n" ); + #endif + info.currPos->visitStage = kIter_VisitChildren; + } else if ( iterOptions & kXMP_IterSkipSiblings ) { + #if TraceIterators + printf ( ", mode = siblings\n" ); + #endif + info.currPos = info.endPos; + AdvanceIterPos ( info ); + } + #if TraceIterators + printf ( " Skipped to %s, stage = %s\n", + info.currPos->fullPath.c_str(), sStageNames[info.currPos->visitStage] ); + #endif + + +} // Skip + +// ================================================================================================= diff --git a/XMPCore/source/XMPIterator.hpp b/XMPCore/source/XMPIterator.hpp new file mode 100644 index 0000000..1bdc1f3 --- /dev/null +++ b/XMPCore/source/XMPIterator.hpp @@ -0,0 +1,144 @@ +#ifndef __XMPIterator_hpp__ +#define __XMPIterator_hpp__ + +// ================================================================================================= +// 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" +#include "public/include/XMP_Const.h" +#include "XMPCore/source/XMPMeta.hpp" + +// ================================================================================================= + +struct IterNode; +typedef std::vector < IterNode > IterOffspring; +typedef IterOffspring::iterator IterPos; + +typedef std::pair < IterPos, IterPos > IterPosPair; +typedef std::vector < IterPosPair > IterPosStack; + +enum { // Values for the visitStage field, used to decide how to proceed past a node. + kIter_BeforeVisit = 0, // Have not visited this node at all. + kIter_VisitSelf = 1, // Have visited this node and returned its value/options portion. + kIter_VisitQualifiers = 2, // In the midst of visiting this node's qualifiers. + kIter_VisitChildren = 3 // In the midst of visiting this node's children. +}; + +struct IterNode { + + XMP_OptionBits options; + XMP_VarString fullPath; + size_t leafOffset; + IterOffspring children, qualifiers; + XMP_Uns8 visitStage; + #if 0 // *** XMP_DebugBuild + XMP_StringPtr _pathPtr, _leafPtr; // *** Not working, need operator=? + #endif + + IterNode() : options(0), leafOffset(0), visitStage(kIter_BeforeVisit) + { + #if 0 // *** XMP_DebugBuild + _pathPtr = _leafPtr = 0; + #endif + }; + + IterNode ( XMP_OptionBits _options, const XMP_VarString& _fullPath, size_t _leafOffset ) + : options(_options), fullPath(_fullPath), leafOffset(_leafOffset), visitStage(kIter_BeforeVisit) + { + #if 0 // *** XMP_DebugBuild + _pathPtr = fullPath.c_str(); + _leafPtr = _pathPtr + leafOffset; + #endif + }; + +}; + +struct IterInfo { + + XMP_OptionBits options; + const XMPMeta * xmpObj; + XMP_VarString currSchema; + IterPos currPos, endPos; + IterPosStack ancestors; + IterNode tree; + #if 0 // *** XMP_DebugBuild + XMP_StringPtr _schemaPtr; // *** Not working, need operator=? + #endif + + IterInfo() : options(0), xmpObj(0) + { + #if 0 // *** XMP_DebugBuild + _schemaPtr = 0; + #endif + }; + + IterInfo ( XMP_OptionBits _options, const XMPMeta * _xmpObj ) : options(_options), xmpObj(_xmpObj) + { + #if 0 // *** XMP_DebugBuild + _schemaPtr = 0; + #endif + }; + +}; + +// ================================================================================================= + +class XMPIterator { +public: + + static bool + Initialize(); // ! For internal use only! + + static void + Terminate() RELEASE_NO_THROW; // ! For internal use only! + + XMPIterator ( const XMPMeta & xmpObj, // Construct a property iterator. + XMP_StringPtr schemaNS, + XMP_StringPtr propName, + XMP_OptionBits options ); + + XMPIterator ( XMP_StringPtr schemaNS, // Construct a table iterator. + XMP_StringPtr propName, + XMP_OptionBits options ); + + virtual ~XMPIterator() RELEASE_NO_THROW; + + bool + Next ( XMP_StringPtr * schemaNS, + XMP_StringLen * nsSize, + XMP_StringPtr * propPath, + XMP_StringLen * pathSize, + XMP_StringPtr * propValue, + XMP_StringLen * valueSize, + XMP_OptionBits * propOptions ); + + void + Skip ( XMP_OptionBits options ); + + // ! Expose so that wrappers and file static functions can see the data. + + XMP_Int32 clientRefs; // ! Must be signed to allow decrement from 0. + XMP_ReadWriteLock lock; + + IterInfo info; + +private: + + // ! These are hidden on purpose: + XMPIterator() : clientRefs(0) + { XMP_Throw ( "Call to hidden constructor", kXMPErr_InternalFailure ); }; + XMPIterator ( const XMPIterator & /* original */ ) : clientRefs(0) + { XMP_Throw ( "Call to hidden constructor", kXMPErr_InternalFailure ); }; + void operator= ( const XMPIterator & /* rhs */ ) + { XMP_Throw ( "Call to hidden operator=", kXMPErr_InternalFailure ); }; + +}; + +// ================================================================================================= + +#endif // __XMPIterator_hpp__ diff --git a/XMPCore/source/XMPMeta-GetSet.cpp b/XMPCore/source/XMPMeta-GetSet.cpp new file mode 100644 index 0000000..e37ceb9 --- /dev/null +++ b/XMPCore/source/XMPMeta-GetSet.cpp @@ -0,0 +1,1307 @@ +// ================================================================================================= +// 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" + +#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 +}; + + +// ================================================================================================= +// Static Variables +// ================ + + +// ================================================================================================= +// Local Utilities +// =============== + + +// ------------------------------------------------------------------------------------------------- +// SetNodeValue +// ------------ + +static inline void +SetNodeValue ( XMP_Node * node, XMP_StringPtr value ) +{ + + #if XMP_DebugBuild // ! Hack to force an assert. + if ( (node->name == "xmp:TestAssertNotify") && XMP_LitMatch ( value, "DoIt!" ) ) { + XMP_Assert ( node->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_BadXML ); + } + } + + } + + if ( XMP_PropIsQualifier(node->options) && (node->name == "xml:lang") ) NormalizeLangValue ( &newValue ); + + node->value.swap ( newValue ); + + #if 0 // *** XMP_DebugBuild + node->_valuePtr = node->value.c_str(); + #endif + +} // SetNodeValue + + +// ------------------------------------------------------------------------------------------------- +// 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 = 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 = 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 = itemNode->qualifiers[0]->value.size(); + *itemValue = itemNode->value.c_str(); + *valueSize = 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; + + 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; + } + } + + } + + // 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/XMPCore/source/XMPMeta-Parse.cpp b/XMPCore/source/XMPMeta-Parse.cpp new file mode 100644 index 0000000..62b9404 --- /dev/null +++ b/XMPCore/source/XMPMeta-Parse.cpp @@ -0,0 +1,1277 @@ +// ================================================================================================= +// 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/XMPUtils.hpp" + +#include "source/UnicodeInlines.incl_cpp" +#include "source/UnicodeConversions.hpp" +#include "source/ExpatAdapter.hpp" + +#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) + #pragma warning ( disable : 4996 ) // '...' was declared deprecated +#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 +// ========================= + + +// ================================================================================================= +// Static Variables +// ================ + +#ifndef Trace_ParsingHackery + #define Trace_ParsingHackery 0 +#endif + +static const char * kReplaceLatin1[128] = + { + + // The 0x80..0x9F range is undefined in Latin-1, but is defined in Windows code page 1252. + // The bytes 0x81, 0x8D, 0x8F, 0x90, and 0x9D are formally undefined by Windows 1252, but + // their conversion API maps them to U+0081, etc. These are in XML's RestrictedChar set, so + // we map them to a space. + + "\xE2\x82\xAC", " ", "\xE2\x80\x9A", "\xC6\x92", // 0x80 .. 0x83 + "\xE2\x80\x9E", "\xE2\x80\xA6", "\xE2\x80\xA0", "\xE2\x80\xA1", // 0x84 .. 0x87 + "\xCB\x86", "\xE2\x80\xB0", "\xC5\xA0", "\xE2\x80\xB9", // 0x88 .. 0x8B + "\xC5\x92", " ", "\xC5\xBD", " ", // 0x8C .. 0x8F + + " ", "\xE2\x80\x98", "\xE2\x80\x99", "\xE2\x80\x9C", // 0x90 .. 0x93 + "\xE2\x80\x9D", "\xE2\x80\xA2", "\xE2\x80\x93", "\xE2\x80\x94", // 0x94 .. 0x97 + "\xCB\x9C", "\xE2\x84\xA2", "\xC5\xA1", "\xE2\x80\xBA", // 0x98 .. 0x9B + "\xC5\x93", " ", "\xC5\xBE", "\xC5\xB8", // 0x9C .. 0x9F + + // These are the UTF-8 forms of the official Latin-1 characters in the range 0xA0..0xFF. Not + // too surprisingly these map to U+00A0, etc. Which is the Unicode Latin Supplement range. + + "\xC2\xA0", "\xC2\xA1", "\xC2\xA2", "\xC2\xA3", "\xC2\xA4", "\xC2\xA5", "\xC2\xA6", "\xC2\xA7", // 0xA0 .. 0xA7 + "\xC2\xA8", "\xC2\xA9", "\xC2\xAA", "\xC2\xAB", "\xC2\xAC", "\xC2\xAD", "\xC2\xAE", "\xC2\xAF", // 0xA8 .. 0xAF + + "\xC2\xB0", "\xC2\xB1", "\xC2\xB2", "\xC2\xB3", "\xC2\xB4", "\xC2\xB5", "\xC2\xB6", "\xC2\xB7", // 0xB0 .. 0xB7 + "\xC2\xB8", "\xC2\xB9", "\xC2\xBA", "\xC2\xBB", "\xC2\xBC", "\xC2\xBD", "\xC2\xBE", "\xC2\xBF", // 0xB8 .. 0xBF + + "\xC3\x80", "\xC3\x81", "\xC3\x82", "\xC3\x83", "\xC3\x84", "\xC3\x85", "\xC3\x86", "\xC3\x87", // 0xC0 .. 0xC7 + "\xC3\x88", "\xC3\x89", "\xC3\x8A", "\xC3\x8B", "\xC3\x8C", "\xC3\x8D", "\xC3\x8E", "\xC3\x8F", // 0xC8 .. 0xCF + + "\xC3\x90", "\xC3\x91", "\xC3\x92", "\xC3\x93", "\xC3\x94", "\xC3\x95", "\xC3\x96", "\xC3\x97", // 0xD0 .. 0xD7 + "\xC3\x98", "\xC3\x99", "\xC3\x9A", "\xC3\x9B", "\xC3\x9C", "\xC3\x9D", "\xC3\x9E", "\xC3\x9F", // 0xD8 .. 0xDF + + "\xC3\xA0", "\xC3\xA1", "\xC3\xA2", "\xC3\xA3", "\xC3\xA4", "\xC3\xA5", "\xC3\xA6", "\xC3\xA7", // 0xE0 .. 0xE7 + "\xC3\xA8", "\xC3\xA9", "\xC3\xAA", "\xC3\xAB", "\xC3\xAC", "\xC3\xAD", "\xC3\xAE", "\xC3\xAF", // 0xE8 .. 0xEF + + "\xC3\xB0", "\xC3\xB1", "\xC3\xB2", "\xC3\xB3", "\xC3\xB4", "\xC3\xB5", "\xC3\xB6", "\xC3\xB7", // 0xF0 .. 0xF7 + "\xC3\xB8", "\xC3\xB9", "\xC3\xBA", "\xC3\xBB", "\xC3\xBC", "\xC3\xBD", "\xC3\xBE", "\xC3\xBF", // 0xF8 .. 0xFF + + }; + + +// ================================================================================================= +// Local Utilities +// =============== + + +#define IsHexDigit(ch) ( (('0' <= (ch)) && ((ch) <= '9')) || (('A' <= (ch)) && ((ch) <= 'F')) ) +#define HexDigitValue(ch) ( (((ch) - '0') < 10) ? ((ch) - '0') : ((ch) - 'A' + 10) ) + + +// ------------------------------------------------------------------------------------------------- +// PickBestRoot +// ------------ +// +// Pick the first x:xmpmeta among multiple root candidates. If there aren't any, pick the first bare +// rdf:RDF if that is allowed. The returned root is the rdf:RDF child if an x:xmpmeta element was +// chosen. The search is breadth first, so a higher level candiate is chosen over a lower level one +// that was textually earlier in the serialized XML. + +static const XML_Node * PickBestRoot ( const XML_Node & xmlParent, XMP_OptionBits options ) +{ + + // Look among this parent's content for x:xmpmeta. The recursion for x:xmpmeta is broader than + // the strictly defined choice, but gives us smaller code. + for ( size_t childNum = 0, childLim = xmlParent.content.size(); childNum < childLim; ++childNum ) { + const XML_Node * childNode = xmlParent.content[childNum]; + if ( childNode->kind != kElemNode ) continue; + if ( (childNode->name == "x:xmpmeta") || (childNode->name == "x:xapmeta") ) return PickBestRoot ( *childNode, 0 ); + } + // Look among this parent's content for a bare rdf:RDF if that is allowed. + if ( ! (options & kXMP_RequireXMPMeta) ) { + for ( size_t childNum = 0, childLim = xmlParent.content.size(); childNum < childLim; ++childNum ) { + const XML_Node * childNode = xmlParent.content[childNum]; + if ( childNode->kind != kElemNode ) continue; + if ( childNode->name == "rdf:RDF" ) return childNode; + } + } + + // Recurse into the content. + for ( size_t childNum = 0, childLim = xmlParent.content.size(); childNum < childLim; ++childNum ) { + const XML_Node * foundRoot = PickBestRoot ( *xmlParent.content[childNum], options ); + if ( foundRoot != 0 ) return foundRoot; + } + + return 0; + +} // PickBestRoot + +// ------------------------------------------------------------------------------------------------- +// FindRootNode +// ------------ +// +// Find the XML node that is the root of the XMP data tree. Generally this will be an outer node, +// but it could be anywhere if a general XML document is parsed (e.g. SVG). The XML parser counted +// all possible root nodes, and kept a pointer to the last one. If there is more than one possible +// root use PickBestRoot to choose among them. +// +// If there is a root node, try to extract the version of the previous XMP toolkit. + +static const XML_Node * FindRootNode ( const XMLParserAdapter & xmlParser, XMP_OptionBits options ) +{ + const XML_Node * rootNode = xmlParser.rootNode; + + if ( xmlParser.rootCount > 1 ) rootNode = PickBestRoot ( xmlParser.tree, options ); + if ( rootNode == 0 ) return 0; + + XMP_Assert ( rootNode->name == "rdf:RDF" ); + + if ( (options & kXMP_RequireXMPMeta) && + ((rootNode->parent == 0) || + ((rootNode->parent->name != "x:xmpmeta") && (rootNode->parent->name != "x:xapmeta"))) ) return 0; + + return rootNode; + +} // FindRootNode + +// ------------------------------------------------------------------------------------------------- +// NormalizeDCArrays +// ----------------- +// +// Undo the denormalization performed by the XMP used in Acrobat 5. If a Dublin Core array had only +// one item, it was serialized as a simple property. The xml:lang attribute was dropped from an +// alt-text item if the language was x-default. + +// *** This depends on the dc: namespace prefix. + +static void +NormalizeDCArrays ( XMP_Node * xmpTree ) +{ + XMP_Node * dcSchema = FindSchemaNode ( xmpTree, kXMP_NS_DC, kXMP_ExistingOnly ); + if ( dcSchema == 0 ) return; + + for ( size_t propNum = 0, propLimit = dcSchema->children.size(); propNum < propLimit; ++propNum ) { + XMP_Node * currProp = dcSchema->children[propNum]; + XMP_OptionBits arrayForm = 0; + + if ( ! XMP_PropIsSimple ( currProp->options ) ) continue; // Nothing to do if not simple. + + if ( (currProp->name == "dc:creator" ) || // See if it is supposed to be an array. + (currProp->name == "dc:date" ) ) { // *** Think about an array of char* and a loop. + arrayForm = kXMP_PropArrayIsOrdered; + } else if ( + (currProp->name == "dc:description" ) || + (currProp->name == "dc:rights" ) || + (currProp->name == "dc:title" ) ) { + arrayForm = kXMP_PropArrayIsAltText; + } else if ( + (currProp->name == "dc:contributor" ) || + (currProp->name == "dc:language" ) || + (currProp->name == "dc:publisher" ) || + (currProp->name == "dc:relation" ) || + (currProp->name == "dc:subject" ) || + (currProp->name == "dc:type" ) ) { + arrayForm = kXMP_PropValueIsArray; + } + if ( arrayForm == 0 ) continue; // Nothing to do if it isn't supposed to be an array. + + arrayForm = VerifySetOptions ( arrayForm, 0 ); // Set the implicit array bits. + XMP_Node * newArray = new XMP_Node ( dcSchema, currProp->name.c_str(), arrayForm ); + dcSchema->children[propNum] = newArray; + + if ( currProp->value.empty() ) { // Don't add an empty item, leave the array empty. + + delete ( currProp ); + + } else { + + newArray->children.push_back ( currProp ); + currProp->parent = newArray; + currProp->name = kXMP_ArrayItemName; + + if ( XMP_ArrayIsAltText ( arrayForm ) && (! (currProp->options & kXMP_PropHasLang)) ) { + XMP_Node * newLang = new XMP_Node ( currProp, "xml:lang", "x-default", kXMP_PropIsQualifier ); + currProp->options |= (kXMP_PropHasQualifiers | kXMP_PropHasLang); + if ( currProp->qualifiers.empty() ) { // *** Need a util? + currProp->qualifiers.push_back ( newLang ); + } else { + currProp->qualifiers.insert ( currProp->qualifiers.begin(), newLang ); + } + } + + } + + } + +} // NormalizeDCArrays + + +// ------------------------------------------------------------------------------------------------- +// CompareAliasedSubtrees +// ---------------------- + +// *** Change to do some alias-specific setup, then use CompareSubtrees. One special case for +// *** aliases is a simple to x-default alias, the options and qualifiers obviously differ. + +static void +CompareAliasedSubtrees ( XMP_Node * aliasNode, XMP_Node * baseNode, + XMPMeta::ErrorCallbackInfo & errorCallback, bool outerCall = true ) +{ + // ! The outermost call is special. The names almost certainly differ. The qualifiers (and + // ! hence options) will differ for an alias to the x-default item of a langAlt array. + + if ( (aliasNode->value != baseNode->value) || + (aliasNode->children.size() != baseNode->children.size()) ) { + // Keep things simple for now. Aliases are virtually unused, so this is very unlikely to + // happen. Recovery can be added later if it becomes important. + XMP_Error error(kXMPErr_BadXMP, "Mismatch between alias and base nodes"); + errorCallback.NotifyClient ( kXMPErrSev_OperationFatal, error ); + } + + if ( ! outerCall ) { + if ( (aliasNode->name != baseNode->name) || + (aliasNode->options != baseNode->options) || + (aliasNode->qualifiers.size() != baseNode->qualifiers.size()) ) { + // Keep things simple for now. Aliases are virtually unused, so this is very unlikely to + // happen. Recovery can be added later if it becomes important. + XMP_Error error(kXMPErr_BadXMP, "Mismatch between alias and base nodes"); + errorCallback.NotifyClient ( kXMPErrSev_OperationFatal, error ); + } + } + + for ( size_t childNum = 0, childLim = aliasNode->children.size(); childNum < childLim; ++childNum ) { + XMP_Node * aliasChild = aliasNode->children[childNum]; + XMP_Node * baseChild = baseNode->children[childNum]; + CompareAliasedSubtrees ( aliasChild, baseChild, errorCallback, false ); + } + + for ( size_t qualNum = 0, qualLim = aliasNode->qualifiers.size(); qualNum < qualLim; ++qualNum ) { + XMP_Node * aliasQual = aliasNode->qualifiers[qualNum]; + XMP_Node * baseQual = baseNode->qualifiers[qualNum]; + CompareAliasedSubtrees ( aliasQual, baseQual, errorCallback, false ); + } + +} // CompareAliasedSubtrees + + +// ------------------------------------------------------------------------------------------------- +// TransplantArrayItemAlias +// ------------------------ + +static void +TransplantArrayItemAlias ( XMP_Node * oldParent, size_t oldNum, XMP_Node * newParent, + XMPMeta::ErrorCallbackInfo & errorCallback ) +{ + XMP_Node * childNode = oldParent->children[oldNum]; + + if ( newParent->options & kXMP_PropArrayIsAltText ) { + if ( childNode->options & kXMP_PropHasLang ) { + // Keep things simple for now. Aliases are virtually unused, so this is very unlikely to + // happen. Recovery can be added later if it becomes important. + XMP_Error error(kXMPErr_BadXMP, "Alias to x-default already has a language qualifier"); + errorCallback.NotifyClient ( kXMPErrSev_OperationFatal, error ); // *** Allow x-default. + } + childNode->options |= (kXMP_PropHasQualifiers | kXMP_PropHasLang); + XMP_Node * langQual = new XMP_Node ( childNode, "xml:lang", "x-default", kXMP_PropIsQualifier ); // *** AddLangQual util? + if ( childNode->qualifiers.empty() ) { + childNode->qualifiers.push_back ( langQual ); + } else { + childNode->qualifiers.insert ( childNode->qualifiers.begin(), langQual ); + } + } + + oldParent->children.erase ( oldParent->children.begin() + oldNum ); + childNode->name = kXMP_ArrayItemName; + childNode->parent = newParent; + if ( newParent->children.empty() ) { + newParent->children.push_back ( childNode ); + } else { + newParent->children.insert ( newParent->children.begin(), childNode ); + } + +} // TransplantArrayItemAlias + + +// ------------------------------------------------------------------------------------------------- +// TransplantNamedAlias +// -------------------- + +static void +TransplantNamedAlias ( XMP_Node * oldParent, size_t oldNum, XMP_Node * newParent, XMP_VarString & newName ) +{ + XMP_Node * childNode = oldParent->children[oldNum]; + + oldParent->children.erase ( oldParent->children.begin() + oldNum ); + childNode->name = newName; + childNode->parent = newParent; + newParent->children.push_back ( childNode ); + +} // TransplantNamedAlias + + +// ------------------------------------------------------------------------------------------------- +// MoveExplicitAliases +// ------------------- + +static void +MoveExplicitAliases ( XMP_Node * tree, XMP_OptionBits parseOptions, XMPMeta::ErrorCallbackInfo & errorCallback ) +{ + tree->options ^= kXMP_PropHasAliases; + const bool strictAliasing = ((parseOptions & kXMP_StrictAliasing) != 0); + + // Visit all of the top level nodes looking for aliases. If there is no base, transplant the + // alias subtree. If there is a base and strict aliasing is on, make sure the alias and base + // subtrees match. + + // ! Use "while" loops not "for" loops since both the schema and property loops can remove the + // ! current item from the vector being traversed. And don't increment the counter for a delete. + + size_t schemaNum = 0; + while ( schemaNum < tree->children.size() ) { + XMP_Node * currSchema = tree->children[schemaNum]; + + size_t propNum = 0; + while ( propNum < currSchema->children.size() ) { + XMP_Node * currProp = currSchema->children[propNum]; + if ( ! (currProp->options & kXMP_PropIsAlias) ) { + ++propNum; + continue; + } + currProp->options ^= kXMP_PropIsAlias; + + // Find the base path, look for the base schema and root node. + + XMP_AliasMapPos aliasPos = sRegisteredAliasMap->find ( currProp->name ); + XMP_Assert ( aliasPos != sRegisteredAliasMap->end() ); + XMP_ExpandedXPath & basePath = aliasPos->second; + XMP_OptionBits arrayOptions = (basePath[kRootPropStep].options & kXMP_PropArrayFormMask); + + XMP_Node * baseSchema = FindSchemaNode ( tree, basePath[kSchemaStep].step.c_str(), kXMP_CreateNodes ); + if ( baseSchema->options & kXMP_NewImplicitNode ) baseSchema->options ^= kXMP_NewImplicitNode; + XMP_Node * baseNode = FindChildNode ( baseSchema, basePath[kRootPropStep].step.c_str(), kXMP_ExistingOnly ); + + if ( baseNode == 0 ) { + + if ( basePath.size() == 2 ) { + // A top-to-top alias, transplant the property. + TransplantNamedAlias ( currSchema, propNum, baseSchema, basePath[kRootPropStep].step ); + } else { + // An alias to an array item, create the array and transplant the property. + baseNode = new XMP_Node ( baseSchema, basePath[kRootPropStep].step.c_str(), arrayOptions ); + baseSchema->children.push_back ( baseNode ); + TransplantArrayItemAlias ( currSchema, propNum, baseNode, errorCallback ); + } + + } else if ( basePath.size() == 2 ) { + + // The base node does exist and this is a top-to-top alias. Check for conflicts if + // strict aliasing is on. Remove and delete the alias subtree. + if ( strictAliasing ) CompareAliasedSubtrees ( currProp, baseNode, errorCallback ); + currSchema->children.erase ( currSchema->children.begin() + propNum ); + delete currProp; + + } else { + + // This is an alias to an array item and the array exists. Look for the aliased item. + // Then transplant or check & delete as appropriate. + + XMP_Node * itemNode = 0; + if ( arrayOptions & kXMP_PropArrayIsAltText ) { + XMP_Index xdIndex = LookupLangItem ( baseNode, *xdefaultName ); + if ( xdIndex != -1 ) itemNode = baseNode->children[xdIndex]; + } else if ( ! baseNode->children.empty() ) { + itemNode = baseNode->children[0]; + } + + if ( itemNode == 0 ) { + TransplantArrayItemAlias ( currSchema, propNum, baseNode, errorCallback ); + } else { + if ( strictAliasing ) CompareAliasedSubtrees ( currProp, itemNode, errorCallback ); + currSchema->children.erase ( currSchema->children.begin() + propNum ); + delete currProp; + } + + } + + } // Property loop + + // Increment the counter or remove an empty schema node. + if ( currSchema->children.size() > 0 ) { + ++schemaNum; + } else { + delete tree->children[schemaNum]; // ! Delete the schema node itself. + tree->children.erase ( tree->children.begin() + schemaNum ); + } + + } // Schema loop + +} // MoveExplicitAliases + + +// ------------------------------------------------------------------------------------------------- +// FixGPSTimeStamp +// --------------- + +static void +FixGPSTimeStamp ( XMP_Node * exifSchema, XMP_Node * gpsDateTime ) +{ + XMP_DateTime binGPSStamp; + try { + XMPUtils::ConvertToDate ( gpsDateTime->value.c_str(), &binGPSStamp ); + } catch ( ... ) { + return; // Don't let a bad date stop other things. + } + if ( (binGPSStamp.year != 0) || (binGPSStamp.month != 0) || (binGPSStamp.day != 0) ) return; + + XMP_Node * otherDate = FindChildNode ( exifSchema, "exif:DateTimeOriginal", kXMP_ExistingOnly ); + if ( otherDate == 0 ) otherDate = FindChildNode ( exifSchema, "exif:DateTimeDigitized", kXMP_ExistingOnly ); + if ( otherDate == 0 ) return; + + XMP_DateTime binOtherDate; + try { + XMPUtils::ConvertToDate ( otherDate->value.c_str(), &binOtherDate ); + } catch ( ... ) { + return; // Don't let a bad date stop other things. + } + + binGPSStamp.year = binOtherDate.year; + binGPSStamp.month = binOtherDate.month; + binGPSStamp.day = binOtherDate.day; + + XMPUtils::ConvertFromDate ( binGPSStamp, &gpsDateTime->value ); + +} // FixGPSTimeStamp + + +// ------------------------------------------------------------------------------------------------- +// MigrateAudioCopyright +// --------------------- +// +// The initial support for WAV files mapped a legacy ID3 audio copyright into a new xmpDM:copyright +// property. This is special case code to migrate that into dc:rights['x-default']. The rules: +// +// 1. If there is no dc:rights array, or an empty array - +// Create one with dc:rights['x-default'] set from double linefeed and xmpDM:copyright. +// +// 2. If there is a dc:rights array but it has no x-default item - +// Create an x-default item as a copy of the first item then apply rule #3. +// +// 3. If there is a dc:rights array with an x-default item, look for a double linefeed in the value. +// A. If no double linefeed, compare the x-default value to the xmpDM:copyright value. +// A1. If they match then leave the x-default value alone. +// A2. Otherwise, append a double linefeed and the xmpDM:copyright value to the x-default value. +// B. If there is a double linefeed, compare the trailing text to the xmpDM:copyright value. +// B1. If they match then leave the x-default value alone. +// B2. Otherwise, replace the trailing x-default text with the xmpDM:copyright value. +// +// 4. In all cases, delete the xmpDM:copyright property. + +static void +MigrateAudioCopyright ( XMPMeta * xmp, XMP_Node * dmCopyright ) +{ + + try { + + std::string & dmValue = dmCopyright->value; + static const char * kDoubleLF = "\xA\xA"; + + XMP_Node * dcSchema = FindSchemaNode ( &xmp->tree, kXMP_NS_DC, kXMP_CreateNodes ); + XMP_Node * dcRightsArray = FindChildNode ( dcSchema, "dc:rights", kXMP_ExistingOnly ); + + if ( (dcRightsArray == 0) || dcRightsArray->children.empty() ) { + + // 1. No dc:rights array, create from double linefeed and xmpDM:copyright. + dmValue.insert ( 0, kDoubleLF ); + xmp->SetLocalizedText ( kXMP_NS_DC, "rights", "", "x-default", dmValue.c_str(), 0 ); + + } else { + + std::string xdefaultStr ( "x-default" ); + + XMP_Index xdIndex = LookupLangItem ( dcRightsArray, xdefaultStr ); + + if ( xdIndex < 0 ) { + // 2. No x-default item, create from the first item. + XMP_StringPtr firstValue = dcRightsArray->children[0]->value.c_str(); + xmp->SetLocalizedText ( kXMP_NS_DC, "rights", "", "x-default", firstValue, 0 ); + xdIndex = LookupLangItem ( dcRightsArray, xdefaultStr ); + } + + // 3. Look for a double linefeed in the x-default value. + XMP_Assert ( xdIndex == 0 ); + std::string & defaultValue = dcRightsArray->children[xdIndex]->value; + XMP_Index lfPos = defaultValue.find ( kDoubleLF ); + + if ( lfPos < 0 ) { + + // 3A. No double LF, compare whole values. + if ( dmValue != defaultValue ) { + // 3A2. Append the xmpDM:copyright to the x-default item. + defaultValue += kDoubleLF; + defaultValue += dmValue; + } + + } else { + + // 3B. Has double LF, compare the tail. + if ( defaultValue.compare ( lfPos+2, std::string::npos, dmValue ) != 0 ) { + // 3B2. Replace the x-default tail. + defaultValue.replace ( lfPos+2, std::string::npos, dmValue ); + } + + } + + } + + // 4. Get rid of the xmpDM:copyright. + xmp->DeleteProperty ( kXMP_NS_DM, "copyright" ); + + } catch ( ... ) { + // Don't let failures (like a bad dc:rights form) stop other cleanup. + } + +} // MigrateAudioCopyright + + +// ------------------------------------------------------------------------------------------------- +// RepairAltText +// ------------- +// +// Make sure that the array is well-formed AltText. Each item must be simple and have an xml:lang +// qualifier. If repairs are needed, keep simple non-empty items by adding the xml:lang. + +static void +RepairAltText ( XMP_Node & tree, XMP_StringPtr schemaNS, XMP_StringPtr arrayName ) +{ + XMP_Node * schemaNode = FindSchemaNode ( &tree, schemaNS, kXMP_ExistingOnly ); + if ( schemaNode == 0 ) return; + + XMP_Node * arrayNode = FindChildNode ( schemaNode, arrayName, kXMP_ExistingOnly ); + if ( (arrayNode == 0) || XMP_ArrayIsAltText ( arrayNode->options ) ) return; // Already OK. + + if ( ! XMP_PropIsArray ( arrayNode->options ) ) return; // ! Not even an array, leave it alone. + // *** Should probably change simple values to LangAlt with 'x-default' item. + + arrayNode->options |= (kXMP_PropArrayIsOrdered | kXMP_PropArrayIsAlternate | kXMP_PropArrayIsAltText); + + for ( int i = arrayNode->children.size()-1; i >= 0; --i ) { // ! Need a signed index type. + + XMP_Node * currChild = arrayNode->children[i]; + + if ( ! XMP_PropIsSimple ( currChild->options ) ) { + + // Delete non-simple children. + delete ( currChild ); + arrayNode->children.erase ( arrayNode->children.begin() + i ); + + } else if ( ! XMP_PropHasLang ( currChild->options ) ) { + + if ( currChild->value.empty() ) { + + // Delete empty valued children that have no xml:lang. + delete ( currChild ); + arrayNode->children.erase ( arrayNode->children.begin() + i ); + + } else { + + // Add an xml:lang qualifier with the value "x-repair". + XMP_Node * repairLang = new XMP_Node ( currChild, "xml:lang", "x-repair", kXMP_PropIsQualifier ); + if ( currChild->qualifiers.empty() ) { + currChild->qualifiers.push_back ( repairLang ); + } else { + currChild->qualifiers.insert ( currChild->qualifiers.begin(), repairLang ); + } + currChild->options |= (kXMP_PropHasQualifiers | kXMP_PropHasLang); + + } + + } + + } + +} // RepairAltText + + +// ------------------------------------------------------------------------------------------------- +// TouchUpDataModel +// ---------------- + +static void +TouchUpDataModel ( XMPMeta * xmp, XMPMeta::ErrorCallbackInfo & errorCallback ) +{ + XMP_Node & tree = xmp->tree; + + // Do special case touch ups for certain schema. + + XMP_Node * currSchema = 0; + + currSchema = FindSchemaNode ( &tree, kXMP_NS_EXIF, kXMP_ExistingOnly ); + if ( currSchema != 0 ) { + + // Do a special case fix for exif:GPSTimeStamp. + XMP_Node * gpsDateTime = FindChildNode ( currSchema, "exif:GPSTimeStamp", kXMP_ExistingOnly ); + if ( gpsDateTime != 0 ) FixGPSTimeStamp ( currSchema, gpsDateTime ); + + // *** Should probably have RepairAltText change simple values to LangAlt with 'x-default' item. + // *** For now just do this for exif:UserComment, the one case we know about, late in cycle fix. + XMP_Node * userComment = FindChildNode ( currSchema, "exif:UserComment", kXMP_ExistingOnly ); + if ( (userComment != 0) && XMP_PropIsSimple ( userComment->options ) ) { + XMP_Node * newChild = new XMP_Node ( userComment, kXMP_ArrayItemName, + userComment->value.c_str(), userComment->options ); + newChild->qualifiers.swap ( userComment->qualifiers ); + if ( ! XMP_PropHasLang ( newChild->options ) ) { + XMP_Node * langQual = new XMP_Node ( newChild, "xml:lang", "x-default", kXMP_PropIsQualifier ); + newChild->qualifiers.insert ( newChild->qualifiers.begin(), langQual ); + newChild->options |= (kXMP_PropHasQualifiers | kXMP_PropHasLang); + } + userComment->value.erase(); + userComment->options = kXMP_PropArrayFormMask; // ! Happens to have all the right bits. + userComment->children.push_back ( newChild ); + } + + } + + currSchema = FindSchemaNode ( &tree, kXMP_NS_DM, kXMP_ExistingOnly ); + if ( currSchema != 0 ) { + // Do a special case migration of xmpDM:copyright to dc:rights['x-default']. Do this before + // the dc: touch up since it can affect the dc: schema. + XMP_Node * dmCopyright = FindChildNode ( currSchema, "xmpDM:copyright", kXMP_ExistingOnly ); + if ( dmCopyright != 0 ) MigrateAudioCopyright ( xmp, dmCopyright ); + } + + currSchema = FindSchemaNode ( &tree, kXMP_NS_DC, kXMP_ExistingOnly ); + if ( currSchema != 0 ) { + // Do a special case fix for dc:subject, make sure it is an unordered array. + XMP_Node * dcSubject = FindChildNode ( currSchema, "dc:subject", kXMP_ExistingOnly ); + if ( dcSubject != 0 ) { + XMP_OptionBits keepMask = ~(kXMP_PropArrayIsOrdered | kXMP_PropArrayIsAlternate | kXMP_PropArrayIsAltText); + dcSubject->options &= keepMask; // Make sure any ordered array bits are clear. + } + } + + // Fix any broken AltText arrays that we know about. + + RepairAltText ( tree, kXMP_NS_DC, "dc:description" ); // ! Note inclusion of prefixes for direct node lookup! + RepairAltText ( tree, kXMP_NS_DC, "dc:rights" ); + RepairAltText ( tree, kXMP_NS_DC, "dc:title" ); + RepairAltText ( tree, kXMP_NS_XMP_Rights, "xmpRights:UsageTerms" ); + RepairAltText ( tree, kXMP_NS_EXIF, "exif:UserComment" ); + + // Tweak old XMP: Move an instance ID from rdf:about to the xmpMM:InstanceID property. An old + // instance ID usually looks like "uuid:bac965c4-9d87-11d9-9a30-000d936b79c4", plus InDesign + // 3.0 wrote them like "bac965c4-9d87-11d9-9a30-000d936b79c4". If the name looks like a UUID + // simply move it to xmpMM:InstanceID, don't worry about any existing xmpMM:InstanceID. Both + // will only be present when a newer file with the xmpMM:InstanceID property is updated by an + // old app that uses rdf:about. + + if ( ! tree.name.empty() ) { + + bool nameIsUUID = false; + XMP_StringPtr nameStr = tree.name.c_str(); + + if ( XMP_LitNMatch ( nameStr, "uuid:", 5 ) ) { + + nameIsUUID = true; + + } else if ( tree.name.size() == 36 ) { + + nameIsUUID = true; // ! Assume true, we'll set it to false below if not. + for ( int i = 0; i < 36; ++i ) { + char ch = nameStr[i]; + if ( ch == '-' ) { + if ( (i == 8) || (i == 13) || (i == 18) || (i == 23) ) continue; + nameIsUUID = false; + break; + } else { + if ( (('0' <= ch) && (ch <= '9')) || (('a' <= ch) && (ch <= 'z')) ) continue; + nameIsUUID = false; + break; + } + } + + } + + if ( nameIsUUID ) { + + XMP_ExpandedXPath expPath; + ExpandXPath ( kXMP_NS_XMP_MM, "InstanceID", &expPath ); + XMP_Node * idNode = FindNode ( &tree, expPath, kXMP_CreateNodes, 0 ); + if ( idNode == 0 ) XMP_Throw ( "Failure creating xmpMM:InstanceID", kXMPErr_InternalFailure ); + + idNode->options = 0; // Clobber any existing xmpMM:InstanceID. + idNode->value = tree.name; + idNode->RemoveChildren(); + idNode->RemoveQualifiers(); + + tree.name.erase(); + + } + + } + +} // TouchUpDataModel + + +// ------------------------------------------------------------------------------------------------- +// DetermineInputEncoding +// ---------------------- +// +// Try to determine the character encoding, making a guess if the input is too short. We make some +// simplifying assumtions: the first character must be U+FEFF or ASCII, U+0000 is not allowed. The +// XML 1.1 spec is even more strict, UTF-16 XML documents must begin with U+FEFF, and the first +// "real" character must be '<'. Ignoring the XML declaration, the first XML character could be '<', +// space, tab, CR, or LF. +// +// The possible input sequences are: +// +// Cases with U+FEFF +// EF BB BF -- - UTF-8 +// FE FF -- -- - Big endian UTF-16 +// 00 00 FE FF - Big endian UTF 32 +// FF FE 00 00 - Little endian UTF-32 +// FF FE -- -- - Little endian UTF-16 +// +// Cases with ASCII +// nn mm -- -- - UTF-8 - +// 00 00 00 nn - Big endian UTF-32 +// 00 nn -- -- - Big endian UTF-16 +// nn 00 00 00 - Little endian UTF-32 +// nn 00 -- -- - Little endian UTF-16 +// +// ! We don't check for full patterns, or for errors. We just check enough to determine what the +// ! only possible (or reasonable) case would be. + +static XMP_OptionBits +DetermineInputEncoding ( const XMP_Uns8 * buffer, size_t length ) +{ + if ( length < 2 ) return kXMP_EncodeUTF8; + + XMP_Uns8 * uniChar = (XMP_Uns8*)buffer; // ! Make sure comparisons are unsigned. + + if ( uniChar[0] == 0 ) { + + // These cases are: + // 00 nn -- -- - Big endian UTF-16 + // 00 00 00 nn - Big endian UTF-32 + // 00 00 FE FF - Big endian UTF 32 + + if ( (length < 4) || (uniChar[1] != 0) ) return kXMP_EncodeUTF16Big; + return kXMP_EncodeUTF32Big; + + } else if ( uniChar[0] < 0x80 ) { + + // These cases are: + // nn mm -- -- - UTF-8, includes EF BB BF case + // nn 00 00 00 - Little endian UTF-32 + // nn 00 -- -- - Little endian UTF-16 + + if ( uniChar[1] != 0 ) return kXMP_EncodeUTF8; + if ( (length < 4) || (uniChar[2] != 0) ) return kXMP_EncodeUTF16Little; + return kXMP_EncodeUTF32Little; + + } else { + + // These cases are: + // EF BB BF -- - UTF-8 + // FE FF -- -- - Big endian UTF-16 + // FF FE 00 00 - Little endian UTF-32 + // FF FE -- -- - Little endian UTF-16 + + if ( uniChar[0] == 0xEF ) return kXMP_EncodeUTF8; + if ( uniChar[0] == 0xFE ) return kXMP_EncodeUTF16Big; + if ( (length < 4) || (uniChar[2] != 0) ) return kXMP_EncodeUTF16Little; + return kXMP_EncodeUTF32Little; + + } + +} // DetermineInputEncoding + + +// ------------------------------------------------------------------------------------------------- +// CountUTF8 +// --------- +// +// Look for a valid multi-byte UTF-8 sequence and return its length. Returns 0 for an invalid UTF-8 +// sequence. Returns a negative value for a partial valid sequence at the end of the buffer. +// +// The checking is not strict. We simply count the number of high order 1 bits in the first byte, +// then look for n-1 following bytes whose high order 2 bits are 1 and 0. We do not check for a +// minimal length representation of the codepoint, or that the codepoint is defined by Unicode. + +static int +CountUTF8 ( const XMP_Uns8 * charStart, const XMP_Uns8 * bufEnd ) +{ + XMP_Assert ( charStart < bufEnd ); // Catch this in debug builds. + if ( charStart >= bufEnd ) return 0; // Don't run-on in release builds. + if ( (*charStart & 0xC0) != 0xC0 ) return 0; // Must have at least 2 high bits set. + + int byteCount = 2; + XMP_Uns8 firstByte = *charStart; + for ( firstByte = firstByte << 2; (firstByte & 0x80) != 0; firstByte = firstByte << 1 ) ++byteCount; + + if ( (charStart + byteCount) > bufEnd ) return -byteCount; + + for ( int i = 1; i < byteCount; ++i ) { + if ( (charStart[i] & 0xC0) != 0x80 ) return 0; + } + + return byteCount; + +} // CountUTF8 + + +// ------------------------------------------------------------------------------------------------- +// CountControlEscape +// ------------------ +// +// Look for a numeric escape sequence for a "prohibited" ASCII control character. These are 0x7F, +// and the range 0x00..0x1F except for tab/LF/CR. Return 0 if this is definitely not a numeric +// escape, the length of the escape if found, or a negative value for a partial escape. + +static int +CountControlEscape ( const XMP_Uns8 * escStart, const XMP_Uns8 * bufEnd ) +{ + XMP_Assert ( escStart < bufEnd ); // Catch this in debug builds. + if ( escStart >= bufEnd ) return 0; // Don't run-on in release builds. + XMP_Assert ( *escStart == '&' ); + + size_t tailLen = bufEnd - escStart; + if ( tailLen < 5 ) return -1; // Don't need a more thorough check, we'll catch it on the next pass. + + if ( strncmp ( (char*)escStart, "&#x", 3 ) != 0 ) return 0; + + XMP_Uns8 escValue = 0; + const XMP_Uns8 * escPos = escStart + 3; + + if ( ('0' <= *escPos) && (*escPos <= '9') ) { + escValue = *escPos - '0'; + ++escPos; + } else if ( ('A' <= *escPos) && (*escPos <= 'F') ) { + escValue = *escPos - 'A' + 10; + ++escPos; + } else if ( ('a' <= *escPos) && (*escPos <= 'f') ) { + escValue = *escPos - 'a' + 10; + ++escPos; + } + + if ( ('0' <= *escPos) && (*escPos <= '9') ) { + escValue = (escValue << 4) + (*escPos - '0'); + ++escPos; + } else if ( ('A' <= *escPos) && (*escPos <= 'F') ) { + escValue = (escValue << 4) + (*escPos - 'A' + 10); + ++escPos; + } else if ( ('a' <= *escPos) && (*escPos <= 'f') ) { + escValue = (escValue << 4) + (*escPos - 'a' + 10); + ++escPos; + } + + if ( escPos == bufEnd ) return -1; // Partial escape. + if ( *escPos != ';' ) return 0; + + size_t escLen = escPos - escStart + 1; + if ( escLen < 5 ) return 0; // ! Catch "&#x;". + + if ( (escValue == kTab) || (escValue == kLF) || (escValue == kCR) ) return 0; // An allowed escape. + + return escLen; // Found a full "prohibited" numeric escape. + +} // CountControlEscape + + +// ------------------------------------------------------------------------------------------------- +// ProcessUTF8Portion +// ------------------ +// +// Early versions of the XMP spec mentioned allowing ISO Latin-1 input. There are also problems with +// some clients placing ASCII control characters within XMP values. This is an XML problem, the XML +// spec only allows tab (0x09), LF (0x0A), and CR (0x0D) from the 0x00..0x1F range. As a concession +// to this we scan 8-bit input for byte sequences that are not valid UTF-8 or in the 0x00..0x1F +// range and replace each byte as follows: +// 0x00..0x1F - Replace with a space, except for tab, CR, and LF. +// 0x7F - Replace with a space. This is ASCII Delete, not allowed by ISO Latin-1. +// 0x80..0x9F - Replace with the UTF-8 for a corresponding Unicode character. +// 0xA0..0XFF - Replace with the UTF-8 for a corresponding Unicode character. +// +// The 0x80..0x9F range is not defined by Latin-1. But the Windows 1252 code page defines these and +// is otherwise the same as Latin-1. +// +// For at least historical compatibility reasons we also find and replace singly escaped ASCII +// control characters. The Expat parser we're using does not allow numeric escapes like "". +// The XML spec is clear that raw controls are not allowed (in the RestrictedChar set), but it isn't +// as clear about numeric escapes for them. At any rate, Expat complains, so we treat the numeric +// escapes like raw characters and replace them with a space. +// +// We check for 1 or 2 hex digits (" " or " ") and upper or lower case (" " or " "). +// The full escape sequence is 5 or 6 bytes. + +static size_t +ProcessUTF8Portion ( XMLParserAdapter * xmlParser, + const XMP_Uns8 * buffer, + size_t length, + bool last ) +{ + const XMP_Uns8 * bufEnd = buffer + length; + + const XMP_Uns8 * spanStart = buffer; + const XMP_Uns8 * spanEnd; + + for ( spanEnd = spanStart; spanEnd < bufEnd; ++spanEnd ) { + + if ( (0x20 <= *spanEnd) && (*spanEnd <= 0x7E) && (*spanEnd != '&') ) continue; // A regular ASCII character. + + if ( *spanEnd >= 0x80 ) { + + // See if this is a multi-byte UTF-8 sequence, or a Latin-1 character to replace. + + int uniLen = CountUTF8 ( spanEnd, bufEnd ); + + if ( uniLen > 0 ) { + + // A valid UTF-8 character, keep it as-is. + spanEnd += uniLen - 1; // ! The loop increment will put back the +1. + + } else if ( (uniLen < 0) && (! last) ) { + + // Have a partial UTF-8 character at the end of the buffer and more input coming. + xmlParser->ParseBuffer ( spanStart, (spanEnd - spanStart), false ); + return (spanEnd - buffer); + + } else { + + // Not a valid UTF-8 sequence. Replace the first byte with the Latin-1 equivalent. + xmlParser->ParseBuffer ( spanStart, (spanEnd - spanStart), false ); + const char * replacement = kReplaceLatin1 [ *spanEnd - 0x80 ]; + xmlParser->ParseBuffer ( replacement, strlen ( replacement ), false ); + spanStart = spanEnd + 1; // ! The loop increment will do "spanEnd = spanStart". + + } + + } else if ( (*spanEnd < 0x20) || (*spanEnd == 0x7F) ) { + + // Replace ASCII controls other than tab, LF, and CR with a space. + + if ( (*spanEnd == kTab) || (*spanEnd == kLF) || (*spanEnd == kCR) ) continue; + + xmlParser->ParseBuffer ( spanStart, (spanEnd - spanStart), false ); + xmlParser->ParseBuffer ( " ", 1, false ); + spanStart = spanEnd + 1; // ! The loop increment will do "spanEnd = spanStart". + + } else { + + // See if this is a numeric escape sequence for a prohibited ASCII control. + + XMP_Assert ( *spanEnd == '&' ); + int escLen = CountControlEscape ( spanEnd, bufEnd ); + + if ( escLen < 0 ) { + + // Have a partial numeric escape in this buffer, wait for more input. + if ( last ) continue; // No more buffers, not an escape, absorb as normal input. + xmlParser->ParseBuffer ( spanStart, (spanEnd - spanStart), false ); + return (spanEnd - buffer); + + } else if ( escLen > 0 ) { + + // Have a complete numeric escape to replace. + xmlParser->ParseBuffer ( spanStart, (spanEnd - spanStart), false ); + xmlParser->ParseBuffer ( " ", 1, false ); + spanStart = spanEnd + escLen; + spanEnd = spanStart - 1; // ! The loop continuation will increment spanEnd! + + } + + } + + } + + XMP_Assert ( spanEnd == bufEnd ); + + if ( spanStart < bufEnd ) xmlParser->ParseBuffer ( spanStart, (spanEnd - spanStart), false ); + if ( last ) xmlParser->ParseBuffer ( " ", 1, true ); + + return length; + +} // ProcessUTF8Portion + + +// ------------------------------------------------------------------------------------------------- +// ProcessXMLBuffer +// ---------------- +// +// Process one buffer of XML. Returns false if this input is put into the pending input buffer. + +bool XMPMeta::ProcessXMLBuffer ( XMP_StringPtr buffer, XMP_StringLen xmpSize, bool lastClientCall ) +{ + + // Determine the character encoding before doing any real parsing. This is needed to do the + // 8-bit special processing. This has to be checked on every call, not just the first, in + // order to handle the edge case of single byte buffers. + + XMLParserAdapter & parser = *this->xmlParser; + + if ( parser.charEncoding == XMP_OptionBits(-1) ) { + + if ( (parser.pendingCount == 0) && (xmpSize >= kXMLPendingInputMax) ) { + + // This ought to be the common case, the first buffer is big enough. + parser.charEncoding = DetermineInputEncoding ( (XMP_Uns8*)buffer, xmpSize ); + + } else { + + // Try to fill the pendingInput buffer before calling DetermineInputEncoding. + + size_t pendingOverlap = kXMLPendingInputMax - parser.pendingCount; + if ( pendingOverlap > xmpSize ) pendingOverlap = xmpSize; + + memcpy ( &parser.pendingInput[parser.pendingCount], buffer, pendingOverlap ); // AUDIT: Count is safe. + buffer += pendingOverlap; + xmpSize -= pendingOverlap; + parser.pendingCount += pendingOverlap; + + if ( (! lastClientCall) && (parser.pendingCount < kXMLPendingInputMax) ) return false; // Wait for the next buffer. + parser.charEncoding = DetermineInputEncoding ( parser.pendingInput, parser.pendingCount ); + + #if Trace_ParsingHackery + fprintf ( stderr, "XMP Character encoding is %d\n", parser.charEncoding ); + #endif + + } + + } + + // We have the character encoding. Process UTF-16 and UTF-32 as is. UTF-8 needs special + // handling to take care of things like ISO Latin-1 or unescaped ASCII controls. + + XMP_Assert ( parser.charEncoding != XMP_OptionBits(-1) ); + + if ( parser.charEncoding != kXMP_EncodeUTF8 ) { + + if ( parser.pendingCount > 0 ) { + // Might have pendingInput from the above portion to determine the character encoding. + parser.ParseBuffer ( parser.pendingInput, parser.pendingCount, false ); + } + parser.ParseBuffer ( buffer, xmpSize, lastClientCall ); + + } else { + + #if Trace_ParsingHackery + fprintf ( stderr, "Parsing %d bytes @ %.8X, %s, %d pending, context: %.8s\n", + xmpSize, buffer, (lastClientCall ? "last" : "not last"), parser.pendingCount, buffer ); + #endif + + // The UTF-8 processing is a bit complex due to the need to tolerate ISO Latin-1 input. + // This is done by scanning the input for byte sequences that are not valid UTF-8, + // assuming they are Latin-1 characters in the range 0x80..0xFF. This requires saving a + // pending input buffer to handle partial UTF-8 sequences at the end of a buffer. + + while ( parser.pendingCount > 0 ) { + + // We've got some leftover input, process it first then continue with the current + // buffer. Try to fill the pendingInput buffer before parsing further. We use a loop + // for wierd edge cases like a 2 byte input buffer, using 1 byte for pendingInput, + // then having a partial UTF-8 end and need to absorb more. + + size_t pendingOverlap = kXMLPendingInputMax - parser.pendingCount; + if ( pendingOverlap > xmpSize ) pendingOverlap = xmpSize; + + memcpy ( &parser.pendingInput[parser.pendingCount], buffer, pendingOverlap ); // AUDIT: Count is safe. + parser.pendingCount += pendingOverlap; + buffer += pendingOverlap; + xmpSize -= pendingOverlap; + + if ( (! lastClientCall) && (parser.pendingCount < kXMLPendingInputMax) ) return false; // Wait for the next buffer. + size_t bytesDone = ProcessUTF8Portion ( xmlParser, parser.pendingInput, parser.pendingCount, lastClientCall ); + size_t bytesLeft = parser.pendingCount - bytesDone; + + #if Trace_ParsingHackery + fprintf ( stderr, " ProcessUTF8Portion handled %d pending bytes\n", bytesDone ); + #endif + + if ( bytesDone == parser.pendingCount ) { + + // Done with all of the pending input, move on to the current buffer. + parser.pendingCount = 0; + + } else if ( bytesLeft <= pendingOverlap ) { + + // The leftover pending input all came from the current buffer. Exit this loop. + buffer -= bytesLeft; + xmpSize += bytesLeft; + parser.pendingCount = 0; + + } else if ( xmpSize > 0 ) { + + // Pull more of the current buffer into the pending input and try again. + // Backup by this pass's overlap so the loop entry code runs OK. + parser.pendingCount -= pendingOverlap; + buffer -= pendingOverlap; + xmpSize += pendingOverlap; + + } else { + + // There is no more of the current buffer. Wait for more. Partial sequences at + // the end of the last buffer should be treated as Latin-1 by ProcessUTF8Portion. + XMP_Assert ( ! lastClientCall ); + parser.pendingCount = bytesLeft; + memcpy ( &parser.pendingInput[0], &parser.pendingInput[bytesDone], bytesLeft ); // AUDIT: Count is safe. + return false; // Wait for the next buffer. + + } + + } + + // Done with the pending input, process the current buffer. + + size_t bytesDone = ProcessUTF8Portion ( xmlParser, (XMP_Uns8*)buffer, xmpSize, lastClientCall ); + + #if Trace_ParsingHackery + fprintf ( stderr, " ProcessUTF8Portion handled %d additional bytes\n", bytesDone ); + #endif + + if ( bytesDone < xmpSize ) { + + XMP_Assert ( ! lastClientCall ); + size_t bytesLeft = xmpSize - bytesDone; + if ( bytesLeft > kXMLPendingInputMax ) XMP_Throw ( "Parser bytesLeft too large", kXMPErr_InternalFailure ); + + memcpy ( parser.pendingInput, &buffer[bytesDone], bytesLeft ); // AUDIT: Count is safe. + parser.pendingCount = bytesLeft; + return false; // Wait for the next buffer. + + } + + } + + return true; // This buffer has been processed. + +} // ProcessXMLBuffer + + +// ------------------------------------------------------------------------------------------------- +// ProcessXMLTree +// -------------- + +void XMPMeta::ProcessXMLTree ( XMP_OptionBits options ) +{ + + #if XMP_DebugBuild && DumpXMLParseTree + if ( this->xmlParser->parseLog == 0 ) this->xmlParser->parseLog = stdout; + DumpXMLTree ( this->xmlParser->parseLog, this->xmlParser->tree, 0 ); + #endif + + const XML_Node * xmlRoot = FindRootNode ( *this->xmlParser, options ); + + if ( xmlRoot != 0 ) { + + this->ProcessRDF ( *xmlRoot, options ); + + NormalizeDCArrays ( &this->tree ); + if ( this->tree.options & kXMP_PropHasAliases ) MoveExplicitAliases ( &this->tree, options, this->errorCallback ); + TouchUpDataModel ( this, this->errorCallback ); + + // Delete empty schema nodes. Do this last, other cleanup can make empty schema. + size_t schemaNum = 0; + while ( schemaNum < this->tree.children.size() ) { + XMP_Node * currSchema = this->tree.children[schemaNum]; + if ( currSchema->children.size() > 0 ) { + ++schemaNum; + } else { + delete this->tree.children[schemaNum]; // ! Delete the schema node itself. + this->tree.children.erase ( this->tree.children.begin() + schemaNum ); + } + } + + } + +} // ProcessXMLTree + + +// ------------------------------------------------------------------------------------------------- +// ParseFromBuffer +// --------------- +// +// Although most clients will probably parse everything in one call, we have a buffered API model +// and need to support even the extreme case of 1 byte at a time parsing. This is considerably +// complicated by some special cases for 8-bit input. Because of this, the first thing we do is +// determine whether the input is 8-bit, UTF-16, or UTF-32. +// +// Both the 8-bit special cases and the encoding determination are easier to do with 8 bytes or more +// of input. The XMLParserAdapter class has a pending-input buffer for this. At the start of parsing +// we (might) try to fill this buffer before determining the input character encoding. After that, +// we (might) use this buffer with the current input to simplify the logic in Process8BitInput. The +// "(might)" part means that we don't actually use the pending-input buffer unless we have to. In +// particular, the common case of single-buffer parsing won't use it. + +void +XMPMeta::ParseFromBuffer ( XMP_StringPtr buffer, + XMP_StringLen xmpSize, + XMP_OptionBits options ) +{ + if ( (buffer == 0) && (xmpSize != 0) ) XMP_Throw ( "Null parse buffer", kXMPErr_BadParam ); + if ( xmpSize == kXMP_UseNullTermination ) xmpSize = strlen ( buffer ); + + const bool lastClientCall = ((options & kXMP_ParseMoreBuffers) == 0); // *** Could use FlagIsSet & FlagIsClear macros. + + if ( this->xmlParser == 0 ) { + this->tree.ClearNode(); // Make sure the target XMP object is totally empty. + if ( (xmpSize == 0) && lastClientCall ) return; // Tolerate empty parse. Expat complains if there are no XML elements. + this->xmlParser = XMP_NewExpatAdapter ( ExpatAdapter::kUseGlobalNamespaces ); + this->xmlParser->SetErrorCallback ( &this->errorCallback ); + } + + try { // Cleanup the tree and xmlParser if anything fails. + + bool done = this->ProcessXMLBuffer ( buffer, xmpSize, lastClientCall ); + if ( ! done ) return; // Wait for the next buffer. + + if ( lastClientCall ) { + this->ProcessXMLTree ( options ); + delete this->xmlParser; + this->xmlParser = 0; + } + + } catch ( ... ) { + + delete this->xmlParser; + this->xmlParser = 0; + throw; + + } + +} // ParseFromBuffer + +// ================================================================================================= diff --git a/XMPCore/source/XMPMeta-Serialize.cpp b/XMPCore/source/XMPMeta-Serialize.cpp new file mode 100644 index 0000000..ef30789 --- /dev/null +++ b/XMPCore/source/XMPMeta-Serialize.cpp @@ -0,0 +1,1388 @@ +// ================================================================================================= +// Copyright 2003-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. +// +// 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 "public/include/XMP_Version.h" +#include "source/UnicodeInlines.incl_cpp" +#include "source/UnicodeConversions.hpp" +#include "third-party/zuid/interfaces/MD5.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 +// ========================= + +static const char * kPacketHeader = ""; +static const char * kPacketTrailer = ""; // ! The w/r is at [size-4]. + +//static const char * kTXMP_SchemaGroup = "XMP_SchemaGroup"; + +static const char * kRDF_XMPMetaStart = ""; +static const char * kRDF_RDFEnd = ""; + +static const char * kRDF_SchemaStart = ""; + +static const char * kRDF_StructStart = ""; +static const char * kRDF_StructEnd = ""; + +static const char * kRDF_BagStart = ""; +//static const char * kRDF_BagEnd = ""; + +//static const char * kRDF_SeqStart = ""; +//static const char * kRDF_SeqEnd = ""; + +//static const char * kRDF_AltStart = ""; +//static const char * kRDF_AltEnd = ""; + +static const char * kRDF_ItemStart = ""; +//static const char * kRDF_ItemEnd = ""; + +static const char * kRDF_ValueStart = ""; +//static const char * kRDF_ValueEnd = ""; + + +// ================================================================================================= +// Static Variables +// ================ + + +// ================================================================================================= +// Local Utilities +// =============== + + +// ------------------------------------------------------------------------------------------------- +// EstimateRDFSize +// --------------- + +// *** Pull the strlen(kXyz) calls into constants. + +static size_t +EstimateRDFSize ( const XMP_Node * currNode, XMP_Index indent, size_t indentLen ) +{ + size_t outputLen = 2 * (indent*indentLen + currNode->name.size() + 4); // The property element tags. + + if ( ! currNode->qualifiers.empty() ) { + // This node has qualifiers, assume it is written using rdf:value and estimate the qualifiers. + + indent += 2; // Everything else is indented inside the rdf:Description element. + outputLen += 2 * ((indent-1)*indentLen + strlen(kRDF_StructStart) + 2); // The rdf:Description tags. + outputLen += 2 * (indent*indentLen + strlen(kRDF_ValueStart) + 2); // The rdf:value tags. + + for ( size_t qualNum = 0, qualLim = currNode->qualifiers.size(); qualNum < qualLim; ++qualNum ) { + const XMP_Node * currQual = currNode->qualifiers[qualNum]; + outputLen += EstimateRDFSize ( currQual, indent, indentLen ); + } + + } + + if ( currNode->options & kXMP_PropValueIsStruct ) { + indent += 1; + outputLen += 2 * (indent*indentLen + strlen(kRDF_StructStart) + 2); // The rdf:Description tags. + } else if ( currNode->options & kXMP_PropValueIsArray ) { + indent += 2; + outputLen += 2 * ((indent-1)*indentLen + strlen(kRDF_BagStart) + 2); // The rdf:Bag/Seq/Alt tags. + outputLen += 2 * currNode->children.size() * (strlen(kRDF_ItemStart) + 2); // The rdf:li tags, indent counted in children. + } else if ( ! (currNode->options & kXMP_SchemaNode) ) { + outputLen += currNode->value.size(); // This is a leaf value node. + } + + for ( size_t childNum = 0, childLim = currNode->children.size(); childNum < childLim; ++childNum ) { + const XMP_Node * currChild = currNode->children[childNum]; + outputLen += EstimateRDFSize ( currChild, indent+1, indentLen ); + } + + return outputLen; + +} // EstimateRDFSize + + +// ------------------------------------------------------------------------------------------------- +// DeclareOneNamespace +// ------------------- + +static void +DeclareOneNamespace ( XMP_StringPtr nsPrefix, + XMP_StringPtr nsURI, + XMP_VarString & usedNS, // ! A catenation of the prefixes with colons. + XMP_VarString & outputStr, + XMP_StringPtr newline, + XMP_StringPtr indentStr, + XMP_Index indent ) +{ + XMP_VarString boundedPrefix = ":"; + boundedPrefix += nsPrefix; + size_t nsPos = usedNS.find ( boundedPrefix ); + + if ( nsPos == XMP_VarString::npos ) { + + outputStr += newline; + for ( ; indent > 0; --indent ) outputStr += indentStr; + outputStr += "xmlns:"; + outputStr += nsPrefix; + outputStr[outputStr.size()-1] = '='; // Change the colon to =. + outputStr += '"'; + outputStr += nsURI; + outputStr += '"'; + + usedNS += nsPrefix; + + } + +} // DeclareOneNamespace + + +// ------------------------------------------------------------------------------------------------- +// DeclareElemNamespace +// -------------------- + +static void +DeclareElemNamespace ( const XMP_VarString & elemName, + XMP_VarString & usedNS, + XMP_VarString & outputStr, + XMP_StringPtr newline, + XMP_StringPtr indentStr, + XMP_Index indent ) +{ + size_t colonPos = elemName.find ( ':' ); + + if ( colonPos != XMP_VarString::npos ) { + XMP_VarString nsPrefix ( elemName.substr ( 0, colonPos+1 ) ); + XMP_StringPtr nsURI; + bool nsFound = sRegisteredNamespaces->GetURI ( nsPrefix.c_str(), &nsURI, 0 ); + XMP_Enforce ( nsFound ); + DeclareOneNamespace ( nsPrefix.c_str(), nsURI, usedNS, outputStr, newline, indentStr, indent ); + } + +} // DeclareElemNamespace + + +// ------------------------------------------------------------------------------------------------- +// DeclareUsedNamespaces +// --------------------- + +static void +DeclareUsedNamespaces ( const XMP_Node * currNode, + XMP_VarString & usedNS, + XMP_VarString & outputStr, + XMP_StringPtr newline, + XMP_StringPtr indentStr, + XMP_Index indent ) +{ + + if ( currNode->options & kXMP_SchemaNode ) { + // The schema node name is the URI, the value is the prefix. + DeclareOneNamespace ( currNode->value.c_str(), currNode->name.c_str(), usedNS, outputStr, newline, indentStr, indent ); + } else if ( currNode->options & kXMP_PropValueIsStruct ) { + for ( size_t fieldNum = 0, fieldLim = currNode->children.size(); fieldNum < fieldLim; ++fieldNum ) { + const XMP_Node * currField = currNode->children[fieldNum]; + DeclareElemNamespace ( currField->name, usedNS, outputStr, newline, indentStr, indent ); + } + } + + for ( size_t childNum = 0, childLim = currNode->children.size(); childNum < childLim; ++childNum ) { + const XMP_Node * currChild = currNode->children[childNum]; + DeclareUsedNamespaces ( currChild, usedNS, outputStr, newline, indentStr, indent ); + } + + for ( size_t qualNum = 0, qualLim = currNode->qualifiers.size(); qualNum < qualLim; ++qualNum ) { + const XMP_Node * currQual = currNode->qualifiers[qualNum]; + DeclareElemNamespace ( currQual->name, usedNS, outputStr, newline, indentStr, indent ); + DeclareUsedNamespaces ( currQual, usedNS, outputStr, newline, indentStr, indent ); + } + +} // DeclareUsedNamespaces + +// ------------------------------------------------------------------------------------------------- +// EmitRDFArrayTag +// --------------- + +enum { + kIsStartTag = true, + kIsEndTag = false +}; + +static void +EmitRDFArrayTag ( XMP_OptionBits arrayForm, + XMP_VarString & outputStr, + XMP_StringPtr newline, + XMP_StringPtr indentStr, + XMP_Index indent, + XMP_Index arraySize, + bool isStartTag ) +{ + if ( (! isStartTag) && (arraySize == 0) ) return; + + for ( XMP_Index level = indent; level > 0; --level ) outputStr += indentStr; + if ( isStartTag ) { + outputStr += "', and ASCII controls (tab, LF, CR). In +// addition, '"' is escaped for attributes. For efficiency, this is done in a double loop. The outer +// loop makes sure the whole value is processed. The inner loop does a contiguous unescaped run +// followed by one escaped character (if we're not at the end). +// +// We depend on parsing and SetProperty logic to make sure there are no invalid ASCII controls in +// the XMP values. The XML spec only allows tab, LF, and CR. Others are not even allowed as +// numeric escape sequences. + +enum { + kForAttribute = true, + kForElement = false +}; + +static void +AppendNodeValue ( XMP_VarString & outputStr, const XMP_VarString & value, bool forAttribute ) +{ + + unsigned char * runStart = (unsigned char *) value.c_str(); + unsigned char * runLimit = runStart + value.size(); + unsigned char * runEnd; + unsigned char ch; + + while ( runStart < runLimit ) { + + for ( runEnd = runStart; runEnd < runLimit; ++runEnd ) { + ch = *runEnd; + if ( forAttribute && (ch == '"') ) break; + if ( (ch < 0x20) || (ch == '&') || (ch == '<') || (ch == '>') ) break; + } + + outputStr.append ( (char *) runStart, (runEnd - runStart) ); + + if ( runEnd < runLimit ) { + + if ( ch < 0x20 ) { + + XMP_Assert ( (ch == kTab) || (ch == kLF) || (ch == kCR) ); + + char hexBuf[16]; + memcpy ( hexBuf, "&#xn;", 6 ); // AUDIT: Length of "&#xn;" is 5, hexBuf size is 16. + hexBuf[3] = kHexDigits[ch&0xF]; + outputStr.append ( hexBuf, 5 ); + + } else { + + if ( ch == '"' ) { + outputStr += """; + } else if ( ch == '<' ) { + outputStr += "<"; + } else if ( ch == '>' ) { + outputStr += ">"; + } else { + XMP_Assert ( ch == '&' ); + outputStr += "&"; + } + + } + + ++runEnd; + + } + + runStart = runEnd; + + } + +} // AppendNodeValue + + +// ------------------------------------------------------------------------------------------------- +// CanBeRDFAttrProp +// ---------------- + +static bool +CanBeRDFAttrProp ( const XMP_Node * propNode ) +{ + + if ( propNode->name[0] == '[' ) return false; + if ( ! propNode->qualifiers.empty() ) return false; + if ( propNode->options & kXMP_PropValueIsURI ) return false; + if ( propNode->options & kXMP_PropCompositeMask ) return false; + + return true; + +} // CanBeRDFAttrProp + + +// ------------------------------------------------------------------------------------------------- +// IsRDFAttrQualifier +// ------------------ + +static XMP_StringPtr sAttrQualifiers[] = { "xml:lang", "rdf:resource", "rdf:ID", "rdf:bagID", "rdf:nodeID", "" }; + +static bool +IsRDFAttrQualifier ( XMP_VarString qualName ) +{ + + for ( size_t i = 0; *sAttrQualifiers[i] != 0; ++i ) { + if ( qualName == sAttrQualifiers[i] ) return true; + } + + return false; + +} // IsRDFAttrQualifier + + +// ------------------------------------------------------------------------------------------------- +// StartOuterRDFDescription +// ------------------------ +// +// Start the outer rdf:Description element, including all needed xmlns attributes. Leave the element +// open so that the compact form can add proprtty attributes. + +static void +StartOuterRDFDescription ( const XMP_Node & xmpTree, + XMP_VarString & outputStr, + XMP_StringPtr newline, + XMP_StringPtr indentStr, + XMP_Index baseIndent ) +{ + + // Begin the outer rdf:Description start tag. + + for ( XMP_Index level = baseIndent+2; level > 0; --level ) outputStr += indentStr; + outputStr += kRDF_SchemaStart; + outputStr += '"'; + outputStr += xmpTree.name; + outputStr += '"'; + + // Write all necessary xmlns attributes. + + XMP_VarString usedNS; + usedNS.reserve ( 400 ); // The predefined prefixes add up to about 320 bytes. + usedNS = ":xml:rdf:"; + + for ( size_t schema = 0, schemaLim = xmpTree.children.size(); schema != schemaLim; ++schema ) { + const XMP_Node * currSchema = xmpTree.children[schema]; + DeclareUsedNamespaces ( currSchema, usedNS, outputStr, newline, indentStr, baseIndent+4 ); + } + +} // StartOuterRDFDescription + +// ------------------------------------------------------------------------------------------------- +// SerializeCanonicalRDFProperty +// ----------------------------- +// +// Recursively handles the "value" for a node. It does not matter if it is a top level property, a +// field of a struct, or an item of an array. The indent is that for the property element. An +// xml:lang qualifier is written as an attribute of the property start tag, not by itself forcing +// the qualified property form. The patterns below mostly ignore attribute qualifiers like xml:lang. +// Except for the one struct case, attribute qualifiers don't affect the output form. +// +// value +// +// (If no rdf:resource qualifier) +// +// ... Fields, same forms as top level properties +// +// +// +// +// +// +// or Seq or Alt +// ... Array items as rdf:li elements, same forms as top level properties +// +// +// +// +// +// ... Property "value" following the unqualified forms ... +// ... Qualifiers looking like named struct fields +// +// + +enum { kUseCanonicalRDF = true, kUseAdobeVerboseRDF = false }; +enum { kEmitAsRDFValue = true, kEmitAsNormalValue = false }; + +static void +SerializeCanonicalRDFProperty ( const XMP_Node * propNode, + XMP_VarString & outputStr, + XMP_StringPtr newline, + XMP_StringPtr indentStr, + XMP_Index indent, + bool useCanonicalRDF, + bool emitAsRDFValue ) +{ + XMP_Index level; + bool emitEndTag = true; + bool indentEndTag = true; + + XMP_OptionBits propForm = propNode->options & kXMP_PropCompositeMask; + + // ------------------------------------------------------------------------------------------ + // Determine the XML element name. Open the start tag with the name and attribute qualifiers. + + XMP_StringPtr elemName = propNode->name.c_str(); + if ( emitAsRDFValue ) { + elemName= "rdf:value"; + } else if ( *elemName == '[' ) { + elemName = "rdf:li"; + } + + for ( level = indent; level > 0; --level ) outputStr += indentStr; + outputStr += '<'; + outputStr += elemName; + + bool hasGeneralQualifiers = false; + bool hasRDFResourceQual = false; + + for ( size_t qualNum = 0, qualLim = propNode->qualifiers.size(); qualNum < qualLim; ++qualNum ) { + const XMP_Node * currQual = propNode->qualifiers[qualNum]; + if ( ! IsRDFAttrQualifier ( currQual->name ) ) { + hasGeneralQualifiers = true; + } else { + if ( currQual->name == "rdf:resource" ) hasRDFResourceQual = true; + if ( ! emitAsRDFValue ) { + outputStr += ' '; + outputStr += currQual->name; + outputStr += "=\""; + AppendNodeValue ( outputStr, currQual->value, kForAttribute ); + outputStr += '"'; + } + } + } + + // -------------------------------------------------------- + // Process the property according to the standard patterns. + + if ( hasGeneralQualifiers && (! emitAsRDFValue) ) { + + // ----------------------------------------------------------------------------------------- + // This node has general, non-attribute, qualifiers. Emit using the qualified property form. + // ! The value is output by a recursive call ON THE SAME NODE with emitAsRDFValue set. + + if ( hasRDFResourceQual ) { + XMP_Throw ( "Can't mix rdf:resource and general qualifiers", kXMPErr_BadRDF ); + } + + if ( ! useCanonicalRDF ) { + outputStr += " rdf:parseType=\"Resource\">"; + outputStr += newline; + } else { + outputStr += '>'; + outputStr += newline; + indent += 1; + for ( level = indent; level > 0; --level ) outputStr += indentStr; + outputStr += ""; + outputStr += newline; + } + + SerializeCanonicalRDFProperty ( propNode, outputStr, newline, indentStr, indent+1, + useCanonicalRDF, kEmitAsRDFValue ); + + for ( size_t qualNum = 0, qualLim = propNode->qualifiers.size(); qualNum < qualLim; ++qualNum ) { + const XMP_Node * currQual = propNode->qualifiers[qualNum]; + if ( IsRDFAttrQualifier ( currQual->name ) ) continue; + SerializeCanonicalRDFProperty ( currQual, outputStr, newline, indentStr, indent+1, + useCanonicalRDF, kEmitAsNormalValue ); + } + + if ( useCanonicalRDF ) { + for ( level = indent; level > 0; --level ) outputStr += indentStr; + outputStr += ""; + outputStr += newline; + indent -= 1; + } + + } else { + + // -------------------------------------------------------------------- + // This node has no general qualifiers. Emit using an unqualified form. + + if ( propForm == 0 ) { + + // -------------------------- + // This is a simple property. + + if ( propNode->options & kXMP_PropValueIsURI ) { + outputStr += " rdf:resource=\""; + AppendNodeValue ( outputStr, propNode->value, kForAttribute ); + outputStr += "\"/>"; + outputStr += newline; + emitEndTag = false; + } else if ( propNode->value.empty() ) { + outputStr += "/>"; + outputStr += newline; + emitEndTag = false; + } else { + outputStr += '>'; + AppendNodeValue ( outputStr, propNode->value, kForElement ); + indentEndTag = false; + } + + } else if ( propForm & kXMP_PropValueIsArray ) { + + // This is an array. + outputStr += '>'; + outputStr += newline; + EmitRDFArrayTag ( propForm, outputStr, newline, indentStr, indent+1, propNode->children.size(), kIsStartTag ); + if ( XMP_ArrayIsAltText(propNode->options) ) NormalizeLangArray ( (XMP_Node*)propNode ); + for ( size_t childNum = 0, childLim = propNode->children.size(); childNum < childLim; ++childNum ) { + const XMP_Node * currChild = propNode->children[childNum]; + SerializeCanonicalRDFProperty ( currChild, outputStr, newline, indentStr, indent+2, + useCanonicalRDF, kEmitAsNormalValue ); + } + EmitRDFArrayTag ( propForm, outputStr, newline, indentStr, indent+1, propNode->children.size(), kIsEndTag ); + + + } else if ( ! hasRDFResourceQual ) { + + // This is a "normal" struct, use the nested field element form form. + XMP_Assert ( propForm & kXMP_PropValueIsStruct ); + if ( propNode->children.size() == 0 ) { + if ( ! useCanonicalRDF ) { + outputStr += " rdf:parseType=\"Resource\"/>"; + outputStr += newline; + emitEndTag = false; + } else { + outputStr += '>'; + outputStr += newline; + for ( level = indent+1; level > 0; --level ) outputStr += indentStr; + outputStr += ""; + outputStr += newline; + } + } else { + if ( ! useCanonicalRDF ) { + outputStr += " rdf:parseType=\"Resource\">"; + outputStr += newline; + } else { + outputStr += '>'; + outputStr += newline; + indent += 1; + for ( level = indent; level > 0; --level ) outputStr += indentStr; + outputStr += ""; + outputStr += newline; + } + for ( size_t childNum = 0, childLim = propNode->children.size(); childNum < childLim; ++childNum ) { + const XMP_Node * currChild = propNode->children[childNum]; + SerializeCanonicalRDFProperty ( currChild, outputStr, newline, indentStr, indent+1, + useCanonicalRDF, kEmitAsNormalValue ); + } + if ( useCanonicalRDF ) { + for ( level = indent; level > 0; --level ) outputStr += indentStr; + outputStr += ""; + outputStr += newline; + indent -= 1; + } + } + + } else { + + // This is a struct with an rdf:resource attribute, use the "empty property element" form. + XMP_Assert ( propForm & kXMP_PropValueIsStruct ); + for ( size_t childNum = 0, childLim = propNode->children.size(); childNum < childLim; ++childNum ) { + const XMP_Node * currChild = propNode->children[childNum]; + if ( ! CanBeRDFAttrProp ( currChild ) ) { + XMP_Throw ( "Can't mix rdf:resource and complex fields", kXMPErr_BadRDF ); + } + outputStr += newline; + for ( level = indent+1; level > 0; --level ) outputStr += indentStr; + outputStr += ' '; + outputStr += currChild->name; + outputStr += "=\""; + outputStr += currChild->value; + outputStr += '"'; + } + outputStr += "/>"; + outputStr += newline; + emitEndTag = false; + + } + + } + + // ---------------------------------- + // Emit the property element end tag. + + if ( emitEndTag ) { + if ( indentEndTag ) for ( level = indent; level > 0; --level ) outputStr += indentStr; + outputStr += "'; + outputStr += newline; + } + +} // SerializeCanonicalRDFProperty + + +// ------------------------------------------------------------------------------------------------- +// SerializeCanonicalRDFSchemas +// ---------------------------- +// +// Each schema's properties are written to the single rdf:Description element. All of the necessary +// namespaces are declared in the rdf:Description element. The baseIndent is the base level for the +// entire serialization, that of the x:xmpmeta element. An xml:lang qualifier is written as an +// attribute of the property start tag, not by itself forcing the qualified property form. +// +// +// +// ... The actual properties of the schema, see SerializeCanonicalRDFProperty +// +// ... If alias comments are wanted +// +// + +static void +SerializeCanonicalRDFSchemas ( const XMP_Node & xmpTree, + XMP_VarString & outputStr, + XMP_StringPtr newline, + XMP_StringPtr indentStr, + XMP_Index baseIndent, + bool useCanonicalRDF ) +{ + + StartOuterRDFDescription ( xmpTree, outputStr, newline, indentStr, baseIndent ); + + if ( xmpTree.children.size() > 0 ) { + outputStr += ">"; + outputStr += newline; + } else { + outputStr += "/>"; + outputStr += newline; + return; // ! Done if there are no XMP properties. + } + + for ( size_t schemaNum = 0, schemaLim = xmpTree.children.size(); schemaNum < schemaLim; ++schemaNum ) { + const XMP_Node * currSchema = xmpTree.children[schemaNum]; + for ( size_t propNum = 0, propLim = currSchema->children.size(); propNum < propLim; ++propNum ) { + const XMP_Node * currProp = currSchema->children[propNum]; + SerializeCanonicalRDFProperty ( currProp, outputStr, newline, indentStr, baseIndent+3, + useCanonicalRDF, kEmitAsNormalValue ); + } + } + + // Write the rdf:Description end tag. + for ( XMP_Index level = baseIndent+2; level > 0; --level ) outputStr += indentStr; + outputStr += kRDF_SchemaEnd; + outputStr += newline; + +} // SerializeCanonicalRDFSchemas + + +// ------------------------------------------------------------------------------------------------- +// SerializeCompactRDFAttrProps +// ---------------------------- +// +// Write each of the parent's simple unqualified properties as an attribute. Returns true if all +// of the properties are written as attributes. + +static bool +SerializeCompactRDFAttrProps ( const XMP_Node * parentNode, + XMP_VarString & outputStr, + XMP_StringPtr newline, + XMP_StringPtr indentStr, + XMP_Index indent ) +{ + size_t prop, propLim; + bool allAreAttrs = true; + + for ( prop = 0, propLim = parentNode->children.size(); prop != propLim; ++prop ) { + + const XMP_Node * currProp = parentNode->children[prop]; + if ( ! CanBeRDFAttrProp ( currProp ) ) { + allAreAttrs = false; + continue; + } + + outputStr += newline; + for ( XMP_Index level = indent; level > 0; --level ) outputStr += indentStr; + outputStr += currProp->name; + outputStr += "=\""; + AppendNodeValue ( outputStr, currProp->value, kForAttribute ); + outputStr += '"'; + + } + + return allAreAttrs; + +} // SerializeCompactRDFAttrProps + + +// ------------------------------------------------------------------------------------------------- +// SerializeCompactRDFElemProps +// ---------------------------- +// +// Recursively handles the "value" for a node that must be written as an RDF property element. It +// does not matter if it is a top level property, a field of a struct, or an item of an array. The +// indent is that for the property element. The patterns bwlow ignore attribute qualifiers such as +// xml:lang, they don't affect the output form. +// +// +// +// +// ... The fields as elements, if none are simple and unqualified +// +// +// +// +// ... The compound or qualified fields as elements +// +// +// +// +// or Seq or Alt +// ... Array items as rdf:li elements, same forms as top level properties +// +// +// +// +// ... Property "value" following the unqualified forms ... +// ... Qualifiers looking like named struct fields +// + +// *** Consider numbered array items, but has compatibility problems. +// *** Consider qualified form with rdf:Description and attributes. + +static void +SerializeCompactRDFElemProps ( const XMP_Node * parentNode, + XMP_VarString & outputStr, + XMP_StringPtr newline, + XMP_StringPtr indentStr, + XMP_Index indent ) +{ + XMP_Index level; + + for ( size_t prop = 0, propLim = parentNode->children.size(); prop != propLim; ++prop ) { + + const XMP_Node * propNode = parentNode->children[prop]; + if ( CanBeRDFAttrProp ( propNode ) ) continue; + + bool emitEndTag = true; + bool indentEndTag = true; + + XMP_OptionBits propForm = propNode->options & kXMP_PropCompositeMask; + + // ----------------------------------------------------------------------------------- + // Determine the XML element name, write the name part of the start tag. Look over the + // qualifiers to decide on "normal" versus "rdf:value" form. Emit the attribute + // qualifiers at the same time. + + XMP_StringPtr elemName = propNode->name.c_str(); + if ( *elemName == '[' ) elemName = "rdf:li"; + + for ( level = indent; level > 0; --level ) outputStr += indentStr; + outputStr += '<'; + outputStr += elemName; + + bool hasGeneralQualifiers = false; + bool hasRDFResourceQual = false; + + for ( size_t qualNum = 0, qualLim = propNode->qualifiers.size(); qualNum < qualLim; ++qualNum ) { + const XMP_Node * currQual = propNode->qualifiers[qualNum]; + if ( ! IsRDFAttrQualifier ( currQual->name ) ) { + hasGeneralQualifiers = true; + } else { + if ( currQual->name == "rdf:resource" ) hasRDFResourceQual = true; + outputStr += ' '; + outputStr += currQual->name; + outputStr += "=\""; + AppendNodeValue ( outputStr, currQual->value, kForAttribute ); + outputStr += '"'; + } + } + + // -------------------------------------------------------- + // Process the property according to the standard patterns. + + if ( hasGeneralQualifiers ) { + + // ------------------------------------------------------------------------------------- + // The node has general qualifiers, ones that can't be attributes on a property element. + // Emit using the qualified property pseudo-struct form. The value is output by a call + // to SerializeCanonicalRDFProperty with emitAsRDFValue set. + + // *** We're losing compactness in the calls to SerializeCanonicalRDFProperty. + // *** Should refactor to have SerializeCompactRDFProperty that does one node. + + outputStr += " rdf:parseType=\"Resource\">"; + outputStr += newline; + + SerializeCanonicalRDFProperty ( propNode, outputStr, newline, indentStr, indent+1, + kUseAdobeVerboseRDF, kEmitAsRDFValue ); + + size_t qualNum = 0; + size_t qualLim = propNode->qualifiers.size(); + if ( propNode->options & kXMP_PropHasLang ) ++qualNum; + + for ( ; qualNum < qualLim; ++qualNum ) { + const XMP_Node * currQual = propNode->qualifiers[qualNum]; + SerializeCanonicalRDFProperty ( currQual, outputStr, newline, indentStr, indent+1, + kUseAdobeVerboseRDF, kEmitAsNormalValue ); + } + + } else { + + // -------------------------------------------------------------------- + // This node has only attribute qualifiers. Emit as a property element. + + if ( propForm == 0 ) { + + // -------------------------- + // This is a simple property. + + if ( propNode->options & kXMP_PropValueIsURI ) { + outputStr += " rdf:resource=\""; + AppendNodeValue ( outputStr, propNode->value, kForAttribute ); + outputStr += "\"/>"; + outputStr += newline; + emitEndTag = false; + } else if ( propNode->value.empty() ) { + outputStr += "/>"; + outputStr += newline; + emitEndTag = false; + } else { + outputStr += '>'; + AppendNodeValue ( outputStr, propNode->value, kForElement ); + indentEndTag = false; + } + + } else if ( propForm & kXMP_PropValueIsArray ) { + + // ----------------- + // This is an array. + + outputStr += '>'; + outputStr += newline; + EmitRDFArrayTag ( propForm, outputStr, newline, indentStr, indent+1, propNode->children.size(), kIsStartTag ); + + if ( XMP_ArrayIsAltText(propNode->options) ) NormalizeLangArray ( (XMP_Node*)propNode ); + SerializeCompactRDFElemProps ( propNode, outputStr, newline, indentStr, indent+2 ); + + EmitRDFArrayTag ( propForm, outputStr, newline, indentStr, indent+1, propNode->children.size(), kIsEndTag ); + + } else { + + // ---------------------- + // This must be a struct. + + XMP_Assert ( propForm & kXMP_PropValueIsStruct ); + + bool hasAttrFields = false; + bool hasElemFields = false; + + size_t field, fieldLim; + for ( field = 0, fieldLim = propNode->children.size(); field != fieldLim; ++field ) { + XMP_Node * currField = propNode->children[field]; + if ( CanBeRDFAttrProp ( currField ) ) { + hasAttrFields = true; + if ( hasElemFields ) break; // No sense looking further. + } else { + hasElemFields = true; + if ( hasAttrFields ) break; // No sense looking further. + } + } + + if ( hasRDFResourceQual && hasElemFields ) { + XMP_Throw ( "Can't mix rdf:resource qualifier and element fields", kXMPErr_BadRDF ); + } + + if ( propNode->children.size() == 0 ) { + + // Catch an empty struct as a special case. The case below would emit an empty + // XML element, which gets reparsed as a simple property with an empty value. + outputStr += " rdf:parseType=\"Resource\"/>"; + outputStr += newline; + emitEndTag = false; + + } else if ( ! hasElemFields ) { + + // All fields can be attributes, use the emptyPropertyElt form. + SerializeCompactRDFAttrProps ( propNode, outputStr, newline, indentStr, indent+1 ); + outputStr += "/>"; + outputStr += newline; + emitEndTag = false; + + } else if ( ! hasAttrFields ) { + + // All fields must be elements, use the parseTypeResourcePropertyElt form. + outputStr += " rdf:parseType=\"Resource\">"; + outputStr += newline; + SerializeCompactRDFElemProps ( propNode, outputStr, newline, indentStr, indent+1 ); + + } else { + + // Have a mix of attributes and elements, use an inner rdf:Description. + outputStr += '>'; + outputStr += newline; + for ( level = indent+1; level > 0; --level ) outputStr += indentStr; + outputStr += " 0; --level ) outputStr += indentStr; + outputStr += kRDF_StructEnd; + outputStr += newline; + + } + + } + + } + + // ---------------------------------- + // Emit the property element end tag. + + if ( emitEndTag ) { + if ( indentEndTag ) for ( level = indent; level > 0; --level ) outputStr += indentStr; + outputStr += "'; + outputStr += newline; + } + + } + +} // SerializeCompactRDFElemProps + + +// ------------------------------------------------------------------------------------------------- +// SerializeCompactRDFSchemas +// -------------------------- +// +// All properties from all schema are written in a single rdf:Description element, as are all of the +// necessary namespace declarations. The baseIndent is the base level for the entire serialization, +// that of the x:xmpmeta element. The x:xmpmeta and rdf:RDF elements have already been written. +// +// Top level simple unqualified properties are written as attributes of the (only) rdf:Description +// element. Structs, arrays, and qualified properties are written by SerializeCompactRDFElemProp. An +// xml:lang qualifier on a simple property prevents the attribute form. +// +// +// ... The remaining properties of the schema, see SerializeCompactRDFElemProps +// + +static void +SerializeCompactRDFSchemas ( const XMP_Node & xmpTree, + XMP_VarString & outputStr, + XMP_StringPtr newline, + XMP_StringPtr indentStr, + XMP_Index baseIndent ) +{ + XMP_Index level; + size_t schema, schemaLim; + + StartOuterRDFDescription ( xmpTree, outputStr, newline, indentStr, baseIndent ); + + // Write the top level "attrProps" and close the rdf:Description start tag. + bool allAreAttrs = true; + for ( schema = 0, schemaLim = xmpTree.children.size(); schema != schemaLim; ++schema ) { + const XMP_Node * currSchema = xmpTree.children[schema]; + allAreAttrs &= SerializeCompactRDFAttrProps ( currSchema, outputStr, newline, indentStr, baseIndent+3 ); + } + if ( ! allAreAttrs ) { + outputStr += ">"; + outputStr += newline; + } else { + outputStr += "/>"; + outputStr += newline; + return; // ! Done if all properties in all schema are written as attributes. + } + + // Write the remaining properties for each schema. + for ( schema = 0, schemaLim = xmpTree.children.size(); schema != schemaLim; ++schema ) { + const XMP_Node * currSchema = xmpTree.children[schema]; + SerializeCompactRDFElemProps ( currSchema, outputStr, newline, indentStr, baseIndent+3 ); + } + + // Write the rdf:Description end tag. + for ( level = baseIndent+2; level > 0; --level ) outputStr += indentStr; + outputStr += kRDF_SchemaEnd; + outputStr += newline; + +} // SerializeCompactRDFSchemas + +// ------------------------------------------------------------------------------------------------- +// SerializeAsRDF +// -------------- +// +// +// +// +// +// ... The properties, see SerializeCanonicalRDFSchema or SerializeCompactRDFSchemas +// +// +// +// + +// *** Need to strip empty arrays? +// *** Option to strip/keep empty structs? +// *** Need to verify handling of rdf:type qualifiers in canonical and compact. +// *** Need to verify round tripping of rdf:ID and similar qualifiers, see RDF 7.2.21. +// *** Check cases of rdf:resource plus explicit attr qualifiers (like xml:lang). + +static void +SerializeAsRDF ( const XMPMeta & xmpObj, + XMP_VarString & headStr, // Everything up to the padding. + XMP_VarString & tailStr, // Everything after the padding. + XMP_OptionBits options, + XMP_StringPtr newline, + XMP_StringPtr indentStr, + XMP_Index baseIndent ) +{ + const size_t treeNameLen = xmpObj.tree.name.size(); + const size_t indentLen = strlen ( indentStr ); + + // First estimate the worst case space and reserve room in the output string. This optimization + // avoids reallocating and copying the output as it grows. The initial count does not look at + // the values of properties, so it does not account for character entities, e.g. for newline. + // Since there can be a lot of these in things like the base 64 encoding of a large thumbnail, + // inflate the count by 1/4 (easy to do) to accommodate. + + // *** Need to include estimate for alias comments. + + size_t outputLen = 2 * (strlen(kPacketHeader) + strlen(kRDF_XMPMetaStart) + strlen(kRDF_RDFStart) + 3*baseIndent*indentLen); + + for ( size_t schemaNum = 0, schemaLim = xmpObj.tree.children.size(); schemaNum < schemaLim; ++schemaNum ) { + const XMP_Node * currSchema = xmpObj.tree.children[schemaNum]; + outputLen += 2*(baseIndent+2)*indentLen + strlen(kRDF_SchemaStart) + treeNameLen + strlen(kRDF_SchemaEnd) + 2; + outputLen += EstimateRDFSize ( currSchema, baseIndent+2, indentLen ); + } + + outputLen += (outputLen >> 2); // Inflate by 1/4, an empirical fudge factor. + + // Now generate the RDF into the head string as UTF-8. + + XMP_Index level; + + std::string rdfstring; + headStr.erase(); + rdfstring.reserve ( outputLen ); + + // Write the rdf:RDF start tag. + rdfstring += kRDF_RDFStart; + rdfstring += newline; + + // Write all of the properties. + if ( options & kXMP_UseCompactFormat ) { + SerializeCompactRDFSchemas ( xmpObj.tree, rdfstring, newline, indentStr, baseIndent ); + } else { + bool useCanonicalRDF = XMP_OptionIsSet ( options, kXMP_UseCanonicalFormat ); + SerializeCanonicalRDFSchemas ( xmpObj.tree, rdfstring, newline, indentStr, baseIndent, useCanonicalRDF ); + } + + // Write the rdf:RDF end tag. + for ( level = baseIndent+1; level > 0; --level ) rdfstring += indentStr; + rdfstring += kRDF_RDFEnd; + // Write the packet header PI. + if ( ! (options & kXMP_OmitPacketWrapper) ) { + for ( level = baseIndent; level > 0; --level ) headStr += indentStr; + headStr += kPacketHeader; + headStr += newline; + } + + // Write the xmpmeta element's start tag. + if ( ! (options & kXMP_OmitXMPMetaElement) ) { + for ( level = baseIndent; level > 0; --level ) headStr += indentStr; + headStr += kRDF_XMPMetaStart; + headStr += kXMPCore_VersionMessage "\""; + std::string digestStr; + unsigned char digestBin [16]; + if (options & kXMP_IncludeRDFHash) + { + std::string hashrdf; + MD5_CTX context; + MD5Init ( &context ); + MD5Update ( &context, (XMP_Uns8*)rdfstring.c_str(), (unsigned int)rdfstring.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 ); + headStr += " rdfhash=\""; + headStr += digestStr + "\""; + headStr += " merged=\"0\""; + } + headStr += ">"; + headStr += newline; + } + + for ( level = baseIndent+1; level > 0; --level ) headStr += indentStr; + headStr+= rdfstring ; + headStr += newline; + + // Write the xmpmeta end tag. + if ( ! (options & kXMP_OmitXMPMetaElement) ) { + for ( level = baseIndent; level > 0; --level ) headStr += indentStr; + headStr += kRDF_XMPMetaEnd; + headStr += newline; + } + + // Write the packet trailer PI into the tail string as UTF-8. + tailStr.erase(); + if ( ! (options & kXMP_OmitPacketWrapper) ) { + tailStr.reserve ( strlen(kPacketTrailer) + (strlen(indentStr) * baseIndent) ); + for ( level = baseIndent; level > 0; --level ) tailStr += indentStr; + tailStr += kPacketTrailer; + if ( options & kXMP_ReadOnlyPacket ) tailStr[tailStr.size()-4] = 'r'; + } + +} // SerializeAsRDF + + +// ------------------------------------------------------------------------------------------------- +// SerializeToBuffer +// ----------------- + +void +XMPMeta::SerializeToBuffer ( XMP_VarString * rdfString, + XMP_OptionBits options, + XMP_StringLen padding, + XMP_StringPtr newline, + XMP_StringPtr indentStr, + XMP_Index baseIndent ) const +{ + XMP_Enforce( rdfString != 0 ); + XMP_Assert ( (newline != 0) && (indentStr != 0) ); + rdfString->erase(); + + // Fix up some default parameters. + + enum { kDefaultPad = 2048 }; + size_t unicodeUnitSize = 1; + XMP_OptionBits charEncoding = options & kXMP_EncodingMask; + + if ( charEncoding != kXMP_EncodeUTF8 ) { + if ( options & _XMP_UTF16_Bit ) { + if ( options & _XMP_UTF32_Bit ) XMP_Throw ( "Can't use both _XMP_UTF16_Bit and _XMP_UTF32_Bit", kXMPErr_BadOptions ); + unicodeUnitSize = 2; + } else if ( options & _XMP_UTF32_Bit ) { + unicodeUnitSize = 4; + } else { + XMP_Throw ( "Can't use _XMP_LittleEndian_Bit by itself", kXMPErr_BadOptions ); + } + } + + if ( options & kXMP_OmitAllFormatting ) { + newline = " "; // ! Yes, a space for "newline". This ensures token separation. + indentStr = ""; + } else { + if ( *newline == 0 ) newline = "\xA"; // Linefeed + if ( *indentStr == 0 ) { + indentStr = " "; + if ( ! (options & kXMP_UseCompactFormat) ) indentStr = " "; + } + } + + if ( options & kXMP_ExactPacketLength ) { + if ( options & (kXMP_OmitPacketWrapper | kXMP_IncludeThumbnailPad) ) { + XMP_Throw ( "Inconsistent options for exact size serialize", kXMPErr_BadOptions ); + } + if ( (padding & (unicodeUnitSize-1)) != 0 ) { + XMP_Throw ( "Exact size must be a multiple of the Unicode element", kXMPErr_BadOptions ); + } + } else if ( options & kXMP_ReadOnlyPacket ) { + if ( options & (kXMP_OmitPacketWrapper | kXMP_IncludeThumbnailPad) ) { + XMP_Throw ( "Inconsistent options for read-only packet", kXMPErr_BadOptions ); + } + padding = 0; + } else if ( options & kXMP_OmitPacketWrapper ) { + if ( options & kXMP_IncludeThumbnailPad ) { + XMP_Throw ( "Inconsistent options for non-packet serialize", kXMPErr_BadOptions ); + } + padding = 0; + } else if ( options & kXMP_OmitXMPMetaElement ) { + if ( options & kXMP_IncludeRDFHash ) { + XMP_Throw ( "Inconsistent options for x:xmpmeta serialize", kXMPErr_BadOptions ); + } + padding = 0; + } else { + if ( padding == 0 ) { + padding = kDefaultPad * unicodeUnitSize; + } else if ( (padding >> 28) != 0 ) { + XMP_Throw ( "Outrageously large padding size", kXMPErr_BadOptions ); // Bigger than 256 MB. + } + if ( options & kXMP_IncludeThumbnailPad ) { + if ( ! this->DoesPropertyExist ( kXMP_NS_XMP, "Thumbnails" ) ) padding += (10000 * unicodeUnitSize); // *** Need a better estimate. + } + } + + // Serialize as UTF-8, then convert to UTF-16 or UTF-32 if necessary, and assemble with the padding and tail. + + std::string tailStr; + + SerializeAsRDF ( *this, *rdfString, tailStr, options, newline, indentStr, baseIndent ); + + if ( charEncoding == kXMP_EncodeUTF8 ) { + + if ( options & kXMP_ExactPacketLength ) { + size_t minSize = rdfString->size() + tailStr.size(); + if ( minSize > padding ) XMP_Throw ( "Can't fit into specified packet size", kXMPErr_BadSerialize ); + padding -= minSize; // Now the actual amount of padding to add. + } + + size_t newlineLen = strlen ( newline ); + + if ( padding < newlineLen ) { + rdfString->append ( padding, ' ' ); + } else { + padding -= newlineLen; // Write this newline last. + while ( padding >= (100 + newlineLen) ) { + rdfString->append ( 100, ' ' ); + *rdfString += newline; + padding -= (100 + newlineLen); + } + rdfString->append ( padding, ' ' ); + *rdfString += newline; + } + + *rdfString += tailStr; + + } else { + + // Need to convert the encoding. Swap the UTF-8 into a local string and convert back. Assemble everything. + + XMP_VarString utf8Str, newlineStr; + bool bigEndian = ((charEncoding & _XMP_LittleEndian_Bit) == 0); + + if ( charEncoding & _XMP_UTF16_Bit ) { + + std::string padStr ( " " ); padStr[0] = 0; // Assume big endian. + + utf8Str.swap ( *rdfString ); + ToUTF16 ( (UTF8Unit*)utf8Str.c_str(), utf8Str.size(), rdfString, bigEndian ); + utf8Str.swap ( tailStr ); + ToUTF16 ( (UTF8Unit*)utf8Str.c_str(), utf8Str.size(), &tailStr, bigEndian ); + + if ( options & kXMP_ExactPacketLength ) { + size_t minSize = rdfString->size() + tailStr.size(); + if ( minSize > padding ) XMP_Throw ( "Can't fit into specified packet size", kXMPErr_BadSerialize ); + padding -= minSize; // Now the actual amount of padding to add (in bytes). + } + + utf8Str.assign ( newline ); + ToUTF16 ( (UTF8Unit*)utf8Str.c_str(), utf8Str.size(), &newlineStr, bigEndian ); + size_t newlineLen = newlineStr.size(); + + if ( padding < newlineLen ) { + for ( int i = padding/2; i > 0; --i ) *rdfString += padStr; + } else { + padding -= newlineLen; // Write this newline last. + while ( padding >= (200 + newlineLen) ) { + for ( int i = 100; i > 0; --i ) *rdfString += padStr; + *rdfString += newlineStr; + padding -= (200 + newlineLen); + } + for ( int i = padding/2; i > 0; --i ) *rdfString += padStr; + *rdfString += newlineStr; + } + + *rdfString += tailStr; + + } else { + + std::string padStr ( " " ); padStr[0] = padStr[1] = padStr[2] = 0; // Assume big endian. + + if ( charEncoding & _XMP_LittleEndian_Bit ) { + padStr[0] = ' '; padStr[1] = padStr[2] = padStr[3] = 0; + } + + utf8Str.swap ( *rdfString ); + ToUTF32 ( (UTF8Unit*)utf8Str.c_str(), utf8Str.size(), rdfString, bigEndian ); + utf8Str.swap ( tailStr ); + ToUTF32 ( (UTF8Unit*)utf8Str.c_str(), utf8Str.size(), &tailStr, bigEndian ); + + if ( options & kXMP_ExactPacketLength ) { + size_t minSize = rdfString->size() + tailStr.size(); + if ( minSize > padding ) XMP_Throw ( "Can't fit into specified packet size", kXMPErr_BadSerialize ); + padding -= minSize; // Now the actual amount of padding to add (in bytes). + } + + utf8Str.assign ( newline ); + ToUTF32 ( (UTF8Unit*)utf8Str.c_str(), utf8Str.size(), &newlineStr, bigEndian ); + size_t newlineLen = newlineStr.size(); + + if ( padding < newlineLen ) { + for ( int i = padding/4; i > 0; --i ) *rdfString += padStr; + } else { + padding -= newlineLen; // Write this newline last. + while ( padding >= (400 + newlineLen) ) { + for ( int i = 100; i > 0; --i ) *rdfString += padStr; + *rdfString += newlineStr; + padding -= (400 + newlineLen); + } + for ( int i = padding/4; i > 0; --i ) *rdfString += padStr; + *rdfString += newlineStr; + } + + *rdfString += tailStr; + + } + + } + +} // SerializeToBuffer + +// ================================================================================================= diff --git a/XMPCore/source/XMPMeta.cpp b/XMPCore/source/XMPMeta.cpp new file mode 100644 index 0000000..eab85e9 --- /dev/null +++ b/XMPCore/source/XMPMeta.cpp @@ -0,0 +1,1380 @@ +// ================================================================================================= +// 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 // For sort and stable_sort. +#include // For snprintf. + +#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) + #pragma warning ( disable : 4996 ) // '...' was declared deprecated +#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 +// ========================= + + +// ================================================================================================= +// Static Variables +// ================ + +XMP_VarString * xdefaultName = 0; // Needed in XMPMeta-Parse.cpp, MoveExplicitAliases. + +static XMPMeta::ErrorCallbackInfo sDefaultErrorCallback; + +// These are embedded version strings. + +const char * kXMPCore_EmbeddedVersion = kXMPCore_VersionMessage; +const char * kXMPCore_EmbeddedCopyright = kXMPCoreName " " kXMP_CopyrightStr; + +// ================================================================================================= +// Local Utilities +// =============== + + +// ------------------------------------------------------------------------------------------------- +// DumpNodeOptions +// --------------- + +static void +DumpNodeOptions ( XMP_OptionBits options, + XMP_TextOutputProc outProc, + void * refCon ) +{ + char buffer [32]; // Decimal of a 64 bit int is at most about 20 digits. + memset(buffer, 0, 32); + + static const char * optNames[] = { " schema", // 0x8000_0000 + " ?30", + " ?29", + " -COMMAS-", + " ?27", // 0x0800_0000 + " ?26", + " ?25", + " ?24", + " ?23", // 0x0080_0000 + " isStale", + " isDerived", + " isStable", + " ?19", // 0x0008_0000 + " isInternal", + " hasAliases", + " isAlias", + " -AFTER-", // 0x0000_8000 + " -BEFORE-", + " isCompact", + " isLangAlt", + " isAlt", // 0x0000_0800 + " isOrdered", + " isArray", + " isStruct", + " hasType", // 0x0000_0080 + " hasLang", + " isQual", + " hasQual", + " ?3", // 0x0000_0008 + " ?2", + " URI", + " ?0" }; + + if ( options == 0 ) { + + OutProcNChars ( "(0x0)", 5 ); + + } else { + + OutProcNChars ( "(0x", 3 ); + OutProcHexInt ( options ); + OutProcNChars ( " :", 2 ); + + XMP_OptionBits mask = 0x80000000; + for ( int b = 0; b < 32; ++b ) { + if ( options & mask ) OutProcLiteral ( optNames[b] ); + mask = mask >> 1; + } + OutProcNChars ( ")", 1 ); + + } + +} // DumpNodeOptions + + +// ------------------------------------------------------------------------------------------------- +// DumpPropertyTree +// ---------------- + +// *** Extract the validation code into a separate routine to call on exit in debug builds. + +static void +DumpPropertyTree ( const XMP_Node * currNode, + int indent, + size_t itemIndex, + XMP_TextOutputProc outProc, + void * refCon ) +{ + char buffer [32]; // Decimal of a 64 bit int is at most about 20 digits. + + OutProcIndent ( (size_t)indent ); + if ( itemIndex == 0 ) { + if ( currNode->options & kXMP_PropIsQualifier ) OutProcNChars ( "? ", 2 ); + DumpClearString ( currNode->name, outProc, refCon ); + } else { + OutProcNChars ( "[", 1 ); + OutProcDecInt ( itemIndex ); + OutProcNChars ( "]", 1 ); + } + + if ( ! (currNode->options & kXMP_PropCompositeMask) ) { + OutProcNChars ( " = \"", 4 ); + DumpClearString ( currNode->value, outProc, refCon ); + OutProcNChars ( "\"", 1 ); + } + + if ( currNode->options != 0 ) { + OutProcNChars ( " ", 2 ); + DumpNodeOptions ( currNode->options, outProc, refCon ); + } + + if ( currNode->options & kXMP_PropHasLang ) { + if ( currNode->qualifiers.empty() || (currNode->qualifiers[0]->name != "xml:lang") ) { + OutProcLiteral ( " ** bad lang flag **" ); + } + } + // *** Check rdf:type also. + + if ( ! (currNode->options & kXMP_PropCompositeMask) ) { + if ( ! currNode->children.empty() ) OutProcLiteral ( " ** bad children **" ); + } else if ( currNode->options & kXMP_PropValueIsArray ) { + if ( currNode->options & kXMP_PropValueIsStruct ) OutProcLiteral ( " ** bad comp flags **" ); + } else if ( (currNode->options & kXMP_PropCompositeMask) != kXMP_PropValueIsStruct ) { + OutProcLiteral ( " ** bad comp flags **" ); + } + + #if 0 // *** XMP_DebugBuild + if ( (currNode->_namePtr != currNode->name.c_str()) || + (currNode->_valuePtr != currNode->value.c_str()) ) OutProcLiteral ( " ** bad debug string **" ); + #endif + + OutProcNewline(); + + for ( size_t qualNum = 0, qualLim = currNode->qualifiers.size(); qualNum < qualLim; ++qualNum ) { + + const XMP_Node * currQual = currNode->qualifiers[qualNum]; + + if ( currQual->parent != currNode ) OutProcLiteral ( "** bad parent link => " ); + if ( currQual->name == kXMP_ArrayItemName ) OutProcLiteral ( "** bad qual name => " ); + if ( ! (currQual->options & kXMP_PropIsQualifier) ) OutProcLiteral ( "** bad qual flag => " ); + if ( currQual->name == "xml:lang" ) { + if ( (qualNum != 0) || (! (currNode->options & kXMP_PropHasLang)) ) OutProcLiteral ( "** bad lang qual => " ); + } + + DumpPropertyTree ( currQual, indent+2, 0, outProc, refCon ); + + } + + for ( size_t childNum = 0, childLim = currNode->children.size(); childNum < childLim; ++childNum ) { + + const XMP_Node * currChild = currNode->children[childNum]; + + if ( currChild->parent != currNode ) OutProcLiteral ( "** bad parent link => " ); + if ( currChild->options & kXMP_PropIsQualifier ) OutProcLiteral ( "** bad qual flag => " ); + + if ( currNode->options & kXMP_PropValueIsArray ) { + itemIndex = childNum+1; + if ( currChild->name != kXMP_ArrayItemName ) OutProcLiteral ( "** bad item name => " ); + } else { + itemIndex = 0; + if ( currChild->name == kXMP_ArrayItemName ) OutProcLiteral ( "** bad field name => " ); + } + + DumpPropertyTree ( currChild, indent+1, itemIndex, outProc, refCon ); + + } + +} // DumpPropertyTree + + +// ------------------------------------------------------------------------------------------------- +// DumpXMLTree +// ----------- + +#if DumpXMLParseTree + +static inline void PutHexByte ( FILE * log, unsigned char ch ) +{ + + fprintf ( log, "\\x" ); + if ( ch < 0x10 ) { + fprintf ( log, "%c", kHexDigits[ch] ); + } else { + fprintf ( log, "%c%c", kHexDigits[ch>>4], kHexDigits[ch&0xF] ); + } + +} // PutHexByte + +// ------------------------------------------------------------------------------------------------- + +static void PutClearString ( FILE * log, const std::string & str ) +{ + + for ( size_t i = 0; i != str.size(); ++i ) { + unsigned char ch = str[i]; + if ( (0x20 <= ch) && (ch <= 0x7F) ) { + fprintf ( log, "%c", ch ); + } else { + PutHexByte ( log, ch ); + } + } + +} // PutClearString + +// ------------------------------------------------------------------------------------------------- + +static void DumpXMLTree ( FILE * log, const XML_Node & node, int indent ) +{ + size_t i; + + #if 0 // *** XMP_DebugBuild + if ( (node._namePtr != node.name.c_str()) || + (node._valuePtr != node.value.c_str()) ) fprintf ( log, "*** bad debug string ***\n" ); + #endif + + for ( i = 0; i != (size_t)indent; ++i ) fprintf ( log, " " ); + + switch ( node.kind ) { + + case kRootNode : + fprintf ( log, "\nStart of XML tree dump\n\n" ); + if ( (indent != 0) || (! node.attrs.empty()) || + (! node.ns.empty()) || (! node.name.empty()) || (!node.value.empty()) ) fprintf ( log, " ** invalid root ** \n" ); + for ( i = 0; i < node.children.size(); ++i ) { + XMP_Uns8 kind = node.children[i]->kind; + if ( (kind == kRootNode) || (kind == kAttrNode) ) fprintf ( log, " ** invalid child ** \n" ); + DumpXMLTree ( log, *node.children[i], indent+1 ); + } + fprintf ( log, "\nEnd of XML tree dump\n" ); + break; + + case kElemNode : + fprintf ( log, "Elem %s", node.name.c_str() ); + if ( indent == 0 ) fprintf ( log, " ** invalid elem ** " ); + if ( ! node.ns.empty() ) fprintf ( log, " @ %s", node.ns.c_str() ); + fprintf ( log, "\n" ); + for ( i = 0; i < node.attrs.size(); ++i ) { + XMP_Uns8 kind = node.attrs[i]->kind; + if ( kind != kAttrNode ) fprintf ( log, " ** invalid attr ** \n" ); + DumpXMLTree ( log, *node.attrs[i], indent+2 ); + } + for ( i = 0; i < node.children.size(); ++i ) { + XMP_Uns8 kind = node.children[i]->kind; + if ( (kind == kRootNode) || (kind == kAttrNode) ) fprintf ( log, " ** invalid child ** \n" ); + DumpXMLTree ( log, *node.children[i], indent+1 ); + } + break; + + case kAttrNode : + fprintf ( log, "Attr %s", node.name.c_str() ); + if ( (indent == 0) || node.name.empty() || (! node.attrs.empty()) || (! node.children.empty()) ) fprintf ( log, " ** invalid attr ** " ); + fprintf ( log, " = \"" ); + PutClearString ( log, node.value ); + fprintf ( log, "\"" ); + if ( ! node.ns.empty() ) fprintf ( log, " @ %s", node.ns.c_str() ); + fprintf ( log, "\n" ); + break; + + case kCDataNode : + if ( (indent == 0) || (! node.ns.empty()) || (! node.name.empty()) || + (! node.attrs.empty()) || (! node.children.empty()) ) fprintf ( log, " ** invalid cdata ** \n" ); + fprintf ( log, "\"" ); + PutClearString ( log, node.value ); + fprintf ( log, "\"\n" ); + break; + + case kPINode : + fprintf ( log, "PI %s", node.name.c_str() ); + if ( (indent == 0) || node.name.empty() || (! node.children.empty()) ) fprintf ( log, " ** invalid pi ** \n" ); + if ( ! node.value.empty() ) { + fprintf ( log, " " ); + } + fprintf ( log, "\n" ); + break; + + } + +} // DumpXMLTree + +#endif // DumpXMLParseTree + + +// ------------------------------------------------------------------------------------------------- +// CompareNodeNames +// ---------------- +// +// Comparison routine for sorting XMP nodes by name. The name "xml:lang" is less than anything else, +// and "rdf:type" is less than anything except "xml:lang". This preserves special rules for qualifiers. + +static bool +CompareNodeNames ( XMP_Node * left, XMP_Node * right ) +{ + + if ( left->name == "xml:lang" ) return true; + if ( right->name == "xml:lang" ) return false; + + if ( left->name == "rdf:type" ) return true; + if ( right->name == "rdf:type" ) return false; + + return ( left->name < right->name ); + +} // CompareNodeNames + + +// ------------------------------------------------------------------------------------------------- +// CompareNodeValues +// ----------------- +// +// Comparison routine for sorting XMP nodes by value. + +static bool +CompareNodeValues ( XMP_Node * left, XMP_Node * right ) +{ + + if ( XMP_PropIsSimple ( left->options ) && XMP_PropIsSimple ( right->options ) ) { + return ( left->value < right->value ); + } + + XMP_OptionBits leftForm = left->options & kXMP_PropCompositeMask; + XMP_OptionBits rightForm = right->options & kXMP_PropCompositeMask; + + return ( leftForm < rightForm ); + +} // CompareNodeValues + + +// ------------------------------------------------------------------------------------------------- +// CompareNodeLangs +// ---------------- +// +// Comparison routine for sorting XMP nodes by xml:lang qualifier. An "x-default" value is less than +// any other language. + +static bool +CompareNodeLangs ( XMP_Node * left, XMP_Node * right ) +{ + + if ( left->qualifiers.empty() || (left->qualifiers[0]->name != "xml:lang") ) return false; + if ( right->qualifiers.empty() || (right->qualifiers[0]->name != "xml:lang") ) return false; + + if ( left->qualifiers[0]->value == "x-default" ) return true; + if ( right->qualifiers[0]->value == "x-default" ) return false; + + return ( left->qualifiers[0]->value < right->qualifiers[0]->value ); + +} // CompareNodeLangs + + +// ------------------------------------------------------------------------------------------------- +// SortWithinOffspring +// ------------------- +// +// Sort one level down, within the elements of a node vector. This sorts the qualifiers of each +// node. If the node is a struct it sorts the fields by names. If the node is an unordered array it +// sorts the elements by value. If the node is an AltText array it sorts the elements by language. + +static void +SortWithinOffspring ( XMP_NodeOffspring & nodeVec ) +{ + + for ( size_t i = 0, limit = nodeVec.size(); i < limit; ++i ) { + + XMP_Node * currPos = nodeVec[i]; + + if ( ! currPos->qualifiers.empty() ) { + sort ( currPos->qualifiers.begin(), currPos->qualifiers.end(), CompareNodeNames ); + SortWithinOffspring ( currPos->qualifiers ); + } + + if ( ! currPos->children.empty() ) { + + if ( XMP_PropIsStruct ( currPos->options ) || XMP_NodeIsSchema ( currPos->options ) ) { + sort ( currPos->children.begin(), currPos->children.end(), CompareNodeNames ); + } else if ( XMP_PropIsArray ( currPos->options ) ) { + if ( XMP_ArrayIsUnordered ( currPos->options ) ) { + stable_sort ( currPos->children.begin(), currPos->children.end(), CompareNodeValues ); + } else if ( XMP_ArrayIsAltText ( currPos->options ) ) { + sort ( currPos->children.begin(), currPos->children.end(), CompareNodeLangs ); + } + } + + SortWithinOffspring ( currPos->children ); + + } + + } + +} // SortWithinOffspring + + +// ------------------------------------------------------------------------------------------------- +// RegisterAlias +// ------------- +// +// Allow 3 kinds of alias: +// TopProp => TopProp +// TopProp => TopArray[1] +// TopProp => TopArray[@xml:lang='x-default'] +// +// A new alias can be made to something that is already aliased, as long as the net result is one of +// the legitimate forms. The new alias can already have aliases to it, also as long as result of +// adjusting all of the exiting aliases leaves them legal. +// +// ! The caller assumes all risk that new aliases do not invalidate existing XMPMeta objects. Any +// ! conflicts will result in later references throwing bad XPath exceptions. + +static void +RegisterAlias ( XMP_StringPtr aliasNS, + XMP_StringPtr aliasProp, + XMP_StringPtr actualNS, + XMP_StringPtr actualProp, + XMP_OptionBits arrayForm ) +{ + XMP_ExpandedXPath expAlias, expActual; + XMP_AliasMapPos mapPos; + XMP_ExpandedXPath * regActual = 0; + + XMP_Assert ( (aliasNS != 0) && (aliasProp != 0) && (actualNS != 0) && (actualProp != 0) ); // Enforced by wrapper. + + // Expand the alias and actual names, make sure they are one of the basic 3 forms. When counting + // the expanded XPath size remember that the schema URI is the first component. We don't have to + // compare the schema URIs though, the (unique) prefix is part of the top property name. + + ExpandXPath ( aliasNS, aliasProp, &expAlias ); + ExpandXPath ( actualNS, actualProp, &expActual ); + if ( (expAlias.size() != 2) || (expActual.size() != 2) ) { + XMP_Throw ( "Alias and actual property names must be simple", kXMPErr_BadXPath ); + } + + arrayForm = VerifySetOptions ( arrayForm, 0 ); + if ( arrayForm != 0 ) { + if ( (arrayForm & ~kXMP_PropArrayFormMask) != 0 ) XMP_Throw ( "Only array form flags are allowed", kXMPErr_BadOptions ); + expActual[1].options |= arrayForm; // Set the array form for the top level step. + if ( ! (arrayForm & kXMP_PropArrayIsAltText) ) { + expActual.push_back ( XPathStepInfo ( "[1]", kXMP_ArrayIndexStep ) ); + } else { + expActual.push_back ( XPathStepInfo ( "[?xml:lang=\"x-default\"]", kXMP_QualSelectorStep ) ); + } + } + + // See if there are any conflicts with existing aliases. A couple of the checks are easy. If the + // alias is already aliased it is only OK to reregister an identical alias. If the actual is + // already aliased to something else and the new chain is legal, just swap in the old base. + + mapPos = sRegisteredAliasMap->find ( expAlias[kRootPropStep].step ); + if ( mapPos != sRegisteredAliasMap->end() ) { + + // This alias is already registered to something, make sure it is the same something. + + regActual = &mapPos->second; + if ( arrayForm != (mapPos->second[1].options & kXMP_PropArrayFormMask) ) { + XMP_Throw ( "Mismatch with existing alias array form", kXMPErr_BadParam ); + } + if ( expActual.size() != regActual->size() ) { + XMP_Throw ( "Mismatch with existing actual path", kXMPErr_BadParam ); + } + if ( expActual[kRootPropStep].step != (*regActual)[kRootPropStep].step ) { + XMP_Throw ( "Mismatch with existing actual name", kXMPErr_BadParam ); + } + if ( (expActual.size() == 3) && (expActual[kAliasIndexStep].step != (*regActual)[kAliasIndexStep].step) ) { + XMP_Throw ( "Mismatch with existing actual array item", kXMPErr_BadParam ); + } + return; + + } + + mapPos = sRegisteredAliasMap->find ( expActual[kRootPropStep].step ); + if ( mapPos != sRegisteredAliasMap->end() ) { + + // The actual is already aliased to something else. + + regActual = &mapPos->second; + if ( expActual.size() == 2 ) { + expActual = *regActual; // TopProp => TopProp => anything : substitute the entire old base. + } else if ( regActual->size() != 2 ) { + XMP_Throw ( "Can't alias an array item to an array item", kXMPErr_BadParam ); // TopProp => TopArray[] => TopArray[] : nope. + } else { + expActual[kSchemaStep].step = (*regActual)[kSchemaStep].step; // TopProp => TopArray[] => TopProp : + expActual[kRootPropStep].step = (*regActual)[kRootPropStep].step; // substitute the old base name. + } + + } + + // Checking for existing aliases to this one is touchier. This involves updating the alias map, + // which must not be done unless all of the changes are legal. So we need 2 loops, one to verify + // that everything is OK, and one to make the changes. The bad case is: + // TopProp => TopArray[] => TopArray[] + // In the valid cases we back substitute the new base. + + for ( mapPos = sRegisteredAliasMap->begin(); mapPos != sRegisteredAliasMap->end(); ++mapPos ) { + regActual = &mapPos->second; + if ( expAlias[kRootPropStep].step == (*regActual)[kRootPropStep].step ) { + if ( (regActual->size() == 2) && (expAlias.size() == 2) ) { + XMP_Throw ( "Can't alias an array item to an array item", kXMPErr_BadParam ); + } + } + } + + for ( mapPos = sRegisteredAliasMap->begin(); mapPos != sRegisteredAliasMap->end(); ++mapPos ) { + regActual = &mapPos->second; + if ( expAlias[kRootPropStep].step == (*regActual)[kRootPropStep].step ) { + + if ( regActual->size() == 1 ) { + *regActual = expActual; // TopProp => TopProp => anything : substitute the entire new base. + } else { + (*regActual)[kSchemaStep].step = expActual[kSchemaStep].step; // TopProp => TopArray[] => TopProp : + (*regActual)[kRootPropStep].step = expActual[kRootPropStep].step; // substitute the new base name. + } + + } + } + + // Finally, all is OK to register the new alias. + + (void) sRegisteredAliasMap->insert ( XMP_AliasMap::value_type ( expAlias[kRootPropStep].step, expActual ) ); + +} // RegisterAlias + + +// ------------------------------------------------------------------------------------------------- +// RegisterStandardAliases +// ----------------------- + +static void +RegisterStandardAliases() +{ + + // Aliases from XMP to DC. + RegisterAlias ( kXMP_NS_XMP, "Author", kXMP_NS_DC, "creator", kXMP_PropArrayIsOrdered ); + RegisterAlias ( kXMP_NS_XMP, "Authors", kXMP_NS_DC, "creator", 0 ); + RegisterAlias ( kXMP_NS_XMP, "Description", kXMP_NS_DC, "description", 0 ); + RegisterAlias ( kXMP_NS_XMP, "Format", kXMP_NS_DC, "format", 0 ); + RegisterAlias ( kXMP_NS_XMP, "Keywords", kXMP_NS_DC, "subject", 0 ); + RegisterAlias ( kXMP_NS_XMP, "Locale", kXMP_NS_DC, "language", 0 ); + RegisterAlias ( kXMP_NS_XMP, "Title", kXMP_NS_DC, "title", 0 ); + RegisterAlias ( kXMP_NS_XMP_Rights, "Copyright", kXMP_NS_DC, "rights", 0 ); + + // Aliases from PDF to DC and XMP. + RegisterAlias ( kXMP_NS_PDF, "Author", kXMP_NS_DC, "creator", kXMP_PropArrayIsOrdered ); + RegisterAlias ( kXMP_NS_PDF, "BaseURL", kXMP_NS_XMP, "BaseURL", 0 ); + RegisterAlias ( kXMP_NS_PDF, "CreationDate", kXMP_NS_XMP, "CreateDate", 0 ); + RegisterAlias ( kXMP_NS_PDF, "Creator", kXMP_NS_XMP, "CreatorTool", 0 ); + RegisterAlias ( kXMP_NS_PDF, "ModDate", kXMP_NS_XMP, "ModifyDate", 0 ); + RegisterAlias ( kXMP_NS_PDF, "Subject", kXMP_NS_DC, "description", kXMP_PropArrayIsAltText ); + RegisterAlias ( kXMP_NS_PDF, "Title", kXMP_NS_DC, "title", kXMP_PropArrayIsAltText ); + + // Aliases from Photoshop to DC and XMP. + RegisterAlias ( kXMP_NS_Photoshop, "Author", kXMP_NS_DC, "creator", kXMP_PropArrayIsOrdered ); + RegisterAlias ( kXMP_NS_Photoshop, "Caption", kXMP_NS_DC, "description", kXMP_PropArrayIsAltText ); + RegisterAlias ( kXMP_NS_Photoshop, "Copyright", kXMP_NS_DC, "rights", kXMP_PropArrayIsAltText ); + RegisterAlias ( kXMP_NS_Photoshop, "Keywords", kXMP_NS_DC, "subject", 0 ); + RegisterAlias ( kXMP_NS_Photoshop, "Marked", kXMP_NS_XMP_Rights, "Marked", 0 ); + RegisterAlias ( kXMP_NS_Photoshop, "Title", kXMP_NS_DC, "title", kXMP_PropArrayIsAltText ); + RegisterAlias ( kXMP_NS_Photoshop, "WebStatement", kXMP_NS_XMP_Rights, "WebStatement", 0 ); + + // Aliases from TIFF and EXIF to DC and XMP. + RegisterAlias ( kXMP_NS_TIFF, "Artist", kXMP_NS_DC, "creator", kXMP_PropArrayIsOrdered); + RegisterAlias ( kXMP_NS_TIFF, "Copyright", kXMP_NS_DC, "rights", 0 ); + RegisterAlias ( kXMP_NS_TIFF, "DateTime", kXMP_NS_XMP, "ModifyDate", 0 ); + RegisterAlias ( kXMP_NS_EXIF, "DateTimeDigitized", kXMP_NS_XMP, "CreateDate", 0 ); + RegisterAlias ( kXMP_NS_TIFF, "ImageDescription", kXMP_NS_DC, "description", 0 ); + RegisterAlias ( kXMP_NS_TIFF, "Software", kXMP_NS_XMP, "CreatorTool", 0 ); + + // Aliases from PNG to DC and XMP. + RegisterAlias ( kXMP_NS_PNG, "Author", kXMP_NS_DC, "creator", kXMP_PropArrayIsOrdered); + RegisterAlias ( kXMP_NS_PNG, "Copyright", kXMP_NS_DC, "rights", kXMP_PropArrayIsAltText); + RegisterAlias ( kXMP_NS_PNG, "CreationTime", kXMP_NS_XMP, "CreateDate", 0 ); + RegisterAlias ( kXMP_NS_PNG, "Description", kXMP_NS_DC, "description", kXMP_PropArrayIsAltText); + RegisterAlias ( kXMP_NS_PNG, "ModificationTime", kXMP_NS_XMP, "ModifyDate", 0 ); + RegisterAlias ( kXMP_NS_PNG, "Software", kXMP_NS_XMP, "CreatorTool", 0 ); + RegisterAlias ( kXMP_NS_PNG, "Title", kXMP_NS_DC, "title", kXMP_PropArrayIsAltText); + +} // RegisterStandardAliases + + +// ================================================================================================= +// Constructors +// ============ + + +XMPMeta::XMPMeta() : clientRefs(0), tree(XMP_Node(0,"",0)), xmlParser(0) +{ + #if XMP_TraceCTorDTor + printf ( "Default construct XMPMeta @ %.8X\n", this ); + #endif + + if ( sDefaultErrorCallback.clientProc != 0 ) { + this->errorCallback.wrapperProc = sDefaultErrorCallback.wrapperProc; + this->errorCallback.clientProc = sDefaultErrorCallback.clientProc; + this->errorCallback.context = sDefaultErrorCallback.context; + this->errorCallback.limit = sDefaultErrorCallback.limit; + } + +} // XMPMeta + +// ------------------------------------------------------------------------------------------------- + +XMPMeta::~XMPMeta() RELEASE_NO_THROW +{ + #if XMP_TraceCTorDTor + printf ( "Destruct XMPMeta @ %.8X\n", this ); + #endif + + XMP_Assert ( this->clientRefs <= 0 ); + if ( xmlParser != 0 ) delete ( xmlParser ); + xmlParser = 0; + +} // ~XMPMeta + + +// ================================================================================================= +// Class Static Functions +// ====================== +// +// +// ================================================================================================= + +// ------------------------------------------------------------------------------------------------- +// GetVersionInfo +// -------------- + +/* class-static */ void +XMPMeta::GetVersionInfo ( XMP_VersionInfo * info ) +{ + + memset ( info, 0, sizeof(*info) ); // AUDIT: Safe, using sizeof the destination. + XMP_Assert ( sizeof(*info) == sizeof(XMP_VersionInfo) ); + + info->major = XMPCORE_API_VERSION_MAJOR; + info->minor = XMPCORE_API_VERSION_MINOR; + info->micro = 0; //no longer used + info->isDebug = kXMPCore_DebugFlag; + info->flags = 0; // ! None defined yet. + info->message = kXMPCore_VersionMessage; + +} // GetVersionInfo + +// ------------------------------------------------------------------------------------------------- +// Initialize +// ---------- + +#if XMP_TraceCoreCalls + FILE * xmpCoreLog = stderr; +#endif + +#if UseGlobalLibraryLock + XMP_BasicMutex sLibraryLock; +#endif + +/* class-static */ bool +XMPMeta::Initialize() +{ + // Allocate and initialize static objects. + + ++sXMP_InitCount; + if ( sXMP_InitCount > 1 ) return true; + + #if XMP_TraceCoreCallsToFile + xmpCoreLog = fopen ( "XMPCoreLog.txt", "w" ); + if ( xmpCoreLog == 0 ) xmpCoreLog = stderr; + #endif + + #if UseGlobalLibraryLock + InitializeBasicMutex ( sLibraryLock ); + #endif + + if ( ! Initialize_LibUtils() ) return false; + xdefaultName = new XMP_VarString ( "x-default" ); + + sRegisteredNamespaces = new XMP_NamespaceTable; + sRegisteredAliasMap = new XMP_AliasMap; + + InitializeUnicodeConversions(); + + + // Register standard namespaces and aliases. + + XMP_StringPtr voidPtr; + XMP_StringLen voidLen; + + (void) RegisterNamespace ( kXMP_NS_XML, "xml", &voidPtr, &voidLen ); + (void) RegisterNamespace ( kXMP_NS_RDF, "rdf", &voidPtr, &voidLen ); + (void) RegisterNamespace ( kXMP_NS_DC, "dc", &voidPtr, &voidLen ); + + (void) RegisterNamespace ( kXMP_NS_XMP, "xmp", &voidPtr, &voidLen ); + (void) RegisterNamespace ( kXMP_NS_PDF, "pdf", &voidPtr, &voidLen ); + (void) RegisterNamespace ( kXMP_NS_Photoshop, "photoshop", &voidPtr, &voidLen ); + (void) RegisterNamespace ( kXMP_NS_PSAlbum, "album", &voidPtr, &voidLen ); + (void) RegisterNamespace ( kXMP_NS_EXIF, "exif", &voidPtr, &voidLen ); + (void) RegisterNamespace ( kXMP_NS_EXIF_Aux, "aux", &voidPtr, &voidLen ); + (void) RegisterNamespace ( kXMP_NS_ExifEX, "exifEX", &voidPtr, &voidLen ); + (void) RegisterNamespace ( kXMP_NS_TIFF, "tiff", &voidPtr, &voidLen ); + (void) RegisterNamespace ( kXMP_NS_PNG, "png", &voidPtr, &voidLen ); + (void) RegisterNamespace ( kXMP_NS_JPEG, "jpeg", &voidPtr, &voidLen ); + (void) RegisterNamespace ( kXMP_NS_JP2K, "jp2k", &voidPtr, &voidLen ); + (void) RegisterNamespace ( kXMP_NS_CameraRaw, "crs", &voidPtr, &voidLen ); + (void) RegisterNamespace ( kXMP_NS_ASF, "asf", &voidPtr, &voidLen ); + (void) RegisterNamespace ( kXMP_NS_WAV, "wav", &voidPtr, &voidLen ); + + (void) RegisterNamespace ( kXMP_NS_AdobeStockPhoto, "bmsp", &voidPtr, &voidLen ); + (void) RegisterNamespace ( kXMP_NS_CreatorAtom, "creatorAtom", &voidPtr, &voidLen ); + + (void) RegisterNamespace ( kXMP_NS_XMP_Rights, "xmpRights", &voidPtr, &voidLen ); + (void) RegisterNamespace ( kXMP_NS_XMP_MM, "xmpMM", &voidPtr, &voidLen ); + (void) RegisterNamespace ( kXMP_NS_XMP_BJ, "xmpBJ", &voidPtr, &voidLen ); + (void) RegisterNamespace ( kXMP_NS_XMP_Note, "xmpNote", &voidPtr, &voidLen ); + + (void) RegisterNamespace ( kXMP_NS_DM, "xmpDM", &voidPtr, &voidLen ); + (void) RegisterNamespace ( kXMP_NS_Script, "xmpScript", &voidPtr, &voidLen ); + (void) RegisterNamespace ( kXMP_NS_BWF, "bext", &voidPtr, &voidLen ); + (void) RegisterNamespace ( kXMP_NS_AEScart, "AEScart", &voidPtr, &voidLen ); + (void) RegisterNamespace ( kXMP_NS_RIFFINFO, "riffinfo", &voidPtr, &voidLen ); + (void) RegisterNamespace ( kXMP_NS_XMP_Text, "xmpT", &voidPtr, &voidLen ); + (void) RegisterNamespace ( kXMP_NS_XMP_PagedFile, "xmpTPg", &voidPtr, &voidLen ); + (void) RegisterNamespace ( kXMP_NS_XMP_Graphics, "xmpG", &voidPtr, &voidLen ); + (void) RegisterNamespace ( kXMP_NS_XMP_Image, "xmpGImg", &voidPtr, &voidLen ); + + (void) RegisterNamespace ( kXMP_NS_XMP_Font, "stFnt", &voidPtr, &voidLen ); + (void) RegisterNamespace ( kXMP_NS_XMP_Dimensions, "stDim", &voidPtr, &voidLen ); + (void) RegisterNamespace ( kXMP_NS_XMP_ResourceEvent, "stEvt", &voidPtr, &voidLen ); + (void) RegisterNamespace ( kXMP_NS_XMP_ResourceRef, "stRef", &voidPtr, &voidLen ); + (void) RegisterNamespace ( kXMP_NS_XMP_ST_Version, "stVer", &voidPtr, &voidLen ); + (void) RegisterNamespace ( kXMP_NS_XMP_ST_Job, "stJob", &voidPtr, &voidLen ); + (void) RegisterNamespace ( kXMP_NS_XMP_ManifestItem, "stMfs", &voidPtr, &voidLen ); + + (void) RegisterNamespace ( kXMP_NS_XMP_IdentifierQual, "xmpidq", &voidPtr, &voidLen ); + + (void) RegisterNamespace ( kXMP_NS_IPTCCore, "Iptc4xmpCore", &voidPtr, &voidLen ); + (void) RegisterNamespace ( kXMP_NS_IPTCExt, "Iptc4xmpExt", &voidPtr, &voidLen ); + (void) RegisterNamespace ( kXMP_NS_DICOM, "DICOM", &voidPtr, &voidLen ); + (void) RegisterNamespace ( kXMP_NS_PLUS, "plus", &voidPtr, &voidLen ); + + (void) RegisterNamespace ( kXMP_NS_PDFA_Schema, "pdfaSchema", &voidPtr, &voidLen ); + (void) RegisterNamespace ( kXMP_NS_PDFA_Property, "pdfaProperty", &voidPtr, &voidLen ); + (void) RegisterNamespace ( kXMP_NS_PDFA_Type, "pdfaType", &voidPtr, &voidLen ); + (void) RegisterNamespace ( kXMP_NS_PDFA_Field, "pdfaField", &voidPtr, &voidLen ); + (void) RegisterNamespace ( kXMP_NS_PDFA_ID, "pdfaid", &voidPtr, &voidLen ); + (void) RegisterNamespace ( kXMP_NS_PDFA_Extension, "pdfaExtension", &voidPtr, &voidLen ); + + (void) RegisterNamespace ( kXMP_NS_PDFX, "pdfx", &voidPtr, &voidLen ); + (void) RegisterNamespace ( kXMP_NS_PDFX_ID, "pdfxid", &voidPtr, &voidLen ); + + (void) RegisterNamespace ( "adobe:ns:meta/", "x", &voidPtr, &voidLen ); + (void) RegisterNamespace ( "http://ns.adobe.com/iX/1.0/", "iX", &voidPtr, &voidLen ); + + RegisterStandardAliases(); + + // Initialize the other core classes. + + if ( ! XMPIterator::Initialize() ) XMP_Throw ( "Failure from XMPIterator::Initialize", kXMPErr_InternalFailure ); + if ( ! XMPUtils::Initialize() ) XMP_Throw ( "Failure from XMPUtils::Initialize", kXMPErr_InternalFailure ); + // Do miscelaneous semantic checks of types and arithmetic. + + XMP_Assert ( sizeof(XMP_Int8) == 1 ); + XMP_Assert ( sizeof(XMP_Int16) == 2 ); + XMP_Assert ( sizeof(XMP_Int32) == 4 ); + XMP_Assert ( sizeof(XMP_Int64) == 8 ); + XMP_Assert ( sizeof(XMP_Uns8) == 1 ); + XMP_Assert ( sizeof(XMP_Uns16) == 2 ); + XMP_Assert ( sizeof(XMP_Uns32) == 4 ); + XMP_Assert ( sizeof(XMP_Uns64) == 8 ); + XMP_Assert ( sizeof(XMP_Bool) == 1 ); + + XMP_Assert ( sizeof(XMP_OptionBits) == 4 ); // Check that option masking work on all 32 bits. +#if XMP_DebugBuild + XMP_OptionBits flag = (XMP_OptionBits) (~0UL); +#endif + XMP_Assert ( flag == (XMP_OptionBits)(-1L) ); + XMP_Assert ( (flag ^ kXMP_PropHasLang) == 0xFFFFFFBFUL ); + XMP_Assert ( (flag & ~kXMP_PropHasLang) == 0xFFFFFFBFUL ); + + XMP_OptionBits opt1 = 0; // Check the general option bit macros. + XMP_OptionBits opt2 = (XMP_OptionBits)~0UL; + XMP_SetOption ( opt1, kXMP_PropValueIsArray ); + XMP_ClearOption ( opt2, kXMP_PropValueIsArray ); + XMP_Assert ( opt1 == ~opt2 ); + XMP_Assert ( XMP_TestOption ( opt1, kXMP_PropValueIsArray ) ); + XMP_Assert ( ! XMP_TestOption ( opt2, kXMP_PropValueIsArray ) ); + + XMP_Assert ( XMP_PropIsSimple ( ~kXMP_PropCompositeMask ) ); // Check the special option bit macros. + XMP_Assert ( ! XMP_PropIsSimple ( kXMP_PropValueIsStruct ) ); + XMP_Assert ( ! XMP_PropIsSimple ( kXMP_PropValueIsArray ) ); + + XMP_Assert ( XMP_PropIsStruct ( kXMP_PropValueIsStruct ) ); + XMP_Assert ( XMP_PropIsArray ( kXMP_PropValueIsArray ) ); + XMP_Assert ( ! XMP_PropIsStruct ( ~kXMP_PropValueIsStruct ) ); + XMP_Assert ( ! XMP_PropIsArray ( ~kXMP_PropValueIsArray ) ); + + XMP_Assert ( XMP_ArrayIsUnordered ( ~kXMP_PropArrayIsOrdered ) ); + XMP_Assert ( XMP_ArrayIsOrdered ( kXMP_PropArrayIsOrdered ) ); + XMP_Assert ( XMP_ArrayIsAlternate ( kXMP_PropArrayIsAlternate ) ); + XMP_Assert ( XMP_ArrayIsAltText ( kXMP_PropArrayIsAltText ) ); + XMP_Assert ( ! XMP_ArrayIsUnordered ( kXMP_PropArrayIsOrdered ) ); + XMP_Assert ( ! XMP_ArrayIsOrdered ( ~kXMP_PropArrayIsOrdered ) ); + XMP_Assert ( ! XMP_ArrayIsAlternate ( ~kXMP_PropArrayIsAlternate ) ); + XMP_Assert ( ! XMP_ArrayIsAltText ( ~kXMP_PropArrayIsAltText ) ); + + XMP_Assert ( XMP_PropHasQualifiers ( kXMP_PropHasQualifiers ) ); + XMP_Assert ( XMP_PropIsQualifier ( kXMP_PropIsQualifier ) ); + XMP_Assert ( XMP_PropHasLang ( kXMP_PropHasLang ) ); + XMP_Assert ( ! XMP_PropHasQualifiers ( ~kXMP_PropHasQualifiers ) ); + XMP_Assert ( ! XMP_PropIsQualifier ( ~kXMP_PropIsQualifier ) ); + XMP_Assert ( ! XMP_PropHasLang ( ~kXMP_PropHasLang ) ); + + XMP_Assert ( XMP_NodeIsSchema ( kXMP_SchemaNode ) ); + XMP_Assert ( XMP_PropIsAlias ( kXMP_PropIsAlias ) ); + XMP_Assert ( ! XMP_NodeIsSchema ( ~kXMP_SchemaNode ) ); + XMP_Assert ( ! XMP_PropIsAlias ( ~kXMP_PropIsAlias ) ); + + #if 0 // Generally off, enable to hand check generated code. + extern XMP_OptionBits opt3, opt4; + if ( XMP_TestOption ( opt3, kXMP_PropValueIsArray ) ) opt4 = opt3; + if ( ! XMP_TestOption ( opt3, kXMP_PropValueIsStruct ) ) opt4 = opt3; + static bool ok1 = XMP_TestOption ( opt4, kXMP_PropValueIsArray ); + static bool ok2 = ! XMP_TestOption ( opt4, kXMP_PropValueIsStruct ); + #endif + + // Make sure the embedded info strings are referenced and kept. + if ( (kXMPCore_EmbeddedVersion[0] == 0) || (kXMPCore_EmbeddedCopyright[0] == 0) ) return false; + return true; + +} // Initialize + + +// ------------------------------------------------------------------------------------------------- +// Terminate +// --------- + +/* class-static */ void +XMPMeta::Terminate() RELEASE_NO_THROW +{ + --sXMP_InitCount; + if ( sXMP_InitCount != 0 ) return; // Not ready to terminate, or already terminated. + + XMPIterator::Terminate(); + XMPUtils::Terminate(); +#if ENABLE_NEW_DOM_MODEL + NS_XMPCOMMON::ITSingleton< NS_INT_XMPCORE::IXMPCoreObjectFactory >::DestroyInstance(); + NS_INT_XMPCOMMON::TerminateXMPCommonFramework(); +#endif + + EliminateGlobal ( sRegisteredNamespaces ); + EliminateGlobal ( sRegisteredAliasMap ); + + EliminateGlobal ( xdefaultName ); + + Terminate_LibUtils(); + + #if UseGlobalLibraryLock + TerminateBasicMutex ( sLibraryLock ); + #endif + + #if XMP_TraceCoreCallsToFile + if ( xmpCoreLog != stderr ) fclose ( xmpCoreLog ); + xmpCoreLog = stderr; + #endif + + // reset static variables + sDefaultErrorCallback.Clear(); +} // Terminate + + +// ------------------------------------------------------------------------------------------------- +// DumpNamespaces +// -------------- +// +// Dump the prefix to URI map (easier to read) and verify that both are consistent and legit. + +// *** Should put checks in a separate routine for regular calling in debug builds. + +/* class-static */ XMP_Status +XMPMeta::DumpNamespaces ( XMP_TextOutputProc outProc, + void * refCon ) +{ + + sRegisteredNamespaces->Dump ( outProc, refCon ); + return 0; + +} // DumpNamespaces + + +// ------------------------------------------------------------------------------------------------- +// GetGlobalOptions +// ---------------- + +/* class-static */ XMP_OptionBits +XMPMeta::GetGlobalOptions() +{ + XMP_OptionBits options = 0; + + return options; + +} // GetGlobalOptions + + +// ------------------------------------------------------------------------------------------------- +// SetGlobalOptions +// ---------------- + +/* class-static */ void +XMPMeta::SetGlobalOptions ( XMP_OptionBits /*options*/ ) +{ + + XMP_Throw ( "Unimplemented method XMPMeta::SetGlobalOptions", kXMPErr_Unimplemented ); + +} // SetGlobalOptions + + +// ------------------------------------------------------------------------------------------------- +// RegisterNamespace +// ----------------- + +/* class-static */ bool +XMPMeta::RegisterNamespace ( XMP_StringPtr namespaceURI, + XMP_StringPtr suggestedPrefix, + XMP_StringPtr * registeredPrefix, + XMP_StringLen * prefixSize ) +{ + + return sRegisteredNamespaces->Define ( namespaceURI, suggestedPrefix, registeredPrefix, prefixSize ); + +} // RegisterNamespace + + +// ------------------------------------------------------------------------------------------------- +// GetNamespacePrefix +// ------------------ + +/* class-static */ bool +XMPMeta::GetNamespacePrefix ( XMP_StringPtr namespaceURI, + XMP_StringPtr * namespacePrefix, + XMP_StringLen * prefixSize ) +{ + + return sRegisteredNamespaces->GetPrefix ( namespaceURI, namespacePrefix, prefixSize ); + +} // GetNamespacePrefix + + +// ------------------------------------------------------------------------------------------------- +// GetNamespaceURI +// --------------- + +/* class-static */ bool +XMPMeta::GetNamespaceURI ( XMP_StringPtr namespacePrefix, + XMP_StringPtr * namespaceURI, + XMP_StringLen * uriSize ) +{ + + return sRegisteredNamespaces->GetURI ( namespacePrefix, namespaceURI, uriSize ); + +} // GetNamespaceURI + + +// ------------------------------------------------------------------------------------------------- +// DeleteNamespace +// --------------- + +// *** Don't allow standard namespaces to be deleted. +// *** We would be better off not having this. Instead, have local namespaces from parsing be +// *** restricted to the object that introduced them. + +/* class-static */ void +XMPMeta::DeleteNamespace ( XMP_StringPtr namespaceURI ) +{ + + XMP_Throw ( "Unimplemented method XMPMeta::DeleteNamespace", kXMPErr_Unimplemented ); + +} // DeleteNamespace + + +// ================================================================================================= +// Class Methods +// ============= +// +// +// ================================================================================================= + + +// ------------------------------------------------------------------------------------------------- +// DumpObject +// ---------- + +void +XMPMeta::DumpObject ( XMP_TextOutputProc outProc, + void * refCon ) const +{ + XMP_Assert ( outProc != 0 ); // ! Enforced by wrapper. + + OutProcLiteral ( "Dumping XMPMeta object \"" ); + DumpClearString ( tree.name, outProc, refCon ); + OutProcNChars ( "\" ", 3 ); + DumpNodeOptions ( tree.options, outProc, refCon ); + #if 0 // *** XMP_DebugBuild + if ( (tree._namePtr != tree.name.c_str()) || + (tree._valuePtr != tree.value.c_str()) ) OutProcLiteral ( " ** bad debug string **" ); + #endif + OutProcNewline(); + + if ( ! tree.value.empty() ) { + OutProcLiteral ( "** bad root value ** \"" ); + DumpClearString ( tree.value, outProc, refCon ); + OutProcNChars ( "\"", 1 ); + OutProcNewline(); + } + + if ( ! tree.qualifiers.empty() ) { + OutProcLiteral ( "** bad root qualifiers **" ); + OutProcNewline(); + for ( size_t qualNum = 0, qualLim = tree.qualifiers.size(); qualNum < qualLim; ++qualNum ) { + DumpPropertyTree ( tree.qualifiers[qualNum], 3, 0, outProc, refCon ); + } + } + + if ( ! tree.children.empty() ) { + + for ( size_t childNum = 0, childLim = tree.children.size(); childNum < childLim; ++childNum ) { + + const XMP_Node * currSchema = tree.children[childNum]; + + OutProcNewline(); + OutProcIndent ( 1 ); + DumpClearString ( currSchema->value, outProc, refCon ); + OutProcNChars ( " ", 2 ); + DumpClearString ( currSchema->name, outProc, refCon ); + OutProcNChars ( " ", 2 ); + DumpNodeOptions ( currSchema->options, outProc, refCon ); + #if 0 // *** XMP_DebugBuild + if ( (currSchema->_namePtr != currSchema->name.c_str()) || + (currSchema->_valuePtr != currSchema->value.c_str()) ) OutProcLiteral ( " ** bad debug string **" ); + #endif + OutProcNewline(); + + if ( ! (currSchema->options & kXMP_SchemaNode) ) { + OutProcLiteral ( "** bad schema options **" ); + OutProcNewline(); + } + + if ( ! currSchema->qualifiers.empty() ) { + OutProcLiteral ( "** bad schema qualifiers **" ); + OutProcNewline(); + for ( size_t qualNum = 0, qualLim = currSchema->qualifiers.size(); qualNum < qualLim; ++qualNum ) { + DumpPropertyTree ( currSchema->qualifiers[qualNum], 3, 0, outProc, refCon ); + } + } + + for ( size_t numChild = 0, childLimit = currSchema->children.size(); numChild < childLimit; ++numChild ) { + DumpPropertyTree ( currSchema->children[numChild], 2, 0, outProc, refCon ); + } + + } + + } + +} // DumpObject + + +// ------------------------------------------------------------------------------------------------- +// CountArrayItems +// --------------- + +XMP_Index +XMPMeta::CountArrayItems ( XMP_StringPtr schemaNS, + XMP_StringPtr arrayName ) const +{ + XMP_Assert ( (schemaNS != 0) && (arrayName != 0) ); // Enforced by wrapper. + + XMP_ExpandedXPath expPath; + ExpandXPath ( schemaNS, arrayName, &expPath ); + + const XMP_Node * arrayNode = FindConstNode ( &tree, expPath ); + + if ( arrayNode == 0 ) return 0; + if ( ! (arrayNode->options & kXMP_PropValueIsArray) ) XMP_Throw ( "The named property is not an array", kXMPErr_BadXPath ); + return arrayNode->children.size(); + +} // CountArrayItems + + +// ------------------------------------------------------------------------------------------------- +// GetObjectName +// ------------- + +void +XMPMeta::GetObjectName ( XMP_StringPtr * namePtr, + XMP_StringLen * nameLen ) const +{ + + *namePtr = tree.name.c_str(); + *nameLen = tree.name.size(); + +} // GetObjectName + + +// ------------------------------------------------------------------------------------------------- +// SetObjectName +// ------------- + +void +XMPMeta::SetObjectName ( XMP_StringPtr name ) +{ + VerifyUTF8 ( name ); // Throws if the string is not legit UTF-8. + tree.name = name; + +} // SetObjectName + + +// ------------------------------------------------------------------------------------------------- +// GetObjectOptions +// ---------------- + +XMP_OptionBits +XMPMeta::GetObjectOptions() const +{ + XMP_OptionBits options = 0; + + return options; + +} // GetObjectOptions + + +// ------------------------------------------------------------------------------------------------- +// SetObjectOptions +// ---------------- + +void +XMPMeta::SetObjectOptions ( XMP_OptionBits /*options*/ ) +{ + + XMP_Throw ( "Unimplemented method XMPMeta::SetObjectOptions", kXMPErr_Unimplemented ); + +} // SetObjectOptions + + +// ------------------------------------------------------------------------------------------------- +// Sort +// ---- +// +// At the top level the namespaces are sorted by their prefixes. Within a namespace, the top level +// properties are sorted by name. Within a struct, the fields are sorted by their qualified name, +// i.e. their XML prefix:local form. Unordered arrays of simple items are sorted by value. Language +// Alternative arrays are sorted by the xml:lang qualifiers, with the "x-default" item placed first. + +void +XMPMeta::Sort() +{ + + if ( ! this->tree.qualifiers.empty() ) { + sort ( this->tree.qualifiers.begin(), this->tree.qualifiers.end(), CompareNodeNames ); + SortWithinOffspring ( this->tree.qualifiers ); + } + + if ( ! this->tree.children.empty() ) { + // The schema prefixes are the node's value, the name is the URI, so we sort schemas by value. + sort ( this->tree.children.begin(), this->tree.children.end(), CompareNodeValues ); + SortWithinOffspring ( this->tree.children ); + } + +} // Sort + + +// ------------------------------------------------------------------------------------------------- +// Erase +// ----- +// +// Clear everything except for clientRefs. + +void +XMPMeta::Erase() +{ + + if ( this->xmlParser != 0 ) { + delete ( this->xmlParser ); + this->xmlParser = 0; + } + this->tree.ClearNode(); + +} // Erase + + +// ------------------------------------------------------------------------------------------------- +// Clone +// ----- + +void +XMPMeta::Clone ( XMPMeta * clone, XMP_OptionBits options ) const +{ + if ( clone == 0 ) XMP_Throw ( "Null clone pointer", kXMPErr_BadParam ); + if ( options != 0 ) XMP_Throw ( "No options are defined yet", kXMPErr_BadOptions ); + XMP_Assert ( this->tree.parent == 0 ); + + clone->tree.ClearNode(); + + clone->tree.options = this->tree.options; + clone->tree.name = this->tree.name; + clone->tree.value = this->tree.value; + clone->errorCallback = this->errorCallback; + + #if 0 // *** XMP_DebugBuild + clone->tree._namePtr = clone->tree.name.c_str(); + clone->tree._valuePtr = clone->tree.value.c_str(); + #endif + + CloneOffspring ( &this->tree, &clone->tree ); + +} // Clone + +// ================================================================================================= +// XMP_Node::GetLocalURI +// ===================== +// +// This has to be someplace where XMPMeta::GetNamespaceURI is visible. + +void XMP_Node::GetLocalURI ( XMP_StringPtr * uriStr, XMP_StringLen * uriSize ) const +{ + + if ( uriStr != 0 ) *uriStr = ""; // Set up empty defaults. + if ( uriSize != 0 ) *uriSize = 0; + + if ( this->name.empty() ) return; + + if ( XMP_NodeIsSchema ( this->options ) ) { + + if ( uriStr != 0 ) *uriStr = this->name.c_str(); + if ( uriSize != 0 ) *uriSize = this->name.size(); + + } else { + + size_t colonPos = this->name.find_first_of(':'); + if ( colonPos == XMP_VarString::npos ) return; // ! Name of array items is "[]". + + XMP_VarString prefix ( this->name, 0, colonPos ); + XMPMeta::GetNamespaceURI ( prefix.c_str(), uriStr, uriSize ); + + } + +} + +// ================================================================================================= +// Error notifications +// =================== + +// ------------------------------------------------------------------------------------------------- +// SetDefaultErrorCallback +// ----------------------- + +/* class-static */ void +XMPMeta::SetDefaultErrorCallback ( XMPMeta_ErrorCallbackWrapper wrapperProc, + XMPMeta_ErrorCallbackProc clientProc, + void * context, + XMP_Uns32 limit ) +{ + XMP_Assert ( wrapperProc != 0 ); // Must always be set by the glue; + + sDefaultErrorCallback.wrapperProc = wrapperProc; + sDefaultErrorCallback.clientProc = clientProc; + sDefaultErrorCallback.context = context; + sDefaultErrorCallback.limit = limit; + +} // SetDefaultErrorCallback + +// ------------------------------------------------------------------------------------------------- +// SetErrorCallback +// ---------------- + +void +XMPMeta::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; + +} // SetErrorCallback + +// ------------------------------------------------------------------------------------------------- +// ResetErrorCallbackLimit +// ----------------------- + +void +XMPMeta::ResetErrorCallbackLimit ( XMP_Uns32 limit ) +{ + + this->errorCallback.limit = limit; + this->errorCallback.notifications = 0; + this->errorCallback.topSeverity = kXMPErrSev_Recoverable; + +} // ResetErrorCallbackLimit + +// ------------------------------------------------------------------------------------------------- +// ErrorCallbackInfo::CanNotify +// ------------------------------- +// +// This is const just to be usable from const XMPMeta functions. + +bool XMPMeta::ErrorCallbackInfo::CanNotify() const +{ + XMP_Assert ( (this->clientProc == 0) || (this->wrapperProc != 0) ); + return ( this->clientProc != 0); +} + +// ------------------------------------------------------------------------------------------------- +// ErrorCallbackInfo::ClientCallbackWrapper +// ------------------------------- +// +// This is const just to be usable from const XMPMeta functions. + +bool XMPMeta::ErrorCallbackInfo::ClientCallbackWrapper ( XMP_StringPtr filePath, XMP_ErrorSeverity severity, XMP_Int32 cause, XMP_StringPtr messsage ) const +{ + XMP_Bool retValue = (*this->wrapperProc) ( this->clientProc, this->context, severity, cause, messsage ); + return ConvertXMP_BoolToBool(retValue); +} + +// ================================================================================================= diff --git a/XMPCore/source/XMPMeta.hpp b/XMPCore/source/XMPMeta.hpp new file mode 100644 index 0000000..1e5f479 --- /dev/null +++ b/XMPCore/source/XMPMeta.hpp @@ -0,0 +1,428 @@ +#ifndef __XMPMeta_hpp__ +#define __XMPMeta_hpp__ + +// ================================================================================================= +// 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" +#include "public/include/XMP_Const.h" +#include "XMPCore/source/XMPCore_Impl.hpp" +#include "source/XMLParserAdapter.hpp" + +// ------------------------------------------------------------------------------------------------- + +#ifndef DumpXMLParseTree + #define DumpXMLParseTree 0 +#endif + +extern XMP_VarString * xdefaultName; // Needed in XMPMeta-Parse.cpp, MoveExplicitAliases. + +class XMPIterator; +class XMPUtils; + +// ------------------------------------------------------------------------------------------------- + +class XMPMeta { +public: + + static void + GetVersionInfo ( XMP_VersionInfo * info ); + + static bool + Initialize(); + static void + Terminate() RELEASE_NO_THROW; + + // --------------------------------------------------------------------------------------------- + + XMPMeta(); + + virtual ~XMPMeta() RELEASE_NO_THROW; + + // --------------------------------------------------------------------------------------------- + + static XMP_OptionBits + GetGlobalOptions(); + + static void + SetGlobalOptions ( XMP_OptionBits options ); + + // --------------------------------------------------------------------------------------------- + + static XMP_Status + DumpNamespaces ( XMP_TextOutputProc outProc, + void * refCon ); + + // --------------------------------------------------------------------------------------------- + + static bool + RegisterNamespace ( XMP_StringPtr namespaceURI, + XMP_StringPtr suggestedPrefix, + XMP_StringPtr * registeredPrefix, + XMP_StringLen * prefixSize ); + + static bool + GetNamespacePrefix ( XMP_StringPtr namespaceURI, + XMP_StringPtr * namespacePrefix, + XMP_StringLen * prefixSize ); + + static bool + GetNamespaceURI ( XMP_StringPtr namespacePrefix, + XMP_StringPtr * namespaceURI, + XMP_StringLen * uriSize ); + + static void + DeleteNamespace ( XMP_StringPtr namespaceURI ); + + // --------------------------------------------------------------------------------------------- + + bool + GetProperty ( XMP_StringPtr schemaNS, + XMP_StringPtr propName, + XMP_StringPtr * propValue, + XMP_StringLen * valueSize, + XMP_OptionBits * options ) const; + + bool + GetArrayItem ( XMP_StringPtr schemaNS, + XMP_StringPtr arrayName, + XMP_Index itemIndex, + XMP_StringPtr * itemValue, + XMP_StringLen * valueSize, + XMP_OptionBits * options ) const; + + bool + GetStructField ( XMP_StringPtr schemaNS, + XMP_StringPtr structName, + XMP_StringPtr fieldNS, + XMP_StringPtr fieldName, + XMP_StringPtr * fieldValue, + XMP_StringLen * valueSize, + XMP_OptionBits * options ) const; + + bool + GetQualifier ( XMP_StringPtr schemaNS, + XMP_StringPtr propName, + XMP_StringPtr qualNS, + XMP_StringPtr qualName, + XMP_StringPtr * qualValue, + XMP_StringLen * valueSize, + XMP_OptionBits * options ) const; + + // --------------------------------------------------------------------------------------------- + + void + SetProperty ( XMP_StringPtr schemaNS, + XMP_StringPtr propName, + XMP_StringPtr propValue, + XMP_OptionBits options ); + + void + SetArrayItem ( XMP_StringPtr schemaNS, + XMP_StringPtr arrayName, + XMP_Index itemIndex, + XMP_StringPtr itemValue, + XMP_OptionBits options ); + + void + AppendArrayItem ( XMP_StringPtr schemaNS, + XMP_StringPtr arrayName, + XMP_OptionBits arrayOptions, + XMP_StringPtr itemValue, + XMP_OptionBits options ); + + void + SetStructField ( XMP_StringPtr schemaNS, + XMP_StringPtr structName, + XMP_StringPtr fieldNS, + XMP_StringPtr fieldName, + XMP_StringPtr fieldValue, + XMP_OptionBits options ); + + void + SetQualifier ( XMP_StringPtr schemaNS, + XMP_StringPtr propName, + XMP_StringPtr qualNS, + XMP_StringPtr qualName, + XMP_StringPtr qualValue, + XMP_OptionBits options ); + + // --------------------------------------------------------------------------------------------- + + void + DeleteProperty ( XMP_StringPtr schemaNS, + XMP_StringPtr propName ); + + void + DeleteArrayItem ( XMP_StringPtr schemaNS, + XMP_StringPtr arrayName, + XMP_Index itemIndex ); + + void + DeleteStructField ( XMP_StringPtr schemaNS, + XMP_StringPtr structName, + XMP_StringPtr fieldNS, + XMP_StringPtr fieldName ); + + void + DeleteQualifier ( XMP_StringPtr schemaNS, + XMP_StringPtr propName, + XMP_StringPtr qualNS, + XMP_StringPtr qualName ); + + // --------------------------------------------------------------------------------------------- + + bool + DoesPropertyExist ( XMP_StringPtr schemaNS, + XMP_StringPtr propName ) const; + + bool + DoesArrayItemExist ( XMP_StringPtr schemaNS, + XMP_StringPtr arrayName, + XMP_Index itemIndex ) const; + + bool + DoesStructFieldExist ( XMP_StringPtr schemaNS, + XMP_StringPtr structName, + XMP_StringPtr fieldNS, + XMP_StringPtr fieldName ) const; + + bool + DoesQualifierExist ( XMP_StringPtr schemaNS, + XMP_StringPtr propName, + XMP_StringPtr qualNS, + XMP_StringPtr qualName ) const; + + // --------------------------------------------------------------------------------------------- + + bool + GetLocalizedText ( XMP_StringPtr schemaNS, + XMP_StringPtr altTextName, + XMP_StringPtr genericLang, + XMP_StringPtr specificLang, + XMP_StringPtr * actualLang, + XMP_StringLen * langSize, + XMP_StringPtr * itemValue, + XMP_StringLen * valueSize, + XMP_OptionBits * options ) const; + + void + SetLocalizedText ( XMP_StringPtr schemaNS, + XMP_StringPtr altTextName, + XMP_StringPtr genericLang, + XMP_StringPtr specificLang, + XMP_StringPtr itemValue, + XMP_OptionBits options ); + + void + DeleteLocalizedText ( XMP_StringPtr schemaNS, + XMP_StringPtr altTextName, + XMP_StringPtr genericLang, + XMP_StringPtr specificLang); + + // --------------------------------------------------------------------------------------------- + + bool + GetProperty_Bool ( XMP_StringPtr schemaNS, + XMP_StringPtr propName, + bool * propValue, + XMP_OptionBits * options ) const; + + bool + GetProperty_Int ( XMP_StringPtr schemaNS, + XMP_StringPtr propName, + XMP_Int32 * propValue, + XMP_OptionBits * options ) const; + + bool + GetProperty_Int64 ( XMP_StringPtr schemaNS, + XMP_StringPtr propName, + XMP_Int64 * propValue, + XMP_OptionBits * options ) const; + + bool + GetProperty_Float ( XMP_StringPtr schemaNS, + XMP_StringPtr propName, + double * propValue, + XMP_OptionBits * options ) const; + + bool + GetProperty_Date ( XMP_StringPtr schemaNS, + XMP_StringPtr propName, + XMP_DateTime * propValue, + XMP_OptionBits * options ) const; + + // --------------------------------------------------------------------------------------------- + + void + SetProperty_Bool ( XMP_StringPtr schemaNS, + XMP_StringPtr propName, + bool propValue, + XMP_OptionBits options ); + + void + SetProperty_Int ( XMP_StringPtr schemaNS, + XMP_StringPtr propName, + XMP_Int32 propValue, + XMP_OptionBits options ); + + void + SetProperty_Int64 ( XMP_StringPtr schemaNS, + XMP_StringPtr propName, + XMP_Int64 propValue, + XMP_OptionBits options ); + + void + SetProperty_Float ( XMP_StringPtr schemaNS, + XMP_StringPtr propName, + double propValue, + XMP_OptionBits options ); + + void + SetProperty_Date ( XMP_StringPtr schemaNS, + XMP_StringPtr propName, + const XMP_DateTime & propValue, + XMP_OptionBits options ); + + // --------------------------------------------------------------------------------------------- + + void + GetObjectName ( XMP_StringPtr * namePtr, + XMP_StringLen * nameLen ) const; + + void + SetObjectName ( XMP_StringPtr name ); + + XMP_OptionBits + GetObjectOptions() const; + + void + SetObjectOptions ( XMP_OptionBits options ); + + void + Sort(); + + void + Erase(); + + void + Clone ( XMPMeta * clone, XMP_OptionBits options ) const; + + XMP_Index + CountArrayItems ( XMP_StringPtr schemaNS, + XMP_StringPtr arrayName ) const; + + void + DumpObject ( XMP_TextOutputProc outProc, + void * refCon ) const; + + // --------------------------------------------------------------------------------------------- + + void + ParseFromBuffer ( XMP_StringPtr buffer, + XMP_StringLen bufferSize, + XMP_OptionBits options ); + + void + SerializeToBuffer ( XMP_VarString * rdfString, + XMP_OptionBits options, + XMP_StringLen padding, + XMP_StringPtr newline, + XMP_StringPtr indent, + XMP_Index baseIndent ) const; + + // --------------------------------------------------------------------------------------------- + + static void + SetDefaultErrorCallback ( XMPMeta_ErrorCallbackWrapper wrapperProc, + XMPMeta_ErrorCallbackProc clientProc, + void * context, + XMP_Uns32 limit ); + + void + SetErrorCallback ( XMPMeta_ErrorCallbackWrapper wrapperProc, + XMPMeta_ErrorCallbackProc clientProc, + void * context, + XMP_Uns32 limit ); + + void + ResetErrorCallbackLimit ( XMP_Uns32 limit ); + + class ErrorCallbackInfo : public GenericErrorCallback { + public: + + XMPMeta_ErrorCallbackWrapper wrapperProc; + XMPMeta_ErrorCallbackProc clientProc; + void * context; + + ErrorCallbackInfo() : wrapperProc(0), clientProc(0), context(0) {}; + + void Clear() { this->wrapperProc = 0; this->clientProc = 0; this->context = 0; + GenericErrorCallback::Clear(); }; + + bool CanNotify() const; + bool ClientCallbackWrapper ( XMP_StringPtr filePath, XMP_ErrorSeverity severity, XMP_Int32 cause, XMP_StringPtr messsage ) const; + }; + + // ============================================================================================= + + // --------------------------------------------------------------------------------------------- + // - Everything is built out of standard nodes. Each node has a name, value, option flags, a + // vector of child nodes, and a vector of qualifier nodes. + // + // - The option flags are those passed to SetProperty and returned from GetProperty. They tell + // if the node is simple, a struct or an array; whether it has qualifiers, etc. + // + // - The name of the node is an XML qualified name, of the form "prefix:simple-name". Since we + // force all namespaces to be known and to have unique prefixes, this is semantically equivalent + // to using a URI and simple name pair. + // + // - Although the value part is only for leaf properties and the children part is only for + // structs and arrays, it is easier to simply have them in every node. This keeps things visible + // so that debugging is easier + // + // - The top level node children are the namespaces that contain properties, the next level are + // the top level properties, lower levels are the fields of structs or items of arrays. The name + // of the top level nodes is just the namespace prefix, with the colon terminator. The name of + // top level properties includes the namespace prefix. + // + // - Any property node, at any level, can have qualifiers. These are themselves general property + // nodes. And could in fact themselves have qualifiers! + + // ! Expose the implementation so that file static functions can see the data. + + XMP_Int32 clientRefs; // ! Must be signed to allow decrement from 0. + XMP_ReadWriteLock lock; + + // ! Any data member changes must be propagted to the Clone function! + + XMP_Node tree; + XMLParserAdapter * xmlParser; + ErrorCallbackInfo errorCallback; + + friend class XMPIterator; + friend class XMPUtils; + +private: + + // ! These are hidden on purpose: + XMPMeta ( const XMPMeta & /* original */ ) : clientRefs(0), tree(XMP_Node(0,"",0)), xmlParser(0) + { XMP_Throw ( "Call to hidden constructor", kXMPErr_InternalFailure ); }; + void operator= ( const XMPMeta & /* rhs */ ) + { XMP_Throw ( "Call to hidden operator=", kXMPErr_InternalFailure ); }; + + // Special support routines for parsing, here to be able to access the errorCallback. + void ProcessXMLTree ( XMP_OptionBits options ); + bool ProcessXMLBuffer ( XMP_StringPtr buffer, XMP_StringLen xmpSize, bool lastClientCall ); + void ProcessRDF ( const XML_Node & xmlTree, XMP_OptionBits options ); + +}; // class XMPMeta + +// ================================================================================================= + +#endif // __XMPMeta_hpp__ diff --git a/XMPCore/source/XMPUtils-FileInfo.cpp b/XMPCore/source/XMPUtils-FileInfo.cpp new file mode 100644 index 0000000..107fa58 --- /dev/null +++ b/XMPCore/source/XMPUtils-FileInfo.cpp @@ -0,0 +1,1493 @@ +// ================================================================================================= +// 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/source/XMPUtils.hpp" + +#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 = *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 )) + +static 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 +// ------------------ + +/* 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 ) +{ + 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 +// ------------------ + +/* class static */ void +XMPUtils::SeparateArrayItems ( XMPMeta * xmpObj, + XMP_StringPtr schemaNS, + XMP_StringPtr arrayName, + XMP_OptionBits options, + XMP_StringPtr catedStr ) +{ + 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 ) +{ + 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 ) +{ + 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 ) +{ + 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/XMPCore/source/XMPUtils.cpp b/XMPCore/source/XMPUtils.cpp new file mode 100644 index 0000000..8004dd1 --- /dev/null +++ b/XMPCore/source/XMPUtils.cpp @@ -0,0 +1,2002 @@ +// ================================================================================================= +// 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/source/XMPUtils.hpp" + +#include "third-party/zuid/interfaces/MD5.h" + + +#include + +#include +#include +#include +#include +#include + +#include // For snprintf. + +#if XMP_WinBuild + #pragma warning ( disable : 4800 ) // forcing value to bool 'true' or 'false' (performance warning) + #pragma warning ( disable : 4996 ) // '...' was declared deprecated +#endif + +// ================================================================================================= +// Local Types and Constants +// ========================= + +static const char * sBase64Chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; + +// ================================================================================================= +// Local Utilities +// =============== + +// ------------------------------------------------------------------------------------------------- +// ANSI Time Functions +// ------------------- +// +// A bit of hackery to use the best available time functions. Mac, UNIX and iOS have thread safe versions +// of gmtime and localtime. + +#if XMP_MacBuild | XMP_UNIXBuild | XMP_iOSBuild + + typedef time_t ansi_tt; + typedef struct tm ansi_tm; + + #define ansi_time time + #define ansi_mktime mktime + #define ansi_difftime difftime + + #define ansi_gmtime gmtime_r + #define ansi_localtime localtime_r + +#elif XMP_WinBuild + + // ! VS.Net 2003 (VC7) does not provide thread safe versions of gmtime and localtime. + // ! VS.Net 2005 (VC8) inverts the parameters for the safe versions of gmtime and localtime. + + typedef time_t ansi_tt; + typedef struct tm ansi_tm; + + #define ansi_time time + #define ansi_mktime mktime + #define ansi_difftime difftime + + #if defined(_MSC_VER) && (_MSC_VER >= 1400) + #define ansi_gmtime(tt,tm) gmtime_s ( tm, tt ) + #define ansi_localtime(tt,tm) localtime_s ( tm, tt ) + #else + static inline void ansi_gmtime ( const ansi_tt * ttTime, ansi_tm * tmTime ) + { + ansi_tm * tmx = gmtime ( ttTime ); // ! Hope that there is no race! + if ( tmx == 0 ) XMP_Throw ( "Failure from ANSI C gmtime function", kXMPErr_ExternalFailure ); + *tmTime = *tmx; + } + static inline void ansi_localtime ( const ansi_tt * ttTime, ansi_tm * tmTime ) + { + ansi_tm * tmx = localtime ( ttTime ); // ! Hope that there is no race! + if ( tmx == 0 ) XMP_Throw ( "Failure from ANSI C localtime function", kXMPErr_ExternalFailure ); + *tmTime = *tmx; + } + #endif + +#endif + +// ------------------------------------------------------------------------------------------------- +// VerifyDateTimeFlags +// ------------------- + +static void +VerifyDateTimeFlags ( XMP_DateTime * dt ) +{ + + if ( (dt->year != 0) || (dt->month != 0) || (dt->day != 0) ) dt->hasDate = true; + if ( (dt->hour != 0) || (dt->minute != 0) || (dt->second != 0) || (dt->nanoSecond != 0) ) dt->hasTime = true; + if ( (dt->tzSign != 0) || (dt->tzHour != 0) || (dt->tzMinute != 0) ) dt->hasTimeZone = true; + if ( dt->hasTimeZone ) dt->hasTime = true; // ! Don't combine with above line, UTC has zero values. + +} // VerifyDateTimeFlags + +// ------------------------------------------------------------------------------------------------- +// IsLeapYear +// ---------- + +static bool +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. + +} // IsLeapYear + +// ------------------------------------------------------------------------------------------------- +// DaysInMonth +// ----------- + +static int +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; + +} // DaysInMonth + +// ------------------------------------------------------------------------------------------------- +// AdjustTimeOverflow +// ------------------ + +static void +AdjustTimeOverflow ( XMP_DateTime * time ) +{ + enum { kBillion = 1000*1000*1000L }; + + // ---------------------------------------------------------------------------------------------- + // To be safe against pathalogical overflow we first adjust from month to second, then from + // nanosecond back up to month. This leaves each value closer to zero before propagating into it. + // For example if the hour and minute are both near max, adjusting minutes first can cause the + // hour to overflow. + + // ! Photoshop 8 creates "time only" values with zeros for year, month, and day. + + if ( (time->year != 0) || (time->month != 0) || (time->day != 0) ) { + + while ( time->month < 1 ) { + time->year -= 1; + time->month += 12; + } + + while ( time->month > 12 ) { + time->year += 1; + time->month -= 12; + } + + while ( time->day < 1 ) { + time->month -= 1; + if ( time->month < 1 ) { // ! Keep the months in range for indexing daysInMonth! + time->year -= 1; + time->month += 12; + } + time->day += DaysInMonth ( time->year, time->month ); // ! Decrement month before so index here is right! + } + + while ( time->day > DaysInMonth ( time->year, time->month ) ) { + time->day -= DaysInMonth ( time->year, time->month ); // ! Increment month after so index here is right! + time->month += 1; + if ( time->month > 12 ) { + time->year += 1; + time->month -= 12; + } + } + + } + + while ( time->hour < 0 ) { + time->day -= 1; + time->hour += 24; + } + + while ( time->hour >= 24 ) { + time->day += 1; + time->hour -= 24; + } + + while ( time->minute < 0 ) { + time->hour -= 1; + time->minute += 60; + } + + while ( time->minute >= 60 ) { + time->hour += 1; + time->minute -= 60; + } + + while ( time->second < 0 ) { + time->minute -= 1; + time->second += 60; + } + + while ( time->second >= 60 ) { + time->minute += 1; + time->second -= 60; + } + + while ( time->nanoSecond < 0 ) { + time->second -= 1; + time->nanoSecond += kBillion; + } + + while ( time->nanoSecond >= kBillion ) { + time->second += 1; + time->nanoSecond -= kBillion; + } + + while ( time->second < 0 ) { + time->minute -= 1; + time->second += 60; + } + + while ( time->second >= 60 ) { + time->minute += 1; + time->second -= 60; + } + + while ( time->minute < 0 ) { + time->hour -= 1; + time->minute += 60; + } + + while ( time->minute >= 60 ) { + time->hour += 1; + time->minute -= 60; + } + + while ( time->hour < 0 ) { + time->day -= 1; + time->hour += 24; + } + + while ( time->hour >= 24 ) { + time->day += 1; + time->hour -= 24; + } + + if ( (time->year != 0) || (time->month != 0) || (time->day != 0) ) { + + while ( time->month < 1 ) { // Make sure the months are OK first, for DaysInMonth. + time->year -= 1; + time->month += 12; + } + + while ( time->month > 12 ) { + time->year += 1; + time->month -= 12; + } + + while ( time->day < 1 ) { + time->month -= 1; + if ( time->month < 1 ) { + time->year -= 1; + time->month += 12; + } + time->day += DaysInMonth ( time->year, time->month ); + } + + while ( time->day > DaysInMonth ( time->year, time->month ) ) { + time->day -= DaysInMonth ( time->year, time->month ); + time->month += 1; + if ( time->month > 12 ) { + time->year += 1; + time->month -= 12; + } + } + + } + +} // AdjustTimeOverflow + +// ------------------------------------------------------------------------------------------------- +// GatherInt +// --------- +// +// Gather into a 64-bit value in order to easily check for overflow. Using a 32-bit value and +// checking for negative isn't reliable, the "*10" part can wrap around to a low positive value. + +static XMP_Int32 +GatherInt ( XMP_StringPtr strValue, size_t * _pos, const char * errMsg ) +{ + size_t pos = *_pos; + XMP_Int64 value = 0; + + enum { kMaxSInt32 = 0x7FFFFFFF }; + + for ( char ch = strValue[pos]; ('0' <= ch) && (ch <= '9'); ++pos, ch = strValue[pos] ) { + value = (value * 10) + (ch - '0'); + if ( value > kMaxSInt32 ) XMP_Throw ( errMsg, kXMPErr_BadValue ); + } + + if ( pos == *_pos ) XMP_Throw ( errMsg, kXMPErr_BadParam ); + *_pos = pos; + return (XMP_Int32)value; + +} // GatherInt + +// ------------------------------------------------------------------------------------------------- + +static void FormatFullDateTime ( XMP_DateTime & tempDate, char * buffer, size_t bufferLen ) +{ + + AdjustTimeOverflow ( &tempDate ); // Make sure all time parts are in range. + + if ( (tempDate.second == 0) && (tempDate.nanoSecond == 0) ) { + + // Output YYYY-MM-DDThh:mmTZD. + snprintf ( buffer, bufferLen, "%.4d-%02d-%02dT%02d:%02d", // AUDIT: Callers pass sizeof(buffer). + tempDate.year, tempDate.month, tempDate.day, tempDate.hour, tempDate.minute ); + + } else if ( tempDate.nanoSecond == 0 ) { + + // Output YYYY-MM-DDThh:mm:ssTZD. + snprintf ( buffer, bufferLen, "%.4d-%02d-%02dT%02d:%02d:%02d", // AUDIT: Callers pass sizeof(buffer). + tempDate.year, tempDate.month, tempDate.day, + tempDate.hour, tempDate.minute, tempDate.second ); + + } else { + + // Output YYYY-MM-DDThh:mm:ss.sTZD. + snprintf ( buffer, bufferLen, "%.4d-%02d-%02dT%02d:%02d:%02d.%09d", // AUDIT: Callers pass sizeof(buffer). + tempDate.year, tempDate.month, tempDate.day, + tempDate.hour, tempDate.minute, tempDate.second, tempDate.nanoSecond ); + buffer[bufferLen - 1] = 0; // AUDIT warning C6053: make sure string is terminated. buffer is already filled with 0 from caller + for ( size_t i = strlen(buffer)-1; buffer[i] == '0'; --i ) buffer[i] = 0; // Trim excess digits. + } + +} // FormatFullDateTime + +// ------------------------------------------------------------------------------------------------- +// DecodeBase64Char +// ---------------- + +// The decode mapping: +// +// encoded encoded raw +// char value value +// ------- ------- ----- +// A .. Z 0x41 .. 0x5A 0 .. 25 +// a .. z 0x61 .. 0x7A 26 .. 51 +// 0 .. 9 0x30 .. 0x39 52 .. 61 +// + 0x2B 62 +// / 0x2F 63 + +static unsigned char +DecodeBase64Char ( XMP_Uns8 ch ) +{ + + if ( ('A' <= ch) && (ch <= 'Z') ) { + ch = ch - 'A'; + } else if ( ('a' <= ch) && (ch <= 'z') ) { + ch = ch - 'a' + 26; + } else if ( ('0' <= ch) && (ch <= '9') ) { + ch = ch - '0' + 52; + } else if ( ch == '+' ) { + ch = 62; + } else if ( ch == '/' ) { + ch = 63; + } else if ( (ch == ' ') || (ch == kTab) || (ch == kLF) || (ch == kCR) ) { + ch = 0xFF; // Will be ignored by the caller. + } else { + XMP_Throw ( "Invalid base-64 encoded character", kXMPErr_BadParam ); + } + + return ch; + +} // DecodeBase64Char (); + +// ------------------------------------------------------------------------------------------------- +// EstimateSizeForJPEG +// ------------------- +// +// Estimate the serialized size for the subtree of an XMP_Node. Support for PackageForJPEG. + +static size_t +EstimateSizeForJPEG ( const XMP_Node * xmpNode ) +{ + + size_t estSize = 0; + size_t nameSize = xmpNode->name.size(); + bool includeName = (! XMP_PropIsArray ( xmpNode->parent->options )); + + if ( XMP_PropIsSimple ( xmpNode->options ) ) { + + if ( includeName ) estSize += (nameSize + 3); // Assume attribute form. + estSize += xmpNode->value.size(); + + } else if ( XMP_PropIsArray ( xmpNode->options ) ) { + + // The form of the value portion is: ...... + if ( includeName ) estSize += (2*nameSize + 5); + size_t arraySize = xmpNode->children.size(); + estSize += 9 + 10; // The rdf:Xyz tags. + estSize += arraySize * (8 + 9); // The rdf:li tags. + for ( size_t i = 0; i < arraySize; ++i ) { + estSize += EstimateSizeForJPEG ( xmpNode->children[i] ); + } + + } else { + + // The form is: ...fields... + if ( includeName ) estSize += (2*nameSize + 5); + estSize += 25; // The rdf:parseType="Resource" attribute. + size_t fieldCount = xmpNode->children.size(); + for ( size_t i = 0; i < fieldCount; ++i ) { + estSize += EstimateSizeForJPEG ( xmpNode->children[i] ); + } + + } + + return estSize; + +} // EstimateSizeForJPEG + +// ------------------------------------------------------------------------------------------------- +// MoveOneProperty +// --------------- + +static bool MoveOneProperty ( XMPMeta & stdXMP, XMPMeta * extXMP, + XMP_StringPtr schemaURI, XMP_StringPtr propName ) +{ + + XMP_Node * propNode = 0; + XMP_NodePtrPos stdPropPos; + + XMP_Node * stdSchema = FindSchemaNode ( &stdXMP.tree, schemaURI, kXMP_ExistingOnly, 0 ); + if ( stdSchema != 0 ) { + propNode = FindChildNode ( stdSchema, propName, kXMP_ExistingOnly, &stdPropPos ); + } + if ( propNode == 0 ) return false; + + XMP_Node * extSchema = FindSchemaNode ( &extXMP->tree, schemaURI, kXMP_CreateNodes ); + + propNode->parent = extSchema; + + extSchema->options &= ~kXMP_NewImplicitNode; + extSchema->children.push_back ( propNode ); + + stdSchema->children.erase ( stdPropPos ); + DeleteEmptySchema ( stdSchema ); + + return true; + +} // MoveOneProperty + +// ------------------------------------------------------------------------------------------------- +// CreateEstimatedSizeMap +// ---------------------- + +#ifndef Trace_PackageForJPEG + #define Trace_PackageForJPEG 0 +#endif + +typedef std::pair < XMP_VarString*, XMP_VarString* > StringPtrPair; +typedef std::multimap < size_t, StringPtrPair > PropSizeMap; + +static void CreateEstimatedSizeMap ( XMPMeta & stdXMP, PropSizeMap * propSizes ) +{ + #if Trace_PackageForJPEG + printf ( " Creating top level property map:\n" ); + #endif + + for ( size_t s = stdXMP.tree.children.size(); s > 0; --s ) { + + XMP_Node * stdSchema = stdXMP.tree.children[s-1]; + + for ( size_t p = stdSchema->children.size(); p > 0; --p ) { + + XMP_Node * stdProp = stdSchema->children[p-1]; + if ( (stdSchema->name == kXMP_NS_XMP_Note) && + (stdProp->name == "xmpNote:HasExtendedXMP") ) continue; // ! Don't move xmpNote:HasExtendedXMP. + + size_t propSize = EstimateSizeForJPEG ( stdProp ); + StringPtrPair namePair ( &stdSchema->name, &stdProp->name ); + PropSizeMap::value_type mapValue ( propSize, namePair ); + + (void) propSizes->insert ( propSizes->upper_bound ( propSize ), mapValue ); + #if Trace_PackageForJPEG + printf ( " %d bytes, %s in %s\n", propSize, stdProp->name.c_str(), stdSchema->name.c_str() ); + #endif + + } + + } + +} // CreateEstimatedSizeMap + +// ------------------------------------------------------------------------------------------------- +// MoveLargestProperty +// ------------------- + +static size_t MoveLargestProperty ( XMPMeta & stdXMP, XMPMeta * extXMP, PropSizeMap & propSizes ) +{ + XMP_Assert ( ! propSizes.empty() ); + + #if 0 + // *** Xocde 2.3 on Mac OS X 10.4.7 seems to have a bug where this does not pick the last + // *** item in the map. We'll just avoid it on all platforms until thoroughly tested. + PropSizeMap::iterator lastPos = propSizes.end(); + --lastPos; // Move to the actual last item. + #else + PropSizeMap::iterator lastPos = propSizes.begin(); + PropSizeMap::iterator nextPos = lastPos; + for ( ++nextPos; nextPos != propSizes.end(); ++nextPos ) lastPos = nextPos; + #endif + + size_t propSize = lastPos->first; + const char * schemaURI = lastPos->second.first->c_str(); + const char * propName = lastPos->second.second->c_str(); + + #if Trace_PackageForJPEG + printf ( " Move %s, %d bytes\n", propName, propSize ); + #endif + +#if XMP_DebugBuild + bool moved = +#endif + MoveOneProperty ( stdXMP, extXMP, schemaURI, propName ); + XMP_Assert ( moved ); + + propSizes.erase ( lastPos ); + return propSize; + +} // MoveLargestProperty + +// ================================================================================================= +// Class Static Functions +// ====================== + +// ------------------------------------------------------------------------------------------------- +// Initialize +// ---------- + +/* class static */ bool +XMPUtils::Initialize() +{ + + if ( WhiteSpaceStrPtr == NULL ) { + WhiteSpaceStrPtr = new std::string(); + WhiteSpaceStrPtr->append( " \t\n\r" ); + } + return true; + +} // Initialize + +// ------------------------------------------------------------------------------------------------- +// Terminate +// --------- + +/* class static */ void +XMPUtils::Terminate() RELEASE_NO_THROW +{ + + delete WhiteSpaceStrPtr; + WhiteSpaceStrPtr = NULL; + return; + +} // Terminate + +// ------------------------------------------------------------------------------------------------- +// ComposeArrayItemPath +// -------------------- +// +// Return "arrayName[index]". + +/* class static */ void +XMPUtils::ComposeArrayItemPath ( XMP_StringPtr schemaNS, + XMP_StringPtr arrayName, + XMP_Index itemIndex, + XMP_VarString * _fullPath ) +{ + XMP_Assert ( schemaNS != 0 ); // Enforced by wrapper. + XMP_Assert ( (arrayName != 0) && (*arrayName != 0) ); // Enforced by wrapper. + XMP_Assert ( _fullPath != 0 ); // Enforced by wrapper. + + XMP_ExpandedXPath expPath; // Just for side effects to check namespace and basic path. + ExpandXPath ( schemaNS, arrayName, &expPath ); + + if ( (itemIndex < 0) && (itemIndex != kXMP_ArrayLastItem) ) XMP_Throw ( "Array index out of bounds", kXMPErr_BadParam ); + + XMP_StringLen reserveLen = strlen(arrayName) + 2 + 32; // Room plus padding. + + XMP_VarString fullPath; // ! Allow for arrayName to be the incoming _fullPath.c_str(). + fullPath.reserve ( reserveLen ); + fullPath = arrayName; + + if ( itemIndex == kXMP_ArrayLastItem ) { + fullPath += "[last()]"; + } else { + // AUDIT: Using sizeof(buffer) for the snprintf length is safe. + char buffer [32]; // A 32 byte buffer is plenty, even for a 64-bit integer. + snprintf ( buffer, sizeof(buffer), "[%d]", itemIndex ); + fullPath += buffer; + } + + *_fullPath = fullPath; + +} // ComposeArrayItemPath + +// ------------------------------------------------------------------------------------------------- +// ComposeStructFieldPath +// ---------------------- +// +// Return "structName/ns:fieldName". + +/* class static */ void +XMPUtils::ComposeStructFieldPath ( XMP_StringPtr schemaNS, + XMP_StringPtr structName, + XMP_StringPtr fieldNS, + XMP_StringPtr fieldName, + XMP_VarString * _fullPath ) +{ + XMP_Assert ( (schemaNS != 0) && (fieldNS != 0) ); // Enforced by wrapper. + XMP_Assert ( (structName != 0) && (*structName != 0) ); // Enforced by wrapper. + XMP_Assert ( (fieldName != 0) && (*fieldName != 0) ); // Enforced by wrapper. + XMP_Assert ( _fullPath != 0 ); // Enforced by wrapper. + + XMP_ExpandedXPath expPath; // Just for side effects to check namespace and basic path. + ExpandXPath ( schemaNS, structName, &expPath ); + + XMP_ExpandedXPath fieldPath; + ExpandXPath ( fieldNS, fieldName, &fieldPath ); + if ( fieldPath.size() != 2 ) XMP_Throw ( "The fieldName must be simple", kXMPErr_BadXPath ); + + XMP_StringLen reserveLen = strlen(structName) + fieldPath[kRootPropStep].step.size() + 1; + + XMP_VarString fullPath; // ! Allow for arrayName to be the incoming _fullPath.c_str(). + fullPath.reserve ( reserveLen ); + fullPath = structName; + fullPath += '/'; + fullPath += fieldPath[kRootPropStep].step; + + *_fullPath = fullPath; + +} // ComposeStructFieldPath + +// ------------------------------------------------------------------------------------------------- +// ComposeQualifierPath +// -------------------- +// +// Return "propName/?ns:qualName". + +/* class static */ void +XMPUtils::ComposeQualifierPath ( XMP_StringPtr schemaNS, + XMP_StringPtr propName, + XMP_StringPtr qualNS, + XMP_StringPtr qualName, + XMP_VarString * _fullPath ) +{ + XMP_Assert ( (schemaNS != 0) && (qualNS != 0) ); // Enforced by wrapper. + XMP_Assert ( (propName != 0) && (*propName != 0) ); // Enforced by wrapper. + XMP_Assert ( (qualName != 0) && (*qualName != 0) ); // Enforced by wrapper. + XMP_Assert ( _fullPath != 0 ); // Enforced by wrapper. + + XMP_ExpandedXPath expPath; // Just for side effects to check namespace and basic path. + ExpandXPath ( schemaNS, propName, &expPath ); + + XMP_ExpandedXPath qualPath; + ExpandXPath ( qualNS, qualName, &qualPath ); + if ( qualPath.size() != 2 ) XMP_Throw ( "The qualifier name must be simple", kXMPErr_BadXPath ); + + XMP_StringLen reserveLen = strlen(propName) + qualPath[kRootPropStep].step.size() + 2; + + XMP_VarString fullPath; // ! Allow for arrayName to be the incoming _fullPath.c_str(). + fullPath.reserve ( reserveLen ); + fullPath = propName; + fullPath += "/?"; + fullPath += qualPath[kRootPropStep].step; + + *_fullPath = fullPath; + +} // ComposeQualifierPath + +// ------------------------------------------------------------------------------------------------- +// ComposeLangSelector +// ------------------- +// +// Return "arrayName[?xml:lang="lang"]". + +// *** #error "handle quotes in the lang - or verify format" + +/* class static */ void +XMPUtils::ComposeLangSelector ( XMP_StringPtr schemaNS, + XMP_StringPtr arrayName, + XMP_StringPtr _langName, + XMP_VarString * _fullPath ) +{ + XMP_Assert ( schemaNS != 0 ); // Enforced by wrapper. + XMP_Assert ( (arrayName != 0) && (*arrayName != 0) ); // Enforced by wrapper. + XMP_Assert ( (_langName != 0) && (*_langName != 0) ); // Enforced by wrapper. + XMP_Assert ( _fullPath != 0 ); // Enforced by wrapper. + + XMP_ExpandedXPath expPath; // Just for side effects to check namespace and basic path. + ExpandXPath ( schemaNS, arrayName, &expPath ); + + XMP_VarString langName ( _langName ); + NormalizeLangValue ( &langName ); + + XMP_StringLen reserveLen = strlen(arrayName) + langName.size() + 14; + + XMP_VarString fullPath; // ! Allow for arrayName to be the incoming _fullPath.c_str(). + fullPath.reserve ( reserveLen ); + fullPath = arrayName; + fullPath += "[?xml:lang=\""; + fullPath += langName; + fullPath += "\"]"; + + *_fullPath = fullPath; + +} // ComposeLangSelector + +// ------------------------------------------------------------------------------------------------- +// ComposeFieldSelector +// -------------------- +// +// Return "arrayName[ns:fieldName="fieldValue"]". + +// *** #error "handle quotes in the value" + +/* class static */ void +XMPUtils::ComposeFieldSelector ( XMP_StringPtr schemaNS, + XMP_StringPtr arrayName, + XMP_StringPtr fieldNS, + XMP_StringPtr fieldName, + XMP_StringPtr fieldValue, + XMP_VarString * _fullPath ) +{ + XMP_Assert ( (schemaNS != 0) && (fieldNS != 0) && (fieldValue != 0) ); // Enforced by wrapper. + XMP_Assert ( (*arrayName != 0) && (*fieldName != 0) ); // Enforced by wrapper. + XMP_Assert ( _fullPath != 0 ); // Enforced by wrapper. + + XMP_ExpandedXPath expPath; // Just for side effects to check namespace and basic path. + ExpandXPath ( schemaNS, arrayName, &expPath ); + + XMP_ExpandedXPath fieldPath; + ExpandXPath ( fieldNS, fieldName, &fieldPath ); + if ( fieldPath.size() != 2 ) XMP_Throw ( "The fieldName must be simple", kXMPErr_BadXPath ); + + XMP_StringLen reserveLen = strlen(arrayName) + fieldPath[kRootPropStep].step.size() + strlen(fieldValue) + 5; + + XMP_VarString fullPath; // ! Allow for arrayName to be the incoming _fullPath.c_str(). + fullPath.reserve ( reserveLen ); + fullPath = arrayName; + fullPath += '['; + fullPath += fieldPath[kRootPropStep].step; + fullPath += "=\""; + fullPath += fieldValue; + fullPath += "\"]"; + + *_fullPath = fullPath; + +} // ComposeFieldSelector + +// ------------------------------------------------------------------------------------------------- +// ConvertFromBool +// --------------- + +/* class static */ void +XMPUtils::ConvertFromBool ( bool binValue, + XMP_VarString * strValue ) +{ + XMP_Assert ( strValue != 0 ); // Enforced by wrapper. + + if ( binValue ) { + *strValue = kXMP_TrueStr; + } else { + *strValue = kXMP_FalseStr; + } + +} // ConvertFromBool + +// ------------------------------------------------------------------------------------------------- +// ConvertFromInt +// -------------- + +/* class static */ void +XMPUtils::ConvertFromInt ( XMP_Int32 binValue, + XMP_StringPtr format, + XMP_VarString * strValue ) +{ + XMP_Assert ( (format != 0) && (strValue != 0) ); // Enforced by wrapper. + + strValue->erase(); + if ( *format == 0 ) format = "%d"; + + // AUDIT: Using sizeof(buffer) for the snprintf length is safe. + char buffer [32]; // Big enough for a 64-bit integer; + snprintf ( buffer, sizeof(buffer), format, binValue ); + + *strValue = buffer; + +} // ConvertFromInt + +// ------------------------------------------------------------------------------------------------- +// ConvertFromInt64 +// ---------------- + +/* class static */ void +XMPUtils::ConvertFromInt64 ( XMP_Int64 binValue, + XMP_StringPtr format, + XMP_VarString * strValue ) +{ + XMP_Assert ( (format != 0) && (strValue != 0) ); // Enforced by wrapper. + + strValue->erase(); + if ( *format == 0 ) format = "%lld"; + + // AUDIT: Using sizeof(buffer) for the snprintf length is safe. + char buffer [32]; // Big enough for a 64-bit integer; + snprintf ( buffer, sizeof(buffer), format, binValue ); + + *strValue = buffer; + +} // ConvertFromInt64 + +// ------------------------------------------------------------------------------------------------- +// ConvertFromFloat +// ---------------- + +/* class static */ void +XMPUtils::ConvertFromFloat ( double binValue, + XMP_StringPtr format, + XMP_VarString * strValue ) +{ + XMP_Assert ( (format != 0) && (strValue != 0) ); // Enforced by wrapper. + + strValue->erase(); + if ( *format == 0 ) format = "%f"; + + // AUDIT: Using sizeof(buffer) for the snprintf length is safe. + char buffer [64]; // Ought to be plenty big enough. + snprintf ( buffer, sizeof(buffer), format, binValue ); + + *strValue = buffer; + +} // ConvertFromFloat + +// ------------------------------------------------------------------------------------------------- +// ConvertFromDate +// --------------- +// +// Format a date-time string according to ISO 8601 and http://www.w3.org/TR/NOTE-datetime: +// YYYY +// YYYY-MM +// YYYY-MM-DD +// YYYY-MM-DDThh:mmTZD +// YYYY-MM-DDThh:mm:ssTZD +// YYYY-MM-DDThh:mm:ss.sTZD +// +// YYYY = four-digit year +// MM = two-digit month (01=January, etc.) +// DD = two-digit day of month (01 through 31) +// hh = two digits of hour (00 through 23) +// mm = two digits of minute (00 through 59) +// ss = two digits of second (00 through 59) +// s = one or more digits representing a decimal fraction of a second +// TZD = time zone designator (Z or +hh:mm or -hh:mm) +// +// Note that ISO 8601 does not seem to allow years less than 1000 or greater than 9999. We allow +// any year, even negative ones. The year is formatted as "%.4d". The TZD is also optional in XMP, +// even though required in the W3C profile. Finally, Photoshop 8 (CS) sometimes created time-only +// values so we tolerate that. + +/* class static */ void +XMPUtils::ConvertFromDate ( const XMP_DateTime & _inValue, + XMP_VarString * strValue ) +{ + XMP_Assert ( strValue != 0 ); // Enforced by wrapper. + + char buffer [100]; // Plenty long enough. + memset( buffer, 0, 100); + + // Pick the format, use snprintf to format into a local buffer, assign to static output string. + // Don't use AdjustTimeOverflow at the start, that will wipe out zero month or day values. + + // ! Photoshop 8 creates "time only" values with zeros for year, month, and day. + + XMP_DateTime binValue = _inValue; + VerifyDateTimeFlags ( &binValue ); + + // Temporary fix for bug 1269463, silently fix out of range month or day. + + if ( binValue.month == 0 ) { + if ( (binValue.day != 0) || binValue.hasTime ) binValue.month = 1; + } else { + if ( binValue.month < 1 ) binValue.month = 1; + if ( binValue.month > 12 ) binValue.month = 12; + } + + if ( binValue.day == 0 ) { + if ( binValue.hasTime ) binValue.day = 1; + } else { + if ( binValue.day < 1 ) binValue.day = 1; + if ( binValue.day > 31 ) binValue.day = 31; + } + + // Now carry on with the original logic. + + if ( binValue.month == 0 ) { + + // Output YYYY if all else is zero, otherwise output a full string for the quasi-bogus + // "time only" values from Photoshop CS. + if ( (binValue.day == 0) && (! binValue.hasTime) ) { + snprintf ( buffer, sizeof(buffer), "%.4d", binValue.year ); // AUDIT: Using sizeof for snprintf length is safe. + } else if ( (binValue.year == 0) && (binValue.day == 0) ) { + FormatFullDateTime ( binValue, buffer, sizeof(buffer) ); + } else { + XMP_Throw ( "Invalid partial date", kXMPErr_BadParam); + } + + } else if ( binValue.day == 0 ) { + + // Output YYYY-MM. + if ( (binValue.month < 1) || (binValue.month > 12) ) XMP_Throw ( "Month is out of range", kXMPErr_BadParam); + if ( binValue.hasTime ) XMP_Throw ( "Invalid partial date, non-zeros after zero month and day", kXMPErr_BadParam); + snprintf ( buffer, sizeof(buffer), "%.4d-%02d", binValue.year, binValue.month ); // AUDIT: Using sizeof for snprintf length is safe. + + } else if ( ! binValue.hasTime ) { + + // Output YYYY-MM-DD. + if ( (binValue.month < 1) || (binValue.month > 12) ) XMP_Throw ( "Month is out of range", kXMPErr_BadParam); + if ( (binValue.day < 1) || (binValue.day > 31) ) XMP_Throw ( "Day is out of range", kXMPErr_BadParam); + snprintf ( buffer, sizeof(buffer), "%.4d-%02d-%02d", binValue.year, binValue.month, binValue.day ); // AUDIT: Using sizeof for snprintf length is safe. + + } else { + + FormatFullDateTime ( binValue, buffer, sizeof(buffer) ); + + } + + strValue->assign ( buffer ); + + if ( binValue.hasTimeZone ) { + + if ( (binValue.tzHour < 0) || (binValue.tzHour > 23) || + (binValue.tzMinute < 0 ) || (binValue.tzMinute > 59) || + (binValue.tzSign < -1) || (binValue.tzSign > +1) || + ((binValue.tzSign == 0) && ((binValue.tzHour != 0) || (binValue.tzMinute != 0))) ) { + XMP_Throw ( "Invalid time zone values", kXMPErr_BadParam ); + } + + if ( binValue.tzSign == 0 ) { + *strValue += 'Z'; + } else { + snprintf ( buffer, sizeof(buffer), "+%02d:%02d", binValue.tzHour, binValue.tzMinute ); // AUDIT: Using sizeof for snprintf length is safe. + if ( binValue.tzSign < 0 ) buffer[0] = '-'; + *strValue += buffer; + } + + } + +} // ConvertFromDate + +// ------------------------------------------------------------------------------------------------- +// ConvertToBool +// ------------- +// +// Formally the string value should be "True" or "False", but we should be more flexible here. Map +// the string to lower case. Allow any of "true", "false", "t", "f", "1", or "0". + +/* class static */ bool +XMPUtils::ConvertToBool ( XMP_StringPtr strValue ) +{ + if ( (strValue == 0) || (*strValue == 0) ) XMP_Throw ( "Empty convert-from string", kXMPErr_BadValue ); + + bool result = false; + XMP_VarString strObj ( strValue ); + + for ( XMP_VarStringPos ch = strObj.begin(); ch != strObj.end(); ++ch ) { + if ( ('A' <= *ch) && (*ch <= 'Z') ) *ch += 0x20; + } + + if ( (strObj == "true") || (strObj == "t") || (strObj == "1") ) { + result = true; + } else if ( (strObj == "false") || (strObj == "f") || (strObj == "0") ) { + result = false; + } else { + XMP_Throw ( "Invalid Boolean string", kXMPErr_BadParam ); + } + + return result; + +} // ConvertToBool + +// ------------------------------------------------------------------------------------------------- +// ConvertToInt +// ------------ + +/* class static */ XMP_Int32 +XMPUtils::ConvertToInt ( XMP_StringPtr strValue ) +{ + if ( (strValue == 0) || (*strValue == 0) ) XMP_Throw ( "Empty convert-from string", kXMPErr_BadValue ); + + int count; + char nextCh; + XMP_Int32 result; + + if ( ! XMP_LitNMatch ( strValue, "0x", 2 ) ) { + count = sscanf ( strValue, "%d%c", &result, &nextCh ); + } else { + count = sscanf ( strValue, "%x%c", &result, &nextCh ); + } + + if ( count != 1 ) XMP_Throw ( "Invalid integer string", kXMPErr_BadParam ); + + return result; + +} // ConvertToInt + +// ------------------------------------------------------------------------------------------------- +// ConvertToInt64 +// -------------- + +/* class static */ XMP_Int64 +XMPUtils::ConvertToInt64 ( XMP_StringPtr strValue ) +{ + if ( (strValue == 0) || (*strValue == 0) ) XMP_Throw ( "Empty convert-from string", kXMPErr_BadValue ); + + int count; + char nextCh; + XMP_Int64 result; + + if ( ! XMP_LitNMatch ( strValue, "0x", 2 ) ) { + count = sscanf ( strValue, "%lld%c", &result, &nextCh ); + } else { + count = sscanf ( strValue, "%llx%c", &result, &nextCh ); + } + + if ( count != 1 ) XMP_Throw ( "Invalid integer string", kXMPErr_BadParam ); + + return result; + +} // ConvertToInt64 + +// ------------------------------------------------------------------------------------------------- +// ConvertToFloat +// -------------- + +/* class static */ double +XMPUtils::ConvertToFloat ( XMP_StringPtr strValue ) +{ + if ( (strValue == 0) || (*strValue == 0) ) XMP_Throw ( "Empty convert-from string", kXMPErr_BadValue ); + + XMP_VarString oldLocale; // Try to make sure number conversion uses '.' as the decimal point. + XMP_StringPtr oldLocalePtr = setlocale ( LC_ALL, 0 ); + if ( oldLocalePtr != 0 ) { + oldLocale.assign ( oldLocalePtr ); // Save the locale to be reset when exiting. + setlocale ( LC_ALL, "C" ); + } + + errno = 0; + char * numEnd; + double result = strtod ( strValue, &numEnd ); + int errnoSave = errno; // The setlocale call below might change errno. + + if ( ! oldLocale.empty() ) setlocale ( LC_ALL, oldLocale.c_str() ); // ! Reset locale before possible throw! + if ( (errnoSave != 0) || (*numEnd != 0) ) XMP_Throw ( "Invalid float string", kXMPErr_BadParam ); + + return result; + +} // ConvertToFloat + +// ------------------------------------------------------------------------------------------------- +// ConvertToDate +// ------------- +// +// Parse a date-time string according to ISO 8601 and http://www.w3.org/TR/NOTE-datetime: +// YYYY +// YYYY-MM +// YYYY-MM-DD +// YYYY-MM-DDThh:mmTZD +// YYYY-MM-DDThh:mm:ssTZD +// YYYY-MM-DDThh:mm:ss.sTZD +// +// YYYY = four-digit year +// MM = two-digit month (01=January, etc.) +// DD = two-digit day of month (01 through 31) +// hh = two digits of hour (00 through 23) +// mm = two digits of minute (00 through 59) +// ss = two digits of second (00 through 59) +// s = one or more digits representing a decimal fraction of a second +// TZD = time zone designator (Z or +hh:mm or -hh:mm) +// +// Note that ISO 8601 does not seem to allow years less than 1000 or greater than 9999. We allow +// any year, even negative ones. The year is formatted as "%.4d". The TZD is also optional in XMP, +// even though required in the W3C profile. Finally, Photoshop 8 (CS) sometimes created time-only +// values so we tolerate that. + +// *** Put the ISO format comments in the header documentation. + +/* class static */ void +XMPUtils::ConvertToDate ( XMP_StringPtr strValue, + XMP_DateTime * binValue ) +{ + if ( (strValue == 0) || (*strValue == 0) ) XMP_Throw ( "Empty convert-from string", kXMPErr_BadValue); + + size_t pos = 0; + XMP_Int32 temp; + + XMP_Assert ( sizeof(*binValue) == sizeof(XMP_DateTime) ); + (void) memset ( binValue, 0, sizeof(*binValue) ); // AUDIT: Safe, using sizeof destination. + + size_t strSize = strlen ( strValue ); + bool timeOnly = ( (strValue[0] == 'T') || + ((strSize >= 2) && (strValue[1] == ':')) || + ((strSize >= 3) && (strValue[2] == ':')) ); + + if ( ! timeOnly ) { + + binValue->hasDate = true; + + if ( strValue[0] == '-' ) pos = 1; + + temp = GatherInt ( strValue, &pos, "Invalid year in date string" ); // Extract the year. + if ( (strValue[pos] != 0) && (strValue[pos] != '-') ) XMP_Throw ( "Invalid date string, after year", kXMPErr_BadParam ); + if ( strValue[0] == '-' ) temp = -temp; + binValue->year = temp; + if ( strValue[pos] == 0 ) return; + + ++pos; + temp = GatherInt ( strValue, &pos, "Invalid month in date string" ); // Extract the month. + if ( (strValue[pos] != 0) && (strValue[pos] != '-') ) XMP_Throw ( "Invalid date string, after month", kXMPErr_BadParam ); + binValue->month = temp; + if ( strValue[pos] == 0 ) return; + + ++pos; + temp = GatherInt ( strValue, &pos, "Invalid day in date string" ); // Extract the day. + if ( (strValue[pos] != 0) && (strValue[pos] != 'T') ) XMP_Throw ( "Invalid date string, after day", kXMPErr_BadParam ); + binValue->day = temp; + if ( strValue[pos] == 0 ) return; + + // Allow year, month, and day to all be zero; implies the date portion is missing. + if ( (binValue->year != 0) || (binValue->month != 0) || (binValue->day != 0) ) { + // Temporary fix for bug 1269463, silently fix out of range month or day. + // if ( (binValue->month < 1) || (binValue->month > 12) ) XMP_Throw ( "Month is out of range", kXMPErr_BadParam ); + // if ( (binValue->day < 1) || (binValue->day > 31) ) XMP_Throw ( "Day is out of range", kXMPErr_BadParam ); + if ( binValue->month < 1 ) binValue->month = 1; + if ( binValue->month > 12 ) binValue->month = 12; + if ( binValue->day < 1 ) binValue->day = 1; + if ( binValue->day > 31 ) binValue->day = 31; + } + + } + + // If we get here there is more of the string, otherwise we would have returned above. + + if ( strValue[pos] == 'T' ) { + ++pos; + } else if ( ! timeOnly ) { + XMP_Throw ( "Invalid date string, missing 'T' after date", kXMPErr_BadParam ); + } + + binValue->hasTime = true; + + temp = GatherInt ( strValue, &pos, "Invalid hour in date string" ); // Extract the hour. + if ( strValue[pos] != ':' ) XMP_Throw ( "Invalid date string, after hour", kXMPErr_BadParam ); + if ( temp > 23 ) temp = 23; // *** 1269463: XMP_Throw ( "Hour is out of range", kXMPErr_BadParam ); + binValue->hour = temp; + // Don't check for done, we have to work up to the time zone. + + ++pos; + temp = GatherInt ( strValue, &pos, "Invalid minute in date string" ); // And the minute. + if ( (strValue[pos] != ':') && (strValue[pos] != 'Z') && + (strValue[pos] != '+') && (strValue[pos] != '-') && (strValue[pos] != 0) ) XMP_Throw ( "Invalid date string, after minute", kXMPErr_BadParam ); + if ( temp > 59 ) temp = 59; // *** 1269463: XMP_Throw ( "Minute is out of range", kXMPErr_BadParam ); + binValue->minute = temp; + // Don't check for done, we have to work up to the time zone. + + if ( strValue[pos] == ':' ) { + + ++pos; + temp = GatherInt ( strValue, &pos, "Invalid whole seconds in date string" ); // Extract the whole seconds. + if ( (strValue[pos] != '.') && (strValue[pos] != 'Z') && + (strValue[pos] != '+') && (strValue[pos] != '-') && (strValue[pos] != 0) ) { + XMP_Throw ( "Invalid date string, after whole seconds", kXMPErr_BadParam ); + } + if ( temp > 59 ) temp = 59; // *** 1269463: XMP_Throw ( "Whole second is out of range", kXMPErr_BadParam ); + binValue->second = temp; + // Don't check for done, we have to work up to the time zone. + + if ( strValue[pos] == '.' ) { + + ++pos; + size_t digits = pos; // Will be the number of digits later. + + temp = GatherInt ( strValue, &pos, "Invalid fractional seconds in date string" ); // Extract the fractional seconds. + if ( (strValue[pos] != 'Z') && (strValue[pos] != '+') && (strValue[pos] != '-') && (strValue[pos] != 0) ) { + XMP_Throw ( "Invalid date string, after fractional second", kXMPErr_BadParam ); + } + + digits = pos - digits; + for ( ; digits > 9; --digits ) temp = temp / 10; + for ( ; digits < 9; ++digits ) temp = temp * 10; + + if ( temp >= 1000*1000*1000 ) XMP_Throw ( "Fractional second is out of range", kXMPErr_BadParam ); + binValue->nanoSecond = temp; + // Don't check for done, we have to work up to the time zone. + + } + + } + + if ( strValue[pos] == 0 ) return; + + binValue->hasTimeZone = true; + + if ( strValue[pos] == 'Z' ) { + + ++pos; + + } else { + + if ( strValue[pos] == '+' ) { + binValue->tzSign = kXMP_TimeEastOfUTC; + } else if ( strValue[pos] == '-' ) { + binValue->tzSign = kXMP_TimeWestOfUTC; + } else { + XMP_Throw ( "Time zone must begin with 'Z', '+', or '-'", kXMPErr_BadParam ); + } + + ++pos; + temp = GatherInt ( strValue, &pos, "Invalid time zone hour in date string" ); // Extract the time zone hour. + if ( strValue[pos] != ':' ) XMP_Throw ( "Invalid date string, after time zone hour", kXMPErr_BadParam ); + if ( temp > 23 ) XMP_Throw ( "Time zone hour is out of range", kXMPErr_BadParam ); + binValue->tzHour = temp; + + ++pos; + temp = GatherInt ( strValue, &pos, "Invalid time zone minute in date string" ); // Extract the time zone minute. + if ( temp > 59 ) XMP_Throw ( "Time zone minute is out of range", kXMPErr_BadParam ); + binValue->tzMinute = temp; + + } + + if ( strValue[pos] != 0 ) XMP_Throw ( "Invalid date string, extra chars at end", kXMPErr_BadParam ); + +} // ConvertToDate + +// ------------------------------------------------------------------------------------------------- +// EncodeToBase64 +// -------------- +// +// Encode a string of raw data bytes in base 64 according to RFC 2045. For the encoding definition +// see section 6.8 in . Although it isn't needed for RDF, we +// do insert a linefeed character as a newline for every 76 characters of encoded output. + +/* class static */ void +XMPUtils::EncodeToBase64 ( XMP_StringPtr rawStr, + XMP_StringLen rawLen, + XMP_VarString * encodedStr ) +{ + if ( (rawStr == 0) && (rawLen != 0) ) XMP_Throw ( "Null raw data buffer", kXMPErr_BadParam ); + XMP_Assert ( encodedStr != 0 ); // Enforced by wrapper. + + encodedStr->erase(); + if ( rawLen == 0 ) return; + + char encChunk[4]; + + unsigned long in, out; + unsigned char c1, c2, c3; + unsigned long merge; + + const size_t outputSize = (rawLen / 3) * 4; // Approximate, might be small. + + encodedStr->reserve ( outputSize ); + + // ---------------------------------------------------------------------------------------- + // Each 6 bits of input produces 8 bits of output, so 3 input bytes become 4 output bytes. + // Process the whole chunks of 3 bytes first, then deal with any remainder. Be careful with + // the loop comparison, size-2 could be negative! + + for ( in = 0, out = 0; (in+2) < rawLen; in += 3, out += 4 ) { + + c1 = rawStr[in]; + c2 = rawStr[in+1]; + c3 = rawStr[in+2]; + + merge = (c1 << 16) + (c2 << 8) + c3; + + encChunk[0] = sBase64Chars [ merge >> 18 ]; + encChunk[1] = sBase64Chars [ (merge >> 12) & 0x3F ]; + encChunk[2] = sBase64Chars [ (merge >> 6) & 0x3F ]; + encChunk[3] = sBase64Chars [ merge & 0x3F ]; + + if ( out >= 76 ) { + encodedStr->append ( 1, kLF ); + out = 0; + } + encodedStr->append ( encChunk, 4 ); + + } + + // ------------------------------------------------------------------------------------------ + // The output must always be a multiple of 4 bytes. If there is a 1 or 2 byte input remainder + // we need to create another chunk. Zero pad with bits to a 6 bit multiple, then add one or + // two '=' characters to pad out to 4 bytes. + + switch ( rawLen - in ) { + + case 0: // Done, no remainder. + break; + + case 1: // One input byte remains. + + c1 = rawStr[in]; + merge = c1 << 16; + + encChunk[0] = sBase64Chars [ merge >> 18 ]; + encChunk[1] = sBase64Chars [ (merge >> 12) & 0x3F ]; + encChunk[2] = encChunk[3] = '='; + + if ( out >= 76 ) encodedStr->append ( 1, kLF ); + encodedStr->append ( encChunk, 4 ); + break; + + case 2: // Two input bytes remain. + + c1 = rawStr[in]; + c2 = rawStr[in+1]; + merge = (c1 << 16) + (c2 << 8); + + encChunk[0] = sBase64Chars [ merge >> 18 ]; + encChunk[1] = sBase64Chars [ (merge >> 12) & 0x3F ]; + encChunk[2] = sBase64Chars [ (merge >> 6) & 0x3F ]; + encChunk[3] = '='; + + if ( out >= 76 ) encodedStr->append ( 1, kLF ); + encodedStr->append ( encChunk, 4 ); + break; + + } + +} // EncodeToBase64 + +// ------------------------------------------------------------------------------------------------- +// DecodeFromBase64 +// ---------------- +// +// Decode a string of raw data bytes from base 64 according to RFC 2045. For the encoding definition +// see section 6.8 in . RFC 2045 talks about ignoring all "bad" +// input but warning about non-whitespace. For XMP use we ignore space, tab, LF, and CR. Any other +// bad input is rejected. + +/* class static */ void +XMPUtils::DecodeFromBase64 ( XMP_StringPtr encodedStr, + XMP_StringLen encodedLen, + XMP_VarString * rawStr ) +{ + if ( (encodedStr == 0) && (encodedLen != 0) ) XMP_Throw ( "Null encoded data buffer", kXMPErr_BadParam ); + XMP_Assert ( rawStr != 0 ); // Enforced by wrapper. + + rawStr->erase(); + if ( encodedLen == 0 ) return; + + unsigned char ch, rawChunk[3]; + unsigned long inStr, inChunk, inLimit, merge, padding; + + XMP_StringLen outputSize = (encodedLen / 4) * 3; // Only a close approximation. + + rawStr->reserve ( outputSize ); + + + // ---------------------------------------------------------------------------------------- + // Each 8 bits of input produces 6 bits of output, so 4 input bytes become 3 output bytes. + // Process all but the last 4 data bytes first, then deal with the final chunk. Whitespace + // in the input must be ignored. The first loop finds where the last 4 data bytes start and + // counts the number of padding equal signs. + + padding = 0; + for ( inStr = 0, inLimit = encodedLen; (inStr < 4) && (inLimit > 0); ) { + inLimit -= 1; // ! Don't do in the loop control, the decr/test order is wrong. + ch = encodedStr[inLimit]; + if ( ch == '=' ) { + padding += 1; // The equal sign padding is a data byte. + } else if ( DecodeBase64Char(ch) == 0xFF ) { + continue; // Ignore whitespace, don't increment inStr. + } else { + inStr += 1; + } + } + + // ! Be careful to count whitespace that is immediately before the final data. Otherwise + // ! middle portion will absorb the final data and mess up the final chunk processing. + + while ( (inLimit > 0) && (DecodeBase64Char(encodedStr[inLimit-1]) == 0xFF) ) --inLimit; + + if ( inStr == 0 ) return; // Nothing but whitespace. + if ( padding > 2 ) XMP_Throw ( "Invalid encoded string", kXMPErr_BadParam ); + + // ------------------------------------------------------------------------------------------- + // Now process all but the last chunk. The limit ensures that we have at least 4 data bytes + // left when entering the output loop, so the inner loop will succeed without overrunning the + // end of the data. At the end of the outer loop we might be past inLimit though. + + inStr = 0; + while ( inStr < inLimit ) { + + merge = 0; + for ( inChunk = 0; inChunk < 4; ++inStr ) { // ! Yes, increment inStr on each pass. + ch = DecodeBase64Char ( encodedStr [inStr] ); + if ( ch == 0xFF ) continue; // Ignore whitespace. + merge = (merge << 6) + ch; + inChunk += 1; + } + + rawChunk[0] = (unsigned char) (merge >> 16); + rawChunk[1] = (unsigned char) ((merge >> 8) & 0xFF); + rawChunk[2] = (unsigned char) (merge & 0xFF); + + rawStr->append ( (char*)rawChunk, 3 ); + + } + + // ------------------------------------------------------------------------------------------- + // Process the final, possibly partial, chunk of data. The input is always a multiple 4 bytes, + // but the raw data can be any length. The number of padding '=' characters determines if the + // final chunk has 1, 2, or 3 raw data bytes. + + XMP_Assert ( inStr < encodedLen ); + + merge = 0; + for ( inChunk = 0; inChunk < 4-padding; ++inStr ) { // ! Yes, increment inStr on each pass. + ch = DecodeBase64Char ( encodedStr[inStr] ); + if ( ch == 0xFF ) continue; // Ignore whitespace. + merge = (merge << 6) + ch; + inChunk += 1; + } + + if ( padding == 2 ) { + + rawChunk[0] = (unsigned char) (merge >> 4); + rawStr->append ( (char*)rawChunk, 1 ); + + } else if ( padding == 1 ) { + + rawChunk[0] = (unsigned char) (merge >> 10); + rawChunk[1] = (unsigned char) ((merge >> 2) & 0xFF); + rawStr->append ( (char*)rawChunk, 2 ); + + } else { + + rawChunk[0] = (unsigned char) (merge >> 16); + rawChunk[1] = (unsigned char) ((merge >> 8) & 0xFF); + rawChunk[2] = (unsigned char) (merge & 0xFF); + rawStr->append ( (char*)rawChunk, 3 ); + + } + +} // DecodeFromBase64 + +// ------------------------------------------------------------------------------------------------- +// PackageForJPEG +// -------------- + +/* class static */ void +XMPUtils::PackageForJPEG ( const XMPMeta & origXMP, + XMP_VarString * stdStr, + XMP_VarString * extStr, + XMP_VarString * digestStr ) +{ + XMP_Assert ( (stdStr != 0) && (extStr != 0) && (digestStr != 0) ); // ! Enforced by wrapper. + + enum { kStdXMPLimit = 65000 }; + static const char * kPacketTrailer = ""; + static size_t kTrailerLen = strlen ( kPacketTrailer ); + + XMP_VarString tempStr; + XMPMeta stdXMP, extXMP; + XMP_OptionBits keepItSmall = kXMP_UseCompactFormat | kXMP_OmitAllFormatting; + + stdStr->erase(); + extStr->erase(); + digestStr->erase(); + + // Try to serialize everything. Note that we're making internal calls to SerializeToBuffer, so + // we'll be getting back the pointer and length for its internal string. + + origXMP.SerializeToBuffer ( &tempStr, keepItSmall, 1, "", "", 0 ); + #if Trace_PackageForJPEG + printf ( "\nXMPUtils::PackageForJPEG - Full serialize %d bytes\n", tempStr.size() ); + #endif + + if ( tempStr.size() > kStdXMPLimit ) { + + // Couldn't fit everything, make a copy of the input XMP and make sure there is no xmp:Thumbnails property. + + stdXMP.tree.options = origXMP.tree.options; + stdXMP.tree.name = origXMP.tree.name; + stdXMP.tree.value = origXMP.tree.value; + CloneOffspring ( &origXMP.tree, &stdXMP.tree ); + + if ( stdXMP.DoesPropertyExist ( kXMP_NS_XMP, "Thumbnails" ) ) { + stdXMP.DeleteProperty ( kXMP_NS_XMP, "Thumbnails" ); + stdXMP.SerializeToBuffer ( &tempStr, keepItSmall, 1, "", "", 0 ); + #if Trace_PackageForJPEG + printf ( " Delete xmp:Thumbnails, %d bytes left\n", tempStr.size() ); + #endif + } + + } + + if ( tempStr.size() > kStdXMPLimit ) { + + // Still doesn't fit, move all of the Camera Raw namespace. Add a dummy value for xmpNote:HasExtendedXMP. + + stdXMP.SetProperty ( kXMP_NS_XMP_Note, "HasExtendedXMP", "123456789-123456789-123456789-12", 0 ); + + XMP_NodePtrPos crSchemaPos; + XMP_Node * crSchema = FindSchemaNode ( &stdXMP.tree, kXMP_NS_CameraRaw, kXMP_ExistingOnly, &crSchemaPos ); + + if ( crSchema != 0 ) { + crSchema->parent = &extXMP.tree; + extXMP.tree.children.push_back ( crSchema ); + stdXMP.tree.children.erase ( crSchemaPos ); + stdXMP.SerializeToBuffer ( &tempStr, keepItSmall, 1, "", "", 0 ); + #if Trace_PackageForJPEG + printf ( " Move Camera Raw schema, %d bytes left\n", tempStr.size() ); + #endif + } + + } + + if ( tempStr.size() > kStdXMPLimit ) { + + // Still doesn't fit, move photoshop:History. + + bool moved = MoveOneProperty ( stdXMP, &extXMP, kXMP_NS_Photoshop, "photoshop:History" ); + + if ( moved ) { + stdXMP.SerializeToBuffer ( &tempStr, keepItSmall, 1, "", "", 0 ); + #if Trace_PackageForJPEG + printf ( " Move photoshop:History, %d bytes left\n", tempStr.size() ); + #endif + } + + } + + if ( tempStr.size() > kStdXMPLimit ) { + + // Still doesn't fit, move top level properties in order of estimated size. This is done by + // creating a multi-map that maps the serialized size to the string pair for the schema URI + // and top level property name. Since maps are inherently ordered, a reverse iteration of + // the map can be done to move the largest things first. We use a double loop to keep going + // until the serialization actually fits, in case the estimates are off. + + PropSizeMap propSizes; + CreateEstimatedSizeMap ( stdXMP, &propSizes ); + + #if Trace_PackageForJPEG + if ( ! propSizes.empty() ) { + printf ( " Top level property map, smallest to largest:\n" ); + PropSizeMap::iterator mapPos = propSizes.begin(); + PropSizeMap::iterator mapEnd = propSizes.end(); + for ( ; mapPos != mapEnd; ++mapPos ) { + size_t propSize = mapPos->first; + const char * schemaName = mapPos->second.first->c_str(); + const char * propName = mapPos->second.second->c_str(); + printf ( " %d bytes, %s in %s\n", propSize, propName, schemaName ); + } + } + #endif + + #if 0 // Trace_PackageForJPEG *** Xcode 2.3 on 10.4.7 has bugs in backwards iteration + if ( ! propSizes.empty() ) { + printf ( " Top level property map, largest to smallest:\n" ); + PropSizeMap::iterator mapPos = propSizes.end(); + PropSizeMap::iterator mapBegin = propSizes.begin(); + for ( --mapPos; true; --mapPos ) { + size_t propSize = mapPos->first; + const char * schemaName = mapPos->second.first->c_str(); + const char * propName = mapPos->second.second->c_str(); + printf ( " %d bytes, %s in %s\n", propSize, propName, schemaName ); + if ( mapPos == mapBegin ) break; + } + } + #endif + + // Outer loop to make sure enough is actually moved. + + while ( (tempStr.size() > kStdXMPLimit) && (! propSizes.empty()) ) { + + // Inner loop, move what seems to be enough according to the estimates. + + size_t tempLen = tempStr.size(); + while ( (tempLen > kStdXMPLimit) && (! propSizes.empty()) ) { + + size_t propSize = MoveLargestProperty ( stdXMP, &extXMP, propSizes ); + XMP_Assert ( propSize > 0 ); + + if ( propSize > tempLen ) propSize = tempLen; // ! Don't go negative. + tempLen -= propSize; + + } + + // Reserialize the remaining standard XMP. + + stdXMP.SerializeToBuffer ( &tempStr, keepItSmall, 1, "", "", 0 ); + + } + + } + + if ( tempStr.size() > kStdXMPLimit ) { + // Still doesn't fit, throw an exception and let the client decide what to do. + // ! This should never happen with the policy of moving any and all top level properties. + XMP_Throw ( "Can't reduce XMP enough for JPEG file", kXMPErr_TooLargeForJPEG ); + } + + // Set the static output strings. + + if ( extXMP.tree.children.empty() ) { + + // Just have the standard XMP. + *stdStr = tempStr; + + } else { + + // Have extended XMP. Serialize it, compute the digest, reset xmpNote:HasExtendedXMP, and + // reserialize the standard XMP. + + extXMP.SerializeToBuffer ( &tempStr, (keepItSmall | kXMP_OmitPacketWrapper), 0, "", "", 0 ); + *extStr = tempStr; + + MD5_CTX context; + XMP_Uns8 digest [16]; + MD5Init ( &context ); + MD5Update ( &context, (XMP_Uns8*)tempStr.c_str(), (XMP_Uns32)tempStr.size() ); + MD5Final ( digest, &context ); + + digestStr->reserve ( 32 ); + for ( size_t i = 0; i < 16; ++i ) { + XMP_Uns8 byte = digest[i]; + digestStr->push_back ( kHexDigits [ byte>>4 ] ); + digestStr->push_back ( kHexDigits [ byte&0xF ] ); + } + + stdXMP.SetProperty ( kXMP_NS_XMP_Note, "HasExtendedXMP", digestStr->c_str(), 0 ); + stdXMP.SerializeToBuffer ( &tempStr, keepItSmall, 1, "", "", 0 ); + *stdStr = tempStr; + + } + + // Adjust the standard XMP padding to be up to 2KB. + +#if XMP_DebugBuild + XMP_Assert ( (stdStr->size() > kTrailerLen) && (stdStr->size() <= kStdXMPLimit) ); + const char * packetEnd = stdStr->c_str() + stdStr->size() - kTrailerLen; + XMP_Assert ( XMP_LitMatch ( packetEnd, kPacketTrailer ) ); +#endif + + size_t extraPadding = kStdXMPLimit - stdStr->size(); // ! Do this before erasing the trailer. + if ( extraPadding > 2047 ) extraPadding = 2047; + stdStr->erase ( stdStr->size() - kTrailerLen ); + stdStr->append ( extraPadding, ' ' ); + stdStr->append ( kPacketTrailer ); + +} // PackageForJPEG + +// ------------------------------------------------------------------------------------------------- +// MergeFromJPEG +// ------------- +// +// Copy all of the top level properties from extendedXMP to fullXMP, replacing any duplicates. +// Delete the xmpNote:HasExtendedXMP property from fullXMP. + +/* class static */ void +XMPUtils::MergeFromJPEG ( XMPMeta * fullXMP, + const XMPMeta & extendedXMP ) +{ + + XMP_OptionBits apFlags = (kXMPTemplate_ReplaceExistingProperties | kXMPTemplate_IncludeInternalProperties); + XMPUtils::ApplyTemplate ( fullXMP, extendedXMP, apFlags ); + fullXMP->DeleteProperty ( kXMP_NS_XMP_Note, "HasExtendedXMP" ); + +} // MergeFromJPEG + +// ------------------------------------------------------------------------------------------------- +// CurrentDateTime +// --------------- + +/* class static */ void +XMPUtils::CurrentDateTime ( XMP_DateTime * xmpTime ) +{ + XMP_Assert ( xmpTime != 0 ); // ! Enforced by wrapper. + + ansi_tt binTime = ansi_time(0); + if ( binTime == -1 ) XMP_Throw ( "Failure from ANSI C time function", kXMPErr_ExternalFailure ); + ansi_tm currTime; + ansi_localtime ( &binTime, &currTime ); + + xmpTime->year = currTime.tm_year + 1900; + xmpTime->month = currTime.tm_mon + 1; + xmpTime->day = currTime.tm_mday; + xmpTime->hasDate = true; + + xmpTime->hour = currTime.tm_hour; + xmpTime->minute = currTime.tm_min; + xmpTime->second = currTime.tm_sec; + xmpTime->nanoSecond = 0; + xmpTime->hasTime = true; + + xmpTime->tzSign = 0; + xmpTime->tzHour = 0; + xmpTime->tzMinute = 0; + xmpTime->hasTimeZone = false; // ! Needed for SetTimeZone. + XMPUtils::SetTimeZone ( xmpTime ); + +} // CurrentDateTime + +// ------------------------------------------------------------------------------------------------- +// SetTimeZone +// ----------- +// +// Sets just the time zone part of the time. Useful for determining the local time zone or for +// converting a "zone-less" time to a proper local time. The ANSI C time functions are smart enough +// to do all the right stuff, as long as we call them properly! + +/* class static */ void +XMPUtils::SetTimeZone ( XMP_DateTime * xmpTime ) +{ + XMP_Assert ( xmpTime != 0 ); // ! Enforced by wrapper. + + VerifyDateTimeFlags ( xmpTime ); + + if ( xmpTime->hasTimeZone ) { + XMP_Throw ( "SetTimeZone can only be used on zone-less times", kXMPErr_BadParam ); + } + + // Create ansi_tt form of the input time. Need the ansi_tm form to make the ansi_tt form. + + ansi_tt ttTime; + ansi_tm tmLocal, tmUTC; + + if ( (xmpTime->year == 0) && (xmpTime->month == 0) && (xmpTime->day == 0) ) { + ansi_tt now = ansi_time(0); + if ( now == -1 ) XMP_Throw ( "Failure from ANSI C time function", kXMPErr_ExternalFailure ); + ansi_localtime ( &now, &tmLocal ); + } else { + tmLocal.tm_year = xmpTime->year - 1900; + while ( tmLocal.tm_year < 70 ) tmLocal.tm_year += 4; // ! Some versions of mktime barf on years before 1970. + tmLocal.tm_mon = xmpTime->month - 1; + tmLocal.tm_mday = xmpTime->day; + } + + tmLocal.tm_hour = xmpTime->hour; + tmLocal.tm_min = xmpTime->minute; + tmLocal.tm_sec = xmpTime->second; + tmLocal.tm_isdst = -1; // Don't know if daylight time is in effect. + + ttTime = ansi_mktime ( &tmLocal ); + if ( ttTime == -1 ) XMP_Throw ( "Failure from ANSI C mktime function", kXMPErr_ExternalFailure ); + + // Convert back to a localized ansi_tm time and get the corresponding UTC ansi_tm time. + + ansi_localtime ( &ttTime, &tmLocal ); + ansi_gmtime ( &ttTime, &tmUTC ); + + // Get the offset direction and amount. + + ansi_tm tmx = tmLocal; // ! Note that mktime updates the ansi_tm parameter, messing up difftime! + ansi_tm tmy = tmUTC; + tmx.tm_isdst = tmy.tm_isdst = 0; + ansi_tt ttx = ansi_mktime ( &tmx ); + ansi_tt tty = ansi_mktime ( &tmy ); + double diffSecs; + + if ( (ttx != -1) && (tty != -1) ) { + diffSecs = ansi_difftime ( ttx, tty ); + } else { + #if XMP_MacBuild | XMP_iOSBuild + // Looks like Apple's mktime is buggy - see W1140533. But the offset is visible. + diffSecs = tmLocal.tm_gmtoff; + #else + // Win and UNIX don't have a visible offset. Make sure we know about the failure, + // then try using the current date/time as a close fallback. + ttTime = ansi_time(0); + if ( ttTime == -1 ) XMP_Throw ( "Failure from ANSI C time function", kXMPErr_ExternalFailure ); + ansi_localtime ( &ttTime, &tmx ); + ansi_gmtime ( &ttTime, &tmy ); + tmx.tm_isdst = tmy.tm_isdst = 0; + ttx = ansi_mktime ( &tmx ); + tty = ansi_mktime ( &tmy ); + if ( (ttx == -1) || (tty == -1) ) XMP_Throw ( "Failure from ANSI C mktime function", kXMPErr_ExternalFailure ); + diffSecs = ansi_difftime ( ttx, tty ); + #endif + } + + if ( diffSecs > 0.0 ) { + xmpTime->tzSign = kXMP_TimeEastOfUTC; + } else if ( diffSecs == 0.0 ) { + xmpTime->tzSign = kXMP_TimeIsUTC; + } else { + xmpTime->tzSign = kXMP_TimeWestOfUTC; + diffSecs = -diffSecs; + } + xmpTime->tzHour = XMP_Int32 ( diffSecs / 3600.0 ); + xmpTime->tzMinute = XMP_Int32 ( (diffSecs / 60.0) - (xmpTime->tzHour * 60.0) ); + + xmpTime->hasTimeZone = xmpTime->hasTime = true; + + // *** Save the tm_isdst flag in a qualifier? + + XMP_Assert ( (0 <= xmpTime->tzHour) && (xmpTime->tzHour <= 23) ); + XMP_Assert ( (0 <= xmpTime->tzMinute) && (xmpTime->tzMinute <= 59) ); + XMP_Assert ( (-1 <= xmpTime->tzSign) && (xmpTime->tzSign <= +1) ); + XMP_Assert ( (xmpTime->tzSign == 0) ? ((xmpTime->tzHour == 0) && (xmpTime->tzMinute == 0)) : + ((xmpTime->tzHour != 0) || (xmpTime->tzMinute != 0)) ); + +} // SetTimeZone + +// ------------------------------------------------------------------------------------------------- +// ConvertToUTCTime +// ---------------- + +/* class static */ void +XMPUtils::ConvertToUTCTime ( XMP_DateTime * time ) +{ + XMP_Assert ( time != 0 ); // ! Enforced by wrapper. + + VerifyDateTimeFlags ( time ); + + if ( ! time->hasTimeZone ) return; // Do nothing if there is no current time zone. + + XMP_Assert ( (0 <= time->tzHour) && (time->tzHour <= 23) ); + XMP_Assert ( (0 <= time->tzMinute) && (time->tzMinute <= 59) ); + XMP_Assert ( (-1 <= time->tzSign) && (time->tzSign <= +1) ); + XMP_Assert ( (time->tzSign == 0) ? ((time->tzHour == 0) && (time->tzMinute == 0)) : + ((time->tzHour != 0) || (time->tzMinute != 0)) ); + + if ( time->tzSign == kXMP_TimeEastOfUTC ) { + // We are before (east of) GMT, subtract the offset from the time. + time->hour -= time->tzHour; + time->minute -= time->tzMinute; + } else if ( time->tzSign == kXMP_TimeWestOfUTC ) { + // We are behind (west of) GMT, add the offset to the time. + time->hour += time->tzHour; + time->minute += time->tzMinute; + } + + AdjustTimeOverflow ( time ); + time->tzSign = time->tzHour = time->tzMinute = 0; + +} // ConvertToUTCTime + +// ------------------------------------------------------------------------------------------------- +// ConvertToLocalTime +// ------------------ + +/* class static */ void +XMPUtils::ConvertToLocalTime ( XMP_DateTime * time ) +{ + XMP_Assert ( time != 0 ); // ! Enforced by wrapper. + + VerifyDateTimeFlags ( time ); + + if ( ! time->hasTimeZone ) return; // Do nothing if there is no current time zone. + + XMP_Assert ( (0 <= time->tzHour) && (time->tzHour <= 23) ); + XMP_Assert ( (0 <= time->tzMinute) && (time->tzMinute <= 59) ); + XMP_Assert ( (-1 <= time->tzSign) && (time->tzSign <= +1) ); + XMP_Assert ( (time->tzSign == 0) ? ((time->tzHour == 0) && (time->tzMinute == 0)) : + ((time->tzHour != 0) || (time->tzMinute != 0)) ); + + ConvertToUTCTime ( time ); // The existing time zone might not be the local one. + time->hasTimeZone = false; // ! Needed for SetTimeZone. + SetTimeZone ( time ); // Fill in the local timezone offset, then adjust the time. + + if ( time->tzSign > 0 ) { + // We are before (east of) GMT, add the offset to the time. + time->hour += time->tzHour; + time->minute += time->tzMinute; + } else if ( time->tzSign < 0 ) { + // We are behind (west of) GMT, subtract the offset from the time. + time->hour -= time->tzHour; + time->minute -= time->tzMinute; + } + + AdjustTimeOverflow ( time ); + +} // ConvertToLocalTime + +// ------------------------------------------------------------------------------------------------- +// CompareDateTime +// --------------- + +/* class static */ int +XMPUtils::CompareDateTime ( const XMP_DateTime & _in_left, + const XMP_DateTime & _in_right ) +{ + int result = 0; + + XMP_DateTime left = _in_left; + XMP_DateTime right = _in_right; + + VerifyDateTimeFlags ( &left ); + VerifyDateTimeFlags ( &right ); + + // Can't compare if one has a date and the other does not. + if ( left.hasDate != right.hasDate ) return 0; // Throw? + + if ( left.hasTimeZone & right.hasTimeZone ) { + // If both times have zones then convert them to UTC, otherwise assume the same zone. + ConvertToUTCTime ( &left ); + ConvertToUTCTime ( &right ); + } + + if ( left.hasDate ) { + + XMP_Assert ( right.hasDate ); + + if ( left.year < right.year ) { + result = -1; + } else if ( left.year > right.year ) { + result = +1; + } else if ( left.month < right.month ) { + result = -1; + } else if ( left.month > right.month ) { + result = +1; + } else if ( left.day < right.day ) { + result = -1; + } else if ( left.day > right.day ) { + result = +1; + } + + if ( result != 0 ) return result; + + } + + if ( left.hasTime & right.hasTime ) { + + // Ignore the time parts if either value is date-only. + + if ( left.hour < right.hour ) { + result = -1; + } else if ( left.hour > right.hour ) { + result = +1; + } else if ( left.minute < right.minute ) { + result = -1; + } else if ( left.minute > right.minute ) { + result = +1; + } else if ( left.second < right.second ) { + result = -1; + } else if ( left.second > right.second ) { + result = +1; + } else if ( left.nanoSecond < right.nanoSecond ) { + result = -1; + } else if ( left.nanoSecond > right.nanoSecond ) { + result = +1; + } else { + result = 0; + } + + } + + return result; + +} // CompareDateTime + +// ================================================================================================= + +std::string& XMPUtils::Trim( std::string& string ) +{ + size_t pos = string.find_last_not_of( *WhiteSpaceStrPtr ); + + if ( pos != std::string::npos ) { + string.erase( pos + 1 ); + pos = string.find_first_not_of( *WhiteSpaceStrPtr ); + if(pos != std::string::npos) string.erase(0, pos); + } else { + string.erase( string.begin(), string.end() ); + } + return string; +} + +std::string * XMPUtils::WhiteSpaceStrPtr = NULL; + +// ================================================================================================= diff --git a/XMPCore/source/XMPUtils.hpp b/XMPCore/source/XMPUtils.hpp new file mode 100644 index 0000000..f9cfce1 --- /dev/null +++ b/XMPCore/source/XMPUtils.hpp @@ -0,0 +1,198 @@ +#ifndef __XMPUtils_hpp__ +#define __XMPUtils_hpp__ + +// ================================================================================================= +// 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" +#include "public/include/XMP_Const.h" + +#include "XMPCore/source/XMPMeta.hpp" +#include "XMPCore/source/XMPCore_Impl.hpp" +#include "public/include/client-glue/WXMPUtils.hpp" + +// ------------------------------------------------------------------------------------------------- + +class XMPUtils { +public: + + static bool + Initialize(); // ! For internal use only! + + static void + Terminate() RELEASE_NO_THROW; // ! For internal use only! + + // --------------------------------------------------------------------------------------------- + + static void + ComposeArrayItemPath ( XMP_StringPtr schemaNS, + XMP_StringPtr arrayName, + XMP_Index itemIndex, + XMP_VarString * fullPath ); + + static void + ComposeStructFieldPath ( XMP_StringPtr schemaNS, + XMP_StringPtr structName, + XMP_StringPtr fieldNS, + XMP_StringPtr fieldName, + XMP_VarString * fullPath ); + + static void + ComposeQualifierPath ( XMP_StringPtr schemaNS, + XMP_StringPtr propName, + XMP_StringPtr qualNS, + XMP_StringPtr qualName, + XMP_VarString * fullPath ); + + static void + ComposeLangSelector ( XMP_StringPtr schemaNS, + XMP_StringPtr arrayName, + XMP_StringPtr langName, + XMP_VarString * fullPath ); + + static void + ComposeFieldSelector ( XMP_StringPtr schemaNS, + XMP_StringPtr arrayName, + XMP_StringPtr fieldNS, + XMP_StringPtr fieldName, + XMP_StringPtr fieldValue, + XMP_VarString * fullPath ); + + // --------------------------------------------------------------------------------------------- + + static void + ConvertFromBool ( bool binValue, + XMP_VarString * strValue ); + + static void + ConvertFromInt ( XMP_Int32 binValue, + XMP_StringPtr format, + XMP_VarString * strValue ); + + static void + ConvertFromInt64 ( XMP_Int64 binValue, + XMP_StringPtr format, + XMP_VarString * strValue ); + + static void + ConvertFromFloat ( double binValue, + XMP_StringPtr format, + XMP_VarString * strValue ); + + static void + ConvertFromDate ( const XMP_DateTime & binValue, + XMP_VarString * strValue ); + + // --------------------------------------------------------------------------------------------- + + static bool + ConvertToBool ( XMP_StringPtr strValue ); + + static XMP_Int32 + ConvertToInt ( XMP_StringPtr strValue ); + + static XMP_Int64 + ConvertToInt64 ( XMP_StringPtr strValue ); + + static double + ConvertToFloat ( XMP_StringPtr strValue ); + + static void + ConvertToDate ( XMP_StringPtr strValue, + XMP_DateTime * binValue ); + + // --------------------------------------------------------------------------------------------- + + static void + CurrentDateTime ( XMP_DateTime * time ); + + static void + SetTimeZone ( XMP_DateTime * time ); + + static void + ConvertToUTCTime ( XMP_DateTime * time ); + + static void + ConvertToLocalTime ( XMP_DateTime * time ); + + static int + CompareDateTime ( const XMP_DateTime & left, + const XMP_DateTime & right ); + // --------------------------------------------------------------------------------------------- + + static void + EncodeToBase64 ( XMP_StringPtr rawStr, + XMP_StringLen rawLen, + XMP_VarString * encodedStr ); + + static void + DecodeFromBase64 ( XMP_StringPtr encodedStr, + XMP_StringLen encodedLen, + XMP_VarString * rawStr ); + + // --------------------------------------------------------------------------------------------- + + static void + PackageForJPEG ( const XMPMeta & xmpObj, + XMP_VarString * stdStr, + XMP_VarString * extStr, + XMP_VarString * digestStr ); + + static void + MergeFromJPEG ( XMPMeta * fullXMP, + const XMPMeta & extendedXMP ); + + // --------------------------------------------------------------------------------------------- + + static void + CatenateArrayItems ( const XMPMeta & xmpObj, + XMP_StringPtr schemaNS, + XMP_StringPtr arrayName, + XMP_StringPtr separator, + XMP_StringPtr quotes, + XMP_OptionBits options, + XMP_VarString * catedStr ); + + static void + SeparateArrayItems ( XMPMeta * xmpObj, + XMP_StringPtr schemaNS, + XMP_StringPtr arrayName, + XMP_OptionBits options, + XMP_StringPtr catedStr ); + + static void + ApplyTemplate ( XMPMeta * workingXMP, + const XMPMeta & templateXMP, + XMP_OptionBits actions ); + + static void + RemoveProperties ( XMPMeta * xmpObj, + XMP_StringPtr schemaNS, + XMP_StringPtr propName, + XMP_OptionBits options ); + + static void + DuplicateSubtree ( const XMPMeta & source, + XMPMeta * dest, + XMP_StringPtr sourceNS, + XMP_StringPtr sourceRoot, + XMP_StringPtr destNS, + XMP_StringPtr destRoot, + XMP_OptionBits options ); + + // --------------------------------------------------------------------------------------------- + + static std::string& Trim(std::string& string); + + static std::string * WhiteSpaceStrPtr; + +}; // XMPUtils + +// ================================================================================================= + +#endif // __XMPUtils_hpp__ diff --git a/XMPFiles/Makefile.am b/XMPFiles/Makefile.am new file mode 100644 index 0000000..525e2e5 --- /dev/null +++ b/XMPFiles/Makefile.am @@ -0,0 +1,3 @@ + + +SUBDIRS=source diff --git a/XMPFiles/Makefile.in b/XMPFiles/Makefile.in new file mode 100644 index 0000000..6e7ee10 --- /dev/null +++ b/XMPFiles/Makefile.in @@ -0,0 +1,642 @@ +# Makefile.in generated by automake 1.15.1 from Makefile.am. +# @configure_input@ + +# Copyright (C) 1994-2017 Free Software Foundation, Inc. + +# This Makefile.in is free software; the Free Software Foundation +# gives unlimited permission to copy and/or distribute it, +# with or without modifications, as long as this notice is preserved. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY, to the extent permitted by law; without +# even the implied warranty of MERCHANTABILITY or FITNESS FOR A +# PARTICULAR PURPOSE. + +@SET_MAKE@ +VPATH = @srcdir@ +am__is_gnu_make = { \ + if test -z '$(MAKELEVEL)'; then \ + false; \ + elif test -n '$(MAKE_HOST)'; then \ + true; \ + elif test -n '$(MAKE_VERSION)' && test -n '$(CURDIR)'; then \ + true; \ + else \ + false; \ + fi; \ +} +am__make_running_with_option = \ + case $${target_option-} in \ + ?) ;; \ + *) echo "am__make_running_with_option: internal error: invalid" \ + "target option '$${target_option-}' specified" >&2; \ + exit 1;; \ + esac; \ + has_opt=no; \ + sane_makeflags=$$MAKEFLAGS; \ + if $(am__is_gnu_make); then \ + sane_makeflags=$$MFLAGS; \ + else \ + case $$MAKEFLAGS in \ + *\\[\ \ ]*) \ + bs=\\; \ + sane_makeflags=`printf '%s\n' "$$MAKEFLAGS" \ + | sed "s/$$bs$$bs[$$bs $$bs ]*//g"`;; \ + esac; \ + fi; \ + skip_next=no; \ + strip_trailopt () \ + { \ + flg=`printf '%s\n' "$$flg" | sed "s/$$1.*$$//"`; \ + }; \ + for flg in $$sane_makeflags; do \ + test $$skip_next = yes && { skip_next=no; continue; }; \ + case $$flg in \ + *=*|--*) continue;; \ + -*I) strip_trailopt 'I'; skip_next=yes;; \ + -*I?*) strip_trailopt 'I';; \ + -*O) strip_trailopt 'O'; skip_next=yes;; \ + -*O?*) strip_trailopt 'O';; \ + -*l) strip_trailopt 'l'; skip_next=yes;; \ + -*l?*) strip_trailopt 'l';; \ + -[dEDm]) skip_next=yes;; \ + -[JT]) skip_next=yes;; \ + esac; \ + case $$flg in \ + *$$target_option*) has_opt=yes; break;; \ + esac; \ + done; \ + test $$has_opt = yes +am__make_dryrun = (target_option=n; $(am__make_running_with_option)) +am__make_keepgoing = (target_option=k; $(am__make_running_with_option)) +pkgdatadir = $(datadir)/@PACKAGE@ +pkgincludedir = $(includedir)/@PACKAGE@ +pkglibdir = $(libdir)/@PACKAGE@ +pkglibexecdir = $(libexecdir)/@PACKAGE@ +am__cd = CDPATH="$${ZSH_VERSION+.}$(PATH_SEPARATOR)" && cd +install_sh_DATA = $(install_sh) -c -m 644 +install_sh_PROGRAM = $(install_sh) -c +install_sh_SCRIPT = $(install_sh) -c +INSTALL_HEADER = $(INSTALL_DATA) +transform = $(program_transform_name) +NORMAL_INSTALL = : +PRE_INSTALL = : +POST_INSTALL = : +NORMAL_UNINSTALL = : +PRE_UNINSTALL = : +POST_UNINSTALL = : +build_triplet = @build@ +host_triplet = @host@ +subdir = XMPFiles +ACLOCAL_M4 = $(top_srcdir)/aclocal.m4 +am__aclocal_m4_deps = $(top_srcdir)/m4/ax_cflags_gcc_option.m4 \ + $(top_srcdir)/m4/ax_cxx_compile_stdcxx_11.m4 \ + $(top_srcdir)/m4/ax_ld_check_flag.m4 \ + $(top_srcdir)/m4/ax_tls.m4 $(top_srcdir)/m4/boost.m4 \ + $(top_srcdir)/m4/libtool.m4 $(top_srcdir)/m4/ltoptions.m4 \ + $(top_srcdir)/m4/ltsugar.m4 $(top_srcdir)/m4/ltversion.m4 \ + $(top_srcdir)/m4/lt~obsolete.m4 $(top_srcdir)/configure.ac +am__configure_deps = $(am__aclocal_m4_deps) $(CONFIGURE_DEPENDENCIES) \ + $(ACLOCAL_M4) +DIST_COMMON = $(srcdir)/Makefile.am $(am__DIST_COMMON) +mkinstalldirs = $(install_sh) -d +CONFIG_CLEAN_FILES = +CONFIG_CLEAN_VPATH_FILES = +AM_V_P = $(am__v_P_@AM_V@) +am__v_P_ = $(am__v_P_@AM_DEFAULT_V@) +am__v_P_0 = false +am__v_P_1 = : +AM_V_GEN = $(am__v_GEN_@AM_V@) +am__v_GEN_ = $(am__v_GEN_@AM_DEFAULT_V@) +am__v_GEN_0 = @echo " GEN " $@; +am__v_GEN_1 = +AM_V_at = $(am__v_at_@AM_V@) +am__v_at_ = $(am__v_at_@AM_DEFAULT_V@) +am__v_at_0 = @ +am__v_at_1 = +SOURCES = +DIST_SOURCES = +RECURSIVE_TARGETS = all-recursive check-recursive cscopelist-recursive \ + ctags-recursive dvi-recursive html-recursive info-recursive \ + install-data-recursive install-dvi-recursive \ + install-exec-recursive install-html-recursive \ + install-info-recursive install-pdf-recursive \ + install-ps-recursive install-recursive installcheck-recursive \ + installdirs-recursive pdf-recursive ps-recursive \ + tags-recursive uninstall-recursive +am__can_run_installinfo = \ + case $$AM_UPDATE_INFO_DIR in \ + n|no|NO) false;; \ + *) (install-info --version) >/dev/null 2>&1;; \ + esac +RECURSIVE_CLEAN_TARGETS = mostlyclean-recursive clean-recursive \ + distclean-recursive maintainer-clean-recursive +am__recursive_targets = \ + $(RECURSIVE_TARGETS) \ + $(RECURSIVE_CLEAN_TARGETS) \ + $(am__extra_recursive_targets) +AM_RECURSIVE_TARGETS = $(am__recursive_targets:-recursive=) TAGS CTAGS \ + distdir +am__tagged_files = $(HEADERS) $(SOURCES) $(TAGS_FILES) $(LISP) +# Read a list of newline-separated strings from the standard input, +# and print each of them once, without duplicates. Input order is +# *not* preserved. +am__uniquify_input = $(AWK) '\ + BEGIN { nonempty = 0; } \ + { items[$$0] = 1; nonempty = 1; } \ + END { if (nonempty) { for (i in items) print i; }; } \ +' +# Make sure the list of sources is unique. This is necessary because, +# e.g., the same source file might be shared among _SOURCES variables +# for different programs/libraries. +am__define_uniq_tagged_files = \ + list='$(am__tagged_files)'; \ + unique=`for i in $$list; do \ + if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \ + done | $(am__uniquify_input)` +ETAGS = etags +CTAGS = ctags +DIST_SUBDIRS = $(SUBDIRS) +am__DIST_COMMON = $(srcdir)/Makefile.in +DISTFILES = $(DIST_COMMON) $(DIST_SOURCES) $(TEXINFOS) $(EXTRA_DIST) +am__relativize = \ + dir0=`pwd`; \ + sed_first='s,^\([^/]*\)/.*$$,\1,'; \ + sed_rest='s,^[^/]*/*,,'; \ + sed_last='s,^.*/\([^/]*\)$$,\1,'; \ + sed_butlast='s,/*[^/]*$$,,'; \ + while test -n "$$dir1"; do \ + first=`echo "$$dir1" | sed -e "$$sed_first"`; \ + if test "$$first" != "."; then \ + if test "$$first" = ".."; then \ + dir2=`echo "$$dir0" | sed -e "$$sed_last"`/"$$dir2"; \ + dir0=`echo "$$dir0" | sed -e "$$sed_butlast"`; \ + else \ + first2=`echo "$$dir2" | sed -e "$$sed_first"`; \ + if test "$$first2" = "$$first"; then \ + dir2=`echo "$$dir2" | sed -e "$$sed_rest"`; \ + else \ + dir2="../$$dir2"; \ + fi; \ + dir0="$$dir0"/"$$first"; \ + fi; \ + fi; \ + dir1=`echo "$$dir1" | sed -e "$$sed_rest"`; \ + done; \ + reldir="$$dir2" +ACLOCAL = @ACLOCAL@ +AMTAR = @AMTAR@ +AM_DEFAULT_VERBOSITY = @AM_DEFAULT_VERBOSITY@ +AR = @AR@ +AUTOCONF = @AUTOCONF@ +AUTOHEADER = @AUTOHEADER@ +AUTOMAKE = @AUTOMAKE@ +AWK = @AWK@ +BOOST_CPPFLAGS = @BOOST_CPPFLAGS@ +BOOST_LDPATH = @BOOST_LDPATH@ +BOOST_ROOT = @BOOST_ROOT@ +BOOST_UNIT_TEST_FRAMEWORK_LDFLAGS = @BOOST_UNIT_TEST_FRAMEWORK_LDFLAGS@ +BOOST_UNIT_TEST_FRAMEWORK_LDPATH = @BOOST_UNIT_TEST_FRAMEWORK_LDPATH@ +BOOST_UNIT_TEST_FRAMEWORK_LIBS = @BOOST_UNIT_TEST_FRAMEWORK_LIBS@ +CC = @CC@ +CCDEPMODE = @CCDEPMODE@ +CFLAGS = @CFLAGS@ +CPP = @CPP@ +CPPFLAGS = @CPPFLAGS@ +CXX = @CXX@ +CXXCPP = @CXXCPP@ +CXXDEPMODE = @CXXDEPMODE@ +CXXFLAGS = @CXXFLAGS@ +CYGPATH_W = @CYGPATH_W@ +DEFS = @DEFS@ +DEPDIR = @DEPDIR@ +DISTCHECK_CONFIGURE_FLAGS = @DISTCHECK_CONFIGURE_FLAGS@ +DLLTOOL = @DLLTOOL@ +DSYMUTIL = @DSYMUTIL@ +DUMPBIN = @DUMPBIN@ +ECHO_C = @ECHO_C@ +ECHO_N = @ECHO_N@ +ECHO_T = @ECHO_T@ +EGREP = @EGREP@ +EXEEXT = @EXEEXT@ +EXEMPI_AGE = @EXEMPI_AGE@ +EXEMPI_CURRENT = @EXEMPI_CURRENT@ +EXEMPI_CURRENT_MIN = @EXEMPI_CURRENT_MIN@ +EXEMPI_INCLUDE_BASE = @EXEMPI_INCLUDE_BASE@ +EXEMPI_MAJOR_VERSION = @EXEMPI_MAJOR_VERSION@ +EXEMPI_PLATFORM_DEF = @EXEMPI_PLATFORM_DEF@ +EXEMPI_REVISION = @EXEMPI_REVISION@ +EXEMPI_VERSION_INFO = @EXEMPI_VERSION_INFO@ +FGREP = @FGREP@ +GREP = @GREP@ +HAVE_CXX11 = @HAVE_CXX11@ +INSTALL = @INSTALL@ +INSTALL_DATA = @INSTALL_DATA@ +INSTALL_PROGRAM = @INSTALL_PROGRAM@ +INSTALL_SCRIPT = @INSTALL_SCRIPT@ +INSTALL_STRIP_PROGRAM = @INSTALL_STRIP_PROGRAM@ +LD = @LD@ +LDFLAGS = @LDFLAGS@ +LIBOBJS = @LIBOBJS@ +LIBS = @LIBS@ +LIBTOOL = @LIBTOOL@ +LIPO = @LIPO@ +LN_S = @LN_S@ +LTLIBOBJS = @LTLIBOBJS@ +LT_SYS_LIBRARY_PATH = @LT_SYS_LIBRARY_PATH@ +MAINT = @MAINT@ +MAKEINFO = @MAKEINFO@ +MANIFEST_TOOL = @MANIFEST_TOOL@ +MKDIR_P = @MKDIR_P@ +NM = @NM@ +NMEDIT = @NMEDIT@ +OBJDUMP = @OBJDUMP@ +OBJEXT = @OBJEXT@ +OTOOL = @OTOOL@ +OTOOL64 = @OTOOL64@ +PACKAGE = @PACKAGE@ +PACKAGE_BUGREPORT = @PACKAGE_BUGREPORT@ +PACKAGE_NAME = @PACKAGE_NAME@ +PACKAGE_STRING = @PACKAGE_STRING@ +PACKAGE_TARNAME = @PACKAGE_TARNAME@ +PACKAGE_URL = @PACKAGE_URL@ +PACKAGE_VERSION = @PACKAGE_VERSION@ +PATH_SEPARATOR = @PATH_SEPARATOR@ +RANLIB = @RANLIB@ +SED = @SED@ +SET_MAKE = @SET_MAKE@ +SHELL = @SHELL@ +STRIP = @STRIP@ +VALGRIND = @VALGRIND@ +VERSION = @VERSION@ +XMPCORE_CPPFLAGS = @XMPCORE_CPPFLAGS@ +abs_builddir = @abs_builddir@ +abs_srcdir = @abs_srcdir@ +abs_top_builddir = @abs_top_builddir@ +abs_top_srcdir = @abs_top_srcdir@ +ac_ct_AR = @ac_ct_AR@ +ac_ct_CC = @ac_ct_CC@ +ac_ct_CXX = @ac_ct_CXX@ +ac_ct_DUMPBIN = @ac_ct_DUMPBIN@ +am__include = @am__include@ +am__leading_dot = @am__leading_dot@ +am__quote = @am__quote@ +am__tar = @am__tar@ +am__untar = @am__untar@ +bindir = @bindir@ +build = @build@ +build_alias = @build_alias@ +build_cpu = @build_cpu@ +build_os = @build_os@ +build_vendor = @build_vendor@ +builddir = @builddir@ +datadir = @datadir@ +datarootdir = @datarootdir@ +docdir = @docdir@ +dvidir = @dvidir@ +exec_prefix = @exec_prefix@ +host = @host@ +host_alias = @host_alias@ +host_cpu = @host_cpu@ +host_os = @host_os@ +host_vendor = @host_vendor@ +htmldir = @htmldir@ +includedir = @includedir@ +infodir = @infodir@ +install_sh = @install_sh@ +libdir = @libdir@ +libexecdir = @libexecdir@ +localedir = @localedir@ +localstatedir = @localstatedir@ +mandir = @mandir@ +mkdir_p = @mkdir_p@ +oldincludedir = @oldincludedir@ +pdfdir = @pdfdir@ +pkgconfigdir = @pkgconfigdir@ +prefix = @prefix@ +program_transform_name = @program_transform_name@ +psdir = @psdir@ +sbindir = @sbindir@ +sharedstatedir = @sharedstatedir@ +srcdir = @srcdir@ +sysconfdir = @sysconfdir@ +target_alias = @target_alias@ +top_build_prefix = @top_build_prefix@ +top_builddir = @top_builddir@ +top_srcdir = @top_srcdir@ +SUBDIRS = source +all: all-recursive + +.SUFFIXES: +$(srcdir)/Makefile.in: @MAINTAINER_MODE_TRUE@ $(srcdir)/Makefile.am $(am__configure_deps) + @for dep in $?; do \ + case '$(am__configure_deps)' in \ + *$$dep*) \ + ( cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh ) \ + && { if test -f $@; then exit 0; else break; fi; }; \ + exit 1;; \ + esac; \ + done; \ + echo ' cd $(top_srcdir) && $(AUTOMAKE) --foreign XMPFiles/Makefile'; \ + $(am__cd) $(top_srcdir) && \ + $(AUTOMAKE) --foreign XMPFiles/Makefile +Makefile: $(srcdir)/Makefile.in $(top_builddir)/config.status + @case '$?' in \ + *config.status*) \ + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh;; \ + *) \ + echo ' cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__depfiles_maybe)'; \ + cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__depfiles_maybe);; \ + esac; + +$(top_builddir)/config.status: $(top_srcdir)/configure $(CONFIG_STATUS_DEPENDENCIES) + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh + +$(top_srcdir)/configure: @MAINTAINER_MODE_TRUE@ $(am__configure_deps) + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh +$(ACLOCAL_M4): @MAINTAINER_MODE_TRUE@ $(am__aclocal_m4_deps) + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh +$(am__aclocal_m4_deps): + +mostlyclean-libtool: + -rm -f *.lo + +clean-libtool: + -rm -rf .libs _libs + +# This directory's subdirectories are mostly independent; you can cd +# into them and run 'make' without going through this Makefile. +# To change the values of 'make' variables: instead of editing Makefiles, +# (1) if the variable is set in 'config.status', edit 'config.status' +# (which will cause the Makefiles to be regenerated when you run 'make'); +# (2) otherwise, pass the desired values on the 'make' command line. +$(am__recursive_targets): + @fail=; \ + if $(am__make_keepgoing); then \ + failcom='fail=yes'; \ + else \ + failcom='exit 1'; \ + fi; \ + dot_seen=no; \ + target=`echo $@ | sed s/-recursive//`; \ + case "$@" in \ + distclean-* | maintainer-clean-*) list='$(DIST_SUBDIRS)' ;; \ + *) list='$(SUBDIRS)' ;; \ + esac; \ + for subdir in $$list; do \ + echo "Making $$target in $$subdir"; \ + if test "$$subdir" = "."; then \ + dot_seen=yes; \ + local_target="$$target-am"; \ + else \ + local_target="$$target"; \ + fi; \ + ($(am__cd) $$subdir && $(MAKE) $(AM_MAKEFLAGS) $$local_target) \ + || eval $$failcom; \ + done; \ + if test "$$dot_seen" = "no"; then \ + $(MAKE) $(AM_MAKEFLAGS) "$$target-am" || exit 1; \ + fi; test -z "$$fail" + +ID: $(am__tagged_files) + $(am__define_uniq_tagged_files); mkid -fID $$unique +tags: tags-recursive +TAGS: tags + +tags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files) + set x; \ + here=`pwd`; \ + if ($(ETAGS) --etags-include --version) >/dev/null 2>&1; then \ + include_option=--etags-include; \ + empty_fix=.; \ + else \ + include_option=--include; \ + empty_fix=; \ + fi; \ + list='$(SUBDIRS)'; for subdir in $$list; do \ + if test "$$subdir" = .; then :; else \ + test ! -f $$subdir/TAGS || \ + set "$$@" "$$include_option=$$here/$$subdir/TAGS"; \ + fi; \ + done; \ + $(am__define_uniq_tagged_files); \ + shift; \ + if test -z "$(ETAGS_ARGS)$$*$$unique"; then :; else \ + test -n "$$unique" || unique=$$empty_fix; \ + if test $$# -gt 0; then \ + $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \ + "$$@" $$unique; \ + else \ + $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \ + $$unique; \ + fi; \ + fi +ctags: ctags-recursive + +CTAGS: ctags +ctags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files) + $(am__define_uniq_tagged_files); \ + test -z "$(CTAGS_ARGS)$$unique" \ + || $(CTAGS) $(CTAGSFLAGS) $(AM_CTAGSFLAGS) $(CTAGS_ARGS) \ + $$unique + +GTAGS: + here=`$(am__cd) $(top_builddir) && pwd` \ + && $(am__cd) $(top_srcdir) \ + && gtags -i $(GTAGS_ARGS) "$$here" +cscopelist: cscopelist-recursive + +cscopelist-am: $(am__tagged_files) + list='$(am__tagged_files)'; \ + case "$(srcdir)" in \ + [\\/]* | ?:[\\/]*) sdir="$(srcdir)" ;; \ + *) sdir=$(subdir)/$(srcdir) ;; \ + esac; \ + for i in $$list; do \ + if test -f "$$i"; then \ + echo "$(subdir)/$$i"; \ + else \ + echo "$$sdir/$$i"; \ + fi; \ + done >> $(top_builddir)/cscope.files + +distclean-tags: + -rm -f TAGS ID GTAGS GRTAGS GSYMS GPATH tags + +distdir: $(DISTFILES) + @srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \ + topsrcdirstrip=`echo "$(top_srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \ + list='$(DISTFILES)'; \ + dist_files=`for file in $$list; do echo $$file; done | \ + sed -e "s|^$$srcdirstrip/||;t" \ + -e "s|^$$topsrcdirstrip/|$(top_builddir)/|;t"`; \ + case $$dist_files in \ + */*) $(MKDIR_P) `echo "$$dist_files" | \ + sed '/\//!d;s|^|$(distdir)/|;s,/[^/]*$$,,' | \ + sort -u` ;; \ + esac; \ + for file in $$dist_files; do \ + if test -f $$file || test -d $$file; then d=.; else d=$(srcdir); fi; \ + if test -d $$d/$$file; then \ + dir=`echo "/$$file" | sed -e 's,/[^/]*$$,,'`; \ + if test -d "$(distdir)/$$file"; then \ + find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \ + fi; \ + if test -d $(srcdir)/$$file && test $$d != $(srcdir); then \ + cp -fpR $(srcdir)/$$file "$(distdir)$$dir" || exit 1; \ + find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \ + fi; \ + cp -fpR $$d/$$file "$(distdir)$$dir" || exit 1; \ + else \ + test -f "$(distdir)/$$file" \ + || cp -p $$d/$$file "$(distdir)/$$file" \ + || exit 1; \ + fi; \ + done + @list='$(DIST_SUBDIRS)'; for subdir in $$list; do \ + if test "$$subdir" = .; then :; else \ + $(am__make_dryrun) \ + || test -d "$(distdir)/$$subdir" \ + || $(MKDIR_P) "$(distdir)/$$subdir" \ + || exit 1; \ + dir1=$$subdir; dir2="$(distdir)/$$subdir"; \ + $(am__relativize); \ + new_distdir=$$reldir; \ + dir1=$$subdir; dir2="$(top_distdir)"; \ + $(am__relativize); \ + new_top_distdir=$$reldir; \ + echo " (cd $$subdir && $(MAKE) $(AM_MAKEFLAGS) top_distdir="$$new_top_distdir" distdir="$$new_distdir" \\"; \ + echo " am__remove_distdir=: am__skip_length_check=: am__skip_mode_fix=: distdir)"; \ + ($(am__cd) $$subdir && \ + $(MAKE) $(AM_MAKEFLAGS) \ + top_distdir="$$new_top_distdir" \ + distdir="$$new_distdir" \ + am__remove_distdir=: \ + am__skip_length_check=: \ + am__skip_mode_fix=: \ + distdir) \ + || exit 1; \ + fi; \ + done +check-am: all-am +check: check-recursive +all-am: Makefile +installdirs: installdirs-recursive +installdirs-am: +install: install-recursive +install-exec: install-exec-recursive +install-data: install-data-recursive +uninstall: uninstall-recursive + +install-am: all-am + @$(MAKE) $(AM_MAKEFLAGS) install-exec-am install-data-am + +installcheck: installcheck-recursive +install-strip: + if test -z '$(STRIP)'; then \ + $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \ + install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \ + install; \ + else \ + $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \ + install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \ + "INSTALL_PROGRAM_ENV=STRIPPROG='$(STRIP)'" install; \ + fi +mostlyclean-generic: + +clean-generic: + +distclean-generic: + -test -z "$(CONFIG_CLEAN_FILES)" || rm -f $(CONFIG_CLEAN_FILES) + -test . = "$(srcdir)" || test -z "$(CONFIG_CLEAN_VPATH_FILES)" || rm -f $(CONFIG_CLEAN_VPATH_FILES) + +maintainer-clean-generic: + @echo "This command is intended for maintainers to use" + @echo "it deletes files that may require special tools to rebuild." +clean: clean-recursive + +clean-am: clean-generic clean-libtool mostlyclean-am + +distclean: distclean-recursive + -rm -f Makefile +distclean-am: clean-am distclean-generic distclean-tags + +dvi: dvi-recursive + +dvi-am: + +html: html-recursive + +html-am: + +info: info-recursive + +info-am: + +install-data-am: + +install-dvi: install-dvi-recursive + +install-dvi-am: + +install-exec-am: + +install-html: install-html-recursive + +install-html-am: + +install-info: install-info-recursive + +install-info-am: + +install-man: + +install-pdf: install-pdf-recursive + +install-pdf-am: + +install-ps: install-ps-recursive + +install-ps-am: + +installcheck-am: + +maintainer-clean: maintainer-clean-recursive + -rm -f Makefile +maintainer-clean-am: distclean-am maintainer-clean-generic + +mostlyclean: mostlyclean-recursive + +mostlyclean-am: mostlyclean-generic mostlyclean-libtool + +pdf: pdf-recursive + +pdf-am: + +ps: ps-recursive + +ps-am: + +uninstall-am: + +.MAKE: $(am__recursive_targets) install-am install-strip + +.PHONY: $(am__recursive_targets) CTAGS GTAGS TAGS all all-am check \ + check-am clean clean-generic clean-libtool cscopelist-am ctags \ + ctags-am distclean distclean-generic distclean-libtool \ + distclean-tags distdir dvi dvi-am html html-am info info-am \ + install install-am install-data install-data-am install-dvi \ + install-dvi-am install-exec install-exec-am install-html \ + install-html-am install-info install-info-am install-man \ + install-pdf install-pdf-am install-ps install-ps-am \ + install-strip installcheck installcheck-am installdirs \ + installdirs-am maintainer-clean maintainer-clean-generic \ + mostlyclean mostlyclean-generic mostlyclean-libtool pdf pdf-am \ + ps ps-am tags tags-am uninstall uninstall-am + +.PRECIOUS: Makefile + + +# Tell versions [3.59,3.63) of GNU make to not export all variables. +# Otherwise a system limit (for SysV at least) may be exceeded. +.NOEXPORT: diff --git a/XMPFiles/source/FileHandlers/AIFF_Handler.cpp b/XMPFiles/source/FileHandlers/AIFF_Handler.cpp new file mode 100644 index 0000000..6331a0b --- /dev/null +++ b/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 ) + : mChunkController(NULL), mChunkBehavior(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/XMPFiles/source/FileHandlers/AIFF_Handler.hpp b/XMPFiles/source/FileHandlers/AIFF_Handler.hpp new file mode 100644 index 0000000..1ccb6a9 --- /dev/null +++ b/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/XMPFiles/source/FileHandlers/ASF_Handler.cpp b/XMPFiles/source/FileHandlers/ASF_Handler.cpp new file mode 100644 index 0000000..14bf225 --- /dev/null +++ b/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/XMPFiles/source/FileHandlers/ASF_Handler.hpp b/XMPFiles/source/FileHandlers/ASF_Handler.hpp new file mode 100644 index 0000000..f8b883e --- /dev/null +++ b/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/XMPFiles/source/FileHandlers/AVCHD_Handler.cpp b/XMPFiles/source/FileHandlers/AVCHD_Handler.cpp new file mode 100644 index 0000000..95d4f1b --- /dev/null +++ b/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 "third-party/zuid/interfaces/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/XMPFiles/source/FileHandlers/AVCHD_Handler.hpp b/XMPFiles/source/FileHandlers/AVCHD_Handler.hpp new file mode 100644 index 0000000..0860d5b --- /dev/null +++ b/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/XMPFiles/source/FileHandlers/Basic_Handler.cpp b/XMPFiles/source/FileHandlers/Basic_Handler.cpp new file mode 100644 index 0000000..795dbd7 --- /dev/null +++ b/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/XMPFiles/source/FileHandlers/Basic_Handler.hpp b/XMPFiles/source/FileHandlers/Basic_Handler.hpp new file mode 100644 index 0000000..d5fca0f --- /dev/null +++ b/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/XMPFiles/source/FileHandlers/FLV_Handler.cpp b/XMPFiles/source/FileHandlers/FLV_Handler.cpp new file mode 100644 index 0000000..db6ae4b --- /dev/null +++ b/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 "third-party/zuid/interfaces/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/XMPFiles/source/FileHandlers/FLV_Handler.hpp b/XMPFiles/source/FileHandlers/FLV_Handler.hpp new file mode 100644 index 0000000..fe129de --- /dev/null +++ b/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/XMPFiles/source/FileHandlers/GIF_Handler.cpp b/XMPFiles/source/FileHandlers/GIF_Handler.cpp new file mode 100644 index 0000000..39090ea --- /dev/null +++ b/XMPFiles/source/FileHandlers/GIF_Handler.cpp @@ -0,0 +1,264 @@ +// ================================================================================================= +// ADOBE SYSTEMS INCORPORATED +// Copyright 2002-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. +// +// Derived from PNG_Handler.cpp by Ian Jacobi +// ================================================================================================= + +#include "GIF_Handler.hpp" + +#include "source/XIO.hpp" +#include "XMPFiles/source/FormatSupport/GIF_Support.hpp" + +using namespace std; + +// ================================================================================================= +/// \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 + +// ================================================================================================= +// 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 ); + + IOBuffer ioBuf; + + fileRef->Rewind(); + if ( ! CheckFileSpace ( fileRef, &ioBuf, GIF_SIGNATURE_LEN ) ) return false; // We need at least 3, so the buffer is not filled. + + if ( ! CheckBytes ( ioBuf.ptr, GIF_SIGNATURE_DATA, GIF_SIGNATURE_LEN ) ) return false; + + return true; + +} // GIF_CheckFormat + +// ================================================================================================= +// GIF_MetaHandler::GIF_MetaHandler +// ================================== + +GIF_MetaHandler::GIF_MetaHandler ( XMPFiles * _parent ) +{ + 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 ); + if ( fileRef == 0) return; + + // We try to navigate through the blocks to find the XMP block. + GIF_Support::BlockState blockState; + long numBlocks = GIF_Support::OpenGIF ( fileRef, blockState ); + if ( numBlocks == 0 ) return; + + if (blockState.xmpLen != 0) + { + // XMP present + + this->xmpPacket.reserve(blockState.xmpLen); + this->xmpPacket.assign(blockState.xmpLen, ' '); + + if (GIF_Support::ReadBuffer ( fileRef, blockState.xmpPos, blockState.xmpLen, const_cast(this->xmpPacket.data()) )) + { + this->packetInfo.offset = blockState.xmpPos; + this->packetInfo.length = blockState.xmpLen; + this->containsXMP = true; + } + } + else + { + // no XMP + } + +} // GIF_MetaHandler::CacheFileData + +// ================================================================================================= +// GIF_MetaHandler::ProcessTNail +// ============================== + +void GIF_MetaHandler::ProcessTNail() +{ + + XMP_Throw ( "GIF_MetaHandler::ProcessTNail isn't implemented yet", kXMPErr_Unimplemented ); + +} // GIF_MetaHandler::ProcessTNail + +// ================================================================================================= +// 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 = this->xmpPacket.size(); + + this->xmpObj.ParseFromBuffer ( packetStr, packetLen ); + + this->containsXMP = true; + + } + +} // GIF_MetaHandler::ProcessXMP + +// ================================================================================================= +// GIF_MetaHandler::UpdateFile +// ============================ + +void GIF_MetaHandler::UpdateFile ( bool doSafeUpdate ) +{ + bool updated = false; + + if ( ! this->needsUpdate ) return; + if ( doSafeUpdate ) XMP_Throw ( "GIF_MetaHandler::UpdateFile: Safe update not supported", kXMPErr_Unavailable ); + + XMP_StringPtr packetStr = xmpPacket.c_str(); + XMP_StringLen packetLen = xmpPacket.size(); + if ( packetLen == 0 ) return; + + XMP_IO* fileRef(this->parent->ioRef); + if ( fileRef == 0 ) return; + + GIF_Support::BlockState blockState; + long numBlocks = GIF_Support::OpenGIF ( fileRef, blockState ); + if ( numBlocks == 0 ) return; + + // write/update block(s) + if (blockState.xmpLen == 0) + { + // no current chunk -> inject + updated = SafeWriteFile(); + } + else if (blockState.xmpLen >= packetLen ) + { + // current chunk size is sufficient -> write and update CRC (in place update) + updated = GIF_Support::WriteBuffer(fileRef, blockState.xmpPos, packetLen, packetStr ); + // GIF doesn't have a CRC like PNG. + } + else if (blockState.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; + +} // GIF_MetaHandler::UpdateFile + +// ================================================================================================= +// GIF_MetaHandler::WriteFile +// =========================== + +void GIF_MetaHandler::WriteTempFile ( XMP_IO* tempRef ) +{ + XMP_IO* originalRef = this->parent->ioRef; + + GIF_Support::BlockState blockState; + long numBlocks = GIF_Support::OpenGIF ( originalRef, blockState ); + if ( numBlocks == 0 ) return; + + tempRef->Truncate( 0 ); + // LFA_Write(destRef, GIF_SIGNATURE_DATA, GIF_SIGNATURE_LEN); + + GIF_Support::BlockIterator curPos = blockState.blocks.begin(); + GIF_Support::BlockIterator endPos = blockState.blocks.end(); + + long blockCount; + + for (blockCount = 0; (curPos != endPos); ++curPos, ++blockCount) + { + GIF_Support::BlockData block = *curPos; + + // discard existing XMP block + if (block.xmp) + continue; + + // copy any other block + GIF_Support::CopyBlock(originalRef, tempRef, block); + + // place XMP block immediately before trailer + if (blockCount == numBlocks - 2) + { + XMP_StringPtr packetStr = xmpPacket.c_str(); + XMP_StringLen packetLen = xmpPacket.size(); + + GIF_Support::WriteXMPBlock(tempRef, packetLen, packetStr ); + } + } + +} // GIF_MetaHandler::WriteFile + +// ================================================================================================= +// GIF_MetaHandler::SafeWriteFile +// =========================== + +bool GIF_MetaHandler::SafeWriteFile () +{ + bool ret = false; + + XMP_IO* originalFile = this->parent->ioRef; + XMP_IO* tempFile = originalFile->DeriveTemp(); + if ( tempFile == 0 ) + XMP_Throw ( "Failure creating GIF temp file", kXMPErr_InternalFailure ); + + this->WriteTempFile( tempFile ); + originalFile->AbsorbTemp(); + + return true; +} // GIF_MetaHandler::SafeWriteFile + +// ================================================================================================= diff --git a/XMPFiles/source/FileHandlers/GIF_Handler.hpp b/XMPFiles/source/FileHandlers/GIF_Handler.hpp new file mode 100644 index 0000000..40f7046 --- /dev/null +++ b/XMPFiles/source/FileHandlers/GIF_Handler.hpp @@ -0,0 +1,70 @@ +#ifndef __GIF_Handler_hpp__ +#define __GIF_Handler_hpp__ 1 + +// ================================================================================================= +// ADOBE SYSTEMS INCORPORATED +// Copyright 2002-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. +// +// Derived from PNG_Handler.hpp by Ian Jacobi +// ================================================================================================= + +#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/GIF_Support.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 ProcessTNail(); + void ProcessXMP(); + + void UpdateFile ( bool doSafeUpdate ); + void WriteTempFile ( XMP_IO* tempRef ); + + bool SafeWriteFile (); + + GIF_MetaHandler ( XMPFiles* parent ); + virtual ~GIF_MetaHandler(); + +}; // GIF_MetaHandler + +// ================================================================================================= + +#endif /* __GIF_Handler_hpp__ */ diff --git a/XMPFiles/source/FileHandlers/InDesign_Handler.cpp b/XMPFiles/source/FileHandlers/InDesign_Handler.cpp new file mode 100644 index 0000000..d627580 --- /dev/null +++ b/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/XMPFiles/source/FileHandlers/InDesign_Handler.hpp b/XMPFiles/source/FileHandlers/InDesign_Handler.hpp new file mode 100644 index 0000000..66d9f79 --- /dev/null +++ b/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/XMPFiles/source/FileHandlers/JPEG_Handler.cpp b/XMPFiles/source/FileHandlers/JPEG_Handler.cpp new file mode 100644 index 0000000..bdc5505 --- /dev/null +++ b/XMPFiles/source/FileHandlers/JPEG_Handler.cpp @@ -0,0 +1,987 @@ +// ================================================================================================= +// 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 "third-party/zuid/interfaces/MD5.h" + +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; + bool parseIPTC = (iptcDigestState != kDigestMatches) || (! readOnly); + if ( parseIPTC ) 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, kExtXMPPrefixLength ) ) { + 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/XMPFiles/source/FileHandlers/JPEG_Handler.hpp b/XMPFiles/source/FileHandlers/JPEG_Handler.hpp new file mode 100644 index 0000000..9e859c1 --- /dev/null +++ b/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/XMPFiles/source/FileHandlers/MP3_Handler.cpp b/XMPFiles/source/FileHandlers/MP3_Handler.cpp new file mode 100644 index 0000000..55211c9 --- /dev/null +++ b/XMPFiles/source/FileHandlers/MP3_Handler.cpp @@ -0,0 +1,729 @@ +// ================================================================================================= +// 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/XMPFiles_IO.hpp" +#include "source/XIO.hpp" + +#include "XMPFiles/source/FileHandlers/MP3_Handler.hpp" + +// ================================================================================================= +/// \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; + this->minorVersion = 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/XMPFiles/source/FileHandlers/MP3_Handler.hpp b/XMPFiles/source/FileHandlers/MP3_Handler.hpp new file mode 100644 index 0000000..05345c6 --- /dev/null +++ b/XMPFiles/source/FileHandlers/MP3_Handler.hpp @@ -0,0 +1,93 @@ +#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/XMPFiles/source/FileHandlers/MPEG2_Handler.cpp b/XMPFiles/source/FileHandlers/MPEG2_Handler.cpp new file mode 100644 index 0000000..abeb781 --- /dev/null +++ b/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/XMPFiles/source/FileHandlers/MPEG2_Handler.hpp b/XMPFiles/source/FileHandlers/MPEG2_Handler.hpp new file mode 100644 index 0000000..549781f --- /dev/null +++ b/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/XMPFiles/source/FileHandlers/MPEG4_Handler.cpp b/XMPFiles/source/FileHandlers/MPEG4_Handler.cpp new file mode 100644 index 0000000..905f19f --- /dev/null +++ b/XMPFiles/source/FileHandlers/MPEG4_Handler.cpp @@ -0,0 +1,3063 @@ +// ================================================================================================= +// 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 "third-party/zuid/interfaces/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 ', or 'qt ' 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 ) ) { + 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; + + // 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 uuidFound = (! 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 ( uuidFound ) break; // Exit the loop when both are found. + + } else if ( (! uuidFound) && (currBox.boxType == ISOMedia::k_uuid) && IsXMPUUID(fileRef,currBox.contentSize) ) { + + 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 + 16; // The 16 is for the UUID. + this->packetInfo.length = (XMP_Int32) (currBox.contentSize - 16); + + this->xmpPacket.assign ( this->packetInfo.length, ' ' ); + fileRef->ReadAll ( (void*)this->xmpPacket.data(), this->packetInfo.length ); + + this->xmpBoxPos = boxPos; + this->xmpBoxSize = (XMP_Uns32)fullUuidSize; + uuidFound = 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 && IsXMPUUID(originalFile,currBox.contentSize) ) { + + 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 + +// ================================================================================================= \ No newline at end of file diff --git a/XMPFiles/source/FileHandlers/MPEG4_Handler.hpp b/XMPFiles/source/FileHandlers/MPEG4_Handler.hpp new file mode 100644 index 0000000..9ad06bb --- /dev/null +++ b/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/XMPFiles/source/FileHandlers/Makefile.am b/XMPFiles/source/FileHandlers/Makefile.am new file mode 100644 index 0000000..44a3232 --- /dev/null +++ b/XMPFiles/source/FileHandlers/Makefile.am @@ -0,0 +1,73 @@ +# +# exempi - Makefile.am +# +# Copyright (C) 2007-2013 Hubert Figuiere +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions +# are met: +# +# 1 Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# +# 2 Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in the +# documentation and/or other materials provided with the +# distribution. +# +# 3 Neither the name of the Authors, nor the names of its +# contributors may be used to endorse or promote products derived +# from this software wit hout specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS +# FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE +# COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, +# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, +# STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED +# OF THE POSSIBILITY OF SUCH DAMAGE. +# + + + +noinst_LTLIBRARIES = libxmpfilehandlers.la + +AM_CPPFLAGS = -I$(top_srcdir) -I$(top_srcdir)/public/include \ + -Wall @XMPCORE_CPPFLAGS@ + + +libxmpfilehandlers_la_SOURCES = \ +AIFF_Handler.cpp AIFF_Handler.hpp\ +ASF_Handler.cpp ASF_Handler.hpp\ +AVCHD_Handler.cpp AVCHD_Handler.hpp\ +Basic_Handler.cpp Basic_Handler.hpp\ +FLV_Handler.cpp FLV_Handler.hpp\ +InDesign_Handler.cpp InDesign_Handler.hpp\ +JPEG_Handler.cpp JPEG_Handler.hpp\ +MP3_Handler.cpp MP3_Handler.hpp\ +MPEG2_Handler.cpp MPEG2_Handler.hpp \ +MPEG4_Handler.cpp MPEG4_Handler.hpp \ +P2_Handler.cpp P2_Handler.hpp\ +PNG_Handler.cpp PNG_Handler.hpp\ +PostScript_Handler.cpp PostScript_Handler.hpp\ +PSD_Handler.cpp PSD_Handler.hpp\ +RIFF_Handler.cpp RIFF_Handler.hpp \ +Scanner_Handler.cpp Scanner_Handler.hpp\ +SonyHDV_Handler.cpp SonyHDV_Handler.hpp\ +SWF_Handler.cpp SWF_Handler.hpp\ +TIFF_Handler.cpp TIFF_Handler.hpp\ +Trivial_Handler.cpp Trivial_Handler.hpp\ +UCF_Handler.cpp UCF_Handler.hpp\ +XDCAMEX_Handler.cpp XDCAMEX_Handler.hpp\ +XDCAM_Handler.cpp XDCAM_Handler.hpp\ +WAVE_Handler.cpp WAVE_Handler.hpp \ +GIF_Handler.cpp GIF_Handler.hpp \ +WEBP_Handler.cpp WEBP_Handler.hpp +$(NULL) + diff --git a/XMPFiles/source/FileHandlers/Makefile.in b/XMPFiles/source/FileHandlers/Makefile.in new file mode 100644 index 0000000..c1d32a5 --- /dev/null +++ b/XMPFiles/source/FileHandlers/Makefile.in @@ -0,0 +1,730 @@ +# Makefile.in generated by automake 1.15.1 from Makefile.am. +# @configure_input@ + +# Copyright (C) 1994-2017 Free Software Foundation, Inc. + +# This Makefile.in is free software; the Free Software Foundation +# gives unlimited permission to copy and/or distribute it, +# with or without modifications, as long as this notice is preserved. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY, to the extent permitted by law; without +# even the implied warranty of MERCHANTABILITY or FITNESS FOR A +# PARTICULAR PURPOSE. + +@SET_MAKE@ + +# +# exempi - Makefile.am +# +# Copyright (C) 2007-2013 Hubert Figuiere +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions +# are met: +# +# 1 Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# +# 2 Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in the +# documentation and/or other materials provided with the +# distribution. +# +# 3 Neither the name of the Authors, nor the names of its +# contributors may be used to endorse or promote products derived +# from this software wit hout specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS +# FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE +# COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, +# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, +# STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED +# OF THE POSSIBILITY OF SUCH DAMAGE. +# + +VPATH = @srcdir@ +am__is_gnu_make = { \ + if test -z '$(MAKELEVEL)'; then \ + false; \ + elif test -n '$(MAKE_HOST)'; then \ + true; \ + elif test -n '$(MAKE_VERSION)' && test -n '$(CURDIR)'; then \ + true; \ + else \ + false; \ + fi; \ +} +am__make_running_with_option = \ + case $${target_option-} in \ + ?) ;; \ + *) echo "am__make_running_with_option: internal error: invalid" \ + "target option '$${target_option-}' specified" >&2; \ + exit 1;; \ + esac; \ + has_opt=no; \ + sane_makeflags=$$MAKEFLAGS; \ + if $(am__is_gnu_make); then \ + sane_makeflags=$$MFLAGS; \ + else \ + case $$MAKEFLAGS in \ + *\\[\ \ ]*) \ + bs=\\; \ + sane_makeflags=`printf '%s\n' "$$MAKEFLAGS" \ + | sed "s/$$bs$$bs[$$bs $$bs ]*//g"`;; \ + esac; \ + fi; \ + skip_next=no; \ + strip_trailopt () \ + { \ + flg=`printf '%s\n' "$$flg" | sed "s/$$1.*$$//"`; \ + }; \ + for flg in $$sane_makeflags; do \ + test $$skip_next = yes && { skip_next=no; continue; }; \ + case $$flg in \ + *=*|--*) continue;; \ + -*I) strip_trailopt 'I'; skip_next=yes;; \ + -*I?*) strip_trailopt 'I';; \ + -*O) strip_trailopt 'O'; skip_next=yes;; \ + -*O?*) strip_trailopt 'O';; \ + -*l) strip_trailopt 'l'; skip_next=yes;; \ + -*l?*) strip_trailopt 'l';; \ + -[dEDm]) skip_next=yes;; \ + -[JT]) skip_next=yes;; \ + esac; \ + case $$flg in \ + *$$target_option*) has_opt=yes; break;; \ + esac; \ + done; \ + test $$has_opt = yes +am__make_dryrun = (target_option=n; $(am__make_running_with_option)) +am__make_keepgoing = (target_option=k; $(am__make_running_with_option)) +pkgdatadir = $(datadir)/@PACKAGE@ +pkgincludedir = $(includedir)/@PACKAGE@ +pkglibdir = $(libdir)/@PACKAGE@ +pkglibexecdir = $(libexecdir)/@PACKAGE@ +am__cd = CDPATH="$${ZSH_VERSION+.}$(PATH_SEPARATOR)" && cd +install_sh_DATA = $(install_sh) -c -m 644 +install_sh_PROGRAM = $(install_sh) -c +install_sh_SCRIPT = $(install_sh) -c +INSTALL_HEADER = $(INSTALL_DATA) +transform = $(program_transform_name) +NORMAL_INSTALL = : +PRE_INSTALL = : +POST_INSTALL = : +NORMAL_UNINSTALL = : +PRE_UNINSTALL = : +POST_UNINSTALL = : +build_triplet = @build@ +host_triplet = @host@ +subdir = XMPFiles/source/FileHandlers +ACLOCAL_M4 = $(top_srcdir)/aclocal.m4 +am__aclocal_m4_deps = $(top_srcdir)/m4/ax_cflags_gcc_option.m4 \ + $(top_srcdir)/m4/ax_cxx_compile_stdcxx_11.m4 \ + $(top_srcdir)/m4/ax_ld_check_flag.m4 \ + $(top_srcdir)/m4/ax_tls.m4 $(top_srcdir)/m4/boost.m4 \ + $(top_srcdir)/m4/libtool.m4 $(top_srcdir)/m4/ltoptions.m4 \ + $(top_srcdir)/m4/ltsugar.m4 $(top_srcdir)/m4/ltversion.m4 \ + $(top_srcdir)/m4/lt~obsolete.m4 $(top_srcdir)/configure.ac +am__configure_deps = $(am__aclocal_m4_deps) $(CONFIGURE_DEPENDENCIES) \ + $(ACLOCAL_M4) +DIST_COMMON = $(srcdir)/Makefile.am $(am__DIST_COMMON) +mkinstalldirs = $(install_sh) -d +CONFIG_CLEAN_FILES = +CONFIG_CLEAN_VPATH_FILES = +LTLIBRARIES = $(noinst_LTLIBRARIES) +libxmpfilehandlers_la_LIBADD = +am_libxmpfilehandlers_la_OBJECTS = AIFF_Handler.lo ASF_Handler.lo \ + AVCHD_Handler.lo Basic_Handler.lo FLV_Handler.lo \ + InDesign_Handler.lo JPEG_Handler.lo MP3_Handler.lo \ + MPEG2_Handler.lo MPEG4_Handler.lo P2_Handler.lo PNG_Handler.lo \ + PostScript_Handler.lo PSD_Handler.lo RIFF_Handler.lo \ + Scanner_Handler.lo SonyHDV_Handler.lo SWF_Handler.lo \ + TIFF_Handler.lo Trivial_Handler.lo UCF_Handler.lo \ + XDCAMEX_Handler.lo XDCAM_Handler.lo WAVE_Handler.lo \ + GIF_Handler.lo WEBP_Handler.lo +libxmpfilehandlers_la_OBJECTS = $(am_libxmpfilehandlers_la_OBJECTS) +AM_V_lt = $(am__v_lt_@AM_V@) +am__v_lt_ = $(am__v_lt_@AM_DEFAULT_V@) +am__v_lt_0 = --silent +am__v_lt_1 = +AM_V_P = $(am__v_P_@AM_V@) +am__v_P_ = $(am__v_P_@AM_DEFAULT_V@) +am__v_P_0 = false +am__v_P_1 = : +AM_V_GEN = $(am__v_GEN_@AM_V@) +am__v_GEN_ = $(am__v_GEN_@AM_DEFAULT_V@) +am__v_GEN_0 = @echo " GEN " $@; +am__v_GEN_1 = +AM_V_at = $(am__v_at_@AM_V@) +am__v_at_ = $(am__v_at_@AM_DEFAULT_V@) +am__v_at_0 = @ +am__v_at_1 = +DEFAULT_INCLUDES = -I.@am__isrc@ +depcomp = $(SHELL) $(top_srcdir)/depcomp +am__depfiles_maybe = depfiles +am__mv = mv -f +CXXCOMPILE = $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) \ + $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) +LTCXXCOMPILE = $(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) \ + $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) \ + $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) \ + $(AM_CXXFLAGS) $(CXXFLAGS) +AM_V_CXX = $(am__v_CXX_@AM_V@) +am__v_CXX_ = $(am__v_CXX_@AM_DEFAULT_V@) +am__v_CXX_0 = @echo " CXX " $@; +am__v_CXX_1 = +CXXLD = $(CXX) +CXXLINK = $(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) \ + $(LIBTOOLFLAGS) --mode=link $(CXXLD) $(AM_CXXFLAGS) \ + $(CXXFLAGS) $(AM_LDFLAGS) $(LDFLAGS) -o $@ +AM_V_CXXLD = $(am__v_CXXLD_@AM_V@) +am__v_CXXLD_ = $(am__v_CXXLD_@AM_DEFAULT_V@) +am__v_CXXLD_0 = @echo " CXXLD " $@; +am__v_CXXLD_1 = +COMPILE = $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) \ + $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) +LTCOMPILE = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \ + $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) \ + $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) \ + $(AM_CFLAGS) $(CFLAGS) +AM_V_CC = $(am__v_CC_@AM_V@) +am__v_CC_ = $(am__v_CC_@AM_DEFAULT_V@) +am__v_CC_0 = @echo " CC " $@; +am__v_CC_1 = +CCLD = $(CC) +LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \ + $(LIBTOOLFLAGS) --mode=link $(CCLD) $(AM_CFLAGS) $(CFLAGS) \ + $(AM_LDFLAGS) $(LDFLAGS) -o $@ +AM_V_CCLD = $(am__v_CCLD_@AM_V@) +am__v_CCLD_ = $(am__v_CCLD_@AM_DEFAULT_V@) +am__v_CCLD_0 = @echo " CCLD " $@; +am__v_CCLD_1 = +SOURCES = $(libxmpfilehandlers_la_SOURCES) +DIST_SOURCES = $(libxmpfilehandlers_la_SOURCES) +am__can_run_installinfo = \ + case $$AM_UPDATE_INFO_DIR in \ + n|no|NO) false;; \ + *) (install-info --version) >/dev/null 2>&1;; \ + esac +am__tagged_files = $(HEADERS) $(SOURCES) $(TAGS_FILES) $(LISP) +# Read a list of newline-separated strings from the standard input, +# and print each of them once, without duplicates. Input order is +# *not* preserved. +am__uniquify_input = $(AWK) '\ + BEGIN { nonempty = 0; } \ + { items[$$0] = 1; nonempty = 1; } \ + END { if (nonempty) { for (i in items) print i; }; } \ +' +# Make sure the list of sources is unique. This is necessary because, +# e.g., the same source file might be shared among _SOURCES variables +# for different programs/libraries. +am__define_uniq_tagged_files = \ + list='$(am__tagged_files)'; \ + unique=`for i in $$list; do \ + if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \ + done | $(am__uniquify_input)` +ETAGS = etags +CTAGS = ctags +am__DIST_COMMON = $(srcdir)/Makefile.in $(top_srcdir)/depcomp +DISTFILES = $(DIST_COMMON) $(DIST_SOURCES) $(TEXINFOS) $(EXTRA_DIST) +ACLOCAL = @ACLOCAL@ +AMTAR = @AMTAR@ +AM_DEFAULT_VERBOSITY = @AM_DEFAULT_VERBOSITY@ +AR = @AR@ +AUTOCONF = @AUTOCONF@ +AUTOHEADER = @AUTOHEADER@ +AUTOMAKE = @AUTOMAKE@ +AWK = @AWK@ +BOOST_CPPFLAGS = @BOOST_CPPFLAGS@ +BOOST_LDPATH = @BOOST_LDPATH@ +BOOST_ROOT = @BOOST_ROOT@ +BOOST_UNIT_TEST_FRAMEWORK_LDFLAGS = @BOOST_UNIT_TEST_FRAMEWORK_LDFLAGS@ +BOOST_UNIT_TEST_FRAMEWORK_LDPATH = @BOOST_UNIT_TEST_FRAMEWORK_LDPATH@ +BOOST_UNIT_TEST_FRAMEWORK_LIBS = @BOOST_UNIT_TEST_FRAMEWORK_LIBS@ +CC = @CC@ +CCDEPMODE = @CCDEPMODE@ +CFLAGS = @CFLAGS@ +CPP = @CPP@ +CPPFLAGS = @CPPFLAGS@ +CXX = @CXX@ +CXXCPP = @CXXCPP@ +CXXDEPMODE = @CXXDEPMODE@ +CXXFLAGS = @CXXFLAGS@ +CYGPATH_W = @CYGPATH_W@ +DEFS = @DEFS@ +DEPDIR = @DEPDIR@ +DISTCHECK_CONFIGURE_FLAGS = @DISTCHECK_CONFIGURE_FLAGS@ +DLLTOOL = @DLLTOOL@ +DSYMUTIL = @DSYMUTIL@ +DUMPBIN = @DUMPBIN@ +ECHO_C = @ECHO_C@ +ECHO_N = @ECHO_N@ +ECHO_T = @ECHO_T@ +EGREP = @EGREP@ +EXEEXT = @EXEEXT@ +EXEMPI_AGE = @EXEMPI_AGE@ +EXEMPI_CURRENT = @EXEMPI_CURRENT@ +EXEMPI_CURRENT_MIN = @EXEMPI_CURRENT_MIN@ +EXEMPI_INCLUDE_BASE = @EXEMPI_INCLUDE_BASE@ +EXEMPI_MAJOR_VERSION = @EXEMPI_MAJOR_VERSION@ +EXEMPI_PLATFORM_DEF = @EXEMPI_PLATFORM_DEF@ +EXEMPI_REVISION = @EXEMPI_REVISION@ +EXEMPI_VERSION_INFO = @EXEMPI_VERSION_INFO@ +FGREP = @FGREP@ +GREP = @GREP@ +HAVE_CXX11 = @HAVE_CXX11@ +INSTALL = @INSTALL@ +INSTALL_DATA = @INSTALL_DATA@ +INSTALL_PROGRAM = @INSTALL_PROGRAM@ +INSTALL_SCRIPT = @INSTALL_SCRIPT@ +INSTALL_STRIP_PROGRAM = @INSTALL_STRIP_PROGRAM@ +LD = @LD@ +LDFLAGS = @LDFLAGS@ +LIBOBJS = @LIBOBJS@ +LIBS = @LIBS@ +LIBTOOL = @LIBTOOL@ +LIPO = @LIPO@ +LN_S = @LN_S@ +LTLIBOBJS = @LTLIBOBJS@ +LT_SYS_LIBRARY_PATH = @LT_SYS_LIBRARY_PATH@ +MAINT = @MAINT@ +MAKEINFO = @MAKEINFO@ +MANIFEST_TOOL = @MANIFEST_TOOL@ +MKDIR_P = @MKDIR_P@ +NM = @NM@ +NMEDIT = @NMEDIT@ +OBJDUMP = @OBJDUMP@ +OBJEXT = @OBJEXT@ +OTOOL = @OTOOL@ +OTOOL64 = @OTOOL64@ +PACKAGE = @PACKAGE@ +PACKAGE_BUGREPORT = @PACKAGE_BUGREPORT@ +PACKAGE_NAME = @PACKAGE_NAME@ +PACKAGE_STRING = @PACKAGE_STRING@ +PACKAGE_TARNAME = @PACKAGE_TARNAME@ +PACKAGE_URL = @PACKAGE_URL@ +PACKAGE_VERSION = @PACKAGE_VERSION@ +PATH_SEPARATOR = @PATH_SEPARATOR@ +RANLIB = @RANLIB@ +SED = @SED@ +SET_MAKE = @SET_MAKE@ +SHELL = @SHELL@ +STRIP = @STRIP@ +VALGRIND = @VALGRIND@ +VERSION = @VERSION@ +XMPCORE_CPPFLAGS = @XMPCORE_CPPFLAGS@ +abs_builddir = @abs_builddir@ +abs_srcdir = @abs_srcdir@ +abs_top_builddir = @abs_top_builddir@ +abs_top_srcdir = @abs_top_srcdir@ +ac_ct_AR = @ac_ct_AR@ +ac_ct_CC = @ac_ct_CC@ +ac_ct_CXX = @ac_ct_CXX@ +ac_ct_DUMPBIN = @ac_ct_DUMPBIN@ +am__include = @am__include@ +am__leading_dot = @am__leading_dot@ +am__quote = @am__quote@ +am__tar = @am__tar@ +am__untar = @am__untar@ +bindir = @bindir@ +build = @build@ +build_alias = @build_alias@ +build_cpu = @build_cpu@ +build_os = @build_os@ +build_vendor = @build_vendor@ +builddir = @builddir@ +datadir = @datadir@ +datarootdir = @datarootdir@ +docdir = @docdir@ +dvidir = @dvidir@ +exec_prefix = @exec_prefix@ +host = @host@ +host_alias = @host_alias@ +host_cpu = @host_cpu@ +host_os = @host_os@ +host_vendor = @host_vendor@ +htmldir = @htmldir@ +includedir = @includedir@ +infodir = @infodir@ +install_sh = @install_sh@ +libdir = @libdir@ +libexecdir = @libexecdir@ +localedir = @localedir@ +localstatedir = @localstatedir@ +mandir = @mandir@ +mkdir_p = @mkdir_p@ +oldincludedir = @oldincludedir@ +pdfdir = @pdfdir@ +pkgconfigdir = @pkgconfigdir@ +prefix = @prefix@ +program_transform_name = @program_transform_name@ +psdir = @psdir@ +sbindir = @sbindir@ +sharedstatedir = @sharedstatedir@ +srcdir = @srcdir@ +sysconfdir = @sysconfdir@ +target_alias = @target_alias@ +top_build_prefix = @top_build_prefix@ +top_builddir = @top_builddir@ +top_srcdir = @top_srcdir@ +noinst_LTLIBRARIES = libxmpfilehandlers.la +AM_CPPFLAGS = -I$(top_srcdir) -I$(top_srcdir)/public/include \ + -Wall @XMPCORE_CPPFLAGS@ + +libxmpfilehandlers_la_SOURCES = \ +AIFF_Handler.cpp AIFF_Handler.hpp\ +ASF_Handler.cpp ASF_Handler.hpp\ +AVCHD_Handler.cpp AVCHD_Handler.hpp\ +Basic_Handler.cpp Basic_Handler.hpp\ +FLV_Handler.cpp FLV_Handler.hpp\ +InDesign_Handler.cpp InDesign_Handler.hpp\ +JPEG_Handler.cpp JPEG_Handler.hpp\ +MP3_Handler.cpp MP3_Handler.hpp\ +MPEG2_Handler.cpp MPEG2_Handler.hpp \ +MPEG4_Handler.cpp MPEG4_Handler.hpp \ +P2_Handler.cpp P2_Handler.hpp\ +PNG_Handler.cpp PNG_Handler.hpp\ +PostScript_Handler.cpp PostScript_Handler.hpp\ +PSD_Handler.cpp PSD_Handler.hpp\ +RIFF_Handler.cpp RIFF_Handler.hpp \ +Scanner_Handler.cpp Scanner_Handler.hpp\ +SonyHDV_Handler.cpp SonyHDV_Handler.hpp\ +SWF_Handler.cpp SWF_Handler.hpp\ +TIFF_Handler.cpp TIFF_Handler.hpp\ +Trivial_Handler.cpp Trivial_Handler.hpp\ +UCF_Handler.cpp UCF_Handler.hpp\ +XDCAMEX_Handler.cpp XDCAMEX_Handler.hpp\ +XDCAM_Handler.cpp XDCAM_Handler.hpp\ +WAVE_Handler.cpp WAVE_Handler.hpp \ +GIF_Handler.cpp GIF_Handler.hpp \ +WEBP_Handler.cpp WEBP_Handler.hpp + +all: all-am + +.SUFFIXES: +.SUFFIXES: .cpp .lo .o .obj +$(srcdir)/Makefile.in: @MAINTAINER_MODE_TRUE@ $(srcdir)/Makefile.am $(am__configure_deps) + @for dep in $?; do \ + case '$(am__configure_deps)' in \ + *$$dep*) \ + ( cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh ) \ + && { if test -f $@; then exit 0; else break; fi; }; \ + exit 1;; \ + esac; \ + done; \ + echo ' cd $(top_srcdir) && $(AUTOMAKE) --foreign XMPFiles/source/FileHandlers/Makefile'; \ + $(am__cd) $(top_srcdir) && \ + $(AUTOMAKE) --foreign XMPFiles/source/FileHandlers/Makefile +Makefile: $(srcdir)/Makefile.in $(top_builddir)/config.status + @case '$?' in \ + *config.status*) \ + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh;; \ + *) \ + echo ' cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__depfiles_maybe)'; \ + cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__depfiles_maybe);; \ + esac; + +$(top_builddir)/config.status: $(top_srcdir)/configure $(CONFIG_STATUS_DEPENDENCIES) + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh + +$(top_srcdir)/configure: @MAINTAINER_MODE_TRUE@ $(am__configure_deps) + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh +$(ACLOCAL_M4): @MAINTAINER_MODE_TRUE@ $(am__aclocal_m4_deps) + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh +$(am__aclocal_m4_deps): + +clean-noinstLTLIBRARIES: + -test -z "$(noinst_LTLIBRARIES)" || rm -f $(noinst_LTLIBRARIES) + @list='$(noinst_LTLIBRARIES)'; \ + locs=`for p in $$list; do echo $$p; done | \ + sed 's|^[^/]*$$|.|; s|/[^/]*$$||; s|$$|/so_locations|' | \ + sort -u`; \ + test -z "$$locs" || { \ + echo rm -f $${locs}; \ + rm -f $${locs}; \ + } + +libxmpfilehandlers.la: $(libxmpfilehandlers_la_OBJECTS) $(libxmpfilehandlers_la_DEPENDENCIES) $(EXTRA_libxmpfilehandlers_la_DEPENDENCIES) + $(AM_V_CXXLD)$(CXXLINK) $(libxmpfilehandlers_la_OBJECTS) $(libxmpfilehandlers_la_LIBADD) $(LIBS) + +mostlyclean-compile: + -rm -f *.$(OBJEXT) + +distclean-compile: + -rm -f *.tab.c + +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/AIFF_Handler.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/ASF_Handler.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/AVCHD_Handler.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/Basic_Handler.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/FLV_Handler.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/GIF_Handler.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/InDesign_Handler.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/JPEG_Handler.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/MP3_Handler.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/MPEG2_Handler.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/MPEG4_Handler.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/P2_Handler.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/PNG_Handler.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/PSD_Handler.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/PostScript_Handler.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/RIFF_Handler.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/SWF_Handler.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/Scanner_Handler.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/SonyHDV_Handler.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/TIFF_Handler.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/Trivial_Handler.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/UCF_Handler.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/WAVE_Handler.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/WEBP_Handler.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/XDCAMEX_Handler.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/XDCAM_Handler.Plo@am__quote@ + +.cpp.o: +@am__fastdepCXX_TRUE@ $(AM_V_CXX)depbase=`echo $@ | sed 's|[^/]*$$|$(DEPDIR)/&|;s|\.o$$||'`;\ +@am__fastdepCXX_TRUE@ $(CXXCOMPILE) -MT $@ -MD -MP -MF $$depbase.Tpo -c -o $@ $< &&\ +@am__fastdepCXX_TRUE@ $(am__mv) $$depbase.Tpo $$depbase.Po +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXXCOMPILE) -c -o $@ $< + +.cpp.obj: +@am__fastdepCXX_TRUE@ $(AM_V_CXX)depbase=`echo $@ | sed 's|[^/]*$$|$(DEPDIR)/&|;s|\.obj$$||'`;\ +@am__fastdepCXX_TRUE@ $(CXXCOMPILE) -MT $@ -MD -MP -MF $$depbase.Tpo -c -o $@ `$(CYGPATH_W) '$<'` &&\ +@am__fastdepCXX_TRUE@ $(am__mv) $$depbase.Tpo $$depbase.Po +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXXCOMPILE) -c -o $@ `$(CYGPATH_W) '$<'` + +.cpp.lo: +@am__fastdepCXX_TRUE@ $(AM_V_CXX)depbase=`echo $@ | sed 's|[^/]*$$|$(DEPDIR)/&|;s|\.lo$$||'`;\ +@am__fastdepCXX_TRUE@ $(LTCXXCOMPILE) -MT $@ -MD -MP -MF $$depbase.Tpo -c -o $@ $< &&\ +@am__fastdepCXX_TRUE@ $(am__mv) $$depbase.Tpo $$depbase.Plo +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='$<' object='$@' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(LTCXXCOMPILE) -c -o $@ $< + +mostlyclean-libtool: + -rm -f *.lo + +clean-libtool: + -rm -rf .libs _libs + +ID: $(am__tagged_files) + $(am__define_uniq_tagged_files); mkid -fID $$unique +tags: tags-am +TAGS: tags + +tags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files) + set x; \ + here=`pwd`; \ + $(am__define_uniq_tagged_files); \ + shift; \ + if test -z "$(ETAGS_ARGS)$$*$$unique"; then :; else \ + test -n "$$unique" || unique=$$empty_fix; \ + if test $$# -gt 0; then \ + $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \ + "$$@" $$unique; \ + else \ + $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \ + $$unique; \ + fi; \ + fi +ctags: ctags-am + +CTAGS: ctags +ctags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files) + $(am__define_uniq_tagged_files); \ + test -z "$(CTAGS_ARGS)$$unique" \ + || $(CTAGS) $(CTAGSFLAGS) $(AM_CTAGSFLAGS) $(CTAGS_ARGS) \ + $$unique + +GTAGS: + here=`$(am__cd) $(top_builddir) && pwd` \ + && $(am__cd) $(top_srcdir) \ + && gtags -i $(GTAGS_ARGS) "$$here" +cscopelist: cscopelist-am + +cscopelist-am: $(am__tagged_files) + list='$(am__tagged_files)'; \ + case "$(srcdir)" in \ + [\\/]* | ?:[\\/]*) sdir="$(srcdir)" ;; \ + *) sdir=$(subdir)/$(srcdir) ;; \ + esac; \ + for i in $$list; do \ + if test -f "$$i"; then \ + echo "$(subdir)/$$i"; \ + else \ + echo "$$sdir/$$i"; \ + fi; \ + done >> $(top_builddir)/cscope.files + +distclean-tags: + -rm -f TAGS ID GTAGS GRTAGS GSYMS GPATH tags + +distdir: $(DISTFILES) + @srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \ + topsrcdirstrip=`echo "$(top_srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \ + list='$(DISTFILES)'; \ + dist_files=`for file in $$list; do echo $$file; done | \ + sed -e "s|^$$srcdirstrip/||;t" \ + -e "s|^$$topsrcdirstrip/|$(top_builddir)/|;t"`; \ + case $$dist_files in \ + */*) $(MKDIR_P) `echo "$$dist_files" | \ + sed '/\//!d;s|^|$(distdir)/|;s,/[^/]*$$,,' | \ + sort -u` ;; \ + esac; \ + for file in $$dist_files; do \ + if test -f $$file || test -d $$file; then d=.; else d=$(srcdir); fi; \ + if test -d $$d/$$file; then \ + dir=`echo "/$$file" | sed -e 's,/[^/]*$$,,'`; \ + if test -d "$(distdir)/$$file"; then \ + find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \ + fi; \ + if test -d $(srcdir)/$$file && test $$d != $(srcdir); then \ + cp -fpR $(srcdir)/$$file "$(distdir)$$dir" || exit 1; \ + find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \ + fi; \ + cp -fpR $$d/$$file "$(distdir)$$dir" || exit 1; \ + else \ + test -f "$(distdir)/$$file" \ + || cp -p $$d/$$file "$(distdir)/$$file" \ + || exit 1; \ + fi; \ + done +check-am: all-am +check: check-am +all-am: Makefile $(LTLIBRARIES) +installdirs: +install: install-am +install-exec: install-exec-am +install-data: install-data-am +uninstall: uninstall-am + +install-am: all-am + @$(MAKE) $(AM_MAKEFLAGS) install-exec-am install-data-am + +installcheck: installcheck-am +install-strip: + if test -z '$(STRIP)'; then \ + $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \ + install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \ + install; \ + else \ + $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \ + install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \ + "INSTALL_PROGRAM_ENV=STRIPPROG='$(STRIP)'" install; \ + fi +mostlyclean-generic: + +clean-generic: + +distclean-generic: + -test -z "$(CONFIG_CLEAN_FILES)" || rm -f $(CONFIG_CLEAN_FILES) + -test . = "$(srcdir)" || test -z "$(CONFIG_CLEAN_VPATH_FILES)" || rm -f $(CONFIG_CLEAN_VPATH_FILES) + +maintainer-clean-generic: + @echo "This command is intended for maintainers to use" + @echo "it deletes files that may require special tools to rebuild." +clean: clean-am + +clean-am: clean-generic clean-libtool clean-noinstLTLIBRARIES \ + mostlyclean-am + +distclean: distclean-am + -rm -rf ./$(DEPDIR) + -rm -f Makefile +distclean-am: clean-am distclean-compile distclean-generic \ + distclean-tags + +dvi: dvi-am + +dvi-am: + +html: html-am + +html-am: + +info: info-am + +info-am: + +install-data-am: + +install-dvi: install-dvi-am + +install-dvi-am: + +install-exec-am: + +install-html: install-html-am + +install-html-am: + +install-info: install-info-am + +install-info-am: + +install-man: + +install-pdf: install-pdf-am + +install-pdf-am: + +install-ps: install-ps-am + +install-ps-am: + +installcheck-am: + +maintainer-clean: maintainer-clean-am + -rm -rf ./$(DEPDIR) + -rm -f Makefile +maintainer-clean-am: distclean-am maintainer-clean-generic + +mostlyclean: mostlyclean-am + +mostlyclean-am: mostlyclean-compile mostlyclean-generic \ + mostlyclean-libtool + +pdf: pdf-am + +pdf-am: + +ps: ps-am + +ps-am: + +uninstall-am: + +.MAKE: install-am install-strip + +.PHONY: CTAGS GTAGS TAGS all all-am check check-am clean clean-generic \ + clean-libtool clean-noinstLTLIBRARIES cscopelist-am ctags \ + ctags-am distclean distclean-compile distclean-generic \ + distclean-libtool distclean-tags distdir dvi dvi-am html \ + html-am info info-am install install-am install-data \ + install-data-am install-dvi install-dvi-am install-exec \ + install-exec-am install-html install-html-am install-info \ + install-info-am install-man install-pdf install-pdf-am \ + install-ps install-ps-am install-strip installcheck \ + installcheck-am installdirs maintainer-clean \ + maintainer-clean-generic mostlyclean mostlyclean-compile \ + mostlyclean-generic mostlyclean-libtool pdf pdf-am ps ps-am \ + tags tags-am uninstall uninstall-am + +.PRECIOUS: Makefile + +$(NULL) + +# Tell versions [3.59,3.63) of GNU make to not export all variables. +# Otherwise a system limit (for SysV at least) may be exceeded. +.NOEXPORT: diff --git a/XMPFiles/source/FileHandlers/P2_Handler.cpp b/XMPFiles/source/FileHandlers/P2_Handler.cpp new file mode 100644 index 0000000..d99235d --- /dev/null +++ b/XMPFiles/source/FileHandlers/P2_Handler.cpp @@ -0,0 +1,1313 @@ +// ================================================================================================= +// 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"; + + } else if ( ( p2FrameRate == "59.94p" ) && ( p2DropFrameFlag != 0 ) ) { + + if ( XMP_LitMatch ( p2DropFrameFlag, "true" ) ) { + dmTimeFormat = "5994DropTimecode"; + } else if ( XMP_LitMatch ( p2DropFrameFlag, "false" ) ) { + dmTimeFormat = "5994NonDropTimecode"; + } + + } 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::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; + } + } + + } + + 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/XMPFiles/source/FileHandlers/P2_Handler.hpp b/XMPFiles/source/FileHandlers/P2_Handler.hpp new file mode 100644 index 0000000..5e492ee --- /dev/null +++ b/XMPFiles/source/FileHandlers/P2_Handler.hpp @@ -0,0 +1,113 @@ +#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 "third-party/zuid/interfaces/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 ); + + 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/XMPFiles/source/FileHandlers/PNG_Handler.cpp b/XMPFiles/source/FileHandlers/PNG_Handler.cpp new file mode 100644 index 0000000..8c2fe8c --- /dev/null +++ b/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/XMPFiles/source/FileHandlers/PNG_Handler.hpp b/XMPFiles/source/FileHandlers/PNG_Handler.hpp new file mode 100644 index 0000000..9298141 --- /dev/null +++ b/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/XMPFiles/source/FileHandlers/PSD_Handler.cpp b/XMPFiles/source/FileHandlers/PSD_Handler.cpp new file mode 100644 index 0000000..e5847e9 --- /dev/null +++ b/XMPFiles/source/FileHandlers/PSD_Handler.cpp @@ -0,0 +1,429 @@ +// ================================================================================================= +// 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 "third-party/zuid/interfaces/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 ) : skipReconcile(false), iptcMgr(0), exifMgr(0),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; + bool parseIPTC = (iptcDigestState != kDigestMatches) || (! readOnly); + if ( parseIPTC ) 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/XMPFiles/source/FileHandlers/PSD_Handler.hpp b/XMPFiles/source/FileHandlers/PSD_Handler.hpp new file mode 100644 index 0000000..4caec1b --- /dev/null +++ b/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() : skipReconcile(false), iptcMgr(0), exifMgr(0) {} // 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/XMPFiles/source/FileHandlers/PostScript_Handler.cpp b/XMPFiles/source/FileHandlers/PostScript_Handler.cpp new file mode 100644 index 0000000..81b6928 --- /dev/null +++ b/XMPFiles/source/FileHandlers/PostScript_Handler.cpp @@ -0,0 +1,1846 @@ +// ================================================================================================= +// 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; + // See bug https://bugs.freedesktop.org/show_bug.cgi?id=105206 + // Ensure we don't get past the limit if + // the data is bogus. + while(ioBuf.ptr < ioBuf.limit && + !IsNewline(*ioBuf.ptr)) { + ++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/XMPFiles/source/FileHandlers/PostScript_Handler.hpp b/XMPFiles/source/FileHandlers/PostScript_Handler.hpp new file mode 100644 index 0000000..7bff0fc --- /dev/null +++ b/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/XMPFiles/source/FileHandlers/RIFF_Handler.cpp b/XMPFiles/source/FileHandlers/RIFF_Handler.cpp new file mode 100644 index 0000000..7d6fdb3 --- /dev/null +++ b/XMPFiles/source/FileHandlers/RIFF_Handler.cpp @@ -0,0 +1,356 @@ +// ================================================================================================= +// 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 = 0; + this->dispChunk = this->bextChunk = this->cr8rChunk = this->prmlChunk = 0; + this->xmpChunk = 0; + this->lastChunk = 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/XMPFiles/source/FileHandlers/RIFF_Handler.hpp b/XMPFiles/source/FileHandlers/RIFF_Handler.hpp new file mode 100644 index 0000000..d8c83a2 --- /dev/null +++ b/XMPFiles/source/FileHandlers/RIFF_Handler.hpp @@ -0,0 +1,73 @@ +// ================================================================================================= +// 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; + RIFF::ValueChunk* dispChunk; + RIFF::ValueChunk* bextChunk; + RIFF::ValueChunk* cr8rChunk; + RIFF::ValueChunk* prmlChunk; + 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/XMPFiles/source/FileHandlers/SWF_Handler.cpp b/XMPFiles/source/FileHandlers/SWF_Handler.cpp new file mode 100644 index 0000000..3266e94 --- /dev/null +++ b/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/XMPFiles/source/FileHandlers/SWF_Handler.hpp b/XMPFiles/source/FileHandlers/SWF_Handler.hpp new file mode 100644 index 0000000..f2ca7cb --- /dev/null +++ b/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/XMPFiles/source/FileHandlers/Scanner_Handler.cpp b/XMPFiles/source/FileHandlers/Scanner_Handler.cpp new file mode 100644 index 0000000..2d6308d --- /dev/null +++ b/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/XMPFiles/source/FileHandlers/Scanner_Handler.hpp b/XMPFiles/source/FileHandlers/Scanner_Handler.hpp new file mode 100644 index 0000000..53c4820 --- /dev/null +++ b/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/XMPFiles/source/FileHandlers/SonyHDV_Handler.cpp b/XMPFiles/source/FileHandlers/SonyHDV_Handler.cpp new file mode 100644 index 0000000..17e5baf --- /dev/null +++ b/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 "third-party/zuid/interfaces/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/XMPFiles/source/FileHandlers/SonyHDV_Handler.hpp b/XMPFiles/source/FileHandlers/SonyHDV_Handler.hpp new file mode 100644 index 0000000..190930d --- /dev/null +++ b/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/XMPFiles/source/FileHandlers/TIFF_Handler.cpp b/XMPFiles/source/FileHandlers/TIFF_Handler.cpp new file mode 100644 index 0000000..37e4ab1 --- /dev/null +++ b/XMPFiles/source/FileHandlers/TIFF_Handler.cpp @@ -0,0 +1,422 @@ +// ================================================================================================= +// 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 "third-party/zuid/interfaces/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 ); + // See bug https://bugs.freedesktop.org/show_bug.cgi?id=105205 + // if iptcInfo.dataLen is 0, then there is no digest. + if ( (iptcDigestState == kDigestDiffers) && (kTIFF_TypeSizes[iptcInfo.type] > 1) && iptcInfo.dataLen > 0 ) { + 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; + bool parseIPTC = (iptcDigestState != kDigestMatches) || (! readOnly); + if ( parseIPTC ) 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/XMPFiles/source/FileHandlers/TIFF_Handler.hpp b/XMPFiles/source/FileHandlers/TIFF_Handler.hpp new file mode 100644 index 0000000..bc653b2 --- /dev/null +++ b/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/XMPFiles/source/FileHandlers/Trivial_Handler.cpp b/XMPFiles/source/FileHandlers/Trivial_Handler.cpp new file mode 100644 index 0000000..ac8b468 --- /dev/null +++ b/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/XMPFiles/source/FileHandlers/Trivial_Handler.hpp b/XMPFiles/source/FileHandlers/Trivial_Handler.hpp new file mode 100644 index 0000000..d9e0e15 --- /dev/null +++ b/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/XMPFiles/source/FileHandlers/UCF_Handler.cpp b/XMPFiles/source/FileHandlers/UCF_Handler.cpp new file mode 100644 index 0000000..d304bc2 --- /dev/null +++ b/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 "third-party/zlib/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/XMPFiles/source/FileHandlers/UCF_Handler.hpp b/XMPFiles/source/FileHandlers/UCF_Handler.hpp new file mode 100644 index 0000000..eee46ef --- /dev/null +++ b/XMPFiles/source/FileHandlers/UCF_Handler.hpp @@ -0,0 +1,723 @@ +#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),extraField(0),filenameLen(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; + comment = 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/XMPFiles/source/FileHandlers/WAVE_Handler.cpp b/XMPFiles/source/FileHandlers/WAVE_Handler.cpp new file mode 100644 index 0000000..6f7f287 --- /dev/null +++ b/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 ) + : mChunkController(NULL), mChunkBehavior(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/XMPFiles/source/FileHandlers/WAVE_Handler.hpp b/XMPFiles/source/FileHandlers/WAVE_Handler.hpp new file mode 100644 index 0000000..29c1c86 --- /dev/null +++ b/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/XMPFiles/source/FileHandlers/WEBP_Handler.cpp b/XMPFiles/source/FileHandlers/WEBP_Handler.cpp new file mode 100644 index 0000000..921f5c2 --- /dev/null +++ b/XMPFiles/source/FileHandlers/WEBP_Handler.cpp @@ -0,0 +1,201 @@ +#include "public/include/XMP_Const.h" +#include "public/include/XMP_Environment.h" // ! XMP_Environment.h must be the first included header. + +#include "XMPFiles/source/FileHandlers/WEBP_Handler.hpp" + +#include "XMPFiles/source/FormatSupport/IPTC_Support.hpp" +#include "XMPFiles/source/FormatSupport/PSIR_Support.hpp" +#include "XMPFiles/source/FormatSupport/ReconcileLegacy.hpp" +#include "XMPFiles/source/FormatSupport/Reconcile_Impl.hpp" +#include "XMPFiles/source/FormatSupport/TIFF_Support.hpp" +#include "XMPFiles/source/FormatSupport/WEBP_Support.hpp" +#include "source/XIO.hpp" + +using namespace std; + +/// File format handler for WEBP. + +XMPFileHandler* WEBP_MetaHandlerCTor(XMPFiles* parent) +{ + return new WEBP_MetaHandler(parent); +} + +// Check that the file begins with "RIFF", a 4 byte length, then the chunkType +// (WEBP). +bool WEBP_CheckFormat(XMP_FileFormat format, XMP_StringPtr filePath, + XMP_IO* file, XMPFiles* parent) +{ + IgnoreParam(format); + IgnoreParam(parent); + XMP_Assert(format == kXMP_WEBPFile); + + 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], "WEBP", 4) && format == kXMP_WEBPFile) { + return true; + } + return false; +} + +WEBP_MetaHandler::WEBP_MetaHandler(XMPFiles* parent) + : mainChunk(NULL) + , xmpChunk(NULL) + , exifMgr(NULL) + , psirMgr(NULL) + , iptcMgr(NULL) +{ + this->parent = parent; + this->handlerFlags = kWEBP_HandlerFlags; + this->stdCharForm = kXMP_Char8Bit; + + this->initialFileSize = 0; + this->mainChunk = 0; +} + +WEBP_MetaHandler::~WEBP_MetaHandler() +{ + if (this->mainChunk) { + delete this->mainChunk; + } + if (this->exifMgr) { + delete this->exifMgr; + } + if (this->iptcMgr) { + delete this->iptcMgr; + } + if (this->psirMgr) { + delete this->psirMgr; + } +} + +void WEBP_MetaHandler::CacheFileData() +{ + this->containsXMP = false; // assume for now + + XMP_IO* file = this->parent->ioRef; + this->initialFileSize = file->Length(); + + file->Rewind(); + + XMP_Int64 filePos = 0; + while (filePos < this->initialFileSize) { + this->mainChunk = new WEBP::Container(this); + filePos = file->Offset(); + } + + // covered before => internal error if it occurs + XMP_Validate(file->Offset() == this->initialFileSize, + "WEBP_MetaHandler::CacheFileData: unknown data at end of file", + kXMPErr_InternalFailure); +} + +void WEBP_MetaHandler::ProcessXMP() +{ + SXMPUtils::RemoveProperties(&this->xmpObj, 0, 0, kXMPUtil_DoAllProperties); + + bool readOnly = false; + bool xmpOnly = false; + bool haveExif = false; + if (this->parent) { + readOnly = + !XMP_OptionIsSet(this->parent->openFlags, kXMPFiles_OpenForUpdate); + xmpOnly = + XMP_OptionIsSet(this->parent->openFlags, kXMPFiles_OpenOnlyXMP); + } + if (!xmpOnly) { + if (readOnly) { + this->exifMgr = new TIFF_MemoryReader(); + } + else { + this->exifMgr = new TIFF_FileWriter(); + } + this->psirMgr = new PSIR_MemoryReader(); + this->iptcMgr = new IPTC_Reader(); + if (this->parent) { + exifMgr->SetErrorCallback(&this->parent->errorCallback); + } + if (this->mainChunk) { + WEBP::Chunk* exifChunk = this->mainChunk->getExifChunk(); + if (exifChunk != NULL) { + haveExif = true; + this->exifMgr->ParseMemoryStream(exifChunk->data.data() + 6, + exifChunk->data.size() - 6); + } + } + } + + if (this->containsXMP) { + this->xmpObj.ParseFromBuffer(this->xmpPacket.c_str(), + (XMP_StringLen) this->xmpPacket.size()); + } + if (haveExif) { + XMP_OptionBits options = k2XMP_FileHadExif; + if (this->containsXMP) { + options |= k2XMP_FileHadXMP; + } + TIFF_Manager& exif = *this->exifMgr; + PSIR_Manager& psir = *this->psirMgr; + IPTC_Manager& iptc = *this->iptcMgr; + ImportPhotoData(exif, iptc, psir, kDigestMatches, &this->xmpObj, + options); + // Assume that, since the file had EXIF data, some of it was mapped to + // XMP + this->containsXMP = true; + } + this->processedXMP = true; +} + +void WEBP_MetaHandler::UpdateFile(bool doSafeUpdate) +{ + XMP_Validate(this->needsUpdate, "nothing to update", + kXMPErr_InternalFailure); + + bool xmpOnly = false; + if (this->parent) { + xmpOnly = + XMP_OptionIsSet(this->parent->openFlags, kXMPFiles_OpenOnlyXMP); + } + + if (!xmpOnly && this->exifMgr) { + WEBP::Chunk* exifChunk = this->mainChunk->getExifChunk(); + if (exifChunk != NULL) { + ExportPhotoData(kXMP_TIFFFile, &this->xmpObj, this->exifMgr, 0, 0); + if (this->exifMgr->IsLegacyChanged()) { + XMP_Uns8* exifPtr; + XMP_Uns32 exifLen = + this->exifMgr->UpdateMemoryStream((void**)&exifPtr); + RawDataBlock exifData(&exifChunk->data[0], &exifChunk->data[6]); + exifData.insert(exifData.begin() + 6, &exifPtr[0], + &exifPtr[exifLen]); + exifChunk->data = exifData; + exifChunk->size = exifLen + 6; + exifChunk->needsRewrite = true; + } + } + } + + this->packetInfo.charForm = stdCharForm; + this->packetInfo.writeable = true; + this->packetInfo.offset = kXMPFiles_UnknownOffset; + this->packetInfo.length = kXMPFiles_UnknownLength; + + this->xmpObj.SerializeToBuffer(&this->xmpPacket, kXMP_OmitPacketWrapper); + + this->mainChunk->write(this); + this->needsUpdate = false; // do last for safety +} + +void WEBP_MetaHandler::WriteTempFile(XMP_IO* tempRef) +{ + IgnoreParam(tempRef); + XMP_Throw("WEBP_MetaHandler::WriteTempFile: Not supported (must go through " + "UpdateFile)", + kXMPErr_Unavailable); +} diff --git a/XMPFiles/source/FileHandlers/WEBP_Handler.hpp b/XMPFiles/source/FileHandlers/WEBP_Handler.hpp new file mode 100644 index 0000000..c5041ea --- /dev/null +++ b/XMPFiles/source/FileHandlers/WEBP_Handler.hpp @@ -0,0 +1,48 @@ +#ifndef __WEBP_Handler_hpp__ +#define __WEBP_Handler_hpp__ 1 + +#include "public/include/XMP_Const.h" +#include "public/include/XMP_Environment.h" + +#include "XMPFiles/source/FormatSupport/WEBP_Support.hpp" +#include "XMPFiles/source/FormatSupport/TIFF_Support.hpp" +#include "XMPFiles/source/FormatSupport/IPTC_Support.hpp" +#include "XMPFiles/source/FormatSupport/PSIR_Support.hpp" + +#include "source/XIO.hpp" + +// File format handler for WEBP + +extern XMPFileHandler* WEBP_MetaHandlerCTor(XMPFiles* parent); + +extern bool WEBP_CheckFormat(XMP_FileFormat format, XMP_StringPtr filePath, + XMP_IO* fileRef, XMPFiles* parent); + +static const XMP_OptionBits kWEBP_HandlerFlags = + (kXMPFiles_CanInjectXMP | kXMPFiles_CanExpand | kXMPFiles_PrefersInPlace | + kXMPFiles_AllowsOnlyXMP | kXMPFiles_ReturnsRawPacket | + kXMPFiles_CanReconcile); + +class WEBP_MetaHandler + : public XMPFileHandler { +public: + WEBP_MetaHandler(XMPFiles* parent); + ~WEBP_MetaHandler(); + + void CacheFileData(); + void ProcessXMP(); + void UpdateFile(bool doSafeUpdate); + void WriteTempFile(XMP_IO* tempRef); + + WEBP::Container* mainChunk; + WEBP::XMPChunk* xmpChunk; + XMP_Int64 initialFileSize; + TIFF_Manager* exifMgr; + // The PSIR_Manager and IPTC_Manager aren't actually used, but they need + // to be instantiated and passed to the function that reconciles EXIF and + // XMP data. + PSIR_Manager* psirMgr; + IPTC_Manager* iptcMgr; +}; + +#endif /* __WEBP_Handler_hpp__ */ diff --git a/XMPFiles/source/FileHandlers/XDCAMEX_Handler.cpp b/XMPFiles/source/FileHandlers/XDCAMEX_Handler.cpp new file mode 100644 index 0000000..826cc0b --- /dev/null +++ b/XMPFiles/source/FileHandlers/XDCAMEX_Handler.cpp @@ -0,0 +1,1040 @@ +// ================================================================================================= +// 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 "third-party/zuid/interfaces/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 ( kDirChar != '/' ) { + for ( size_t i = 0, limit = takeDir.size(); i < limit; ++i ) { + if ( takeDir[i] == '/' ) takeDir[i] = kDirChar; + } + } + + 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/XMPFiles/source/FileHandlers/XDCAMEX_Handler.hpp b/XMPFiles/source/FileHandlers/XDCAMEX_Handler.hpp new file mode 100644 index 0000000..3673b1f --- /dev/null +++ b/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/XMPFiles/source/FileHandlers/XDCAM_Handler.cpp b/XMPFiles/source/FileHandlers/XDCAM_Handler.cpp new file mode 100644 index 0000000..2a05be9 --- /dev/null +++ b/XMPFiles/source/FileHandlers/XDCAM_Handler.cpp @@ -0,0 +1,1620 @@ +// ================================================================================================= +// 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 "third-party/zuid/interfaces/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. + +bool XDCAM_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; // ! Need tweaking in the existing file cases (FAM and SAM). + std::string gpName = _gpName; + + bool isFAM = false; + + std::string tempPath, childName; + + std::string clipName = leafName; + + // Do some basic checks on the root path and component names. Decide if this is FAM or SAM. + + 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. Just look for PROAV to see if this is FAM or SAM. + if ( Host_IO::GetChildMode ( rootPath.c_str(), "PROAV" ) != Host_IO::kFMode_IsFolder ) isFAM = true; + + } else { + + // This is the existing file case. See if this is FAM or SAM, tweak the clip name as needed. + + if ( (parentName == "CLIP") || (parentName == "EDIT") || (parentName == "SUB") ) { + // ! The standard says Clip/Edit/Sub, but the caller has already shifted to upper case. + isFAM = true; + } else if ( (gpName != "CLPR") && (gpName != "EDTR") ) { + return false; + } + + if ( isFAM ) { + + // Put the proper root path together. Clean up the clip name if needed. + + if ( ! rootPath.empty() ) rootPath += kDirChar; + rootPath += gpName; + gpName.erase(); + + // XMPilot has no ALIAS.XML, but does have a UserData folder, don't change the first + // letter of the clip name for XMPilot. + if ( (Host_IO::GetChildMode ( rootPath.c_str(), "ALIAS.XML" ) != Host_IO::kFMode_IsFile) && + (Host_IO::GetChildMode ( rootPath.c_str(), "UserData" ) != Host_IO::kFMode_IsFolder) ) { + clipName[0] = 'C'; // ! See notes above about pending bug. + } + + if ( clipName.size() > 3 ) { + size_t clipMid = clipName.size() - 3; + char c1 = clipName[clipMid]; + char c2 = clipName[clipMid+1]; + char c3 = clipName[clipMid+2]; + if ( ('A' <= c1) && (c1 <= 'Z') && + ('0' <= c2) && (c2 <= '9') && ('0' <= c3) && (c3 <= '9') ) { + clipName.erase ( clipMid ); + } + } + + } else { + + // Fix the clip name. Check for and strip the "PROAV" suffix on the root path. + + clipName = parentName; // ! We have a folder with the (almost) exact clip name. + clipName[0] = 'C'; + + std::string proav; + XIO::SplitLeafName ( &rootPath, &proav ); + MakeUpperCase ( &proav ); + if ( (rootPath.empty()) || (proav != "PROAV") ) return false; + + } + + } + + // Make sure the general XDCAM package structure is legit. Set tempPath as a bogus path of the + // form //, e.g. ".../MyMovie/FAM/C0001". This is passed the handler via + // the tempPtr hackery. + + if ( isFAM ) { + + if ( (format != kXMP_XDCAM_FAMFile) && (format != kXMP_UnknownFile) ) return false; + + tempPath = rootPath; + + // XMPilot does not have INDEX.XML but does have UserData. + if ( (Host_IO::GetChildMode ( tempPath.c_str(), "INDEX.XML" ) != Host_IO::kFMode_IsFile) && + !((Host_IO::GetChildMode ( rootPath.c_str(), "UserData" ) == Host_IO::kFMode_IsFolder) + // Changes introduced by Sony for XDCAM Memory SxS format in the FAM file structure are + // 1) There is no INDEX.XML in the root directory for XDCAM Memory SxS. + // 2) There is a new Take folder(similar to XDCAMEX) in the root directory. + || (Host_IO::GetChildMode ( tempPath.c_str(), "Take" ) == Host_IO::kFMode_IsFolder))) return false; + 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; + + tempPath += kDirChar; + tempPath += "Clip"; // ! Yes, mixed case. + tempPath += kDirChar; + tempPath += clipName; + tempPath += "M01.XML"; + if ( Host_IO::GetFileMode ( tempPath.c_str() ) != Host_IO::kFMode_IsFile ) return false; + + tempPath = rootPath; + tempPath += kDirChar; + tempPath += "FAM"; + tempPath += kDirChar; + tempPath += clipName; + + } else { + + if ( (format != kXMP_XDCAM_SAMFile) && (format != kXMP_UnknownFile) ) return false; + + // We already know about the PROAV folder, just check below it. + + tempPath = rootPath; + tempPath += kDirChar; + tempPath += "PROAV"; + + 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; + + tempPath += kDirChar; + tempPath += "CLPR"; + tempPath += kDirChar; + tempPath += clipName; + if ( Host_IO::GetFileMode ( tempPath.c_str() ) != Host_IO::kFMode_IsFolder ) return false; + + tempPath += kDirChar; + tempPath += clipName; + tempPath += "M01.XML"; + if ( Host_IO::GetFileMode ( tempPath.c_str() ) != Host_IO::kFMode_IsFile ) return false; + + tempPath = rootPath; + tempPath += kDirChar; + tempPath += "SAM"; + tempPath += kDirChar; + tempPath += clipName; + + } + + // Save the pseudo-path for the handler object. A bit of a hack, but the only way to get info + // from here to there. + + size_t pathLen = tempPath.size() + 1; // Include a terminating nul. + parent->tempPtr = malloc ( pathLen ); + if ( parent->tempPtr == 0 ) XMP_Throw ( "No memory for XDCAM clip info", kXMPErr_NoMemory ); + memcpy ( parent->tempPtr, tempPath.c_str(), pathLen ); // AUDIT: Safe, allocated above. + + return true; + +} // XDCAM_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; + std::string clipName; + bool isSAM; + + size_t pathLen; + void* tempPtr = 0; + + if ( ! Host_IO::Exists ( pseudoPath.c_str() ) ) { + + // This is the logical clip path case. Look for PROAV to see if this is FAM or SAM. + + XIO::SplitLeafName ( &pseudoPath, &clipName ); // Extract the logical clip name, no extension. + isSAM = ( Host_IO::GetChildMode ( pseudoPath.c_str(), "PROAV" ) == Host_IO::kFMode_IsFolder ); + + } else { + + // The client passed a physical path. We have separate cases for FAM and SAM. If the last + // folder, the parent of the file, is Clip, Edit, or Sub (ignoring case) then this is FAM + // and things are a bit messy. For SAM, the parent folder is the almost clip name. + + std::string parentName, ignored; + + XIO::SplitLeafName ( &pseudoPath, &clipName ); // Extract the logical clip name. + XIO::SplitFileExtension ( &clipName, &ignored ); + + XIO::SplitLeafName ( &pseudoPath, &parentName ); + MakeUpperCase ( &parentName ); + isSAM = ( (parentName != "CLIP") && (parentName != "EDIT") && (parentName != "SUB") ); + + if ( isSAM ) { + + // SAM is easy, the parent name is almost the clip name, the first letter gets coerced + // to 'C'. There are 2 other folders to remove from the path. + + clipName = parentName; + clipName[0] = 'C'; + XIO::SplitLeafName ( &pseudoPath, &ignored ); // Remove the 2 intermediate folder levels. + XIO::SplitLeafName ( &pseudoPath, &ignored ); + + } else { + + // FAM is a bit messy, study the comments and code of XDCAM_CheckFormat for details. + + if ( Host_IO::GetChildMode ( pseudoPath.c_str(), "ALIAS.XML" ) != Host_IO::kFMode_IsFile ) { + clipName[0] = 'C'; // ! See notes in XDCAM_CheckFormat about pending bug. + } + + if ( clipName.size() > 3 ) { + size_t clipMid = clipName.size() - 3; + char c1 = clipName[clipMid]; + char c2 = clipName[clipMid+1]; + char c3 = clipName[clipMid+2]; + if ( ('A' <= c1) && (c1 <= 'Z') && + ('0' <= c2) && (c2 <= '9') && ('0' <= c3) && (c3 <= '9') ) { + clipName.erase ( clipMid ); + } + } + + } + + } + + pseudoPath += kDirChar; + if ( isSAM ) { + pseudoPath += "SAM"; + } else { + pseudoPath += "FAM"; + } + pseudoPath += kDirChar; + pseudoPath += clipName; + + pathLen = pseudoPath.size() + 1; // Include a terminating nul. + tempPtr = malloc ( pathLen ); + if ( tempPtr == 0 ) XMP_Throw ( "No memory for XDCAM clip info", kXMPErr_NoMemory ); + memcpy ( tempPtr, pseudoPath.c_str(), pathLen ); + + return tempPtr; + +} // CreatePseudoClipPath + +// ================================================================================================= +// XDCAM_MetaHandlerCTor +// ===================== + +XMPFileHandler * XDCAM_MetaHandlerCTor ( XMPFiles * parent ) +{ + return new XDCAM_MetaHandler ( parent ); + +} // XDCAM_MetaHandlerCTor + + +// ================================================================================================= +// XDCAM_MetaHandler::SetSidecarPath +// ==================================== +void XDCAM_MetaHandler::SetSidecarPath() +{ + // Here, we set the appropriate sidecar name for this format. + // If, the format if XMPilot (no INDEX.XML but UserData folder present) or + // SxS (no INDEX.XML but Take folder present) then sidecar name will be + // old name used by MXFHandler i.e, {clipName}.MXF.xmp or {clipname}.mxf.xmp + // For all other cases, new side car name i.e, {clipname}M01.XMP will be used. + + try + { + if(this->isFAM && Host_IO::GetChildMode ( this->rootPath.c_str(), "INDEX.XML" ) != Host_IO::kFMode_IsFile && + (Host_IO::GetChildMode ( rootPath.c_str(), "UserData" ) == Host_IO::kFMode_IsFolder + || Host_IO::GetChildMode ( this->rootPath.c_str(), "Take" ) == Host_IO::kFMode_IsFolder) ) + { + // this is either XMPilot or SxS format. + XMP_VarString mxfFilePath; + if(MakeClipFilePath ( &mxfFilePath , ".MXF", true ) || MakeClipFilePath ( &mxfFilePath , ".mxf", true ) ) + { + Host_IO::FileRef hostRef = Host_IO::Open ( mxfFilePath.c_str(), Host_IO::openReadOnly ); + if ( hostRef != Host_IO::noFileRef ) + { + + XMPFiles_IO mxfFile ( hostRef, mxfFilePath.c_str() , Host_IO::openReadOnly ); + + if ( Host_IO::Length(hostRef) >= 16 ) + { + XMP_Uns8 buffer[16]; + Host_IO::Seek(hostRef, 0, kXMP_SeekFromStart); + XMP_Uns32 readBytes = Host_IO::Read(hostRef, buffer, 16 ); + + if ( ( readBytes == 16 ) && + ( GetUns32BE(&buffer[0]) == 0x060E2B34 ) && + ( GetUns32BE(&buffer[4]) == 0x02050101 ) && + ( GetUns32BE(&buffer[8]) == 0x0D010201 ) && + ( ( GetUns32BE(&buffer[12]) & 0xFFFF00FF ) == 0x01020000 ) + ) + { + std::string pathtomxfclip=Host_IO::GetCasePreservedName(mxfFilePath); + if ( pathtomxfclip != "" ) + { + std::string ext; + XIO::SplitFileExtension( &pathtomxfclip, &ext , false); + ext="."+ext; + MakeClipFilePath ( &mxfFilePath , ext.c_str() , false ); + this->sidecarPath = mxfFilePath + ".xmp"; + } + } + } + } + } + } + } + catch( ... ) + { + // Use new side car name. + } + if(this->sidecarPath.empty()) + { + MakeClipFilePath ( &this->sidecarPath , "M01.XMP", false ) ; + } +}// XDCAM_MetaHandler::SetSidecarPath + +// ================================================================================================= +// XDCAM_MetaHandler::XDCAM_MetaHandler +// ==================================== + +XDCAM_MetaHandler::XDCAM_MetaHandler ( XMPFiles * _parent ) : isFAM(false), expat(0),clipMetadata(NULL) +{ + + this->parent = _parent; // Inherited, can't set in the prefix. + this->handlerFlags = kXDCAM_HandlerFlags; + this->stdCharForm = kXMP_Char8Bit; + + // Extract the root path, clip name, and FAM/SAM flag 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 temp; + XIO::SplitLeafName ( &this->rootPath, &temp ); + XMP_Assert ( (temp == "FAM") || (temp == "SAM") ); + if ( temp == "FAM" ) this->isFAM = true; + // backward compatibility ensured for XMPilot Clips + // XMPilot is FAM + this->SetSidecarPath(); + XMP_Assert ( this->isFAM ? (this->parent->format == kXMP_XDCAM_FAMFile) : (this->parent->format == kXMP_XDCAM_SAMFile) ); + +} // 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::MakeClipFilePath +// =================================== + +bool XDCAM_MetaHandler::MakeClipFilePath ( std::string * path, XMP_StringPtr suffix, bool checkFile /* = false */ ) +{ + + *path = this->rootPath; + *path += kDirChar; + + if ( this->isFAM ) { + *path += "Clip"; // ! Yes, mixed case. + } else { + *path += "PROAV"; + *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() ); + +} // XDCAM_MetaHandler::MakeClipFilePath + +// ================================================================================================= +// 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" + +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 + +// ================================================================================================= +// P2_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 + +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); +} + +bool XDCAM_MetaHandler::GetFileModDate ( XMP_DateTime * modDate ) +{ + + // The XDCAM FAM locations of metadata: + // MEDIAPRO.XML // Has non-XMP metadata. + // Clip: + // C0001_50i_DVCAM_43_4chM01.XML // Has non-XMP metadata. + // C0001_50i_DVCAM_43_4chM01.XMP + + // The XDCAM SAM locations of metadata: + // PROAV: + // CLPR: + // C0001: + // C0001M01.XML // Has non-XMP metadata. + // C0001M01.XMP + + bool ok, haveDate = false; + std::string fullPath; + XMP_DateTime oneDate, junkDate; + if ( modDate == 0 ) modDate = &junkDate; + + 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; + } + + 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; + +} // XDCAM_MetaHandler::GetFileModDate + + +// ================================================================================================= +// XDCAM_MetaHandler::GetClipUmid +// ============================== +bool XDCAM_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; +}// XDCAM_MetaHandler::GetClipUmid + +// ================================================================================================= +// XDCAM_MetaHandler::IsClipsPlanning +// ================================== +bool XDCAM_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; +} // XDCAM_MetaHandler::IsClipsPlanning + + +// ================================================================================================= +// 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 + +inline bool IsDigit( char c ) +{ + return c >= '0' && c <= '9'; +} + + +// ================================================================================================= +// XDCAM_MetaHandler::GetEditInfoFilesSAM +// ====================================== +bool XDCAM_MetaHandler::GetEditInfoFilesSAM ( 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; +} // XDCAM_MetaHandler::GetEditInfoFilesSAM + +// ================================================================================================= +// XDCAM_MetaHandler::GetInfoFilesFAM +// ================================== +bool XDCAM_MetaHandler::GetInfoFilesFAM ( std::vector &editInfoList, 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 editFolder; + std::string edlistChild; + + editFolder.folder = Host_IO::OpenFolder ( pathToFolder.c_str() ); + while ( Host_IO::GetNextChild ( editFolder.folder, &edlistChild ) ) { + size_t filenamelen = edlistChild.size(); + std::string editListFilePath = pathToFolder + edlistChild ; + if ( ! ( filenamelen > 7 && + edlistChild.compare ( filenamelen - 4, 4 , ".SMI" ) == 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; +} // XDCAM_MetaHandler::GetInfoFilesFAM + +// ================================================================================================= +// XDCAM_MetaHandler::GetPlanningFilesFAM +// ====================================== +bool XDCAM_MetaHandler::GetPlanningFilesFAM ( 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; +} // XDCAM_MetaHandler::GetPlanningFilesFAM + +// ================================================================================================= +// 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::FillFAMAssociatedResources +// ============================================= +void XDCAM_MetaHandler::FillFAMAssociatedResources ( std::vector * resourceList ) +{ + // The possible associated resources: + // .../MyMovie/ + // ALIAS.XML + // INDEX.XML + // DISCMETA.XML + // MEDIAPRO.XML + // MEDIAPRO.BUP + // CUEUP.XML + // CUEUP.BUP + // Clip/ + // AAAAA.MXF AAAAA is the clipname with clipserial + // XX 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 and same clipname/editListName/Takename. + // AAAAAMXX.XML + // AAAAAMXX.XMP + // AAAAARXX.BIM + // Sub/ + // AAAAASXX.MXF + // Local/ + // AAAAACXX.SMI + // AAAAACXX.PPN + // Edit/ DDDDD is the editListName + // DDDDDEXX.SMI + // DDDDDMXX.XML + // Take/ TTTTT is the Takename + // TTTTT.SMI + // TTTTTUNN.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, TTTTT.SMI shall be picked up. + // TTTTTMXX.XML + // General/ + // Sony/ + // Planning/ AAAAA is the clipname without clipserial + // YYYYMMDDHHMISS is DateTime + // BBBBB_YYYYMMDDHHMISS.xml + // UserData/ + // + + //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); + + 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); + + filePath = rootPath + kDirChar + "CUEUP.XML"; + PackageFormat_Support::AddResourceIfExists(resourceList, filePath); + filePath = rootPath + kDirChar + "CUEUP.BUP"; + PackageFormat_Support::AddResourceIfExists(resourceList, filePath); + + // Add the UserData folder which is used to identify the format in any way + filePath = rootPath + kDirChar + "UserData" + kDirChar; + PackageFormat_Support::AddResourceIfExists(resourceList, filePath); + + XMP_VarString clipPath = rootPath + kDirChar + "Clip" + kDirChar ; + + size_t oldCount = resourceList->size(); + // Get the files present inside clip folder. + XMP_VarString regExp; + XMP_StringVector regExpVec; + + regExp = "^" + clipName + ".MXF$"; + regExpVec.push_back ( regExp ); + regExp = "^" + clipName + "M\\d\\d.XML$"; + regExpVec.push_back ( regExp ); + regExp = "^" + clipName + "R\\d\\d.BIM$"; + 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 + clipPath = rootPath + kDirChar + "Local" + kDirChar ; + regExpVec.clear(); + regExp = "^" + clipName + "C\\d\\d.SMI$"; + regExpVec.push_back ( regExp ); + regExp = "^" + clipName + "I\\d\\d.PPN$"; + regExpVec.push_back ( regExp ); + oldCount = resourceList->size(); + IOUtils::GetMatchingChildren ( *resourceList, clipPath, regExpVec, false, true, true ); + + //Add the Edit lists associated to this clip + XMP_StringVector editInfoList; + bool atLeastOneFileAdded = false; + clipPath = rootPath + kDirChar + "Edit" + kDirChar ; + if ( GetInfoFilesFAM ( editInfoList , clipPath ) ) + { + size_t noOfEditInfoFiles = editInfoList.size() ; + for( size_t count = 0; count < noOfEditInfoFiles; count++ ) + { + atLeastOneFileAdded = PackageFormat_Support::AddResourceIfExists(resourceList, editInfoList[count]) ? true : atLeastOneFileAdded; + std::string editNRTFile = editInfoList[count] ; + size_t filenamelen = editInfoList[count].length() ; + editNRTFile[ filenamelen - 7 ] = 'M'; + editNRTFile[ filenamelen - 3 ] = 'X'; + editNRTFile[ filenamelen - 2 ] = 'M'; + editNRTFile[ filenamelen - 1 ] = 'L'; + atLeastOneFileAdded = PackageFormat_Support::AddResourceIfExists(resourceList, editNRTFile ) ? true : atLeastOneFileAdded; + } + } + // 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 + XMP_StringVector takeList; + clipPath = rootPath + kDirChar + "Take" + kDirChar ; + if( GetInfoFilesFAM ( 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 + XMP_StringVector planList; + clipPath = rootPath + kDirChar + "General" + kDirChar + "Sony" + kDirChar+ "Planning" + kDirChar; + if( GetPlanningFilesFAM ( planList , clipPath ) ) + { + size_t noOfPlans = planList.size() ; + for( size_t count = 0; count < noOfPlans; count++ ) + { + resourceList->push_back( planList[count] ); + } + } +} // XDCAM_MetaHandler::FillFAMAssociatedResources + +// ================================================================================================= +// XDCAM_MetaHandler::FillSAMAssociatedResources +// ============================================= +void XDCAM_MetaHandler::FillSAMAssociatedResources ( std::vector * resourceList ) +{ + // The possible associated resources: + // .../MyMovie/ + // PROAV/ + // INDEX.XML + // INDEX.BUP + // DISCMETA.XML + // DISCINFO.XML + // DISCINFO.BUP + // CLPR/ + // CXXXX/ XXXX is ClipSerial and 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. + // CXXXXCNN.SMI + // CXXXXVNN.MXF + // CXXXXANN.MXF + // CXXXXRNN.BIM + // CXXXXINN.PPN + // CXXXXMNN.XML + // CXXXXSNN.MXF + // EDTR/ + // EXXXX: + // EXXXXENN.SMI + // EXXXXMNN.XML + // + 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); + + XMP_VarString clipPath = proavPath + "CLPR" + kDirChar + clipName + kDirChar; + XMP_VarString regExp; + XMP_StringVector regExpVec; + + regExp = "^" + clipName + "C\\d\\d.SMI$"; + regExpVec.push_back ( regExp ); + regExp = "^" + clipName + "M\\d\\d.XML$"; + regExpVec.push_back ( regExp ); + regExp = "^" + clipName + "V\\d\\d.MXF$"; + regExpVec.push_back ( regExp ); + regExp = "^" + clipName + "A\\d\\d.MXF$"; + regExpVec.push_back ( regExp ); + regExp = "^" + clipName + "R\\d\\d.BIM$"; + regExpVec.push_back ( regExp ); + regExp = "^" + clipName + "I\\d\\d.PPN$"; + regExpVec.push_back ( regExp ); + regExp = "^" + clipName + "S\\d\\d.MXF$"; + regExpVec.push_back ( regExp ); + IOUtils::GetMatchingChildren ( *resourceList, clipPath, regExpVec, false, true, true ); + PackageFormat_Support::AddResourceIfExists(resourceList, this->sidecarPath); + //Add the Edit lists that refer this clip + std::vector editInfoList; + if( GetEditInfoFilesSAM ( 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 ); + } + } +}// XDCAM_MetaHandler::FillSAMAssociatedResources + +// ================================================================================================= +// XDCAM_MetaHandler::FillAssociatedResources +// ====================================== +void XDCAM_MetaHandler::FillAssociatedResources ( std::vector * resourceList ) +{ + if( this->isFAM ) + FillFAMAssociatedResources ( resourceList ); + else + FillSAMAssociatedResources ( resourceList ); +} +// ================================================================================================= +// XDCAM_MetaHandler::FillMetadataFiles +// ==================================== +void XDCAM_MetaHandler::FillMetadataFiles ( std::vector * metadataFiles ) +{ + std::string noExtPath, filePath; + + if(this->isFAM) { + noExtPath = rootPath + kDirChar + "Clip" + kDirChar + clipName; + } else { + noExtPath = rootPath + kDirChar + "PROAV" + kDirChar + "CLPR" + + kDirChar + clipName + kDirChar + clipName; + } + + metadataFiles->push_back ( this->sidecarPath ); + filePath = noExtPath + "M01.XML"; + metadataFiles->push_back ( filePath ); + +} // 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 +// ====================================== + +bool XDCAM_MetaHandler::GetMediaProMetadata ( SXMPMeta * xmpObjPtr, + const std::string& clipUMID, + bool digestFound ) +{ + if (!this->isFAM) return false; + + // Build a directory string to the MEDIAPRO file. + + std::string mediaproPath; + MakeMediaproPath ( &mediaproPath ); + return XDCAM_Support::GetMediaProLegacyMetadata ( xmpObjPtr, clipUMID, mediaproPath, digestFound ); + +} + +// ================================================================================================= +// 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, umid; + this->MakeClipFilePath ( &xmlPath, "M01.XML" ); + + 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 ); + 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 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/XMPFiles/source/FileHandlers/XDCAM_Handler.hpp b/XMPFiles/source/FileHandlers/XDCAM_Handler.hpp new file mode 100644 index 0000000..f03ada8 --- /dev/null +++ b/XMPFiles/source/FileHandlers/XDCAM_Handler.hpp @@ -0,0 +1,102 @@ +#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 ... +/// +// ================================================================================================= + +extern XMPFileHandler * XDCAM_MetaHandlerCTor ( XMPFiles * parent ); + +extern bool XDCAM_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 kXDCAM_HandlerFlags = (kXMPFiles_CanInjectXMP | + kXMPFiles_CanExpand | + kXMPFiles_CanRewrite | + kXMPFiles_PrefersInPlace | + kXMPFiles_CanReconcile | + kXMPFiles_AllowsOnlyXMP | + kXMPFiles_ReturnsRawPacket | + kXMPFiles_HandlerOwnsFile | + kXMPFiles_AllowsSafeUpdate | + kXMPFiles_FolderBasedFormat); + +class XDCAM_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); }; + + XDCAM_MetaHandler ( XMPFiles * _parent ); + virtual ~XDCAM_MetaHandler(); + +private: + + XDCAM_MetaHandler() : isFAM(false), 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 CleanupLegacyXML(); + void SetSidecarPath(); + + void readXMLFile( XMP_StringPtr filePath,ExpatAdapter* &expat ); + bool GetClipUmid ( std::string &clipUmid ) ; + bool IsClipsPlanning ( std::string clipUmid , XMP_StringPtr planPath ) ; + bool RefersClipUmid ( std::string clipUmid , XMP_StringPtr editInfoPath ) ; + bool GetInfoFilesFAM ( std::vector &InfoList, std::string pathToFolder) ; + bool GetPlanningFilesFAM ( std::vector &planInfoList, std::string pathToFolder) ; + bool GetEditInfoFilesSAM ( std::vector &editInfoList ) ; + void FillFAMAssociatedResources ( std::vector * resourceList ); + void FillSAMAssociatedResources ( std::vector * resourceList ); + + bool GetMediaProMetadata ( SXMPMeta * xmpObjPtr, const std::string& clipUMID, bool digestFound ); + + std::string rootPath, clipName, xdcNS, legacyNS, sidecarPath; + + bool isFAM; + + ExpatAdapter * expat; + XML_Node * clipMetadata; // ! Don't delete, points into the Expat tree. + +}; // XDCAM_MetaHandler + +// ================================================================================================= + +#endif /* __XDCAM_Handler_hpp__ */ diff --git a/XMPFiles/source/FormatSupport/AIFF/AIFFBehavior.cpp b/XMPFiles/source/FormatSupport/AIFF/AIFFBehavior.cpp new file mode 100644 index 0000000..a97c2b0 --- /dev/null +++ b/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/XMPFiles/source/FormatSupport/AIFF/AIFFBehavior.h b/XMPFiles/source/FormatSupport/AIFF/AIFFBehavior.h new file mode 100644 index 0000000..deadac3 --- /dev/null +++ b/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/XMPFiles/source/FormatSupport/AIFF/AIFFMetadata.cpp b/XMPFiles/source/FormatSupport/AIFF/AIFFMetadata.cpp new file mode 100644 index 0000000..8fadba3 --- /dev/null +++ b/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/XMPFiles/source/FormatSupport/AIFF/AIFFMetadata.h b/XMPFiles/source/FormatSupport/AIFF/AIFFMetadata.h new file mode 100644 index 0000000..13dbf1c --- /dev/null +++ b/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/XMPFiles/source/FormatSupport/AIFF/AIFFReconcile.cpp b/XMPFiles/source/FormatSupport/AIFF/AIFFReconcile.cpp new file mode 100644 index 0000000..af91524 --- /dev/null +++ b/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/XMPFiles/source/FormatSupport/AIFF/AIFFReconcile.h b/XMPFiles/source/FormatSupport/AIFF/AIFFReconcile.h new file mode 100644 index 0000000..41e5989 --- /dev/null +++ b/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/XMPFiles/source/FormatSupport/ASF_Support.cpp b/XMPFiles/source/FormatSupport/ASF_Support.cpp new file mode 100644 index 0000000..95fd192 --- /dev/null +++ b/XMPFiles/source/FormatSupport/ASF_Support.cpp @@ -0,0 +1,1444 @@ +// ================================================================================================= +// 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 ); + + } else if (objectBase.size == 0) { + break; + } + + 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/XMPFiles/source/FormatSupport/ASF_Support.hpp b/XMPFiles/source/FormatSupport/ASF_Support.hpp new file mode 100644 index 0000000..3e170e3 --- /dev/null +++ b/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/XMPFiles/source/FormatSupport/GIF_Support.cpp b/XMPFiles/source/FormatSupport/GIF_Support.cpp new file mode 100644 index 0000000..4f2ac6c --- /dev/null +++ b/XMPFiles/source/FormatSupport/GIF_Support.cpp @@ -0,0 +1,352 @@ +// ================================================================================================= +// ADOBE SYSTEMS INCORPORATED +// Copyright 2002-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. +// +// Derived from PNG_Support.cpp by Ian Jacobi +// Ported to the CS6 version by Hubert Figuiere +// ================================================================================================= + +#include "GIF_Support.hpp" +#include + +#include "source/XIO.hpp" + +typedef std::basic_string filebuffer; + +// Don't need CRC. + +namespace GIF_Support +{ + // This only really counts for extension blocks. + enum blockType { + bGraphicControl = 0xF9, + bComment = 0xFE, + bPlainText = 0x01, + bApplication = 0xFF, + // Hacky. Don't like the following, but there's no easy way. + bImage = 0x2C, + bExtension = 0x21, + bTerminator = 0x3B, + bHeader = 0x47 + }; + + // ============================================================================================= + + long OpenGIF ( XMP_IO* fileRef, BlockState & inOutBlockState ) + { + XMP_Uns64 pos = 0; + unsigned char name; + XMP_Uns32 len; + BlockData newBlock; + + pos = fileRef->Seek ( 0, kXMP_SeekFromStart ); + // header needs to be a block, mostly for our safe write. + pos = ReadHeader ( fileRef ); + if (pos < 13) + return 0; + + newBlock.pos = 0; + newBlock.len = pos; + newBlock.type = bHeader; + inOutBlockState.blocks.push_back(newBlock); + + // read first and following blocks + while ( ReadBlock ( fileRef, inOutBlockState, &name, &len, pos) ) {} + + return inOutBlockState.blocks.size(); + + } + + // ============================================================================================= + + long ReadHeader ( XMP_IO* fileRef ) + { + long bytesRead; + long headerSize; + long tableSize = 0; + long bytesPerColor = 0; + unsigned char buffer[768]; + + headerSize = 0; + bytesRead = fileRef->Read ( buffer, GIF_SIGNATURE_LEN ); + if ( bytesRead != GIF_SIGNATURE_LEN ) return 0; + if ( memcmp ( buffer, GIF_SIGNATURE_DATA, GIF_SIGNATURE_LEN) != 0 ) return 0; + headerSize += bytesRead; + bytesRead = fileRef->Read ( buffer, 3 ); + if ( bytesRead != 3 ) return 0; + if ( memcmp ( buffer, "87a", 3 ) != 0 && memcmp ( buffer, "89a", 3 ) != 0 ) return 0; + headerSize += bytesRead; + bytesRead = fileRef->Read ( buffer, 4 ); + if ( bytesRead != 4 ) return 0; + headerSize += bytesRead; + bytesRead = fileRef->Read ( buffer, 3 ); + if ( bytesRead != 3 ) return 0; + headerSize += bytesRead; + if ( buffer[0] & 0x80 ) tableSize = (1 << ((buffer[0] & 0x07) + 1)) * 3; + bytesRead = fileRef->Read ( buffer, tableSize ); + if ( bytesRead != tableSize ) return 0; + headerSize += bytesRead; + + return headerSize; + } + + // ============================================================================================= + + bool ReadBlock ( XMP_IO* fileRef, BlockState & inOutBlockState, unsigned char * blockType, XMP_Uns32 * blockLength, XMP_Uns64 & inOutPosition ) + { + try + { + XMP_Uns64 startPosition = inOutPosition; + long bytesRead; + long blockSize; + unsigned char buffer[768]; + + bytesRead = fileRef->Read ( buffer, 1 ); + if ( bytesRead != 1 ) return false; + inOutPosition += 1; + if ( buffer[0] == bImage ) + { + // Image is a special case. + long tableSize = 0; + bytesRead = fileRef->Read ( buffer, 4 ); + if ( bytesRead != 4 ) return false; + inOutPosition += 4; + bytesRead = fileRef->Read ( buffer, 4 ); + if ( bytesRead != 4 ) return false; + inOutPosition += 4; + bytesRead = fileRef->Read ( buffer, 1 ); + if ( bytesRead != 1 ) return false; + inOutPosition += 1; + if ( buffer[0] & 0x80 ) tableSize = (1 << ((buffer[0] & 0x07) + 1)) * 3; + bytesRead = fileRef->Read ( buffer, tableSize ); + if ( bytesRead != tableSize ) return 0; + inOutPosition += tableSize; + bytesRead = fileRef->Read ( buffer, 1 ); + if ( bytesRead != 1 ) return false; + inOutPosition += 1; + bytesRead = fileRef->Read ( buffer, 1 ); + if ( bytesRead != 1 ) return false; + inOutPosition += 1; + tableSize = buffer[0]; + while ( tableSize != 0x00 ) + { + bytesRead = fileRef->Read ( buffer, tableSize ); + if ( bytesRead != tableSize ) return false; + inOutPosition += tableSize; + bytesRead = fileRef->Read ( buffer, 1 ); + if ( bytesRead != 1 ) return false; + inOutPosition += 1; + tableSize = buffer[0]; + } + + BlockData newBlock; + + newBlock.pos = startPosition; + newBlock.len = inOutPosition - startPosition; + newBlock.type = bImage; + + inOutBlockState.blocks.push_back ( newBlock ); + } + else if ( buffer[0] == bExtension ) + { + unsigned char type; + long tableSize = 0; + + BlockData newBlock; + + newBlock.pos = startPosition; + + bytesRead = fileRef->Read ( buffer, 1 ); + if ( bytesRead != 1 ) return false; + inOutPosition += 1; + type = buffer[0]; + newBlock.type = type; + + bytesRead = fileRef->Read ( buffer, 1 ); + if ( bytesRead != 1 ) return false; + inOutPosition += 1; + tableSize = buffer[0]; + while ( tableSize != 0x00 ) + { + bytesRead = fileRef->Read ( buffer, tableSize ); + if ( bytesRead != tableSize ) return false; + inOutPosition += tableSize; + if ( inOutPosition - startPosition == 14 && type == bApplication ) + { + // Check for XMP identifier... + CheckApplicationBlockHeader ( fileRef, inOutBlockState, newBlock, inOutPosition ); + + if ( newBlock.xmp == true ) + { + newBlock.len = inOutPosition - startPosition; + + inOutBlockState.blocks.push_back ( newBlock ); + + return true; + } + } + bytesRead = fileRef->Read ( buffer, 1 ); + if ( bytesRead != 1 ) return false; + inOutPosition += 1; + tableSize = buffer[0]; + } + + newBlock.len = inOutPosition - startPosition; + + inOutBlockState.blocks.push_back ( newBlock ); + } + else if ( buffer[0] == bTerminator ) + { + BlockData newBlock; + + newBlock.pos = startPosition; + newBlock.len = 1; + newBlock.type = buffer[0]; + + inOutBlockState.blocks.push_back ( newBlock ); + } + + } catch ( ... ) { + + return false; + + } + + return true; + + } + + // ============================================================================================= + + bool WriteXMPBlock ( XMP_IO* fileRef, XMP_Uns32 len, const char* inBuffer ) + { + bool ret = false; + unsigned long datalen = (APPLICATION_HEADER_LEN + len + MAGIC_TRAILER_LEN); + unsigned char* buffer = new unsigned char[datalen]; + + try + { + size_t pos = 0; + memcpy(&buffer[pos], APPLICATION_HEADER_DATA, APPLICATION_HEADER_LEN); + pos += APPLICATION_HEADER_LEN; + memcpy(&buffer[pos], inBuffer, len); + pos += len; + memcpy(&buffer[pos], MAGIC_TRAILER_DATA, MAGIC_TRAILER_LEN); + + fileRef->Write(buffer, datalen); + + ret = true; + } + catch ( ... ) {} + + delete [] buffer; + + return ret; + } + + // ============================================================================================= + + bool CopyBlock ( XMP_IO* sourceRef, XMP_IO* destRef, BlockData& block ) + { + try + { + sourceRef->Seek ( block.pos, kXMP_SeekFromStart ); + XIO::Copy (sourceRef, destRef, (block.len)); + + } catch ( ... ) { + + return false; + + } + + return true; + } + + // ============================================================================================= + + // Don't need CRC. + + // ============================================================================================= + + unsigned long CheckApplicationBlockHeader ( XMP_IO* fileRef, BlockState& inOutBlockState, BlockData& inOutBlockData, XMP_Uns64& inOutPosition ) + { + try + { + fileRef->Seek((inOutBlockData.pos), kXMP_SeekFromStart); + + unsigned char buffer[256]; + long bytesRead = fileRef->Read ( buffer, APPLICATION_HEADER_LEN ); + + if (bytesRead == APPLICATION_HEADER_LEN) + { + if (memcmp(buffer, APPLICATION_HEADER_DATA, APPLICATION_HEADER_LEN) == 0) + { + // We still have to go through all of the data... + long tableSize = 0; + long xmpSize; + + inOutPosition = inOutBlockData.pos + APPLICATION_HEADER_LEN; + inOutBlockState.xmpPos = inOutPosition; + bytesRead = fileRef->Read ( buffer, 1 ); + if ( bytesRead != 1 ) return 0; + inOutPosition += 1; + tableSize = buffer[0]; + while ( tableSize != 0x00 ) + { + bytesRead = fileRef->Read ( buffer, tableSize ); + if ( bytesRead != tableSize ) return false; + inOutPosition += tableSize; + bytesRead = fileRef->Read ( buffer, 1 ); + if ( bytesRead != 1 ) return false; + inOutPosition += 1; + tableSize = buffer[0]; + } + inOutBlockState.xmpLen = inOutPosition - inOutBlockData.pos - APPLICATION_HEADER_LEN - MAGIC_TRAILER_LEN; + inOutBlockState.xmpBlock = inOutBlockData; + inOutBlockData.xmp = true; + } + } + } + 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; + } + +} // namespace GIF_Support diff --git a/XMPFiles/source/FormatSupport/GIF_Support.hpp b/XMPFiles/source/FormatSupport/GIF_Support.hpp new file mode 100644 index 0000000..aa81b8a --- /dev/null +++ b/XMPFiles/source/FormatSupport/GIF_Support.hpp @@ -0,0 +1,71 @@ +#ifndef __GIF_Support_hpp__ +#define __GIF_Support_hpp__ 1 + +// ================================================================================================= +// ADOBE SYSTEMS INCORPORATED +// Copyright 2002-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 +// +// Derived from PNG_Support.hpp by Ian Jacobi +// ================================================================================================= + +#include "XMP_Environment.h" // ! This must be the first include. + +#include "XMPFiles/source/XMPFiles_Impl.hpp" + +#define GIF_SIGNATURE_LEN 3 +#define GIF_SIGNATURE_DATA "\x47\x49\x46" + +#define APPLICATION_HEADER_LEN 14 +#define APPLICATION_HEADER_DATA "\x21\xFF\x0B\x58\x4D\x50\x20\x44\x61\x74\x61\x58\x4D\x50" + +#define MAGIC_TRAILER_LEN 258 +#define MAGIC_TRAILER_DATA "\x01\xFF\xFE\xFD\xFC\xFB\xFA\xF9\xF8\xF7\xF6\xF5\xF4\xF3\xF2\xF1\xF0\xEF\xEE\xED\xEC\xEB\xEA\xE9\xE8\xE7\xE6\xE5\xE4\xE3\xE2\xE1\xE0\xDF\xDE\xDD\xDC\xDB\xDA\xD9\xD8\xD7\xD6\xD5\xD4\xD3\xD2\xD1\xD0\xCF\xCE\xCD\xCC\xCB\xCA\xC9\xC8\xC7\xC6\xC5\xC4\xC3\xC2\xC1\xC0\xBF\xBE\xBD\xBC\xBB\xBA\xB9\xB8\xB7\xB6\xB5\xB4\xB3\xB2\xB1\xB0\xAF\xAE\xAD\xAC\xAB\xAA\xA9\xA8\xA7\xA6\xA5\xA4\xA3\xA2\xA1\xA0\x9F\x9E\x9D\x9C\x9B\x9A\x99\x98\x97\x96\x95\x94\x93\x92\x91\x90\x8F\x8E\x8D\x8C\x8B\x8A\x89\x88\x87\x86\x85\x84\x83\x82\x81\x80\x7F\x7E\x7D\x7C\x7B\x7A\x79\x78\x77\x76\x75\x74\x73\x72\x71\x70\x6F\x6E\x6D\x6C\x6B\x6A\x69\x68\x67\x66\x65\x64\x63\x62\x61\x60\x5F\x5E\x5D\x5C\x5B\x5A\x59\x58\x57\x56\x55\x54\x53\x52\x51\x50\x4F\x4E\x4D\x4C\x4B\x4A\x49\x48\x47\x46\x45\x44\x43\x42\x41\x40\x3F\x3E\x3D\x3C\x3B\x3A\x39\x38\x37\x36\x35\x34\x33\x32\x31\x30\x2F\x2E\x2D\x2C\x2B\x2A\x29\x28\x27\x26\x25\x24\x23\x22\x21\x20\x1F\x1E\x1D\x1C\x1B\x1A\x19\x18\x17\x16\x15\x14\x13\x12\x11\x10\x0F\x0E\x0D\x0C\x0B\x0A\x09\x08\x07\x06\x05\x04\x03\x02\x01\x00\x00" + +namespace GIF_Support +{ + class BlockData + { + public: + BlockData() : pos(0), len(0), type(0), xmp(false) {} + virtual ~BlockData() {} + + XMP_Uns64 pos; // file offset of block + XMP_Uns32 len; // length of block data, including extension introducer and label + char type; // name/type of block + bool xmp; // application extension block with XMP ? + }; + + typedef std::vector BlockVector; + typedef BlockVector::iterator BlockIterator; + + class BlockState + { + public: + BlockState() : xmpPos(0), xmpLen(0) {} + virtual ~BlockState() {} + + XMP_Uns64 xmpPos; + XMP_Uns32 xmpLen; + BlockData xmpBlock; + BlockVector blocks; /* vector of blocks */ + }; + + long OpenGIF ( XMP_IO* fileRef, BlockState& inOutBlockState ); + + long ReadHeader ( XMP_IO* fileRef ); + bool ReadBlock ( XMP_IO* fileRef, BlockState& inOutBlockState, unsigned char* blockType, XMP_Uns32* blockLength, XMP_Uns64& inOutPosition ); + bool WriteXMPBlock ( XMP_IO* fileRef, XMP_Uns32 len, const char* inBuffer ); + bool CopyBlock ( XMP_IO* sourceRef, XMP_IO* destRef, BlockData& block ); + + unsigned long CheckApplicationBlockHeader ( XMP_IO* fileRef, BlockState& inOutBlockState, BlockData& inOutBlockData, XMP_Uns64& inOutPosition ); + + 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 ); + +} // namespace GIF_Support + +#endif // __GIF_Support_hpp__ diff --git a/XMPFiles/source/FormatSupport/ID3_Support.cpp b/XMPFiles/source/FormatSupport/ID3_Support.cpp new file mode 100644 index 0000000..dd19c16 --- /dev/null +++ b/XMPFiles/source/FormatSupport/ID3_Support.cpp @@ -0,0 +1,892 @@ +// ================================================================================================= +// 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)) + +#if !XMP_WinBuild + 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 { + +// ================================================================================================= + +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 ) +{ + + 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 ( 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(); + + 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 ]; + 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, 0 ) ) { + + 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/XMPFiles/source/FormatSupport/ID3_Support.hpp b/XMPFiles/source/FormatSupport/ID3_Support.hpp new file mode 100644 index 0000000..1ca3107 --- /dev/null +++ b/XMPFiles/source/FormatSupport/ID3_Support.hpp @@ -0,0 +1,174 @@ +#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 + int stricmp ( const char * left, const char * right ); // Case insensitive ASCII compare. +#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 ); + + 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/XMPFiles/source/FormatSupport/IFF/Chunk.cpp b/XMPFiles/source/FormatSupport/IFF/Chunk.cpp new file mode 100644 index 0000000..e8acc9f --- /dev/null +++ b/XMPFiles/source/FormatSupport/IFF/Chunk.cpp @@ -0,0 +1,1229 @@ +// ================================================================================================= +// 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 +#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 += std::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/XMPFiles/source/FormatSupport/IFF/Chunk.h b/XMPFiles/source/FormatSupport/IFF/Chunk.h new file mode 100644 index 0000000..ef2ba47 --- /dev/null +++ b/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/XMPFiles/source/FormatSupport/IFF/ChunkController.cpp b/XMPFiles/source/FormatSupport/IFF/ChunkController.cpp new file mode 100644 index 0000000..29dec94 --- /dev/null +++ b/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), + mTrailingGarbageOffset (0), + mTrailingGarbageSize (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/XMPFiles/source/FormatSupport/IFF/ChunkController.h b/XMPFiles/source/FormatSupport/IFF/ChunkController.h new file mode 100644 index 0000000..52dea42 --- /dev/null +++ b/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/XMPFiles/source/FormatSupport/IFF/ChunkPath.cpp b/XMPFiles/source/FormatSupport/IFF/ChunkPath.cpp new file mode 100644 index 0000000..54680f8 --- /dev/null +++ b/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/XMPFiles/source/FormatSupport/IFF/ChunkPath.h b/XMPFiles/source/FormatSupport/IFF/ChunkPath.h new file mode 100644 index 0000000..30ed3c0 --- /dev/null +++ b/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/XMPFiles/source/FormatSupport/IFF/IChunkBehavior.cpp b/XMPFiles/source/FormatSupport/IFF/IChunkBehavior.cpp new file mode 100644 index 0000000..44159c2 --- /dev/null +++ b/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/XMPFiles/source/FormatSupport/IFF/IChunkBehavior.h b/XMPFiles/source/FormatSupport/IFF/IChunkBehavior.h new file mode 100644 index 0000000..418afb3 --- /dev/null +++ b/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/XMPFiles/source/FormatSupport/IFF/IChunkContainer.h b/XMPFiles/source/FormatSupport/IFF/IChunkContainer.h new file mode 100644 index 0000000..04ad425 --- /dev/null +++ b/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/XMPFiles/source/FormatSupport/IFF/IChunkData.h b/XMPFiles/source/FormatSupport/IFF/IChunkData.h new file mode 100644 index 0000000..62674e6 --- /dev/null +++ b/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/XMPFiles/source/FormatSupport/IPTC_Support.cpp b/XMPFiles/source/FormatSupport/IPTC_Support.cpp new file mode 100644 index 0000000..fece006 --- /dev/null +++ b/XMPFiles/source/FormatSupport/IPTC_Support.cpp @@ -0,0 +1,758 @@ +// ================================================================================================= +// 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, 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 */ ) +{ + 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 ( 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; + } + + } + + // 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 = dataLen ? (XMP_Uns8*) malloc ( dataLen ) : NULL; + 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; + + 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; + + 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/XMPFiles/source/FormatSupport/IPTC_Support.hpp b/XMPFiles/source/FormatSupport/IPTC_Support.hpp new file mode 100644 index 0000000..4c2d2bc --- /dev/null +++ b/XMPFiles/source/FormatSupport/IPTC_Support.hpp @@ -0,0 +1,305 @@ +#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, 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/XMPFiles/source/FormatSupport/ISOBaseMedia_Support.cpp b/XMPFiles/source/FormatSupport/ISOBaseMedia_Support.cpp new file mode 100644 index 0000000..d4a8076 --- /dev/null +++ b/XMPFiles/source/FormatSupport/ISOBaseMedia_Support.cpp @@ -0,0 +1,172 @@ +// ================================================================================================= +// 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; + + 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 ) { + info->headerSize = 8; // Normal explicit size case. + info->contentSize = u32Size - 8; + } 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; + + 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 ) { + info->headerSize = 8; // Normal explicit size case. + info->contentSize = u32Size - 8; + } 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/XMPFiles/source/FormatSupport/ISOBaseMedia_Support.hpp b/XMPFiles/source/FormatSupport/ISOBaseMedia_Support.hpp new file mode 100644 index 0000000..728293f --- /dev/null +++ b/XMPFiles/source/FormatSupport/ISOBaseMedia_Support.hpp @@ -0,0 +1,123 @@ +#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_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_Uns32 k_xmpUUID [4] = { MakeUns32BE ( 0xBE7ACFCBUL ), + MakeUns32BE ( 0x97A942E8UL ), + MakeUns32BE ( 0x9C719994UL ), + MakeUns32BE ( 0x91E3AFACUL ) }; + + 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". + BoxInfo() : boxType(0), headerSize(0), contentSize(0) {}; + }; + + // 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/XMPFiles/source/FormatSupport/MOOV_Support.cpp b/XMPFiles/source/FormatSupport/MOOV_Support.cpp new file mode 100644 index 0000000..959df44 --- /dev/null +++ b/XMPFiles/source/FormatSupport/MOOV_Support.cpp @@ -0,0 +1,555 @@ +// ================================================================================================= +// 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 ); + +} // 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); + 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 ) +{ + XMP_Enforce ( size < moovBoxSizeLimit ); + BoxNode * node = (BoxNode*)theBox; + + if ( node->contentSize == size ) { + + 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; + 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 ) +{ + 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 ); + + } + + this->SetBox ( currRef, dataPtr, size ); + +} // MOOV_Manager::SetBox + +// ================================================================================================= +// MOOV_Manager::AddChildBox +// ========================= + +MOOV_Manager::BoxRef MOOV_Manager::AddChildBox ( BoxRef parentRef, XMP_Uns32 childType, const void* dataPtr, XMP_Uns32 size ) +{ + BoxNode * parent = (BoxNode*)parentRef; + XMP_Assert ( parent != 0 ); + + 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_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.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/XMPFiles/source/FormatSupport/MOOV_Support.hpp b/XMPFiles/source/FormatSupport/MOOV_Support.hpp new file mode 100644 index 0000000..7d34ca2 --- /dev/null +++ b/XMPFiles/source/FormatSupport/MOOV_Support.hpp @@ -0,0 +1,223 @@ +#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. + BoxInfo() : boxType(0), childCount(0), contentSize(0), content(0) {}; + }; + + // --------------------------------------------------------------------------------------------- + // 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 ); + void SetBox ( const char * boxPath, const void* dataPtr, XMP_Uns32 size ); + + BoxRef AddChildBox ( BoxRef parentRef, XMP_Uns32 childType, const void * dataPtr, XMP_Uns32 size ); + + // --------------------------------------------------------------------------------------------- + // 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. + BoxList children; + 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) {}; + BoxNode ( XMP_Uns32 _offset, XMP_Uns32 _boxType, XMP_Uns32 _headerSize, XMP_Uns32 _contentSize ) + : offset(_offset), boxType(_boxType), headerSize(_headerSize), contentSize(_contentSize), changed(false) {}; + + }; + + 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/XMPFiles/source/FormatSupport/MacScriptExtracts.h b/XMPFiles/source/FormatSupport/MacScriptExtracts.h new file mode 100644 index 0000000..9856183 --- /dev/null +++ b/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/XMPFiles/source/FormatSupport/Makefile.am b/XMPFiles/source/FormatSupport/Makefile.am new file mode 100644 index 0000000..8916332 --- /dev/null +++ b/XMPFiles/source/FormatSupport/Makefile.am @@ -0,0 +1,82 @@ +# +# exempi - Makefile.am +# +# Copyright (C) 2007-2013 Hubert Figuiere +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions +# are met: +# +# 1 Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# +# 2 Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in the +# documentation and/or other materials provided with the +# distribution. +# +# 3 Neither the name of the Authors, nor the names of its +# contributors may be used to endorse or promote products derived +# from this software wit hout specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS +# FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE +# COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, +# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, +# STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED +# OF THE POSSIBILITY OF SUCH DAMAGE. +# + +noinst_LTLIBRARIES = libformatsupport.la +noinst_HEADERS = IPTC_Support.hpp PSIR_Support.hpp\ + Reconcile_Impl.hpp RIFF_Support.hpp XMPScanner.hpp\ + ID3_Support.hpp PNG_Support.hpp QuickTime_Support.hpp\ + ReconcileLegacy.hpp TIFF_Support.hpp GIF_Support.hpp + + +AM_CXXFLAGS = -Wno-multichar -Wno-ctor-dtor-privacy \ + -funsigned-char -fexceptions +AM_CPPFLAGS = -I$(top_srcdir) \ + -I$(top_srcdir)/public/include/ \ + -Wall @XMPCORE_CPPFLAGS@ + +libformatsupport_la_SOURCES = \ + PackageFormat_Support.cpp PackageFormat_Support.hpp \ + PostScript_Support.cpp PostScript_Support.hpp \ + PSIR_FileWriter.cpp Reconcile_Impl.cpp \ + ReconcileTIFF.cpp TIFF_MemoryReader.cpp \ + IPTC_Support.cpp PSIR_MemoryReader.cpp ReconcileIPTC.cpp \ + RIFF.cpp RIFF.hpp ID3_Support.cpp ID3_Support.hpp\ + RIFF_Support.cpp TIFF_Support.cpp \ + ISOBaseMedia_Support.hpp ISOBaseMedia_Support.cpp \ + PNG_Support.cpp ReconcileLegacy.cpp \ + GIF_Support.cpp \ + SWF_Support.hpp SWF_Support.cpp\ + XDCAM_Support.hpp XDCAM_Support.cpp\ + TIFF_FileWriter.cpp XMPScanner.cpp \ + MOOV_Support.cpp MOOV_Support.hpp \ + QuickTime_Support.cpp \ + ASF_Support.hpp ASF_Support.cpp \ + MacScriptExtracts.h \ + TimeConversionUtils.cpp TimeConversionUtils.hpp \ + AIFF/AIFFBehavior.cpp AIFF/AIFFBehavior.h \ + AIFF/AIFFMetadata.cpp AIFF/AIFFMetadata.h \ + AIFF/AIFFReconcile.cpp AIFF/AIFFReconcile.h \ + IFF/ChunkController.cpp IFF/Chunk.cpp IFF/ChunkPath.cpp IFF/IChunkBehavior.cpp IFF/IChunkContainer.h \ + IFF/ChunkController.h IFF/Chunk.h IFF/ChunkPath.h IFF/IChunkBehavior.h IFF/IChunkData.h \ + WAVE/BEXTMetadata.cpp WAVE/CartMetadata.h WAVE/DISPMetadata.cpp WAVE/INFOMetadata.h WAVE/WAVEBehavior.cpp WAVE/WAVEReconcile.h \ + WAVE/BEXTMetadata.h WAVE/Cr8rMetadata.cpp WAVE/DISPMetadata.h WAVE/PrmLMetadata.cpp WAVE/WAVEBehavior.h \ + WAVE/CartMetadata.cpp WAVE/Cr8rMetadata.h WAVE/INFOMetadata.cpp WAVE/PrmLMetadata.h WAVE/WAVEReconcile.cpp \ + WAVE/iXMLMetadata.h WAVE/iXMLMetadata.cpp \ + P2_Support.hpp P2_Support.cpp \ + WEBP_Support.hpp WEBP_Support.cpp \ + $(NULL) + + diff --git a/XMPFiles/source/FormatSupport/Makefile.in b/XMPFiles/source/FormatSupport/Makefile.in new file mode 100644 index 0000000..1e73f25 --- /dev/null +++ b/XMPFiles/source/FormatSupport/Makefile.in @@ -0,0 +1,833 @@ +# Makefile.in generated by automake 1.15.1 from Makefile.am. +# @configure_input@ + +# Copyright (C) 1994-2017 Free Software Foundation, Inc. + +# This Makefile.in is free software; the Free Software Foundation +# gives unlimited permission to copy and/or distribute it, +# with or without modifications, as long as this notice is preserved. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY, to the extent permitted by law; without +# even the implied warranty of MERCHANTABILITY or FITNESS FOR A +# PARTICULAR PURPOSE. + +@SET_MAKE@ + +# +# exempi - Makefile.am +# +# Copyright (C) 2007-2013 Hubert Figuiere +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions +# are met: +# +# 1 Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# +# 2 Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in the +# documentation and/or other materials provided with the +# distribution. +# +# 3 Neither the name of the Authors, nor the names of its +# contributors may be used to endorse or promote products derived +# from this software wit hout specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS +# FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE +# COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, +# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, +# STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED +# OF THE POSSIBILITY OF SUCH DAMAGE. +# + + +VPATH = @srcdir@ +am__is_gnu_make = { \ + if test -z '$(MAKELEVEL)'; then \ + false; \ + elif test -n '$(MAKE_HOST)'; then \ + true; \ + elif test -n '$(MAKE_VERSION)' && test -n '$(CURDIR)'; then \ + true; \ + else \ + false; \ + fi; \ +} +am__make_running_with_option = \ + case $${target_option-} in \ + ?) ;; \ + *) echo "am__make_running_with_option: internal error: invalid" \ + "target option '$${target_option-}' specified" >&2; \ + exit 1;; \ + esac; \ + has_opt=no; \ + sane_makeflags=$$MAKEFLAGS; \ + if $(am__is_gnu_make); then \ + sane_makeflags=$$MFLAGS; \ + else \ + case $$MAKEFLAGS in \ + *\\[\ \ ]*) \ + bs=\\; \ + sane_makeflags=`printf '%s\n' "$$MAKEFLAGS" \ + | sed "s/$$bs$$bs[$$bs $$bs ]*//g"`;; \ + esac; \ + fi; \ + skip_next=no; \ + strip_trailopt () \ + { \ + flg=`printf '%s\n' "$$flg" | sed "s/$$1.*$$//"`; \ + }; \ + for flg in $$sane_makeflags; do \ + test $$skip_next = yes && { skip_next=no; continue; }; \ + case $$flg in \ + *=*|--*) continue;; \ + -*I) strip_trailopt 'I'; skip_next=yes;; \ + -*I?*) strip_trailopt 'I';; \ + -*O) strip_trailopt 'O'; skip_next=yes;; \ + -*O?*) strip_trailopt 'O';; \ + -*l) strip_trailopt 'l'; skip_next=yes;; \ + -*l?*) strip_trailopt 'l';; \ + -[dEDm]) skip_next=yes;; \ + -[JT]) skip_next=yes;; \ + esac; \ + case $$flg in \ + *$$target_option*) has_opt=yes; break;; \ + esac; \ + done; \ + test $$has_opt = yes +am__make_dryrun = (target_option=n; $(am__make_running_with_option)) +am__make_keepgoing = (target_option=k; $(am__make_running_with_option)) +pkgdatadir = $(datadir)/@PACKAGE@ +pkgincludedir = $(includedir)/@PACKAGE@ +pkglibdir = $(libdir)/@PACKAGE@ +pkglibexecdir = $(libexecdir)/@PACKAGE@ +am__cd = CDPATH="$${ZSH_VERSION+.}$(PATH_SEPARATOR)" && cd +install_sh_DATA = $(install_sh) -c -m 644 +install_sh_PROGRAM = $(install_sh) -c +install_sh_SCRIPT = $(install_sh) -c +INSTALL_HEADER = $(INSTALL_DATA) +transform = $(program_transform_name) +NORMAL_INSTALL = : +PRE_INSTALL = : +POST_INSTALL = : +NORMAL_UNINSTALL = : +PRE_UNINSTALL = : +POST_UNINSTALL = : +build_triplet = @build@ +host_triplet = @host@ +subdir = XMPFiles/source/FormatSupport +ACLOCAL_M4 = $(top_srcdir)/aclocal.m4 +am__aclocal_m4_deps = $(top_srcdir)/m4/ax_cflags_gcc_option.m4 \ + $(top_srcdir)/m4/ax_cxx_compile_stdcxx_11.m4 \ + $(top_srcdir)/m4/ax_ld_check_flag.m4 \ + $(top_srcdir)/m4/ax_tls.m4 $(top_srcdir)/m4/boost.m4 \ + $(top_srcdir)/m4/libtool.m4 $(top_srcdir)/m4/ltoptions.m4 \ + $(top_srcdir)/m4/ltsugar.m4 $(top_srcdir)/m4/ltversion.m4 \ + $(top_srcdir)/m4/lt~obsolete.m4 $(top_srcdir)/configure.ac +am__configure_deps = $(am__aclocal_m4_deps) $(CONFIGURE_DEPENDENCIES) \ + $(ACLOCAL_M4) +DIST_COMMON = $(srcdir)/Makefile.am $(noinst_HEADERS) \ + $(am__DIST_COMMON) +mkinstalldirs = $(install_sh) -d +CONFIG_CLEAN_FILES = +CONFIG_CLEAN_VPATH_FILES = +LTLIBRARIES = $(noinst_LTLIBRARIES) +libformatsupport_la_LIBADD = +am__dirstamp = $(am__leading_dot)dirstamp +am_libformatsupport_la_OBJECTS = PackageFormat_Support.lo \ + PostScript_Support.lo PSIR_FileWriter.lo Reconcile_Impl.lo \ + ReconcileTIFF.lo TIFF_MemoryReader.lo IPTC_Support.lo \ + PSIR_MemoryReader.lo ReconcileIPTC.lo RIFF.lo ID3_Support.lo \ + RIFF_Support.lo TIFF_Support.lo ISOBaseMedia_Support.lo \ + PNG_Support.lo ReconcileLegacy.lo GIF_Support.lo \ + SWF_Support.lo XDCAM_Support.lo TIFF_FileWriter.lo \ + XMPScanner.lo MOOV_Support.lo QuickTime_Support.lo \ + ASF_Support.lo TimeConversionUtils.lo AIFF/AIFFBehavior.lo \ + AIFF/AIFFMetadata.lo AIFF/AIFFReconcile.lo \ + IFF/ChunkController.lo IFF/Chunk.lo IFF/ChunkPath.lo \ + IFF/IChunkBehavior.lo WAVE/BEXTMetadata.lo \ + WAVE/DISPMetadata.lo WAVE/WAVEBehavior.lo WAVE/Cr8rMetadata.lo \ + WAVE/PrmLMetadata.lo WAVE/CartMetadata.lo WAVE/INFOMetadata.lo \ + WAVE/WAVEReconcile.lo WAVE/iXMLMetadata.lo P2_Support.lo \ + WEBP_Support.lo +libformatsupport_la_OBJECTS = $(am_libformatsupport_la_OBJECTS) +AM_V_lt = $(am__v_lt_@AM_V@) +am__v_lt_ = $(am__v_lt_@AM_DEFAULT_V@) +am__v_lt_0 = --silent +am__v_lt_1 = +AM_V_P = $(am__v_P_@AM_V@) +am__v_P_ = $(am__v_P_@AM_DEFAULT_V@) +am__v_P_0 = false +am__v_P_1 = : +AM_V_GEN = $(am__v_GEN_@AM_V@) +am__v_GEN_ = $(am__v_GEN_@AM_DEFAULT_V@) +am__v_GEN_0 = @echo " GEN " $@; +am__v_GEN_1 = +AM_V_at = $(am__v_at_@AM_V@) +am__v_at_ = $(am__v_at_@AM_DEFAULT_V@) +am__v_at_0 = @ +am__v_at_1 = +DEFAULT_INCLUDES = -I.@am__isrc@ +depcomp = $(SHELL) $(top_srcdir)/depcomp +am__depfiles_maybe = depfiles +am__mv = mv -f +CXXCOMPILE = $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) \ + $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) +LTCXXCOMPILE = $(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) \ + $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) \ + $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) \ + $(AM_CXXFLAGS) $(CXXFLAGS) +AM_V_CXX = $(am__v_CXX_@AM_V@) +am__v_CXX_ = $(am__v_CXX_@AM_DEFAULT_V@) +am__v_CXX_0 = @echo " CXX " $@; +am__v_CXX_1 = +CXXLD = $(CXX) +CXXLINK = $(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) \ + $(LIBTOOLFLAGS) --mode=link $(CXXLD) $(AM_CXXFLAGS) \ + $(CXXFLAGS) $(AM_LDFLAGS) $(LDFLAGS) -o $@ +AM_V_CXXLD = $(am__v_CXXLD_@AM_V@) +am__v_CXXLD_ = $(am__v_CXXLD_@AM_DEFAULT_V@) +am__v_CXXLD_0 = @echo " CXXLD " $@; +am__v_CXXLD_1 = +COMPILE = $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) \ + $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) +LTCOMPILE = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \ + $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) \ + $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) \ + $(AM_CFLAGS) $(CFLAGS) +AM_V_CC = $(am__v_CC_@AM_V@) +am__v_CC_ = $(am__v_CC_@AM_DEFAULT_V@) +am__v_CC_0 = @echo " CC " $@; +am__v_CC_1 = +CCLD = $(CC) +LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \ + $(LIBTOOLFLAGS) --mode=link $(CCLD) $(AM_CFLAGS) $(CFLAGS) \ + $(AM_LDFLAGS) $(LDFLAGS) -o $@ +AM_V_CCLD = $(am__v_CCLD_@AM_V@) +am__v_CCLD_ = $(am__v_CCLD_@AM_DEFAULT_V@) +am__v_CCLD_0 = @echo " CCLD " $@; +am__v_CCLD_1 = +SOURCES = $(libformatsupport_la_SOURCES) +DIST_SOURCES = $(libformatsupport_la_SOURCES) +am__can_run_installinfo = \ + case $$AM_UPDATE_INFO_DIR in \ + n|no|NO) false;; \ + *) (install-info --version) >/dev/null 2>&1;; \ + esac +HEADERS = $(noinst_HEADERS) +am__tagged_files = $(HEADERS) $(SOURCES) $(TAGS_FILES) $(LISP) +# Read a list of newline-separated strings from the standard input, +# and print each of them once, without duplicates. Input order is +# *not* preserved. +am__uniquify_input = $(AWK) '\ + BEGIN { nonempty = 0; } \ + { items[$$0] = 1; nonempty = 1; } \ + END { if (nonempty) { for (i in items) print i; }; } \ +' +# Make sure the list of sources is unique. This is necessary because, +# e.g., the same source file might be shared among _SOURCES variables +# for different programs/libraries. +am__define_uniq_tagged_files = \ + list='$(am__tagged_files)'; \ + unique=`for i in $$list; do \ + if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \ + done | $(am__uniquify_input)` +ETAGS = etags +CTAGS = ctags +am__DIST_COMMON = $(srcdir)/Makefile.in $(top_srcdir)/depcomp +DISTFILES = $(DIST_COMMON) $(DIST_SOURCES) $(TEXINFOS) $(EXTRA_DIST) +ACLOCAL = @ACLOCAL@ +AMTAR = @AMTAR@ +AM_DEFAULT_VERBOSITY = @AM_DEFAULT_VERBOSITY@ +AR = @AR@ +AUTOCONF = @AUTOCONF@ +AUTOHEADER = @AUTOHEADER@ +AUTOMAKE = @AUTOMAKE@ +AWK = @AWK@ +BOOST_CPPFLAGS = @BOOST_CPPFLAGS@ +BOOST_LDPATH = @BOOST_LDPATH@ +BOOST_ROOT = @BOOST_ROOT@ +BOOST_UNIT_TEST_FRAMEWORK_LDFLAGS = @BOOST_UNIT_TEST_FRAMEWORK_LDFLAGS@ +BOOST_UNIT_TEST_FRAMEWORK_LDPATH = @BOOST_UNIT_TEST_FRAMEWORK_LDPATH@ +BOOST_UNIT_TEST_FRAMEWORK_LIBS = @BOOST_UNIT_TEST_FRAMEWORK_LIBS@ +CC = @CC@ +CCDEPMODE = @CCDEPMODE@ +CFLAGS = @CFLAGS@ +CPP = @CPP@ +CPPFLAGS = @CPPFLAGS@ +CXX = @CXX@ +CXXCPP = @CXXCPP@ +CXXDEPMODE = @CXXDEPMODE@ +CXXFLAGS = @CXXFLAGS@ +CYGPATH_W = @CYGPATH_W@ +DEFS = @DEFS@ +DEPDIR = @DEPDIR@ +DISTCHECK_CONFIGURE_FLAGS = @DISTCHECK_CONFIGURE_FLAGS@ +DLLTOOL = @DLLTOOL@ +DSYMUTIL = @DSYMUTIL@ +DUMPBIN = @DUMPBIN@ +ECHO_C = @ECHO_C@ +ECHO_N = @ECHO_N@ +ECHO_T = @ECHO_T@ +EGREP = @EGREP@ +EXEEXT = @EXEEXT@ +EXEMPI_AGE = @EXEMPI_AGE@ +EXEMPI_CURRENT = @EXEMPI_CURRENT@ +EXEMPI_CURRENT_MIN = @EXEMPI_CURRENT_MIN@ +EXEMPI_INCLUDE_BASE = @EXEMPI_INCLUDE_BASE@ +EXEMPI_MAJOR_VERSION = @EXEMPI_MAJOR_VERSION@ +EXEMPI_PLATFORM_DEF = @EXEMPI_PLATFORM_DEF@ +EXEMPI_REVISION = @EXEMPI_REVISION@ +EXEMPI_VERSION_INFO = @EXEMPI_VERSION_INFO@ +FGREP = @FGREP@ +GREP = @GREP@ +HAVE_CXX11 = @HAVE_CXX11@ +INSTALL = @INSTALL@ +INSTALL_DATA = @INSTALL_DATA@ +INSTALL_PROGRAM = @INSTALL_PROGRAM@ +INSTALL_SCRIPT = @INSTALL_SCRIPT@ +INSTALL_STRIP_PROGRAM = @INSTALL_STRIP_PROGRAM@ +LD = @LD@ +LDFLAGS = @LDFLAGS@ +LIBOBJS = @LIBOBJS@ +LIBS = @LIBS@ +LIBTOOL = @LIBTOOL@ +LIPO = @LIPO@ +LN_S = @LN_S@ +LTLIBOBJS = @LTLIBOBJS@ +LT_SYS_LIBRARY_PATH = @LT_SYS_LIBRARY_PATH@ +MAINT = @MAINT@ +MAKEINFO = @MAKEINFO@ +MANIFEST_TOOL = @MANIFEST_TOOL@ +MKDIR_P = @MKDIR_P@ +NM = @NM@ +NMEDIT = @NMEDIT@ +OBJDUMP = @OBJDUMP@ +OBJEXT = @OBJEXT@ +OTOOL = @OTOOL@ +OTOOL64 = @OTOOL64@ +PACKAGE = @PACKAGE@ +PACKAGE_BUGREPORT = @PACKAGE_BUGREPORT@ +PACKAGE_NAME = @PACKAGE_NAME@ +PACKAGE_STRING = @PACKAGE_STRING@ +PACKAGE_TARNAME = @PACKAGE_TARNAME@ +PACKAGE_URL = @PACKAGE_URL@ +PACKAGE_VERSION = @PACKAGE_VERSION@ +PATH_SEPARATOR = @PATH_SEPARATOR@ +RANLIB = @RANLIB@ +SED = @SED@ +SET_MAKE = @SET_MAKE@ +SHELL = @SHELL@ +STRIP = @STRIP@ +VALGRIND = @VALGRIND@ +VERSION = @VERSION@ +XMPCORE_CPPFLAGS = @XMPCORE_CPPFLAGS@ +abs_builddir = @abs_builddir@ +abs_srcdir = @abs_srcdir@ +abs_top_builddir = @abs_top_builddir@ +abs_top_srcdir = @abs_top_srcdir@ +ac_ct_AR = @ac_ct_AR@ +ac_ct_CC = @ac_ct_CC@ +ac_ct_CXX = @ac_ct_CXX@ +ac_ct_DUMPBIN = @ac_ct_DUMPBIN@ +am__include = @am__include@ +am__leading_dot = @am__leading_dot@ +am__quote = @am__quote@ +am__tar = @am__tar@ +am__untar = @am__untar@ +bindir = @bindir@ +build = @build@ +build_alias = @build_alias@ +build_cpu = @build_cpu@ +build_os = @build_os@ +build_vendor = @build_vendor@ +builddir = @builddir@ +datadir = @datadir@ +datarootdir = @datarootdir@ +docdir = @docdir@ +dvidir = @dvidir@ +exec_prefix = @exec_prefix@ +host = @host@ +host_alias = @host_alias@ +host_cpu = @host_cpu@ +host_os = @host_os@ +host_vendor = @host_vendor@ +htmldir = @htmldir@ +includedir = @includedir@ +infodir = @infodir@ +install_sh = @install_sh@ +libdir = @libdir@ +libexecdir = @libexecdir@ +localedir = @localedir@ +localstatedir = @localstatedir@ +mandir = @mandir@ +mkdir_p = @mkdir_p@ +oldincludedir = @oldincludedir@ +pdfdir = @pdfdir@ +pkgconfigdir = @pkgconfigdir@ +prefix = @prefix@ +program_transform_name = @program_transform_name@ +psdir = @psdir@ +sbindir = @sbindir@ +sharedstatedir = @sharedstatedir@ +srcdir = @srcdir@ +sysconfdir = @sysconfdir@ +target_alias = @target_alias@ +top_build_prefix = @top_build_prefix@ +top_builddir = @top_builddir@ +top_srcdir = @top_srcdir@ +noinst_LTLIBRARIES = libformatsupport.la +noinst_HEADERS = IPTC_Support.hpp PSIR_Support.hpp\ + Reconcile_Impl.hpp RIFF_Support.hpp XMPScanner.hpp\ + ID3_Support.hpp PNG_Support.hpp QuickTime_Support.hpp\ + ReconcileLegacy.hpp TIFF_Support.hpp GIF_Support.hpp + +AM_CXXFLAGS = -Wno-multichar -Wno-ctor-dtor-privacy \ + -funsigned-char -fexceptions + +AM_CPPFLAGS = -I$(top_srcdir) \ + -I$(top_srcdir)/public/include/ \ + -Wall @XMPCORE_CPPFLAGS@ + +libformatsupport_la_SOURCES = \ + PackageFormat_Support.cpp PackageFormat_Support.hpp \ + PostScript_Support.cpp PostScript_Support.hpp \ + PSIR_FileWriter.cpp Reconcile_Impl.cpp \ + ReconcileTIFF.cpp TIFF_MemoryReader.cpp \ + IPTC_Support.cpp PSIR_MemoryReader.cpp ReconcileIPTC.cpp \ + RIFF.cpp RIFF.hpp ID3_Support.cpp ID3_Support.hpp\ + RIFF_Support.cpp TIFF_Support.cpp \ + ISOBaseMedia_Support.hpp ISOBaseMedia_Support.cpp \ + PNG_Support.cpp ReconcileLegacy.cpp \ + GIF_Support.cpp \ + SWF_Support.hpp SWF_Support.cpp\ + XDCAM_Support.hpp XDCAM_Support.cpp\ + TIFF_FileWriter.cpp XMPScanner.cpp \ + MOOV_Support.cpp MOOV_Support.hpp \ + QuickTime_Support.cpp \ + ASF_Support.hpp ASF_Support.cpp \ + MacScriptExtracts.h \ + TimeConversionUtils.cpp TimeConversionUtils.hpp \ + AIFF/AIFFBehavior.cpp AIFF/AIFFBehavior.h \ + AIFF/AIFFMetadata.cpp AIFF/AIFFMetadata.h \ + AIFF/AIFFReconcile.cpp AIFF/AIFFReconcile.h \ + IFF/ChunkController.cpp IFF/Chunk.cpp IFF/ChunkPath.cpp IFF/IChunkBehavior.cpp IFF/IChunkContainer.h \ + IFF/ChunkController.h IFF/Chunk.h IFF/ChunkPath.h IFF/IChunkBehavior.h IFF/IChunkData.h \ + WAVE/BEXTMetadata.cpp WAVE/CartMetadata.h WAVE/DISPMetadata.cpp WAVE/INFOMetadata.h WAVE/WAVEBehavior.cpp WAVE/WAVEReconcile.h \ + WAVE/BEXTMetadata.h WAVE/Cr8rMetadata.cpp WAVE/DISPMetadata.h WAVE/PrmLMetadata.cpp WAVE/WAVEBehavior.h \ + WAVE/CartMetadata.cpp WAVE/Cr8rMetadata.h WAVE/INFOMetadata.cpp WAVE/PrmLMetadata.h WAVE/WAVEReconcile.cpp \ + WAVE/iXMLMetadata.h WAVE/iXMLMetadata.cpp \ + P2_Support.hpp P2_Support.cpp \ + WEBP_Support.hpp WEBP_Support.cpp \ + $(NULL) + +all: all-am + +.SUFFIXES: +.SUFFIXES: .cpp .lo .o .obj +$(srcdir)/Makefile.in: @MAINTAINER_MODE_TRUE@ $(srcdir)/Makefile.am $(am__configure_deps) + @for dep in $?; do \ + case '$(am__configure_deps)' in \ + *$$dep*) \ + ( cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh ) \ + && { if test -f $@; then exit 0; else break; fi; }; \ + exit 1;; \ + esac; \ + done; \ + echo ' cd $(top_srcdir) && $(AUTOMAKE) --foreign XMPFiles/source/FormatSupport/Makefile'; \ + $(am__cd) $(top_srcdir) && \ + $(AUTOMAKE) --foreign XMPFiles/source/FormatSupport/Makefile +Makefile: $(srcdir)/Makefile.in $(top_builddir)/config.status + @case '$?' in \ + *config.status*) \ + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh;; \ + *) \ + echo ' cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__depfiles_maybe)'; \ + cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__depfiles_maybe);; \ + esac; + +$(top_builddir)/config.status: $(top_srcdir)/configure $(CONFIG_STATUS_DEPENDENCIES) + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh + +$(top_srcdir)/configure: @MAINTAINER_MODE_TRUE@ $(am__configure_deps) + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh +$(ACLOCAL_M4): @MAINTAINER_MODE_TRUE@ $(am__aclocal_m4_deps) + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh +$(am__aclocal_m4_deps): + +clean-noinstLTLIBRARIES: + -test -z "$(noinst_LTLIBRARIES)" || rm -f $(noinst_LTLIBRARIES) + @list='$(noinst_LTLIBRARIES)'; \ + locs=`for p in $$list; do echo $$p; done | \ + sed 's|^[^/]*$$|.|; s|/[^/]*$$||; s|$$|/so_locations|' | \ + sort -u`; \ + test -z "$$locs" || { \ + echo rm -f $${locs}; \ + rm -f $${locs}; \ + } +AIFF/$(am__dirstamp): + @$(MKDIR_P) AIFF + @: > AIFF/$(am__dirstamp) +AIFF/$(DEPDIR)/$(am__dirstamp): + @$(MKDIR_P) AIFF/$(DEPDIR) + @: > AIFF/$(DEPDIR)/$(am__dirstamp) +AIFF/AIFFBehavior.lo: AIFF/$(am__dirstamp) \ + AIFF/$(DEPDIR)/$(am__dirstamp) +AIFF/AIFFMetadata.lo: AIFF/$(am__dirstamp) \ + AIFF/$(DEPDIR)/$(am__dirstamp) +AIFF/AIFFReconcile.lo: AIFF/$(am__dirstamp) \ + AIFF/$(DEPDIR)/$(am__dirstamp) +IFF/$(am__dirstamp): + @$(MKDIR_P) IFF + @: > IFF/$(am__dirstamp) +IFF/$(DEPDIR)/$(am__dirstamp): + @$(MKDIR_P) IFF/$(DEPDIR) + @: > IFF/$(DEPDIR)/$(am__dirstamp) +IFF/ChunkController.lo: IFF/$(am__dirstamp) \ + IFF/$(DEPDIR)/$(am__dirstamp) +IFF/Chunk.lo: IFF/$(am__dirstamp) IFF/$(DEPDIR)/$(am__dirstamp) +IFF/ChunkPath.lo: IFF/$(am__dirstamp) IFF/$(DEPDIR)/$(am__dirstamp) +IFF/IChunkBehavior.lo: IFF/$(am__dirstamp) \ + IFF/$(DEPDIR)/$(am__dirstamp) +WAVE/$(am__dirstamp): + @$(MKDIR_P) WAVE + @: > WAVE/$(am__dirstamp) +WAVE/$(DEPDIR)/$(am__dirstamp): + @$(MKDIR_P) WAVE/$(DEPDIR) + @: > WAVE/$(DEPDIR)/$(am__dirstamp) +WAVE/BEXTMetadata.lo: WAVE/$(am__dirstamp) \ + WAVE/$(DEPDIR)/$(am__dirstamp) +WAVE/DISPMetadata.lo: WAVE/$(am__dirstamp) \ + WAVE/$(DEPDIR)/$(am__dirstamp) +WAVE/WAVEBehavior.lo: WAVE/$(am__dirstamp) \ + WAVE/$(DEPDIR)/$(am__dirstamp) +WAVE/Cr8rMetadata.lo: WAVE/$(am__dirstamp) \ + WAVE/$(DEPDIR)/$(am__dirstamp) +WAVE/PrmLMetadata.lo: WAVE/$(am__dirstamp) \ + WAVE/$(DEPDIR)/$(am__dirstamp) +WAVE/CartMetadata.lo: WAVE/$(am__dirstamp) \ + WAVE/$(DEPDIR)/$(am__dirstamp) +WAVE/INFOMetadata.lo: WAVE/$(am__dirstamp) \ + WAVE/$(DEPDIR)/$(am__dirstamp) +WAVE/WAVEReconcile.lo: WAVE/$(am__dirstamp) \ + WAVE/$(DEPDIR)/$(am__dirstamp) +WAVE/iXMLMetadata.lo: WAVE/$(am__dirstamp) \ + WAVE/$(DEPDIR)/$(am__dirstamp) + +libformatsupport.la: $(libformatsupport_la_OBJECTS) $(libformatsupport_la_DEPENDENCIES) $(EXTRA_libformatsupport_la_DEPENDENCIES) + $(AM_V_CXXLD)$(CXXLINK) $(libformatsupport_la_OBJECTS) $(libformatsupport_la_LIBADD) $(LIBS) + +mostlyclean-compile: + -rm -f *.$(OBJEXT) + -rm -f AIFF/*.$(OBJEXT) + -rm -f AIFF/*.lo + -rm -f IFF/*.$(OBJEXT) + -rm -f IFF/*.lo + -rm -f WAVE/*.$(OBJEXT) + -rm -f WAVE/*.lo + +distclean-compile: + -rm -f *.tab.c + +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/ASF_Support.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/GIF_Support.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/ID3_Support.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/IPTC_Support.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/ISOBaseMedia_Support.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/MOOV_Support.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/P2_Support.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/PNG_Support.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/PSIR_FileWriter.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/PSIR_MemoryReader.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/PackageFormat_Support.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/PostScript_Support.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/QuickTime_Support.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/RIFF.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/RIFF_Support.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/ReconcileIPTC.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/ReconcileLegacy.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/ReconcileTIFF.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/Reconcile_Impl.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/SWF_Support.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/TIFF_FileWriter.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/TIFF_MemoryReader.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/TIFF_Support.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/TimeConversionUtils.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/WEBP_Support.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/XDCAM_Support.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/XMPScanner.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@AIFF/$(DEPDIR)/AIFFBehavior.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@AIFF/$(DEPDIR)/AIFFMetadata.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@AIFF/$(DEPDIR)/AIFFReconcile.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@IFF/$(DEPDIR)/Chunk.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@IFF/$(DEPDIR)/ChunkController.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@IFF/$(DEPDIR)/ChunkPath.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@IFF/$(DEPDIR)/IChunkBehavior.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@WAVE/$(DEPDIR)/BEXTMetadata.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@WAVE/$(DEPDIR)/CartMetadata.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@WAVE/$(DEPDIR)/Cr8rMetadata.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@WAVE/$(DEPDIR)/DISPMetadata.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@WAVE/$(DEPDIR)/INFOMetadata.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@WAVE/$(DEPDIR)/PrmLMetadata.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@WAVE/$(DEPDIR)/WAVEBehavior.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@WAVE/$(DEPDIR)/WAVEReconcile.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@WAVE/$(DEPDIR)/iXMLMetadata.Plo@am__quote@ + +.cpp.o: +@am__fastdepCXX_TRUE@ $(AM_V_CXX)depbase=`echo $@ | sed 's|[^/]*$$|$(DEPDIR)/&|;s|\.o$$||'`;\ +@am__fastdepCXX_TRUE@ $(CXXCOMPILE) -MT $@ -MD -MP -MF $$depbase.Tpo -c -o $@ $< &&\ +@am__fastdepCXX_TRUE@ $(am__mv) $$depbase.Tpo $$depbase.Po +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXXCOMPILE) -c -o $@ $< + +.cpp.obj: +@am__fastdepCXX_TRUE@ $(AM_V_CXX)depbase=`echo $@ | sed 's|[^/]*$$|$(DEPDIR)/&|;s|\.obj$$||'`;\ +@am__fastdepCXX_TRUE@ $(CXXCOMPILE) -MT $@ -MD -MP -MF $$depbase.Tpo -c -o $@ `$(CYGPATH_W) '$<'` &&\ +@am__fastdepCXX_TRUE@ $(am__mv) $$depbase.Tpo $$depbase.Po +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXXCOMPILE) -c -o $@ `$(CYGPATH_W) '$<'` + +.cpp.lo: +@am__fastdepCXX_TRUE@ $(AM_V_CXX)depbase=`echo $@ | sed 's|[^/]*$$|$(DEPDIR)/&|;s|\.lo$$||'`;\ +@am__fastdepCXX_TRUE@ $(LTCXXCOMPILE) -MT $@ -MD -MP -MF $$depbase.Tpo -c -o $@ $< &&\ +@am__fastdepCXX_TRUE@ $(am__mv) $$depbase.Tpo $$depbase.Plo +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='$<' object='$@' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(LTCXXCOMPILE) -c -o $@ $< + +mostlyclean-libtool: + -rm -f *.lo + +clean-libtool: + -rm -rf .libs _libs + -rm -rf AIFF/.libs AIFF/_libs + -rm -rf IFF/.libs IFF/_libs + -rm -rf WAVE/.libs WAVE/_libs + +ID: $(am__tagged_files) + $(am__define_uniq_tagged_files); mkid -fID $$unique +tags: tags-am +TAGS: tags + +tags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files) + set x; \ + here=`pwd`; \ + $(am__define_uniq_tagged_files); \ + shift; \ + if test -z "$(ETAGS_ARGS)$$*$$unique"; then :; else \ + test -n "$$unique" || unique=$$empty_fix; \ + if test $$# -gt 0; then \ + $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \ + "$$@" $$unique; \ + else \ + $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \ + $$unique; \ + fi; \ + fi +ctags: ctags-am + +CTAGS: ctags +ctags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files) + $(am__define_uniq_tagged_files); \ + test -z "$(CTAGS_ARGS)$$unique" \ + || $(CTAGS) $(CTAGSFLAGS) $(AM_CTAGSFLAGS) $(CTAGS_ARGS) \ + $$unique + +GTAGS: + here=`$(am__cd) $(top_builddir) && pwd` \ + && $(am__cd) $(top_srcdir) \ + && gtags -i $(GTAGS_ARGS) "$$here" +cscopelist: cscopelist-am + +cscopelist-am: $(am__tagged_files) + list='$(am__tagged_files)'; \ + case "$(srcdir)" in \ + [\\/]* | ?:[\\/]*) sdir="$(srcdir)" ;; \ + *) sdir=$(subdir)/$(srcdir) ;; \ + esac; \ + for i in $$list; do \ + if test -f "$$i"; then \ + echo "$(subdir)/$$i"; \ + else \ + echo "$$sdir/$$i"; \ + fi; \ + done >> $(top_builddir)/cscope.files + +distclean-tags: + -rm -f TAGS ID GTAGS GRTAGS GSYMS GPATH tags + +distdir: $(DISTFILES) + @srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \ + topsrcdirstrip=`echo "$(top_srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \ + list='$(DISTFILES)'; \ + dist_files=`for file in $$list; do echo $$file; done | \ + sed -e "s|^$$srcdirstrip/||;t" \ + -e "s|^$$topsrcdirstrip/|$(top_builddir)/|;t"`; \ + case $$dist_files in \ + */*) $(MKDIR_P) `echo "$$dist_files" | \ + sed '/\//!d;s|^|$(distdir)/|;s,/[^/]*$$,,' | \ + sort -u` ;; \ + esac; \ + for file in $$dist_files; do \ + if test -f $$file || test -d $$file; then d=.; else d=$(srcdir); fi; \ + if test -d $$d/$$file; then \ + dir=`echo "/$$file" | sed -e 's,/[^/]*$$,,'`; \ + if test -d "$(distdir)/$$file"; then \ + find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \ + fi; \ + if test -d $(srcdir)/$$file && test $$d != $(srcdir); then \ + cp -fpR $(srcdir)/$$file "$(distdir)$$dir" || exit 1; \ + find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \ + fi; \ + cp -fpR $$d/$$file "$(distdir)$$dir" || exit 1; \ + else \ + test -f "$(distdir)/$$file" \ + || cp -p $$d/$$file "$(distdir)/$$file" \ + || exit 1; \ + fi; \ + done +check-am: all-am +check: check-am +all-am: Makefile $(LTLIBRARIES) $(HEADERS) +installdirs: +install: install-am +install-exec: install-exec-am +install-data: install-data-am +uninstall: uninstall-am + +install-am: all-am + @$(MAKE) $(AM_MAKEFLAGS) install-exec-am install-data-am + +installcheck: installcheck-am +install-strip: + if test -z '$(STRIP)'; then \ + $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \ + install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \ + install; \ + else \ + $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \ + install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \ + "INSTALL_PROGRAM_ENV=STRIPPROG='$(STRIP)'" install; \ + fi +mostlyclean-generic: + +clean-generic: + +distclean-generic: + -test -z "$(CONFIG_CLEAN_FILES)" || rm -f $(CONFIG_CLEAN_FILES) + -test . = "$(srcdir)" || test -z "$(CONFIG_CLEAN_VPATH_FILES)" || rm -f $(CONFIG_CLEAN_VPATH_FILES) + -rm -f AIFF/$(DEPDIR)/$(am__dirstamp) + -rm -f AIFF/$(am__dirstamp) + -rm -f IFF/$(DEPDIR)/$(am__dirstamp) + -rm -f IFF/$(am__dirstamp) + -rm -f WAVE/$(DEPDIR)/$(am__dirstamp) + -rm -f WAVE/$(am__dirstamp) + +maintainer-clean-generic: + @echo "This command is intended for maintainers to use" + @echo "it deletes files that may require special tools to rebuild." +clean: clean-am + +clean-am: clean-generic clean-libtool clean-noinstLTLIBRARIES \ + mostlyclean-am + +distclean: distclean-am + -rm -rf ./$(DEPDIR) AIFF/$(DEPDIR) IFF/$(DEPDIR) WAVE/$(DEPDIR) + -rm -f Makefile +distclean-am: clean-am distclean-compile distclean-generic \ + distclean-tags + +dvi: dvi-am + +dvi-am: + +html: html-am + +html-am: + +info: info-am + +info-am: + +install-data-am: + +install-dvi: install-dvi-am + +install-dvi-am: + +install-exec-am: + +install-html: install-html-am + +install-html-am: + +install-info: install-info-am + +install-info-am: + +install-man: + +install-pdf: install-pdf-am + +install-pdf-am: + +install-ps: install-ps-am + +install-ps-am: + +installcheck-am: + +maintainer-clean: maintainer-clean-am + -rm -rf ./$(DEPDIR) AIFF/$(DEPDIR) IFF/$(DEPDIR) WAVE/$(DEPDIR) + -rm -f Makefile +maintainer-clean-am: distclean-am maintainer-clean-generic + +mostlyclean: mostlyclean-am + +mostlyclean-am: mostlyclean-compile mostlyclean-generic \ + mostlyclean-libtool + +pdf: pdf-am + +pdf-am: + +ps: ps-am + +ps-am: + +uninstall-am: + +.MAKE: install-am install-strip + +.PHONY: CTAGS GTAGS TAGS all all-am check check-am clean clean-generic \ + clean-libtool clean-noinstLTLIBRARIES cscopelist-am ctags \ + ctags-am distclean distclean-compile distclean-generic \ + distclean-libtool distclean-tags distdir dvi dvi-am html \ + html-am info info-am install install-am install-data \ + install-data-am install-dvi install-dvi-am install-exec \ + install-exec-am install-html install-html-am install-info \ + install-info-am install-man install-pdf install-pdf-am \ + install-ps install-ps-am install-strip installcheck \ + installcheck-am installdirs maintainer-clean \ + maintainer-clean-generic mostlyclean mostlyclean-compile \ + mostlyclean-generic mostlyclean-libtool pdf pdf-am ps ps-am \ + tags tags-am uninstall uninstall-am + +.PRECIOUS: Makefile + + +# Tell versions [3.59,3.63) of GNU make to not export all variables. +# Otherwise a system limit (for SysV at least) may be exceeded. +.NOEXPORT: diff --git a/XMPFiles/source/FormatSupport/P2_Support.cpp b/XMPFiles/source/FormatSupport/P2_Support.cpp new file mode 100644 index 0000000..fc5a334 --- /dev/null +++ b/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 "third-party/zuid/interfaces/MD5.h" +#include + +P2_Clip::P2_Clip(const std::string & p2ClipMetadataFilePath) + try :headContentCached(false),p2XMLParser(0),p2Root(0) + ,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/XMPFiles/source/FormatSupport/P2_Support.hpp b/XMPFiles/source/FormatSupport/P2_Support.hpp new file mode 100644 index 0000000..8375d32 --- /dev/null +++ b/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 "third-party/zuid/interfaces/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():clipTitle(0),clipId(0),scaleUnit(0), + duration(0),OffsetInShot(0),topClipId(0),nextClipId(0), + prevClipId(0),shotId(0),clipMetadata(0),essenceList(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/XMPFiles/source/FormatSupport/PNG_Support.cpp b/XMPFiles/source/FormatSupport/PNG_Support.cpp new file mode 100644 index 0000000..c58fc87 --- /dev/null +++ b/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/XMPFiles/source/FormatSupport/PNG_Support.hpp b/XMPFiles/source/FormatSupport/PNG_Support.hpp new file mode 100644 index 0000000..6b0afbf --- /dev/null +++ b/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/XMPFiles/source/FormatSupport/PSIR_FileWriter.cpp b/XMPFiles/source/FormatSupport/PSIR_FileWriter.cpp new file mode 100644 index 0000000..f8ab565 --- /dev/null +++ b/XMPFiles/source/FormatSupport/PSIR_FileWriter.cpp @@ -0,0 +1,615 @@ +// ================================================================================================= +// 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. + // See bug https://bugs.freedesktop.org/show_bug.cgi?id=105204 + // If dataLen is 0xffffffff, then dataTotal might be 0 + // and therefor make the CheckFileSpace test pass. + if (dataTotal < dataLen) { + break; + } + 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 = newLength ? (XMP_Uns8*) malloc ( newLength ) : NULL; + 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/XMPFiles/source/FormatSupport/PSIR_MemoryReader.cpp b/XMPFiles/source/FormatSupport/PSIR_MemoryReader.cpp new file mode 100644 index 0000000..7432554 --- /dev/null +++ b/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/XMPFiles/source/FormatSupport/PSIR_Support.hpp b/XMPFiles/source/FormatSupport/PSIR_Support.hpp new file mode 100644 index 0000000..bd231b3 --- /dev/null +++ b/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/XMPFiles/source/FormatSupport/PackageFormat_Support.cpp b/XMPFiles/source/FormatSupport/PackageFormat_Support.cpp new file mode 100644 index 0000000..9405293 --- /dev/null +++ b/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/XMPFiles/source/FormatSupport/PackageFormat_Support.hpp b/XMPFiles/source/FormatSupport/PackageFormat_Support.hpp new file mode 100644 index 0000000..8883c9e --- /dev/null +++ b/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/XMPFiles/source/FormatSupport/PostScript_Support.cpp b/XMPFiles/source/FormatSupport/PostScript_Support.cpp new file mode 100644 index 0000000..029ff0c --- /dev/null +++ b/XMPFiles/source/FormatSupport/PostScript_Support.cpp @@ -0,0 +1,1101 @@ +// ================================================================================================= +// 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++) + { + // token[0] is invalid on an empty string. -- Hub + if(!itr->token.empty() && (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()) + { + // bug 101914 - corrupt file make us + // reach the end. -- Hub + // https://bugs.freedesktop.org/show_bug.cgi?id=101914 + break; + } + 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; + } + } + } + } + } + } + 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/XMPFiles/source/FormatSupport/PostScript_Support.hpp b/XMPFiles/source/FormatSupport/PostScript_Support.hpp new file mode 100644 index 0000000..688fa6d --- /dev/null +++ b/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/XMPFiles/source/FormatSupport/QuickTime_Support.cpp b/XMPFiles/source/FormatSupport/QuickTime_Support.cpp new file mode 100644 index 0000000..c735693 --- /dev/null +++ b/XMPFiles/source/FormatSupport/QuickTime_Support.cpp @@ -0,0 +1,1332 @@ +// ================================================================================================= +// 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)) ) + break; // 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/XMPFiles/source/FormatSupport/QuickTime_Support.hpp b/XMPFiles/source/FormatSupport/QuickTime_Support.hpp new file mode 100644 index 0000000..691e746 --- /dev/null +++ b/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/XMPFiles/source/FormatSupport/RIFF.cpp b/XMPFiles/source/FormatSupport/RIFF.cpp new file mode 100644 index 0000000..93a318c --- /dev/null +++ b/XMPFiles/source/FormatSupport/RIFF.cpp @@ -0,0 +1,884 @@ +// ================================================================================================= +// 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 ); + 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; + } + } + // 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 ); + this->oldSize += 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 ) + ); + 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; + } + 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++ ) + { + Chunk* temp1 = *iter; + Chunk* temp2 = needle; + 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/XMPFiles/source/FormatSupport/RIFF.hpp b/XMPFiles/source/FormatSupport/RIFF.hpp new file mode 100644 index 0000000..e2451e5 --- /dev/null +++ b/XMPFiles/source/FormatSupport/RIFF.hpp @@ -0,0 +1,330 @@ +#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" + + // 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/XMPFiles/source/FormatSupport/RIFF_Support.cpp b/XMPFiles/source/FormatSupport/RIFF_Support.cpp new file mode 100644 index 0000000..152a53b --- /dev/null +++ b/XMPFiles/source/FormatSupport/RIFF_Support.cpp @@ -0,0 +1,939 @@ +// ================================================================================================= +// 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" + +#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; + +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 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 + +} // 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, 0 )) + { + 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, 0 ) ) + { + setBextField( &value, (XMP_Uns8*) buffer, 0, 256 ); + xmp->DeleteProperty( bextDescription.ns, bextDescription.prop) ; + chunkUsed = true; + } + // bextOriginator ------------------------------------------------- + if ( xmp->GetProperty( bextOriginator.ns , bextOriginator.prop, &value, 0 ) ) + { + setBextField( &value, (XMP_Uns8*) buffer, 256, 32 ); + xmp->DeleteProperty( bextOriginator.ns , bextOriginator.prop ); + chunkUsed = true; + } + // bextOriginatorRef ---------------------------------------------- + if ( xmp->GetProperty( bextOriginatorRef.ns , bextOriginatorRef.prop, &value, 0 ) ) + { + 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, 0 ) ) + { + 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, 0 ) ) + { + 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, 0 ) ) + { + 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, 0 ) ) + { + 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, 0 ) ) + { + 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 ) ); + +} + +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/4 BWF Bext extension chunk ----------------------------------------------- + if ( handler->parent->format == kXMP_WAVFile ) { // applies only to WAV + exportXMPtoBextChunk ( handler, &handler->bextChunk ); + } + + // 2/4 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/4 LIST:INFO + exportXMPtoListChunk ( kChunk_LIST, kType_INFO, handler, &handler->listInfoChunk, listInfoProps ); + + // 4/4 LIST:Tdat + exportXMPtoListChunk ( kChunk_LIST, kType_Tdat, handler, &handler->listTdatChunk, listTdatProps ); + +} + +} // namespace RIFF diff --git a/XMPFiles/source/FormatSupport/RIFF_Support.hpp b/XMPFiles/source/FormatSupport/RIFF_Support.hpp new file mode 100644 index 0000000..c88599b --- /dev/null +++ b/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/XMPFiles/source/FormatSupport/ReconcileIPTC.cpp b/XMPFiles/source/FormatSupport/ReconcileIPTC.cpp new file mode 100644 index 0000000..df9d676 --- /dev/null +++ b/XMPFiles/source/FormatSupport/ReconcileIPTC.cpp @@ -0,0 +1,857 @@ +// ================================================================================================= +// 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 ) 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 ( 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 ( 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 ) { + + 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 ( 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 ( 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 ( (dsInfo.dataPtr[chPos] != ' ') && (dsInfo.dataPtr[chPos] != 0) ) { // Tolerate a missing TZ. + + if ( dsInfo.dataPtr[chPos] == '+' ) { + xmpDate.tzSign = kXMP_TimeEastOfUTC; + } else if ( 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 ( 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 ) +{ + if ( iptcDigestState == kDigestMatches ) return; // Ignore the IPTC if the digest matches. + + 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 ) continue; // GetNativeInfo returns 0 for ignored local text. + + if ( iptcDigestState == kDigestMissing ) { + 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/XMPFiles/source/FormatSupport/ReconcileLegacy.cpp b/XMPFiles/source/FormatSupport/ReconcileLegacy.cpp new file mode 100644 index 0000000..e71af09 --- /dev/null +++ b/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/XMPFiles/source/FormatSupport/ReconcileLegacy.hpp b/XMPFiles/source/FormatSupport/ReconcileLegacy.hpp new file mode 100644 index 0000000..16333a3 --- /dev/null +++ b/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/XMPFiles/source/FormatSupport/ReconcileTIFF.cpp b/XMPFiles/source/FormatSupport/ReconcileTIFF.cpp new file mode 100644 index 0000000..7e89b0e --- /dev/null +++ b/XMPFiles/source/FormatSupport/ReconcileTIFF.cpp @@ -0,0 +1,3454 @@ +// ================================================================================================= +// 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 ( !firstChar || 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; + + if ( (digestState == kDigestDiffers) || ((digestState == kDigestMissing) && (! haveXMP)) ) { + 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 = !tagInfo.dataLen || !chPtr || (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); + XMP_Assert ( (iptcDigestState == kDigestMatches) ? (! haveIPTC) : true ); + haveExif = (! haveXMP) && (! haveIPTC) && PhotoDataUtils::GetNativeInfo ( exif, kTIFF_ExifIFD, exifTag, &exifInfo ); + XMP_Assert ( (! (haveExif & haveXMP)) & (! (haveExif & haveIPTC)) ); + + if ( haveIPTC ) { + + 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); + XMP_Assert ( (iptcDigestState == kDigestMatches) ? (! haveIPTC) : true ); + 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 ) { + 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); + XMP_Assert ( (iptcDigestState == kDigestMatches) ? (! haveIPTC) : true ); + haveExif = (! haveXMP) && (! haveIPTC) && PhotoDataUtils::GetNativeInfo ( exif, kTIFF_PrimaryIFD, kTIFF_ImageDescription, &exifInfo ); + XMP_Assert ( (! (haveExif & haveXMP)) & (! (haveExif & haveIPTC)) ); + + if ( haveIPTC ) { + 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); + XMP_Assert ( (iptcDigestState == kDigestMatches) ? (! haveIPTC) : true ); + haveExif = (! haveXMP) && (! haveIPTC) && PhotoDataUtils::GetNativeInfo ( exif, kTIFF_PrimaryIFD, kTIFF_Artist, &exifInfo ); + XMP_Assert ( (! (haveExif & haveXMP)) & (! (haveExif & haveIPTC)) ); + + if ( haveIPTC ) { + 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/XMPFiles/source/FormatSupport/Reconcile_Impl.cpp b/XMPFiles/source/FormatSupport/Reconcile_Impl.cpp new file mode 100644 index 0000000..7ff57c2 --- /dev/null +++ b/XMPFiles/source/FormatSupport/Reconcile_Impl.cpp @@ -0,0 +1,473 @@ +// ================================================================================================= +// 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 + +const char * ReconcileUtils::kHexDigits = "0123456789ABCDEF"; + +// ================================================================================================= +/// \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/XMPFiles/source/FormatSupport/Reconcile_Impl.hpp b/XMPFiles/source/FormatSupport/Reconcile_Impl.hpp new file mode 100644 index 0000000..6fc357d --- /dev/null +++ b/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 "third-party/zuid/interfaces/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. + + extern const char * kHexDigits; + + 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/XMPFiles/source/FormatSupport/SWF_Support.cpp b/XMPFiles/source/FormatSupport/SWF_Support.cpp new file mode 100644 index 0000000..5ff80f1 --- /dev/null +++ b/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 "third-party/zlib/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/XMPFiles/source/FormatSupport/SWF_Support.hpp b/XMPFiles/source/FormatSupport/SWF_Support.hpp new file mode 100644 index 0000000..731d64e --- /dev/null +++ b/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 "third-party/zlib/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/XMPFiles/source/FormatSupport/TIFF_FileWriter.cpp b/XMPFiles/source/FormatSupport/TIFF_FileWriter.cpp new file mode 100644 index 0000000..da4d260 --- /dev/null +++ b/XMPFiles/source/FormatSupport/TIFF_FileWriter.cpp @@ -0,0 +1,2034 @@ +// ================================================================================================= +// 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 */ ) +{ + 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); + this->ownedStream = true; + 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 ); + + 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->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->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->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/XMPFiles/source/FormatSupport/TIFF_MemoryReader.cpp b/XMPFiles/source/FormatSupport/TIFF_MemoryReader.cpp new file mode 100644 index 0000000..fcf9e43 --- /dev/null +++ b/XMPFiles/source/FormatSupport/TIFF_MemoryReader.cpp @@ -0,0 +1,711 @@ +// ================================================================================================= +// 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. + memmove ( &ifdEntries[i-1], &ifdEntries[i], 12*(tagCount-i) ); // may overlap -- Hub + --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]; + memmove ( &ifdEntries[i], &ifdEntries[i+1], 12*(tagCount-(i+1)) ); // may overlap -- Hub + --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. + memmove ( &ifdEntries[j+1], &ifdEntries[j], 12*(i-j) ); // FAILED -- 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. + memmove ( &ifdEntries[j+1], &ifdEntries[j], 12*(i-j) ); // FAILED -- 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 ); + // Here we know that if it is NULL, it is wrong. -- Hub + // GetDataPtr will return NULL in case of overflow. + if (info->dataPtr == NULL) { + return false; + } + } + + 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. + +void TIFF_MemoryReader::ParseMemoryStream ( const void* data, XMP_Uns32 length, bool copyData /* = true */ ) +{ + // 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. + + // Find and process the primary, Exif, GPS, and Interoperability IFDs. + + XMP_Uns32 primaryIFDOffset = this->CheckTIFFHeader ( this->tiffStream, length ); + XMP_Uns32 tnailIFDOffset = 0; + + 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 +// ================================ + +XMP_Uns32 TIFF_MemoryReader::ProcessOneIFD ( XMP_Uns32 ifdOffset, XMP_Uns8 ifd ) +{ + TweakedIFDInfo& 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->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/XMPFiles/source/FormatSupport/TIFF_Support.cpp b/XMPFiles/source/FormatSupport/TIFF_Support.cpp new file mode 100644 index 0000000..08b0bc6 --- /dev/null +++ b/XMPFiles/source/FormatSupport/TIFF_Support.cpp @@ -0,0 +1,460 @@ +// ================================================================================================= +// 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" + +static const char * kTIFF_TypeNames[] = { "ShortOrLong", "BYTE", "ASCII", "SHORT", "LONG", "RATIONAL", + "SBYTE", "UNDEFINED", "SSHORT", "SLONG", "SRATIONAL", + "FLOAT", "DOUBLE" }; + +// ================================================================================================= +/// \file TIFF_Support.cpp +/// \brief Manager for parsing and serializing TIFF streams. +/// +// ================================================================================================= + +// ================================================================================================= +// TIFF_Manager::TIFF_Manager +// ========================== + +static bool sFirstCTor = true; + +TIFF_Manager::TIFF_Manager() + : GetUns16(0), GetUns32(0), GetFloat(0), GetDouble(0), + PutUns16(0), PutUns32(0), PutFloat(0), PutDouble(0), + bigEndian(false), nativeEndian(false), errorCallbackPtr( NULL ) +{ + + 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/XMPFiles/source/FormatSupport/TIFF_Support.hpp b/XMPFiles/source/FormatSupport/TIFF_Support.hpp new file mode 100644 index 0000000..e3e458b --- /dev/null +++ b/XMPFiles/source/FormatSupport/TIFF_Support.hpp @@ -0,0 +1,987 @@ +#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_LastType = 12 +}; + +static const size_t kTIFF_TypeSizes[] = { 0, 1, 1, 2, 4, 8, 1, 1, 2, 4, 8, 4, 8 }; + +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 }; + +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. + + virtual void ParseMemoryStream ( const void* data, XMP_Uns32 length, bool copyData = true ) = 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; }; + + void ParseMemoryStream ( const void* data, XMP_Uns32 length, bool copyData = true ); + 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 ); + + XMP_Uns32 ProcessOneIFD ( XMP_Uns32 ifdOffset, XMP_Uns8 ifd ); + + 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 { + XMP_Uns32 pos = GetUns32AsIs(&tifdEntry->dataOrPos); + if (pos + GetUns32AsIs (&tifdEntry->bytes) > this->tiffLength) { + // Invalid file. + // The data is past the length of the TIFF. + return NULL; + } + return (this->tiffStream + pos); + } + } + + 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 }; + + void ParseMemoryStream ( const void* data, XMP_Uns32 length, bool copyData = true ); + 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/XMPFiles/source/FormatSupport/TimeConversionUtils.cpp b/XMPFiles/source/FormatSupport/TimeConversionUtils.cpp new file mode 100644 index 0000000..2dbb459 --- /dev/null +++ b/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/XMPFiles/source/FormatSupport/TimeConversionUtils.hpp b/XMPFiles/source/FormatSupport/TimeConversionUtils.hpp new file mode 100644 index 0000000..bf7ed04 --- /dev/null +++ b/XMPFiles/source/FormatSupport/TimeConversionUtils.hpp @@ -0,0 +1,35 @@ +#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 "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/XMPFiles/source/FormatSupport/WAVE/BEXTMetadata.cpp b/XMPFiles/source/FormatSupport/WAVE/BEXTMetadata.cpp new file mode 100644 index 0000000..5b98ba0 --- /dev/null +++ b/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/XMPFiles/source/FormatSupport/WAVE/BEXTMetadata.h b/XMPFiles/source/FormatSupport/WAVE/BEXTMetadata.h new file mode 100644 index 0000000..0f57246 --- /dev/null +++ b/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