1 /**
2 * @file mediafileattribute.cpp
3 * @brief Classes for file attributes fetching
4 *
5 * (c) 2013-2017 by Mega Limited, Auckland, New Zealand
6 *
7 * This file is part of the MEGA SDK - Client Access Engine.
8 *
9 * Applications using the MEGA API must present a valid application key
10 * and comply with the the rules set forth in the Terms of Service.
11 *
12 * The MEGA SDK is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
15 *
16 * @copyright Simplified (2-clause) BSD License.
17 *
18 * You should have received a copy of the license along with this
19 * program.
20 */
21
22 #include "mega/mediafileattribute.h"
23 #include "mega/logging.h"
24 #include "mega/base64.h"
25 #include "mega/command.h"
26 #include "mega/megaclient.h"
27 #include "mega/megaapp.h"
28
29 #ifdef USE_MEDIAINFO
30 #include "MediaInfo/MediaInfo.h"
31 #include "ZenLib/Ztring.h"
32 #endif
33
34 namespace mega {
35
36 #define MEDIA_INFO_BUILD 1 // Increment this anytime we change the way we use mediainfo, eq query new or different fields etc. Needs to be coordinated with the way the webclient works also.
37
38 #ifdef USE_MEDIAINFO
39
GetMediaInfoVersion()40 uint32_t GetMediaInfoVersion()
41 {
42 static uint32_t version = 0;
43
44 if (version == 0)
45 {
46 std::string s = ZenLib::Ztring(MediaInfoLib::MediaInfo::Option_Static(__T("Info_Version")).c_str()).To_Local(); // eg. __T("MediaInfoLib - v17.10")
47 unsigned column = 1;
48 for (size_t i = s.size(); i--; )
49 {
50 if (isdigit(s[i]))
51 {
52 version += column * (s[i] - '0');
53 column *= 10;
54 }
55 else if (s[i] == 'v')
56 {
57 break;
58 }
59 }
60 assert(version != 0);
61 }
62
63 return version;
64 }
65
MediaFileInfo()66 MediaFileInfo::MediaFileInfo()
67 : mediaCodecsRequested(false)
68 , mediaCodecsReceived(false)
69 , mediaCodecsFailed(false)
70 , downloadedCodecMapsVersion(0)
71 {
72 LOG_debug << "MediaInfo version: " << GetMediaInfoVersion();
73 }
74
requestCodecMappingsOneTime(MegaClient * client,LocalPath * ifSuitableFilename)75 void MediaFileInfo::requestCodecMappingsOneTime(MegaClient* client, LocalPath* ifSuitableFilename)
76 {
77 if (!mediaCodecsReceived && !mediaCodecsRequested)
78 {
79 if (ifSuitableFilename)
80 {
81 char ext[8];
82 if (!client->fsaccess->getextension(*ifSuitableFilename, ext, sizeof(ext))
83 || !MediaProperties::isMediaFilenameExt(ext))
84 {
85 return;
86 }
87 }
88
89 LOG_debug << "Requesting code mappings";
90 client->reqs.add(new CommandMediaCodecs(client, &MediaFileInfo::onCodecMappingsReceiptStatic));
91 mediaCodecsRequested = true;
92 }
93 }
94
95 static void ReadShortFormats(std::vector<MediaFileInfo::MediaCodecs::shortformatrec>& vec, JSON& json);
96
Lookup(const std::string & name,std::map<std::string,unsigned> & data,unsigned notfoundvalue)97 unsigned MediaFileInfo::Lookup(const std::string& name, std::map<std::string, unsigned>& data, unsigned notfoundvalue)
98 {
99 size_t seppos = name.find(" / ");
100 if (seppos != std::string::npos)
101 {
102 // CodecId can contain a list in order of preference, separated by " / "
103 size_t pos = 0;
104 while (seppos != std::string::npos)
105 {
106 unsigned result = MediaFileInfo::Lookup(name.substr(pos, seppos), data, notfoundvalue);
107 if (result != notfoundvalue)
108 return result;
109 pos = seppos + 3;
110 seppos = name.find(" / ", pos);
111 }
112 return MediaFileInfo::Lookup(name.substr(pos), data, notfoundvalue);
113 }
114
115 std::map<std::string, unsigned>::iterator i = data.find(name);
116 return i == data.end() ? notfoundvalue : i->second;
117 }
118
LookupShortFormat(unsigned containerid,unsigned videocodecid,unsigned audiocodecid)119 byte MediaFileInfo::LookupShortFormat(unsigned containerid, unsigned videocodecid, unsigned audiocodecid)
120 {
121 for (size_t i = mediaCodecs.shortformats.size(); i--; )
122 {
123 // only 256 entries max, so iterating will be very quick
124 MediaCodecs::shortformatrec& r = mediaCodecs.shortformats[i];
125 if (r.containerid == containerid && r.videocodecid == videocodecid && r.audiocodecid == audiocodecid)
126 {
127 return r.shortformatid;
128 }
129 }
130 return 0; // 0 indicates an exotic combination, which requires attribute 9
131 }
132
ReadIdRecords(std::map<std::string,unsigned> & data,JSON & json)133 void MediaFileInfo::ReadIdRecords(std::map<std::string, unsigned>& data, JSON& json)
134 {
135 if (json.enterarray())
136 {
137 while (json.enterarray())
138 {
139 assert(json.isnumeric());
140 m_off_t id = json.getint();
141 std::string name;
142 if (json.storeobject(&name) && id > 0)
143 {
144 data[name] = (unsigned) id;
145 }
146 json.leavearray();
147 }
148 json.leavearray();
149 }
150 }
151
ReadShortFormats(std::vector<MediaFileInfo::MediaCodecs::shortformatrec> & vec,JSON & json)152 static void ReadShortFormats(std::vector<MediaFileInfo::MediaCodecs::shortformatrec>& vec, JSON& json)
153 {
154 bool working = json.enterarray();
155 if (working)
156 {
157 while ((working = json.enterarray()))
158 {
159 MediaFileInfo::MediaCodecs::shortformatrec rec;
160 unsigned id = atoi(json.getvalue());
161 assert(id < 256);
162 std::string a, b, c;
163 working = json.storeobject(&a) && json.storeobject(&b) && json.storeobject(&c);
164 if (working)
165 {
166 rec.shortformatid = byte(id);
167 rec.containerid = atoi(a.c_str());
168 rec.videocodecid = atoi(b.c_str());
169 rec.audiocodecid = atoi(c.c_str());
170 vec.push_back(rec);
171 }
172 json.leavearray();
173 }
174 json.leavearray();
175 }
176 }
177
onCodecMappingsReceiptStatic(MegaClient * client,int codecListVersion)178 void MediaFileInfo::onCodecMappingsReceiptStatic(MegaClient* client, int codecListVersion)
179 {
180 client->mediaFileInfo.onCodecMappingsReceipt(client, codecListVersion);
181 }
182
onCodecMappingsReceipt(MegaClient * client,int codecListVersion)183 void MediaFileInfo::onCodecMappingsReceipt(MegaClient* client, int codecListVersion)
184 {
185 if (codecListVersion < 0)
186 {
187 LOG_err << "Error getting media codec mappings";
188
189 mediaCodecsFailed = true;
190 queuedForDownloadTranslation.clear();
191 }
192 else
193 {
194 LOG_debug << "Media codec mappings correctly received";
195
196 downloadedCodecMapsVersion = codecListVersion;
197 assert(downloadedCodecMapsVersion < 10000);
198 client->json.enterarray();
199 ReadIdRecords(mediaCodecs.containers, client->json);
200 ReadIdRecords(mediaCodecs.videocodecs, client->json);
201 ReadIdRecords(mediaCodecs.audiocodecs, client->json);
202 ReadShortFormats(mediaCodecs.shortformats, client->json);
203 client->json.leavearray();
204 mediaCodecsReceived = true;
205
206 // update any download transfers we already processed
207 for (size_t i = queuedForDownloadTranslation.size(); i--; )
208 {
209 queuedvp& q = queuedForDownloadTranslation[i];
210 sendOrQueueMediaPropertiesFileAttributesForExistingFile(q.vp, q.fakey, client, q.handle);
211 }
212 queuedForDownloadTranslation.clear();
213 }
214
215 // resume any upload transfers that were waiting for this
216 for (std::map<handle, queuedvp>::iterator i = uploadFileAttributes.begin(); i != uploadFileAttributes.end(); )
217 {
218 handle th = i->second.handle;
219 ++i; // the call below may remove this item from the map
220
221 // indicate that file attribute 8 can be retrieved now, allowing the transfer to complete
222 client->pendingfa[pair<handle, fatype>(th, fatype(fa_media))] = pair<handle, int>(0, 0);
223 client->checkfacompletion(th);
224 }
225
226 client->app->mediadetection_ready();
227 }
228
queueMediaPropertiesFileAttributesForUpload(MediaProperties & vp,uint32_t fakey[4],MegaClient * client,handle uploadHandle)229 unsigned MediaFileInfo::queueMediaPropertiesFileAttributesForUpload(MediaProperties& vp, uint32_t fakey[4], MegaClient* client, handle uploadHandle)
230 {
231 if (mediaCodecsFailed)
232 {
233 return 0; // we can't do it - let the transfer complete anyway
234 }
235
236 MediaFileInfo::queuedvp q;
237 q.handle = uploadHandle;
238 q.vp = vp;
239 memcpy(q.fakey, fakey, sizeof(q.fakey));
240 uploadFileAttributes[uploadHandle] = q;
241 LOG_debug << "Media attribute enqueued for upload";
242
243 if (mediaCodecsReceived)
244 {
245 // indicate we have this attribute ready to go. Otherwise the transfer will be put on hold till we can
246 client->pendingfa[pair<handle, fatype>(uploadHandle, fatype(fa_media))] = pair<handle, int>(0, 0);
247 }
248 return 1;
249 }
250
sendOrQueueMediaPropertiesFileAttributesForExistingFile(MediaProperties & vp,uint32_t fakey[4],MegaClient * client,handle fileHandle)251 void MediaFileInfo::sendOrQueueMediaPropertiesFileAttributesForExistingFile(MediaProperties& vp, uint32_t fakey[4], MegaClient* client, handle fileHandle)
252 {
253 if (mediaCodecsFailed)
254 {
255 return; // we can't do it
256 }
257
258 if (!mediaCodecsReceived)
259 {
260 MediaFileInfo::queuedvp q;
261 q.handle = fileHandle;
262 q.vp = vp;
263 memcpy(q.fakey, fakey, sizeof(q.fakey));
264 queuedForDownloadTranslation.push_back(q);
265 LOG_debug << "Media attribute enqueued for existing file";
266 }
267 else
268 {
269 LOG_debug << "Sending media attributes";
270 std::string mediafileattributes = vp.convertMediaPropertyFileAttributes(fakey, client->mediaFileInfo);
271 client->reqs.add(new CommandAttachFA(client, fileHandle, fa_media, mediafileattributes.c_str(), 0));
272 }
273 }
274
addUploadMediaFileAttributes(handle & uploadhandle,std::string * s)275 void MediaFileInfo::addUploadMediaFileAttributes(handle& uploadhandle, std::string* s)
276 {
277 std::map<handle, MediaFileInfo::queuedvp>::iterator i = uploadFileAttributes.find(uploadhandle);
278 if (i != uploadFileAttributes.end())
279 {
280 if (!mediaCodecsFailed)
281 {
282 if (!s->empty())
283 {
284 *s += "/";
285 }
286 *s += i->second.vp.convertMediaPropertyFileAttributes(i->second.fakey, *this);
287 LOG_debug << "Media attributes added to putnodes";
288 }
289 uploadFileAttributes.erase(i);
290 }
291 }
292
293 #endif // USE_MEDIAINFO
294
295 // ----------------------------------------- xxtea encryption / decryption --------------------------------------------------------
296
297 static uint32_t endianDetectionValue = 0x01020304;
298
DetectBigEndian()299 inline bool DetectBigEndian()
300 {
301 return 0x01 == *(byte*)&endianDetectionValue;
302 }
303
EndianConversion32(uint32_t x)304 inline uint32_t EndianConversion32(uint32_t x)
305 {
306 return ((x & 0xff000000u) >> 24) | ((x & 0x00ff0000u) >> 8) | ((x & 0x0000ff00u) << 8) | ((x & 0x000000ffu) << 24);
307 }
308
EndianConversion32(uint32_t * v,unsigned vlen)309 inline void EndianConversion32(uint32_t* v, unsigned vlen)
310 {
311 for (; vlen--; ++v)
312 *v = EndianConversion32(*v);
313 }
314
315 uint32_t DELTA = 0x9E3779B9;
316
mx(uint32_t sum,uint32_t y,uint32_t z,uint32_t p,uint32_t e,const uint32_t key[4])317 inline uint32_t mx(uint32_t sum, uint32_t y, uint32_t z, uint32_t p, uint32_t e, const uint32_t key[4])
318 {
319 return (((z >> 5) ^ (y << 2)) + ((y >> 3) ^ (z << 4))) ^ ((sum ^ y) + (key[(p & 3) ^ e] ^ z));
320 }
321
322
xxteaEncrypt(uint32_t * v,uint32_t vlen,uint32_t key[4],bool endianConv)323 void xxteaEncrypt(uint32_t* v, uint32_t vlen, uint32_t key[4], bool endianConv)
324 {
325 if (endianConv)
326 {
327 if (DetectBigEndian())
328 {
329 EndianConversion32(v, vlen);
330 }
331 else
332 {
333 // match webclient
334 EndianConversion32(key, 4);
335 }
336 }
337
338 uint32_t n = vlen - 1;
339 uint32_t z = v[n];
340 uint32_t q = 6 + 52 / vlen;
341 uint32_t sum = 0;
342 for (; q > 0; --q)
343 {
344 sum += DELTA;
345 uint32_t e = (sum >> 2) & 3;
346 for (unsigned p = 0; p < n; ++p)
347 {
348 uint32_t y = v[p + 1];
349 z = v[p] = v[p] + mx(sum, y, z, p, e, key);
350 }
351 uint32_t y = v[0];
352 z = v[n] = v[n] + mx(sum, y, z, n, e, key);
353 }
354
355 if (endianConv)
356 {
357 if (DetectBigEndian())
358 {
359 EndianConversion32(v, vlen);
360 }
361 else
362 {
363 EndianConversion32(key, 4);
364 }
365 }
366 }
367
xxteaDecrypt(uint32_t * v,uint32_t vlen,uint32_t key[4],bool endianConv)368 void xxteaDecrypt(uint32_t* v, uint32_t vlen, uint32_t key[4], bool endianConv)
369 {
370 if (endianConv)
371 {
372 if (DetectBigEndian())
373 {
374 EndianConversion32(v, vlen);
375 }
376 else
377 {
378 EndianConversion32(key, 4);
379 }
380 }
381
382 uint32_t n = vlen - 1;
383 uint32_t y = v[0];
384 uint32_t q = 6 + 52 / vlen;
385 uint32_t sum = q * DELTA;
386 for (; sum != 0; sum -= DELTA)
387 {
388 uint32_t e = (sum >> 2) & 3;
389 for (unsigned p = n; p > 0; --p)
390 {
391 uint32_t z = v[p - 1];
392 y = v[p] = v[p] - mx(sum, y, z, p, e, key);
393 }
394 uint32_t z = v[n];
395 y = v[0] = v[0] - mx(sum, y, z, 0, e, key);
396 }
397
398 if (endianConv)
399 {
400 if (DetectBigEndian())
401 {
402 EndianConversion32(v, vlen);
403 }
404 else
405 {
406 EndianConversion32(key, 4);
407 }
408 }
409 }
410
formatfileattr(uint32_t id,byte * data,unsigned datalen,uint32_t fakey[4])411 std::string formatfileattr(uint32_t id, byte* data, unsigned datalen, uint32_t fakey[4])
412 {
413 assert(datalen % 4 == 0);
414 xxteaEncrypt((uint32_t*)data, datalen/4, fakey);
415
416 std::string encb64;
417 Base64::btoa(std::string((char*)data, datalen), encb64);
418
419 std::ostringstream result;
420 result << id << "*" << encb64;
421 return result.str();
422 }
423
424 // ----------------------------------------- MediaProperties --------------------------------------------------------
425
MediaProperties()426 MediaProperties::MediaProperties()
427 : shortformat(UNKNOWN_FORMAT)
428 , width(0)
429 , height(0)
430 , fps(0)
431 , playtime(0)
432 , containerid(0)
433 , videocodecid(0)
434 , audiocodecid(0)
435 , is_VFR(false)
436 , no_audio(false)
437 {
438 }
439
MediaProperties(const std::string & deserialize)440 MediaProperties::MediaProperties(const std::string& deserialize)
441 {
442 CacheableReader r(deserialize);
443 r.unserializebyte(shortformat);
444 r.unserializeu32(width);
445 r.unserializeu32(height);
446 r.unserializeu32(fps);
447 r.unserializeu32(playtime);
448 r.unserializeu32(containerid);
449 r.unserializeu32(videocodecid);
450 r.unserializeu32(audiocodecid);
451 r.unserializebool(is_VFR);
452 r.unserializebool(no_audio);
453 }
454
serialize()455 std::string MediaProperties::serialize()
456 {
457 std::string s;
458 CacheableWriter r(s);
459 r.serializebyte(shortformat);
460 r.serializeu32(width);
461 r.serializeu32(height);
462 r.serializeu32(fps);
463 r.serializeu32(playtime);
464 r.serializeu32(containerid);
465 r.serializeu32(videocodecid);
466 r.serializeu32(audiocodecid);
467 r.serializebool(is_VFR);
468 r.serializebool(no_audio);
469 r.serializeexpansionflags();
470 return s;
471 }
472
isPopulated()473 bool MediaProperties::isPopulated()
474 {
475 return shortformat != UNKNOWN_FORMAT;
476 }
477
isIdentified()478 bool MediaProperties::isIdentified()
479 {
480 return isPopulated() && shortformat != NOT_IDENTIFIED_FORMAT;
481 }
482
operator ==(const MediaProperties & o) const483 bool MediaProperties::operator==(const MediaProperties& o) const
484 {
485 return shortformat == o.shortformat && width == o.width && height == o.height && fps == o.fps && playtime == o.playtime &&
486 (shortformat || (containerid == o.containerid && videocodecid == o.videocodecid && audiocodecid == o.audiocodecid));
487 }
488
489 // shortformat must be 0 if the format is exotic - in that case, container/videocodec/audiocodec must be valid
490 // if shortformat is > 0, container/videocodec/audiocodec are ignored and no attribute 9 is returned.
491 // fakey is an 4-uint32 Array with the file attribute key (the nonce from the file key)
encodeMediaPropertiesAttributes(MediaProperties vp,uint32_t fakey[4])492 std::string MediaProperties::encodeMediaPropertiesAttributes(MediaProperties vp, uint32_t fakey[4])
493 {
494 vp.width <<= 1;
495 if (vp.width >= 32768) vp.width = ((vp.width - 32768) >> 3) | 1;
496 if (vp.width >= 32768) vp.width = 32767;
497
498 vp.height <<= 1;
499 if (vp.height >= 32768) vp.height = ((vp.height - 32768) >> 3) | 1;
500 if (vp.height >= 32768) vp.height = 32767;
501
502 vp.playtime <<= 1;
503 if (vp.playtime >= 262144) vp.playtime = ((vp.playtime - 262200) / 60) | 1;
504 if (vp.playtime >= 262144) vp.playtime = 262143;
505
506 vp.fps <<= 1;
507 if (vp.fps >= 256) vp.fps = ((vp.fps - 256) >> 3) | 1;
508 if (vp.fps >= 256) vp.fps = 255;
509
510 // LE code below
511 byte v[8];
512 v[7] = vp.shortformat;
513 v[6] = byte(vp.playtime >> 10);
514 v[5] = byte((vp.playtime >> 2) & 255);
515 v[4] = byte(((vp.playtime & 3) << 6) + (vp.fps >> 2));
516 v[3] = byte(((vp.fps & 3) << 6) + ((vp.height >> 9) & 63));
517 v[2] = byte((vp.height >> 1) & 255);
518 v[1] = byte(((vp.width >> 8) & 127) + ((vp.height & 1) << 7));
519 v[0] = byte(vp.width & 255);
520
521 std::string result = formatfileattr(fa_media, v, sizeof v, fakey);
522
523 if (!vp.shortformat) // exotic combination of container/codecids
524 {
525 LOG_debug << "The file requires extended media attributes";
526
527 memset(v, 0, sizeof v);
528 v[3] = (vp.audiocodecid >> 4) & 255;
529 v[2] = ((vp.videocodecid >> 8) & 15) + ((vp.audiocodecid & 15) << 4);
530 v[1] = vp.videocodecid & 255;
531 v[0] = byte(vp.containerid);
532 result.append("/");
533 result.append(formatfileattr(fa_mediaext, v, sizeof v, fakey));
534 }
535 return result;
536 }
537
decodeMediaPropertiesAttributes(const std::string & attrs,uint32_t fakey[4])538 MediaProperties MediaProperties::decodeMediaPropertiesAttributes(const std::string& attrs, uint32_t fakey[4])
539 {
540 MediaProperties r;
541
542 int ppo = Node::hasfileattribute(&attrs, fa_media);
543 int pos = ppo - 1;
544 if (ppo && pos + 3 + 11 <= (int)attrs.size())
545 {
546 std::string binary;
547 Base64::atob(attrs.substr(pos + 3, 11), binary);
548 assert(binary.size() == 8);
549 byte v[8];
550 memcpy(v, binary.data(), std::min<size_t>(sizeof v, binary.size()));
551 xxteaDecrypt((uint32_t*)v, sizeof(v)/4, fakey);
552
553 r.width = (v[0] >> 1) + ((v[1] & 127) << 7);
554 if (v[0] & 1) r.width = (r.width << 3) + 16384;
555
556 r.height = v[2] + ((v[3] & 63) << 8);
557 if (v[1] & 128) r.height = (r.height << 3) + 16384;
558
559 r.fps = (v[3] >> 7) + ((v[4] & 63) << 1);
560 if (v[3] & 64) r.fps = (r.fps << 3) + 128;
561
562 r.playtime = (v[4] >> 7) + (v[5] << 1) + (v[6] << 9);
563 if (v[4] & 64) r.playtime = r.playtime * 60 + 131100;
564
565 if (!(r.shortformat = v[7]))
566 {
567 int ppo = Node::hasfileattribute(&attrs, fa_mediaext);
568 int pos = ppo - 1;
569 if (ppo && pos + 3 + 11 <= (int)attrs.size())
570 {
571 Base64::atob(attrs.substr(pos + 3, 11), binary);
572 assert(binary.size() == 8);
573 memcpy(v, binary.data(), std::min<size_t>(sizeof v, binary.size()));
574 xxteaDecrypt((uint32_t*)v, sizeof(v) / 4, fakey);
575
576 r.containerid = v[0];
577 r.videocodecid = v[1] + ((v[2] & 15) << 8);
578 r.audiocodecid = (v[2] >> 4) + (v[3] << 4);
579 }
580 }
581 }
582
583 return r;
584 }
585
586 #ifdef USE_MEDIAINFO
587
isMediaFilenameExt(const std::string & ext)588 bool MediaProperties::isMediaFilenameExt(const std::string& ext)
589 {
590 static const char* supportedformats =
591 ".264.265.3g2.3ga.3gp.3gpa.3gpp.3gpp2.aac.aacp.ac3.act.adts.aif.aifc.aiff.als.apl.at3.avc"
592 ".avi.dd+.dde.divx.dts.dtshd.eac3.ec3.evo.f4a.f4b.f4v.flac.gvi.h261.h263.h264.h265.hevc.isma"
593 ".ismt.ismv.ivf.jpm.k3g.m1a.m1v.m2a.m2p.m2s.m2t.m2v.m4a.m4b.m4p.m4s.m4t.m4v.m4v.mac.mkv.mk3d"
594 ".mka.mks.mlp.mov.mp1.mp1v.mp2.mp2v.mp3.mp4.mp4v.mpa1.mpa2.mpeg.mpg.mpgv.mpv.mqv.ogg.ogm.ogv"
595 ".omg.opus.qt.sls.spx.thd.tmf.trp.ts.ty.vc1.vob.vr.w64.wav.webm.wma.wmv.";
596
597 for (const char* ptr = supportedformats; NULL != (ptr = strstr(ptr, ext.c_str())); ptr += ext.size())
598 {
599 if (ptr[ext.size()] == '.')
600 {
601 return true;
602 }
603 }
604 return false;
605 }
606
coalesce(uint32_t a,uint32_t b)607 static inline uint32_t coalesce(uint32_t a, uint32_t b)
608 {
609 return a != 0 ? a : b;
610 }
611
timeToRetryMediaPropertyExtraction(const std::string & fileattributes,uint32_t fakey[4])612 bool MediaFileInfo::timeToRetryMediaPropertyExtraction(const std::string& fileattributes, uint32_t fakey[4])
613 {
614 // Check if we should retry video property extraction, due to previous failure with older library
615 MediaProperties vp = MediaProperties::decodeMediaPropertiesAttributes(fileattributes, fakey);
616 if (vp.isIdentified())
617 {
618 if (vp.fps < MEDIA_INFO_BUILD)
619 {
620 LOG_debug << "Media extraction retry needed with a newer build. Old: "
621 << vp.fps << " New: " << MEDIA_INFO_BUILD;
622 return true;
623 }
624 if (vp.width < GetMediaInfoVersion())
625 {
626 LOG_debug << "Media extraction retry needed with a newer MediaInfo version. Old: "
627 << vp.width << " New: " << GetMediaInfoVersion();
628 return true;
629 }
630 if (vp.playtime < downloadedCodecMapsVersion)
631 {
632 LOG_debug << "Media extraction retry needed with newer code mappings. Old: "
633 << vp.playtime << " New: " << downloadedCodecMapsVersion;
634 return true;
635 }
636 }
637 return false;
638 }
639
mediaInfoOpenFileWithLimits(MediaInfoLib::MediaInfo & mi,LocalPath & filename,FileAccess * fa,unsigned maxBytesToRead,unsigned maxSeconds)640 bool mediaInfoOpenFileWithLimits(MediaInfoLib::MediaInfo& mi, LocalPath& filename, FileAccess* fa, unsigned maxBytesToRead, unsigned maxSeconds)
641 {
642 if (!fa->fopen(filename, true, false))
643 {
644 LOG_err << "could not open local file for mediainfo";
645 return false;
646 }
647
648 m_off_t filesize = fa->size;
649 size_t totalBytesRead = 0, jumps = 0;
650 mi.Open_Buffer_Init(filesize, 0);
651 m_off_t readpos = 0;
652 m_time_t startTime = 0;
653
654 for (;;)
655 {
656 byte buf[30 * 1024];
657
658 unsigned n = unsigned(std::min<m_off_t>(filesize - readpos, sizeof(buf)));
659 if (n == 0)
660 {
661 break;
662 }
663
664 if (totalBytesRead > maxBytesToRead || (startTime != 0 && ((m_time() - startTime) > maxSeconds)))
665 {
666 LOG_warn << "could not extract mediainfo data within reasonable limits";
667 mi.Open_Buffer_Finalize();
668 fa->closef();
669 return false;
670 }
671
672 if (!fa->frawread(buf, n, readpos, true))
673 {
674 LOG_err << "could not read local file";
675 mi.Open_Buffer_Finalize();
676 fa->closef();
677 return false;
678 }
679 readpos += n;
680 if (startTime == 0)
681 {
682 startTime = m_time();
683 }
684
685 totalBytesRead += n;
686 size_t bitfield = mi.Open_Buffer_Continue((byte*)buf, n);
687 // flag bitmask --> 1:accepted, 2:filled, 4:updated, 8:finalised
688 bool accepted = bitfield & 1;
689 bool filled = bitfield & 2;
690 bool finalised = bitfield & 8;
691 if (filled || finalised)
692 {
693 break;
694 }
695
696 if (accepted)
697 {
698 bool hasVideo = 0 < mi.Count_Get(MediaInfoLib::Stream_Video, 0);
699 bool hasAudio = 0 < mi.Count_Get(MediaInfoLib::Stream_Audio, 0);
700
701 bool vidDuration = !mi.Get(MediaInfoLib::Stream_Video, 0, __T("Duration"), MediaInfoLib::Info_Text).empty();
702 bool audDuration = !mi.Get(MediaInfoLib::Stream_Audio, 0, __T("Duration"), MediaInfoLib::Info_Text).empty();
703
704 if (hasVideo && hasAudio && vidDuration && audDuration)
705 {
706 break;
707 }
708 }
709
710 m_off_t requestPos = mi.Open_Buffer_Continue_GoTo_Get();
711 if (requestPos != (m_off_t)-1)
712 {
713 readpos = requestPos;
714 mi.Open_Buffer_Init(filesize, readpos);
715 jumps += 1;
716 }
717 }
718
719 mi.Open_Buffer_Finalize();
720 fa->closef();
721 return true;
722 }
723
extractMediaPropertyFileAttributes(LocalPath & localFilename,FileSystemAccess * fsa)724 void MediaProperties::extractMediaPropertyFileAttributes(LocalPath& localFilename, FileSystemAccess* fsa)
725 {
726 if (auto tmpfa = fsa->newfileaccess())
727 {
728 try
729 {
730 MediaInfoLib::MediaInfo minfo;
731
732 if (mediaInfoOpenFileWithLimits(minfo, localFilename, tmpfa.get(), 10485760, 3)) // we can read more off local disk
733 {
734 if (!minfo.Count_Get(MediaInfoLib::Stream_General, 0))
735 {
736 LOG_warn << "mediainfo: no general information found in file";
737 }
738 if (!minfo.Count_Get(MediaInfoLib::Stream_Video, 0))
739 {
740 LOG_warn << "mediainfo: no video information found in file";
741 }
742 if (!minfo.Count_Get(MediaInfoLib::Stream_Audio, 0))
743 {
744 LOG_warn << "mediainfo: no audio information found in file";
745 no_audio = true;
746 }
747
748 ZenLib::Ztring gci = minfo.Get(MediaInfoLib::Stream_General, 0, __T("CodecID"), MediaInfoLib::Info_Text);
749 ZenLib::Ztring gf = minfo.Get(MediaInfoLib::Stream_General, 0, __T("Format"), MediaInfoLib::Info_Text);
750 ZenLib::Ztring gd = minfo.Get(MediaInfoLib::Stream_General, 0, __T("Duration"), MediaInfoLib::Info_Text);
751 ZenLib::Ztring vw = minfo.Get(MediaInfoLib::Stream_Video, 0, __T("Width"), MediaInfoLib::Info_Text);
752 ZenLib::Ztring vh = minfo.Get(MediaInfoLib::Stream_Video, 0, __T("Height"), MediaInfoLib::Info_Text);
753 ZenLib::Ztring vd = minfo.Get(MediaInfoLib::Stream_Video, 0, __T("Duration"), MediaInfoLib::Info_Text);
754 ZenLib::Ztring vfr = minfo.Get(MediaInfoLib::Stream_Video, 0, __T("FrameRate"), MediaInfoLib::Info_Text);
755 ZenLib::Ztring vrm = minfo.Get(MediaInfoLib::Stream_Video, 0, __T("FrameRate_Mode"), MediaInfoLib::Info_Text);
756 ZenLib::Ztring vci = minfo.Get(MediaInfoLib::Stream_Video, 0, __T("CodecID"), MediaInfoLib::Info_Text);
757 ZenLib::Ztring vcf = minfo.Get(MediaInfoLib::Stream_Video, 0, __T("Format"), MediaInfoLib::Info_Text);
758 ZenLib::Ztring vr = minfo.Get(MediaInfoLib::Stream_Video, 0, __T("Rotation"), MediaInfoLib::Info_Text);
759 ZenLib::Ztring aci = minfo.Get(MediaInfoLib::Stream_Audio, 0, __T("CodecID"), MediaInfoLib::Info_Text);
760 ZenLib::Ztring acf = minfo.Get(MediaInfoLib::Stream_Audio, 0, __T("Format"), MediaInfoLib::Info_Text);
761 ZenLib::Ztring ad = minfo.Get(MediaInfoLib::Stream_Audio, 0, __T("Duration"), MediaInfoLib::Info_Text);
762
763 if (vr.To_int32u() == 90 || vr.To_int32u() == 270)
764 {
765 width = vh.To_int32u();
766 height = vw.To_int32u();
767 }
768 else
769 {
770 width = vw.To_int32u();
771 height = vh.To_int32u();
772 }
773
774 fps = vfr.To_int32u();
775 playtime = (coalesce(gd.To_int32u(), coalesce(vd.To_int32u(), ad.To_int32u()))) / 1000;
776 videocodecNames = vci.To_Local();
777 videocodecFormat = vcf.To_Local();
778 audiocodecNames = aci.To_Local();
779 audiocodecFormat = acf.To_Local();
780 containerName = gci.To_Local();
781 containerFormat = gf.To_Local();
782 is_VFR = vrm.To_Local() == "VFR"; // variable frame rate - send through as 0 in fps field
783 if (!fps)
784 {
785 ZenLib::Ztring vrn = minfo.Get(MediaInfoLib::Stream_Video, 0, __T("FrameRate_Num"), MediaInfoLib::Info_Text);
786 ZenLib::Ztring vrd = minfo.Get(MediaInfoLib::Stream_Video, 0, __T("FrameRate_Den"), MediaInfoLib::Info_Text);
787 uint32_t num = vrn.To_int32u();
788 uint32_t den = vrd.To_int32u();
789 if (num > 0 && den > 0)
790 {
791 fps = (num + den / 2) / den;
792 }
793 }
794 if (!fps)
795 {
796 ZenLib::Ztring vro = minfo.Get(MediaInfoLib::Stream_Video, 0, __T("FrameRate_Original"), MediaInfoLib::Info_Text);
797 fps = vro.To_int32u();
798 }
799
800 if (SimpleLogger::logCurrentLevel >= logDebug)
801 {
802 LOG_debug << "MediaInfo on " << localFilename.toPath(*fsa) << " | " << vw.To_Local() << " " << vh.To_Local() << " " << vd.To_Local() << " " << vr.To_Local() << " |\"" << gci.To_Local() << "\",\"" << gf.To_Local() << "\",\"" << vci.To_Local() << "\",\"" << vcf.To_Local() << "\",\"" << aci.To_Local() << "\",\"" << acf.To_Local() << "\"";
803 }
804 }
805 }
806 catch (std::exception& e)
807 {
808 LOG_err << "exception caught reading media file attibutes: " << e.what();
809 }
810 catch (...)
811 {
812 LOG_err << "unknown excption caught reading media file attributes";
813 }
814 }
815 }
816
convertMediaPropertyFileAttributes(uint32_t fakey[4],MediaFileInfo & mediaInfo)817 std::string MediaProperties::convertMediaPropertyFileAttributes(uint32_t fakey[4], MediaFileInfo& mediaInfo)
818 {
819 containerid = mediaInfo.Lookup(containerName, mediaInfo.mediaCodecs.containers, 0);
820 if (!containerid)
821 {
822 containerid = mediaInfo.Lookup(containerFormat, mediaInfo.mediaCodecs.containers, 0);
823 }
824 videocodecid = mediaInfo.Lookup(videocodecNames, mediaInfo.mediaCodecs.videocodecs, 0);
825 if (!videocodecid)
826 {
827 videocodecid = mediaInfo.Lookup(videocodecFormat, mediaInfo.mediaCodecs.videocodecs, 0);
828 }
829 audiocodecid = mediaInfo.Lookup(audiocodecNames, mediaInfo.mediaCodecs.audiocodecs, 0);
830 if (!audiocodecid)
831 {
832 audiocodecid = mediaInfo.Lookup(audiocodecFormat, mediaInfo.mediaCodecs.audiocodecs, 0);
833 }
834
835 if (!(containerid && (
836 (videocodecid && width && height && /*(fps || is_VFR) &&*/ (audiocodecid || no_audio)) ||
837 (audiocodecid && !videocodecid))))
838 {
839 LOG_warn << "mediainfo failed to extract media information for this file";
840 shortformat = NOT_IDENTIFIED_FORMAT; // mediaInfo could not fully identify this file. Maybe a later version can.
841 fps = MEDIA_INFO_BUILD; // updated when we change relevant things in this executable
842 width = GetMediaInfoVersion(); // mediaInfoLib version that couldn't do it. 1710 at time of writing (ie oct 2017 tag)
843 height = 0;
844 playtime = mediaInfo.downloadedCodecMapsVersion; // updated when we add more codec names etc
845 }
846 else
847 {
848 LOG_debug << "mediainfo processed the file correctly";
849
850 // attribute 8 valid, and either shortformat specifies a common combination of (containerid, videocodecid, audiocodecid),
851 // or we make an attribute 9 with those values, and set shortformat=0.
852 shortformat = mediaInfo.LookupShortFormat(containerid, videocodecid, audiocodecid);
853 }
854
855 LOG_debug << "MediaInfo converted: " << (int)shortformat << "," << width << "," << height << "," << fps << "," << playtime << "," << videocodecid << "," << audiocodecid << "," << containerid;
856
857 std::string mediafileattributes = MediaProperties::encodeMediaPropertiesAttributes(*this, fakey);
858
859 #ifdef _DEBUG
860 // double check decode is the opposite of encode
861 std::string simServerAttribs = ":" + mediafileattributes;
862 size_t pos = simServerAttribs.find("/");
863 if (pos != std::string::npos)
864 simServerAttribs.replace(pos, 1, ":");
865 MediaProperties decVp = MediaProperties::decodeMediaPropertiesAttributes(simServerAttribs, fakey);
866 assert(*this == decVp);
867 #endif
868
869 return mediafileattributes;
870 }
871 #endif
872
873 } // namespace
874