Merge pull request #3020 from brooc/master
[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
48 #include "DllAvCodec.h"
49 #include "DllSwScale.h"
50 #include "filesystem/File.h"
51 #include "TextureCache.h"
52
53
54 bool CDVDFileInfo::GetFileDuration(const CStdString &path, int& duration)
55 {
56   std::auto_ptr<CDVDInputStream> input;
57   std::auto_ptr<CDVDDemux> demux;
58
59   input.reset(CDVDFactoryInputStream::CreateInputStream(NULL, path, ""));
60   if (!input.get())
61     return false;
62
63   if (!input->Open(path, ""))
64     return false;
65
66   demux.reset(CDVDFactoryDemuxer::CreateDemuxer(input.get()));
67   if (!demux.get())
68     return false;
69
70   duration = demux->GetStreamLength();
71   if (duration > 0)
72     return true;
73   else
74     return false;
75 }
76
77 int DegreeToOrientation(int degrees)
78 {
79   switch(degrees)
80   {
81     case 90:
82       return 5;
83     case 180:
84       return 2;
85     case 270:
86       return 7;
87     default:
88       return 0;
89   }
90 }
91
92 bool CDVDFileInfo::ExtractThumb(const CStdString &strPath, CTextureDetails &details, CStreamDetails *pStreamDetails)
93 {
94   unsigned int nTime = XbmcThreads::SystemClockMillis();
95   CDVDInputStream *pInputStream = CDVDFactoryInputStream::CreateInputStream(NULL, strPath, "");
96   if (!pInputStream)
97   {
98     CLog::Log(LOGERROR, "InputStream: Error creating stream for %s", strPath.c_str());
99     return false;
100   }
101
102   if (pInputStream->IsStreamType(DVDSTREAM_TYPE_DVD))
103   {
104     CLog::Log(LOGERROR, "InputStream: dvd streams not supported for thumb extraction, file: %s", strPath.c_str());
105     delete pInputStream;
106     return false;
107   }
108
109   if (!pInputStream->Open(strPath.c_str(), ""))
110   {
111     CLog::Log(LOGERROR, "InputStream: Error opening, %s", strPath.c_str());
112     if (pInputStream)
113       delete pInputStream;
114     return false;
115   }
116
117   CDVDDemux *pDemuxer = NULL;
118
119   try
120   {
121     pDemuxer = CDVDFactoryDemuxer::CreateDemuxer(pInputStream);
122     if(!pDemuxer)
123     {
124       delete pInputStream;
125       CLog::Log(LOGERROR, "%s - Error creating demuxer", __FUNCTION__);
126       return false;
127     }
128   }
129   catch(...)
130   {
131     CLog::Log(LOGERROR, "%s - Exception thrown when opening demuxer", __FUNCTION__);
132     if (pDemuxer)
133       delete pDemuxer;
134     delete pInputStream;
135     return false;
136   }
137
138   if (pStreamDetails)
139     DemuxerToStreamDetails(pInputStream, pDemuxer, *pStreamDetails, strPath);
140
141   int nVideoStream = -1;
142   for (int i = 0; i < pDemuxer->GetNrOfStreams(); i++)
143   {
144     CDemuxStream* pStream = pDemuxer->GetStream(i);
145     if (pStream)
146     {
147       if(pStream->type == STREAM_VIDEO)
148         nVideoStream = i;
149       else
150         pStream->SetDiscard(AVDISCARD_ALL);
151     }
152   }
153
154   bool bOk = false;
155   int packetsTried = 0;
156
157   if (nVideoStream != -1)
158   {
159     CDVDVideoCodec *pVideoCodec;
160
161     CDVDStreamInfo hint(*pDemuxer->GetStream(nVideoStream), true);
162     hint.software = true;
163
164     if (hint.codec == AV_CODEC_ID_MPEG2VIDEO || hint.codec == AV_CODEC_ID_MPEG1VIDEO)
165     {
166       // libmpeg2 is not thread safe so use ffmepg for mpeg2/mpeg1 thumb extraction
167       CDVDCodecOptions dvdOptions;
168       pVideoCodec = CDVDFactoryCodec::OpenCodec(new CDVDVideoCodecFFmpeg(), hint, dvdOptions);
169     }
170     else
171     {
172       pVideoCodec = CDVDFactoryCodec::CreateVideoCodec( hint );
173     }
174
175     if (pVideoCodec)
176     {
177       int nTotalLen = pDemuxer->GetStreamLength();
178       int nSeekTo = nTotalLen / 3;
179
180       CLog::Log(LOGDEBUG,"%s - seeking to pos %dms (total: %dms) in %s", __FUNCTION__, nSeekTo, nTotalLen, strPath.c_str());
181       if (pDemuxer->SeekTime(nSeekTo, true))
182       {
183         int iDecoderState = VC_ERROR;
184         DVDVideoPicture picture;
185
186         memset(&picture, 0, sizeof(picture));
187
188         // num streams * 80 frames, should get a valid frame, if not abort.
189         int abort_index = pDemuxer->GetNrOfStreams() * 80;
190         do
191         {
192           DemuxPacket* pPacket = pDemuxer->Read();
193           packetsTried++;
194
195           if (!pPacket)
196             break;
197
198           if (pPacket->iStreamId != nVideoStream)
199           {
200             CDVDDemuxUtils::FreeDemuxPacket(pPacket);
201             continue;
202           }
203
204           iDecoderState = pVideoCodec->Decode(pPacket->pData, pPacket->iSize, pPacket->dts, pPacket->pts);
205           CDVDDemuxUtils::FreeDemuxPacket(pPacket);
206
207           if (iDecoderState & VC_ERROR)
208             break;
209
210           if (iDecoderState & VC_PICTURE)
211           {
212             memset(&picture, 0, sizeof(DVDVideoPicture));
213             if (pVideoCodec->GetPicture(&picture))
214             {
215               if(!(picture.iFlags & DVP_FLAG_DROPPED))
216                 break;
217             }
218           }
219
220         } while (abort_index--);
221
222         if (iDecoderState & VC_PICTURE && !(picture.iFlags & DVP_FLAG_DROPPED))
223         {
224           {
225             unsigned int nWidth = g_advancedSettings.GetThumbSize();
226             double aspect = (double)picture.iDisplayWidth / (double)picture.iDisplayHeight;
227             if(hint.forced_aspect && hint.aspect != 0)
228               aspect = hint.aspect;
229             unsigned int nHeight = (unsigned int)((double)g_advancedSettings.GetThumbSize() / aspect);
230
231             DllSwScale dllSwScale;
232             dllSwScale.Load();
233
234             uint8_t *pOutBuf = new uint8_t[nWidth * nHeight * 4];
235             struct SwsContext *context = dllSwScale.sws_getContext(picture.iWidth, picture.iHeight,
236                   PIX_FMT_YUV420P, nWidth, nHeight, PIX_FMT_BGRA, SWS_FAST_BILINEAR | SwScaleCPUFlags(), NULL, NULL, NULL);
237
238             if (context)
239             {
240               uint8_t *src[] = { picture.data[0], picture.data[1], picture.data[2], 0 };
241               int     srcStride[] = { picture.iLineSize[0], picture.iLineSize[1], picture.iLineSize[2], 0 };
242               uint8_t *dst[] = { pOutBuf, 0, 0, 0 };
243               int     dstStride[] = { (int)nWidth*4, 0, 0, 0 };
244               int orientation = DegreeToOrientation(hint.orientation);
245               dllSwScale.sws_scale(context, src, srcStride, 0, picture.iHeight, dst, dstStride);
246               dllSwScale.sws_freeContext(context);
247
248               details.width = nWidth;
249               details.height = nHeight;
250               CPicture::CacheTexture(pOutBuf, nWidth, nHeight, nWidth * 4, orientation, nWidth, nHeight, CTextureCache::GetCachedPath(details.file));
251               bOk = true;
252             }
253
254             dllSwScale.Unload();
255             delete [] pOutBuf;
256           }
257         }
258         else
259         {
260           CLog::Log(LOGDEBUG,"%s - decode failed in %s after %d packets.", __FUNCTION__, strPath.c_str(), packetsTried);
261         }
262       }
263       delete pVideoCodec;
264     }
265   }
266
267   if (pDemuxer)
268     delete pDemuxer;
269
270   delete pInputStream;
271
272   if(!bOk)
273   {
274     XFILE::CFile file;
275     if(file.OpenForWrite(CTextureCache::GetCachedPath(details.file)))
276       file.Close();
277   }
278
279   unsigned int nTotalTime = XbmcThreads::SystemClockMillis() - nTime;
280   CLog::Log(LOGDEBUG,"%s - measured %u ms to extract thumb from file <%s> in %d packets. ", __FUNCTION__, nTotalTime, strPath.c_str(), packetsTried);
281   return bOk;
282 }
283
284 /**
285  * \brief Open the item pointed to by pItem and extact streamdetails
286  * \return true if the stream details have changed
287  */
288 bool CDVDFileInfo::GetFileStreamDetails(CFileItem *pItem)
289 {
290   if (!pItem)
291     return false;
292
293   CStdString strFileNameAndPath;
294   if (pItem->HasVideoInfoTag())
295     strFileNameAndPath = pItem->GetVideoInfoTag()->m_strFileNameAndPath;
296
297   if (strFileNameAndPath.empty())
298     strFileNameAndPath = pItem->GetPath();
299
300   CStdString playablePath = strFileNameAndPath;
301   if (URIUtils::IsStack(playablePath))
302     playablePath = XFILE::CStackDirectory::GetFirstStackedFile(playablePath);
303
304   CDVDInputStream *pInputStream = CDVDFactoryInputStream::CreateInputStream(NULL, playablePath, "");
305   if (!pInputStream)
306     return false;
307
308   if (pInputStream->IsStreamType(DVDSTREAM_TYPE_DVD) || !pInputStream->Open(playablePath.c_str(), ""))
309   {
310     delete pInputStream;
311     return false;
312   }
313
314   CDVDDemux *pDemuxer = CDVDFactoryDemuxer::CreateDemuxer(pInputStream);
315   if (pDemuxer)
316   {
317     bool retVal = DemuxerToStreamDetails(pInputStream, pDemuxer, pItem->GetVideoInfoTag()->m_streamDetails, strFileNameAndPath);
318     delete pDemuxer;
319     delete pInputStream;
320     return retVal;
321   }
322   else
323   {
324     delete pInputStream;
325     return false;
326   }
327 }
328
329 /* returns true if details have been added */
330 bool CDVDFileInfo::DemuxerToStreamDetails(CDVDInputStream *pInputStream, CDVDDemux *pDemux, CStreamDetails &details, const CStdString &path)
331 {
332   bool retVal = false;
333   details.Reset();
334
335   for (int iStream=0; iStream<pDemux->GetNrOfStreams(); iStream++)
336   {
337     CDemuxStream *stream = pDemux->GetStream(iStream);
338     if (stream->type == STREAM_VIDEO)
339     {
340       CStreamDetailVideo *p = new CStreamDetailVideo();
341       p->m_iWidth = ((CDemuxStreamVideo *)stream)->iWidth;
342       p->m_iHeight = ((CDemuxStreamVideo *)stream)->iHeight;
343       p->m_fAspect = ((CDemuxStreamVideo *)stream)->fAspect;
344       if (p->m_fAspect == 0.0f)
345         p->m_fAspect = (float)p->m_iWidth / p->m_iHeight;
346       pDemux->GetStreamCodecName(iStream, p->m_strCodec);
347       p->m_iDuration = pDemux->GetStreamLength();
348
349       // stack handling
350       if (URIUtils::IsStack(path))
351       {
352         CFileItemList files;
353         XFILE::CStackDirectory stack;
354         stack.GetDirectory(path, files);
355
356         // skip first path as we already know the duration
357         for (int i = 1; i < files.Size(); i++)
358         {
359            int duration = 0;
360            if (CDVDFileInfo::GetFileDuration(files[i]->GetPath(), duration))
361              p->m_iDuration = p->m_iDuration + duration;
362         }
363       }
364
365       // finally, calculate seconds
366       if (p->m_iDuration > 0)
367         p->m_iDuration = p->m_iDuration / 1000;
368
369       details.AddStream(p);
370       retVal = true;
371     }
372
373     else if (stream->type == STREAM_AUDIO)
374     {
375       CStreamDetailAudio *p = new CStreamDetailAudio();
376       p->m_iChannels = ((CDemuxStreamAudio *)stream)->iChannels;
377       p->m_strLanguage = stream->language;
378       pDemux->GetStreamCodecName(iStream, p->m_strCodec);
379       details.AddStream(p);
380       retVal = true;
381     }
382
383     else if (stream->type == STREAM_SUBTITLE)
384     {
385       CStreamDetailSubtitle *p = new CStreamDetailSubtitle();
386       p->m_strLanguage = stream->language;
387       details.AddStream(p);
388       retVal = true;
389     }
390   }  /* for iStream */
391
392   details.DetermineBestStreams();
393 #ifdef HAVE_LIBBLURAY
394   // correct bluray runtime. we need the duration from the input stream, not the demuxer.
395   if (pInputStream->IsStreamType(DVDSTREAM_TYPE_BLURAY))
396   {
397     if(((CDVDInputStreamBluray*)pInputStream)->GetTotalTime() > 0)
398     {
399       ((CStreamDetailVideo*)details.GetNthStream(CStreamDetail::VIDEO,0))->m_iDuration = ((CDVDInputStreamBluray*)pInputStream)->GetTotalTime() / 1000;
400     }
401   }
402 #endif
403   return retVal;
404 }
405