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