MythTV  0.26-pre
thumbfinder.cpp
Go to the documentation of this file.
00001 /* -*- Mode: c++ -*-
00002  * vim: set expandtab tabstop=4 shiftwidth=4:
00003  *
00004  * Original Project
00005  *      MythTV      http://www.mythtv.org
00006  *
00007  * Copyright (c) 2004, 2005 John Pullan <john@pullan.org>
00008  * Copyright (c) 2009, Janne Grunau <janne-mythtv@grunau.be>
00009  *
00010  * This program is free software; you can redistribute it and/or
00011  * modify it under the terms of the GNU General Public License
00012  * as published by the Free Software Foundation; either version 2
00013  * of the License, or (at your option) any later version.
00014  *
00015  * This program is distributed in the hope that it will be useful,
00016  * but WITHOUT ANY WARRANTY; without even the implied warranty of
00017  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
00018  * GNU General Public License for more details.
00019  *
00020  * You should have received a copy of the GNU General Public License
00021  * along with this program; if not, write to the Free Software
00022  * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
00023  * Or, point your browser to http://www.gnu.org/copyleft/gpl.html
00024  *
00025  */
00026 
00027 // c
00028 #include <sys/stat.h>
00029 #include <math.h>
00030 #include <errno.h>
00031 
00032 // c++
00033 #include <cstdlib>
00034 #include <iostream>
00035 
00036 // qt
00037 #include <QApplication>
00038 #include <QDomDocument>
00039 #include <QFile>
00040 #include <QDir>
00041 #include <QPainter>
00042 
00043 // myth
00044 #include <mythcontext.h>
00045 #include <mythdbcon.h>
00046 #include <programinfo.h>
00047 #include <mythuihelper.h>
00048 #include <mythmainwindow.h>
00049 #include <mythdialogbox.h>
00050 #include <mythdirs.h>
00051 #include <mythmiscutil.h>
00052 #include <mythuitext.h>
00053 #include <mythuibutton.h>
00054 #include <mythuiimage.h>
00055 #include <mythuibuttonlist.h>
00056 #include <mythimage.h>
00057 #include <mythconfig.h>
00058 extern "C" {
00059 #include <swscale.h>
00060 }
00061 
00062 #ifndef INT64_C    // Used in FFmpeg headers to define some constants
00063 #define INT64_C(v)   (v ## LL)
00064 #endif
00065 
00066 // mytharchive
00067 #include "thumbfinder.h"
00068 
00069 // the amount to seek before the required frame
00070 #define PRE_SEEK_AMOUNT 50
00071 
00072 struct SeekAmount SeekAmounts[] =
00073 {
00074     {"frame",       -1},
00075     {"1 second",     1},
00076     {"5 seconds",    5},
00077     {"10 seconds",  10},
00078     {"30 seconds",  30},
00079     {"1 minute",    60},
00080     {"5 minutes",  300},
00081     {"10 minutes", 600},
00082     {"Cut Point",   -2},
00083 };
00084 
00085 int SeekAmountsCount = sizeof(SeekAmounts) / sizeof(SeekAmounts[0]);
00086 
00087 ThumbFinder::ThumbFinder(MythScreenStack *parent, ArchiveItem *archiveItem,
00088                          const QString &menuTheme)
00089             :MythScreenType(parent, "ThumbFinder")
00090 {
00091     m_archiveItem = archiveItem;
00092 
00093     m_thumbDir = createThumbDir();
00094 
00095     // copy thumbList so we can abandon changes if required
00096     m_thumbList.clear();
00097     for (int x = 0; x < m_archiveItem->thumbList.size(); x++)
00098     {
00099         ThumbImage *thumb = new ThumbImage;
00100         *thumb = *m_archiveItem->thumbList.at(x);
00101         m_thumbList.append(thumb);
00102     }
00103 
00104     m_thumbCount = getChapterCount(menuTheme);
00105 
00106     m_currentSeek = 0;
00107     m_offset = 0;
00108     m_startTime = -1;
00109     m_startPTS = -1;
00110     m_currentPTS = -1;
00111     m_firstIFramePTS = -1;
00112     m_image = NULL;
00113 }
00114 
00115 void ThumbFinder::Init(void)
00116 {
00117     getThumbImages();
00118 }
00119 
00120 ThumbFinder::~ThumbFinder()
00121 {
00122     while (!m_thumbList.isEmpty())
00123          delete m_thumbList.takeFirst();
00124     m_thumbList.clear();
00125 
00126     closeAVCodec();
00127 
00128     if (m_image)
00129     {
00130         m_image->DownRef();
00131         m_image = NULL;
00132     }
00133 }
00134 
00135 bool ThumbFinder::Create(void)
00136 {
00137     bool foundtheme = false;
00138 
00139     // Load the theme for this screen
00140     foundtheme = LoadWindowFromXML("mythburn-ui.xml", "thumbfinder", this);
00141 
00142     if (!foundtheme)
00143         return false;
00144 
00145     bool err = false;
00146     UIUtilE::Assign(this, m_frameImage, "frameimage", &err);
00147     UIUtilE::Assign(this, m_positionImage, "positionimage", &err);
00148     UIUtilE::Assign(this, m_imageGrid, "thumblist", &err);
00149     UIUtilE::Assign(this, m_saveButton, "save_button", &err);
00150     UIUtilE::Assign(this, m_cancelButton, "cancel_button", &err);
00151     UIUtilE::Assign(this, m_frameButton, "frame_button", &err);
00152     UIUtilE::Assign(this, m_seekAmountText, "seekamount", &err);
00153     UIUtilE::Assign(this, m_currentPosText, "currentpos", &err);
00154 
00155     if (err)
00156     {
00157         LOG(VB_GENERAL, LOG_ERR, "Cannot load screen 'mythburn'");
00158         return false;
00159     }
00160 
00161     connect(m_imageGrid, SIGNAL(itemSelected(MythUIButtonListItem *)),
00162             this, SLOT(gridItemChanged(MythUIButtonListItem *)));
00163 
00164     connect(m_saveButton, SIGNAL(Clicked()), this, SLOT(savePressed()));
00165     connect(m_cancelButton, SIGNAL(Clicked()), this, SLOT(cancelPressed()));
00166 
00167     connect(m_frameButton, SIGNAL(Clicked()), this, SLOT(updateThumb()));
00168 
00169     BuildFocusList();
00170 
00171     SetFocusWidget(m_imageGrid);
00172 
00173     return true;
00174 }
00175 
00176 bool ThumbFinder::keyPressEvent(QKeyEvent *event)
00177 {
00178     if (GetFocusWidget()->keyPressEvent(event))
00179         return true;
00180 
00181     bool handled = false;
00182     QStringList actions;
00183     handled = GetMythMainWindow()->TranslateKeyPress("Archive", event, actions);
00184 
00185     for (int i = 0; i < actions.size() && !handled; i++)
00186     {
00187         QString action = actions[i];
00188         handled = true;
00189 
00190         if (action == "MENU")
00191         {
00192             NextPrevWidgetFocus(true);
00193             return true;
00194         }
00195 
00196         if (action == "ESCAPE")
00197         {
00198             showMenu();
00199             return true;
00200         }
00201 
00202         if (action == "0" || action == "1" || action == "2" || action == "3" ||
00203             action == "4" || action == "5" || action == "6" || action == "7" ||
00204             action == "8" || action == "9")
00205         {
00206             m_imageGrid->SetItemCurrent(action.toInt());
00207             int itemNo = m_imageGrid->GetCurrentPos();
00208             ThumbImage *thumb = m_thumbList.at(itemNo);
00209             if (thumb)
00210                 seekToFrame(thumb->frame);
00211             return true;
00212         }
00213 
00214         if (GetFocusWidget() == m_frameButton)
00215         {
00216             if (action == "UP")
00217             {
00218                 changeSeekAmount(true);
00219             }
00220             else if (action == "DOWN")
00221             {
00222                 changeSeekAmount(false);
00223             }
00224             else if (action == "LEFT")
00225             {
00226                 seekBackward();
00227             }
00228             else if (action == "RIGHT")
00229             {
00230                 seekForward();
00231             }
00232             else if (action == "SELECT")
00233             {
00234                 updateThumb();
00235             }
00236             else
00237                 handled = false;
00238         }
00239         else
00240             handled = false;
00241     }
00242 
00243     if (!handled && MythScreenType::keyPressEvent(event))
00244         handled = true;
00245 
00246     return handled;
00247 }
00248 
00249 int  ThumbFinder::getChapterCount(const QString &menuTheme)
00250 {
00251     QString filename = GetShareDir() + "mytharchive/themes/" +
00252             menuTheme + "/theme.xml";
00253     QDomDocument doc("mydocument");
00254     QFile file(filename);
00255 
00256     if (!file.open(QIODevice::ReadOnly))
00257     {
00258         LOG(VB_GENERAL, LOG_ERR, "Failed to open theme file: " + filename);
00259         return 0; //??
00260     }
00261     if (!doc.setContent(&file))
00262     {
00263         file.close();
00264         LOG(VB_GENERAL, LOG_ERR, "Failed to parse theme file: " + filename);
00265         return 0;
00266     }
00267     file.close();
00268 
00269     QDomNodeList chapterNodeList = doc.elementsByTagName("chapter");
00270 
00271     return chapterNodeList.count();
00272 }
00273 
00274 void ThumbFinder::loadCutList()
00275 {
00276     ProgramInfo *progInfo = getProgramInfoForFile(m_archiveItem->filename);
00277 
00278     if (progInfo && m_archiveItem->hasCutlist)
00279     {
00280         progInfo->QueryCutList(m_deleteMap);
00281         delete progInfo;
00282     }
00283 
00284     // if the first mark is a end mark then add the start mark at the beginning
00285     frm_dir_map_t::const_iterator it = m_deleteMap.begin();
00286     if (it.value() == MARK_CUT_END)
00287         m_deleteMap.insert(0, MARK_CUT_START);
00288 
00289 
00290     // if the last mark is a start mark then add the end mark at the end
00291     it = m_deleteMap.end();
00292     --it;
00293     if (it != m_deleteMap.end())
00294     {
00295         if (it.value() == MARK_CUT_START)
00296             m_deleteMap.insert(m_archiveItem->duration * m_fps, MARK_CUT_END);
00297     }
00298 }
00299 
00300 void ThumbFinder::savePressed()
00301 {
00302     // copy the thumb details to the archiveItem
00303     while (!m_archiveItem->thumbList.isEmpty())
00304          delete m_archiveItem->thumbList.takeFirst();
00305     m_archiveItem->thumbList.clear();
00306 
00307     for (int x = 0; x < m_thumbList.size(); x++)
00308     {
00309         ThumbImage *thumb = new ThumbImage;
00310         *thumb = *m_thumbList.at(x);
00311         m_archiveItem->thumbList.append(thumb);
00312     }
00313 
00314     Close();
00315 }
00316 
00317 void ThumbFinder::cancelPressed()
00318 {
00319     Close();
00320 }
00321 
00322 void ThumbFinder::updateCurrentPos()
00323 {
00324     int64_t pos = m_currentPTS - m_firstIFramePTS;
00325     int64_t frame = pos / m_frameTime;
00326 
00327     if (m_currentPosText)
00328         m_currentPosText->SetText(frameToTime(frame, true));
00329 
00330     updatePositionBar(frame);
00331 }
00332 
00333 void ThumbFinder::changeSeekAmount(bool up)
00334 {
00335     if (up)
00336     {
00337         m_currentSeek++;
00338         if (m_currentSeek >= SeekAmountsCount)
00339             m_currentSeek = 0;
00340     }
00341     else
00342     {
00343         m_currentSeek--;
00344         if (m_currentSeek < 0)
00345             m_currentSeek = SeekAmountsCount - 1;
00346     }
00347 
00348     m_seekAmountText->SetText(SeekAmounts[m_currentSeek].name);
00349 }
00350 
00351 void ThumbFinder::gridItemChanged(MythUIButtonListItem *item)
00352 {
00353     (void) item;
00354 
00355     int itemNo = m_imageGrid->GetCurrentPos();
00356     ThumbImage *thumb = m_thumbList.at(itemNo);
00357     if (thumb)
00358           seekToFrame(thumb->frame);
00359 }
00360 
00361 QString ThumbFinder::createThumbDir(void)
00362 {
00363     QString thumbDir = getTempDirectory() + "config/thumbs";
00364 
00365     // make sure the thumb directory exists
00366     QDir dir(thumbDir);
00367     if (!dir.exists())
00368     {
00369         dir.mkdir(thumbDir);
00370         if( chmod(qPrintable(thumbDir), 0777) )
00371             LOG(VB_GENERAL, LOG_ERR, "ThumbFinder: Failed to change permissions"
00372                                      " on thumb directory: " + ENO);
00373     }
00374 
00375     QString path;
00376     for (int x = 1; dir.exists(); x++)
00377     {
00378         path = QString(thumbDir + "/%1").arg(x);
00379         dir.setPath(path);
00380     }
00381 
00382     dir.mkdir(path);
00383     if( chmod(qPrintable(path), 0777) )
00384         LOG(VB_GENERAL, LOG_ERR, "ThumbFinder: Failed to change permissions on "
00385                                  "thumb directory: %1" + ENO);
00386 
00387     return path;
00388 }
00389 
00390 void ThumbFinder::updateThumb(void)
00391 {
00392     int itemNo = m_imageGrid->GetCurrentPos();
00393     MythUIButtonListItem *item = m_imageGrid->GetItemCurrent();
00394 
00395     ThumbImage *thumb = m_thumbList.at(itemNo);
00396     if (!thumb)
00397         return;
00398 
00399     // copy current frame image to the selected thumb image
00400     QString imageFile = thumb->filename;
00401     QFile dst(imageFile);
00402     QFile src(m_frameFile);
00403     copy(dst, src);
00404 
00405     item->SetImage(imageFile, "", true);
00406 
00407     // update the image grid item
00408     int64_t pos = (int) ((m_currentPTS - m_startPTS) / m_frameTime);
00409     thumb->frame = pos - m_offset;
00410     if (itemNo != 0)
00411     {
00412         thumb->caption = frameToTime(thumb->frame);
00413         item->SetText(thumb->caption);
00414     }
00415 
00416     m_imageGrid->SetRedraw();
00417 }
00418 
00419 QString ThumbFinder::frameToTime(int64_t frame, bool addFrame)
00420 {
00421     int hour, min, sec;
00422     QString str;
00423 
00424     sec = (int) (frame / m_fps);
00425     frame = frame - (int) (sec * m_fps);
00426     min = sec / 60;
00427     sec %= 60;
00428     hour = min / 60;
00429     min %= 60;
00430 
00431     if (addFrame)
00432         str = str.sprintf("%01d:%02d:%02d.%02d", hour, min, sec, (int) frame);
00433     else
00434         str = str.sprintf("%02d:%02d:%02d", hour, min, sec);
00435     return str;
00436 }
00437 
00438 bool ThumbFinder::getThumbImages()
00439 {
00440     if (!getFileDetails(m_archiveItem))
00441     {
00442         LOG(VB_GENERAL, LOG_ERR, 
00443             QString("ThumbFinder:: Failed to get file details for %1")
00444                 .arg(m_archiveItem->filename));
00445         return false;
00446     }
00447 
00448     if (!initAVCodec(m_archiveItem->filename))
00449         return false;
00450 
00451     if (m_archiveItem->type == "Recording")
00452         loadCutList();
00453 
00454     // calculate the file duration taking the cut list into account
00455     m_finalDuration = calcFinalDuration();
00456 
00457     QString origFrameFile = m_frameFile;
00458 
00459     m_updateFrame = true;
00460     getFrameImage();
00461 
00462     int chapterLen;
00463     if (m_thumbCount)
00464         chapterLen = m_finalDuration / m_thumbCount;
00465     else
00466         chapterLen = m_finalDuration;
00467 
00468     QString thumbList = "";
00469     m_updateFrame = false;
00470 
00471     // add title thumb
00472     m_frameFile = m_thumbDir + "/title.jpg";
00473     ThumbImage *thumb = NULL;
00474 
00475     if (m_thumbList.size() > 0)
00476     {
00477         // use the thumb details in the thumbList if already available
00478         thumb = m_thumbList.at(0);
00479     }
00480 
00481     if (!thumb)
00482     {
00483         // no thumb available create a new one
00484         thumb = new ThumbImage;
00485         thumb->filename = m_frameFile;
00486         thumb->frame = (int64_t) 0;
00487         thumb->caption = "Title";
00488         m_thumbList.append(thumb);
00489     }
00490     else
00491         m_frameFile = thumb->filename;
00492 
00493     seekToFrame(thumb->frame);
00494     getFrameImage();
00495 
00496     new MythUIButtonListItem(m_imageGrid, thumb->caption, thumb->filename);
00497 
00498     qApp->processEvents();
00499 
00500     for (int x = 1; x <= m_thumbCount; x++)
00501     {
00502         m_frameFile = QString(m_thumbDir + "/chapter-%1.jpg").arg(x);
00503 
00504         thumb = NULL;
00505 
00506         if (m_archiveItem->thumbList.size() > x)
00507         {
00508             // use the thumb details in the archiveItem if already available
00509             thumb = m_archiveItem->thumbList.at(x);
00510         }
00511 
00512         if (!thumb)
00513         {
00514             QString time;
00515             int chapter, hour, min, sec;
00516 
00517             chapter = chapterLen * (x - 1);
00518             hour = chapter / 3600;
00519             min = (chapter % 3600) / 60;
00520             sec = chapter % 60;
00521             time = time.sprintf("%02d:%02d:%02d", hour, min, sec);
00522 
00523             int64_t frame = (int64_t) (chapter * ceil(m_fps));
00524 
00525             // no thumb available create a new one
00526             thumb = new ThumbImage;
00527             thumb->filename = m_frameFile;
00528             thumb->frame = frame;
00529             thumb->caption = time;
00530             m_thumbList.append(thumb);
00531         }
00532         else
00533             m_frameFile = thumb->filename;
00534 
00535         seekToFrame(thumb->frame);
00536         qApp->processEvents();
00537         getFrameImage();
00538         qApp->processEvents();
00539         new MythUIButtonListItem(m_imageGrid, thumb->caption, thumb->filename);
00540         qApp->processEvents();
00541     }
00542 
00543     m_frameFile = origFrameFile;
00544     seekToFrame(0);
00545 
00546     m_updateFrame = true;
00547 
00548     m_imageGrid->SetRedraw();
00549 
00550     SetFocusWidget(m_imageGrid);
00551 
00552     return true;
00553 }
00554 
00555 bool ThumbFinder::initAVCodec(const QString &inFile)
00556 {
00557     av_register_all();
00558 
00559     m_inputFC = NULL;
00560 
00561     // Open recording
00562     LOG(VB_JOBQUEUE, LOG_INFO, QString("ThumbFinder: Opening '%1'")
00563             .arg(inFile));
00564 
00565     QByteArray inFileBA = inFile.toLocal8Bit();
00566 
00567     int ret = avformat_open_input(&m_inputFC, inFileBA.constData(), NULL, NULL);
00568 
00569     if (ret)
00570     {
00571         LOG(VB_GENERAL, LOG_ERR, "ThumbFinder, Couldn't open input file" + ENO);
00572         return false;
00573     }
00574 
00575     // Getting stream information
00576     if ((ret = avformat_find_stream_info(m_inputFC, NULL)) < 0)
00577     {
00578         LOG(VB_GENERAL, LOG_ERR,
00579             QString("Couldn't get stream info, error #%1").arg(ret));
00580         avformat_close_input(&m_inputFC);
00581         m_inputFC = NULL;
00582         return false;
00583     }
00584     av_estimate_timings(m_inputFC, 0);
00585     av_dump_format(m_inputFC, 0, inFileBA.constData(), 0);
00586 
00587     // find the first video stream
00588     m_videostream = -1;
00589 
00590     for (uint i = 0; i < m_inputFC->nb_streams; i++)
00591     {
00592         AVStream *st = m_inputFC->streams[i];
00593         if (m_inputFC->streams[i]->codec->codec_type == AVMEDIA_TYPE_VIDEO)
00594         {
00595             m_startTime = -1;
00596             if (m_inputFC->streams[i]->start_time != (int) AV_NOPTS_VALUE)
00597                 m_startTime = m_inputFC->streams[i]->start_time;
00598             else
00599             {
00600                 LOG(VB_GENERAL, LOG_ERR,
00601                     "ThumbFinder: Failed to get start time");
00602                 return false;
00603             }
00604 
00605             m_videostream = i;
00606             m_frameWidth = st->codec->width;
00607             m_frameHeight = st->codec->height;
00608             if (st->r_frame_rate.den && st->r_frame_rate.num)
00609                 m_fps = av_q2d(st->r_frame_rate);
00610             else
00611                 m_fps = 1/av_q2d(st->time_base);
00612             break;
00613         }
00614     }
00615 
00616     if (m_videostream == -1)
00617     {
00618         LOG(VB_GENERAL, LOG_ERR, "Couldn't find a video stream");
00619         return false;
00620     }
00621 
00622     // get the codec context for the video stream
00623     m_codecCtx = m_inputFC->streams[m_videostream]->codec;
00624     m_codecCtx->debug_mv = 0;
00625     m_codecCtx->debug = 0;
00626     m_codecCtx->workaround_bugs = 1;
00627     m_codecCtx->lowres = 0;
00628     m_codecCtx->idct_algo = FF_IDCT_AUTO;
00629     m_codecCtx->skip_frame = AVDISCARD_DEFAULT;
00630     m_codecCtx->skip_idct = AVDISCARD_DEFAULT;
00631     m_codecCtx->skip_loop_filter = AVDISCARD_DEFAULT;
00632     m_codecCtx->err_recognition = AV_EF_CAREFUL;
00633     m_codecCtx->error_concealment = 3;
00634 
00635     // get decoder for video stream
00636     m_codec = avcodec_find_decoder(m_codecCtx->codec_id);
00637 
00638     if (m_codec == NULL)
00639     {
00640         LOG(VB_GENERAL, LOG_ERR,
00641             "ThumbFinder: Couldn't find codec for video stream");
00642         return false;
00643     }
00644 
00645     // open codec
00646     if (avcodec_open2(m_codecCtx, m_codec, NULL) < 0)
00647     {
00648         LOG(VB_GENERAL, LOG_ERR,
00649             "ThumbFinder: Couldn't open codec for video stream");
00650         return false;
00651     }
00652 
00653     // allocate temp buffer
00654     int bufflen = m_frameWidth * m_frameHeight * 4;
00655     m_outputbuf = new unsigned char[bufflen];
00656 
00657     m_frame = avcodec_alloc_frame();
00658 
00659     m_frameFile = getTempDirectory() + "work/frame.jpg";
00660 
00661     return true;
00662 }
00663 
00664 int ThumbFinder::checkFramePosition(int frameNumber)
00665 {
00666     if (m_deleteMap.isEmpty() || !m_archiveItem->useCutlist)
00667         return frameNumber;
00668 
00669     int diff = 0;
00670     frm_dir_map_t::const_iterator it = m_deleteMap.find(frameNumber);
00671 
00672     for (it = m_deleteMap.begin(); it != m_deleteMap.end(); ++it)
00673     {
00674         int start = it.key();
00675 
00676         ++it;
00677         if (it == m_deleteMap.end())
00678         {
00679             LOG(VB_GENERAL, LOG_ERR, "ThumbFinder: found a start cut but no cut end");
00680             break;
00681         }
00682 
00683         int end = it.key();
00684 
00685         if (start <= frameNumber + diff)
00686             diff += end - start;
00687     }
00688 
00689     m_offset = diff;
00690     return frameNumber + diff;
00691 }
00692 
00693 bool ThumbFinder::seekToFrame(int frame, bool checkPos)
00694 {
00695     // make sure the frame is not in a cut point
00696     if (checkPos)
00697         frame = checkFramePosition(frame);
00698 
00699     // seek to a position PRE_SEEK_AMOUNT frames before the required frame
00700     int64_t timestamp = m_startTime + (frame * m_frameTime) -
00701                         (PRE_SEEK_AMOUNT * m_frameTime);
00702     int64_t requiredPTS = m_startPTS + (frame * m_frameTime);
00703 
00704     if (timestamp < m_startTime)
00705         timestamp = m_startTime;
00706 
00707     if (av_seek_frame(m_inputFC, m_videostream, timestamp, AVSEEK_FLAG_ANY) < 0)
00708     {
00709         LOG(VB_GENERAL, LOG_ERR, "ThumbFinder::SeekToFrame: seek failed") ;
00710         return false;
00711     }
00712 
00713     avcodec_flush_buffers(m_codecCtx);
00714     getFrameImage(true, requiredPTS);
00715 
00716     return true;
00717 }
00718 
00719 bool ThumbFinder::seekForward()
00720 {
00721     int inc;
00722     int64_t currentFrame = (m_currentPTS - m_startPTS) / m_frameTime;
00723     int64_t newFrame;
00724 
00725     inc = SeekAmounts[m_currentSeek].amount;
00726 
00727     if (inc == -1)
00728         inc = 1;
00729     else if (inc == -2)
00730     {
00731         int pos = 0;
00732         frm_dir_map_t::const_iterator it;
00733         for (it = m_deleteMap.begin(); it != m_deleteMap.end(); ++it)
00734         {
00735             if (it.key() > (uint64_t)currentFrame)
00736             {
00737                 pos = it.key();
00738                 break;
00739             }
00740         }
00741         // seek to next cutpoint
00742         m_offset = 0;
00743         seekToFrame(pos, false);
00744         return true;
00745     }
00746     else
00747         inc = (int) (inc * ceil(m_fps));
00748 
00749     newFrame = currentFrame + inc - m_offset;
00750     if (newFrame == currentFrame + 1)
00751         getFrameImage(false);
00752     else
00753         seekToFrame(newFrame);
00754 
00755     return true;
00756 }
00757 
00758 bool ThumbFinder::seekBackward()
00759 {
00760     int inc;
00761     int64_t newFrame;
00762     int64_t currentFrame = (m_currentPTS - m_startPTS) / m_frameTime;
00763 
00764     inc = SeekAmounts[m_currentSeek].amount;
00765     if (inc == -1)
00766         inc = -1;
00767     else if (inc == -2)
00768     {
00769         // seek to previous cut point
00770         frm_dir_map_t::const_iterator it;
00771         int pos = 0;
00772         for (it = m_deleteMap.begin(); it != m_deleteMap.end(); ++it)
00773         {
00774             if (it.key() >= (uint64_t)currentFrame)
00775                 break;
00776 
00777             pos = it.key();
00778         }
00779 
00780         // seek to next cutpoint
00781         m_offset = 0;
00782         seekToFrame(pos, false);
00783         return true;
00784     }
00785     else
00786         inc = (int) (-inc * ceil(m_fps));
00787 
00788     newFrame = currentFrame + inc - m_offset;
00789     seekToFrame(newFrame);
00790 
00791     return true;
00792 }
00793 
00794 // Note: copied this function from myth_imgconvert.cpp -- dtk 2009-08-17
00795 static int myth_sws_img_convert(
00796     AVPicture *dst, PixelFormat dst_pix_fmt, AVPicture *src,
00797     PixelFormat pix_fmt, int width, int height)
00798 {
00799     static QMutex lock;
00800     QMutexLocker locker(&lock);
00801 
00802     static struct SwsContext *convert_ctx;
00803 
00804     convert_ctx = sws_getCachedContext(convert_ctx, width, height, pix_fmt,
00805                                        width, height, dst_pix_fmt,
00806                                        SWS_FAST_BILINEAR, NULL, NULL, NULL);
00807     if (!convert_ctx)
00808     {
00809         LOG(VB_GENERAL, LOG_ERR, "myth_sws_img_convert: Cannot initialize "
00810                                  "the image conversion context");
00811         return -1;
00812     }
00813 
00814     sws_scale(convert_ctx, src->data, src->linesize,
00815               0, height, dst->data, dst->linesize);
00816 
00817     return 0;
00818 }
00819 
00820 bool ThumbFinder::getFrameImage(bool needKeyFrame, int64_t requiredPTS)
00821 {
00822     AVPacket pkt;
00823     AVPicture orig;
00824     AVPicture retbuf;
00825     memset(&orig, 0, sizeof(AVPicture));
00826     memset(&retbuf, 0, sizeof(AVPicture));
00827 
00828     av_init_packet(&pkt);
00829 
00830     int frameFinished = 0;
00831     int keyFrame;
00832     int frameCount = 0;
00833     bool gotKeyFrame = false;
00834 
00835     while (av_read_frame(m_inputFC, &pkt) >= 0 && !frameFinished)
00836     {
00837         if (pkt.stream_index == m_videostream)
00838         {
00839             frameCount++;
00840 
00841             keyFrame = pkt.flags & AV_PKT_FLAG_KEY;
00842 
00843             if (m_startPTS == -1 && pkt.dts != (int64_t)AV_NOPTS_VALUE)
00844             {
00845                 m_startPTS = pkt.dts;
00846                 m_frameTime = pkt.duration;
00847             }
00848 
00849             if (keyFrame)
00850                 gotKeyFrame = true;
00851 
00852             if (!gotKeyFrame && needKeyFrame)
00853             {
00854                 av_free_packet(&pkt);
00855                 continue;
00856             }
00857 
00858             if (m_firstIFramePTS == -1)
00859                 m_firstIFramePTS = pkt.dts;
00860 
00861             avcodec_decode_video2(m_codecCtx, m_frame, &frameFinished, &pkt);
00862 
00863             if (requiredPTS != -1 && pkt.dts != (int64_t)AV_NOPTS_VALUE && pkt.dts < requiredPTS)
00864                 frameFinished = false;
00865 
00866             m_currentPTS = pkt.dts;
00867         }
00868 
00869         av_free_packet(&pkt);
00870     }
00871 
00872     if (frameFinished)
00873     {
00874         avpicture_fill(&retbuf, m_outputbuf, PIX_FMT_RGB32, m_frameWidth, m_frameHeight);
00875 
00876         avpicture_deinterlace((AVPicture*)m_frame, (AVPicture*)m_frame,
00877                                 m_codecCtx->pix_fmt, m_frameWidth, m_frameHeight);
00878 
00879         myth_sws_img_convert(
00880                     &retbuf, PIX_FMT_RGB32,
00881                         (AVPicture*) m_frame, m_codecCtx->pix_fmt, m_frameWidth, m_frameHeight);
00882 
00883         QImage img(m_outputbuf, m_frameWidth, m_frameHeight,
00884                    QImage::Format_RGB32);
00885 
00886         QByteArray ffile = m_frameFile.toLocal8Bit();
00887         if (!img.save(ffile.constData(), "JPEG"))
00888         {
00889             LOG(VB_GENERAL, LOG_ERR, "Failed to save thumb: " + m_frameFile);
00890         }
00891 
00892         if (m_updateFrame)
00893         {
00894             if (m_image)
00895             {
00896                 m_image->DownRef();
00897                 m_image = NULL;
00898             }
00899 
00900             m_image = GetMythMainWindow()->GetCurrentPainter()->GetFormatImage();
00901             m_image->Assign(img);
00902             m_image->UpRef();
00903 
00904             m_frameImage->SetImage(m_image);
00905         }
00906 
00907         updateCurrentPos();
00908     }
00909 
00910     return true;
00911 }
00912 
00913 void ThumbFinder::closeAVCodec()
00914 {
00915     if (m_outputbuf)
00916         delete[] m_outputbuf;
00917 
00918     // free the frame
00919     av_free(m_frame);
00920 
00921     // close the codec
00922     avcodec_close(m_codecCtx);
00923 
00924     // close the video file
00925     avformat_close_input(&m_inputFC);
00926 }
00927 
00928 void ThumbFinder::showMenu()
00929 {
00930     MythScreenStack *popupStack = GetMythMainWindow()->GetStack("popup stack");
00931     MythDialogBox *menuPopup = new MythDialogBox(tr("Menu"), popupStack, "actionmenu");
00932 
00933     if (menuPopup->Create())
00934         popupStack->AddScreen(menuPopup);
00935 
00936     menuPopup->SetReturnEvent(this, "action");
00937 
00938     menuPopup->AddButton(tr("Exit, Save Thumbnails"), SLOT(savePressed()));
00939     menuPopup->AddButton(tr("Exit, Don't Save Thumbnails"), SLOT(cancelPressed()));
00940 }
00941 
00942 void ThumbFinder::updatePositionBar(int64_t frame)
00943 {
00944     if (!m_positionImage)
00945         return;
00946 
00947     QSize size = m_positionImage->GetArea().size();
00948     QPixmap *pixmap = new QPixmap(size.width(), size.height());
00949 
00950     QPainter p(pixmap);
00951     QBrush brush(Qt::green);
00952 
00953     p.setBrush(brush);
00954     p.setPen(Qt::NoPen);
00955     p.fillRect(0, 0, size.width(), size.height(), brush);
00956 
00957     frm_dir_map_t::const_iterator it;
00958 
00959     brush.setColor(Qt::red);
00960     double startdelta, enddelta;
00961 
00962     for (it = m_deleteMap.begin(); it != m_deleteMap.end(); ++it)
00963     {
00964         if (it.key() != 0)
00965             startdelta = (m_archiveItem->duration * m_fps) / it.key();
00966         else
00967             startdelta = size.width();
00968 
00969         ++it;
00970         if (it == m_deleteMap.end())
00971         {
00972             LOG(VB_GENERAL, LOG_ERR, "ThumbFinder: found a start cut but no cut end");
00973             break;
00974         }
00975 
00976         if (it.key() != 0)
00977             enddelta = (m_archiveItem->duration * m_fps) / it.key();
00978         else
00979             enddelta = size.width();
00980         int start = (int) (size.width() / startdelta);
00981         int end = (int) (size.width() / enddelta);
00982         p.fillRect(start - 1, 0, end - start, size.height(), brush);
00983     }
00984 
00985     if (frame == 0)
00986         frame = 1;
00987     brush.setColor(Qt::yellow);
00988     int pos = (int) (size.width() / ((m_archiveItem->duration * m_fps) / frame));
00989     p.fillRect(pos, 0, 3, size.height(), brush);
00990 
00991     MythImage *image = GetMythMainWindow()->GetCurrentPainter()->GetFormatImage();
00992     image->Assign(*pixmap);
00993     m_positionImage->SetImage(image);
00994 
00995     p.end();
00996     delete pixmap;
00997 }
00998 
00999 int ThumbFinder::calcFinalDuration()
01000 {
01001     if (m_archiveItem->type == "Recording")
01002     {
01003         if (m_archiveItem->useCutlist)
01004         {
01005             frm_dir_map_t::const_iterator it;
01006 
01007             int start, end, cutLen = 0;
01008 
01009             for (it = m_deleteMap.begin(); it != m_deleteMap.end(); ++it)
01010             {
01011                 start = it.key();
01012 
01013                 ++it;
01014                 if (it == m_deleteMap.end())
01015                 {
01016                     LOG(VB_GENERAL, LOG_ERR, "ThumbFinder: found a start cut but no cut end");
01017                     break;
01018                 }
01019 
01020                 end = it.key();
01021                 cutLen += end - start;
01022             }
01023             return m_archiveItem->duration - (int) (cutLen / m_fps);
01024         }
01025     }
01026 
01027     return m_archiveItem->duration;
01028 }
01029 
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Properties Friends