backport 569648f2a3b8e094c33d102edd41397ca04b07be into dharma 10.1 (because git maste...
[vuplus_xbmc] / xbmc / cores / dvdplayer / DVDFileInfo.cpp
1 /*
2  *      Copyright (C) 2005-2008 Team XBMC
3  *      http://www.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, write to
17  *  the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.
18  *  http://www.gnu.org/copyleft/gpl.html
19  *
20  */
21
22 #include "FileItem.h"
23 #include "AdvancedSettings.h"
24 #include "Picture.h"
25 #include "VideoInfoTag.h"
26 #include "Util.h"
27 #include "FileSystem/StackDirectory.h"
28 #include "utils/log.h"
29 #include "utils/TimeUtils.h"
30
31 #include "DVDClock.h"
32 #include "DVDFileInfo.h"
33 #include "DVDStreamInfo.h"
34 #include "DVDInputStreams/DVDInputStream.h"
35 #include "DVDInputStreams/DVDInputStreamBluray.h"
36 #include "DVDInputStreams/DVDFactoryInputStream.h"
37 #include "DVDDemuxers/DVDDemux.h"
38 #include "DVDDemuxers/DVDDemuxUtils.h"
39 #include "DVDDemuxers/DVDFactoryDemuxer.h"
40 #include "DVDDemuxers/DVDDemuxFFmpeg.h"
41 #include "DVDCodecs/DVDCodecs.h"
42 #include "DVDCodecs/DVDFactoryCodec.h"
43 #include "DVDCodecs/Video/DVDVideoCodec.h"
44 #include "DVDCodecs/Video/DVDVideoCodecFFmpeg.h"
45
46 #include "Codecs/DllAvFormat.h"
47 #include "Codecs/DllAvCodec.h"
48 #include "Codecs/DllSwScale.h"
49 #include "FileSystem/File.h"
50
51
52 bool CDVDFileInfo::GetFileDuration(const CStdString &path, int& duration)
53 {
54   std::auto_ptr<CDVDInputStream> input;
55   std::auto_ptr<CDVDDemux> demux;
56
57   input.reset(CDVDFactoryInputStream::CreateInputStream(NULL, path, ""));
58   if (!input.get())
59     return false;
60
61   if (!input->Open(path, ""))
62     return false;
63
64   demux.reset(CDVDFactoryDemuxer::CreateDemuxer(input.get()));
65   if (!demux.get())
66     return false;
67
68   duration = demux->GetStreamLength();
69   if (duration > 0)
70     return true;
71   else
72     return false;
73 }
74
75 bool CDVDFileInfo::ExtractThumb(const CStdString &strPath, const CStdString &strTarget, CStreamDetails *pStreamDetails)
76 {
77   int nTime = CTimeUtils::GetTimeMS();
78   CDVDInputStream *pInputStream = CDVDFactoryInputStream::CreateInputStream(NULL, strPath, "");
79   if (!pInputStream)
80   {
81     CLog::Log(LOGERROR, "InputStream: Error creating stream for %s", strPath.c_str());
82     return false;
83   }
84
85   if (pInputStream->IsStreamType(DVDSTREAM_TYPE_DVD))
86   {
87     CLog::Log(LOGERROR, "InputStream: dvd streams not supported for thumb extraction, file: %s", strPath.c_str());
88     delete pInputStream;
89     return false;
90   }
91
92   if (!pInputStream->Open(strPath.c_str(), ""))
93   {
94     CLog::Log(LOGERROR, "InputStream: Error opening, %s", strPath.c_str());
95     if (pInputStream)
96       delete pInputStream;
97     return false;
98   }
99
100   CDVDDemux *pDemuxer = NULL;
101
102   try
103   {
104     pDemuxer = CDVDFactoryDemuxer::CreateDemuxer(pInputStream);
105     if(!pDemuxer)
106     {
107       delete pInputStream;
108       CLog::Log(LOGERROR, "%s - Error creating demuxer", __FUNCTION__);
109       return false;
110     }
111   }
112   catch(...)
113   {
114     CLog::Log(LOGERROR, "%s - Exception thrown when opening demuxer", __FUNCTION__);
115     if (pDemuxer)
116       delete pDemuxer;
117     delete pInputStream;
118     return false;
119   }
120
121   if (pStreamDetails)
122     DemuxerToStreamDetails(pInputStream, pDemuxer, *pStreamDetails, strPath);
123
124   CDemuxStream* pStream = NULL;
125   int nVideoStream = -1;
126   for (int i = 0; i < pDemuxer->GetNrOfStreams(); i++)
127   {
128     pStream = pDemuxer->GetStream(i);
129     if (pStream)
130     {
131       if(pStream->type == STREAM_VIDEO)
132         nVideoStream = i;
133       else
134         pStream->SetDiscard(AVDISCARD_ALL);
135     }
136   }
137
138   bool bOk = false;
139   if (nVideoStream != -1)
140   {
141     CDVDVideoCodec *pVideoCodec;
142
143     CDVDStreamInfo hint(*pDemuxer->GetStream(nVideoStream), true);
144     hint.software = true;
145
146     if (hint.codec == CODEC_ID_MPEG2VIDEO || hint.codec == CODEC_ID_MPEG1VIDEO)
147     {
148       // libmpeg2 is not thread safe so use ffmepg for mpeg2/mpeg1 thumb extraction
149       CDVDCodecOptions dvdOptions;
150       pVideoCodec = CDVDFactoryCodec::OpenCodec(new CDVDVideoCodecFFmpeg(), hint, dvdOptions);
151     }
152     else
153     {
154       pVideoCodec = CDVDFactoryCodec::CreateVideoCodec( hint );
155     }
156
157     if (pVideoCodec)
158     {
159       int nTotalLen = pDemuxer->GetStreamLength();
160       int nSeekTo = nTotalLen / 3;
161
162       CLog::Log(LOGDEBUG,"%s - seeking to pos %dms (total: %dms) in %s", __FUNCTION__, nSeekTo, nTotalLen, strPath.c_str());
163       if (pDemuxer->SeekTime(nSeekTo, true))
164       {
165         DemuxPacket* pPacket = NULL;
166         int iDecoderState = VC_ERROR;
167         DVDVideoPicture picture;
168
169         // num streams * 40 frames, should get a valid frame, if not abort.
170         int abort_index = pDemuxer->GetNrOfStreams() * 40;
171         do
172         {
173           pPacket = pDemuxer->Read();
174           if (!pPacket)
175             break;
176
177           if (pPacket->iStreamId != nVideoStream)
178           {
179             CDVDDemuxUtils::FreeDemuxPacket(pPacket);
180             continue;
181           }
182
183           iDecoderState = pVideoCodec->Decode(pPacket->pData, pPacket->iSize, pPacket->dts, pPacket->pts);
184           CDVDDemuxUtils::FreeDemuxPacket(pPacket);
185
186           if (iDecoderState & VC_ERROR)
187             break;
188
189           if (iDecoderState & VC_PICTURE)
190           {
191             memset(&picture, 0, sizeof(DVDVideoPicture));
192             if (pVideoCodec->GetPicture(&picture))
193             {
194               if(!(picture.iFlags & DVP_FLAG_DROPPED))
195                 break;
196             }
197           }
198
199         } while (abort_index--);
200
201         if (iDecoderState & VC_PICTURE && !(picture.iFlags & DVP_FLAG_DROPPED))
202         {
203           {
204             int nWidth = g_advancedSettings.m_thumbSize;
205             double aspect = (double)picture.iWidth / (double)picture.iHeight;
206             int nHeight = (int)((double)g_advancedSettings.m_thumbSize / aspect);
207
208             DllSwScale dllSwScale;
209             dllSwScale.Load();
210
211             BYTE *pOutBuf = new BYTE[nWidth * nHeight * 4];
212             struct SwsContext *context = dllSwScale.sws_getContext(picture.iWidth, picture.iHeight,
213                   PIX_FMT_YUV420P, nWidth, nHeight, PIX_FMT_BGRA, SWS_FAST_BILINEAR | SwScaleCPUFlags(), NULL, NULL, NULL);
214             uint8_t *src[] = { picture.data[0], picture.data[1], picture.data[2], 0 };
215             int     srcStride[] = { picture.iLineSize[0], picture.iLineSize[1], picture.iLineSize[2], 0 };
216             uint8_t *dst[] = { pOutBuf, 0, 0, 0 };
217             int     dstStride[] = { nWidth*4, 0, 0, 0 };
218
219             if (context)
220             {
221               dllSwScale.sws_scale(context, src, srcStride, 0, picture.iHeight, dst, dstStride);
222               dllSwScale.sws_freeContext(context);
223
224               CPicture::CreateThumbnailFromSurface(pOutBuf, nWidth, nHeight, nWidth * 4, strTarget);
225               bOk = true;
226             }
227
228             dllSwScale.Unload();
229             delete [] pOutBuf;
230           }
231         }
232         else
233         {
234           CLog::Log(LOGDEBUG,"%s - decode failed in %s", __FUNCTION__, strPath.c_str());
235         }
236       }
237       delete pVideoCodec;
238     }
239   }
240
241   if (pDemuxer)
242     delete pDemuxer;
243
244   delete pInputStream;
245
246   if(!bOk)
247   {
248     XFILE::CFile file;
249     if(file.OpenForWrite(strTarget))
250       file.Close();
251   }
252
253   int nTotalTime = CTimeUtils::GetTimeMS() - nTime;
254   CLog::Log(LOGDEBUG,"%s - measured %d ms to extract thumb from file <%s> ", __FUNCTION__, nTotalTime, strPath.c_str());
255   return bOk;
256 }
257
258
259 void CDVDFileInfo::GetFileMetaData(const CStdString &strPath, CFileItem *pItem)
260 {
261   if (!pItem)
262     return;
263
264   CDVDInputStream *pInputStream = CDVDFactoryInputStream::CreateInputStream(NULL, strPath, "");
265   if (!pInputStream)
266   {
267     CLog::Log(LOGERROR, "%s - Error creating stream for %s", __FUNCTION__, strPath.c_str());
268     return ;
269   }
270
271   if (pInputStream->IsStreamType(DVDSTREAM_TYPE_DVD) || !pInputStream->Open(strPath.c_str(), ""))
272   {
273     CLog::Log(LOGERROR, "%s - invalid stream in %s", __FUNCTION__, strPath.c_str());
274     delete pInputStream;
275     return ;
276   }
277
278   CDVDDemuxFFmpeg *pDemuxer = new CDVDDemuxFFmpeg;
279
280   try
281   {
282     if (!pDemuxer->Open(pInputStream))
283     {
284       CLog::Log(LOGERROR, "%s - Error opening demuxer", __FUNCTION__);
285       delete pDemuxer;
286       delete pInputStream;
287       return ;
288     }
289   }
290   catch(...)
291   {
292     CLog::Log(LOGERROR, "%s - Exception thrown when opening demuxer", __FUNCTION__);
293     if (pDemuxer)
294       delete pDemuxer;
295     delete pInputStream;
296     return ;
297   }
298
299   AVFormatContext *pContext = pDemuxer->m_pFormatContext;
300   if (pContext)
301   {
302     int nLenMsec = pDemuxer->GetStreamLength();
303     CStdString strDuration;
304     int nHours = nLenMsec / 1000 / 60 / 60;
305     int nMinutes = ((nLenMsec / 1000) - nHours * 3600) / 60;
306     int nSec = (nLenMsec / 1000)  - nHours * 3600 - nMinutes * 60;
307     strDuration.Format("%d", nLenMsec);
308     pItem->SetProperty("duration-msec", strDuration);
309     strDuration.Format("%02d:%02d:%02d", nHours, nMinutes, nSec);
310     pItem->SetProperty("duration-str", strDuration);
311     pItem->SetProperty("title", pContext->title);
312     pItem->SetProperty("author", pContext->author);
313     pItem->SetProperty("copyright", pContext->copyright);
314     pItem->SetProperty("comment", pContext->comment);
315     pItem->SetProperty("album", pContext->album);
316     strDuration.Format("%d", pContext->year);
317     pItem->SetProperty("year", strDuration);
318     strDuration.Format("%d", pContext->track);
319     pItem->SetProperty("track", strDuration);
320     pItem->SetProperty("genre", pContext->genre);
321   }
322
323   delete pDemuxer;
324   pInputStream->Close();
325   delete pInputStream;
326
327 }
328
329 /**
330  * \brief Open the item pointed to by pItem and extact streamdetails
331  * \return true if the stream details have changed
332  */
333 bool CDVDFileInfo::GetFileStreamDetails(CFileItem *pItem)
334 {
335   if (!pItem)
336     return false;
337
338   CStdString strFileNameAndPath;
339   if (pItem->HasVideoInfoTag())
340     strFileNameAndPath = pItem->GetVideoInfoTag()->m_strFileNameAndPath;
341   else
342     return false;
343
344   CStdString playablePath = strFileNameAndPath;
345   if (CUtil::IsStack(playablePath))
346     playablePath = XFILE::CStackDirectory::GetFirstStackedFile(playablePath);
347
348   CDVDInputStream *pInputStream = CDVDFactoryInputStream::CreateInputStream(NULL, playablePath, "");
349   if (!pInputStream)
350     return false;
351
352   if (pInputStream->IsStreamType(DVDSTREAM_TYPE_DVD) || !pInputStream->Open(playablePath.c_str(), ""))
353   {
354     delete pInputStream;
355     return false;
356   }
357
358   CDVDDemux *pDemuxer = CDVDFactoryDemuxer::CreateDemuxer(pInputStream);
359   if (pDemuxer)
360   {
361     bool retVal = DemuxerToStreamDetails(pInputStream, pDemuxer, pItem->GetVideoInfoTag()->m_streamDetails, strFileNameAndPath);
362     delete pDemuxer;
363     delete pInputStream;
364     return retVal;
365   }
366   else
367   {
368     delete pInputStream;
369     return false;
370   }
371 }
372
373 /* returns true if details have been added */
374 bool CDVDFileInfo::DemuxerToStreamDetails(CDVDInputStream *pInputStream, CDVDDemux *pDemux, CStreamDetails &details, const CStdString &path)
375 {
376   bool retVal = false;
377   details.Reset();
378
379   for (int iStream=0; iStream<pDemux->GetNrOfStreams(); iStream++)
380   {
381     CDemuxStream *stream = pDemux->GetStream(iStream);
382     if (stream->type == STREAM_VIDEO)
383     {
384       CStreamDetailVideo *p = new CStreamDetailVideo();
385       p->m_iWidth = ((CDemuxStreamVideo *)stream)->iWidth;
386       p->m_iHeight = ((CDemuxStreamVideo *)stream)->iHeight;
387       p->m_fAspect = ((CDemuxStreamVideo *)stream)->fAspect;
388       if (p->m_fAspect == 0.0f)
389         p->m_fAspect = (float)p->m_iWidth / p->m_iHeight;
390       pDemux->GetStreamCodecName(iStream, p->m_strCodec);
391       p->m_iDuration = pDemux->GetStreamLength();
392
393       // stack handling
394       if (CUtil::IsStack(path))
395       {
396         CFileItemList files;
397         XFILE::CStackDirectory stack;
398         stack.GetDirectory(path, files);
399
400         // skip first path as we already know the duration
401         for (int i = 1; i < files.Size(); i++)
402         {
403            int duration = 0;
404            if (CDVDFileInfo::GetFileDuration(files[i]->m_strPath, duration))
405              p->m_iDuration = p->m_iDuration + duration;
406         }
407       }
408
409       // finally, calculate seconds
410       if (p->m_iDuration > 0)
411         p->m_iDuration = p->m_iDuration / 1000;
412
413       details.AddStream(p);
414       retVal = true;
415     }
416
417     else if (stream->type == STREAM_AUDIO)
418     {
419       CStreamDetailAudio *p = new CStreamDetailAudio();
420       p->m_iChannels = ((CDemuxStreamAudio *)stream)->iChannels;
421       if (stream->language)
422         p->m_strLanguage = stream->language;
423       pDemux->GetStreamCodecName(iStream, p->m_strCodec);
424       details.AddStream(p);
425       retVal = true;
426     }
427
428     else if (stream->type == STREAM_SUBTITLE)
429     {
430       if (stream->language)
431       {
432         CStreamDetailSubtitle *p = new CStreamDetailSubtitle();
433         p->m_strLanguage = stream->language;
434         details.AddStream(p);
435         retVal = true;
436       }
437     }
438   }  /* for iStream */
439
440   details.DetermineBestStreams();
441
442   // correct bluray runtime. we need the duration from the input stream, not the demuxer.
443   if (pInputStream->IsStreamType(DVDSTREAM_TYPE_BLURAY))
444   {
445     if(((CDVDInputStreamBluray*)pInputStream)->GetTotalTime() > 0)
446     {
447       ((CStreamDetailVideo*)details.GetNthStream(CStreamDetail::VIDEO,0))->m_iDuration = ((CDVDInputStreamBluray*)pInputStream)->GetTotalTime() / 1000;
448     }
449   }
450
451   return retVal;
452 }
453