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