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