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