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.
9 #include "GUIDialogVideoBookmarks.h"
12 #include "ServiceBroker.h"
13 #include "TextureCache.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 "input/actions/Action.h"
24 #include "input/actions/ActionIDs.h"
25 #include "messaging/ApplicationMessenger.h"
26 #include "pictures/Picture.h"
27 #include "profiles/ProfileManager.h"
28 #include "settings/AdvancedSettings.h"
29 #include "settings/Settings.h"
30 #include "settings/SettingsComponent.h"
31 #include "utils/Crc32.h"
32 #include "utils/FileUtils.h"
33 #include "utils/StringUtils.h"
34 #include "utils/URIUtils.h"
35 #include "utils/Variant.h"
36 #include "utils/log.h"
37 #include "video/VideoDatabase.h"
38 #include "video/VideoThumbLoader.h"
39 #include "view/ViewState.h"
45 #define BOOKMARK_THUMB_WIDTH CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->m_imageRes
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 CGUIDialogVideoBookmarks::CGUIDialogVideoBookmarks()
54 : CGUIDialog(WINDOW_DIALOG_VIDEO_BOOKMARKS
, "VideoOSDBookmarks.xml"),
55 CJobQueue(false, 1, CJob::PRIORITY_NORMAL
)
57 m_vecItems
= new CFileItemList
;
58 m_loadType
= LOAD_EVERY_TIME
;
62 CGUIDialogVideoBookmarks::~CGUIDialogVideoBookmarks()
67 bool CGUIDialogVideoBookmarks::OnMessage(CGUIMessage
& message
)
69 switch ( message
.GetMessage() )
71 case GUI_MSG_WINDOW_DEINIT
:
73 CUtil::DeleteVideoDatabaseDirectoryCache();
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())
86 CGUIWindow::OnMessage(message
);
94 int iControl
= message
.GetSenderId();
95 if (iControl
== CONTROL_ADD_BOOKMARK
)
100 else if (iControl
== CONTROL_CLEAR_BOOKMARKS
)
104 else if (iControl
== CONTROL_ADD_EPISODE_BOOKMARK
)
106 AddEpisodeBookmark();
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
)
117 else if (iAction
== ACTION_SELECT_ITEM
|| iAction
== ACTION_MOUSE_LEFT_CLICK
)
124 case GUI_MSG_SETFOCUS
:
126 if (m_viewControl
.HasControl(message
.GetControlId()) && m_viewControl
.GetCurrentControl() != message
.GetControlId())
128 m_viewControl
.SetFocused();
133 case GUI_MSG_REFRESH_LIST
:
135 switch (message
.GetParam1())
141 UpdateItem(message
.GetParam2());
150 return CGUIDialog::OnMessage(message
);
153 bool CGUIDialogVideoBookmarks::OnAction(const CAction
&action
)
155 switch(action
.GetID())
157 case ACTION_CONTEXT_MENU
:
158 case ACTION_MOUSE_RIGHT_CLICK
:
160 OnPopupMenu(m_viewControl
.GetSelectedItem());
164 return CGUIDialog::OnAction(action
);
168 void CGUIDialogVideoBookmarks::OnPopupMenu(int item
)
170 if (item
< 0 || item
>= (int) m_bookmarks
.size())
173 // highlight the item
174 (*m_vecItems
)[item
]->Select(true);
176 CContextButtons choices
;
177 choices
.Add(1, (m_bookmarks
[item
].type
== CBookmark::EPISODE
? 20405 : 20404)); // "Remove episode bookmark" or "Remove bookmark"
179 int button
= CGUIDialogContextMenu::ShowAndGetChoice(choices
);
181 // unhighlight the item
182 (*m_vecItems
)[item
]->Select(false);
188 void CGUIDialogVideoBookmarks::Delete(int item
)
190 if ( item
>=0 && (unsigned)item
< m_bookmarks
.size() )
192 CVideoDatabase videoDatabase
;
193 videoDatabase
.Open();
194 std::string
path(g_application
.CurrentFile());
195 if (g_application
.CurrentFileItem().HasProperty("original_listitem_url") &&
196 !URIUtils::IsVideoDb(g_application
.CurrentFileItem().GetProperty("original_listitem_url").asString()))
197 path
= g_application
.CurrentFileItem().GetProperty("original_listitem_url").asString();
198 videoDatabase
.ClearBookMarkOfFile(path
, m_bookmarks
[item
], m_bookmarks
[item
].type
);
199 videoDatabase
.Close();
200 CUtil::DeleteVideoDatabaseDirectoryCache();
205 void CGUIDialogVideoBookmarks::UpdateItem(unsigned int chapterIdx
)
207 std::unique_lock
<CCriticalSection
> lock(m_refreshSection
);
210 for (const auto& item
: *m_vecItems
)
212 if (chapterIdx
== item
->GetProperty("chapter").asInteger())
217 if (itemPos
< m_vecItems
->Size())
219 std::string time
= StringUtils::Format("chapter://{}/{}", m_filePath
, chapterIdx
);
220 std::string cachefile
= CServiceBroker::GetTextureCache()->GetCachedPath(
221 CServiceBroker::GetTextureCache()->GetCacheFile(time
) + ".jpg");
222 if (CFileUtils::Exists(cachefile
))
224 (*m_vecItems
)[itemPos
]->SetArt("thumb", cachefile
);
229 void CGUIDialogVideoBookmarks::OnRefreshList()
232 std::vector
<CFileItemPtr
> items
;
234 // open the d/b and retrieve the bookmarks for the current movie
235 m_filePath
= g_application
.CurrentFile();
236 if (g_application
.CurrentFileItem().HasProperty("original_listitem_url") &&
237 !URIUtils::IsVideoDb(g_application
.CurrentFileItem().GetProperty("original_listitem_url").asString()))
238 m_filePath
= g_application
.CurrentFileItem().GetProperty("original_listitem_url").asString();
240 CVideoDatabase videoDatabase
;
241 videoDatabase
.Open();
242 videoDatabase
.GetBookMarksForFile(m_filePath
, m_bookmarks
);
243 videoDatabase
.GetBookMarksForFile(m_filePath
, m_bookmarks
, CBookmark::EPISODE
, true);
244 videoDatabase
.Close();
246 std::unique_lock
<CCriticalSection
> lock(m_refreshSection
);
249 // cycle through each stored bookmark and add it to our list control
250 for (unsigned int i
= 0; i
< m_bookmarks
.size(); ++i
)
252 std::string bookmarkTime
;
253 if (m_bookmarks
[i
].type
== CBookmark::EPISODE
)
254 bookmarkTime
= StringUtils::Format("{} {} {} {}", g_localizeStrings
.Get(20373),
255 m_bookmarks
[i
].seasonNumber
, g_localizeStrings
.Get(20359),
256 m_bookmarks
[i
].episodeNumber
);
258 bookmarkTime
= StringUtils::SecondsToTimeString((long)m_bookmarks
[i
].timeInSeconds
, TIME_FORMAT_HH_MM_SS
);
260 CFileItemPtr
item(new CFileItem(StringUtils::Format(g_localizeStrings
.Get(299), i
+ 1)));
261 item
->SetLabel2(bookmarkTime
);
262 item
->SetArt("thumb", m_bookmarks
[i
].thumbNailImage
);
263 item
->SetProperty("resumepoint", m_bookmarks
[i
].timeInSeconds
);
264 item
->SetProperty("playerstate", m_bookmarks
[i
].playerState
);
265 item
->SetProperty("isbookmark", "true");
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 std::string chapterPath
= StringUtils::Format("chapter://{}/{}", m_filePath
, i
);
289 std::string cachefile
= CServiceBroker::GetTextureCache()->GetCachedPath(
290 CServiceBroker::GetTextureCache()->GetCacheFile(chapterPath
) + ".jpg");
291 if (CFileUtils::Exists(cachefile
))
292 item
->SetArt("thumb", cachefile
);
293 else if (i
> m_jobsStarted
&& CServiceBroker::GetSettingsComponent()->GetSettings()->GetBool(CSettings::SETTING_MYVIDEOS_EXTRACTCHAPTERTHUMBS
))
295 CFileItem
item(m_filePath
, false);
296 CJob
* job
= new CThumbExtractor(item
, m_filePath
, true, chapterPath
, pos
* 1000, false);
298 m_mapJobsChapter
[job
] = i
;
302 item
->SetProperty("chapter", i
);
303 item
->SetProperty("resumepoint", static_cast<double>(pos
));
304 item
->SetProperty("ischapter", "true");
305 items
.push_back(item
);
308 // sort items by resume point
309 std::sort(items
.begin(), items
.end(), [](const CFileItemPtr
&item1
, const CFileItemPtr
&item2
) {
310 return item1
->GetProperty("resumepoint").asDouble() < item2
->GetProperty("resumepoint").asDouble();
313 // add items to file list and mark the proper item as selected if the current playtime is above
314 int selectedItemIndex
= 0;
315 double playTime
= g_application
.GetTime();
316 for (auto& item
: items
)
318 m_vecItems
->Add(item
);
319 if (playTime
>= item
->GetProperty("resumepoint").asDouble())
320 selectedItemIndex
= m_vecItems
->Size() - 1;
323 m_viewControl
.SetItems(*m_vecItems
);
324 m_viewControl
.SetSelectedItem(selectedItemIndex
);
327 void CGUIDialogVideoBookmarks::Update()
329 CVideoDatabase videoDatabase
;
330 videoDatabase
.Open();
332 if (g_application
.CurrentFileItem().HasVideoInfoTag() && g_application
.CurrentFileItem().GetVideoInfoTag()->m_iEpisode
> -1)
334 std::vector
<CVideoInfoTag
> episodes
;
335 videoDatabase
.GetEpisodesByFile(g_application
.CurrentFile(),episodes
);
336 if (episodes
.size() > 1)
338 CONTROL_ENABLE(CONTROL_ADD_EPISODE_BOOKMARK
);
342 CONTROL_DISABLE(CONTROL_ADD_EPISODE_BOOKMARK
);
347 CONTROL_DISABLE(CONTROL_ADD_EPISODE_BOOKMARK
);
351 m_viewControl
.SetCurrentView(DEFAULT_VIEW_ICONS
);
353 // empty the list ready for population
358 videoDatabase
.Close();
361 void CGUIDialogVideoBookmarks::Clear()
363 m_viewControl
.Clear();
367 void CGUIDialogVideoBookmarks::GotoBookmark(int item
)
369 auto& components
= CServiceBroker::GetAppComponents();
370 const auto appPlayer
= components
.GetComponent
<CApplicationPlayer
>();
371 if (item
< 0 || item
>= m_vecItems
->Size() || !appPlayer
->HasPlayer())
374 CFileItemPtr fileItem
= m_vecItems
->Get(item
);
375 int chapter
= static_cast<int>(fileItem
->GetProperty("chapter").asInteger());
378 appPlayer
->SetPlayerState(fileItem
->GetProperty("playerstate").asString());
379 g_application
.SeekTime(fileItem
->GetProperty("resumepoint").asDouble());
382 appPlayer
->SeekChapter(chapter
);
387 void CGUIDialogVideoBookmarks::ClearBookmarks()
389 CVideoDatabase videoDatabase
;
390 videoDatabase
.Open();
391 std::string path
= g_application
.CurrentFile();
392 if (g_application
.CurrentFileItem().HasProperty("original_listitem_url") &&
393 !URIUtils::IsVideoDb(g_application
.CurrentFileItem().GetProperty("original_listitem_url").asString()))
394 path
= g_application
.CurrentFileItem().GetProperty("original_listitem_url").asString();
395 videoDatabase
.ClearBookMarksOfFile(path
, CBookmark::STANDARD
);
396 videoDatabase
.ClearBookMarksOfFile(path
, CBookmark::RESUME
);
397 videoDatabase
.ClearBookMarksOfFile(path
, CBookmark::EPISODE
);
398 videoDatabase
.Close();
402 bool CGUIDialogVideoBookmarks::AddBookmark(CVideoInfoTag
* tag
)
404 CVideoDatabase videoDatabase
;
406 bookmark
.timeInSeconds
= (int)g_application
.GetTime();
407 bookmark
.totalTimeInSeconds
= (int)g_application
.GetTotalTime();
409 auto& components
= CServiceBroker::GetAppComponents();
410 const auto appPlayer
= components
.GetComponent
<CApplicationPlayer
>();
412 if (appPlayer
->HasPlayer())
413 bookmark
.playerState
= appPlayer
->GetPlayerState();
415 bookmark
.playerState
.clear();
417 bookmark
.player
= g_application
.GetCurrentPlayer();
419 // create the thumbnail image
420 float aspectRatio
= appPlayer
->GetRenderAspectRatio();
421 int width
= BOOKMARK_THUMB_WIDTH
;
422 int height
= (int)(BOOKMARK_THUMB_WIDTH
/ aspectRatio
);
423 if (height
> (int)BOOKMARK_THUMB_WIDTH
)
425 height
= BOOKMARK_THUMB_WIDTH
;
426 width
= (int)(BOOKMARK_THUMB_WIDTH
* aspectRatio
);
430 uint8_t *pixels
= (uint8_t*)malloc(height
* width
* 4);
431 unsigned int captureId
= appPlayer
->RenderCaptureAlloc();
433 appPlayer
->RenderCapture(captureId
, width
, height
, CAPTUREFLAG_IMMEDIATELY
);
434 bool hasImage
= appPlayer
->RenderCaptureGetPixels(captureId
, 1000, pixels
, height
* width
* 4);
438 const std::shared_ptr
<CProfileManager
> profileManager
= CServiceBroker::GetSettingsComponent()->GetProfileManager();
440 auto crc
= Crc32::ComputeFromLowerCase(g_application
.CurrentFile());
441 bookmark
.thumbNailImage
=
442 StringUtils::Format("{:08x}_{}.jpg", crc
, (int)bookmark
.timeInSeconds
);
443 bookmark
.thumbNailImage
= URIUtils::AddFileToFolder(profileManager
->GetBookmarksThumbFolder(), bookmark
.thumbNailImage
);
445 if (!CPicture::CreateThumbnailFromSurface(pixels
, width
, height
, width
* 4,
446 bookmark
.thumbNailImage
))
448 bookmark
.thumbNailImage
.clear();
451 CLog::Log(LOGERROR
,"CGUIDialogVideoBookmarks: failed to create thumbnail");
453 appPlayer
->RenderCaptureRelease(captureId
);
456 CLog::Log(LOGERROR
,"CGUIDialogVideoBookmarks: failed to create thumbnail 2");
460 videoDatabase
.Open();
462 videoDatabase
.AddBookMarkForEpisode(*tag
, bookmark
);
465 std::string path
= g_application
.CurrentFile();
466 if (g_application
.CurrentFileItem().HasProperty("original_listitem_url") &&
467 !URIUtils::IsVideoDb(g_application
.CurrentFileItem().GetProperty("original_listitem_url").asString()))
468 path
= g_application
.CurrentFileItem().GetProperty("original_listitem_url").asString();
469 videoDatabase
.AddBookMarkToFile(path
, bookmark
, CBookmark::STANDARD
);
471 videoDatabase
.Close();
475 void CGUIDialogVideoBookmarks::OnWindowLoaded()
477 CGUIDialog::OnWindowLoaded();
478 m_viewControl
.Reset();
479 m_viewControl
.SetParentWindow(GetID());
480 m_viewControl
.AddView(GetControl(CONTROL_THUMBS
));
482 m_mapJobsChapter
.clear();
486 void CGUIDialogVideoBookmarks::OnWindowUnload()
488 //stop running thumb extraction jobs
490 m_mapJobsChapter
.clear();
492 CGUIDialog::OnWindowUnload();
493 m_viewControl
.Reset();
496 CGUIControl
*CGUIDialogVideoBookmarks::GetFirstFocusableControl(int id
)
498 if (m_viewControl
.HasControl(id
))
499 id
= m_viewControl
.GetCurrentControl();
500 return CGUIWindow::GetFirstFocusableControl(id
);
503 bool CGUIDialogVideoBookmarks::AddEpisodeBookmark()
505 std::vector
<CVideoInfoTag
> episodes
;
506 CVideoDatabase videoDatabase
;
507 videoDatabase
.Open();
508 videoDatabase
.GetEpisodesByFile(g_application
.CurrentFile(), episodes
);
509 videoDatabase
.Close();
510 if (!episodes
.empty())
512 CContextButtons choices
;
513 for (unsigned int i
=0; i
< episodes
.size(); ++i
)
515 std::string strButton
=
516 StringUtils::Format("{} {}, {} {}", g_localizeStrings
.Get(20373), episodes
[i
].m_iSeason
,
517 g_localizeStrings
.Get(20359), episodes
[i
].m_iEpisode
);
518 choices
.Add(i
, strButton
);
521 int pressed
= CGUIDialogContextMenu::ShowAndGetChoice(choices
);
524 AddBookmark(&episodes
[pressed
]);
533 bool CGUIDialogVideoBookmarks::OnAddBookmark()
535 if (!g_application
.CurrentFileItem().IsVideo())
538 if (CGUIDialogVideoBookmarks::AddBookmark())
540 CServiceBroker::GetGUI()->GetWindowManager().SendMessage(GUI_MSG_REFRESH_LIST
, 0, WINDOW_DIALOG_VIDEO_BOOKMARKS
);
541 CGUIDialogKaiToast::QueueNotification(CGUIDialogKaiToast::Info
,
542 g_localizeStrings
.Get(298), // "Bookmarks"
543 g_localizeStrings
.Get(21362));// "Bookmark created"
549 bool CGUIDialogVideoBookmarks::OnAddEpisodeBookmark()
551 bool bReturn
= false;
552 if (g_application
.CurrentFileItem().HasVideoInfoTag() && g_application
.CurrentFileItem().GetVideoInfoTag()->m_iEpisode
> -1)
554 CVideoDatabase videoDatabase
;
555 videoDatabase
.Open();
556 std::vector
<CVideoInfoTag
> episodes
;
557 videoDatabase
.GetEpisodesByFile(g_application
.CurrentFile(),episodes
);
558 if (episodes
.size() > 1)
560 bReturn
= CGUIDialogVideoBookmarks::AddEpisodeBookmark();
563 CServiceBroker::GetGUI()->GetWindowManager().SendMessage(GUI_MSG_REFRESH_LIST
, 0, WINDOW_DIALOG_VIDEO_BOOKMARKS
);
564 CGUIDialogKaiToast::QueueNotification(CGUIDialogKaiToast::Info
,
565 g_localizeStrings
.Get(298), // "Bookmarks"
566 g_localizeStrings
.Get(21363));// "Episode Bookmark created"
570 videoDatabase
.Close();
575 void CGUIDialogVideoBookmarks::OnJobComplete(unsigned int jobID
,
576 bool success
, CJob
* job
)
578 if (success
&& IsActive())
580 MAPJOBSCHAPS::iterator iter
= m_mapJobsChapter
.find(job
);
581 if (iter
!= m_mapJobsChapter
.end())
583 unsigned int chapterIdx
= (*iter
).second
;
584 CGUIMessage
m(GUI_MSG_REFRESH_LIST
, GetID(), 0, 1, chapterIdx
);
585 CServiceBroker::GetAppMessenger()->SendGUIMessage(m
);
586 m_mapJobsChapter
.erase(iter
);
589 CJobQueue::OnJobComplete(jobID
, success
, job
);