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 <algorithm>
29#include <cerrno>
30#include <cmath>
31#include <cstdlib>
32#include <iostream>
33#include <sys/stat.h>
34
35// qt
36#include <QApplication>
37#include <QDir>
38#include <QDomDocument>
39#include <QFile>
40#include <QPainter>
41
42// myth
43#include <mythconfig.h>
48#include <libmythbase/mythmiscutil.h> // for MythFile::copy
51#include <libmythui/mythimage.h>
58
59extern "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
71static constexpr int8_t PRE_SEEK_AMOUNT { 50 };
72
73static const std::array<const SeekAmount,9> kSeekAmounts
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
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 (const auto *item : std::as_const(m_archiveItem->thumbList))
96 {
97 auto *thumb = new ThumbImage;
98 *thumb = *item;
99 m_thumbList.append(thumb);
100 }
101}
102
104{
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
142
145
147
149
151
153
154 return true;
155}
156
157bool 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 const QString& action = actions[i];
168 handled = true;
169
170 if (action == "MENU")
171 {
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
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 {
218 handled = false;
219 }
220 }
221 else
222 {
223 handled = false;
224 }
225 }
226
227 if (!handled && MythScreenType::keyPressEvent(event))
228 handled = true;
229
230 return handled;
231}
232
233int ThumbFinder::getChapterCount(const QString &menuTheme)
234{
235 QString filename = GetShareDir() + "mytharchive/themes/" +
236 menuTheme + "/theme.xml";
237 QDomDocument doc("mydocument");
238 QFile file(filename);
239
240 if (!file.open(QIODevice::ReadOnly))
241 {
242 LOG(VB_GENERAL, LOG_ERR, "Failed to open theme file: " + filename);
243 return 0; //??
244 }
245 if (!doc.setContent(&file))
246 {
247 file.close();
248 LOG(VB_GENERAL, LOG_ERR, "Failed to parse theme file: " + filename);
249 return 0;
250 }
251 file.close();
252
253 QDomNodeList chapterNodeList = doc.elementsByTagName("chapter");
254
255 return chapterNodeList.count();
256}
257
259{
261
262 if (progInfo && m_archiveItem->hasCutlist)
263 progInfo->QueryCutList(m_deleteMap);
264 delete progInfo;
265
266 if (m_deleteMap.isEmpty())
267 {
268 LOG(VB_GENERAL, LOG_ERR, "ThumbFinder::loadCutList: Got an empty delete map");
269 return;
270 }
271
272 // if the first mark is a end mark then add the start mark at the beginning
273 frm_dir_map_t::const_iterator it = m_deleteMap.constBegin();
274 if (it.value() == MARK_CUT_END)
275 m_deleteMap.insert(0, MARK_CUT_START);
276
277
278 // if the last mark is a start mark then add the end mark at the end
279 it = m_deleteMap.constEnd();
280 --it;
281 if (it != m_deleteMap.constEnd())
282 {
283 if (it.value() == MARK_CUT_START)
285 }
286}
287
289{
290 // copy the thumb details to the archiveItem
291 while (!m_archiveItem->thumbList.isEmpty())
292 delete m_archiveItem->thumbList.takeFirst();
293 m_archiveItem->thumbList.clear();
294
295 for (const auto *item : std::as_const(m_thumbList))
296 {
297 auto *thumb = new ThumbImage;
298 *thumb = *item;
299 m_archiveItem->thumbList.append(thumb);
300 }
301
302 Close();
303}
304
306{
307 Close();
308}
309
311{
312 int64_t pos = m_currentPTS - m_firstIFramePTS;
313 int64_t frame = pos / m_frameTime;
314
316 m_currentPosText->SetText(frameToTime(frame, true));
317
318 updatePositionBar(frame);
319}
320
322{
323 if (up)
324 {
326 if (m_currentSeek >= kSeekAmounts.size())
327 m_currentSeek = 0;
328 }
329 else
330 {
331 if (m_currentSeek == 0)
334 }
335
337}
338
340{
341 int itemNo = m_imageGrid->GetCurrentPos();
342 ThumbImage *thumb = m_thumbList.at(itemNo);
343 if (thumb)
344 seekToFrame(thumb->frame);
345}
346
348{
349 QString thumbDir = getTempDirectory() + "config/thumbs";
350
351 // make sure the thumb directory exists
352 QDir dir(thumbDir);
353 if (!dir.exists())
354 {
355 dir.mkdir(thumbDir);
356 if( chmod(qPrintable(thumbDir), 0777) != 0 )
357 LOG(VB_GENERAL, LOG_ERR, "ThumbFinder: Failed to change permissions"
358 " on thumb directory: " + ENO);
359 }
360
361 QString path;
362 for (int x = 1; dir.exists(); x++)
363 {
364 path = thumbDir + QString("/%1").arg(x);
365 dir.setPath(path);
366 }
367
368 dir.mkdir(path);
369 if( chmod(qPrintable(path), 0777) != 0 )
370 LOG(VB_GENERAL, LOG_ERR, "ThumbFinder: Failed to change permissions on "
371 "thumb directory: %1" + ENO);
372
373 return path;
374}
375
377{
378 int itemNo = m_imageGrid->GetCurrentPos();
380
381 ThumbImage *thumb = m_thumbList.at(itemNo);
382 if (!thumb)
383 return;
384
385 // copy current frame image to the selected thumb image
386 QString imageFile = thumb->filename;
387 QFile dst(imageFile);
388 QFile src(m_frameFile);
389 MythFile::copy(dst, src);
390
391 item->SetImage(imageFile, "", true);
392
393 // update the image grid item
394 int64_t pos = (m_currentPTS - m_startPTS) / m_frameTime;
395 thumb->frame = pos - m_offset;
396 if (itemNo != 0)
397 {
398 thumb->caption = frameToTime(thumb->frame);
399 item->SetText(thumb->caption);
400 }
401
403}
404
405QString ThumbFinder::frameToTime(int64_t frame, bool addFrame) const
406{
407 int sec = (int) (frame / m_fps);
408 frame = frame - (int) (sec * m_fps);
409
410 QString str = MythDate::formatTime(std::chrono::seconds(sec), "HH:mm:ss");
411 if (addFrame)
412 str += QString(".%1").arg(frame,10,2,QChar('0'));
413 return str;
414}
415
417{
419 {
420 LOG(VB_GENERAL, LOG_ERR,
421 QString("ThumbFinder:: Failed to get file details for %1")
422 .arg(m_archiveItem->filename));
423 return false;
424 }
425
427 return false;
428
429 if (m_archiveItem->type == "Recording")
430 loadCutList();
431
432 // calculate the file duration taking the cut list into account
434
435 QString origFrameFile = m_frameFile;
436
437 m_updateFrame = true;
439
440 int chapterLen = 0;
441 if (m_thumbCount)
442 chapterLen = m_finalDuration / m_thumbCount;
443 else
444 chapterLen = m_finalDuration;
445
446 m_updateFrame = false;
447
448 // add title thumb
449 m_frameFile = m_thumbDir + "/title.jpg";
450 ThumbImage *thumb = nullptr;
451
452 if (!m_thumbList.empty())
453 {
454 // use the thumb details in the thumbList if already available
455 thumb = m_thumbList.at(0);
456 }
457
458 if (!thumb)
459 {
460 // no thumb available create a new one
461 thumb = new ThumbImage;
462 thumb->filename = m_frameFile;
463 thumb->frame = (int64_t) 0;
464 thumb->caption = "Title";
465 m_thumbList.append(thumb);
466 }
467 else
468 {
469 m_frameFile = thumb->filename;
470 }
471
472 seekToFrame(thumb->frame);
474
476
477 QCoreApplication::processEvents();
478
479 for (int x = 1; x <= m_thumbCount; x++)
480 {
481 m_frameFile = m_thumbDir + QString("/chapter-%1.jpg").arg(x);
482
483 thumb = nullptr;
484
485 if (m_archiveItem->thumbList.size() > x)
486 {
487 // use the thumb details in the archiveItem if already available
488 thumb = m_archiveItem->thumbList.at(x);
489 }
490
491 if (!thumb)
492 {
493 int chapter = chapterLen * (x - 1);
494 int hour = chapter / 3600;
495 int min = (chapter % 3600) / 60;
496 int sec = chapter % 60;
497 QString time = QString::asprintf("%02d:%02d:%02d", hour, min, sec);
498
499 auto frame = (int64_t) (chapter * ceil(m_fps));
500
501 // no thumb available create a new one
502 thumb = new ThumbImage;
503 thumb->filename = m_frameFile;
504 thumb->frame = frame;
505 thumb->caption = time;
506 m_thumbList.append(thumb);
507 }
508 else
509 {
510 m_frameFile = thumb->filename;
511 }
512
513 seekToFrame(thumb->frame);
514 QCoreApplication::processEvents();
516 QCoreApplication::processEvents();
518 QCoreApplication::processEvents();
519 }
520
521 m_frameFile = origFrameFile;
522 seekToFrame(0);
523
524 m_updateFrame = true;
525
527
529
530 return true;
531}
532
533bool ThumbFinder::initAVCodec(const QString &inFile)
534{
535 // Open recording
536 LOG(VB_JOBQUEUE, LOG_INFO, QString("ThumbFinder: Opening '%1'")
537 .arg(inFile));
538
539 if (!m_inputFC.Open(inFile))
540 {
541 LOG(VB_GENERAL, LOG_ERR, "ThumbFinder, Couldn't open input file" + ENO);
542 return false;
543 }
544
545 // Getting stream information
546 int ret = avformat_find_stream_info(m_inputFC, nullptr);
547 if (ret < 0)
548 {
549 LOG(VB_GENERAL, LOG_ERR,
550 QString("Couldn't get stream info, error #%1").arg(ret));
551 return false;
552 }
553
554 av_dump_format(m_inputFC, 0, qPrintable(inFile), 0);
555
556 // find the first video stream
557 m_videostream = -1;
558
559 for (uint i = 0; i < m_inputFC->nb_streams; i++)
560 {
561 AVStream *st = m_inputFC->streams[i];
562 if (m_inputFC->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_VIDEO)
563 {
564 m_startTime = -1;
565 if (m_inputFC->streams[i]->start_time != (int) AV_NOPTS_VALUE)
566 m_startTime = m_inputFC->streams[i]->start_time;
567 else
568 {
569 LOG(VB_GENERAL, LOG_ERR,
570 "ThumbFinder: Failed to get start time");
571 return false;
572 }
573
574 m_videostream = i;
575 m_frameWidth = st->codecpar->width;
576 m_frameHeight = st->codecpar->height;
577 if (st->r_frame_rate.den && st->r_frame_rate.num)
578 m_fps = av_q2d(st->r_frame_rate);
579 else
580 m_fps = 1/av_q2d(st->time_base);
581 break;
582 }
583 }
584
585 if (m_videostream == -1)
586 {
587 LOG(VB_GENERAL, LOG_ERR, "Couldn't find a video stream");
588 return false;
589 }
590
591 // get the codec context for the video stream
593 m_codecCtx->debug = 0;
594 m_codecCtx->workaround_bugs = 1;
595 m_codecCtx->lowres = 0;
596 m_codecCtx->idct_algo = FF_IDCT_AUTO;
597 m_codecCtx->skip_frame = AVDISCARD_DEFAULT;
598 m_codecCtx->skip_idct = AVDISCARD_DEFAULT;
599 m_codecCtx->skip_loop_filter = AVDISCARD_DEFAULT;
600 m_codecCtx->err_recognition = AV_EF_CAREFUL;
601 m_codecCtx->error_concealment = 3;
602
603 // get decoder for video stream
604 m_codec = avcodec_find_decoder(m_codecCtx->codec_id);
605
606 if (m_codec == nullptr)
607 {
608 LOG(VB_GENERAL, LOG_ERR,
609 "ThumbFinder: Couldn't find codec for video stream");
610 return false;
611 }
612
613 // open codec
614 if (avcodec_open2(m_codecCtx, m_codec, nullptr) < 0)
615 {
616 LOG(VB_GENERAL, LOG_ERR,
617 "ThumbFinder: Couldn't open codec for video stream");
618 return false;
619 }
620
621 // allocate temp buffer
622 int bufflen = m_frameWidth * m_frameHeight * 4;
623 m_outputbuf = new unsigned char[bufflen];
624 m_frameFile = getTempDirectory() + "work/frame.jpg";
625 return true;
626}
627
629{
630 if (m_deleteMap.isEmpty() || !m_archiveItem->useCutlist)
631 return frameNumber;
632
633 int diff = 0;
634 frm_dir_map_t::const_iterator it = m_deleteMap.constFind(frameNumber);
635
636 for (it = m_deleteMap.constBegin(); it != m_deleteMap.constEnd(); ++it)
637 {
638 int start = it.key();
639
640 ++it;
641 if (it == m_deleteMap.constEnd())
642 {
643 LOG(VB_GENERAL, LOG_ERR, "ThumbFinder: found a start cut but no cut end");
644 break;
645 }
646
647 int end = it.key();
648
649 if (start <= frameNumber + diff)
650 diff += end - start;
651 }
652
653 m_offset = diff;
654 return frameNumber + diff;
655}
656
657bool ThumbFinder::seekToFrame(int frame, bool checkPos)
658{
659 // make sure the frame is not in a cut point
660 if (checkPos)
661 frame = checkFramePosition(frame);
662
663 // seek to a position PRE_SEEK_AMOUNT frames before the required frame
664 int64_t timestamp = m_startTime + (frame * m_frameTime) -
666 int64_t requiredPTS = m_startPTS + (frame * m_frameTime);
667
668 timestamp = std::max(timestamp, m_startTime);
669
670 if (av_seek_frame(m_inputFC, m_videostream, timestamp, AVSEEK_FLAG_ANY) < 0)
671 {
672 LOG(VB_GENERAL, LOG_ERR, "ThumbFinder::SeekToFrame: seek failed") ;
673 return false;
674 }
675
676 avcodec_flush_buffers(m_codecCtx);
677 getFrameImage(true, requiredPTS);
678
679 return true;
680}
681
683{
684 int64_t currentFrame = (m_currentPTS - m_startPTS) / m_frameTime;
685
686 int inc = kSeekAmounts[m_currentSeek].amount;
687
688 if (inc == -1)
689 inc = 1;
690 else if (inc == -2)
691 {
692 int pos = 0;
693 frm_dir_map_t::const_iterator it;
694 for (it = m_deleteMap.constBegin(); it != m_deleteMap.constEnd(); ++it)
695 {
696 if (it.key() > (uint64_t)currentFrame)
697 {
698 pos = it.key();
699 break;
700 }
701 }
702 // seek to next cutpoint
703 m_offset = 0;
704 seekToFrame(pos, false);
705 return true;
706 }
707 else
708 {
709 inc = (int) (inc * ceil(m_fps));
710 }
711
712 int64_t newFrame = currentFrame + inc - m_offset;
713 if (newFrame == currentFrame + 1)
714 getFrameImage(false);
715 else
716 seekToFrame(newFrame);
717
718 return true;
719}
720
722{
723 int64_t currentFrame = (m_currentPTS - m_startPTS) / m_frameTime;
724
725 int inc = kSeekAmounts[m_currentSeek].amount;
726 if (inc == -1)
727 inc = -1;
728 else if (inc == -2)
729 {
730 // seek to previous cut point
731 frm_dir_map_t::const_iterator it;
732 int pos = 0;
733 for (it = m_deleteMap.constBegin(); it != m_deleteMap.constEnd(); ++it)
734 {
735 if (it.key() >= (uint64_t)currentFrame)
736 break;
737
738 pos = it.key();
739 }
740
741 // seek to next cutpoint
742 m_offset = 0;
743 seekToFrame(pos, false);
744 return true;
745 }
746 else
747 {
748 inc = (int) (-inc * ceil(m_fps));
749 }
750
751 int64_t newFrame = currentFrame + inc - m_offset;
752 seekToFrame(newFrame);
753
754 return true;
755}
756
757bool ThumbFinder::getFrameImage(bool needKeyFrame, int64_t requiredPTS)
758{
759 AVFrame orig;
760 AVFrame retbuf;
761 memset(&orig, 0, sizeof(AVFrame));
762 memset(&retbuf, 0, sizeof(AVFrame));
763
764 AVPacket *pkt = av_packet_alloc();
765 if (pkt == nullptr)
766 {
767 LOG(VB_GENERAL, LOG_ERR, "packet allocation failed");
768 return false;
769 }
770
771 bool frameFinished = false;
772 bool gotKeyFrame = false;
773
774 while (av_read_frame(m_inputFC, pkt) >= 0 && !frameFinished)
775 {
776 if (pkt->stream_index == m_videostream)
777 {
778 int keyFrame = pkt->flags & AV_PKT_FLAG_KEY;
779
780 if (m_startPTS == -1 && pkt->dts != AV_NOPTS_VALUE)
781 {
782 m_startPTS = pkt->dts;
783 m_frameTime = pkt->duration;
784 }
785
786 if (keyFrame)
787 gotKeyFrame = true;
788
789 if (!gotKeyFrame && needKeyFrame)
790 {
791 av_packet_unref(pkt);
792 continue;
793 }
794
795 if (m_firstIFramePTS == -1)
796 m_firstIFramePTS = pkt->dts;
797
798 av_frame_unref(m_frame);
799 frameFinished = false;
800 int ret = avcodec_receive_frame(m_codecCtx, m_frame);
801 if (ret == 0)
802 frameFinished = true;
803 if (ret == 0 || ret == AVERROR(EAGAIN))
804 avcodec_send_packet(m_codecCtx, pkt);
805 if (requiredPTS != -1 && pkt->dts != AV_NOPTS_VALUE && pkt->dts < requiredPTS)
806 frameFinished = false;
807
808 m_currentPTS = pkt->dts;
809 }
810
811 av_packet_unref(pkt);
812 }
813 av_packet_free(&pkt);
814
815 if (frameFinished)
816 {
817 av_image_fill_arrays(retbuf.data, retbuf.linesize, m_outputbuf,
818 AV_PIX_FMT_RGB32, m_frameWidth, m_frameHeight, IMAGE_ALIGN);
821 m_copy.Copy(&retbuf, AV_PIX_FMT_RGB32, tmp, m_codecCtx->pix_fmt,
823
825 QImage::Format_RGB32);
826
827 QByteArray ffile = m_frameFile.toLocal8Bit();
828 if (!img.save(ffile.constData(), "JPEG"))
829 {
830 LOG(VB_GENERAL, LOG_ERR, "Failed to save thumb: " + m_frameFile);
831 }
832
833 if (m_updateFrame)
834 {
835 MythImage *mimage =
837 mimage->Assign(img);
838 m_frameImage->SetImage(mimage);
839 mimage->DecrRef();
840 }
841
843 }
844
845 return true;
846}
847
849{
850 delete[] m_outputbuf;
851
852 // close the codec
853 if (m_inputFC.isOpen() && m_inputFC->streams)
855
856 // close the video file
858}
859
861{
862 MythScreenStack *popupStack = GetMythMainWindow()->GetStack("popup stack");
863 auto *menuPopup = new MythDialogBox(tr("Menu"), popupStack, "actionmenu");
864
865 if (menuPopup->Create())
866 popupStack->AddScreen(menuPopup);
867
868 menuPopup->SetReturnEvent(this, "action");
869
870 menuPopup->AddButton(tr("Exit, Save Thumbnails"), &ThumbFinder::savePressed);
871 menuPopup->AddButton(tr("Exit, Don't Save Thumbnails"), &ThumbFinder::cancelPressed);
872}
873
875{
876 if (!m_positionImage)
877 return;
878
879 QSize size = m_positionImage->GetArea().size();
880 auto *pixmap = new QPixmap(size.width(), size.height());
881
882 QPainter p(pixmap);
883 QBrush brush(Qt::green);
884
885 p.setBrush(brush);
886 p.setPen(Qt::NoPen);
887 p.fillRect(0, 0, size.width(), size.height(), brush);
888
889 frm_dir_map_t::const_iterator it;
890
891 brush.setColor(Qt::red);
892
893 for (it = m_deleteMap.constBegin(); it != m_deleteMap.constEnd(); ++it)
894 {
895 double startdelta = size.width();
896 if (it.key() != 0)
897 startdelta = (m_archiveItem->duration * m_fps) / it.key();
898
899 ++it;
900 if (it == m_deleteMap.constEnd())
901 {
902 LOG(VB_GENERAL, LOG_ERR, "ThumbFinder: found a start cut but no cut end");
903 break;
904 }
905
906 double enddelta = size.width();
907 if (it.key() != 0)
908 enddelta = (m_archiveItem->duration * m_fps) / it.key();
909
910 int start = (int) (size.width() / startdelta);
911 int end = (int) (size.width() / enddelta);
912 p.fillRect(start - 1, 0, end - start, size.height(), brush);
913 }
914
915 if (frame == 0)
916 frame = 1;
917 brush.setColor(Qt::yellow);
918 int pos = (int) (size.width() / ((m_archiveItem->duration * m_fps) / frame));
919 p.fillRect(pos, 0, 3, size.height(), brush);
920
922 image->Assign(*pixmap);
924
925 p.end();
926 delete pixmap;
927}
928
930{
931 if (m_archiveItem->type == "Recording")
932 {
934 {
935 frm_dir_map_t::const_iterator it;
936
937 int cutLen = 0;
938
939 for (it = m_deleteMap.constBegin(); it != m_deleteMap.constEnd(); ++it)
940 {
941 int start = it.key();
942
943 ++it;
944 if (it == m_deleteMap.constEnd())
945 {
946 LOG(VB_GENERAL, LOG_ERR, "ThumbFinder: found a start cut but no cut end");
947 break;
948 }
949
950 int end = it.key();
951 cutLen += end - start;
952 }
953 return m_archiveItem->duration - (int) (cutLen / m_fps);
954 }
955 }
956
957 return m_archiveItem->duration;
958}
AVFrame AVFrame
bool getFileDetails(ArchiveItem *a)
ProgramInfo * getProgramInfoForFile(const QString &inFile)
QString getTempDirectory(bool showError)
Definition: archiveutil.cpp:46
bool Open(const QString &filename)
int Copy(AVFrame *To, const MythVideoFrame *From, unsigned char *Buffer, AVPixelFormat Fmt=AV_PIX_FMT_YUV420P)
Initialise AVFrame and copy contents of VideoFrame frame into it, performing any required conversion.
Definition: mythavutil.cpp:267
static void DeinterlaceAVFrame(AVFrame *Frame)
Deinterlace an AVFrame.
Definition: mythavutil.cpp:135
AVCodecContext * GetCodecContext(const AVStream *Stream, const AVCodec *Codec=nullptr, bool NullCodec=false)
Definition: mythavutil.cpp:288
void FreeCodecContext(const AVStream *Stream)
Definition: mythavutil.cpp:333
Basic menu dialog, message and a list of options.
void Assign(const QImage &img)
Definition: mythimage.cpp:77
int DecrRef(void) override
Decrements reference count and deletes on 0.
Definition: mythimage.cpp:52
MythPainter * GetPainter()
bool TranslateKeyPress(const QString &Context, QKeyEvent *Event, QStringList &Actions, bool AllowJumps=true)
Get a list of actions for a keypress in the given context.
MythScreenStack * GetStack(const QString &Stackname)
MythImage * GetFormatImage()
Returns a blank reference counted image in the format required for the Draw functions for this painte...
virtual void AddScreen(MythScreenType *screen, bool allowFade=true)
Screen in which all other widgets are contained and rendered.
virtual bool NextPrevWidgetFocus(bool up_or_down)
void BuildFocusList(void)
MythUIType * GetFocusWidget(void) const
bool keyPressEvent(QKeyEvent *event) override
Key event handler.
bool SetFocusWidget(MythUIType *widget=nullptr)
virtual void Close()
void SetImage(MythImage *image, const QString &name="")
Sets an image directly, should only be used in special circumstances since it bypasses the cache.
void SetText(const QString &text, const QString &name="", const QString &state="")
MythUIButtonListItem * GetItemCurrent() const
void SetItemCurrent(MythUIButtonListItem *item)
int GetCurrentPos() const
void itemSelected(MythUIButtonListItem *item)
void Clicked()
void SetImage(MythImage *img)
Should not be used unless absolutely necessary since it bypasses the image caching and threaded loade...
virtual void SetText(const QString &text)
Definition: mythuitext.cpp:115
void SetRedraw(void)
Definition: mythuitype.cpp:313
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
Holds information on recordings and videos.
Definition: programinfo.h:68
bool QueryCutList(frm_dir_map_t &delMap, bool loadAutosave=false) const
bool seekForward()
bool seekToFrame(int frame, bool checkPos=true)
void ShowMenu(void) override
unsigned char * m_outputbuf
Definition: thumbfinder.h:88
QString frameToTime(int64_t frame, bool addFrame=false) const
int m_frameHeight
Definition: thumbfinder.h:91
int m_finalDuration
Definition: thumbfinder.h:101
int64_t m_firstIFramePTS
Definition: thumbfinder.h:97
bool keyPressEvent(QKeyEvent *event) override
Key event handler.
size_t m_currentSeek
Definition: thumbfinder.h:93
QList< ThumbImage * > m_thumbList
Definition: thumbfinder.h:106
bool initAVCodec(const QString &inFile)
ArchiveRemoteAVFormatContext m_inputFC
Definition: thumbfinder.h:80
int64_t m_startPTS
Definition: thumbfinder.h:95
int checkFramePosition(int frameNumber)
bool seekBackward()
MythUIButton * m_cancelButton
Definition: thumbfinder.h:112
void Init(void) override
Used after calling Load() to assign data to widgets and other UI initilisation which is prohibited in...
bool getFrameImage(bool needKeyFrame=true, int64_t requiredPTS=-1)
int64_t m_startTime
Definition: thumbfinder.h:94
ArchiveItem * m_archiveItem
Definition: thumbfinder.h:104
MythUIButton * m_saveButton
Definition: thumbfinder.h:111
bool Create(void) override
frm_dir_map_t m_deleteMap
Definition: thumbfinder.h:100
bool m_updateFrame
Definition: thumbfinder.h:99
static int getChapterCount(const QString &menuTheme)
MythAVCopy m_copy
Definition: thumbfinder.h:85
~ThumbFinder() override
void updateCurrentPos(void)
int64_t m_frameTime
Definition: thumbfinder.h:98
int m_frameWidth
Definition: thumbfinder.h:90
void closeAVCodec()
MythUIImage * m_positionImage
Definition: thumbfinder.h:114
void updateThumb(void)
void changeSeekAmount(bool up)
float m_fps
Definition: thumbfinder.h:87
MythUIButton * m_frameButton
Definition: thumbfinder.h:110
MythUIButtonList * m_imageGrid
Definition: thumbfinder.h:115
void gridItemChanged(MythUIButtonListItem *item)
QString m_thumbDir
Definition: thumbfinder.h:107
MythCodecMap m_codecMap
Definition: thumbfinder.h:82
const AVCodec * m_codec
Definition: thumbfinder.h:83
int64_t m_currentPTS
Definition: thumbfinder.h:96
int calcFinalDuration(void)
void updatePositionBar(int64_t frame)
int m_thumbCount
Definition: thumbfinder.h:105
QString m_frameFile
Definition: thumbfinder.h:89
void savePressed(void)
ThumbFinder(MythScreenStack *parent, ArchiveItem *archiveItem, const QString &menuTheme)
Definition: thumbfinder.cpp:86
int m_videostream
Definition: thumbfinder.h:92
bool getThumbImages(void)
static QString createThumbDir(void)
MythUIText * m_seekAmountText
Definition: thumbfinder.h:116
AVCodecContext * m_codecCtx
Definition: thumbfinder.h:81
MythAVFrame m_frame
Definition: thumbfinder.h:84
MythUIImage * m_frameImage
Definition: thumbfinder.h:113
void loadCutList(void)
void cancelPressed(void)
MythUIText * m_currentPosText
Definition: thumbfinder.h:117
static bool LoadWindowFromXML(const QString &xmlfile, const QString &windowname, MythUIType *parent)
unsigned int uint
Definition: freesurround.h:24
static guint32 * tmp
Definition: goom_core.cpp:26
QString GetShareDir(void)
Definition: mythdirs.cpp:261
#define ENO
This can be appended to the LOG args with "+".
Definition: mythlogging.h:74
#define LOG(_MASK_, _LEVEL_, _QSTRING_)
Definition: mythlogging.h:39
MythMainWindow * GetMythMainWindow(void)
QString formatTime(std::chrono::milliseconds msecs, QString fmt)
Format a milliseconds time value.
Definition: mythdate.cpp:242
MBASE_PUBLIC long long copy(QFile &dst, QFile &src, uint block_size=0)
Copies src file to dst file.
@ MARK_CUT_START
Definition: programtypes.h:55
@ MARK_CUT_END
Definition: programtypes.h:54
QList< ThumbImage * > thumbList
Definition: archiveutil.h:72
bool hasCutlist
Definition: archiveutil.h:69
QString type
Definition: archiveutil.h:53
QString filename
Definition: archiveutil.h:59
bool useCutlist
Definition: archiveutil.h:70
qint64 frame
Definition: archiveutil.h:47
QString filename
Definition: archiveutil.h:46
QString caption
Definition: archiveutil.h:45
static bool Assign(ContainerType *container, UIType *&item, const QString &name, bool *err=nullptr)
Definition: mythuiutils.h:27
static constexpr int8_t PRE_SEEK_AMOUNT
Definition: thumbfinder.cpp:71
static const std::array< const SeekAmount, 9 > kSeekAmounts
Definition: thumbfinder.cpp:74