Blob Blame History Raw
// =================================================================================================
// 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<avchdProgramInfo.mNumberOfStreamsInPS; ++i ) {

		XMP_Uns8	length = 0;
		XMP_Uns8	streamCodingType = 0;

		streamPID = XIO::ReadUns16_BE ( &cpiFile );
		cpiFile.ReadAll ( &length, 1 );

		XMP_Int64 pos = cpiFile.Offset();

		cpiFile.ReadAll ( &streamCodingType, 1 );

		switch ( streamCodingType ) {

			case 0x1B	: // Video stream case.
				{
					XMP_Uns8 videoFormatAndFrameRate;
					cpiFile.ReadAll ( &videoFormatAndFrameRate, 1 );
					avchdProgramInfo.mVideoStream.mVideoFormat	= videoFormatAndFrameRate >> 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<const char*>( 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<const XMP_Uns32*>( 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<const UTF16Unit*> ( avchdField ), stringLength,
						  utf8Name, 512, &utf16Read, &utf8Written );
		xmpString.assign ( reinterpret_cast<const char*> ( 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<const char*> ( 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<XMP_Uns8> 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<XMP_Int32> ( 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<std::string> * 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<std::string> metadataFiles;
	FillMetadataFiles(&metadataFiles);
	std::vector<std::string>::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<std::string> * 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

// =================================================================================================