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