Merge pull request #5101 from FernetMenta/ffmpeg-threads
[vuplus_xbmc] / xbmc / cores / dvdplayer / DVDFileInfo.cpp
1 /*
2  *      Copyright (C) 2005-2013 Team XBMC
3  *      http://xbmc.org
4  *
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)
8  *  any later version.
9  *
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.
14  *
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/>.
18  *
19  */
20
21 #include "threads/SystemClock.h"
22 #include "DVDFileInfo.h"
23 #include "FileItem.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"
31
32 #include "DVDClock.h"
33 #include "DVDStreamInfo.h"
34 #include "DVDInputStreams/DVDInputStream.h"
35 #ifdef HAVE_LIBBLURAY
36 #include "DVDInputStreams/DVDInputStreamBluray.h"
37 #endif
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"
48
49 #include "DllAvCodec.h"
50 #include "DllSwScale.h"
51 #include "filesystem/File.h"
52 #include "TextureCache.h"
53 #include "Util.h"
54 #include "utils/LangCodeExpander.h"
55
56
57 bool CDVDFileInfo::GetFileDuration(const CStdString &path, int& duration)
58 {
59   std::auto_ptr<CDVDInputStream> input;
60   std::auto_ptr<CDVDDemux> demux;
61
62   input.reset(CDVDFactoryInputStream::CreateInputStream(NULL, path, ""));
63   if (!input.get())
64     return false;
65
66   if (!input->Open(path, ""))
67     return false;
68
69   demux.reset(CDVDFactoryDemuxer::CreateDemuxer(input.get()));
70   if (!demux.get())
71     return false;
72
73   duration = demux->GetStreamLength();
74   if (duration > 0)
75     return true;
76   else
77     return false;
78 }
79
80 int DegreeToOrientation(int degrees)
81 {
82   switch(degrees)
83   {
84     case 90:
85       return 5;
86     case 180:
87       return 2;
88     case 270:
89       return 7;
90     default:
91       return 0;
92   }
93 }
94
95 bool CDVDFileInfo::ExtractThumb(const CStdString &strPath, CTextureDetails &details, CStreamDetails *pStreamDetails)
96 {
97   std::string redactPath = CURL::GetRedacted(strPath);
98   unsigned int nTime = XbmcThreads::SystemClockMillis();
99   CDVDInputStream *pInputStream = CDVDFactoryInputStream::CreateInputStream(NULL, strPath, "");
100   if (!pInputStream)
101   {
102     CLog::Log(LOGERROR, "InputStream: Error creating stream for %s", redactPath.c_str());
103     return false;
104   }
105
106   if (pInputStream->IsStreamType(DVDSTREAM_TYPE_DVD))
107   {
108     CLog::Log(LOGERROR, "InputStream: dvd streams not supported for thumb extraction, file: %s", redactPath.c_str());
109     delete pInputStream;
110     return false;
111   }
112
113   if (!pInputStream->Open(strPath.c_str(), ""))
114   {
115     CLog::Log(LOGERROR, "InputStream: Error opening, %s", redactPath.c_str());
116     if (pInputStream)
117       delete pInputStream;
118     return false;
119   }
120
121   CDVDDemux *pDemuxer = NULL;
122
123   try
124   {
125     pDemuxer = CDVDFactoryDemuxer::CreateDemuxer(pInputStream);
126     if(!pDemuxer)
127     {
128       delete pInputStream;
129       CLog::Log(LOGERROR, "%s - Error creating demuxer", __FUNCTION__);
130       return false;
131     }
132   }
133   catch(...)
134   {
135     CLog::Log(LOGERROR, "%s - Exception thrown when opening demuxer", __FUNCTION__);
136     if (pDemuxer)
137       delete pDemuxer;
138     delete pInputStream;
139     return false;
140   }
141
142   if (pStreamDetails)
143   {
144     DemuxerToStreamDetails(pInputStream, pDemuxer, *pStreamDetails, strPath);
145
146     //extern subtitles
147     std::vector<CStdString> filenames;
148     CStdString video_path;
149     if (strPath.empty())
150       video_path = pInputStream->GetFileName();
151     else
152       video_path = strPath;
153
154     CUtil::ScanForExternalSubtitles(video_path, filenames);
155
156     for(unsigned int i=0;i<filenames.size();i++)
157     {
158       // if vobsub subtitle:
159       if (URIUtils::GetExtension(filenames[i]) == ".idx")
160       {
161         CStdString strSubFile;
162         if ( CUtil::FindVobSubPair(filenames, filenames[i], strSubFile) )
163           AddExternalSubtitleToDetails(video_path, *pStreamDetails, filenames[i], strSubFile);
164       }
165       else
166       {
167         if ( !CUtil::IsVobSub(filenames, filenames[i]) )
168         {
169           AddExternalSubtitleToDetails(video_path, *pStreamDetails, filenames[i]);
170         }
171       }
172     }
173   }
174
175   int nVideoStream = -1;
176   for (int i = 0; i < pDemuxer->GetNrOfStreams(); i++)
177   {
178     CDemuxStream* pStream = pDemuxer->GetStream(i);
179     if (pStream)
180     {
181       // ignore if it's a picture attachment (e.g. jpeg artwork)
182       if(pStream->type == STREAM_VIDEO && !(pStream->flags & AV_DISPOSITION_ATTACHED_PIC))
183         nVideoStream = i;
184       else
185         pStream->SetDiscard(AVDISCARD_ALL);
186     }
187   }
188
189   bool bOk = false;
190   int packetsTried = 0;
191
192   if (nVideoStream != -1)
193   {
194     CDVDVideoCodec *pVideoCodec;
195
196     CDVDStreamInfo hint(*pDemuxer->GetStream(nVideoStream), true);
197     hint.software = true;
198
199     if (hint.codec == AV_CODEC_ID_MPEG2VIDEO || hint.codec == AV_CODEC_ID_MPEG1VIDEO)
200     {
201       // libmpeg2 is not thread safe so use ffmepg for mpeg2/mpeg1 thumb extraction
202       CDVDCodecOptions dvdOptions;
203       pVideoCodec = CDVDFactoryCodec::OpenCodec(new CDVDVideoCodecFFmpeg(), hint, dvdOptions);
204     }
205     else
206     {
207       pVideoCodec = CDVDFactoryCodec::CreateVideoCodec( hint );
208     }
209
210     if (pVideoCodec)
211     {
212       int nTotalLen = pDemuxer->GetStreamLength();
213       int nSeekTo = nTotalLen / 3;
214
215       CLog::Log(LOGDEBUG,"%s - seeking to pos %dms (total: %dms) in %s", __FUNCTION__, nSeekTo, nTotalLen, redactPath.c_str());
216       if (pDemuxer->SeekTime(nSeekTo, true))
217       {
218         int iDecoderState = VC_ERROR;
219         DVDVideoPicture picture;
220
221         memset(&picture, 0, sizeof(picture));
222
223         // num streams * 80 frames, should get a valid frame, if not abort.
224         int abort_index = pDemuxer->GetNrOfStreams() * 80;
225         do
226         {
227           DemuxPacket* pPacket = pDemuxer->Read();
228           packetsTried++;
229
230           if (!pPacket)
231             break;
232
233           if (pPacket->iStreamId != nVideoStream)
234           {
235             CDVDDemuxUtils::FreeDemuxPacket(pPacket);
236             continue;
237           }
238
239           iDecoderState = pVideoCodec->Decode(pPacket->pData, pPacket->iSize, pPacket->dts, pPacket->pts);
240           CDVDDemuxUtils::FreeDemuxPacket(pPacket);
241
242           if (iDecoderState & VC_ERROR)
243             break;
244
245           if (iDecoderState & VC_PICTURE)
246           {
247             memset(&picture, 0, sizeof(DVDVideoPicture));
248             if (pVideoCodec->GetPicture(&picture))
249             {
250               if(!(picture.iFlags & DVP_FLAG_DROPPED))
251                 break;
252             }
253           }
254
255         } while (abort_index--);
256
257         if (iDecoderState & VC_PICTURE && !(picture.iFlags & DVP_FLAG_DROPPED))
258         {
259           {
260             unsigned int nWidth = g_advancedSettings.GetThumbSize();
261             double aspect = (double)picture.iDisplayWidth / (double)picture.iDisplayHeight;
262             if(hint.forced_aspect && hint.aspect != 0)
263               aspect = hint.aspect;
264             unsigned int nHeight = (unsigned int)((double)g_advancedSettings.GetThumbSize() / aspect);
265
266             DllSwScale dllSwScale;
267             dllSwScale.Load();
268
269             uint8_t *pOutBuf = new uint8_t[nWidth * nHeight * 4];
270             struct SwsContext *context = dllSwScale.sws_getContext(picture.iWidth, picture.iHeight,
271                   PIX_FMT_YUV420P, nWidth, nHeight, PIX_FMT_BGRA, SWS_FAST_BILINEAR | SwScaleCPUFlags(), NULL, NULL, NULL);
272
273             if (context)
274             {
275               uint8_t *src[] = { picture.data[0], picture.data[1], picture.data[2], 0 };
276               int     srcStride[] = { picture.iLineSize[0], picture.iLineSize[1], picture.iLineSize[2], 0 };
277               uint8_t *dst[] = { pOutBuf, 0, 0, 0 };
278               int     dstStride[] = { (int)nWidth*4, 0, 0, 0 };
279               int orientation = DegreeToOrientation(hint.orientation);
280               dllSwScale.sws_scale(context, src, srcStride, 0, picture.iHeight, dst, dstStride);
281               dllSwScale.sws_freeContext(context);
282
283               details.width = nWidth;
284               details.height = nHeight;
285               CPicture::CacheTexture(pOutBuf, nWidth, nHeight, nWidth * 4, orientation, nWidth, nHeight, CTextureCache::GetCachedPath(details.file));
286               bOk = true;
287             }
288
289             dllSwScale.Unload();
290             delete [] pOutBuf;
291           }
292         }
293         else
294         {
295           CLog::Log(LOGDEBUG,"%s - decode failed in %s after %d packets.", __FUNCTION__, redactPath.c_str(), packetsTried);
296         }
297       }
298       delete pVideoCodec;
299     }
300   }
301
302   if (pDemuxer)
303     delete pDemuxer;
304
305   delete pInputStream;
306
307   if(!bOk)
308   {
309     XFILE::CFile file;
310     if(file.OpenForWrite(CTextureCache::GetCachedPath(details.file)))
311       file.Close();
312   }
313
314   unsigned int nTotalTime = XbmcThreads::SystemClockMillis() - nTime;
315   CLog::Log(LOGDEBUG,"%s - measured %u ms to extract thumb from file <%s> in %d packets. ", __FUNCTION__, nTotalTime, redactPath.c_str(), packetsTried);
316   return bOk;
317 }
318
319 /**
320  * \brief Open the item pointed to by pItem and extact streamdetails
321  * \return true if the stream details have changed
322  */
323 bool CDVDFileInfo::GetFileStreamDetails(CFileItem *pItem)
324 {
325   if (!pItem)
326     return false;
327
328   CStdString strFileNameAndPath;
329   if (pItem->HasVideoInfoTag())
330     strFileNameAndPath = pItem->GetVideoInfoTag()->m_strFileNameAndPath;
331
332   if (strFileNameAndPath.empty())
333     strFileNameAndPath = pItem->GetPath();
334
335   CStdString playablePath = strFileNameAndPath;
336   if (URIUtils::IsStack(playablePath))
337     playablePath = XFILE::CStackDirectory::GetFirstStackedFile(playablePath);
338
339   CDVDInputStream *pInputStream = CDVDFactoryInputStream::CreateInputStream(NULL, playablePath, "");
340   if (!pInputStream)
341     return false;
342
343   if (pInputStream->IsStreamType(DVDSTREAM_TYPE_DVD) || !pInputStream->Open(playablePath.c_str(), ""))
344   {
345     delete pInputStream;
346     return false;
347   }
348
349   CDVDDemux *pDemuxer = CDVDFactoryDemuxer::CreateDemuxer(pInputStream);
350   if (pDemuxer)
351   {
352     bool retVal = DemuxerToStreamDetails(pInputStream, pDemuxer, pItem->GetVideoInfoTag()->m_streamDetails, strFileNameAndPath);
353     delete pDemuxer;
354     delete pInputStream;
355     return retVal;
356   }
357   else
358   {
359     delete pInputStream;
360     return false;
361   }
362 }
363
364 bool CDVDFileInfo::DemuxerToStreamDetails(CDVDInputStream *pInputStream, CDVDDemux *pDemuxer, const std::vector<CStreamDetailSubtitle> &subs, CStreamDetails &details)
365 {
366   bool result = DemuxerToStreamDetails(pInputStream, pDemuxer, details);
367   for (unsigned int i = 0; i < subs.size(); i++)
368   {
369     CStreamDetailSubtitle* sub = new CStreamDetailSubtitle();
370     sub->m_strLanguage = subs[i].m_strLanguage;
371     details.AddStream(sub);
372     result = true;
373   }
374   return result;
375 }
376
377 /* returns true if details have been added */
378 bool CDVDFileInfo::DemuxerToStreamDetails(CDVDInputStream *pInputStream, CDVDDemux *pDemux, CStreamDetails &details, const CStdString &path)
379 {
380   bool retVal = false;
381   details.Reset();
382
383   for (int iStream=0; iStream<pDemux->GetNrOfStreams(); iStream++)
384   {
385     CDemuxStream *stream = pDemux->GetStream(iStream);
386     if (stream->type == STREAM_VIDEO && !(stream->flags & AV_DISPOSITION_ATTACHED_PIC))
387     {
388       CStreamDetailVideo *p = new CStreamDetailVideo();
389       p->m_iWidth = ((CDemuxStreamVideo *)stream)->iWidth;
390       p->m_iHeight = ((CDemuxStreamVideo *)stream)->iHeight;
391       p->m_fAspect = ((CDemuxStreamVideo *)stream)->fAspect;
392       if (p->m_fAspect == 0.0f)
393         p->m_fAspect = (float)p->m_iWidth / p->m_iHeight;
394       pDemux->GetStreamCodecName(iStream, p->m_strCodec);
395       p->m_iDuration = pDemux->GetStreamLength();
396       p->m_strStereoMode = ((CDemuxStreamVideo *)stream)->stereo_mode;
397
398       // stack handling
399       if (URIUtils::IsStack(path))
400       {
401         CFileItemList files;
402         XFILE::CStackDirectory stack;
403         stack.GetDirectory(path, files);
404
405         // skip first path as we already know the duration
406         for (int i = 1; i < files.Size(); i++)
407         {
408            int duration = 0;
409            if (CDVDFileInfo::GetFileDuration(files[i]->GetPath(), duration))
410              p->m_iDuration = p->m_iDuration + duration;
411         }
412       }
413
414       // finally, calculate seconds
415       if (p->m_iDuration > 0)
416         p->m_iDuration = p->m_iDuration / 1000;
417
418       details.AddStream(p);
419       retVal = true;
420     }
421
422     else if (stream->type == STREAM_AUDIO)
423     {
424       CStreamDetailAudio *p = new CStreamDetailAudio();
425       p->m_iChannels = ((CDemuxStreamAudio *)stream)->iChannels;
426       p->m_strLanguage = stream->language;
427       pDemux->GetStreamCodecName(iStream, p->m_strCodec);
428       details.AddStream(p);
429       retVal = true;
430     }
431
432     else if (stream->type == STREAM_SUBTITLE)
433     {
434       CStreamDetailSubtitle *p = new CStreamDetailSubtitle();
435       p->m_strLanguage = stream->language;
436       details.AddStream(p);
437       retVal = true;
438     }
439   }  /* for iStream */
440
441   details.DetermineBestStreams();
442 #ifdef HAVE_LIBBLURAY
443   // correct bluray runtime. we need the duration from the input stream, not the demuxer.
444   if (pInputStream->IsStreamType(DVDSTREAM_TYPE_BLURAY))
445   {
446     if(((CDVDInputStreamBluray*)pInputStream)->GetTotalTime() > 0)
447     {
448       ((CStreamDetailVideo*)details.GetNthStream(CStreamDetail::VIDEO,0))->m_iDuration = ((CDVDInputStreamBluray*)pInputStream)->GetTotalTime() / 1000;
449     }
450   }
451 #endif
452   return retVal;
453 }
454
455 bool CDVDFileInfo::AddExternalSubtitleToDetails(const CStdString &path, CStreamDetails &details, const std::string& filename, const std::string& subfilename)
456 {
457   std::string ext = URIUtils::GetExtension(filename);
458   std::string vobsubfile = subfilename;
459   if(ext == ".idx")
460   {
461     if (vobsubfile.empty())
462       vobsubfile = URIUtils::ReplaceExtension(filename, ".sub");
463
464     CDVDDemuxVobsub v;
465     if(!v.Open(filename, vobsubfile))
466       return false;
467
468     int count = v.GetNrOfStreams();
469
470     for(int i = 0; i < count; i++)
471     {
472       CStreamDetailSubtitle *dsub = new CStreamDetailSubtitle();
473       CDemuxStream* stream = v.GetStream(i);
474       std::string lang = stream->language;
475       dsub->m_strLanguage = g_LangCodeExpander.ConvertToISO6392T(lang);
476       details.AddStream(dsub);
477     }
478     return true;
479   }
480   if(ext == ".sub")
481   {
482     CStdString strReplace(URIUtils::ReplaceExtension(filename,".idx"));
483     if (XFILE::CFile::Exists(strReplace))
484       return false;
485   }
486
487   CStreamDetailSubtitle *dsub = new CStreamDetailSubtitle();
488   ExternalStreamInfo info;
489   CUtil::GetExternalStreamDetailsFromFilename(path, filename, info);
490   dsub->m_strLanguage = g_LangCodeExpander.ConvertToISO6392T(info.language);
491   details.AddStream(dsub);
492
493   return true;
494 }
495