2 * Copyright (C) 2005-2013 Team XBMC
5 * This Program is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation; either version 2, or (at your option)
10 * This Program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
15 * You should have received a copy of the GNU General Public License
16 * along with XBMC; see the file COPYING. If not, see
17 * <http://www.gnu.org/licenses/>.
21 #include "threads/SystemClock.h"
22 #include "DVDFileInfo.h"
24 #include "settings/AdvancedSettings.h"
25 #include "pictures/Picture.h"
26 #include "video/VideoInfoTag.h"
27 #include "filesystem/StackDirectory.h"
28 #include "utils/log.h"
29 #include "utils/TimeUtils.h"
30 #include "utils/URIUtils.h"
33 #include "DVDStreamInfo.h"
34 #include "DVDInputStreams/DVDInputStream.h"
36 #include "DVDInputStreams/DVDInputStreamBluray.h"
38 #include "DVDInputStreams/DVDFactoryInputStream.h"
39 #include "DVDDemuxers/DVDDemux.h"
40 #include "DVDDemuxers/DVDDemuxUtils.h"
41 #include "DVDDemuxers/DVDFactoryDemuxer.h"
42 #include "DVDDemuxers/DVDDemuxFFmpeg.h"
43 #include "DVDCodecs/DVDCodecs.h"
44 #include "DVDCodecs/DVDFactoryCodec.h"
45 #include "DVDCodecs/Video/DVDVideoCodec.h"
46 #include "DVDCodecs/Video/DVDVideoCodecFFmpeg.h"
47 #include "DVDDemuxers/DVDDemuxVobsub.h"
49 #include "DllAvCodec.h"
50 #include "DllSwScale.h"
51 #include "filesystem/File.h"
52 #include "TextureCache.h"
54 #include "utils/LangCodeExpander.h"
57 bool CDVDFileInfo::GetFileDuration(const CStdString &path, int& duration)
59 std::auto_ptr<CDVDInputStream> input;
60 std::auto_ptr<CDVDDemux> demux;
62 input.reset(CDVDFactoryInputStream::CreateInputStream(NULL, path, ""));
66 if (!input->Open(path, ""))
69 demux.reset(CDVDFactoryDemuxer::CreateDemuxer(input.get()));
73 duration = demux->GetStreamLength();
80 int DegreeToOrientation(int degrees)
95 bool CDVDFileInfo::ExtractThumb(const CStdString &strPath, CTextureDetails &details, CStreamDetails *pStreamDetails)
97 std::string redactPath = CURL::GetRedacted(strPath);
98 unsigned int nTime = XbmcThreads::SystemClockMillis();
99 CDVDInputStream *pInputStream = CDVDFactoryInputStream::CreateInputStream(NULL, strPath, "");
102 CLog::Log(LOGERROR, "InputStream: Error creating stream for %s", redactPath.c_str());
106 if (pInputStream->IsStreamType(DVDSTREAM_TYPE_DVD))
108 CLog::Log(LOGERROR, "InputStream: dvd streams not supported for thumb extraction, file: %s", redactPath.c_str());
113 if (!pInputStream->Open(strPath.c_str(), ""))
115 CLog::Log(LOGERROR, "InputStream: Error opening, %s", redactPath.c_str());
121 CDVDDemux *pDemuxer = NULL;
125 pDemuxer = CDVDFactoryDemuxer::CreateDemuxer(pInputStream);
129 CLog::Log(LOGERROR, "%s - Error creating demuxer", __FUNCTION__);
135 CLog::Log(LOGERROR, "%s - Exception thrown when opening demuxer", __FUNCTION__);
144 DemuxerToStreamDetails(pInputStream, pDemuxer, *pStreamDetails, strPath);
147 std::vector<CStdString> filenames;
148 CStdString video_path;
150 video_path = pInputStream->GetFileName();
152 video_path = strPath;
154 CUtil::ScanForExternalSubtitles(video_path, filenames);
156 for(unsigned int i=0;i<filenames.size();i++)
158 // if vobsub subtitle:
159 if (URIUtils::GetExtension(filenames[i]) == ".idx")
161 CStdString strSubFile;
162 if ( CUtil::FindVobSubPair(filenames, filenames[i], strSubFile) )
163 AddExternalSubtitleToDetails(video_path, *pStreamDetails, filenames[i], strSubFile);
167 if ( !CUtil::IsVobSub(filenames, filenames[i]) )
169 AddExternalSubtitleToDetails(video_path, *pStreamDetails, filenames[i]);
175 int nVideoStream = -1;
176 for (int i = 0; i < pDemuxer->GetNrOfStreams(); i++)
178 CDemuxStream* pStream = pDemuxer->GetStream(i);
181 if(pStream->type == STREAM_VIDEO)
184 pStream->SetDiscard(AVDISCARD_ALL);
189 int packetsTried = 0;
191 if (nVideoStream != -1)
193 CDVDVideoCodec *pVideoCodec;
195 CDVDStreamInfo hint(*pDemuxer->GetStream(nVideoStream), true);
196 hint.software = true;
198 if (hint.codec == AV_CODEC_ID_MPEG2VIDEO || hint.codec == AV_CODEC_ID_MPEG1VIDEO)
200 // libmpeg2 is not thread safe so use ffmepg for mpeg2/mpeg1 thumb extraction
201 CDVDCodecOptions dvdOptions;
202 pVideoCodec = CDVDFactoryCodec::OpenCodec(new CDVDVideoCodecFFmpeg(), hint, dvdOptions);
206 pVideoCodec = CDVDFactoryCodec::CreateVideoCodec( hint );
211 int nTotalLen = pDemuxer->GetStreamLength();
212 int nSeekTo = nTotalLen / 3;
214 CLog::Log(LOGDEBUG,"%s - seeking to pos %dms (total: %dms) in %s", __FUNCTION__, nSeekTo, nTotalLen, redactPath.c_str());
215 if (pDemuxer->SeekTime(nSeekTo, true))
217 int iDecoderState = VC_ERROR;
218 DVDVideoPicture picture;
220 memset(&picture, 0, sizeof(picture));
222 // num streams * 80 frames, should get a valid frame, if not abort.
223 int abort_index = pDemuxer->GetNrOfStreams() * 80;
226 DemuxPacket* pPacket = pDemuxer->Read();
232 if (pPacket->iStreamId != nVideoStream)
234 CDVDDemuxUtils::FreeDemuxPacket(pPacket);
238 iDecoderState = pVideoCodec->Decode(pPacket->pData, pPacket->iSize, pPacket->dts, pPacket->pts);
239 CDVDDemuxUtils::FreeDemuxPacket(pPacket);
241 if (iDecoderState & VC_ERROR)
244 if (iDecoderState & VC_PICTURE)
246 memset(&picture, 0, sizeof(DVDVideoPicture));
247 if (pVideoCodec->GetPicture(&picture))
249 if(!(picture.iFlags & DVP_FLAG_DROPPED))
254 } while (abort_index--);
256 if (iDecoderState & VC_PICTURE && !(picture.iFlags & DVP_FLAG_DROPPED))
259 unsigned int nWidth = g_advancedSettings.GetThumbSize();
260 double aspect = (double)picture.iDisplayWidth / (double)picture.iDisplayHeight;
261 if(hint.forced_aspect && hint.aspect != 0)
262 aspect = hint.aspect;
263 unsigned int nHeight = (unsigned int)((double)g_advancedSettings.GetThumbSize() / aspect);
265 DllSwScale dllSwScale;
268 uint8_t *pOutBuf = new uint8_t[nWidth * nHeight * 4];
269 struct SwsContext *context = dllSwScale.sws_getContext(picture.iWidth, picture.iHeight,
270 PIX_FMT_YUV420P, nWidth, nHeight, PIX_FMT_BGRA, SWS_FAST_BILINEAR | SwScaleCPUFlags(), NULL, NULL, NULL);
274 uint8_t *src[] = { picture.data[0], picture.data[1], picture.data[2], 0 };
275 int srcStride[] = { picture.iLineSize[0], picture.iLineSize[1], picture.iLineSize[2], 0 };
276 uint8_t *dst[] = { pOutBuf, 0, 0, 0 };
277 int dstStride[] = { (int)nWidth*4, 0, 0, 0 };
278 int orientation = DegreeToOrientation(hint.orientation);
279 dllSwScale.sws_scale(context, src, srcStride, 0, picture.iHeight, dst, dstStride);
280 dllSwScale.sws_freeContext(context);
282 details.width = nWidth;
283 details.height = nHeight;
284 CPicture::CacheTexture(pOutBuf, nWidth, nHeight, nWidth * 4, orientation, nWidth, nHeight, CTextureCache::GetCachedPath(details.file));
294 CLog::Log(LOGDEBUG,"%s - decode failed in %s after %d packets.", __FUNCTION__, redactPath.c_str(), packetsTried);
309 if(file.OpenForWrite(CTextureCache::GetCachedPath(details.file)))
313 unsigned int nTotalTime = XbmcThreads::SystemClockMillis() - nTime;
314 CLog::Log(LOGDEBUG,"%s - measured %u ms to extract thumb from file <%s> in %d packets. ", __FUNCTION__, nTotalTime, redactPath.c_str(), packetsTried);
319 * \brief Open the item pointed to by pItem and extact streamdetails
320 * \return true if the stream details have changed
322 bool CDVDFileInfo::GetFileStreamDetails(CFileItem *pItem)
327 CStdString strFileNameAndPath;
328 if (pItem->HasVideoInfoTag())
329 strFileNameAndPath = pItem->GetVideoInfoTag()->m_strFileNameAndPath;
331 if (strFileNameAndPath.empty())
332 strFileNameAndPath = pItem->GetPath();
334 CStdString playablePath = strFileNameAndPath;
335 if (URIUtils::IsStack(playablePath))
336 playablePath = XFILE::CStackDirectory::GetFirstStackedFile(playablePath);
338 CDVDInputStream *pInputStream = CDVDFactoryInputStream::CreateInputStream(NULL, playablePath, "");
342 if (pInputStream->IsStreamType(DVDSTREAM_TYPE_DVD) || !pInputStream->Open(playablePath.c_str(), ""))
348 CDVDDemux *pDemuxer = CDVDFactoryDemuxer::CreateDemuxer(pInputStream);
351 bool retVal = DemuxerToStreamDetails(pInputStream, pDemuxer, pItem->GetVideoInfoTag()->m_streamDetails, strFileNameAndPath);
363 bool CDVDFileInfo::DemuxerToStreamDetails(CDVDInputStream *pInputStream, CDVDDemux *pDemuxer, const std::vector<CStreamDetailSubtitle> &subs, CStreamDetails &details)
365 bool result = DemuxerToStreamDetails(pInputStream, pDemuxer, details);
366 for (unsigned int i = 0; i < subs.size(); i++)
368 CStreamDetailSubtitle* sub = new CStreamDetailSubtitle();
369 sub->m_strLanguage = subs[i].m_strLanguage;
370 details.AddStream(sub);
376 /* returns true if details have been added */
377 bool CDVDFileInfo::DemuxerToStreamDetails(CDVDInputStream *pInputStream, CDVDDemux *pDemux, CStreamDetails &details, const CStdString &path)
382 for (int iStream=0; iStream<pDemux->GetNrOfStreams(); iStream++)
384 CDemuxStream *stream = pDemux->GetStream(iStream);
385 if (stream->type == STREAM_VIDEO)
387 CStreamDetailVideo *p = new CStreamDetailVideo();
388 p->m_iWidth = ((CDemuxStreamVideo *)stream)->iWidth;
389 p->m_iHeight = ((CDemuxStreamVideo *)stream)->iHeight;
390 p->m_fAspect = ((CDemuxStreamVideo *)stream)->fAspect;
391 if (p->m_fAspect == 0.0f)
392 p->m_fAspect = (float)p->m_iWidth / p->m_iHeight;
393 pDemux->GetStreamCodecName(iStream, p->m_strCodec);
394 p->m_iDuration = pDemux->GetStreamLength();
395 p->m_strStereoMode = ((CDemuxStreamVideo *)stream)->stereo_mode;
398 if (URIUtils::IsStack(path))
401 XFILE::CStackDirectory stack;
402 stack.GetDirectory(path, files);
404 // skip first path as we already know the duration
405 for (int i = 1; i < files.Size(); i++)
408 if (CDVDFileInfo::GetFileDuration(files[i]->GetPath(), duration))
409 p->m_iDuration = p->m_iDuration + duration;
413 // finally, calculate seconds
414 if (p->m_iDuration > 0)
415 p->m_iDuration = p->m_iDuration / 1000;
417 details.AddStream(p);
421 else if (stream->type == STREAM_AUDIO)
423 CStreamDetailAudio *p = new CStreamDetailAudio();
424 p->m_iChannels = ((CDemuxStreamAudio *)stream)->iChannels;
425 p->m_strLanguage = stream->language;
426 pDemux->GetStreamCodecName(iStream, p->m_strCodec);
427 details.AddStream(p);
431 else if (stream->type == STREAM_SUBTITLE)
433 CStreamDetailSubtitle *p = new CStreamDetailSubtitle();
434 p->m_strLanguage = stream->language;
435 details.AddStream(p);
440 details.DetermineBestStreams();
441 #ifdef HAVE_LIBBLURAY
442 // correct bluray runtime. we need the duration from the input stream, not the demuxer.
443 if (pInputStream->IsStreamType(DVDSTREAM_TYPE_BLURAY))
445 if(((CDVDInputStreamBluray*)pInputStream)->GetTotalTime() > 0)
447 ((CStreamDetailVideo*)details.GetNthStream(CStreamDetail::VIDEO,0))->m_iDuration = ((CDVDInputStreamBluray*)pInputStream)->GetTotalTime() / 1000;
454 bool CDVDFileInfo::AddExternalSubtitleToDetails(const CStdString &path, CStreamDetails &details, const std::string& filename, const std::string& subfilename)
456 std::string ext = URIUtils::GetExtension(filename);
457 std::string vobsubfile = subfilename;
460 if (vobsubfile.empty())
461 vobsubfile = URIUtils::ReplaceExtension(filename, ".sub");
464 if(!v.Open(filename, vobsubfile))
467 int count = v.GetNrOfStreams();
469 for(int i = 0; i < count; i++)
471 CStreamDetailSubtitle *dsub = new CStreamDetailSubtitle();
472 CDemuxStream* stream = v.GetStream(i);
473 std::string lang = stream->language;
474 dsub->m_strLanguage = g_LangCodeExpander.ConvertToISO6392T(lang);
475 details.AddStream(dsub);
481 CStdString strReplace(URIUtils::ReplaceExtension(filename,".idx"));
482 if (XFILE::CFile::Exists(strReplace))
486 CStreamDetailSubtitle *dsub = new CStreamDetailSubtitle();
487 ExternalStreamInfo info;
488 CUtil::GetExternalStreamDetailsFromFilename(path, filename, info);
489 dsub->m_strLanguage = g_LangCodeExpander.ConvertToISO6392T(info.language);
490 details.AddStream(dsub);