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 <libmythbase/mythconfig.h> // IMAGE_ALIGN
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 {.name="frame", .amount=-1},
76 {.name="1 second", .amount=1},
77 {.name="5 seconds", .amount=5},
78 {.name="10 seconds", .amount=10},
79 {.name="30 seconds", .amount=30},
80 {.name="1 minute", .amount=60},
81 {.name="5 minutes", .amount=300},
82 {.name="10 minutes", .amount=600},
83 {.name="Cut Point", .amount=-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 {
567 m_startTime = m_inputFC->streams[i]->start_time;
568 }
569 else
570 {
571 LOG(VB_GENERAL, LOG_ERR,
572 "ThumbFinder: Failed to get start time");
573 return false;
574 }
575
576 m_videostream = i;
577 m_frameWidth = st->codecpar->width;
578 m_frameHeight = st->codecpar->height;
579 if (st->r_frame_rate.den && st->r_frame_rate.num)
580 m_fps = av_q2d(st->r_frame_rate);
581 else
582 m_fps = 1/av_q2d(st->time_base);
583 break;
584 }
585 }
586
587 if (m_videostream == -1)
588 {
589 LOG(VB_GENERAL, LOG_ERR, "Couldn't find a video stream");
590 return false;
591 }
592
593 // get the codec context for the video stream
595 m_codecCtx->debug = 0;
596 m_codecCtx->workaround_bugs = 1;
597 m_codecCtx->lowres = 0;
598 m_codecCtx->idct_algo = FF_IDCT_AUTO;
599 m_codecCtx->skip_frame = AVDISCARD_DEFAULT;
600 m_codecCtx->skip_idct = AVDISCARD_DEFAULT;
601 m_codecCtx->skip_loop_filter = AVDISCARD_DEFAULT;
602 m_codecCtx->err_recognition = AV_EF_CAREFUL;
603 m_codecCtx->error_concealment = 3;
604
605 // get decoder for video stream
606 m_codec = avcodec_find_decoder(m_codecCtx->codec_id);
607
608 if (m_codec == nullptr)
609 {
610 LOG(VB_GENERAL, LOG_ERR,
611 "ThumbFinder: Couldn't find codec for video stream");
612 return false;
613 }
614
615 // open codec
616 if (avcodec_open2(m_codecCtx, m_codec, nullptr) < 0)
617 {
618 LOG(VB_GENERAL, LOG_ERR,
619 "ThumbFinder: Couldn't open codec for video stream");
620 return false;
621 }
622
623 // allocate temp buffer
624 int bufflen = m_frameWidth * m_frameHeight * 4;
625 m_outputbuf = new unsigned char[bufflen];
626 m_frameFile = getTempDirectory() + "work/frame.jpg";
627 return true;
628}
629
631{
632 if (m_deleteMap.isEmpty() || !m_archiveItem->useCutlist)
633 return frameNumber;
634
635 int diff = 0;
636 frm_dir_map_t::const_iterator it = m_deleteMap.constFind(frameNumber);
637
638 for (it = m_deleteMap.constBegin(); it != m_deleteMap.constEnd(); ++it)
639 {
640 int start = it.key();
641
642 ++it;
643 if (it == m_deleteMap.constEnd())
644 {
645 LOG(VB_GENERAL, LOG_ERR, "ThumbFinder: found a start cut but no cut end");
646 break;
647 }
648
649 int end = it.key();
650
651 if (start <= frameNumber + diff)
652 diff += end - start;
653 }
654
655 m_offset = diff;
656 return frameNumber + diff;
657}
658
659bool ThumbFinder::seekToFrame(int frame, bool checkPos)
660{
661 // make sure the frame is not in a cut point
662 if (checkPos)
663 frame = checkFramePosition(frame);
664
665 // seek to a position PRE_SEEK_AMOUNT frames before the required frame
666 int64_t timestamp = m_startTime + (frame * m_frameTime) -
668 int64_t requiredPTS = m_startPTS + (frame * m_frameTime);
669
670 timestamp = std::max(timestamp, m_startTime);
671
672 if (av_seek_frame(m_inputFC, m_videostream, timestamp, AVSEEK_FLAG_ANY) < 0)
673 {
674 LOG(VB_GENERAL, LOG_ERR, "ThumbFinder::SeekToFrame: seek failed") ;
675 return false;
676 }
677
678 avcodec_flush_buffers(m_codecCtx);
679 getFrameImage(true, requiredPTS);
680
681 return true;
682}
683
685{
686 int64_t currentFrame = (m_currentPTS - m_startPTS) / m_frameTime;
687
688 int inc = kSeekAmounts[m_currentSeek].amount;
689
690 if (inc == -1)
691 {
692 inc = 1;
693 }
694 else if (inc == -2)
695 {
696 int pos = 0;
697 frm_dir_map_t::const_iterator it;
698 for (it = m_deleteMap.constBegin(); it != m_deleteMap.constEnd(); ++it)
699 {
700 if (it.key() > (uint64_t)currentFrame)
701 {
702 pos = it.key();
703 break;
704 }
705 }
706 // seek to next cutpoint
707 m_offset = 0;
708 seekToFrame(pos, false);
709 return true;
710 }
711 else
712 {
713 inc = (int) (inc * ceil(m_fps));
714 }
715
716 int64_t newFrame = currentFrame + inc - m_offset;
717 if (newFrame == currentFrame + 1)
718 getFrameImage(false);
719 else
720 seekToFrame(newFrame);
721
722 return true;
723}
724
726{
727 int64_t currentFrame = (m_currentPTS - m_startPTS) / m_frameTime;
728
729 int inc = kSeekAmounts[m_currentSeek].amount;
730 if (inc == -1)
731 {
732 inc = -1;
733 }
734 else if (inc == -2)
735 {
736 // seek to previous cut point
737 frm_dir_map_t::const_iterator it;
738 int pos = 0;
739 for (it = m_deleteMap.constBegin(); it != m_deleteMap.constEnd(); ++it)
740 {
741 if (it.key() >= (uint64_t)currentFrame)
742 break;
743
744 pos = it.key();
745 }
746
747 // seek to next cutpoint
748 m_offset = 0;
749 seekToFrame(pos, false);
750 return true;
751 }
752 else
753 {
754 inc = (int) (-inc * ceil(m_fps));
755 }
756
757 int64_t newFrame = currentFrame + inc - m_offset;
758 seekToFrame(newFrame);
759
760 return true;
761}
762
763bool ThumbFinder::getFrameImage(bool needKeyFrame, int64_t requiredPTS)
764{
765 AVFrame orig;
766 AVFrame retbuf;
767 memset(&orig, 0, sizeof(AVFrame));
768 memset(&retbuf, 0, sizeof(AVFrame));
769
770 AVPacket *pkt = av_packet_alloc();
771 if (pkt == nullptr)
772 {
773 LOG(VB_GENERAL, LOG_ERR, "packet allocation failed");
774 return false;
775 }
776
777 bool frameFinished = false;
778 bool gotKeyFrame = false;
779
780 while (av_read_frame(m_inputFC, pkt) >= 0 && !frameFinished)
781 {
782 if (pkt->stream_index == m_videostream)
783 {
784 int keyFrame = pkt->flags & AV_PKT_FLAG_KEY;
785
786 if (m_startPTS == -1 && pkt->dts != AV_NOPTS_VALUE)
787 {
788 m_startPTS = pkt->dts;
789 m_frameTime = pkt->duration;
790 }
791
792 if (keyFrame)
793 gotKeyFrame = true;
794
795 if (!gotKeyFrame && needKeyFrame)
796 {
797 av_packet_unref(pkt);
798 continue;
799 }
800
801 if (m_firstIFramePTS == -1)
802 m_firstIFramePTS = pkt->dts;
803
804 av_frame_unref(m_frame);
805 frameFinished = false;
806 int ret = avcodec_receive_frame(m_codecCtx, m_frame);
807 if (ret == 0)
808 frameFinished = true;
809 if (ret == 0 || ret == AVERROR(EAGAIN))
810 avcodec_send_packet(m_codecCtx, pkt);
811 if (requiredPTS != -1 && pkt->dts != AV_NOPTS_VALUE && pkt->dts < requiredPTS)
812 frameFinished = false;
813
814 m_currentPTS = pkt->dts;
815 }
816
817 av_packet_unref(pkt);
818 }
819 av_packet_free(&pkt);
820
821 if (frameFinished)
822 {
823 av_image_fill_arrays(retbuf.data, retbuf.linesize, m_outputbuf,
824 AV_PIX_FMT_RGB32, m_frameWidth, m_frameHeight, IMAGE_ALIGN);
825 AVFrame *tmp = m_frame;
827 m_copy.Copy(&retbuf, AV_PIX_FMT_RGB32, tmp, m_codecCtx->pix_fmt,
829
831 QImage::Format_RGB32);
832
833 QByteArray ffile = m_frameFile.toLocal8Bit();
834 if (!img.save(ffile.constData(), "JPEG"))
835 {
836 LOG(VB_GENERAL, LOG_ERR, "Failed to save thumb: " + m_frameFile);
837 }
838
839 if (m_updateFrame)
840 {
841 MythImage *mimage =
843 mimage->Assign(img);
844 m_frameImage->SetImage(mimage);
845 mimage->DecrRef();
846 }
847
849 }
850
851 return true;
852}
853
855{
856 delete[] m_outputbuf;
857
858 // close the codec
859 if (m_inputFC.isOpen() && m_inputFC->streams)
861
862 // close the video file
864}
865
867{
868 MythScreenStack *popupStack = GetMythMainWindow()->GetStack("popup stack");
869 auto *menuPopup = new MythDialogBox(tr("Menu"), popupStack, "actionmenu");
870
871 if (menuPopup->Create())
872 popupStack->AddScreen(menuPopup);
873
874 menuPopup->SetReturnEvent(this, "action");
875
876 menuPopup->AddButton(tr("Exit, Save Thumbnails"), &ThumbFinder::savePressed);
877 menuPopup->AddButton(tr("Exit, Don't Save Thumbnails"), &ThumbFinder::cancelPressed);
878}
879
881{
882 if (!m_positionImage)
883 return;
884
885 QSize size = m_positionImage->GetArea().size();
886 auto *pixmap = new QPixmap(size.width(), size.height());
887
888 QPainter p(pixmap);
889 QBrush brush(Qt::green);
890
891 p.setBrush(brush);
892 p.setPen(Qt::NoPen);
893 p.fillRect(0, 0, size.width(), size.height(), brush);
894
895 frm_dir_map_t::const_iterator it;
896
897 brush.setColor(Qt::red);
898
899 for (it = m_deleteMap.constBegin(); it != m_deleteMap.constEnd(); ++it)
900 {
901 double startdelta = size.width();
902 if (it.key() != 0)
903 startdelta = (m_archiveItem->duration * m_fps) / it.key();
904
905 ++it;
906 if (it == m_deleteMap.constEnd())
907 {
908 LOG(VB_GENERAL, LOG_ERR, "ThumbFinder: found a start cut but no cut end");
909 break;
910 }
911
912 double enddelta = size.width();
913 if (it.key() != 0)
914 enddelta = (m_archiveItem->duration * m_fps) / it.key();
915
916 int start = (int) (size.width() / startdelta);
917 int end = (int) (size.width() / enddelta);
918 p.fillRect(start - 1, 0, end - start, size.height(), brush);
919 }
920
921 if (frame == 0)
922 frame = 1;
923 brush.setColor(Qt::yellow);
924 int pos = (int) (size.width() / ((m_archiveItem->duration * m_fps) / frame));
925 p.fillRect(pos, 0, 3, size.height(), brush);
926
928 image->Assign(*pixmap);
930
931 p.end();
932 delete pixmap;
933}
934
936{
937 if (m_archiveItem->type == "Recording")
938 {
940 {
941 frm_dir_map_t::const_iterator it;
942
943 int cutLen = 0;
944
945 for (it = m_deleteMap.constBegin(); it != m_deleteMap.constEnd(); ++it)
946 {
947 int start = it.key();
948
949 ++it;
950 if (it == m_deleteMap.constEnd())
951 {
952 LOG(VB_GENERAL, LOG_ERR, "ThumbFinder: found a start cut but no cut end");
953 break;
954 }
955
956 int end = it.key();
957 cutLen += end - start;
958 }
959 return m_archiveItem->duration - (int) (cutLen / m_fps);
960 }
961 }
962
963 return m_archiveItem->duration;
964}
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:270
static void DeinterlaceAVFrame(AVFrame *Frame)
Deinterlace an AVFrame.
Definition: mythavutil.cpp:138
AVCodecContext * GetCodecContext(const AVStream *Stream, const AVCodec *Codec=nullptr, bool NullCodec=false)
Definition: mythavutil.cpp:291
void FreeCodecContext(const AVStream *Stream)
Definition: mythavutil.cpp:336
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:299
virtual MythRect GetArea(void) const
If the object has a minimum area defined, return it, other wise return the default area.
Definition: mythuitype.cpp:871
Holds information on recordings and videos.
Definition: programinfo.h:74
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:89
QString frameToTime(int64_t frame, bool addFrame=false) const
int m_frameHeight
Definition: thumbfinder.h:92
int m_finalDuration
Definition: thumbfinder.h:102
int64_t m_firstIFramePTS
Definition: thumbfinder.h:98
bool keyPressEvent(QKeyEvent *event) override
Key event handler.
size_t m_currentSeek
Definition: thumbfinder.h:94
QList< ThumbImage * > m_thumbList
Definition: thumbfinder.h:107
bool initAVCodec(const QString &inFile)
ArchiveRemoteAVFormatContext m_inputFC
Definition: thumbfinder.h:81
int64_t m_startPTS
Definition: thumbfinder.h:96
int checkFramePosition(int frameNumber)
bool seekBackward()
MythUIButton * m_cancelButton
Definition: thumbfinder.h:113
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:95
ArchiveItem * m_archiveItem
Definition: thumbfinder.h:105
MythUIButton * m_saveButton
Definition: thumbfinder.h:112
bool Create(void) override
frm_dir_map_t m_deleteMap
Definition: thumbfinder.h:101
bool m_updateFrame
Definition: thumbfinder.h:100
static int getChapterCount(const QString &menuTheme)
MythAVCopy m_copy
Definition: thumbfinder.h:86
~ThumbFinder() override
void updateCurrentPos(void)
int64_t m_frameTime
Definition: thumbfinder.h:99
int m_frameWidth
Definition: thumbfinder.h:91
void closeAVCodec()
MythUIImage * m_positionImage
Definition: thumbfinder.h:115
void updateThumb(void)
void changeSeekAmount(bool up)
float m_fps
Definition: thumbfinder.h:88
MythUIButton * m_frameButton
Definition: thumbfinder.h:111
MythUIButtonList * m_imageGrid
Definition: thumbfinder.h:116
void gridItemChanged(MythUIButtonListItem *item)
QString m_thumbDir
Definition: thumbfinder.h:108
MythCodecMap m_codecMap
Definition: thumbfinder.h:83
const AVCodec * m_codec
Definition: thumbfinder.h:84
int64_t m_currentPTS
Definition: thumbfinder.h:97
int calcFinalDuration(void)
void updatePositionBar(int64_t frame)
int m_thumbCount
Definition: thumbfinder.h:106
QString m_frameFile
Definition: thumbfinder.h:90
void savePressed(void)
ThumbFinder(MythScreenStack *parent, ArchiveItem *archiveItem, const QString &menuTheme)
Definition: thumbfinder.cpp:86
int m_videostream
Definition: thumbfinder.h:93
bool getThumbImages(void)
static QString createThumbDir(void)
MythUIText * m_seekAmountText
Definition: thumbfinder.h:117
AVCodecContext * m_codecCtx
Definition: thumbfinder.h:82
MythAVFrame m_frame
Definition: thumbfinder.h:85
MythUIImage * m_frameImage
Definition: thumbfinder.h:114
void loadCutList(void)
void cancelPressed(void)
MythUIText * m_currentPosText
Definition: thumbfinder.h:118
static bool LoadWindowFromXML(const QString &xmlfile, const QString &windowname, MythUIType *parent)
unsigned int uint
Definition: compat.h:60
QString GetShareDir(void)
Definition: mythdirs.cpp:283
#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