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 extern "C" {
60 #include "libavutil/imgutils.h"
61 }
62 
63 #ifndef INT64_C // Used in FFmpeg headers to define some constants
64 #define INT64_C(v) (v ## LL)
65 #endif
66 
67 // mytharchive
68 #include "thumbfinder.h"
69 
70 // the amount to seek before the required frame
71 #define PRE_SEEK_AMOUNT 50
72 
74 {
75  {"frame", -1},
76  {"1 second", 1},
77  {"5 seconds", 5},
78  {"10 seconds", 10},
79  {"30 seconds", 30},
80  {"1 minute", 60},
81  {"5 minutes", 300},
82  {"10 minutes", 600},
83  {"Cut Point", -2},
84 };
85 
86 int SeekAmountsCount = sizeof(SeekAmounts) / sizeof(SeekAmounts[0]);
87 
89  const QString &menuTheme)
90  :MythScreenType(parent, "ThumbFinder"),
91  m_inputFC(NULL), m_codecCtx(NULL),
92  m_codec(NULL),
93  m_fps(0.0), m_outputbuf(NULL),
94  m_frameWidth(0), m_frameHeight(0),
95  m_videostream(0), m_currentSeek(0),
96  m_startTime(-1), m_startPTS(-1),
97  m_currentPTS(-1), m_firstIFramePTS(-1),
98  m_frameTime(0), m_updateFrame(false),
99  m_finalDuration(0), m_offset(0),
100  m_archiveItem(archiveItem),
101  m_thumbCount(getChapterCount(menuTheme)),
102  m_thumbDir(createThumbDir()),
103  m_frameButton(NULL), m_saveButton(NULL),
104  m_cancelButton(NULL), m_frameImage(NULL),
105  m_positionImage(NULL), m_imageGrid(NULL),
106  m_seekAmountText(NULL), m_currentPosText(NULL)
107 {
108  // copy thumbList so we can abandon changes if required
109  m_thumbList.clear();
110  for (int x = 0; x < m_archiveItem->thumbList.size(); x++)
111  {
112  ThumbImage *thumb = new ThumbImage;
113  *thumb = *m_archiveItem->thumbList.at(x);
114  m_thumbList.append(thumb);
115  }
116 }
117 
119 {
120  getThumbImages();
121 }
122 
124 {
125  while (!m_thumbList.isEmpty())
126  delete m_thumbList.takeFirst();
127  m_thumbList.clear();
128 
129  closeAVCodec();
130 }
131 
133 {
134  bool foundtheme = false;
135 
136  // Load the theme for this screen
137  foundtheme = LoadWindowFromXML("mythburn-ui.xml", "thumbfinder", this);
138 
139  if (!foundtheme)
140  return false;
141 
142  bool err = false;
143  UIUtilE::Assign(this, m_frameImage, "frameimage", &err);
144  UIUtilE::Assign(this, m_positionImage, "positionimage", &err);
145  UIUtilE::Assign(this, m_imageGrid, "thumblist", &err);
146  UIUtilE::Assign(this, m_saveButton, "save_button", &err);
147  UIUtilE::Assign(this, m_cancelButton, "cancel_button", &err);
148  UIUtilE::Assign(this, m_frameButton, "frame_button", &err);
149  UIUtilE::Assign(this, m_seekAmountText, "seekamount", &err);
150  UIUtilE::Assign(this, m_currentPosText, "currentpos", &err);
151 
152  if (err)
153  {
154  LOG(VB_GENERAL, LOG_ERR, "Cannot load screen 'mythburn'");
155  return false;
156  }
157 
158  connect(m_imageGrid, SIGNAL(itemSelected(MythUIButtonListItem *)),
159  this, SLOT(gridItemChanged(MythUIButtonListItem *)));
160 
161  connect(m_saveButton, SIGNAL(Clicked()), this, SLOT(savePressed()));
162  connect(m_cancelButton, SIGNAL(Clicked()), this, SLOT(cancelPressed()));
163 
164  connect(m_frameButton, SIGNAL(Clicked()), this, SLOT(updateThumb()));
165 
167 
168  BuildFocusList();
169 
171 
172  return true;
173 }
174 
175 bool ThumbFinder::keyPressEvent(QKeyEvent *event)
176 {
177  if (GetFocusWidget()->keyPressEvent(event))
178  return true;
179 
180  bool handled = false;
181  QStringList actions;
182  handled = GetMythMainWindow()->TranslateKeyPress("Archive", event, actions);
183 
184  for (int i = 0; i < actions.size() && !handled; i++)
185  {
186  QString action = actions[i];
187  handled = true;
188 
189  if (action == "MENU")
190  {
191  NextPrevWidgetFocus(true);
192  return true;
193  }
194 
195  if (action == "ESCAPE")
196  {
197  showMenu();
198  return true;
199  }
200 
201  if (action == "0" || action == "1" || action == "2" || action == "3" ||
202  action == "4" || action == "5" || action == "6" || action == "7" ||
203  action == "8" || action == "9")
204  {
205  m_imageGrid->SetItemCurrent(action.toInt());
206  int itemNo = m_imageGrid->GetCurrentPos();
207  ThumbImage *thumb = m_thumbList.at(itemNo);
208  if (thumb)
209  seekToFrame(thumb->frame);
210  return true;
211  }
212 
213  if (GetFocusWidget() == m_frameButton)
214  {
215  if (action == "UP")
216  {
217  changeSeekAmount(true);
218  }
219  else if (action == "DOWN")
220  {
221  changeSeekAmount(false);
222  }
223  else if (action == "LEFT")
224  {
225  seekBackward();
226  }
227  else if (action == "RIGHT")
228  {
229  seekForward();
230  }
231  else if (action == "SELECT")
232  {
233  updateThumb();
234  }
235  else
236  handled = false;
237  }
238  else
239  handled = false;
240  }
241 
242  if (!handled && MythScreenType::keyPressEvent(event))
243  handled = true;
244 
245  return handled;
246 }
247 
248 int ThumbFinder::getChapterCount(const QString &menuTheme)
249 {
250  QString filename = GetShareDir() + "mytharchive/themes/" +
251  menuTheme + "/theme.xml";
252  QDomDocument doc("mydocument");
253  QFile file(filename);
254 
255  if (!file.open(QIODevice::ReadOnly))
256  {
257  LOG(VB_GENERAL, LOG_ERR, "Failed to open theme file: " + filename);
258  return 0; //??
259  }
260  if (!doc.setContent(&file))
261  {
262  file.close();
263  LOG(VB_GENERAL, LOG_ERR, "Failed to parse theme file: " + filename);
264  return 0;
265  }
266  file.close();
267 
268  QDomNodeList chapterNodeList = doc.elementsByTagName("chapter");
269 
270  return chapterNodeList.count();
271 }
272 
274 {
276 
277  if (progInfo && m_archiveItem->hasCutlist)
278  {
279  progInfo->QueryCutList(m_deleteMap);
280  delete progInfo;
281  }
282 
283  if (m_deleteMap.isEmpty())
284  {
285  LOG(VB_GENERAL, LOG_ERR, "ThumbFinder::loadCutList: Got an empty delete map");
286  return;
287  }
288 
289  // if the first mark is a end mark then add the start mark at the beginning
290  frm_dir_map_t::const_iterator it = m_deleteMap.begin();
291  if (it.value() == MARK_CUT_END)
292  m_deleteMap.insert(0, MARK_CUT_START);
293 
294 
295  // if the last mark is a start mark then add the end mark at the end
296  it = m_deleteMap.end();
297  --it;
298  if (it != m_deleteMap.end())
299  {
300  if (it.value() == MARK_CUT_START)
302  }
303 }
304 
306 {
307  // copy the thumb details to the archiveItem
308  while (!m_archiveItem->thumbList.isEmpty())
309  delete m_archiveItem->thumbList.takeFirst();
310  m_archiveItem->thumbList.clear();
311 
312  for (int x = 0; x < m_thumbList.size(); x++)
313  {
314  ThumbImage *thumb = new ThumbImage;
315  *thumb = *m_thumbList.at(x);
316  m_archiveItem->thumbList.append(thumb);
317  }
318 
319  Close();
320 }
321 
323 {
324  Close();
325 }
326 
328 {
329  int64_t pos = m_currentPTS - m_firstIFramePTS;
330  int64_t frame = pos / m_frameTime;
331 
332  if (m_currentPosText)
333  m_currentPosText->SetText(frameToTime(frame, true));
334 
335  updatePositionBar(frame);
336 }
337 
339 {
340  if (up)
341  {
342  m_currentSeek++;
344  m_currentSeek = 0;
345  }
346  else
347  {
348  m_currentSeek--;
349  if (m_currentSeek < 0)
351  }
352 
354 }
355 
357 {
358  (void) item;
359 
360  int itemNo = m_imageGrid->GetCurrentPos();
361  ThumbImage *thumb = m_thumbList.at(itemNo);
362  if (thumb)
363  seekToFrame(thumb->frame);
364 }
365 
367 {
368  QString thumbDir = getTempDirectory() + "config/thumbs";
369 
370  // make sure the thumb directory exists
371  QDir dir(thumbDir);
372  if (!dir.exists())
373  {
374  dir.mkdir(thumbDir);
375  if( chmod(qPrintable(thumbDir), 0777) )
376  LOG(VB_GENERAL, LOG_ERR, "ThumbFinder: Failed to change permissions"
377  " on thumb directory: " + ENO);
378  }
379 
380  QString path;
381  for (int x = 1; dir.exists(); x++)
382  {
383  path = QString(thumbDir + "/%1").arg(x);
384  dir.setPath(path);
385  }
386 
387  dir.mkdir(path);
388  if( chmod(qPrintable(path), 0777) )
389  LOG(VB_GENERAL, LOG_ERR, "ThumbFinder: Failed to change permissions on "
390  "thumb directory: %1" + ENO);
391 
392  return path;
393 }
394 
396 {
397  int itemNo = m_imageGrid->GetCurrentPos();
399 
400  ThumbImage *thumb = m_thumbList.at(itemNo);
401  if (!thumb)
402  return;
403 
404  // copy current frame image to the selected thumb image
405  QString imageFile = thumb->filename;
406  QFile dst(imageFile);
407  QFile src(m_frameFile);
408  copy(dst, src);
409 
410  item->SetImage(imageFile, "", true);
411 
412  // update the image grid item
413  int64_t pos = (int) ((m_currentPTS - m_startPTS) / m_frameTime);
414  thumb->frame = pos - m_offset;
415  if (itemNo != 0)
416  {
417  thumb->caption = frameToTime(thumb->frame);
418  item->SetText(thumb->caption);
419  }
420 
422 }
423 
424 QString ThumbFinder::frameToTime(int64_t frame, bool addFrame)
425 {
426  int hour, min, sec;
427  QString str;
428 
429  sec = (int) (frame / m_fps);
430  frame = frame - (int) (sec * m_fps);
431  min = sec / 60;
432  sec %= 60;
433  hour = min / 60;
434  min %= 60;
435 
436  if (addFrame)
437  str = str.sprintf("%01d:%02d:%02d.%02d", hour, min, sec, (int) frame);
438  else
439  str = str.sprintf("%02d:%02d:%02d", hour, min, sec);
440  return str;
441 }
442 
444 {
446  {
447  LOG(VB_GENERAL, LOG_ERR,
448  QString("ThumbFinder:: Failed to get file details for %1")
449  .arg(m_archiveItem->filename));
450  return false;
451  }
452 
454  return false;
455 
456  if (m_archiveItem->type == "Recording")
457  loadCutList();
458 
459  // calculate the file duration taking the cut list into account
461 
462  QString origFrameFile = m_frameFile;
463 
464  m_updateFrame = true;
465  getFrameImage();
466 
467  int chapterLen;
468  if (m_thumbCount)
469  chapterLen = m_finalDuration / m_thumbCount;
470  else
471  chapterLen = m_finalDuration;
472 
473  QString thumbList = "";
474  m_updateFrame = false;
475 
476  // add title thumb
477  m_frameFile = m_thumbDir + "/title.jpg";
478  ThumbImage *thumb = NULL;
479 
480  if (m_thumbList.size() > 0)
481  {
482  // use the thumb details in the thumbList if already available
483  thumb = m_thumbList.at(0);
484  }
485 
486  if (!thumb)
487  {
488  // no thumb available create a new one
489  thumb = new ThumbImage;
490  thumb->filename = m_frameFile;
491  thumb->frame = (int64_t) 0;
492  thumb->caption = "Title";
493  m_thumbList.append(thumb);
494  }
495  else
496  m_frameFile = thumb->filename;
497 
498  seekToFrame(thumb->frame);
499  getFrameImage();
500 
501  new MythUIButtonListItem(m_imageGrid, thumb->caption, thumb->filename);
502 
503  qApp->processEvents();
504 
505  for (int x = 1; x <= m_thumbCount; x++)
506  {
507  m_frameFile = QString(m_thumbDir + "/chapter-%1.jpg").arg(x);
508 
509  thumb = NULL;
510 
511  if (m_archiveItem->thumbList.size() > x)
512  {
513  // use the thumb details in the archiveItem if already available
514  thumb = m_archiveItem->thumbList.at(x);
515  }
516 
517  if (!thumb)
518  {
519  QString time;
520  int chapter, hour, min, sec;
521 
522  chapter = chapterLen * (x - 1);
523  hour = chapter / 3600;
524  min = (chapter % 3600) / 60;
525  sec = chapter % 60;
526  time = time.sprintf("%02d:%02d:%02d", hour, min, sec);
527 
528  int64_t frame = (int64_t) (chapter * ceil(m_fps));
529 
530  // no thumb available create a new one
531  thumb = new ThumbImage;
532  thumb->filename = m_frameFile;
533  thumb->frame = frame;
534  thumb->caption = time;
535  m_thumbList.append(thumb);
536  }
537  else
538  m_frameFile = thumb->filename;
539 
540  seekToFrame(thumb->frame);
541  qApp->processEvents();
542  getFrameImage();
543  qApp->processEvents();
544  new MythUIButtonListItem(m_imageGrid, thumb->caption, thumb->filename);
545  qApp->processEvents();
546  }
547 
548  m_frameFile = origFrameFile;
549  seekToFrame(0);
550 
551  m_updateFrame = true;
552 
554 
556 
557  return true;
558 }
559 
560 bool ThumbFinder::initAVCodec(const QString &inFile)
561 {
562  av_register_all();
563 
564  // Open recording
565  LOG(VB_JOBQUEUE, LOG_INFO, QString("ThumbFinder: Opening '%1'")
566  .arg(inFile));
567 
568  if (!m_inputFC.Open(inFile))
569  {
570  LOG(VB_GENERAL, LOG_ERR, "ThumbFinder, Couldn't open input file" + ENO);
571  return false;
572  }
573 
574  // Getting stream information
575  int ret = avformat_find_stream_info(m_inputFC, NULL);
576  if (ret < 0)
577  {
578  LOG(VB_GENERAL, LOG_ERR,
579  QString("Couldn't get stream info, error #%1").arg(ret));
580  return false;
581  }
582 
583  av_dump_format(m_inputFC, 0, qPrintable(inFile), 0);
584 
585  // find the first video stream
586  m_videostream = -1;
587 
588  for (uint i = 0; i < m_inputFC->nb_streams; i++)
589  {
590  AVStream *st = m_inputFC->streams[i];
591  if (m_inputFC->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_VIDEO)
592  {
593  m_startTime = -1;
594  if (m_inputFC->streams[i]->start_time != (int) AV_NOPTS_VALUE)
595  m_startTime = m_inputFC->streams[i]->start_time;
596  else
597  {
598  LOG(VB_GENERAL, LOG_ERR,
599  "ThumbFinder: Failed to get start time");
600  return false;
601  }
602 
603  m_videostream = i;
604  m_frameWidth = st->codecpar->width;
605  m_frameHeight = st->codecpar->height;
606  if (st->r_frame_rate.den && st->r_frame_rate.num)
607  m_fps = av_q2d(st->r_frame_rate);
608  else
609  m_fps = 1/av_q2d(st->time_base);
610  break;
611  }
612  }
613 
614  if (m_videostream == -1)
615  {
616  LOG(VB_GENERAL, LOG_ERR, "Couldn't find a video stream");
617  return false;
618  }
619 
620  // get the codec context for the video stream
622  (m_inputFC->streams[m_videostream]);
623  m_codecCtx->debug_mv = 0;
624  m_codecCtx->debug = 0;
625  m_codecCtx->workaround_bugs = 1;
626  m_codecCtx->lowres = 0;
627  m_codecCtx->idct_algo = FF_IDCT_AUTO;
628  m_codecCtx->skip_frame = AVDISCARD_DEFAULT;
629  m_codecCtx->skip_idct = AVDISCARD_DEFAULT;
630  m_codecCtx->skip_loop_filter = AVDISCARD_DEFAULT;
631  m_codecCtx->err_recognition = AV_EF_CAREFUL;
632  m_codecCtx->error_concealment = 3;
633 
634  // get decoder for video stream
635  m_codec = avcodec_find_decoder(m_codecCtx->codec_id);
636 
637  if (m_codec == NULL)
638  {
639  LOG(VB_GENERAL, LOG_ERR,
640  "ThumbFinder: Couldn't find codec for video stream");
641  return false;
642  }
643 
644  // open codec
645  if (avcodec_open2(m_codecCtx, m_codec, NULL) < 0)
646  {
647  LOG(VB_GENERAL, LOG_ERR,
648  "ThumbFinder: Couldn't open codec for video stream");
649  return false;
650  }
651 
652  // allocate temp buffer
653  int bufflen = m_frameWidth * m_frameHeight * 4;
654  m_outputbuf = new unsigned char[bufflen];
655 
656  m_frameFile = getTempDirectory() + "work/frame.jpg";
657 
659  m_frameWidth, m_frameHeight));
660  return true;
661 }
662 
664 {
665  if (m_deleteMap.isEmpty() || !m_archiveItem->useCutlist)
666  return frameNumber;
667 
668  int diff = 0;
669  frm_dir_map_t::const_iterator it = m_deleteMap.find(frameNumber);
670 
671  for (it = m_deleteMap.begin(); it != m_deleteMap.end(); ++it)
672  {
673  int start = it.key();
674 
675  ++it;
676  if (it == m_deleteMap.end())
677  {
678  LOG(VB_GENERAL, LOG_ERR, "ThumbFinder: found a start cut but no cut end");
679  break;
680  }
681 
682  int end = it.key();
683 
684  if (start <= frameNumber + diff)
685  diff += end - start;
686  }
687 
688  m_offset = diff;
689  return frameNumber + diff;
690 }
691 
692 bool ThumbFinder::seekToFrame(int frame, bool checkPos)
693 {
694  // make sure the frame is not in a cut point
695  if (checkPos)
696  frame = checkFramePosition(frame);
697 
698  // seek to a position PRE_SEEK_AMOUNT frames before the required frame
699  int64_t timestamp = m_startTime + (frame * m_frameTime) -
701  int64_t requiredPTS = m_startPTS + (frame * m_frameTime);
702 
703  if (timestamp < m_startTime)
704  timestamp = m_startTime;
705 
706  if (av_seek_frame(m_inputFC, m_videostream, timestamp, AVSEEK_FLAG_ANY) < 0)
707  {
708  LOG(VB_GENERAL, LOG_ERR, "ThumbFinder::SeekToFrame: seek failed") ;
709  return false;
710  }
711 
712  avcodec_flush_buffers(m_codecCtx);
713  getFrameImage(true, requiredPTS);
714 
715  return true;
716 }
717 
719 {
720  int inc;
721  int64_t currentFrame = (m_currentPTS - m_startPTS) / m_frameTime;
722  int64_t newFrame;
723 
724  inc = SeekAmounts[m_currentSeek].amount;
725 
726  if (inc == -1)
727  inc = 1;
728  else if (inc == -2)
729  {
730  int pos = 0;
731  frm_dir_map_t::const_iterator it;
732  for (it = m_deleteMap.begin(); it != m_deleteMap.end(); ++it)
733  {
734  if (it.key() > (uint64_t)currentFrame)
735  {
736  pos = it.key();
737  break;
738  }
739  }
740  // seek to next cutpoint
741  m_offset = 0;
742  seekToFrame(pos, false);
743  return true;
744  }
745  else
746  inc = (int) (inc * ceil(m_fps));
747 
748  newFrame = currentFrame + inc - m_offset;
749  if (newFrame == currentFrame + 1)
750  getFrameImage(false);
751  else
752  seekToFrame(newFrame);
753 
754  return true;
755 }
756 
758 {
759  int inc;
760  int64_t newFrame;
761  int64_t currentFrame = (m_currentPTS - m_startPTS) / m_frameTime;
762 
763  inc = SeekAmounts[m_currentSeek].amount;
764  if (inc == -1)
765  inc = -1;
766  else if (inc == -2)
767  {
768  // seek to previous cut point
769  frm_dir_map_t::const_iterator it;
770  int pos = 0;
771  for (it = m_deleteMap.begin(); it != m_deleteMap.end(); ++it)
772  {
773  if (it.key() >= (uint64_t)currentFrame)
774  break;
775 
776  pos = it.key();
777  }
778 
779  // seek to next cutpoint
780  m_offset = 0;
781  seekToFrame(pos, false);
782  return true;
783  }
784  else
785  inc = (int) (-inc * ceil(m_fps));
786 
787  newFrame = currentFrame + inc - m_offset;
788  seekToFrame(newFrame);
789 
790  return true;
791 }
792 
793 bool ThumbFinder::getFrameImage(bool needKeyFrame, int64_t requiredPTS)
794 {
795  AVPacket pkt;
796  AVFrame orig;
797  AVFrame retbuf;
798  memset(&orig, 0, sizeof(AVFrame));
799  memset(&retbuf, 0, sizeof(AVFrame));
800 
801  av_init_packet(&pkt);
802 
803  int frameFinished = 0;
804  int keyFrame;
805  int frameCount = 0;
806  bool gotKeyFrame = false;
807 
808  while (av_read_frame(m_inputFC, &pkt) >= 0 && !frameFinished)
809  {
810  if (pkt.stream_index == m_videostream)
811  {
812  frameCount++;
813 
814  keyFrame = pkt.flags & AV_PKT_FLAG_KEY;
815 
816  if (m_startPTS == -1 && pkt.dts != (int64_t)AV_NOPTS_VALUE)
817  {
818  m_startPTS = pkt.dts;
819  m_frameTime = pkt.duration;
820  }
821 
822  if (keyFrame)
823  gotKeyFrame = true;
824 
825  if (!gotKeyFrame && needKeyFrame)
826  {
827  av_packet_unref(&pkt);
828  continue;
829  }
830 
831  if (m_firstIFramePTS == -1)
832  m_firstIFramePTS = pkt.dts;
833 
834  av_frame_unref(m_frame);
835  frameFinished = 0;
836  int ret = avcodec_receive_frame(m_codecCtx, m_frame);
837  if (ret == 0)
838  frameFinished = 1;
839  if (ret == 0 || ret == AVERROR(EAGAIN))
840  ret = avcodec_send_packet(m_codecCtx, &pkt);
841  if (requiredPTS != -1 && pkt.dts != (int64_t)AV_NOPTS_VALUE && pkt.dts < requiredPTS)
842  frameFinished = false;
843 
844  m_currentPTS = pkt.dts;
845  }
846 
847  av_packet_unref(&pkt);
848  }
849 
850  if (frameFinished)
851  {
852  av_image_fill_arrays(retbuf.data, retbuf.linesize, m_outputbuf,
853  AV_PIX_FMT_RGB32, m_frameWidth, m_frameHeight, IMAGE_ALIGN);
854  AVFrame *tmp = m_frame;
855 
856  m_deinterlacer->DeinterlaceSingle(tmp, tmp);
857 
858  m_copy.Copy(&retbuf, AV_PIX_FMT_RGB32, tmp, m_codecCtx->pix_fmt,
860 
862  QImage::Format_RGB32);
863 
864  QByteArray ffile = m_frameFile.toLocal8Bit();
865  if (!img.save(ffile.constData(), "JPEG"))
866  {
867  LOG(VB_GENERAL, LOG_ERR, "Failed to save thumb: " + m_frameFile);
868  }
869 
870  if (m_updateFrame)
871  {
872  MythImage *mimage =
874  mimage->Assign(img);
875  m_frameImage->SetImage(mimage);
876  mimage->DecrRef();
877  }
878 
880  }
881 
882  return true;
883 }
884 
886 {
887  if (m_outputbuf)
888  delete[] m_outputbuf;
889 
890  // close the codec
892  (m_inputFC->streams[m_videostream]);
893 
894  // close the video file
895  m_inputFC.Close();
896 }
897 
899 {
900  MythScreenStack *popupStack = GetMythMainWindow()->GetStack("popup stack");
901  MythDialogBox *menuPopup = new MythDialogBox(tr("Menu"), popupStack, "actionmenu");
902 
903  if (menuPopup->Create())
904  popupStack->AddScreen(menuPopup);
905 
906  menuPopup->SetReturnEvent(this, "action");
907 
908  menuPopup->AddButton(tr("Exit, Save Thumbnails"), SLOT(savePressed()));
909  menuPopup->AddButton(tr("Exit, Don't Save Thumbnails"), SLOT(cancelPressed()));
910 }
911 
913 {
914  if (!m_positionImage)
915  return;
916 
917  QSize size = m_positionImage->GetArea().size();
918  QPixmap *pixmap = new QPixmap(size.width(), size.height());
919 
920  QPainter p(pixmap);
921  QBrush brush(Qt::green);
922 
923  p.setBrush(brush);
924  p.setPen(Qt::NoPen);
925  p.fillRect(0, 0, size.width(), size.height(), brush);
926 
927  frm_dir_map_t::const_iterator it;
928 
929  brush.setColor(Qt::red);
930  double startdelta, enddelta;
931 
932  for (it = m_deleteMap.begin(); it != m_deleteMap.end(); ++it)
933  {
934  if (it.key() != 0)
935  startdelta = (m_archiveItem->duration * m_fps) / it.key();
936  else
937  startdelta = size.width();
938 
939  ++it;
940  if (it == m_deleteMap.end())
941  {
942  LOG(VB_GENERAL, LOG_ERR, "ThumbFinder: found a start cut but no cut end");
943  break;
944  }
945 
946  if (it.key() != 0)
947  enddelta = (m_archiveItem->duration * m_fps) / it.key();
948  else
949  enddelta = size.width();
950  int start = (int) (size.width() / startdelta);
951  int end = (int) (size.width() / enddelta);
952  p.fillRect(start - 1, 0, end - start, size.height(), brush);
953  }
954 
955  if (frame == 0)
956  frame = 1;
957  brush.setColor(Qt::yellow);
958  int pos = (int) (size.width() / ((m_archiveItem->duration * m_fps) / frame));
959  p.fillRect(pos, 0, 3, size.height(), brush);
960 
962  image->Assign(*pixmap);
963  m_positionImage->SetImage(image);
964 
965  p.end();
966  delete pixmap;
967 }
968 
970 {
971  if (m_archiveItem->type == "Recording")
972  {
974  {
975  frm_dir_map_t::const_iterator it;
976 
977  int start, end, cutLen = 0;
978 
979  for (it = m_deleteMap.begin(); it != m_deleteMap.end(); ++it)
980  {
981  start = it.key();
982 
983  ++it;
984  if (it == m_deleteMap.end())
985  {
986  LOG(VB_GENERAL, LOG_ERR, "ThumbFinder: found a start cut but no cut end");
987  break;
988  }
989 
990  end = it.key();
991  cutLen += end - start;
992  }
993  return m_archiveItem->duration - (int) (cutLen / m_fps);
994  }
995  }
996 
997  return m_archiveItem->duration;
998 }
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:73
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...
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:195
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:318
struct AVFrame AVFrame
bool useCutlist
Definition: archiveutil.h:70
Basic menu dialog, message and a list of options.
int m_frameWidth
Definition: thumbfinder.h:91
void freeCodecContext(const AVStream *)
Definition: mythavutil.cpp:431
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
#define PRE_SEEK_AMOUNT
Definition: thumbfinder.cpp:71
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)
VERBOSE_PREAMBLE false
Definition: verbosedefs.h:83
int m_currentSeek
Definition: thumbfinder.h:94
int checkFramePosition(int frameNumber)
MythUIButtonList * m_imageGrid
Definition: thumbfinder.h:116
bool Create(void)
MythCodecMap * gCodecMap
This global variable contains the MythCodecMap instance for the app.
Definition: mythavutil.cpp:381
int SeekAmountsCount
Definition: thumbfinder.cpp:86
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:885
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
AVCodecContext * getCodecContext(const AVStream *, const AVCodec *pCodec=NULL, bool nullCodec=false)
Definition: mythavutil.cpp:392
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
const char * name
Definition: ParseText.cpp:339
#define ENO
This can be appended to the LOG args with "+".
Definition: mythlogging.h:98
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:88
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:169
void loadCutList(void)
#define LOG(_MASK_, _LEVEL_, _STRING_)
Definition: mythlogging.h:38
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...
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