1 /*
2 mkvmerge -- utility for splicing together matroska files
3 from component media subtypes
4
5 Distributed under the GPL v2
6 see the file COPYING for details
7 or visit https://www.gnu.org/licenses/old-licenses/gpl-2.0.html
8
9 tag helper functions
10
11 Written by Moritz Bunkus <moritz@bunkus.org>.
12 */
13
14 #include "common/common_pch.h"
15
16 #include <set>
17
18 #include "common/chapters/chapters.h"
19 #include "common/ebml.h"
20 #include "common/strings/editing.h"
21 #include "common/strings/formatting.h"
22 #include "common/strings/utf8.h"
23 #include "common/tags/tags.h"
24 #include "common/tags/target_type.h"
25
26 #include <matroska/KaxTags.h>
27 #include <matroska/KaxTag.h>
28
29 using namespace libebml;
30 using namespace libmatroska;
31
32 namespace mtx::tags {
33
34 KaxTags *
select_for_chapters(KaxTags & tags,KaxChapters & chapters)35 select_for_chapters(KaxTags &tags,
36 KaxChapters &chapters) {
37 KaxTags *new_tags = nullptr;
38
39 for (auto tag_child : tags) {
40 auto tag = dynamic_cast<KaxTag *>(tag_child);
41 if (!tag)
42 continue;
43
44 bool copy = true;
45 KaxTagTargets *targets = FindChild<KaxTagTargets>(tag);
46
47 if (targets) {
48 for (auto child : *targets) {
49 auto t_euid = dynamic_cast<KaxTagEditionUID *>(child);
50 if (t_euid && !mtx::chapters::find_edition_with_uid(chapters, t_euid->GetValue())) {
51 copy = false;
52 break;
53 }
54
55 auto t_cuid = dynamic_cast<KaxTagChapterUID *>(child);
56 if (t_cuid && !mtx::chapters::find_chapter_with_uid(chapters, t_cuid->GetValue())) {
57 copy = false;
58 break;
59 }
60 }
61 }
62
63 if (!copy)
64 continue;
65
66 if (!new_tags)
67 new_tags = new KaxTags;
68 new_tags->PushElement(*(tag->Clone()));
69 }
70
71 return new_tags;
72 }
73
74 KaxTagSimple &
find_simple(const std::string & name,EbmlMaster & m)75 find_simple(const std::string &name,
76 EbmlMaster &m) {
77 return find_simple(to_utfstring(name), m);
78 }
79
80 KaxTagSimple &
find_simple(const UTFstring & name,EbmlMaster & m)81 find_simple(const UTFstring &name,
82 EbmlMaster &m) {
83 if (Is<KaxTagSimple>(&m)) {
84 KaxTagName *tname = FindChild<KaxTagName>(&m);
85 if (tname && (name == UTFstring(*tname)))
86 return *static_cast<KaxTagSimple *>(&m);
87 }
88
89 size_t i;
90 for (i = 0; i < m.ListSize(); i++)
91 if (Is<KaxTag, KaxTagSimple>(m[i]))
92 try {
93 return find_simple(name, *static_cast<EbmlMaster *>(m[i]));
94 } catch (...) {
95 }
96
97 throw false;
98 }
99
100 std::string
get_simple_value(const std::string & name,EbmlMaster & m)101 get_simple_value(const std::string &name,
102 EbmlMaster &m) {
103 try {
104 auto tvalue = FindChild<KaxTagString>(&find_simple(name, m));
105 if (tvalue)
106 return tvalue->GetValueUTF8();
107 } catch (...) {
108 }
109
110 return "";
111 }
112
113 std::string
get_simple_name(const KaxTagSimple & tag)114 get_simple_name(const KaxTagSimple &tag) {
115 KaxTagName *tname = FindChild<KaxTagName>(&tag);
116 return tname ? tname->GetValueUTF8() : ""s;
117 }
118
119 std::string
get_simple_value(const KaxTagSimple & tag)120 get_simple_value(const KaxTagSimple &tag) {
121 KaxTagString *tstring = FindChild<KaxTagString>(&tag);
122 return tstring ? tstring->GetValueUTF8() : ""s;
123 }
124
125 void
set_simple_name(KaxTagSimple & tag,const std::string & name)126 set_simple_name(KaxTagSimple &tag,
127 const std::string &name) {
128 GetChild<KaxTagName>(tag).SetValueUTF8(name);
129 }
130
131 void
set_simple_value(KaxTagSimple & tag,const std::string & value)132 set_simple_value(KaxTagSimple &tag,
133 const std::string &value) {
134 GetChild<KaxTagString>(tag).SetValueUTF8(value);
135 }
136
137 void
set_simple(KaxTagSimple & tag,const std::string & name,const std::string & value)138 set_simple(KaxTagSimple &tag,
139 const std::string &name,
140 const std::string &value) {
141 set_simple_name(tag, name);
142 set_simple_value(tag, value);
143 }
144
145 int64_t
get_tuid(const KaxTag & tag)146 get_tuid(const KaxTag &tag) {
147 auto targets = FindChild<KaxTagTargets>(&tag);
148 if (!targets)
149 return -1;
150
151 auto tuid = FindChild<KaxTagTrackUID>(targets);
152 if (!tuid)
153 return -1;
154
155 return tuid->GetValue();
156 }
157
158 int64_t
get_cuid(const KaxTag & tag)159 get_cuid(const KaxTag &tag) {
160 auto targets = FindChild<KaxTagTargets>(&tag);
161 if (!targets)
162 return -1;
163
164 auto cuid = FindChild<KaxTagChapterUID>(targets);
165 if (!cuid)
166 return -1;
167
168 return cuid->GetValue();
169 }
170
171 /** \brief Convert older tags to current specs
172 */
173 void
convert_old(KaxTags & tags)174 convert_old(KaxTags &tags) {
175 bool has_level_type = false;
176 try {
177 find_simple("LEVEL_TYPE", tags);
178 has_level_type = true;
179 } catch (...) {
180 }
181
182 size_t tags_idx;
183 for (tags_idx = 0; tags_idx < tags.ListSize(); tags_idx++) {
184 if (!Is<KaxTag>(tags[tags_idx]))
185 continue;
186
187 KaxTag &tag = *static_cast<KaxTag *>(tags[tags_idx]);
188
189 auto target_type_value = Track;
190 size_t tag_idx = 0;
191 while (tag_idx < tag.ListSize()) {
192 tag_idx++;
193 if (!Is<KaxTagSimple>(tag[tag_idx - 1]))
194 continue;
195
196 KaxTagSimple &simple = *static_cast<KaxTagSimple *>(tag[tag_idx - 1]);
197
198 std::string name = get_simple_name(simple);
199 std::string value = get_simple_value(simple);
200
201 if (name == "CATALOG")
202 set_simple_name(simple, "CATALOG_NUMBER");
203
204 else if (name == "DATE")
205 set_simple_name(simple, "DATE_RELEASED");
206
207 else if (name == "LEVEL_TYPE") {
208 if (value == "MEDIA")
209 target_type_value = Album;
210 tag_idx--;
211 delete tag[tag_idx];
212 tag.Remove(tag_idx);
213 }
214 }
215
216 if (!has_level_type)
217 continue;
218
219 auto targets = FindChild<KaxTagTargets>(&tag);
220 if (targets)
221 GetChild<KaxTagTargetTypeValue>(*targets).SetValue(target_type_value);
222 }
223 }
224
225 int
count_simple(EbmlMaster & master)226 count_simple(EbmlMaster &master) {
227 int count = 0;
228
229 for (auto child : master)
230 if (Is<KaxTagSimple>(child))
231 ++count;
232
233 else if (dynamic_cast<EbmlMaster *>(child))
234 count += count_simple(*static_cast<EbmlMaster *>(child));
235
236 return count;
237 }
238
239 void
remove_track_uid_targets(EbmlMaster * tag)240 remove_track_uid_targets(EbmlMaster *tag) {
241 for (auto el : *tag) {
242 if (!Is<KaxTagTargets>(el))
243 continue;
244
245 KaxTagTargets *targets = static_cast<KaxTagTargets *>(el);
246 size_t idx_target = 0;
247
248 while (targets->ListSize() > idx_target) {
249 EbmlElement *uid_el = (*targets)[idx_target];
250 if (Is<KaxTagTrackUID>(uid_el)) {
251 targets->Remove(idx_target);
252 delete uid_el;
253
254 } else
255 ++idx_target;
256 }
257 }
258 }
259
260 void
set_simple(KaxTag & tag,std::string const & name,std::string const & value,mtx::bcp47::language_c const & language)261 set_simple(KaxTag &tag,
262 std::string const &name,
263 std::string const &value,
264 mtx::bcp47::language_c const &language) {
265 KaxTagSimple *k_simple_tag = nullptr;
266
267 for (auto const &element : tag) {
268 auto s_tag = dynamic_cast<KaxTagSimple *>(element);
269 if (!s_tag || (to_utf8(FindChildValue<KaxTagName>(s_tag)) != name))
270 continue;
271
272 k_simple_tag = s_tag;
273 break;
274 }
275
276 if (!k_simple_tag) {
277 k_simple_tag = static_cast<KaxTagSimple *>(empty_ebml_master(new KaxTagSimple));
278 tag.PushElement(*k_simple_tag);
279 }
280
281 GetChild<KaxTagName>(k_simple_tag).SetValueUTF8(name);
282 GetChild<KaxTagString>(k_simple_tag).SetValueUTF8(value);
283
284 if (!language.is_valid())
285 return;
286
287 if (language.has_valid_iso639_2_code())
288 GetChild<KaxTagLangue>(k_simple_tag).SetValue(language.get_iso639_alpha_3_code());
289
290 if (!mtx::bcp47::language_c::is_disabled())
291 GetChild<KaxTagLanguageIETF>(k_simple_tag).SetValue(language.format());
292 }
293
294 void
set_target_type(KaxTag & tag,target_type_e target_type_value,std::string const & target_type)295 set_target_type(KaxTag &tag,
296 target_type_e target_type_value,
297 std::string const &target_type) {
298 auto &targets = GetChild<KaxTagTargets>(tag);
299
300 GetChild<KaxTagTargetTypeValue>(targets).SetValue(target_type_value);
301 GetChild<KaxTagTargetType>(targets).SetValue(target_type);
302 }
303
304 void
remove_elements_unsupported_by_webm(EbmlMaster & master)305 remove_elements_unsupported_by_webm(EbmlMaster &master) {
306 static auto s_supported_elements = std::map<uint32_t, bool>{};
307
308 if (s_supported_elements.empty()) {
309 s_supported_elements[ EBML_ID_VALUE(EBML_ID(KaxTags)) ] = true;
310 s_supported_elements[ EBML_ID_VALUE(EBML_ID(KaxTag)) ] = true;
311 s_supported_elements[ EBML_ID_VALUE(EBML_ID(KaxTagTargets)) ] = true;
312 s_supported_elements[ EBML_ID_VALUE(EBML_ID(KaxTagTargetTypeValue)) ] = true;
313 s_supported_elements[ EBML_ID_VALUE(EBML_ID(KaxTagTargetType)) ] = true;
314 s_supported_elements[ EBML_ID_VALUE(EBML_ID(KaxTagTrackUID)) ] = true;
315 s_supported_elements[ EBML_ID_VALUE(EBML_ID(KaxTagSimple)) ] = true;
316 s_supported_elements[ EBML_ID_VALUE(EBML_ID(KaxTagName)) ] = true;
317 s_supported_elements[ EBML_ID_VALUE(EBML_ID(KaxTagLangue)) ] = true;
318 s_supported_elements[ EBML_ID_VALUE(EBML_ID(KaxTagLanguageIETF)) ] = true;
319 s_supported_elements[ EBML_ID_VALUE(EBML_ID(KaxTagDefault)) ] = true;
320 s_supported_elements[ EBML_ID_VALUE(EBML_ID(KaxTagString)) ] = true;
321 s_supported_elements[ EBML_ID_VALUE(EBML_ID(KaxTagBinary)) ] = true;
322 }
323
324 auto is_simple = Is<KaxTagSimple>(master);
325 auto idx = 0u;
326
327 while (idx < master.ListSize()) {
328 auto e = master[idx];
329
330 if (e && s_supported_elements[ EBML_ID_VALUE(EbmlId(*e)) ] && !(is_simple && Is<KaxTagSimple>(e))) {
331 ++idx;
332
333 auto sub_master = dynamic_cast<EbmlMaster *>(e);
334 if (sub_master)
335 remove_elements_unsupported_by_webm(*sub_master);
336
337 } else {
338 delete master[idx];
339 master.Remove(idx);
340 }
341 }
342 }
343
344 bool
remove_track_statistics(KaxTags * tags,std::optional<uint64_t> track_uid)345 remove_track_statistics(KaxTags *tags,
346 std::optional<uint64_t> track_uid) {
347 if (!tags)
348 return false;
349
350 auto tags_to_discard = std::set<std::string>{
351 "_STATISTICS_TAGS",
352 "_STATISTICS_WRITING_APP",
353 "_STATISTICS_WRITING_DATE_UTC",
354 };
355
356 auto const wanted_target_type = static_cast<unsigned int>(mtx::tags::Movie);
357
358 for (auto const &tag_elt : *tags) {
359 auto tag = dynamic_cast<KaxTag *>(tag_elt);
360 if (!tag)
361 continue;
362
363 auto targets = FindChild<KaxTagTargets>(tag);
364 if (!targets || (FindChildValue<KaxTagTargetTypeValue>(targets, wanted_target_type) != wanted_target_type))
365 continue;
366
367 for (auto const &simple_tag_elt : *tag) {
368 auto simple_tag = dynamic_cast<KaxTagSimple *>(simple_tag_elt);
369 if (!simple_tag)
370 continue;
371
372 auto simple_tag_name = mtx::tags::get_simple_name(*simple_tag);
373 if (simple_tag_name != "_STATISTICS_TAGS")
374 continue;
375
376 auto all_to_discard = mtx::string::split(mtx::tags::get_simple_value(*simple_tag), QRegularExpression{"\\s+"});
377 for (auto const &to_discard : all_to_discard)
378 tags_to_discard.insert(to_discard);
379 }
380 }
381
382 auto removed_something = false;
383
384 for (auto const &tag_name : tags_to_discard) {
385 auto removed_something_here = mtx::tags::remove_simple_tags_for<KaxTagTrackUID>(*tags, track_uid, tag_name);
386 removed_something = removed_something || removed_something_here;
387 }
388
389 return removed_something;
390 }
391
392 namespace {
393 std::string
convert_tagets_to_index(KaxTagTargets const & targets)394 convert_tagets_to_index(KaxTagTargets const &targets) {
395 std::vector<std::string> properties;
396
397 properties.emplace_back(fmt::format("TargetTypeValue:{}", FindChildValue<KaxTagTargetTypeValue>(targets, 50)));
398 properties.emplace_back(fmt::format("TargetType:{}", FindChildValue<KaxTagTargetType>( targets, ""s)));
399
400 for (auto const &child : targets)
401 if (dynamic_cast<EbmlUInteger *>(child) && !dynamic_cast<KaxTagTargetTypeValue *>(child))
402 properties.emplace_back(fmt::format("{}:{}", EBML_NAME(child), static_cast<EbmlUInteger *>(child)->GetValue()));
403
404 std::sort(properties.begin(), properties.end());
405
406 return mtx::string::join(properties, " ");
407 }
408
409 }
410
411 std::shared_ptr<libmatroska::KaxTags>
merge(std::shared_ptr<libmatroska::KaxTags> const & t1,std::shared_ptr<libmatroska::KaxTags> const & t2)412 merge(std::shared_ptr<libmatroska::KaxTags> const &t1,
413 std::shared_ptr<libmatroska::KaxTags> const &t2) {
414 if (!t1 && !t2)
415 return {};
416
417 std::map<std::string, KaxTag *> tag_by_target_type;
418 auto dest = std::make_shared<libmatroska::KaxTags>();
419
420 for (auto const &src : std::vector<std::shared_ptr<libmatroska::KaxTags>>{t1, t2}) {
421 if (!src)
422 continue;
423
424 for (auto src_child : *src) {
425 if (!dynamic_cast<KaxTag *>(src_child)) {
426 delete src_child;
427 continue;
428 }
429
430 auto tag = static_cast<KaxTag *>(src_child);
431 auto &targets = GetChild<KaxTagTargets>(*tag);
432 auto idx = convert_tagets_to_index(targets);
433 auto existing_tag_itr = tag_by_target_type.find(idx);
434
435 if (existing_tag_itr == tag_by_target_type.end()) {
436 dest->PushElement(*tag);
437 tag_by_target_type[idx] = tag;
438 continue;
439 }
440
441 for (auto const &tag_child : *tag) {
442 if (dynamic_cast<KaxTagTargets *>(tag_child))
443 delete tag_child;
444 else
445 existing_tag_itr->second->PushElement(*tag_child);
446 }
447
448 tag->RemoveAll();
449 }
450
451 src->RemoveAll();
452 }
453
454 return dest;
455 }
456
457 }
458