// ================================================================================================= // 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 ); 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; for ( int i = 0; i < avchdLegacyData.mNumberOfMakerEntries; ++i ) { makerID = XIO::ReadUns16_BE ( &mplFile ); makerModelCode = XIO::ReadUns16_BE ( &mplFile ); mpdStartAddress = XIO::ReadUns32_BE ( &mplFile ); // Unused bu we must read it. /*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 [16]; 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 [40]; 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 // =================================================================================================