|
MythTV
0.26-pre
|
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
1.7.6.1