MythTV  master
thumbfinder.cpp
Go to the documentation of this file.
1 /* -*- Mode: c++ -*-
2  * vim: set expandtab tabstop=4 shiftwidth=4:
3  *
4  * Original Project
5  * MythTV http://www.mythtv.org
6  *
7  * Copyright (c) 2004, 2005 John Pullan <john@pullan.org>
8  * Copyright (c) 2009, Janne Grunau <janne-mythtv@grunau.be>
9  *
10  * This program is free software; you can redistribute it and/or
11  * modify it under the terms of the GNU General Public License
12  * as published by the Free Software Foundation; either version 2
13  * of the License, or (at your option) any later version.
14  *
15  * This program is distributed in the hope that it will be useful,
16  * but WITHOUT ANY WARRANTY; without even the implied warranty of
17  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18  * GNU General Public License for more details.
19  *
20  * You should have received a copy of the GNU General Public License
21  * along with this program; if not, write to the Free Software
22  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
23  * Or, point your browser to http://www.gnu.org/copyleft/gpl.html
24  *
25  */
26 
27 // c
28 #include <sys/stat.h>
29 #include <math.h>
30 #include <errno.h>
31 
32 // c++
33 #include <cstdlib>
34 #include <iostream>
35 
36 // qt
37 #include <QApplication>
38 #include <QDomDocument>
39 #include <QFile>
40 #include <QDir>
41 #include <QPainter>
42 
43 // myth
44 #include <mythcontext.h>
45 #include <mythdbcon.h>
46 #include <programinfo.h>
47 #include <mythuihelper.h>
48 #include <mythmainwindow.h>
49 #include <mythdialogbox.h>
50 #include <mythdirs.h>
51 #include <mythmiscutil.h>
52 #include <mythuitext.h>
53 #include <mythuibutton.h>
54 #include <mythuiimage.h>
55 #include <mythuibuttonlist.h>
56 #include <mythimage.h>
57 #include <mythconfig.h>
58 
59 #ifndef INT64_C // Used in FFmpeg headers to define some constants
60 #define INT64_C(v) (v ## LL)
61 #endif
62 
63 // mytharchive
64 #include "thumbfinder.h"
65 
66 // the amount to seek before the required frame
67 #define PRE_SEEK_AMOUNT 50
68 
70 {
71  {"frame", -1},
72  {"1 second", 1},
73  {"5 seconds", 5},
74  {"10 seconds", 10},
75  {"30 seconds", 30},
76  {"1 minute", 60},
77  {"5 minutes", 300},
78  {"10 minutes", 600},
79  {"Cut Point", -2},
80 };
81 
82 int SeekAmountsCount = sizeof(SeekAmounts) / sizeof(SeekAmounts[0]);
83 
85  const QString &menuTheme)
86  :MythScreenType(parent, "ThumbFinder"),
87  m_inputFC(NULL), m_codecCtx(NULL),
88  m_codec(NULL),
89  m_fps(0.0), m_outputbuf(NULL),
90  m_frameWidth(0), m_frameHeight(0),
91  m_videostream(0), m_currentSeek(0),
92  m_startTime(-1), m_startPTS(-1),
93  m_currentPTS(-1), m_firstIFramePTS(-1),
94  m_frameTime(0), m_updateFrame(false),
95  m_finalDuration(0), m_offset(0),
96  m_archiveItem(archiveItem),
97  m_thumbCount(getChapterCount(menuTheme)),
98  m_thumbDir(createThumbDir()),
99  m_frameButton(NULL), m_saveButton(NULL),
100  m_cancelButton(NULL), m_frameImage(NULL),
101  m_positionImage(NULL), m_imageGrid(NULL),
102  m_seekAmountText(NULL), m_currentPosText(NULL)
103 {
104  // copy thumbList so we can abandon changes if required
105  m_thumbList.clear();
106  for (int x = 0; x < m_archiveItem->thumbList.size(); x++)
107  {
108  ThumbImage *thumb = new ThumbImage;
109  *thumb = *m_archiveItem->thumbList.at(x);
110  m_thumbList.append(thumb);
111  }
112 }
113 
115 {
116  getThumbImages();
117 }
118 
120 {
121  while (!m_thumbList.isEmpty())
122  delete m_thumbList.takeFirst();
123  m_thumbList.clear();
124 
125  closeAVCodec();
126 }
127 
129 {
130  bool foundtheme = false;
131 
132  // Load the theme for this screen
133  foundtheme = LoadWindowFromXML("mythburn-ui.xml", "thumbfinder", this);
134 
135  if (!foundtheme)
136  return false;
137 
138  bool err = false;
139  UIUtilE::Assign(this, m_frameImage, "frameimage", &err);
140  UIUtilE::Assign(this, m_positionImage, "positionimage", &err);
141  UIUtilE::Assign(this, m_imageGrid, "thumblist", &err);
142  UIUtilE::Assign(this, m_saveButton, "save_button", &err);
143  UIUtilE::Assign(this, m_cancelButton, "cancel_button", &err);
144  UIUtilE::Assign(this, m_frameButton, "frame_button", &err);
145  UIUtilE::Assign(this, m_seekAmountText, "seekamount", &err);
146  UIUtilE::Assign(this, m_currentPosText, "currentpos", &err);
147 
148  if (err)
149  {
150  LOG(VB_GENERAL, LOG_ERR, "Cannot load screen 'mythburn'");
151  return false;
152  }
153 
154  connect(m_imageGrid, SIGNAL(itemSelected(MythUIButtonListItem *)),
155  this, SLOT(gridItemChanged(MythUIButtonListItem *)));
156 
157  connect(m_saveButton, SIGNAL(Clicked()), this, SLOT(savePressed()));
158  connect(m_cancelButton, SIGNAL(Clicked()), this, SLOT(cancelPressed()));
159 
160  connect(m_frameButton, SIGNAL(Clicked()), this, SLOT(updateThumb()));
161 
163 
164  BuildFocusList();
165 
167 
168  return true;
169 }
170 
171 bool ThumbFinder::keyPressEvent(QKeyEvent *event)
172 {
173  if (GetFocusWidget()->keyPressEvent(event))
174  return true;
175 
176  bool handled = false;
177  QStringList actions;
178  handled = GetMythMainWindow()->TranslateKeyPress("Archive", event, actions);
179 
180  for (int i = 0; i < actions.size() && !handled; i++)
181  {
182  QString action = actions[i];
183  handled = true;
184 
185  if (action == "MENU")
186  {
187  NextPrevWidgetFocus(true);
188  return true;
189  }
190 
191  if (action == "ESCAPE")
192  {
193  showMenu();
194  return true;
195  }
196 
197  if (action == "0" || action == "1" || action == "2" || action == "3" ||
198  action == "4" || action == "5" || action == "6" || action == "7" ||
199  action == "8" || action == "9")
200  {
201  m_imageGrid->SetItemCurrent(action.toInt());
202  int itemNo = m_imageGrid->GetCurrentPos();
203  ThumbImage *thumb = m_thumbList.at(itemNo);
204  if (thumb)
205  seekToFrame(thumb->frame);
206  return true;
207  }
208 
209  if (GetFocusWidget() == m_frameButton)
210  {
211  if (action == "UP")
212  {
213  changeSeekAmount(true);
214  }
215  else if (action == "DOWN")
216  {
217  changeSeekAmount(false);
218  }
219  else if (action == "LEFT")
220  {
221  seekBackward();
222  }
223  else if (action == "RIGHT")
224  {
225  seekForward();
226  }
227  else if (action == "SELECT")
228  {
229  updateThumb();
230  }
231  else
232  handled = false;
233  }
234  else
235  handled = false;
236  }
237 
238  if (!handled && MythScreenType::keyPressEvent(event))
239  handled = true;
240 
241  return handled;
242 }
243 
244 int ThumbFinder::getChapterCount(const QString &menuTheme)
245 {
246  QString filename = GetShareDir() + "mytharchive/themes/" +
247  menuTheme + "/theme.xml";
248  QDomDocument doc("mydocument");
249  QFile file(filename);
250 
251  if (!file.open(QIODevice::ReadOnly))
252  {
253  LOG(VB_GENERAL, LOG_ERR, "Failed to open theme file: " + filename);
254  return 0; //??
255  }
256  if (!doc.setContent(&file))
257  {
258  file.close();
259  LOG(VB_GENERAL, LOG_ERR, "Failed to parse theme file: " + filename);
260  return 0;
261  }
262  file.close();
263 
264  QDomNodeList chapterNodeList = doc.elementsByTagName("chapter");
265 
266  return chapterNodeList.count();
267 }
268 
270 {
272 
273  if (progInfo && m_archiveItem->hasCutlist)
274  {
275  progInfo->QueryCutList(m_deleteMap);
276  delete progInfo;
277  }
278 
279  if (m_deleteMap.isEmpty())
280  {
281  LOG(VB_GENERAL, LOG_ERR, "ThumbFinder::loadCutList: Got an empty delete map");
282  return;
283  }
284 
285  // if the first mark is a end mark then add the start mark at the beginning
286  frm_dir_map_t::const_iterator it = m_deleteMap.begin();
287  if (it.value() == MARK_CUT_END)
288  m_deleteMap.insert(0, MARK_CUT_START);
289 
290 
291  // if the last mark is a start mark then add the end mark at the end
292  it = m_deleteMap.end();
293  --it;
294  if (it != m_deleteMap.end())
295  {
296  if (it.value() == MARK_CUT_START)
298  }
299 }
300 
302 {
303  // copy the thumb details to the archiveItem
304  while (!m_archiveItem->thumbList.isEmpty())
305  delete m_archiveItem->thumbList.takeFirst();
306  m_archiveItem->thumbList.clear();
307 
308  for (int x = 0; x < m_thumbList.size(); x++)
309  {
310  ThumbImage *thumb = new ThumbImage;
311  *thumb = *m_thumbList.at(x);
312  m_archiveItem->thumbList.append(thumb);
313  }
314 
315  Close();
316 }
317 
319 {
320  Close();
321 }
322 
324 {
325  int64_t pos = m_currentPTS - m_firstIFramePTS;
326  int64_t frame = pos / m_frameTime;
327 
328  if (m_currentPosText)
329  m_currentPosText->SetText(frameToTime(frame, true));
330 
331  updatePositionBar(frame);
332 }
333 
335 {
336  if (up)
337  {
338  m_currentSeek++;
340  m_currentSeek = 0;
341  }
342  else
343  {
344  m_currentSeek--;
345  if (m_currentSeek < 0)
347  }
348 
350 }
351 
353 {
354  (void) item;
355 
356  int itemNo = m_imageGrid->GetCurrentPos();
357  ThumbImage *thumb = m_thumbList.at(itemNo);
358  if (thumb)
359  seekToFrame(thumb->frame);
360 }
361 
363 {
364  QString thumbDir = getTempDirectory() + "config/thumbs";
365 
366  // make sure the thumb directory exists
367  QDir dir(thumbDir);
368  if (!dir.exists())
369  {
370  dir.mkdir(thumbDir);
371  if( chmod(qPrintable(thumbDir), 0777) )
372  LOG(VB_GENERAL, LOG_ERR, "ThumbFinder: Failed to change permissions"
373  " on thumb directory: " + ENO);
374  }
375 
376  QString path;
377  for (int x = 1; dir.exists(); x++)
378  {
379  path = QString(thumbDir + "/%1").arg(x);
380  dir.setPath(path);
381  }
382 
383  dir.mkdir(path);
384  if( chmod(qPrintable(path), 0777) )
385  LOG(VB_GENERAL, LOG_ERR, "ThumbFinder: Failed to change permissions on "
386  "thumb directory: %1" + ENO);
387 
388  return path;
389 }
390 
392 {
393  int itemNo = m_imageGrid->GetCurrentPos();
395 
396  ThumbImage *thumb = m_thumbList.at(itemNo);
397  if (!thumb)
398  return;
399 
400  // copy current frame image to the selected thumb image
401  QString imageFile = thumb->filename;
402  QFile dst(imageFile);
403  QFile src(m_frameFile);
404  copy(dst, src);
405 
406  item->SetImage(imageFile, "", true);
407 
408  // update the image grid item
409  int64_t pos = (int) ((m_currentPTS - m_startPTS) / m_frameTime);
410  thumb->frame = pos - m_offset;
411  if (itemNo != 0)
412  {
413  thumb->caption = frameToTime(thumb->frame);
414  item->SetText(thumb->caption);
415  }
416 
418 }
419 
420 QString ThumbFinder::frameToTime(int64_t frame, bool addFrame)
421 {
422  int hour, min, sec;
423  QString str;
424 
425  sec = (int) (frame / m_fps);
426  frame = frame - (int) (sec * m_fps);
427  min = sec / 60;
428  sec %= 60;
429  hour = min / 60;
430  min %= 60;
431 
432  if (addFrame)
433  str = str.sprintf("%01d:%02d:%02d.%02d", hour, min, sec, (int) frame);
434  else
435  str = str.sprintf("%02d:%02d:%02d", hour, min, sec);
436  return str;
437 }
438 
440 {
442  {
443  LOG(VB_GENERAL, LOG_ERR,
444  QString("ThumbFinder:: Failed to get file details for %1")
445  .arg(m_archiveItem->filename));
446  return false;
447  }
448 
450  return false;
451 
452  if (m_archiveItem->type == "Recording")
453  loadCutList();
454 
455  // calculate the file duration taking the cut list into account
457 
458  QString origFrameFile = m_frameFile;
459 
460  m_updateFrame = true;
461  getFrameImage();
462 
463  int chapterLen;
464  if (m_thumbCount)
465  chapterLen = m_finalDuration / m_thumbCount;
466  else
467  chapterLen = m_finalDuration;
468 
469  QString thumbList = "";
470  m_updateFrame = false;
471 
472  // add title thumb
473  m_frameFile = m_thumbDir + "/title.jpg";
474  ThumbImage *thumb = NULL;
475 
476  if (m_thumbList.size() > 0)
477  {
478  // use the thumb details in the thumbList if already available
479  thumb = m_thumbList.at(0);
480  }
481 
482  if (!thumb)
483  {
484  // no thumb available create a new one
485  thumb = new ThumbImage;
486  thumb->filename = m_frameFile;
487  thumb->frame = (int64_t) 0;
488  thumb->caption = "Title";
489  m_thumbList.append(thumb);
490  }
491  else
492  m_frameFile = thumb->filename;
493 
494  seekToFrame(thumb->frame);
495  getFrameImage();
496 
497  new MythUIButtonListItem(m_imageGrid, thumb->caption, thumb->filename);
498 
499  qApp->processEvents();
500 
501  for (int x = 1; x <= m_thumbCount; x++)
502  {
503  m_frameFile = QString(m_thumbDir + "/chapter-%1.jpg").arg(x);
504 
505  thumb = NULL;
506 
507  if (m_archiveItem->thumbList.size() > x)
508  {
509  // use the thumb details in the archiveItem if already available
510  thumb = m_archiveItem->thumbList.at(x);
511  }
512 
513  if (!thumb)
514  {
515  QString time;
516  int chapter, hour, min, sec;
517 
518  chapter = chapterLen * (x - 1);
519  hour = chapter / 3600;
520  min = (chapter % 3600) / 60;
521  sec = chapter % 60;
522  time = time.sprintf("%02d:%02d:%02d", hour, min, sec);
523 
524  int64_t frame = (int64_t) (chapter * ceil(m_fps));
525 
526  // no thumb available create a new one
527  thumb = new ThumbImage;
528  thumb->filename = m_frameFile;
529  thumb->frame = frame;
530  thumb->caption = time;
531  m_thumbList.append(thumb);
532  }
533  else
534  m_frameFile = thumb->filename;
535 
536  seekToFrame(thumb->frame);
537  qApp->processEvents();
538  getFrameImage();
539  qApp->processEvents();
540  new MythUIButtonListItem(m_imageGrid, thumb->caption, thumb->filename);
541  qApp->processEvents();
542  }
543 
544  m_frameFile = origFrameFile;
545  seekToFrame(0);
546 
547  m_updateFrame = true;
548 
550 
552 
553  return true;
554 }
555 
556 bool ThumbFinder::initAVCodec(const QString &inFile)
557 {
558  av_register_all();
559 
560  // Open recording
561  LOG(VB_JOBQUEUE, LOG_INFO, QString("ThumbFinder: Opening '%1'")
562  .arg(inFile));
563 
564  if (!m_inputFC.Open(inFile))
565  {
566  LOG(VB_GENERAL, LOG_ERR, "ThumbFinder, Couldn't open input file" + ENO);
567  return false;
568  }
569 
570  // Getting stream information
571  int ret = avformat_find_stream_info(m_inputFC, NULL);
572  if (ret < 0)
573  {
574  LOG(VB_GENERAL, LOG_ERR,
575  QString("Couldn't get stream info, error #%1").arg(ret));
576  return false;
577  }
578 
579  av_dump_format(m_inputFC, 0, qPrintable(inFile), 0);
580 
581  // find the first video stream
582  m_videostream = -1;
583 
584  for (uint i = 0; i < m_inputFC->nb_streams; i++)
585  {
586  AVStream *st = m_inputFC->streams[i];
587  if (m_inputFC->streams[i]->codec->codec_type == AVMEDIA_TYPE_VIDEO)
588  {
589  m_startTime = -1;
590  if (m_inputFC->streams[i]->start_time != (int) AV_NOPTS_VALUE)
591  m_startTime = m_inputFC->streams[i]->start_time;
592  else
593  {
594  LOG(VB_GENERAL, LOG_ERR,
595  "ThumbFinder: Failed to get start time");
596  return false;
597  }
598 
599  m_videostream = i;
600  m_frameWidth = st->codec->width;
601  m_frameHeight = st->codec->height;
602  if (st->r_frame_rate.den && st->r_frame_rate.num)
603  m_fps = av_q2d(st->r_frame_rate);
604  else
605  m_fps = 1/av_q2d(st->time_base);
606  break;
607  }
608  }
609 
610  if (m_videostream == -1)
611  {
612  LOG(VB_GENERAL, LOG_ERR, "Couldn't find a video stream");
613  return false;
614  }
615 
616  // get the codec context for the video stream
617  m_codecCtx = m_inputFC->streams[m_videostream]->codec;
618  m_codecCtx->debug_mv = 0;
619  m_codecCtx->debug = 0;
620  m_codecCtx->workaround_bugs = 1;
621  m_codecCtx->lowres = 0;
622  m_codecCtx->idct_algo = FF_IDCT_AUTO;
623  m_codecCtx->skip_frame = AVDISCARD_DEFAULT;
624  m_codecCtx->skip_idct = AVDISCARD_DEFAULT;
625  m_codecCtx->skip_loop_filter = AVDISCARD_DEFAULT;
626  m_codecCtx->err_recognition = AV_EF_CAREFUL;
627  m_codecCtx->error_concealment = 3;
628 
629  // get decoder for video stream
630  m_codec = avcodec_find_decoder(m_codecCtx->codec_id);
631 
632  if (m_codec == NULL)
633  {
634  LOG(VB_GENERAL, LOG_ERR,
635  "ThumbFinder: Couldn't find codec for video stream");
636  return false;
637  }
638 
639  // open codec
640  if (avcodec_open2(m_codecCtx, m_codec, NULL) < 0)
641  {
642  LOG(VB_GENERAL, LOG_ERR,
643  "ThumbFinder: Couldn't open codec for video stream");
644  return false;
645  }
646 
647  // allocate temp buffer
648  int bufflen = m_frameWidth * m_frameHeight * 4;
649  m_outputbuf = new unsigned char[bufflen];
650 
651  m_frameFile = getTempDirectory() + "work/frame.jpg";
652 
654  m_frameWidth, m_frameHeight));
655  return true;
656 }
657 
659 {
660  if (m_deleteMap.isEmpty() || !m_archiveItem->useCutlist)
661  return frameNumber;
662 
663  int diff = 0;
664  frm_dir_map_t::const_iterator it = m_deleteMap.find(frameNumber);
665 
666  for (it = m_deleteMap.begin(); it != m_deleteMap.end(); ++it)
667  {
668  int start = it.key();
669 
670  ++it;
671  if (it == m_deleteMap.end())
672  {
673  LOG(VB_GENERAL, LOG_ERR, "ThumbFinder: found a start cut but no cut end");
674  break;
675  }
676 
677  int end = it.key();
678 
679  if (start <= frameNumber + diff)
680  diff += end - start;
681  }
682 
683  m_offset = diff;
684  return frameNumber + diff;
685 }
686 
687 bool ThumbFinder::seekToFrame(int frame, bool checkPos)
688 {
689  // make sure the frame is not in a cut point
690  if (checkPos)
691  frame = checkFramePosition(frame);
692 
693  // seek to a position PRE_SEEK_AMOUNT frames before the required frame
694  int64_t timestamp = m_startTime + (frame * m_frameTime) -
695  (PRE_SEEK_AMOUNT * m_frameTime);
696  int64_t requiredPTS = m_startPTS + (frame * m_frameTime);
697 
698  if (timestamp < m_startTime)
699  timestamp = m_startTime;
700 
701  if (av_seek_frame(m_inputFC, m_videostream, timestamp, AVSEEK_FLAG_ANY) < 0)
702  {
703  LOG(VB_GENERAL, LOG_ERR, "ThumbFinder::SeekToFrame: seek failed") ;
704  return false;
705  }
706 
707  avcodec_flush_buffers(m_codecCtx);
708  getFrameImage(true, requiredPTS);
709 
710  return true;
711 }
712 
714 {
715  int inc;
716  int64_t currentFrame = (m_currentPTS - m_startPTS) / m_frameTime;
717  int64_t newFrame;
718 
719  inc = SeekAmounts[m_currentSeek].amount;
720 
721  if (inc == -1)
722  inc = 1;
723  else if (inc == -2)
724  {
725  int pos = 0;
726  frm_dir_map_t::const_iterator it;
727  for (it = m_deleteMap.begin(); it != m_deleteMap.end(); ++it)
728  {
729  if (it.key() > (uint64_t)currentFrame)
730  {
731  pos = it.key();
732  break;
733  }
734  }
735  // seek to next cutpoint
736  m_offset = 0;
737  seekToFrame(pos, false);
738  return true;
739  }
740  else
741  inc = (int) (inc * ceil(m_fps));
742 
743  newFrame = currentFrame + inc - m_offset;
744  if (newFrame == currentFrame + 1)
745  getFrameImage(false);
746  else
747  seekToFrame(newFrame);
748 
749  return true;
750 }
751 
753 {
754  int inc;
755  int64_t newFrame;
756  int64_t currentFrame = (m_currentPTS - m_startPTS) / m_frameTime;
757 
758  inc = SeekAmounts[m_currentSeek].amount;
759  if (inc == -1)
760  inc = -1;
761  else if (inc == -2)
762  {
763  // seek to previous cut point
764  frm_dir_map_t::const_iterator it;
765  int pos = 0;
766  for (it = m_deleteMap.begin(); it != m_deleteMap.end(); ++it)
767  {
768  if (it.key() >= (uint64_t)currentFrame)
769  break;
770 
771  pos = it.key();
772  }
773 
774  // seek to next cutpoint
775  m_offset = 0;
776  seekToFrame(pos, false);
777  return true;
778  }
779  else
780  inc = (int) (-inc * ceil(m_fps));
781 
782  newFrame = currentFrame + inc - m_offset;
783  seekToFrame(newFrame);
784 
785  return true;
786 }
787 
788 bool ThumbFinder::getFrameImage(bool needKeyFrame, int64_t requiredPTS)
789 {
790  AVPacket pkt;
791  AVPicture orig;
792  AVPicture retbuf;
793  memset(&orig, 0, sizeof(AVPicture));
794  memset(&retbuf, 0, sizeof(AVPicture));
795 
796  av_init_packet(&pkt);
797 
798  int frameFinished = 0;
799  int keyFrame;
800  int frameCount = 0;
801  bool gotKeyFrame = false;
802 
803  while (av_read_frame(m_inputFC, &pkt) >= 0 && !frameFinished)
804  {
805  if (pkt.stream_index == m_videostream)
806  {
807  frameCount++;
808 
809  keyFrame = pkt.flags & AV_PKT_FLAG_KEY;
810 
811  if (m_startPTS == -1 && pkt.dts != (int64_t)AV_NOPTS_VALUE)
812  {
813  m_startPTS = pkt.dts;
814  m_frameTime = pkt.duration;
815  }
816 
817  if (keyFrame)
818  gotKeyFrame = true;
819 
820  if (!gotKeyFrame && needKeyFrame)
821  {
822  av_packet_unref(&pkt);
823  continue;
824  }
825 
826  if (m_firstIFramePTS == -1)
827  m_firstIFramePTS = pkt.dts;
828 
829  av_frame_unref(m_frame);
830  avcodec_decode_video2(m_codecCtx, m_frame, &frameFinished, &pkt);
831 
832  if (requiredPTS != -1 && pkt.dts != (int64_t)AV_NOPTS_VALUE && pkt.dts < requiredPTS)
833  frameFinished = false;
834 
835  m_currentPTS = pkt.dts;
836  }
837 
838  av_packet_unref(&pkt);
839  }
840 
841  if (frameFinished)
842  {
843  avpicture_fill(&retbuf, m_outputbuf, AV_PIX_FMT_RGB32, m_frameWidth, m_frameHeight);
844  AVPicture *tmp = m_frame;
845 
846  m_deinterlacer->DeinterlaceSingle(tmp, tmp);
847 
848  m_copy.Copy(&retbuf, AV_PIX_FMT_RGB32, tmp, m_codecCtx->pix_fmt,
850 
852  QImage::Format_RGB32);
853 
854  QByteArray ffile = m_frameFile.toLocal8Bit();
855  if (!img.save(ffile.constData(), "JPEG"))
856  {
857  LOG(VB_GENERAL, LOG_ERR, "Failed to save thumb: " + m_frameFile);
858  }
859 
860  if (m_updateFrame)
861  {
862  MythImage *mimage =
864  mimage->Assign(img);
865  m_frameImage->SetImage(mimage);
866  mimage->DecrRef();
867  }
868 
870  }
871 
872  return true;
873 }
874 
876 {
877  if (m_outputbuf)
878  delete[] m_outputbuf;
879 
880  // close the codec
881  if (m_codecCtx)
882  avcodec_close(m_codecCtx);
883 
884  // close the video file
885  m_inputFC.Close();
886 }
887 
889 {
890  MythScreenStack *popupStack = GetMythMainWindow()->GetStack("popup stack");
891  MythDialogBox *menuPopup = new MythDialogBox(tr("Menu"), popupStack, "actionmenu");
892 
893  if (menuPopup->Create())
894  popupStack->AddScreen(menuPopup);
895 
896  menuPopup->SetReturnEvent(this, "action");
897 
898  menuPopup->AddButton(tr("Exit, Save Thumbnails"), SLOT(savePressed()));
899  menuPopup->AddButton(tr("Exit, Don't Save Thumbnails"), SLOT(cancelPressed()));
900 }
901 
903 {
904  if (!m_positionImage)
905  return;
906 
907  QSize size = m_positionImage->GetArea().size();
908  QPixmap *pixmap = new QPixmap(size.width(), size.height());
909 
910  QPainter p(pixmap);
911  QBrush brush(Qt::green);
912 
913  p.setBrush(brush);
914  p.setPen(Qt::NoPen);
915  p.fillRect(0, 0, size.width(), size.height(), brush);
916 
917  frm_dir_map_t::const_iterator it;
918 
919  brush.setColor(Qt::red);
920  double startdelta, enddelta;
921 
922  for (it = m_deleteMap.begin(); it != m_deleteMap.end(); ++it)
923  {
924  if (it.key() != 0)
925  startdelta = (m_archiveItem->duration * m_fps) / it.key();
926  else
927  startdelta = size.width();
928 
929  ++it;
930  if (it == m_deleteMap.end())
931  {
932  LOG(VB_GENERAL, LOG_ERR, "ThumbFinder: found a start cut but no cut end");
933  break;
934  }
935 
936  if (it.key() != 0)
937  enddelta = (m_archiveItem->duration * m_fps) / it.key();
938  else
939  enddelta = size.width();
940  int start = (int) (size.width() / startdelta);
941  int end = (int) (size.width() / enddelta);
942  p.fillRect(start - 1, 0, end - start, size.height(), brush);
943  }
944 
945  if (frame == 0)
946  frame = 1;
947  brush.setColor(Qt::yellow);
948  int pos = (int) (size.width() / ((m_archiveItem->duration * m_fps) / frame));
949  p.fillRect(pos, 0, 3, size.height(), brush);
950 
952  image->Assign(*pixmap);
953  m_positionImage->SetImage(image);
954 
955  p.end();
956  delete pixmap;
957 }
958 
960 {
961  if (m_archiveItem->type == "Recording")
962  {
964  {
965  frm_dir_map_t::const_iterator it;
966 
967  int start, end, cutLen = 0;
968 
969  for (it = m_deleteMap.begin(); it != m_deleteMap.end(); ++it)
970  {
971  start = it.key();
972 
973  ++it;
974  if (it == m_deleteMap.end())
975  {
976  LOG(VB_GENERAL, LOG_ERR, "ThumbFinder: found a start cut but no cut end");
977  break;
978  }
979 
980  end = it.key();
981  cutLen += end - start;
982  }
983  return m_archiveItem->duration - (int) (cutLen / m_fps);
984  }
985  }
986 
987  return m_archiveItem->duration;
988 }
static bool Assign(ContainerType *container, UIType *&item, const QString &name, bool *err=NULL)
Definition: mythuiutils.h:27
void updateCurrentPos(void)
QString filename
Definition: archiveutil.h:47
struct SeekAmount SeekAmounts[]
Definition: thumbfinder.cpp:69
MythAVCopy m_copy
Definition: thumbfinder.h:85
int m_videostream
Definition: thumbfinder.h:93
bool keyPressEvent(QKeyEvent *)
Key event handler.
void SetImage(MythImage *img)
Should not be used unless absolutely necessary since it bypasses the image caching and threaded loade...
VERBOSE_PREAMBLE false
Definition: verbosedefs.h:83
QString caption
Definition: archiveutil.h:46
bool getThumbImages(void)
QString frameToTime(int64_t frame, bool addFrame=false)
void cancelPressed(void)
int64_t m_currentPTS
Definition: thumbfinder.h:97
int Copy(VideoFrame *dst, const VideoFrame *src)
Definition: mythavutil.cpp:191
QString getTempDirectory(bool showError)
Definition: archiveutil.cpp:75
AVCodecContext * m_codecCtx
Definition: thumbfinder.h:82
QString m_frameFile
Definition: thumbfinder.h:90
bool hasCutlist
Definition: archiveutil.h:69
void SetRedraw(void)
Definition: mythuitype.cpp:313
bool useCutlist
Definition: archiveutil.h:70
Basic menu dialog, message and a list of options.
int m_frameWidth
Definition: thumbfinder.h:91
ArchiveItem * m_archiveItem
Definition: thumbfinder.h:105
bool QueryCutList(frm_dir_map_t &, bool loadAutosave=false) const
struct ThumbImage ThumbImage
virtual bool Create(void)
virtual void SetText(const QString &text)
Definition: mythuitext.cpp:166
voidpf void uLong size
Definition: ioapi.h:136
MythScreenStack * GetStack(const QString &stackname)
int64_t m_startTime
Definition: thumbfinder.h:95
const char * filename
Definition: ioapi.h:135
unsigned int uint
Definition: compat.h:136
typedef int(ZCALLBACK *close_file_func) OF((voidpf opaque
bool getFileDetails(ArchiveItem *a)
long long copy(QFile &dst, QFile &src, uint block_size)
Copies src file to dst file.
void changeSeekAmount(bool up)
frm_dir_map_t m_deleteMap
Definition: thumbfinder.h:101
virtual int DecrRef(void)
Decrements reference count and deletes on 0.
Definition: mythimage.cpp:72
MythAVFrame m_frame
Definition: thumbfinder.h:84
void BuildFocusList(void)
void SetImage(MythImage *image, const QString &name="")
Sets an image directly, should only be used in special circumstances since it bypasses the cache...
qint64 frame
Definition: archiveutil.h:48
int64_t m_firstIFramePTS
Definition: thumbfinder.h:98
QString filename
Definition: archiveutil.h:60
virtual void Close()
void AddButton(const QString &title, QVariant data=0, bool newMenu=false, bool setCurrent=false)
Holds information on recordings and videos.
Definition: programinfo.h:66
MythUIButton * m_frameButton
Definition: thumbfinder.h:111
virtual void AddScreen(MythScreenType *screen, bool allowFade=true)
const char * name
Definition: ParseText.cpp:338
int m_currentSeek
Definition: thumbfinder.h:94
int checkFramePosition(int frameNumber)
MythUIButtonList * m_imageGrid
Definition: thumbfinder.h:116
bool Create(void)
int SeekAmountsCount
Definition: thumbfinder.cpp:82
int calcFinalDuration(void)
bool initAVCodec(const QString &inFile)
virtual MythRect GetArea(void) const
If the object has a minimum area defined, return it, other wise return the default area...
Definition: mythuitype.cpp:880
virtual bool NextPrevWidgetFocus(bool up_or_down)
QString GetShareDir(void)
Definition: mythdirs.cpp:222
MythUIButton * m_saveButton
Definition: thumbfinder.h:112
bool getFrameImage(bool needKeyFrame=true, int64_t requiredPTS=-1)
RemoteAVFormatContext m_inputFC
Definition: thumbfinder.h:81
void gridItemChanged(MythUIButtonListItem *item)
bool seekToFrame(int frame, bool checkPos=true)
void SetText(const QString &text, const QString &name="", const QString &state="")
MythPainter * GetCurrentPainter()
bool m_updateFrame
Definition: thumbfinder.h:100
void showMenu(void)
QScopedPointer< MythPictureDeinterlacer > m_deinterlacer
Definition: thumbfinder.h:86
static bool LoadWindowFromXML(const QString &xmlfile, const QString &windowname, MythUIType *parent)
QString m_thumbDir
Definition: thumbfinder.h:108
MythUIType * GetFocusWidget(void) const
QString type
Definition: archiveutil.h:54
ThumbFinder(MythScreenStack *parent, ArchiveItem *archiveItem, const QString &menuTheme)
Definition: thumbfinder.cpp:84
MythMainWindow * GetMythMainWindow(void)
void Init(void)
Used after calling Load() to assign data to widgets and other UI initilisation which is prohibited in...
void savePressed(void)
MythPictureDeinterlacer simple deinterlacer based on FFmpeg&#39;s yadif filter.
Definition: mythavutil.h:142
void loadCutList(void)
MythUIImage * m_positionImage
Definition: thumbfinder.h:115
virtual bool keyPressEvent(QKeyEvent *)
Key event handler.
float m_fps
Definition: thumbfinder.h:88
void closeAVCodec()
MythUIButton * m_cancelButton
Definition: thumbfinder.h:113
void SetReturnEvent(QObject *retobject, const QString &resultid)
MythUIText * m_currentPosText
Definition: thumbfinder.h:118
void updateThumb(void)
QString createThumbDir(void)
bool SetFocusWidget(MythUIType *widget=NULL)
bool TranslateKeyPress(const QString &context, QKeyEvent *e, QStringList &actions, bool allowJumps=true) MUNUSED_RESULT
Get a list of actions for a keypress in the given context.
int getChapterCount(const QString &menuTheme)
QList< ThumbImage * > m_thumbList
Definition: thumbfinder.h:107
void SetItemCurrent(MythUIButtonListItem *item)
MythUIImage * m_frameImage
Definition: thumbfinder.h:114
int m_thumbCount
Definition: thumbfinder.h:106
ProgramInfo * getProgramInfoForFile(const QString &inFile)
unsigned char * m_outputbuf
Definition: thumbfinder.h:89
Screen in which all other widgets are contained and rendered.
void Assign(const QImage &img)
Definition: mythimage.cpp:97
int m_frameHeight
Definition: thumbfinder.h:92
int GetCurrentPos() const
AVCodec * m_codec
Definition: thumbfinder.h:83
MythImage * GetFormatImage()
Returns a blank reference counted image in the format required for the Draw functions for this painte...
struct AVPicture AVPicture
int64_t m_startPTS
Definition: thumbfinder.h:96
QList< ThumbImage * > thumbList
Definition: archiveutil.h:72
bool seekForward()
bool seekBackward()
void updatePositionBar(int64_t frame)
MythUIText * m_seekAmountText
Definition: thumbfinder.h:117
MythUIButtonListItem * GetItemCurrent() const
int m_finalDuration
Definition: thumbfinder.h:102
int m_frameTime
Definition: thumbfinder.h:99