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