1 /*
2  *  Copyright (C) 2005-2018 Team Kodi
3  *  This file is part of Kodi - https://kodi.tv
4  *
5  *  SPDX-License-Identifier: GPL-2.0-or-later
6  *  See LICENSES/README.md for more information.
7  */
8 
9 #include "StreamDetails.h"
10 
11 #include "LangInfo.h"
12 #include "StreamUtils.h"
13 #include "cores/VideoPlayer/Interface/StreamInfo.h"
14 #include "utils/Archive.h"
15 #include "utils/LangCodeExpander.h"
16 #include "utils/Variant.h"
17 
18 #include <math.h>
19 
20 const float VIDEOASPECT_EPSILON = 0.025f;
21 
CStreamDetailVideo()22 CStreamDetailVideo::CStreamDetailVideo() :
23   CStreamDetail(CStreamDetail::VIDEO)
24 {
25 }
26 
CStreamDetailVideo(const VideoStreamInfo & info,int duration)27 CStreamDetailVideo::CStreamDetailVideo(const VideoStreamInfo &info, int duration) :
28   CStreamDetail(CStreamDetail::VIDEO),
29   m_iWidth(info.width),
30   m_iHeight(info.height),
31   m_fAspect(info.videoAspectRatio),
32   m_iDuration(duration),
33   m_strCodec(info.codecName),
34   m_strStereoMode(info.stereoMode),
35   m_strLanguage(info.language)
36 {
37 }
38 
Archive(CArchive & ar)39 void CStreamDetailVideo::Archive(CArchive& ar)
40 {
41   if (ar.IsStoring())
42   {
43     ar << m_strCodec;
44     ar << m_fAspect;
45     ar << m_iHeight;
46     ar << m_iWidth;
47     ar << m_iDuration;
48     ar << m_strStereoMode;
49     ar << m_strLanguage;
50   }
51   else
52   {
53     ar >> m_strCodec;
54     ar >> m_fAspect;
55     ar >> m_iHeight;
56     ar >> m_iWidth;
57     ar >> m_iDuration;
58     ar >> m_strStereoMode;
59     ar >> m_strLanguage;
60   }
61 }
Serialize(CVariant & value) const62 void CStreamDetailVideo::Serialize(CVariant& value) const
63 {
64   value["codec"] = m_strCodec;
65   value["aspect"] = m_fAspect;
66   value["height"] = m_iHeight;
67   value["width"] = m_iWidth;
68   value["duration"] = m_iDuration;
69   value["stereomode"] = m_strStereoMode;
70   value["language"] = m_strLanguage;
71 }
72 
IsWorseThan(const CStreamDetail & that) const73 bool CStreamDetailVideo::IsWorseThan(const CStreamDetail &that) const
74 {
75   if (that.m_eType != CStreamDetail::VIDEO)
76     return true;
77 
78   // Best video stream is that with the most pixels
79   auto &sdv = static_cast<const CStreamDetailVideo &>(that);
80   return (sdv.m_iWidth * sdv.m_iHeight) > (m_iWidth * m_iHeight);
81 }
82 
CStreamDetailAudio()83 CStreamDetailAudio::CStreamDetailAudio() :
84   CStreamDetail(CStreamDetail::AUDIO)
85 {
86 }
87 
CStreamDetailAudio(const AudioStreamInfo & info)88 CStreamDetailAudio::CStreamDetailAudio(const AudioStreamInfo &info) :
89   CStreamDetail(CStreamDetail::AUDIO),
90   m_iChannels(info.channels),
91   m_strCodec(info.codecName),
92   m_strLanguage(info.language)
93 {
94 }
95 
Archive(CArchive & ar)96 void CStreamDetailAudio::Archive(CArchive& ar)
97 {
98   if (ar.IsStoring())
99   {
100     ar << m_strCodec;
101     ar << m_strLanguage;
102     ar << m_iChannels;
103   }
104   else
105   {
106     ar >> m_strCodec;
107     ar >> m_strLanguage;
108     ar >> m_iChannels;
109   }
110 }
Serialize(CVariant & value) const111 void CStreamDetailAudio::Serialize(CVariant& value) const
112 {
113   value["codec"] = m_strCodec;
114   value["language"] = m_strLanguage;
115   value["channels"] = m_iChannels;
116 }
117 
IsWorseThan(const CStreamDetail & that) const118 bool CStreamDetailAudio::IsWorseThan(const CStreamDetail &that) const
119 {
120   if (that.m_eType != CStreamDetail::AUDIO)
121     return true;
122 
123   auto &sda = static_cast<const CStreamDetailAudio &>(that);
124   // First choice is the thing with the most channels
125   if (sda.m_iChannels > m_iChannels)
126     return true;
127   if (m_iChannels > sda.m_iChannels)
128     return false;
129 
130   // In case of a tie, revert to codec priority
131   return StreamUtils::GetCodecPriority(sda.m_strCodec) > StreamUtils::GetCodecPriority(m_strCodec);
132 }
133 
CStreamDetailSubtitle()134 CStreamDetailSubtitle::CStreamDetailSubtitle() :
135   CStreamDetail(CStreamDetail::SUBTITLE)
136 {
137 }
138 
CStreamDetailSubtitle(const SubtitleStreamInfo & info)139 CStreamDetailSubtitle::CStreamDetailSubtitle(const SubtitleStreamInfo &info) :
140   CStreamDetail(CStreamDetail::SUBTITLE),
141   m_strLanguage(info.language)
142 {
143 }
144 
Archive(CArchive & ar)145 void CStreamDetailSubtitle::Archive(CArchive& ar)
146 {
147   if (ar.IsStoring())
148   {
149     ar << m_strLanguage;
150   }
151   else
152   {
153     ar >> m_strLanguage;
154   }
155 }
Serialize(CVariant & value) const156 void CStreamDetailSubtitle::Serialize(CVariant& value) const
157 {
158   value["language"] = m_strLanguage;
159 }
160 
IsWorseThan(const CStreamDetail & that) const161 bool CStreamDetailSubtitle::IsWorseThan(const CStreamDetail &that) const
162 {
163   if (that.m_eType != CStreamDetail::SUBTITLE)
164     return true;
165 
166   if (g_LangCodeExpander.CompareISO639Codes(m_strLanguage, static_cast<const CStreamDetailSubtitle &>(that).m_strLanguage))
167     return false;
168 
169   // the best subtitle should be the one in the user's preferred language
170   // If preferred language is set to "original" this is "eng"
171   return m_strLanguage.empty() ||
172     g_LangCodeExpander.CompareISO639Codes(static_cast<const CStreamDetailSubtitle &>(that).m_strLanguage, g_langInfo.GetSubtitleLanguage());
173 }
174 
operator =(const CStreamDetailSubtitle & that)175 CStreamDetailSubtitle& CStreamDetailSubtitle::operator=(const CStreamDetailSubtitle &that)
176 {
177   if (this != &that)
178   {
179     this->m_pParent = that.m_pParent;
180     this->m_strLanguage = that.m_strLanguage;
181   }
182   return *this;
183 }
184 
operator =(const CStreamDetails & that)185 CStreamDetails& CStreamDetails::operator=(const CStreamDetails &that)
186 {
187   if (this != &that)
188   {
189     Reset();
190     for (const auto &iter : that.m_vecItems)
191     {
192       switch (iter->m_eType)
193       {
194       case CStreamDetail::VIDEO:
195         AddStream(new CStreamDetailVideo(static_cast<const CStreamDetailVideo&>(*iter)));
196         break;
197       case CStreamDetail::AUDIO:
198         AddStream(new CStreamDetailAudio(static_cast<const CStreamDetailAudio&>(*iter)));
199         break;
200       case CStreamDetail::SUBTITLE:
201         AddStream(new CStreamDetailSubtitle(static_cast<const CStreamDetailSubtitle&>(*iter)));
202         break;
203       }
204     }
205 
206     DetermineBestStreams();
207   }  /* if this != that */
208 
209   return *this;
210 }
211 
operator ==(const CStreamDetails & right) const212 bool CStreamDetails::operator ==(const CStreamDetails &right) const
213 {
214   if (this == &right) return true;
215 
216   if (GetVideoStreamCount()    != right.GetVideoStreamCount() ||
217       GetAudioStreamCount()    != right.GetAudioStreamCount() ||
218       GetSubtitleStreamCount() != right.GetSubtitleStreamCount())
219     return false;
220 
221   for (int iStream=1; iStream<=GetVideoStreamCount(); iStream++)
222   {
223     if (GetVideoCodec(iStream)    != right.GetVideoCodec(iStream)    ||
224         GetVideoWidth(iStream)    != right.GetVideoWidth(iStream)    ||
225         GetVideoHeight(iStream)   != right.GetVideoHeight(iStream)   ||
226         GetVideoDuration(iStream) != right.GetVideoDuration(iStream) ||
227         fabs(GetVideoAspect(iStream) - right.GetVideoAspect(iStream)) > VIDEOASPECT_EPSILON)
228       return false;
229   }
230 
231   for (int iStream=1; iStream<=GetAudioStreamCount(); iStream++)
232   {
233     if (GetAudioCodec(iStream)    != right.GetAudioCodec(iStream)    ||
234         GetAudioLanguage(iStream) != right.GetAudioLanguage(iStream) ||
235         GetAudioChannels(iStream) != right.GetAudioChannels(iStream) )
236       return false;
237   }
238 
239   for (int iStream=1; iStream<=GetSubtitleStreamCount(); iStream++)
240   {
241     if (GetSubtitleLanguage(iStream) != right.GetSubtitleLanguage(iStream) )
242       return false;
243   }
244 
245   return true;
246 }
247 
operator !=(const CStreamDetails & right) const248 bool CStreamDetails::operator !=(const CStreamDetails &right) const
249 {
250   if (this == &right) return false;
251 
252   return !(*this == right);
253 }
254 
NewStream(CStreamDetail::StreamType type)255 CStreamDetail *CStreamDetails::NewStream(CStreamDetail::StreamType type)
256 {
257   CStreamDetail *retVal = NULL;
258   switch (type)
259   {
260   case CStreamDetail::VIDEO:
261     retVal = new CStreamDetailVideo();
262     break;
263   case CStreamDetail::AUDIO:
264     retVal = new CStreamDetailAudio();
265     break;
266   case CStreamDetail::SUBTITLE:
267     retVal = new CStreamDetailSubtitle();
268     break;
269   }
270 
271   if (retVal)
272     AddStream(retVal);
273 
274   return retVal;
275 }
276 
GetVideoLanguage(int idx) const277 std::string CStreamDetails::GetVideoLanguage(int idx) const
278 {
279   const CStreamDetailVideo *item = static_cast<const CStreamDetailVideo*>(GetNthStream(CStreamDetail::VIDEO, idx));
280   if (item)
281     return item->m_strLanguage;
282   else
283     return "";
284 }
285 
GetStreamCount(CStreamDetail::StreamType type) const286 int CStreamDetails::GetStreamCount(CStreamDetail::StreamType type) const
287 {
288   int retVal = 0;
289   for (const auto &iter : m_vecItems)
290     if (iter->m_eType == type)
291       retVal++;
292   return retVal;
293 }
294 
GetVideoStreamCount(void) const295 int CStreamDetails::GetVideoStreamCount(void) const
296 {
297   return GetStreamCount(CStreamDetail::VIDEO);
298 }
299 
GetAudioStreamCount(void) const300 int CStreamDetails::GetAudioStreamCount(void) const
301 {
302   return GetStreamCount(CStreamDetail::AUDIO);
303 }
304 
GetSubtitleStreamCount(void) const305 int CStreamDetails::GetSubtitleStreamCount(void) const
306 {
307   return GetStreamCount(CStreamDetail::SUBTITLE);
308 }
309 
CStreamDetails(const CStreamDetails & that)310 CStreamDetails::CStreamDetails(const CStreamDetails &that)
311 {
312   m_pBestVideo = nullptr;
313   m_pBestAudio = nullptr;
314   m_pBestSubtitle = nullptr;
315   *this = that;
316 }
317 
AddStream(CStreamDetail * item)318 void CStreamDetails::AddStream(CStreamDetail *item)
319 {
320   item->m_pParent = this;
321   m_vecItems.emplace_back(item);
322 }
323 
Reset(void)324 void CStreamDetails::Reset(void)
325 {
326   m_pBestVideo = nullptr;
327   m_pBestAudio = nullptr;
328   m_pBestSubtitle = nullptr;
329 
330   m_vecItems.clear();
331 }
332 
GetNthStream(CStreamDetail::StreamType type,int idx) const333 const CStreamDetail* CStreamDetails::GetNthStream(CStreamDetail::StreamType type, int idx) const
334 {
335   if (idx == 0)
336   {
337     switch (type)
338     {
339     case CStreamDetail::VIDEO:
340       return m_pBestVideo;
341       break;
342     case CStreamDetail::AUDIO:
343       return m_pBestAudio;
344       break;
345     case CStreamDetail::SUBTITLE:
346       return m_pBestSubtitle;
347       break;
348     default:
349       return NULL;
350       break;
351     }
352   }
353 
354   for (const auto &iter : m_vecItems)
355     if (iter->m_eType == type)
356     {
357       idx--;
358       if (idx < 1)
359         return iter.get();
360     }
361 
362   return NULL;
363 }
364 
GetVideoCodec(int idx) const365 std::string CStreamDetails::GetVideoCodec(int idx) const
366 {
367   const CStreamDetailVideo *item = static_cast<const CStreamDetailVideo*>(GetNthStream(CStreamDetail::VIDEO, idx));
368   if (item)
369     return item->m_strCodec;
370   else
371     return "";
372 }
373 
GetVideoAspect(int idx) const374 float CStreamDetails::GetVideoAspect(int idx) const
375 {
376   const CStreamDetailVideo *item = static_cast<const CStreamDetailVideo*>(GetNthStream(CStreamDetail::VIDEO, idx));
377   if (item)
378     return item->m_fAspect;
379   else
380     return 0.0;
381 }
382 
GetVideoWidth(int idx) const383 int CStreamDetails::GetVideoWidth(int idx) const
384 {
385   const CStreamDetailVideo *item = static_cast<const CStreamDetailVideo*>(GetNthStream(CStreamDetail::VIDEO, idx));
386   if (item)
387     return item->m_iWidth;
388   else
389     return 0;
390 }
391 
GetVideoHeight(int idx) const392 int CStreamDetails::GetVideoHeight(int idx) const
393 {
394   const CStreamDetailVideo *item = static_cast<const CStreamDetailVideo*>(GetNthStream(CStreamDetail::VIDEO, idx));
395   if (item)
396     return item->m_iHeight;
397   else
398     return 0;
399 }
400 
GetVideoDuration(int idx) const401 int CStreamDetails::GetVideoDuration(int idx) const
402 {
403   const CStreamDetailVideo *item = static_cast<const CStreamDetailVideo*>(GetNthStream(CStreamDetail::VIDEO, idx));
404   if (item)
405     return item->m_iDuration;
406   else
407     return 0;
408 }
409 
SetVideoDuration(int idx,const int duration)410 void CStreamDetails::SetVideoDuration(int idx, const int duration)
411 {
412   CStreamDetailVideo *item = const_cast<CStreamDetailVideo*>(static_cast<const CStreamDetailVideo*>(GetNthStream(CStreamDetail::VIDEO, idx)));
413   if (item)
414     item->m_iDuration = duration;
415 }
416 
GetStereoMode(int idx) const417 std::string CStreamDetails::GetStereoMode(int idx) const
418 {
419   const CStreamDetailVideo *item = static_cast<const CStreamDetailVideo*>(GetNthStream(CStreamDetail::VIDEO, idx));
420   if (item)
421     return item->m_strStereoMode;
422   else
423     return "";
424 }
425 
GetAudioCodec(int idx) const426 std::string CStreamDetails::GetAudioCodec(int idx) const
427 {
428   const CStreamDetailAudio *item = static_cast<const CStreamDetailAudio*>(GetNthStream(CStreamDetail::AUDIO, idx));
429   if (item)
430     return item->m_strCodec;
431   else
432     return "";
433 }
434 
GetAudioLanguage(int idx) const435 std::string CStreamDetails::GetAudioLanguage(int idx) const
436 {
437   const CStreamDetailAudio *item = static_cast<const CStreamDetailAudio*>(GetNthStream(CStreamDetail::AUDIO, idx));
438   if (item)
439     return item->m_strLanguage;
440   else
441     return "";
442 }
443 
GetAudioChannels(int idx) const444 int CStreamDetails::GetAudioChannels(int idx) const
445 {
446   const CStreamDetailAudio *item = static_cast<const CStreamDetailAudio*>(GetNthStream(CStreamDetail::AUDIO, idx));
447   if (item)
448     return item->m_iChannels;
449   else
450     return -1;
451 }
452 
GetSubtitleLanguage(int idx) const453 std::string CStreamDetails::GetSubtitleLanguage(int idx) const
454 {
455   const CStreamDetailSubtitle *item = static_cast<const CStreamDetailSubtitle*>(GetNthStream(CStreamDetail::SUBTITLE, idx));
456   if (item)
457     return item->m_strLanguage;
458   else
459     return "";
460 }
461 
Archive(CArchive & ar)462 void CStreamDetails::Archive(CArchive& ar)
463 {
464   if (ar.IsStoring())
465   {
466     ar << (int)m_vecItems.size();
467 
468     for (auto &iter : m_vecItems)
469     {
470       // the type goes before the actual item.  When loading we need
471       // to know the type before we can construct an instance to serialize
472       ar << (int)iter->m_eType;
473       ar << (*iter);
474     }
475   }
476   else
477   {
478     int count;
479     ar >> count;
480 
481     Reset();
482     for (int i=0; i<count; i++)
483     {
484       int type;
485       CStreamDetail *p = NULL;
486 
487       ar >> type;
488       p = NewStream(CStreamDetail::StreamType(type));
489       if (p)
490         ar >> (*p);
491     }
492 
493     DetermineBestStreams();
494   }
495 }
Serialize(CVariant & value) const496 void CStreamDetails::Serialize(CVariant& value) const
497 {
498   // make sure these properties are always present
499   value["audio"] = CVariant(CVariant::VariantTypeArray);
500   value["video"] = CVariant(CVariant::VariantTypeArray);
501   value["subtitle"] = CVariant(CVariant::VariantTypeArray);
502 
503   CVariant v;
504   for (const auto &iter : m_vecItems)
505   {
506     v.clear();
507     iter->Serialize(v);
508     switch (iter->m_eType)
509     {
510     case CStreamDetail::AUDIO:
511       value["audio"].push_back(v);
512       break;
513     case CStreamDetail::VIDEO:
514       value["video"].push_back(v);
515       break;
516     case CStreamDetail::SUBTITLE:
517       value["subtitle"].push_back(v);
518       break;
519     }
520   }
521 }
522 
DetermineBestStreams(void)523 void CStreamDetails::DetermineBestStreams(void)
524 {
525   m_pBestVideo = NULL;
526   m_pBestAudio = NULL;
527   m_pBestSubtitle = NULL;
528 
529   for (const auto &iter : m_vecItems)
530   {
531     const CStreamDetail **champion;
532     switch (iter->m_eType)
533     {
534     case CStreamDetail::VIDEO:
535       champion = (const CStreamDetail **)&m_pBestVideo;
536       break;
537     case CStreamDetail::AUDIO:
538       champion = (const CStreamDetail **)&m_pBestAudio;
539       break;
540     case CStreamDetail::SUBTITLE:
541       champion = (const CStreamDetail **)&m_pBestSubtitle;
542       break;
543     default:
544       champion = NULL;
545     }  /* switch type */
546 
547     if (!champion)
548       continue;
549 
550     if ((*champion == NULL) || (*champion)->IsWorseThan(*iter))
551       *champion = iter.get();
552   }  /* for each */
553 }
554 
VideoDimsToResolutionDescription(int iWidth,int iHeight)555 std::string CStreamDetails::VideoDimsToResolutionDescription(int iWidth, int iHeight)
556 {
557   if (iWidth == 0 || iHeight == 0)
558     return "";
559 
560   else if (iWidth <= 720 && iHeight <= 480)
561     return "480";
562   // 720x576 (PAL) (768 when rescaled for square pixels)
563   else if (iWidth <= 768 && iHeight <= 576)
564     return "576";
565   // 960x540 (sometimes 544 which is multiple of 16)
566   else if (iWidth <= 960 && iHeight <= 544)
567     return "540";
568   // 1280x720
569   else if (iWidth <= 1280 && iHeight <= 962)
570     return "720";
571   // 1920x1080
572   else if (iWidth <= 1920 && iHeight <= 1440)
573     return "1080";
574   // 4K
575   else if (iWidth <= 4096 && iHeight <= 3072)
576     return "4K";
577   // 8K
578   else if (iWidth <= 8192 && iHeight <= 6144)
579     return "8K";
580   else
581     return "";
582 }
583 
VideoAspectToAspectDescription(float fAspect)584 std::string CStreamDetails::VideoAspectToAspectDescription(float fAspect)
585 {
586   if (fAspect == 0.0f)
587     return "";
588 
589   // Given that we're never going to be able to handle every single possibility in
590   // aspect ratios, particularly when cropping prior to video encoding is taken into account
591   // the best we can do is take the "common" aspect ratios, and return the closest one available.
592   // The cutoffs are the geometric mean of the two aspect ratios either side.
593   if (fAspect < 1.3499f) // sqrt(1.33*1.37)
594     return "1.33";
595   else if (fAspect < 1.5080f) // sqrt(1.37*1.66)
596     return "1.37";
597   else if (fAspect < 1.7190f) // sqrt(1.66*1.78)
598     return "1.66";
599   else if (fAspect < 1.8147f) // sqrt(1.78*1.85)
600     return "1.78";
601   else if (fAspect < 2.0174f) // sqrt(1.85*2.20)
602     return "1.85";
603   else if (fAspect < 2.2738f) // sqrt(2.20*2.35)
604     return "2.20";
605   else if (fAspect < 2.3749f) // sqrt(2.35*2.40)
606     return "2.35";
607   else if (fAspect < 2.4739f) // sqrt(2.40*2.55)
608     return "2.40";
609   else if (fAspect < 2.6529f) // sqrt(2.55*2.76)
610     return "2.55";
611   return "2.76";
612 }
613 
SetStreams(const VideoStreamInfo & videoInfo,int videoDuration,const AudioStreamInfo & audioInfo,const SubtitleStreamInfo & subtitleInfo)614 bool CStreamDetails::SetStreams(const VideoStreamInfo& videoInfo, int videoDuration, const AudioStreamInfo& audioInfo, const SubtitleStreamInfo& subtitleInfo)
615 {
616   if (!videoInfo.valid && !audioInfo.valid && !subtitleInfo.valid)
617     return false;
618   Reset();
619   if (videoInfo.valid)
620     AddStream(new CStreamDetailVideo(videoInfo, videoDuration));
621   if (audioInfo.valid)
622     AddStream(new CStreamDetailAudio(audioInfo));
623   if (subtitleInfo.valid)
624     AddStream(new CStreamDetailSubtitle(subtitleInfo));
625   DetermineBestStreams();
626   return true;
627 }
628