2 * Copyright (C) 2005-2018 Team Kodi
3 * This file is part of Kodi - https://kodi.tv
5 * SPDX-License-Identifier: GPL-2.0-or-later
6 * See LICENSES/README.md for more information.
9 #include "DVDFileInfo.h"
11 #include "DVDInputStreams/DVDInputStream.h"
12 #include "DVDStreamInfo.h"
14 #include "FileItemList.h"
15 #include "ServiceBroker.h"
16 #include "filesystem/StackDirectory.h"
17 #include "guilib/Texture.h"
18 #include "network/NetworkFileItemClassify.h"
19 #include "pictures/Picture.h"
20 #include "playlists/PlayListFileItemClassify.h"
21 #include "settings/AdvancedSettings.h"
22 #include "settings/SettingsComponent.h"
23 #include "utils/MemUtils.h"
24 #include "utils/URIUtils.h"
25 #include "utils/log.h"
26 #include "video/VideoFileItemClassify.h"
27 #include "video/VideoInfoTag.h"
29 #include "DVDInputStreams/DVDInputStreamBluray.h"
31 #include "DVDInputStreams/DVDFactoryInputStream.h"
32 #include "DVDDemuxers/DVDDemux.h"
33 #include "DVDDemuxers/DVDDemuxUtils.h"
34 #include "DVDDemuxers/DVDFactoryDemuxer.h"
35 #include "DVDCodecs/DVDFactoryCodec.h"
36 #include "DVDCodecs/Video/DVDVideoCodec.h"
37 #include "DVDCodecs/Video/DVDVideoCodecFFmpeg.h"
38 #include "DVDDemuxers/DVDDemuxVobsub.h"
39 #include "Process/ProcessInfo.h"
41 #include "filesystem/File.h"
42 #include "cores/FFmpeg.h"
43 #include "TextureCache.h"
45 #include "utils/LangCodeExpander.h"
51 #include <libavcodec/avcodec.h>
52 #include <libavformat/avformat.h>
53 #include <libswscale/swscale.h>
58 bool CDVDFileInfo::GetFileDuration(const std::string
&path
, int& duration
)
60 std::unique_ptr
<CDVDDemux
> demux
;
62 CFileItem
item(path
, false);
63 auto input
= CDVDFactoryInputStream::CreateInputStream(NULL
, item
);
70 demux
.reset(CDVDFactoryDemuxer::CreateDemuxer(input
, true));
74 duration
= demux
->GetStreamLength();
81 int DegreeToOrientation(int degrees
)
96 std::unique_ptr
<CTexture
> CDVDFileInfo::ExtractThumbToTexture(const CFileItem
& fileItem
,
99 if (!CanExtract(fileItem
))
102 const std::string redactPath
= CURL::GetRedacted(fileItem
.GetPath());
103 auto start
= std::chrono::steady_clock::now();
105 CFileItem
item(fileItem
);
106 item
.SetMimeTypeForInternetFile();
107 auto pInputStream
= CDVDFactoryInputStream::CreateInputStream(NULL
, item
);
110 CLog::Log(LOGERROR
, "InputStream: Error creating stream for {}", redactPath
);
114 if (!pInputStream
->Open())
116 CLog::Log(LOGERROR
, "InputStream: Error opening, {}", redactPath
);
120 std::unique_ptr
<CDVDDemux
> demuxer
{CDVDFactoryDemuxer::CreateDemuxer(pInputStream
, true)};
123 CLog::LogF(LOGERROR
, "Error creating demuxer");
127 int nVideoStream
= -1;
128 int64_t demuxerId
= -1;
129 for (CDemuxStream
* pStream
: demuxer
->GetStreams())
133 // ignore if it's a picture attachment (e.g. jpeg artwork)
134 if (pStream
->type
== STREAM_VIDEO
&& !(pStream
->flags
& AV_DISPOSITION_ATTACHED_PIC
))
136 nVideoStream
= pStream
->uniqueId
;
137 demuxerId
= pStream
->demuxerId
;
140 demuxer
->EnableStream(pStream
->demuxerId
, pStream
->uniqueId
, false);
144 int packetsTried
= 0;
146 std::unique_ptr
<CTexture
> result
{};
147 if (nVideoStream
!= -1)
149 std::unique_ptr
<CProcessInfo
> pProcessInfo(CProcessInfo::CreateInstance());
150 std::vector
<AVPixelFormat
> pixFmts
;
151 pixFmts
.push_back(AV_PIX_FMT_YUV420P
);
152 pProcessInfo
->SetPixFormats(pixFmts
);
154 CDVDStreamInfo
hint(*demuxer
->GetStream(demuxerId
, nVideoStream
), true);
155 hint
.codecOptions
= CODEC_FORCE_SOFTWARE
;
157 std::unique_ptr
<CDVDVideoCodec
> pVideoCodec
=
158 CDVDFactoryCodec::CreateVideoCodec(hint
, *pProcessInfo
);
162 int nTotalLen
= demuxer
->GetStreamLength();
164 bool seekToChapter
= chapterNumber
> 0 && demuxer
->GetChapterCount() > 0;
166 seekToChapter
? demuxer
->GetChapterPos(chapterNumber
) * 1000 : nTotalLen
/ 3;
168 CLog::LogF(LOGDEBUG
, "seeking to pos {}ms (total: {}ms) in {}", nSeekTo
, nTotalLen
,
171 if (demuxer
->SeekTime(static_cast<double>(nSeekTo
), true))
173 CDVDVideoCodec::VCReturn iDecoderState
= CDVDVideoCodec::VC_NONE
;
174 VideoPicture picture
= {};
176 // num streams * 160 frames, should get a valid frame, if not abort.
177 int abort_index
= demuxer
->GetNrOfStreams() * 160;
180 DemuxPacket
* pPacket
= demuxer
->Read();
186 if (pPacket
->iStreamId
!= nVideoStream
)
188 CDVDDemuxUtils::FreeDemuxPacket(pPacket
);
192 pVideoCodec
->AddData(*pPacket
);
193 CDVDDemuxUtils::FreeDemuxPacket(pPacket
);
195 iDecoderState
= CDVDVideoCodec::VC_NONE
;
196 while (iDecoderState
== CDVDVideoCodec::VC_NONE
)
198 iDecoderState
= pVideoCodec
->GetPicture(&picture
);
201 if (iDecoderState
== CDVDVideoCodec::VC_PICTURE
)
203 if (!(picture
.iFlags
& DVP_FLAG_DROPPED
))
207 } while (abort_index
--);
209 if (iDecoderState
== CDVDVideoCodec::VC_PICTURE
&& !(picture
.iFlags
& DVP_FLAG_DROPPED
))
211 unsigned int nWidth
=
212 std::min(picture
.iDisplayWidth
,
213 CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->m_imageRes
);
214 double aspect
= (double)picture
.iDisplayWidth
/ (double)picture
.iDisplayHeight
;
215 if (hint
.forced_aspect
&& hint
.aspect
!= 0)
216 aspect
= hint
.aspect
;
217 unsigned int nHeight
= (unsigned int)((double)nWidth
/ aspect
);
219 result
= CTexture::CreateTexture(nWidth
, nHeight
);
220 result
->SetAlpha(false);
221 struct SwsContext
* context
=
222 sws_getContext(picture
.iWidth
, picture
.iHeight
, AV_PIX_FMT_YUV420P
, nWidth
, nHeight
,
223 AV_PIX_FMT_BGRA
, SWS_FAST_BILINEAR
, NULL
, NULL
, NULL
);
227 uint8_t* planes
[YuvImage::MAX_PLANES
];
228 int stride
[YuvImage::MAX_PLANES
];
229 picture
.videoBuffer
->GetPlanes(planes
);
230 picture
.videoBuffer
->GetStrides(stride
);
231 uint8_t* src
[4] = {planes
[0], planes
[1], planes
[2], 0};
232 int srcStride
[] = {stride
[0], stride
[1], stride
[2], 0};
233 uint8_t* dst
[] = {result
->GetPixels(), 0, 0, 0};
234 int dstStride
[] = {static_cast<int>(result
->GetPitch()), 0, 0, 0};
235 result
->SetOrientation(DegreeToOrientation(hint
.orientation
));
236 sws_scale(context
, src
, srcStride
, 0, picture
.iHeight
, dst
, dstStride
);
237 sws_freeContext(context
);
242 CLog::LogF(LOGDEBUG
, "decode failed in {} after {} packets.", redactPath
, packetsTried
);
248 auto end
= std::chrono::steady_clock::now();
249 auto duration
= std::chrono::duration_cast
<std::chrono::milliseconds
>(end
- start
);
250 CLog::LogF(LOGDEBUG
, "measured {} ms to extract thumb from file <{}> in {} packets. ",
251 duration
.count(), redactPath
, packetsTried
);
256 bool CDVDFileInfo::CanExtract(const CFileItem
& fileItem
)
258 if (fileItem
.m_bIsFolder
)
261 if (fileItem
.IsLiveTV() ||
262 // Due to a pvr addon api design flaw (no support for multiple concurrent streams
263 // per addon instance), pvr recording thumbnail extraction does not work (reliably).
264 URIUtils::IsPVRRecording(fileItem
.GetDynPath()) ||
265 // plugin path not fully resolved
266 URIUtils::IsPlugin(fileItem
.GetDynPath()) || URIUtils::IsUPnP(fileItem
.GetPath()) ||
267 NETWORK::IsInternetStream(fileItem
) || VIDEO::IsDiscStub(fileItem
) ||
268 PLAYLIST::IsPlayList(fileItem
))
271 // mostly can't extract from discs and files from discs.
272 if (URIUtils::IsBluray(fileItem
.GetPath()) || VIDEO::IsBDFile(fileItem
) || fileItem
.IsDVD() ||
273 fileItem
.IsDiscImage() || VIDEO::IsDVDFile(fileItem
, false, true))
276 // For HTTP/FTP we only allow extraction when on a LAN
277 if (URIUtils::IsRemote(fileItem
.GetPath()) && !URIUtils::IsOnLAN(fileItem
.GetPath()) &&
278 (URIUtils::IsFTP(fileItem
.GetPath()) || URIUtils::IsHTTP(fileItem
.GetPath())))
285 * \brief Open the item pointed to by pItem and extract streamdetails
286 * \return true if the stream details have changed
288 bool CDVDFileInfo::GetFileStreamDetails(CFileItem
*pItem
)
293 if (!CanExtract(*pItem
))
296 std::string strFileNameAndPath
;
297 if (pItem
->HasVideoInfoTag())
298 strFileNameAndPath
= pItem
->GetVideoInfoTag()->m_strFileNameAndPath
;
300 if (strFileNameAndPath
.empty())
301 strFileNameAndPath
= pItem
->GetDynPath();
303 std::string playablePath
= strFileNameAndPath
;
304 if (URIUtils::IsStack(playablePath
))
305 playablePath
= XFILE::CStackDirectory::GetFirstStackedFile(playablePath
);
307 CFileItem
item(playablePath
, false);
308 item
.SetMimeTypeForInternetFile();
309 auto pInputStream
= CDVDFactoryInputStream::CreateInputStream(NULL
, item
);
313 if (pInputStream
->IsStreamType(DVDSTREAM_TYPE_PVRMANAGER
))
318 if (pInputStream
->IsStreamType(DVDSTREAM_TYPE_DVD
) || !pInputStream
->Open())
323 CDVDDemux
*pDemuxer
= CDVDFactoryDemuxer::CreateDemuxer(pInputStream
, true);
326 bool retVal
= DemuxerToStreamDetails(pInputStream
, pDemuxer
, pItem
->GetVideoInfoTag()->m_streamDetails
, strFileNameAndPath
);
327 ProcessExternalSubtitles(pItem
);
337 bool CDVDFileInfo::DemuxerToStreamDetails(const std::shared_ptr
<CDVDInputStream
>& pInputStream
,
339 const std::vector
<CStreamDetailSubtitle
>& subs
,
340 CStreamDetails
& details
)
342 bool result
= DemuxerToStreamDetails(pInputStream
, pDemuxer
, details
);
343 for (unsigned int i
= 0; i
< subs
.size(); i
++)
345 CStreamDetailSubtitle
* sub
= new CStreamDetailSubtitle();
346 sub
->m_strLanguage
= subs
[i
].m_strLanguage
;
347 details
.AddStream(sub
);
353 /* returns true if details have been added */
354 bool CDVDFileInfo::DemuxerToStreamDetails(const std::shared_ptr
<CDVDInputStream
>& pInputStream
,
356 CStreamDetails
& details
,
357 const std::string
& path
)
362 const CURL
pathToUrl(path
);
363 for (CDemuxStream
* stream
: pDemux
->GetStreams())
365 if (stream
->type
== STREAM_VIDEO
&& !(stream
->flags
& AV_DISPOSITION_ATTACHED_PIC
))
367 CStreamDetailVideo
*p
= new CStreamDetailVideo();
368 CDemuxStreamVideo
* vstream
= static_cast<CDemuxStreamVideo
*>(stream
);
369 p
->m_iWidth
= vstream
->iWidth
;
370 p
->m_iHeight
= vstream
->iHeight
;
371 p
->m_fAspect
= static_cast<float>(vstream
->fAspect
);
372 if (p
->m_fAspect
== 0.0f
&& p
->m_iHeight
> 0)
373 p
->m_fAspect
= (float)p
->m_iWidth
/ p
->m_iHeight
;
374 p
->m_strCodec
= pDemux
->GetStreamCodecName(stream
->demuxerId
, stream
->uniqueId
);
375 p
->m_iDuration
= pDemux
->GetStreamLength();
376 p
->m_strStereoMode
= vstream
->stereo_mode
;
377 p
->m_strLanguage
= vstream
->language
;
378 p
->m_strHdrType
= CStreamDetails::HdrTypeToString(vstream
->hdr_type
);
381 if (URIUtils::IsStack(path
))
384 XFILE::CStackDirectory stack
;
385 stack
.GetDirectory(pathToUrl
, files
);
387 // skip first path as we already know the duration
388 for (int i
= 1; i
< files
.Size(); i
++)
391 if (CDVDFileInfo::GetFileDuration(files
[i
]->GetDynPath(), duration
))
392 p
->m_iDuration
= p
->m_iDuration
+ duration
;
396 // finally, calculate seconds
397 if (p
->m_iDuration
> 0)
398 p
->m_iDuration
= p
->m_iDuration
/ 1000;
400 details
.AddStream(p
);
404 else if (stream
->type
== STREAM_AUDIO
)
406 CStreamDetailAudio
*p
= new CStreamDetailAudio();
407 p
->m_iChannels
= static_cast<CDemuxStreamAudio
*>(stream
)->iChannels
;
408 p
->m_strLanguage
= stream
->language
;
409 p
->m_strCodec
= pDemux
->GetStreamCodecName(stream
->demuxerId
, stream
->uniqueId
);
410 details
.AddStream(p
);
414 else if (stream
->type
== STREAM_SUBTITLE
)
416 CStreamDetailSubtitle
*p
= new CStreamDetailSubtitle();
417 p
->m_strLanguage
= stream
->language
;
418 details
.AddStream(p
);
423 details
.DetermineBestStreams();
424 #ifdef HAVE_LIBBLURAY
425 // correct bluray runtime. we need the duration from the input stream, not the demuxer.
426 if (pInputStream
->IsStreamType(DVDSTREAM_TYPE_BLURAY
))
428 if (std::static_pointer_cast
<CDVDInputStreamBluray
>(pInputStream
)->GetTotalTime() > 0)
430 const CStreamDetailVideo
* dVideo
= static_cast<const CStreamDetailVideo
*>(details
.GetNthStream(CStreamDetail::VIDEO
, 0));
431 CStreamDetailVideo
* detailVideo
= const_cast<CStreamDetailVideo
*>(dVideo
);
433 detailVideo
->m_iDuration
= std::static_pointer_cast
<CDVDInputStreamBluray
>(pInputStream
)->GetTotalTime() / 1000;
440 void CDVDFileInfo::ProcessExternalSubtitles(CFileItem
* item
)
442 std::vector
<std::string
> externalSubtitles
;
443 const std::string videoPath
= item
->GetDynPath();
445 CUtil::ScanForExternalSubtitles(videoPath
, externalSubtitles
);
447 for (const auto& externalSubtitle
: externalSubtitles
)
449 // if vobsub subtitle:
450 if (URIUtils::GetExtension(externalSubtitle
) == ".idx")
453 if (CUtil::FindVobSubPair(externalSubtitles
, externalSubtitle
, subFile
))
454 AddExternalSubtitleToDetails(videoPath
, item
->GetVideoInfoTag()->m_streamDetails
,
455 externalSubtitle
, subFile
);
459 if (!CUtil::IsVobSub(externalSubtitles
, externalSubtitle
))
461 AddExternalSubtitleToDetails(videoPath
, item
->GetVideoInfoTag()->m_streamDetails
,
468 bool CDVDFileInfo::AddExternalSubtitleToDetails(const std::string
&path
, CStreamDetails
&details
, const std::string
& filename
, const std::string
& subfilename
)
470 std::string ext
= URIUtils::GetExtension(filename
);
471 std::string vobsubfile
= subfilename
;
474 if (vobsubfile
.empty())
475 vobsubfile
= URIUtils::ReplaceExtension(filename
, ".sub");
478 if (!v
.Open(filename
, STREAM_SOURCE_NONE
, vobsubfile
))
481 for(CDemuxStream
* stream
: v
.GetStreams())
483 CStreamDetailSubtitle
*dsub
= new CStreamDetailSubtitle();
484 std::string lang
= stream
->language
;
485 dsub
->m_strLanguage
= g_LangCodeExpander
.ConvertToISO6392B(lang
);
486 details
.AddStream(dsub
);
492 std::string
strReplace(URIUtils::ReplaceExtension(filename
,".idx"));
493 if (XFILE::CFile::Exists(strReplace
))
497 CStreamDetailSubtitle
*dsub
= new CStreamDetailSubtitle();
498 ExternalStreamInfo info
= CUtil::GetExternalStreamDetailsFromFilename(path
, filename
);
499 dsub
->m_strLanguage
= g_LangCodeExpander
.ConvertToISO6392B(info
.language
);
500 details
.AddStream(dsub
);