|
| 1 | +// ================================================================================================= |
| 2 | +// ADOBE SYSTEMS INCORPORATED |
| 3 | +// Copyright 2016 Adobe Systems Incorporated |
| 4 | +// All Rights Reserved |
| 5 | +// |
| 6 | +// NOTICE: Adobe permits you to use, modify, and distribute this file in accordance with the terms |
| 7 | +// of the Adobe license agreement accompanying it. |
| 8 | +// ================================================================================================= |
| 9 | + |
| 10 | +#include "XMPFiles/source/FileHandlers/Matroska_Handler.hpp" |
| 11 | +#include "XMPFiles/source/FormatSupport/EBML_Support.hpp" |
| 12 | +#include "XMPFiles/source/FormatSupport/EBML/ids.h" |
| 13 | +#include "XMPFiles/source/FormatSupport/EBML/exceptions.h" |
| 14 | +#include "XMPFiles/source/FormatSupport/EBML/variant.h" |
| 15 | +#include "XMPFiles/source/FormatSupport/EBML/element.h" |
| 16 | + |
| 17 | +using namespace ebml; |
| 18 | + |
| 19 | +// ================================================================================================= |
| 20 | +// Matroska_CheckFormat |
| 21 | +// =============== |
| 22 | +// |
| 23 | +// A Matroska file must begin with EBML [1A][45][DF][A3], a 4 byte length |
| 24 | + |
| 25 | +class MatroskaDOM |
| 26 | +{ |
| 27 | +public: |
| 28 | + MatroskaDOM(XMP_IO * fileRef) |
| 29 | + { |
| 30 | + assert(fileRef); |
| 31 | + fileRef->Rewind(); |
| 32 | + |
| 33 | + _root = std::make_shared<ebml_element_t>(ebml_type_t::kEBMLTypeMaster, fileRef->Length()); |
| 34 | + |
| 35 | + ebml_restrictions restrictions; |
| 36 | + restrictions.max_id_length = 4; |
| 37 | + restrictions.max_size_length = 8; |
| 38 | + |
| 39 | + ReadEBMLMaster(fileRef, _root, restrictions, 0, std::bind(&MatroskaDOM::OnElement, this, std::placeholders::_1)); |
| 40 | + |
| 41 | + auto segments = _root->getElementsById(kSegment); |
| 42 | + if (segments.empty()) throw no_elements(); |
| 43 | + } |
| 44 | + bool OnElement(ebml_element_t::ptr element) |
| 45 | + { |
| 46 | + return true; |
| 47 | + } |
| 48 | + ebml_element_t::ptr _root; |
| 49 | +}; |
| 50 | + |
| 51 | +bool Matroska_CheckFormat(XMP_FileFormat format, |
| 52 | + XMP_StringPtr filePath, |
| 53 | + XMP_IO* file, |
| 54 | + XMPFiles* parent) |
| 55 | +{ |
| 56 | + IgnoreParam(format); IgnoreParam(parent); |
| 57 | + XMP_Assert(format == kXMP_MatroskaFile); |
| 58 | + |
| 59 | + try |
| 60 | + { |
| 61 | + MatroskaDOM DOM(file); |
| 62 | + |
| 63 | + return true; |
| 64 | + } |
| 65 | + catch (std::logic_error &) |
| 66 | + { |
| 67 | + return false; |
| 68 | + } |
| 69 | +} // Matroska_CheckFormat |
| 70 | + |
| 71 | +extern XMPFileHandler * Matroska_MetaHandlerCTor(XMPFiles * parent) |
| 72 | +{ |
| 73 | + return new Matroska_MetaHandler(parent); |
| 74 | +} |
| 75 | + |
| 76 | +Matroska_MetaHandler::Matroska_MetaHandler(XMPFiles* parent) |
| 77 | + : XMPFileHandler(parent) |
| 78 | +{ |
| 79 | + this->parent = parent; |
| 80 | + this->handlerFlags = kMatroska_HandlerFlags; |
| 81 | + this->stdCharForm = kXMP_Char8Bit; |
| 82 | +} |
| 83 | + |
| 84 | +void Matroska_MetaHandler::CacheFileData() |
| 85 | +{ |
| 86 | + XMP_Assert(parent && parent->ioRef); |
| 87 | + try |
| 88 | + { |
| 89 | + containsXMP = false; |
| 90 | + _dom = std::make_shared<MatroskaDOM>(parent->ioRef); |
| 91 | + |
| 92 | + for (auto segment : _dom->_root->getElementsById(kSegment)) |
| 93 | + { |
| 94 | + for (auto tags : segment->getElementsById(kTags)) |
| 95 | + { |
| 96 | + for (auto tag : tags->getElementsById(kTag)) |
| 97 | + { |
| 98 | + for (auto simple_tag : tag->getElementsById(kSimpleTag)) |
| 99 | + { |
| 100 | + auto tag_name = simple_tag->getElementById(kTagName); |
| 101 | + if (tag_name->_value.StringValue == "XMP") |
| 102 | + { |
| 103 | + auto tag_string = simple_tag->getElementById(kTagString); |
| 104 | + |
| 105 | + xmpPacket = tag_string->_value.StringValue; |
| 106 | + containsXMP = true; |
| 107 | + |
| 108 | + break; |
| 109 | + } |
| 110 | + } |
| 111 | + } |
| 112 | + } |
| 113 | + } |
| 114 | + } |
| 115 | + catch (std::logic_error & e) |
| 116 | + { |
| 117 | + e.what(); |
| 118 | + XMP_Validate(false, e.what(), kXMPErr_BadFileFormat); |
| 119 | + } |
| 120 | +} |
| 121 | + |
| 122 | +void Matroska_MetaHandler::UpdateFile(bool doSafeUpdate) |
| 123 | +{ |
| 124 | + if (!needsUpdate) return; // If needsUpdate is set then at least the XMP changed. |
| 125 | + |
| 126 | + needsUpdate = false; // Make sure only called once. |
| 127 | + XMP_Assert(!doSafeUpdate); // This should only be called for "unsafe" updates. |
| 128 | + XMP_Assert(parent && parent->ioRef); |
| 129 | + |
| 130 | + XMP_AbortProc abortProc = parent->abortProc; |
| 131 | + void * abortArg = parent->abortArg; |
| 132 | + const bool checkAbort = (abortProc != 0); |
| 133 | + |
| 134 | + bool localProgressTracking(false); |
| 135 | + XMP_ProgressTracker* progressTracker = parent->progressTracker; |
| 136 | + if (progressTracker) |
| 137 | + { |
| 138 | + float xmpSize = static_cast<float>(xmpPacket.size()); |
| 139 | + if (progressTracker->WorkInProgress()) |
| 140 | + { |
| 141 | + progressTracker->AddTotalWork(xmpSize); |
| 142 | + } |
| 143 | + else |
| 144 | + { |
| 145 | + localProgressTracking = true; |
| 146 | + progressTracker->BeginWork(xmpSize); |
| 147 | + } |
| 148 | + } |
| 149 | + UpdateXMP(); |
| 150 | + |
| 151 | + if (localProgressTracking) progressTracker->WorkComplete(); |
| 152 | +} |
| 153 | + |
| 154 | +void Matroska_MetaHandler::UpdateXMP() |
| 155 | +{ |
| 156 | + XMP_IO* fileRef = parent->ioRef; |
| 157 | + |
| 158 | + fileRef->Seek(fileRef->Length(), kXMP_SeekFromStart); |
| 159 | + |
| 160 | + for (auto segment : _dom->_root->getElementsById(kSegment)) |
| 161 | + { |
| 162 | + for (auto tags : segment->getElementsById(kTags)) |
| 163 | + { |
| 164 | + for (auto tag : tags->getElementsById(kTag)) |
| 165 | + { |
| 166 | + for (auto simple_tag : tag->getElementsById(kSimpleTag)) |
| 167 | + { |
| 168 | + auto tag_name = simple_tag->getElementById(kTagName); |
| 169 | + if (tag_name->_value.StringValue == "XMP") |
| 170 | + { |
| 171 | + auto tag_string = simple_tag->getElementById(kTagString); |
| 172 | + |
| 173 | + // we have found valid XMP, and if it's in the very end of file, we can truncate segment |
| 174 | + if (tag_string->_offset + tag_string->size() == fileRef->Length()) |
| 175 | + { |
| 176 | + segment->_size._value -= tags->size(); |
| 177 | + |
| 178 | + fileRef->Truncate(tags->_offset); |
| 179 | + fileRef->Seek(tags->_offset, kXMP_SeekFromStart); |
| 180 | + } |
| 181 | + // otherwise, make old XMP tag Void and create new one from the scratch |
| 182 | + else |
| 183 | + { |
| 184 | + tags->wipe(fileRef); |
| 185 | + } |
| 186 | + } |
| 187 | + } |
| 188 | + } |
| 189 | + } |
| 190 | + } |
| 191 | + auto segments = _dom->_root->getElementsById(kSegment); |
| 192 | + auto segment = segments.back(); |
| 193 | + |
| 194 | + auto tag_name = std::make_shared<ebml_element_t>(kTagName, ebml_variant_t("XMP")); |
| 195 | + auto tag_string = std::make_shared<ebml_element_t>(kTagString, ebml_variant_t(xmpPacket)); |
| 196 | + auto tag_language = std::make_shared<ebml_element_t>(kTagLanguage, ebml_variant_t("eng")); |
| 197 | + auto tag_default = std::make_shared<ebml_element_t>(kTagDefault, ebml_variant_t(1ULL)); |
| 198 | + auto simple_tag = std::make_shared<ebml_element_t>(kSimpleTag, ebml_element_t::vec{ tag_language, tag_default, tag_name, tag_string }); |
| 199 | + auto tag = std::make_shared<ebml_element_t>(kTag, simple_tag); |
| 200 | + auto tags = std::make_shared<ebml_element_t>(kTags, tag); |
| 201 | + |
| 202 | + tags->write(fileRef); |
| 203 | + |
| 204 | + segment->_size._value += tags->size(); |
| 205 | + segment->update_header(fileRef); |
| 206 | +} |
| 207 | + |
| 208 | +void Matroska_MetaHandler::WriteTempFile(XMP_IO* tempRef) |
| 209 | +{ |
| 210 | + XMP_Assert(needsUpdate); |
| 211 | + |
| 212 | + XMP_IO* originalRef = parent->ioRef; |
| 213 | + |
| 214 | + bool localProgressTracking(false); |
| 215 | + XMP_ProgressTracker* progressTracker = parent->progressTracker; |
| 216 | + if (progressTracker) |
| 217 | + { |
| 218 | + float xmpSize = static_cast<float>(xmpPacket.size()); |
| 219 | + if (progressTracker->WorkInProgress()) |
| 220 | + { |
| 221 | + progressTracker->AddTotalWork(xmpSize); |
| 222 | + } |
| 223 | + else |
| 224 | + { |
| 225 | + localProgressTracking = true; |
| 226 | + progressTracker->BeginWork(xmpSize); |
| 227 | + } |
| 228 | + } |
| 229 | + |
| 230 | + XMP_Assert(tempRef); |
| 231 | + XMP_Assert(originalRef); |
| 232 | + |
| 233 | + tempRef->Rewind(); |
| 234 | + originalRef->Rewind(); |
| 235 | + XIO::Copy(originalRef, tempRef, originalRef->Length(), parent->abortProc, parent->abortArg); |
| 236 | + |
| 237 | + try |
| 238 | + { |
| 239 | + parent->ioRef = tempRef; // ! Fool UpdateFile into using the temp file. |
| 240 | + UpdateFile(false); |
| 241 | + parent->ioRef = originalRef; |
| 242 | + } |
| 243 | + catch (...) |
| 244 | + { |
| 245 | + parent->ioRef = originalRef; |
| 246 | + throw; |
| 247 | + } |
| 248 | + if (localProgressTracking) progressTracker->WorkComplete(); |
| 249 | +} |
| 250 | + |
| 251 | +void Matroska_MetaHandler::ProcessXMP() |
| 252 | +{ |
| 253 | + if (processedXMP) return; |
| 254 | + processedXMP = true; // Make sure we only come through here once. |
| 255 | + |
| 256 | + // Process the XMP packet. |
| 257 | + |
| 258 | + if (!xmpPacket.empty()) |
| 259 | + { |
| 260 | + XMP_Assert(containsXMP); |
| 261 | + |
| 262 | + xmpObj.ParseFromBuffer(xmpPacket.c_str(), static_cast<XMP_StringLen>(xmpPacket.size())); |
| 263 | + |
| 264 | + containsXMP = true; |
| 265 | + } |
| 266 | +} |
0 commit comments