Merge pull request #26126 from stephan49/fix-pipewire-unlock-error
[xbmc.git] / xbmc / video / dialogs / GUIDialogVideoBookmarks.cpp
blob97b9ce677e5768d54714f7f86f9dea61d5d80c6b
1 /*
2 * Copyright (C) 2005-2018 Team Kodi
3 * This file is part of Kodi - https://kodi.tv
5 * SPDX-License-Identifier: GPL-2.0-or-later
6 * See LICENSES/README.md for more information.
7 */
9 #include "GUIDialogVideoBookmarks.h"
11 #include "FileItem.h"
12 #include "FileItemList.h"
13 #include "ServiceBroker.h"
14 #include "Util.h"
15 #include "application/Application.h"
16 #include "application/ApplicationComponents.h"
17 #include "application/ApplicationPlayer.h"
18 #include "dialogs/GUIDialogContextMenu.h"
19 #include "dialogs/GUIDialogKaiToast.h"
20 #include "guilib/GUIComponent.h"
21 #include "guilib/GUIWindowManager.h"
22 #include "guilib/LocalizeStrings.h"
23 #include "imagefiles/ImageFileURL.h"
24 #include "input/actions/Action.h"
25 #include "input/actions/ActionIDs.h"
26 #include "messaging/ApplicationMessenger.h"
27 #include "pictures/Picture.h"
28 #include "profiles/ProfileManager.h"
29 #include "settings/AdvancedSettings.h"
30 #include "settings/Settings.h"
31 #include "settings/SettingsComponent.h"
32 #include "utils/Crc32.h"
33 #include "utils/FileUtils.h"
34 #include "utils/StringUtils.h"
35 #include "utils/URIUtils.h"
36 #include "utils/Variant.h"
37 #include "utils/log.h"
38 #include "video/VideoDatabase.h"
39 #include "video/VideoFileItemClassify.h"
40 #include "view/ViewState.h"
42 #include <algorithm>
43 #include <mutex>
44 #include <string>
45 #include <vector>
47 #define CONTROL_ADD_BOOKMARK 2
48 #define CONTROL_CLEAR_BOOKMARKS 3
49 #define CONTROL_ADD_EPISODE_BOOKMARK 4
51 #define CONTROL_THUMBS 11
53 using namespace KODI::VIDEO;
55 CGUIDialogVideoBookmarks::CGUIDialogVideoBookmarks()
56 : CGUIDialog(WINDOW_DIALOG_VIDEO_BOOKMARKS, "VideoOSDBookmarks.xml")
58 m_vecItems = new CFileItemList;
59 m_loadType = LOAD_EVERY_TIME;
62 CGUIDialogVideoBookmarks::~CGUIDialogVideoBookmarks()
64 delete m_vecItems;
67 bool CGUIDialogVideoBookmarks::OnMessage(CGUIMessage& message)
69 switch ( message.GetMessage() )
71 case GUI_MSG_WINDOW_DEINIT:
73 CUtil::DeleteVideoDatabaseDirectoryCache();
74 Clear();
76 break;
78 case GUI_MSG_WINDOW_INIT:
80 // don't init this dialog if we don't playback a file
81 const auto& components = CServiceBroker::GetAppComponents();
82 const auto appPlayer = components.GetComponent<CApplicationPlayer>();
83 if (!appPlayer->IsPlaying())
84 return false;
86 CGUIWindow::OnMessage(message);
87 Update();
88 return true;
90 break;
92 case GUI_MSG_CLICKED:
94 int iControl = message.GetSenderId();
95 if (iControl == CONTROL_ADD_BOOKMARK)
97 AddBookmark();
98 Update();
100 else if (iControl == CONTROL_CLEAR_BOOKMARKS)
102 ClearBookmarks();
104 else if (iControl == CONTROL_ADD_EPISODE_BOOKMARK)
106 AddEpisodeBookmark();
107 Update();
109 else if (m_viewControl.HasControl(iControl)) // list/thumb control
111 int iItem = m_viewControl.GetSelectedItem();
112 int iAction = message.GetParam1();
113 if (iAction == ACTION_DELETE_ITEM)
115 Delete(iItem);
117 else if (iAction == ACTION_SELECT_ITEM || iAction == ACTION_MOUSE_LEFT_CLICK)
119 GotoBookmark(iItem);
123 break;
124 case GUI_MSG_SETFOCUS:
126 if (m_viewControl.HasControl(message.GetControlId()) && m_viewControl.GetCurrentControl() != message.GetControlId())
128 m_viewControl.SetFocused();
129 return true;
132 break;
133 case GUI_MSG_REFRESH_LIST:
135 switch (message.GetParam1())
137 case 0:
138 OnRefreshList();
139 break;
140 default:
141 break;
144 break;
147 return CGUIDialog::OnMessage(message);
150 bool CGUIDialogVideoBookmarks::OnAction(const CAction &action)
152 switch(action.GetID())
154 case ACTION_CONTEXT_MENU:
155 case ACTION_MOUSE_RIGHT_CLICK:
157 OnPopupMenu(m_viewControl.GetSelectedItem());
158 return true;
161 return CGUIDialog::OnAction(action);
164 int CGUIDialogVideoBookmarks::ItemToBookmarkIndex(int item) const
166 if (item < 0 || item >= static_cast<int>(m_vecItems->Size()))
167 return -1;
169 const std::shared_ptr<CFileItem> fileItem{m_vecItems->Get(item)};
171 if (!fileItem->GetProperty("isbookmark").asBoolean(false))
172 return -1;
174 const int bookmarkIdx{fileItem->GetProperty("bookmark").asInteger32(-1)};
175 if (bookmarkIdx < 0 || bookmarkIdx >= static_cast<int>(m_bookmarks.size()))
177 CLog::LogF(LOGERROR, "invalid bookmark index {} for {} bookmark(s)", bookmarkIdx,
178 m_bookmarks.size());
179 return -1;
181 return bookmarkIdx;
184 void CGUIDialogVideoBookmarks::OnPopupMenu(int item)
186 const int bookmarkIdx{ItemToBookmarkIndex(item)};
187 if (bookmarkIdx < 0)
188 return;
190 const CBookmark& bm{m_bookmarks[bookmarkIdx]};
192 // highlight the item
193 (*m_vecItems)[item]->Select(true);
195 CContextButtons choices;
196 choices.Add(1, (bm.type == CBookmark::EPISODE
197 ? 20405
198 : 20404)); // "Remove episode bookmark" or "Remove bookmark"
200 const int button{CGUIDialogContextMenu::ShowAndGetChoice(choices)};
202 // unhighlight the item
203 (*m_vecItems)[item]->Select(false);
205 if (button == 1)
206 Delete(bm);
209 void CGUIDialogVideoBookmarks::Delete(int item)
211 const int bookmarkIdx{ItemToBookmarkIndex(item)};
212 if (bookmarkIdx >= 0)
213 Delete(m_bookmarks[bookmarkIdx]);
216 void CGUIDialogVideoBookmarks::Delete(const CBookmark& bm)
218 CVideoDatabase videoDatabase;
219 if (!videoDatabase.Open())
220 return;
222 const std::string path{g_application.CurrentFileItem().GetDynPath()};
223 videoDatabase.ClearBookMarkOfFile(path, bm, bm.type);
224 videoDatabase.Close();
225 CUtil::DeleteVideoDatabaseDirectoryCache();
226 Update();
229 void CGUIDialogVideoBookmarks::OnRefreshList()
231 m_bookmarks.clear();
232 std::vector<CFileItemPtr> items;
234 // open the d/b and retrieve the bookmarks for the current movie
235 m_filePath = g_application.CurrentFileItem().GetDynPath();
237 CVideoDatabase videoDatabase;
238 if (!videoDatabase.Open())
239 return;
241 videoDatabase.GetBookMarksForFile(m_filePath, m_bookmarks);
242 videoDatabase.GetBookMarksForFile(m_filePath, m_bookmarks, CBookmark::EPISODE, true);
243 videoDatabase.Close();
245 std::unique_lock<CCriticalSection> lock(m_refreshSection);
246 m_vecItems->Clear();
248 // cycle through each stored bookmark and add it to our list control
249 for (unsigned int i = 0; i < m_bookmarks.size(); ++i)
251 std::string bookmarkTime;
252 if (m_bookmarks[i].type == CBookmark::EPISODE)
253 bookmarkTime = StringUtils::Format("{} {} {} {}", g_localizeStrings.Get(20373),
254 m_bookmarks[i].seasonNumber, g_localizeStrings.Get(20359),
255 m_bookmarks[i].episodeNumber);
256 else
257 bookmarkTime = StringUtils::SecondsToTimeString((long)m_bookmarks[i].timeInSeconds, TIME_FORMAT_HH_MM_SS);
259 CFileItemPtr item(new CFileItem(StringUtils::Format(g_localizeStrings.Get(299), i + 1)));
260 item->SetLabel2(bookmarkTime);
261 item->SetArt("thumb", m_bookmarks[i].thumbNailImage);
262 item->SetProperty("resumepoint", m_bookmarks[i].timeInSeconds);
263 item->SetProperty("playerstate", m_bookmarks[i].playerState);
264 item->SetProperty("isbookmark", "true");
265 item->SetProperty("bookmark", i);
266 items.push_back(item);
269 // add chapters if around
270 const auto& components = CServiceBroker::GetAppComponents();
271 const auto appPlayer = components.GetComponent<CApplicationPlayer>();
272 for (int i = 1; i <= appPlayer->GetChapterCount(); ++i)
274 std::string chapterName;
275 appPlayer->GetChapterName(chapterName, i);
277 int64_t pos = appPlayer->GetChapterPos(i);
278 std::string time = StringUtils::SecondsToTimeString((long) pos, TIME_FORMAT_HH_MM_SS);
280 if (chapterName.empty() ||
281 StringUtils::StartsWithNoCase(chapterName, time) ||
282 StringUtils::IsNaturalNumber(chapterName))
283 chapterName = StringUtils::Format(g_localizeStrings.Get(25010), i);
285 CFileItemPtr item(new CFileItem(chapterName));
286 item->SetLabel2(time);
288 if (CServiceBroker::GetSettingsComponent()->GetSettings()->GetBool(
289 CSettings::SETTING_MYVIDEOS_EXTRACTCHAPTERTHUMBS))
291 auto chapterPath = IMAGE_FILES::CImageFileURL::FromFile(m_filePath, "video");
292 chapterPath.AddOption("chapter", std::to_string(i));
293 item->SetArt("thumb", chapterPath.ToCacheKey());
296 item->SetProperty("chapter", i);
297 item->SetProperty("resumepoint", static_cast<double>(pos));
298 item->SetProperty("ischapter", "true");
299 items.push_back(item);
302 // sort items by resume point
303 std::sort(items.begin(), items.end(), [](const CFileItemPtr &item1, const CFileItemPtr &item2) {
304 return item1->GetProperty("resumepoint").asDouble() < item2->GetProperty("resumepoint").asDouble();
307 // add items to file list and mark the proper item as selected if the current playtime is above
308 int selectedItemIndex = 0;
309 double playTime = g_application.GetTime();
310 for (auto& item : items)
312 m_vecItems->Add(item);
313 if (playTime >= item->GetProperty("resumepoint").asDouble())
314 selectedItemIndex = m_vecItems->Size() - 1;
317 m_viewControl.SetItems(*m_vecItems);
318 m_viewControl.SetSelectedItem(selectedItemIndex);
321 void CGUIDialogVideoBookmarks::Update()
323 CVideoDatabase videoDatabase;
324 if (!videoDatabase.Open())
325 return;
327 if (g_application.CurrentFileItem().HasVideoInfoTag() && g_application.CurrentFileItem().GetVideoInfoTag()->m_iEpisode > -1)
329 std::vector<CVideoInfoTag> episodes;
330 videoDatabase.GetEpisodesByFile(g_application.CurrentFile(),episodes);
331 if (episodes.size() > 1)
333 CONTROL_ENABLE(CONTROL_ADD_EPISODE_BOOKMARK);
335 else
337 CONTROL_DISABLE(CONTROL_ADD_EPISODE_BOOKMARK);
340 else
342 CONTROL_DISABLE(CONTROL_ADD_EPISODE_BOOKMARK);
346 m_viewControl.SetCurrentView(DEFAULT_VIEW_ICONS);
348 // empty the list ready for population
349 Clear();
351 OnRefreshList();
353 videoDatabase.Close();
356 void CGUIDialogVideoBookmarks::Clear()
358 m_viewControl.Clear();
359 m_vecItems->Clear();
362 void CGUIDialogVideoBookmarks::GotoBookmark(int item)
364 auto& components = CServiceBroker::GetAppComponents();
365 const auto appPlayer = components.GetComponent<CApplicationPlayer>();
366 if (item < 0 || item >= m_vecItems->Size() || !appPlayer->HasPlayer())
367 return;
369 CFileItemPtr fileItem = m_vecItems->Get(item);
370 int chapter = static_cast<int>(fileItem->GetProperty("chapter").asInteger());
371 if (chapter <= 0)
373 appPlayer->SetPlayerState(fileItem->GetProperty("playerstate").asString());
374 g_application.SeekTime(fileItem->GetProperty("resumepoint").asDouble());
376 else
377 appPlayer->SeekChapter(chapter);
379 Close();
382 void CGUIDialogVideoBookmarks::ClearBookmarks()
384 CVideoDatabase videoDatabase;
385 if (!videoDatabase.Open())
386 return;
388 const std::string path{g_application.CurrentFileItem().GetDynPath()};
389 videoDatabase.ClearBookMarksOfFile(path, CBookmark::STANDARD);
390 videoDatabase.ClearBookMarksOfFile(path, CBookmark::RESUME);
391 videoDatabase.ClearBookMarksOfFile(path, CBookmark::EPISODE);
392 videoDatabase.Close();
393 Update();
396 bool CGUIDialogVideoBookmarks::AddBookmark(CVideoInfoTag* tag)
398 CBookmark bookmark;
399 bookmark.timeInSeconds = (int)g_application.GetTime();
400 bookmark.totalTimeInSeconds = (int)g_application.GetTotalTime();
402 auto& components = CServiceBroker::GetAppComponents();
403 const auto appPlayer = components.GetComponent<CApplicationPlayer>();
405 if (appPlayer->HasPlayer())
406 bookmark.playerState = appPlayer->GetPlayerState();
407 else
408 bookmark.playerState.clear();
410 bookmark.player = g_application.GetCurrentPlayer();
412 // create the thumbnail image
413 const float aspectRatio{appPlayer->GetRenderAspectRatio()};
414 CRect srcRect{}, renderRect{}, viewRect{};
415 appPlayer->GetRects(srcRect, renderRect, viewRect);
416 const unsigned int srcWidth{static_cast<unsigned int>(srcRect.Width())};
417 const unsigned int srcHeight{static_cast<unsigned int>(srcRect.Height())};
418 const unsigned int renderWidth{static_cast<unsigned int>(renderRect.Width())};
419 const unsigned int renderHeight{static_cast<unsigned int>(renderRect.Height())};
420 const unsigned int viewWidth{static_cast<unsigned int>(viewRect.Width())};
421 const unsigned int viewHeight{static_cast<unsigned int>(viewRect.Height())};
423 if (!srcWidth || !srcHeight || !renderWidth || !renderHeight || !viewWidth || !viewHeight)
424 return false;
426 const unsigned int orientation{appPlayer->GetOrientation()};
427 const bool rotated{orientation == 90 || orientation == 270};
429 // FIXME: the renderer sets the scissors to the size of the screen (provided by graphiccontext),
430 // limiting the max size of thumbs (for example 4k video played on 1024x768 screen)
432 // The advanced setting defines the max size of the largest dimension (depends on orientation)
433 const unsigned int maxThumbDim{
434 CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->m_imageRes};
435 unsigned int width{}, height{};
437 if (!rotated)
439 if (aspectRatio >= 1.0f)
441 width = std::min({maxThumbDim, viewWidth, renderWidth, srcWidth});
442 height = static_cast<unsigned int>(width / aspectRatio);
444 else
446 height = std::min({maxThumbDim, viewHeight, renderHeight, srcHeight});
447 width = static_cast<unsigned int>(height * aspectRatio);
450 else
452 // rotation is applied during rendering, switching source width and height
453 if (aspectRatio >= 1.0f)
455 height = std::min({maxThumbDim, viewHeight, renderHeight, srcWidth});
456 width = static_cast<unsigned int>(height / aspectRatio);
458 else
460 width = std::min({maxThumbDim, viewWidth, renderWidth, srcHeight});
461 height = static_cast<unsigned int>(width * aspectRatio);
465 uint8_t *pixels = (uint8_t*)malloc(height * width * 4);
466 unsigned int captureId = appPlayer->RenderCaptureAlloc();
468 appPlayer->RenderCapture(captureId, width, height, CAPTUREFLAG_IMMEDIATELY);
469 bool hasImage = appPlayer->RenderCaptureGetPixels(captureId, 1000, pixels, height * width * 4);
471 if (hasImage)
473 const std::shared_ptr<CProfileManager> profileManager = CServiceBroker::GetSettingsComponent()->GetProfileManager();
475 auto crc = Crc32::ComputeFromLowerCase(g_application.CurrentFile());
476 bookmark.thumbNailImage =
477 StringUtils::Format("{:08x}_{}.jpg", crc, (int)bookmark.timeInSeconds);
478 bookmark.thumbNailImage = URIUtils::AddFileToFolder(profileManager->GetBookmarksThumbFolder(), bookmark.thumbNailImage);
480 if (!CPicture::CreateThumbnailFromSurface(pixels, width, height, width * 4,
481 bookmark.thumbNailImage))
483 bookmark.thumbNailImage.clear();
485 else
486 CLog::Log(LOGERROR,"CGUIDialogVideoBookmarks: failed to create thumbnail");
488 appPlayer->RenderCaptureRelease(captureId);
490 else
491 CLog::Log(LOGERROR,"CGUIDialogVideoBookmarks: failed to create thumbnail 2");
493 free(pixels);
495 CVideoDatabase videoDatabase;
496 if (!videoDatabase.Open())
497 return false;
499 if (tag)
500 videoDatabase.AddBookMarkForEpisode(*tag, bookmark);
501 else
503 const std::string path{g_application.CurrentFileItem().GetDynPath()};
504 videoDatabase.AddBookMarkToFile(path, bookmark, CBookmark::STANDARD);
506 videoDatabase.Close();
507 return true;
510 void CGUIDialogVideoBookmarks::OnWindowLoaded()
512 CGUIDialog::OnWindowLoaded();
513 m_viewControl.Reset();
514 m_viewControl.SetParentWindow(GetID());
515 m_viewControl.AddView(GetControl(CONTROL_THUMBS));
516 m_vecItems->Clear();
519 void CGUIDialogVideoBookmarks::OnWindowUnload()
521 m_vecItems->Clear();
522 CGUIDialog::OnWindowUnload();
523 m_viewControl.Reset();
526 CGUIControl *CGUIDialogVideoBookmarks::GetFirstFocusableControl(int id)
528 if (m_viewControl.HasControl(id))
529 id = m_viewControl.GetCurrentControl();
530 return CGUIWindow::GetFirstFocusableControl(id);
533 bool CGUIDialogVideoBookmarks::AddEpisodeBookmark()
535 std::vector<CVideoInfoTag> episodes;
536 CVideoDatabase videoDatabase;
537 if (!videoDatabase.Open())
538 return false;
540 videoDatabase.GetEpisodesByFile(g_application.CurrentFile(), episodes);
541 videoDatabase.Close();
542 if (!episodes.empty())
544 CContextButtons choices;
545 for (unsigned int i=0; i < episodes.size(); ++i)
547 std::string strButton =
548 StringUtils::Format("{} {}, {} {}", g_localizeStrings.Get(20373), episodes[i].m_iSeason,
549 g_localizeStrings.Get(20359), episodes[i].m_iEpisode);
550 choices.Add(i, strButton);
553 int pressed = CGUIDialogContextMenu::ShowAndGetChoice(choices);
554 if (pressed >= 0)
556 AddBookmark(&episodes[pressed]);
557 return true;
560 return false;
565 bool CGUIDialogVideoBookmarks::OnAddBookmark()
567 if (!IsVideo(g_application.CurrentFileItem()))
568 return false;
570 if (CGUIDialogVideoBookmarks::AddBookmark())
572 CServiceBroker::GetGUI()->GetWindowManager().SendMessage(GUI_MSG_REFRESH_LIST, 0, WINDOW_DIALOG_VIDEO_BOOKMARKS);
573 CGUIDialogKaiToast::QueueNotification(CGUIDialogKaiToast::Info,
574 g_localizeStrings.Get(298), // "Bookmarks"
575 g_localizeStrings.Get(21362));// "Bookmark created"
576 return true;
578 return false;
581 bool CGUIDialogVideoBookmarks::OnAddEpisodeBookmark()
583 bool bReturn = false;
584 if (g_application.CurrentFileItem().HasVideoInfoTag() && g_application.CurrentFileItem().GetVideoInfoTag()->m_iEpisode > -1)
586 CVideoDatabase videoDatabase;
587 if (!videoDatabase.Open())
588 return bReturn;
589 std::vector<CVideoInfoTag> episodes;
590 videoDatabase.GetEpisodesByFile(g_application.CurrentFile(),episodes);
591 if (episodes.size() > 1)
593 bReturn = CGUIDialogVideoBookmarks::AddEpisodeBookmark();
594 if(bReturn)
596 CServiceBroker::GetGUI()->GetWindowManager().SendMessage(GUI_MSG_REFRESH_LIST, 0, WINDOW_DIALOG_VIDEO_BOOKMARKS);
597 CGUIDialogKaiToast::QueueNotification(CGUIDialogKaiToast::Info,
598 g_localizeStrings.Get(298), // "Bookmarks"
599 g_localizeStrings.Get(21363));// "Episode Bookmark created"
603 videoDatabase.Close();
605 return bReturn;