1 /*
2 * Copyright (C) 2005-2018 Team Kodi
3 * This file is part of Kodi - https://kodi.tv
4 *
5 * SPDX-License-Identifier: GPL-2.0-or-later
6 * See LICENSES/README.md for more information.
7 */
8
9 #include "GUIWindowVideoBase.h"
10
11 #include "Application.h"
12 #include "Autorun.h"
13 #include "GUIPassword.h"
14 #include "GUIUserMessages.h"
15 #include "PartyModeManager.h"
16 #include "PlayListPlayer.h"
17 #include "ServiceBroker.h"
18 #include "TextureDatabase.h"
19 #include "URL.h"
20 #include "Util.h"
21 #include "addons/gui/GUIDialogAddonInfo.h"
22 #include "cores/playercorefactory/PlayerCoreFactory.h"
23 #include "dialogs/GUIDialogProgress.h"
24 #include "dialogs/GUIDialogSelect.h"
25 #include "dialogs/GUIDialogSmartPlaylistEditor.h"
26 #include "dialogs/GUIDialogYesNo.h"
27 #include "filesystem/Directory.h"
28 #include "filesystem/StackDirectory.h"
29 #include "filesystem/VideoDatabaseDirectory.h"
30 #include "guilib/GUIComponent.h"
31 #include "guilib/GUIKeyboardFactory.h"
32 #include "guilib/GUIWindowManager.h"
33 #include "guilib/LocalizeStrings.h"
34 #include "input/Key.h"
35 #include "messaging/helpers/DialogOKHelper.h"
36 #include "music/dialogs/GUIDialogMusicInfo.h"
37 #include "playlists/PlayList.h"
38 #include "playlists/PlayListFactory.h"
39 #include "profiles/ProfileManager.h"
40 #include "settings/AdvancedSettings.h"
41 #include "settings/MediaSettings.h"
42 #include "settings/Settings.h"
43 #include "settings/SettingsComponent.h"
44 #include "settings/SettingUtils.h"
45 #include "settings/dialogs/GUIDialogContentSettings.h"
46 #include "storage/MediaManager.h"
47 #include "utils/FileExtensionProvider.h"
48 #include "utils/FileUtils.h"
49 #include "utils/GroupUtils.h"
50 #include "utils/StringUtils.h"
51 #include "utils/URIUtils.h"
52 #include "utils/Variant.h"
53 #include "utils/log.h"
54 #include "video/VideoInfoDownloader.h"
55 #include "video/VideoInfoScanner.h"
56 #include "video/VideoLibraryQueue.h"
57 #include "video/dialogs/GUIDialogVideoInfo.h"
58 #include "view/GUIViewState.h"
59
60 using namespace XFILE;
61 using namespace PLAYLIST;
62 using namespace VIDEODATABASEDIRECTORY;
63 using namespace VIDEO;
64 using namespace ADDON;
65 using namespace PVR;
66 using namespace KODI::MESSAGING;
67
68 #define CONTROL_BTNVIEWASICONS 2
69 #define CONTROL_BTNSORTBY 3
70 #define CONTROL_BTNSORTASC 4
71 #define CONTROL_LABELFILES 12
72
73 #define CONTROL_PLAY_DVD 6
74
75 #define PROPERTY_GROUP_BY "group.by"
76 #define PROPERTY_GROUP_MIXED "group.mixed"
77
78 static constexpr int SETTING_AUTOPLAYNEXT_MUSICVIDEOS = 0;
79 static constexpr int SETTING_AUTOPLAYNEXT_EPISODES = 2;
80 static constexpr int SETTING_AUTOPLAYNEXT_MOVIES = 3;
81 static constexpr int SETTING_AUTOPLAYNEXT_UNCATEGORIZED = 4;
82
CGUIWindowVideoBase(int id,const std::string & xmlFile)83 CGUIWindowVideoBase::CGUIWindowVideoBase(int id, const std::string &xmlFile)
84 : CGUIMediaWindow(id, xmlFile.c_str())
85 {
86 m_thumbLoader.SetObserver(this);
87 m_stackingAvailable = true;
88 m_dlgProgress = NULL;
89 }
90
91 CGUIWindowVideoBase::~CGUIWindowVideoBase() = default;
92
OnAction(const CAction & action)93 bool CGUIWindowVideoBase::OnAction(const CAction &action)
94 {
95 if (action.GetID() == ACTION_SCAN_ITEM)
96 return OnContextButton(m_viewControl.GetSelectedItem(),CONTEXT_BUTTON_SCAN);
97 else if (action.GetID() == ACTION_SHOW_PLAYLIST)
98 {
99 if (CServiceBroker::GetPlaylistPlayer().GetCurrentPlaylist() == PLAYLIST_VIDEO ||
100 CServiceBroker::GetPlaylistPlayer().GetPlaylist(PLAYLIST_VIDEO).size() > 0)
101 {
102 CServiceBroker::GetGUI()->GetWindowManager().ActivateWindow(WINDOW_VIDEO_PLAYLIST);
103 return true;
104 }
105 }
106
107 return CGUIMediaWindow::OnAction(action);
108 }
109
OnMessage(CGUIMessage & message)110 bool CGUIWindowVideoBase::OnMessage(CGUIMessage& message)
111 {
112 switch ( message.GetMessage() )
113 {
114 case GUI_MSG_WINDOW_DEINIT:
115 if (m_thumbLoader.IsLoading())
116 m_thumbLoader.StopThread();
117 m_database.Close();
118 break;
119
120 case GUI_MSG_WINDOW_INIT:
121 {
122 m_database.Open();
123 m_dlgProgress = CServiceBroker::GetGUI()->GetWindowManager().GetWindow<CGUIDialogProgress>(WINDOW_DIALOG_PROGRESS);
124 return CGUIMediaWindow::OnMessage(message);
125 }
126 break;
127
128 case GUI_MSG_CLICKED:
129 {
130 int iControl = message.GetSenderId();
131 #if defined(HAS_DVD_DRIVE)
132 if (iControl == CONTROL_PLAY_DVD)
133 {
134 // play movie...
135 MEDIA_DETECT::CAutorun::PlayDiscAskResume(
136 CServiceBroker::GetMediaManager().TranslateDevicePath(""));
137 }
138 else
139 #endif
140 if (m_viewControl.HasControl(iControl)) // list/thumb control
141 {
142 // get selected item
143 int iItem = m_viewControl.GetSelectedItem();
144 int iAction = message.GetParam1();
145
146 // iItem is checked for validity inside these routines
147 if (iAction == ACTION_QUEUE_ITEM || iAction == ACTION_MOUSE_MIDDLE_CLICK)
148 {
149 OnQueueItem(iItem);
150 return true;
151 }
152 else if (iAction == ACTION_QUEUE_ITEM_NEXT)
153 {
154 OnQueueItem(iItem, true);
155 return true;
156 }
157 else if (iAction == ACTION_SHOW_INFO)
158 {
159 return OnItemInfo(iItem);
160 }
161 else if (iAction == ACTION_PLAYER_PLAY)
162 {
163 // if playback is paused or playback speed != 1, return
164 if (g_application.GetAppPlayer().IsPlayingVideo())
165 {
166 if (g_application.GetAppPlayer().IsPausedPlayback())
167 return false;
168 if (g_application.GetAppPlayer().GetPlaySpeed() != 1)
169 return false;
170 }
171
172 // not playing video, or playback speed == 1
173 return OnResumeItem(iItem);
174 }
175 else if (iAction == ACTION_DELETE_ITEM)
176 {
177 const std::shared_ptr<CProfileManager> profileManager = CServiceBroker::GetSettingsComponent()->GetProfileManager();
178
179 // is delete allowed?
180 if (profileManager->GetCurrentProfile().canWriteDatabases())
181 {
182 // must be at the title window
183 if (GetID() == WINDOW_VIDEO_NAV)
184 OnDeleteItem(iItem);
185
186 // or be at the video playlists location
187 else if (m_vecItems->IsPath("special://videoplaylists/"))
188 OnDeleteItem(iItem);
189 else
190 return false;
191
192 return true;
193 }
194 }
195 }
196 }
197 break;
198 case GUI_MSG_SEARCH:
199 OnSearch();
200 break;
201 }
202 return CGUIMediaWindow::OnMessage(message);
203 }
204
OnItemInfo(const CFileItem & fileItem,ADDON::ScraperPtr & scraper)205 void CGUIWindowVideoBase::OnItemInfo(const CFileItem& fileItem, ADDON::ScraperPtr& scraper)
206 {
207 if (fileItem.IsParentFolder() || fileItem.m_bIsShareOrDrive || fileItem.IsPath("add") ||
208 (fileItem.IsPlayList() && !URIUtils::HasExtension(fileItem.GetDynPath(), ".strm")))
209 return;
210
211 CFileItem item(fileItem);
212 bool fromDB = false;
213 if ((item.IsVideoDb() && item.HasVideoInfoTag()) ||
214 (item.HasVideoInfoTag() && item.GetVideoInfoTag()->m_iDbId != -1))
215 {
216 if (item.GetVideoInfoTag()->m_type == MediaTypeSeason)
217 { // clear out the art - we're really grabbing the info on the show here
218 item.ClearArt();
219 item.GetVideoInfoTag()->m_iDbId = item.GetVideoInfoTag()->m_iIdShow;
220 }
221 item.SetPath(item.GetVideoInfoTag()->GetPath());
222 fromDB = true;
223 }
224 else
225 {
226 if (item.m_bIsFolder && scraper && scraper->Content() != CONTENT_TVSHOWS)
227 {
228 CFileItemList items;
229 CDirectory::GetDirectory(item.GetPath(), items, CServiceBroker::GetFileExtensionProvider().GetVideoExtensions(),
230 DIR_FLAG_DEFAULTS);
231
232 // Check for cases 1_dir/1_dir/.../file (e.g. by packages where have a extra folder)
233 while (items.Size() == 1 && items[0]->m_bIsFolder)
234 {
235 const std::string path = items[0]->GetPath();
236 items.Clear();
237 CDirectory::GetDirectory(path, items,
238 CServiceBroker::GetFileExtensionProvider().GetVideoExtensions(),
239 DIR_FLAG_DEFAULTS);
240 }
241
242 items.Stack();
243
244 // check for media files
245 bool bFoundFile(false);
246 for (int i = 0; i < items.Size(); ++i)
247 {
248 CFileItemPtr item2 = items[i];
249
250 if (item2->IsVideo() && !item2->IsPlayList() &&
251 !CUtil::ExcludeFileOrFolder(item2->GetPath(), CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->m_moviesExcludeFromScanRegExps))
252 {
253 item.SetPath(item2->GetPath());
254 item.m_bIsFolder = false;
255 bFoundFile = true;
256 break;
257 }
258 }
259
260 // no video file in this folder
261 if (!bFoundFile)
262 {
263 HELPERS::ShowOKDialogText(CVariant{13346}, CVariant{20349});
264 return;
265 }
266 }
267 }
268
269 // we need to also request any thumbs be applied to the folder item
270 if (fileItem.m_bIsFolder)
271 item.SetProperty("set_folder_thumb", fileItem.GetPath());
272
273 bool modified = ShowIMDB(CFileItemPtr(new CFileItem(item)), scraper, fromDB);
274 if (modified &&
275 (CServiceBroker::GetGUI()->GetWindowManager().GetActiveWindow() == WINDOW_VIDEO_NAV)) // since we can be called from the music library we need this check
276 {
277 int itemNumber = m_viewControl.GetSelectedItem();
278 Refresh();
279 m_viewControl.SetSelectedItem(itemNumber);
280 }
281 }
282
283 // ShowIMDB is called as follows:
284 // 1. To lookup info on a file.
285 // 2. To lookup info on a folder (which may or may not contain a file)
286 // 3. To lookup info just for fun (no file or folder related)
287
288 // We just need the item object for this.
289 // A "blank" item object is sent for 3.
290 // If a folder is sent, currently it sets strFolder and bFolder
291 // this is only used for setting the folder thumb, however.
292
293 // Steps should be:
294
295 // 1. Check database to see if we have this information already
296 // 2. Else, check for a nfoFile to get the URL
297 // 3. Run a loop to check for refresh
298 // 4. If no URL is present do a search to get the URL
299 // 4. Once we have the URL, download the details
300 // 5. Once we have the details, add to the database if necessary (case 1,2)
301 // and show the information.
302 // 6. Check for a refresh, and if so, go to 3.
303
ShowIMDB(CFileItemPtr item,const ScraperPtr & info2,bool fromDB)304 bool CGUIWindowVideoBase::ShowIMDB(CFileItemPtr item, const ScraperPtr &info2, bool fromDB)
305 {
306 /*
307 CLog::Log(LOGDEBUG,"CGUIWindowVideoBase::ShowIMDB");
308 CLog::Log(LOGDEBUG," strMovie = [%s]", strMovie.c_str());
309 CLog::Log(LOGDEBUG," strFile = [%s]", strFile.c_str());
310 CLog::Log(LOGDEBUG," strFolder = [%s]", strFolder.c_str());
311 CLog::Log(LOGDEBUG," bFolder = [%s]", ((int)bFolder ? "true" : "false"));
312 */
313
314 CGUIDialogProgress* pDlgProgress = CServiceBroker::GetGUI()->GetWindowManager().GetWindow<CGUIDialogProgress>(WINDOW_DIALOG_PROGRESS);
315 CGUIDialogSelect* pDlgSelect = CServiceBroker::GetGUI()->GetWindowManager().GetWindow<CGUIDialogSelect>(WINDOW_DIALOG_SELECT);
316 CGUIDialogVideoInfo* pDlgInfo = CServiceBroker::GetGUI()->GetWindowManager().GetWindow<CGUIDialogVideoInfo>(WINDOW_DIALOG_VIDEO_INFO);
317
318 const ScraperPtr& info(info2); // use this as nfo might change it..
319
320 if (!pDlgProgress) return false;
321 if (!pDlgSelect) return false;
322 if (!pDlgInfo) return false;
323
324 // 1. Check for already downloaded information, and if we have it, display our dialog
325 // Return if no Refresh is needed.
326 bool bHasInfo=false;
327
328 CVideoInfoTag movieDetails;
329 if (info)
330 {
331 m_database.Open(); // since we can be called from the music library
332
333 int dbId = item->HasVideoInfoTag() ? item->GetVideoInfoTag()->m_iDbId : -1;
334 if (info->Content() == CONTENT_MOVIES)
335 {
336 bHasInfo = m_database.GetMovieInfo(item->GetPath(), movieDetails, dbId);
337 }
338 if (info->Content() == CONTENT_TVSHOWS)
339 {
340 if (item->m_bIsFolder)
341 {
342 bHasInfo = m_database.GetTvShowInfo(item->GetPath(), movieDetails, dbId);
343 }
344 else
345 {
346 bHasInfo = m_database.GetEpisodeInfo(item->GetPath(), movieDetails, dbId);
347 if (!bHasInfo)
348 {
349 // !! WORKAROUND !!
350 // As we cannot add an episode to a non-existing tvshow entry, we have to check the parent directory
351 // to see if it`s already in our video database. If it's not yet part of the database we will exit here.
352 // (Ticket #4764)
353 //
354 // NOTE: This will fail for episodes on multipath shares, as the parent path isn't what is stored in the
355 // database. Possible solutions are to store the paths in the db separately and rely on the show
356 // stacking stuff, or to modify GetTvShowId to do support multipath:// shares
357 std::string strParentDirectory;
358 URIUtils::GetParentPath(item->GetPath(), strParentDirectory);
359 if (m_database.GetTvShowId(strParentDirectory) < 0)
360 {
361 CLog::Log(LOGERROR,"%s: could not add episode [%s]. tvshow does not exist yet..", __FUNCTION__, item->GetPath().c_str());
362 return false;
363 }
364 }
365 }
366 }
367 if (info->Content() == CONTENT_MUSICVIDEOS)
368 {
369 bHasInfo = m_database.GetMusicVideoInfo(item->GetPath(), movieDetails);
370 }
371 m_database.Close();
372 }
373 else if(item->HasVideoInfoTag())
374 {
375 bHasInfo = true;
376 movieDetails = *item->GetVideoInfoTag();
377 }
378
379 bool needsRefresh = false;
380 if (bHasInfo)
381 {
382 if (!info || info->Content() == CONTENT_NONE) // disable refresh button
383 item->SetProperty("xxuniqueid", "xx" + movieDetails.GetUniqueID());
384 *item->GetVideoInfoTag() = movieDetails;
385 pDlgInfo->SetMovie(item.get());
386 pDlgInfo->Open();
387 if (pDlgInfo->HasUpdatedUserrating())
388 return true;
389 needsRefresh = pDlgInfo->NeedRefresh();
390 if (!needsRefresh)
391 return pDlgInfo->HasUpdatedThumb();
392 // check if the item in the video info dialog has changed and if so, get the new item
393 else if (pDlgInfo->GetCurrentListItem() != NULL)
394 {
395 item = pDlgInfo->GetCurrentListItem();
396
397 if (item->IsVideoDb() && item->HasVideoInfoTag())
398 item->SetPath(item->GetVideoInfoTag()->GetPath());
399 }
400 }
401
402 const std::shared_ptr<CProfileManager> profileManager = CServiceBroker::GetSettingsComponent()->GetProfileManager();
403
404 // quietly return if Internet lookups are disabled
405 if (!profileManager->GetCurrentProfile().canWriteDatabases() && !g_passwordManager.bMasterUser)
406 return false;
407
408 if (!info)
409 return false;
410
411 if (g_application.IsVideoScanning())
412 {
413 HELPERS::ShowOKDialogText(CVariant{13346}, CVariant{14057});
414 return false;
415 }
416
417 bool listNeedsUpdating = false;
418 // 3. Run a loop so that if we Refresh we re-run this block
419 do
420 {
421 if (!CVideoLibraryQueue::GetInstance().RefreshItemModal(item, needsRefresh, pDlgInfo->RefreshAll()))
422 return listNeedsUpdating;
423
424 // remove directory caches and reload images
425 CUtil::DeleteVideoDatabaseDirectoryCache();
426 CGUIMessage reload(GUI_MSG_NOTIFY_ALL, 0, 0, GUI_MSG_REFRESH_THUMBS);
427 OnMessage(reload);
428
429 pDlgInfo->SetMovie(item.get());
430 pDlgInfo->Open();
431 item->SetArt("thumb", pDlgInfo->GetThumbnail());
432 needsRefresh = pDlgInfo->NeedRefresh();
433 listNeedsUpdating = true;
434 } while (needsRefresh);
435
436 return listNeedsUpdating;
437 }
438
OnQueueItem(int iItem,bool first)439 void CGUIWindowVideoBase::OnQueueItem(int iItem, bool first)
440 {
441 // Determine the proper list to queue this element
442 int playlist = CServiceBroker::GetPlaylistPlayer().GetCurrentPlaylist();
443 if (playlist == PLAYLIST_NONE)
444 playlist = g_application.GetAppPlayer().GetPreferredPlaylist();
445 if (playlist == PLAYLIST_NONE)
446 playlist = PLAYLIST_VIDEO;
447
448 // don't re-queue items from playlist window
449 if ( iItem < 0 || iItem >= m_vecItems->Size() || GetID() == WINDOW_VIDEO_PLAYLIST ) return ;
450
451 // we take a copy so that we can alter the queue state
452 CFileItemPtr item(new CFileItem(*m_vecItems->Get(iItem)));
453 if (item->IsRAR() || item->IsZIP())
454 return;
455
456 // Allow queuing of unqueueable items
457 // when we try to queue them directly
458 if (!item->CanQueue())
459 item->SetCanQueue(true);
460
461 CFileItemList queuedItems;
462 AddItemToPlayList(item, queuedItems);
463 // if party mode, add items but DONT start playing
464 if (g_partyModeManager.IsEnabled(PARTYMODECONTEXT_VIDEO))
465 {
466 g_partyModeManager.AddUserSongs(queuedItems, false);
467 return;
468 }
469
470 if (first && g_application.GetAppPlayer().IsPlaying())
471 CServiceBroker::GetPlaylistPlayer().Insert(playlist, queuedItems, CServiceBroker::GetPlaylistPlayer().GetCurrentSong()+1);
472 else
473 CServiceBroker::GetPlaylistPlayer().Add(playlist, queuedItems);
474 CServiceBroker::GetPlaylistPlayer().SetCurrentPlaylist(playlist);
475 // video does not auto play on queue like music
476 m_viewControl.SetSelectedItem(iItem + 1);
477 }
478
AddItemToPlayList(const CFileItemPtr & pItem,CFileItemList & queuedItems)479 void CGUIWindowVideoBase::AddItemToPlayList(const CFileItemPtr &pItem, CFileItemList &queuedItems)
480 {
481 if (!pItem->CanQueue() || pItem->IsRAR() || pItem->IsZIP() || pItem->IsParentFolder()) // no zip/rar enqueues thank you!
482 return;
483
484 if (pItem->m_bIsFolder)
485 {
486 if (pItem->IsParentFolder())
487 return;
488
489 // check if it's a folder with dvd or bluray files, then just add the relevant file
490 std::string mediapath(pItem->GetOpticalMediaPath());
491 if (!mediapath.empty())
492 {
493 CFileItemPtr item(new CFileItem(mediapath, false));
494 queuedItems.Add(item);
495 return;
496 }
497
498 // Check if we add a locked share
499 if ( pItem->m_bIsShareOrDrive )
500 {
501 CFileItem item = *pItem;
502 if ( !g_passwordManager.IsItemUnlocked( &item, "video" ) )
503 return;
504 }
505
506 // recursive
507 CFileItemList items;
508 GetDirectory(pItem->GetPath(), items);
509 FormatAndSort(items);
510
511 int watchedMode = CMediaSettings::GetInstance().GetWatchedMode(items.GetContent());
512 bool unwatchedOnly = watchedMode == WatchedModeUnwatched;
513 bool watchedOnly = watchedMode == WatchedModeWatched;
514 for (int i = 0; i < items.Size(); ++i)
515 {
516 if (items[i]->m_bIsFolder)
517 {
518 std::string strPath = items[i]->GetPath();
519 URIUtils::RemoveSlashAtEnd(strPath);
520 if (StringUtils::EndsWithNoCase(strPath, "sample")) // skip sample folders
521 {
522 continue;
523 }
524 }
525 else if (items[i]->HasVideoInfoTag() &&
526 ((unwatchedOnly && items[i]->GetVideoInfoTag()->GetPlayCount() > 0) ||
527 (watchedOnly && items[i]->GetVideoInfoTag()->GetPlayCount() <= 0)))
528 continue;
529
530 AddItemToPlayList(items[i], queuedItems);
531 }
532 }
533 else
534 {
535 // just an item
536 if (pItem->IsPlayList())
537 {
538 std::unique_ptr<CPlayList> pPlayList (CPlayListFactory::Create(*pItem));
539 if (pPlayList)
540 {
541 // load it
542 if (!pPlayList->Load(pItem->GetPath()))
543 {
544 HELPERS::ShowOKDialogText(CVariant{6}, CVariant{477});
545 return; //hmmm unable to load playlist?
546 }
547
548 CPlayList playlist = *pPlayList;
549 for (int i = 0; i < playlist.size(); ++i)
550 {
551 AddItemToPlayList(playlist[i], queuedItems);
552 }
553 return;
554 }
555 }
556 else if(pItem->IsInternetStream())
557 { // just queue the internet stream, it will be expanded on play
558 queuedItems.Add(pItem);
559 }
560 else if (pItem->IsPlugin() && pItem->GetProperty("isplayable").asBoolean())
561 { // a playable python files
562 queuedItems.Add(pItem);
563 }
564 else if (pItem->IsVideoDb())
565 { // this case is needed unless we allow IsVideo() to return true for videodb items,
566 // but then we have issues with playlists of videodb items
567 CFileItemPtr item(new CFileItem(*pItem->GetVideoInfoTag()));
568 queuedItems.Add(item);
569 }
570 else if (!pItem->IsNFO() && pItem->IsVideo())
571 {
572 queuedItems.Add(pItem);
573 }
574 }
575 }
576
GetResumeItemOffset(const CFileItem * item,int64_t & startoffset,int & partNumber)577 void CGUIWindowVideoBase::GetResumeItemOffset(const CFileItem *item, int64_t& startoffset, int& partNumber)
578 {
579 // do not resume Live TV and 'deleted' items (e.g. trashed pvr recordings)
580 if (item->IsLiveTV() || item->IsDeleted())
581 return;
582
583 startoffset = 0;
584 partNumber = 0;
585
586 if (!item->IsNFO() && !item->IsPlayList())
587 {
588 if (item->GetCurrentResumeTimeAndPartNumber(startoffset, partNumber))
589 {
590 startoffset = CUtil::ConvertSecsToMilliSecs(startoffset);
591 }
592 else
593 {
594 CBookmark bookmark;
595 std::string strPath = item->GetPath();
596 if ((item->IsVideoDb() || item->IsDVD()) && item->HasVideoInfoTag())
597 strPath = item->GetVideoInfoTag()->m_strFileNameAndPath;
598
599 CVideoDatabase db;
600 if (!db.Open())
601 {
602 CLog::Log(LOGERROR, "%s - Cannot open VideoDatabase", __FUNCTION__);
603 return;
604 }
605 if (db.GetResumeBookMark(strPath, bookmark))
606 {
607 startoffset = CUtil::ConvertSecsToMilliSecs(bookmark.timeInSeconds);
608 partNumber = bookmark.partNumber;
609 }
610 db.Close();
611 }
612 }
613 }
614
HasResumeItemOffset(const CFileItem * item)615 bool CGUIWindowVideoBase::HasResumeItemOffset(const CFileItem *item)
616 {
617 int64_t startoffset = 0;
618 int partNumber = 0;
619 GetResumeItemOffset(item, startoffset, partNumber);
620 return startoffset > 0;
621 }
622
OnClick(int iItem,const std::string & player)623 bool CGUIWindowVideoBase::OnClick(int iItem, const std::string &player)
624 {
625 return CGUIMediaWindow::OnClick(iItem, player);
626 }
627
OnSelect(int iItem)628 bool CGUIWindowVideoBase::OnSelect(int iItem)
629 {
630 if (iItem < 0 || iItem >= m_vecItems->Size())
631 return false;
632
633 CFileItemPtr item = m_vecItems->Get(iItem);
634
635 std::string path = item->GetPath();
636 if (!item->m_bIsFolder && path != "add" &&
637 !StringUtils::StartsWith(path, "newsmartplaylist://") &&
638 !StringUtils::StartsWith(path, "newplaylist://") &&
639 !StringUtils::StartsWith(path, "newtag://") &&
640 !StringUtils::StartsWith(path, "script://"))
641 return OnFileAction(iItem, CServiceBroker::GetSettingsComponent()->GetSettings()->GetInt(CSettings::SETTING_MYVIDEOS_SELECTACTION), "");
642
643 return CGUIMediaWindow::OnSelect(iItem);
644 }
645
OnFileAction(int iItem,int action,const std::string & player)646 bool CGUIWindowVideoBase::OnFileAction(int iItem, int action, const std::string& player)
647 {
648 CFileItemPtr item = m_vecItems->Get(iItem);
649
650 // Reset the current start offset. The actual resume
651 // option is set in the switch, based on the action passed.
652 item->m_lStartOffset = 0;
653
654 switch (action)
655 {
656 case SELECT_ACTION_CHOOSE:
657 {
658 CContextButtons choices;
659
660 if (item->IsVideoDb())
661 {
662 std::string itemPath(item->GetPath());
663 itemPath = item->GetVideoInfoTag()->m_strFileNameAndPath;
664 if (URIUtils::IsStack(itemPath) && CFileItem(CStackDirectory::GetFirstStackedFile(itemPath),false).IsDiscImage())
665 choices.Add(SELECT_ACTION_PLAYPART, 20324); // Play Part
666 }
667
668 std::string resumeString = GetResumeString(*item);
669 if (!resumeString.empty())
670 {
671 choices.Add(SELECT_ACTION_RESUME, resumeString);
672 choices.Add(SELECT_ACTION_PLAY, 12021); // Play from beginning
673 }
674 else
675 choices.Add(SELECT_ACTION_PLAY, 208); // Play
676
677 choices.Add(SELECT_ACTION_INFO, 22081); // Info
678 choices.Add(SELECT_ACTION_MORE, 22082); // More
679 int value = CGUIDialogContextMenu::ShowAndGetChoice(choices);
680 if (value < 0)
681 return true;
682
683 return OnFileAction(iItem, value, player);
684 }
685 break;
686 case SELECT_ACTION_PLAY_OR_RESUME:
687 return OnResumeItem(iItem, player);
688 case SELECT_ACTION_INFO:
689 if (OnItemInfo(iItem))
690 return true;
691 break;
692 case SELECT_ACTION_MORE:
693 OnPopupMenu(iItem);
694 return true;
695 case SELECT_ACTION_RESUME:
696 item->m_lStartOffset = STARTOFFSET_RESUME;
697 break;
698 case SELECT_ACTION_PLAYPART:
699 if (!OnPlayStackPart(iItem))
700 return false;
701 break;
702 case SELECT_ACTION_QUEUE:
703 OnQueueItem(iItem);
704 return true;
705 case SELECT_ACTION_PLAY:
706 default:
707 break;
708 }
709 return OnClick(iItem, player);
710 }
711
OnItemInfo(int iItem)712 bool CGUIWindowVideoBase::OnItemInfo(int iItem)
713 {
714 if (iItem < 0 || iItem >= m_vecItems->Size())
715 return false;
716
717 CFileItemPtr item = m_vecItems->Get(iItem);
718
719 if (item->IsPath("add") || item->IsParentFolder() ||
720 (item->IsPlayList() && !URIUtils::HasExtension(item->GetDynPath(), ".strm")))
721 return false;
722
723 if (!m_vecItems->IsPlugin() && (item->IsPlugin() || item->IsScript()))
724 return CGUIDialogAddonInfo::ShowForItem(item);
725
726 if (item->m_bIsFolder &&
727 item->IsVideoDb() &&
728 StringUtils::StartsWith(item->GetPath(), "videodb://movies/sets/"))
729 return ShowIMDB(item, nullptr, true);
730
731 ADDON::ScraperPtr scraper;
732
733 // Match visibility test of CMusicInfo::IsVisible
734 if (item->IsVideoDb() && item->HasVideoInfoTag() &&
735 (item->HasProperty("artist_musicid") || item->HasProperty("album_musicid")))
736 {
737 CGUIDialogMusicInfo::ShowFor(item.get());
738 return true;
739 }
740 if (!m_vecItems->IsPlugin() && !m_vecItems->IsRSS() && !m_vecItems->IsLiveTV())
741 {
742 std::string strDir;
743 if (item->IsVideoDb() &&
744 item->HasVideoInfoTag() &&
745 !item->GetVideoInfoTag()->m_strPath.empty())
746 {
747 strDir = item->GetVideoInfoTag()->m_strPath;
748 }
749 else
750 strDir = URIUtils::GetDirectory(item->GetPath());
751
752 SScanSettings settings;
753 bool foundDirectly = false;
754 scraper = m_database.GetScraperForPath(strDir, settings, foundDirectly);
755
756 if (!scraper &&
757 !(m_database.HasMovieInfo(item->GetPath()) ||
758 m_database.HasTvShowInfo(strDir) ||
759 m_database.HasEpisodeInfo(item->GetPath())))
760 {
761 return false;
762 }
763
764 if (scraper && scraper->Content() == CONTENT_TVSHOWS && foundDirectly && !settings.parent_name_root) // dont lookup on root tvshow folder
765 return true;
766 }
767
768 OnItemInfo(*item, scraper);
769
770 // Return whether or not we have information to display.
771 // Note: This will cause the default select action to start
772 // playback in case it's set to "Show information".
773 return item->HasVideoInfoTag();
774 }
775
OnRestartItem(int iItem,const std::string & player)776 void CGUIWindowVideoBase::OnRestartItem(int iItem, const std::string &player)
777 {
778 CGUIMediaWindow::OnClick(iItem, player);
779 }
780
GetResumeString(const CFileItem & item)781 std::string CGUIWindowVideoBase::GetResumeString(const CFileItem &item)
782 {
783 std::string resumeString;
784 int64_t startOffset = 0;
785 int startPart = 0;
786 GetResumeItemOffset(&item, startOffset, startPart);
787 if (startOffset > 0)
788 {
789 resumeString = StringUtils::Format(g_localizeStrings.Get(12022).c_str(),
790 StringUtils::SecondsToTimeString(static_cast<long>(CUtil::ConvertMilliSecsToSecsInt(startOffset)), TIME_FORMAT_HH_MM_SS).c_str());
791 if (startPart > 0)
792 {
793 std::string partString = StringUtils::Format(g_localizeStrings.Get(23051).c_str(), startPart);
794 resumeString += " (" + partString + ")";
795 }
796 }
797 return resumeString;
798 }
799
ShowResumeMenu(CFileItem & item)800 bool CGUIWindowVideoBase::ShowResumeMenu(CFileItem &item)
801 {
802 if (!item.m_bIsFolder && !item.IsPVR())
803 {
804 std::string resumeString = GetResumeString(item);
805 if (!resumeString.empty())
806 { // prompt user whether they wish to resume
807 CContextButtons choices;
808 choices.Add(1, resumeString);
809 choices.Add(2, 12021); // Play from beginning
810 int retVal = CGUIDialogContextMenu::ShowAndGetChoice(choices);
811 if (retVal < 0)
812 return false; // don't do anything
813 if (retVal == 1)
814 item.m_lStartOffset = STARTOFFSET_RESUME;
815 }
816 }
817 return true;
818 }
819
OnResumeItem(int iItem,const std::string & player)820 bool CGUIWindowVideoBase::OnResumeItem(int iItem, const std::string &player)
821 {
822 if (iItem < 0 || iItem >= m_vecItems->Size()) return true;
823 CFileItemPtr item = m_vecItems->Get(iItem);
824
825 if (item->m_bIsFolder)
826 {
827 // resuming directories isn't supported yet. play.
828 PlayItem(iItem, player);
829 return true;
830 }
831
832 std::string resumeString = GetResumeString(*item);
833
834 if (!resumeString.empty())
835 {
836 CContextButtons choices;
837 choices.Add(SELECT_ACTION_RESUME, resumeString);
838 choices.Add(SELECT_ACTION_PLAY, 12021); // Play from beginning
839 int value = CGUIDialogContextMenu::ShowAndGetChoice(choices);
840 if (value < 0)
841 return true;
842 return OnFileAction(iItem, value, player);
843 }
844
845 return OnFileAction(iItem, SELECT_ACTION_PLAY, player);
846 }
847
GetContextButtons(int itemNumber,CContextButtons & buttons)848 void CGUIWindowVideoBase::GetContextButtons(int itemNumber, CContextButtons &buttons)
849 {
850 CFileItemPtr item;
851 if (itemNumber >= 0 && itemNumber < m_vecItems->Size())
852 item = m_vecItems->Get(itemNumber);
853
854 // contextual buttons
855 if (item)
856 {
857 if (!item->IsParentFolder())
858 {
859 std::string path(item->GetPath());
860 if (item->IsVideoDb() && item->HasVideoInfoTag())
861 path = item->GetVideoInfoTag()->m_strFileNameAndPath;
862
863 if (!item->IsPath("add") && !item->IsPlugin() &&
864 !item->IsScript() && !item->IsAddonsPath() && !item->IsLiveTV())
865 {
866 if (URIUtils::IsStack(path))
867 {
868 std::vector<uint64_t> times;
869 if (m_database.GetStackTimes(path,times) || CFileItem(CStackDirectory::GetFirstStackedFile(path),false).IsDiscImage())
870 buttons.Add(CONTEXT_BUTTON_PLAY_PART, 20324);
871 }
872
873 // allow a folder to be ad-hoc queued and played by the default player
874 if (item->m_bIsFolder || (item->IsPlayList() &&
875 !CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->m_playlistAsFolders))
876 {
877 buttons.Add(CONTEXT_BUTTON_PLAY_ITEM, 208);
878 }
879
880 if (!m_vecItems->GetPath().empty() && !StringUtils::StartsWithNoCase(item->GetPath(), "newsmartplaylist://") && !StringUtils::StartsWithNoCase(item->GetPath(), "newtag://")
881 && !m_vecItems->IsSourcesPath())
882 {
883 buttons.Add(CONTEXT_BUTTON_QUEUE_ITEM, 13347); // Add to Playlist
884 buttons.Add(CONTEXT_BUTTON_PLAY_NEXT, 10008); // Play next
885 }
886 }
887
888 if (!item->m_bIsFolder && !(item->IsPlayList() && !CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->m_playlistAsFolders))
889 {
890 const CPlayerCoreFactory &playerCoreFactory = CServiceBroker::GetPlayerCoreFactory();
891
892 // get players
893 std::vector<std::string> players;
894 if (item->IsVideoDb())
895 {
896 CFileItem item2(item->GetVideoInfoTag()->m_strFileNameAndPath, false);
897 playerCoreFactory.GetPlayers(item2, players);
898 }
899 else
900 playerCoreFactory.GetPlayers(*item, players);
901
902 if (players.size() > 1)
903 buttons.Add(CONTEXT_BUTTON_PLAY_WITH, 15213);
904 }
905 if (item->IsSmartPlayList())
906 {
907 buttons.Add(CONTEXT_BUTTON_PLAY_PARTYMODE, 15216); // Play in Partymode
908 }
909
910 // if the item isn't a folder or script, is not explicitly marked as not playable,
911 // is a member of a list rather than a single item and we're not on the last element of the list,
912 // then add either 'play from here' or 'play only this' depending on default behaviour
913 if (!(item->m_bIsFolder || item->IsScript()) &&
914 (!item->HasProperty("IsPlayable") || item->GetProperty("IsPlayable").asBoolean()) &&
915 m_vecItems->Size() > 1 && itemNumber < m_vecItems->Size() - 1)
916 {
917 int settingValue = SETTING_AUTOPLAYNEXT_UNCATEGORIZED;
918
919 if (item->IsVideoDb() && item->HasVideoInfoTag())
920 {
921 const std::string mediaType = item->GetVideoInfoTag()->m_type;
922
923 if (mediaType == MediaTypeMusicVideo)
924 settingValue = SETTING_AUTOPLAYNEXT_MUSICVIDEOS;
925 else if (mediaType == MediaTypeEpisode)
926 settingValue = SETTING_AUTOPLAYNEXT_EPISODES;
927 else if (mediaType == MediaTypeMovie)
928 settingValue = SETTING_AUTOPLAYNEXT_MOVIES;
929 }
930
931 const auto setting = std::dynamic_pointer_cast<CSettingList>(
932 CServiceBroker::GetSettingsComponent()->GetSettings()->GetSetting(
933 CSettings::SETTING_VIDEOPLAYER_AUTOPLAYNEXTITEM));
934
935 if (setting && CSettingUtils::FindIntInList(setting, settingValue))
936 buttons.Add(CONTEXT_BUTTON_PLAY_ONLY_THIS, 13434);
937 else
938 buttons.Add(CONTEXT_BUTTON_PLAY_AND_QUEUE, 13412);
939 }
940 if (item->IsSmartPlayList() || m_vecItems->IsSmartPlayList())
941 buttons.Add(CONTEXT_BUTTON_EDIT_SMART_PLAYLIST, 586);
942 }
943 }
944 CGUIMediaWindow::GetContextButtons(itemNumber, buttons);
945 }
946
OnPlayStackPart(int iItem)947 bool CGUIWindowVideoBase::OnPlayStackPart(int iItem)
948 {
949 if (iItem < 0 || iItem >= m_vecItems->Size())
950 return false;
951
952 CFileItemPtr stack = m_vecItems->Get(iItem);
953 std::string path(stack->GetPath());
954 if (stack->IsVideoDb())
955 path = stack->GetVideoInfoTag()->m_strFileNameAndPath;
956
957 if (!URIUtils::IsStack(path))
958 return false;
959
960 CFileItemList parts;
961 CDirectory::GetDirectory(path, parts, "", DIR_FLAG_DEFAULTS);
962
963 for (int i = 0; i < parts.Size(); i++)
964 parts[i]->SetLabel(StringUtils::Format(g_localizeStrings.Get(23051).c_str(), i+1));
965
966 CGUIDialogSelect* pDialog = CServiceBroker::GetGUI()->GetWindowManager().GetWindow<CGUIDialogSelect>(WINDOW_DIALOG_SELECT);
967
968 pDialog->Reset();
969 pDialog->SetHeading(CVariant{20324});
970 pDialog->SetItems(parts);
971 pDialog->Open();
972
973 if (!pDialog->IsConfirmed())
974 return false;
975
976 int selectedFile = pDialog->GetSelectedItem();
977 if (selectedFile >= 0)
978 {
979 // ISO stack
980 if (CFileItem(CStackDirectory::GetFirstStackedFile(path),false).IsDiscImage())
981 {
982 std::string resumeString = CGUIWindowVideoBase::GetResumeString(*(parts[selectedFile].get()));
983 stack->m_lStartOffset = 0;
984 if (!resumeString.empty())
985 {
986 CContextButtons choices;
987 choices.Add(SELECT_ACTION_RESUME, resumeString);
988 choices.Add(SELECT_ACTION_PLAY, 12021); // Play from beginning
989 int value = CGUIDialogContextMenu::ShowAndGetChoice(choices);
990 if (value == SELECT_ACTION_RESUME)
991 GetResumeItemOffset(parts[selectedFile].get(), stack->m_lStartOffset, stack->m_lStartPartNumber);
992 else if (value != SELECT_ACTION_PLAY)
993 return false; // if not selected PLAY, then we changed our mind so return
994 }
995 stack->m_lStartPartNumber = selectedFile + 1;
996 }
997 // regular stack
998 else
999 {
1000 if (selectedFile > 0)
1001 {
1002 std::vector<uint64_t> times;
1003 if (m_database.GetStackTimes(path,times))
1004 stack->m_lStartOffset = times[selectedFile - 1];
1005 }
1006 else
1007 stack->m_lStartOffset = 0;
1008 }
1009
1010
1011 }
1012
1013 return true;
1014 }
1015
OnContextButton(int itemNumber,CONTEXT_BUTTON button)1016 bool CGUIWindowVideoBase::OnContextButton(int itemNumber, CONTEXT_BUTTON button)
1017 {
1018 CFileItemPtr item;
1019 if (itemNumber >= 0 && itemNumber < m_vecItems->Size())
1020 item = m_vecItems->Get(itemNumber);
1021 switch (button)
1022 {
1023 case CONTEXT_BUTTON_SET_CONTENT:
1024 {
1025 OnAssignContent(item->HasVideoInfoTag() && !item->GetVideoInfoTag()->m_strPath.empty() ? item->GetVideoInfoTag()->m_strPath : item->GetPath());
1026 return true;
1027 }
1028 case CONTEXT_BUTTON_PLAY_PART:
1029 {
1030 if (OnPlayStackPart(itemNumber))
1031 {
1032 // call CGUIMediaWindow::OnClick() as otherwise autoresume will kick in
1033 CGUIMediaWindow::OnClick(itemNumber);
1034 return true;
1035 }
1036 else
1037 return false;
1038 }
1039 case CONTEXT_BUTTON_QUEUE_ITEM:
1040 OnQueueItem(itemNumber);
1041 return true;
1042
1043 case CONTEXT_BUTTON_PLAY_NEXT:
1044 OnQueueItem(itemNumber, true);
1045 return true;
1046
1047 case CONTEXT_BUTTON_PLAY_ITEM:
1048 PlayItem(itemNumber);
1049 return true;
1050
1051 case CONTEXT_BUTTON_PLAY_WITH:
1052 {
1053 const CPlayerCoreFactory &playerCoreFactory = CServiceBroker::GetPlayerCoreFactory();
1054
1055 std::vector<std::string> players;
1056 if (item->IsVideoDb())
1057 {
1058 CFileItem item2(*item->GetVideoInfoTag());
1059 playerCoreFactory.GetPlayers(item2, players);
1060 }
1061 else
1062 playerCoreFactory.GetPlayers(*item, players);
1063
1064 std:: string player = playerCoreFactory.SelectPlayerDialog(players);
1065 if (!player.empty())
1066 {
1067 // any other select actions but play or resume, resume, play or playpart
1068 // don't make any sense here since the user already decided that he'd
1069 // like to play the item (just with a specific player)
1070 VideoSelectAction selectAction = (VideoSelectAction)CServiceBroker::GetSettingsComponent()->GetSettings()->GetInt(CSettings::SETTING_MYVIDEOS_SELECTACTION);
1071 if (selectAction != SELECT_ACTION_PLAY_OR_RESUME &&
1072 selectAction != SELECT_ACTION_RESUME &&
1073 selectAction != SELECT_ACTION_PLAY &&
1074 selectAction != SELECT_ACTION_PLAYPART)
1075 selectAction = SELECT_ACTION_PLAY_OR_RESUME;
1076 return OnFileAction(itemNumber, selectAction, player);
1077 }
1078 return true;
1079 }
1080
1081 case CONTEXT_BUTTON_PLAY_PARTYMODE:
1082 g_partyModeManager.Enable(PARTYMODECONTEXT_VIDEO, m_vecItems->Get(itemNumber)->GetPath());
1083 return true;
1084
1085 case CONTEXT_BUTTON_SCAN:
1086 {
1087 if( !item)
1088 return false;
1089 ADDON::ScraperPtr info;
1090 SScanSettings settings;
1091 GetScraperForItem(item.get(), info, settings);
1092 std::string strPath = item->GetPath();
1093 if (item->IsVideoDb() && (!item->m_bIsFolder || item->GetVideoInfoTag()->m_strPath.empty()))
1094 return false;
1095
1096 if (item->IsVideoDb())
1097 strPath = item->GetVideoInfoTag()->m_strPath;
1098
1099 if (!info || info->Content() == CONTENT_NONE)
1100 return false;
1101
1102 if (item->m_bIsFolder)
1103 {
1104 OnScan(strPath, true);
1105 }
1106 else
1107 OnItemInfo(*item, info);
1108
1109 return true;
1110 }
1111 case CONTEXT_BUTTON_DELETE:
1112 OnDeleteItem(itemNumber);
1113 return true;
1114 case CONTEXT_BUTTON_EDIT_SMART_PLAYLIST:
1115 {
1116 std::string playlist = m_vecItems->Get(itemNumber)->IsSmartPlayList() ? m_vecItems->Get(itemNumber)->GetPath() : m_vecItems->GetPath(); // save path as activatewindow will destroy our items
1117 if (CGUIDialogSmartPlaylistEditor::EditPlaylist(playlist, "video"))
1118 Refresh(true); // need to update
1119 return true;
1120 }
1121 case CONTEXT_BUTTON_RENAME:
1122 OnRenameItem(itemNumber);
1123 return true;
1124 case CONTEXT_BUTTON_PLAY_AND_QUEUE:
1125 return OnPlayAndQueueMedia(item);
1126 case CONTEXT_BUTTON_PLAY_ONLY_THIS:
1127 return OnPlayMedia(itemNumber);
1128 default:
1129 break;
1130 }
1131 return CGUIMediaWindow::OnContextButton(itemNumber, button);
1132 }
1133
OnPlayMedia(int iItem,const std::string & player)1134 bool CGUIWindowVideoBase::OnPlayMedia(int iItem, const std::string &player)
1135 {
1136 if ( iItem < 0 || iItem >= m_vecItems->Size() )
1137 return false;
1138
1139 CFileItemPtr pItem = m_vecItems->Get(iItem);
1140
1141 // party mode
1142 if (g_partyModeManager.IsEnabled(PARTYMODECONTEXT_VIDEO))
1143 {
1144 CPlayList playlistTemp;
1145 playlistTemp.Add(pItem);
1146 g_partyModeManager.AddUserSongs(playlistTemp, true);
1147 return true;
1148 }
1149
1150 // Reset Playlistplayer, playback started now does
1151 // not use the playlistplayer.
1152 CServiceBroker::GetPlaylistPlayer().Reset();
1153 CServiceBroker::GetPlaylistPlayer().SetCurrentPlaylist(PLAYLIST_NONE);
1154
1155 CFileItem item(*pItem);
1156 if (pItem->IsVideoDb())
1157 {
1158 item.SetPath(pItem->GetVideoInfoTag()->m_strFileNameAndPath);
1159 item.SetProperty("original_listitem_url", pItem->GetPath());
1160 }
1161 CLog::Log(LOGDEBUG, "%s %s", __FUNCTION__, CURL::GetRedacted(item.GetPath()).c_str());
1162
1163 PlayMovie(&item, player);
1164
1165 return true;
1166 }
1167
OnPlayAndQueueMedia(const CFileItemPtr & item,const std::string & player)1168 bool CGUIWindowVideoBase::OnPlayAndQueueMedia(const CFileItemPtr& item, const std::string& player)
1169 {
1170 // Get the current playlist and make sure it is not shuffled
1171 int iPlaylist = m_guiState->GetPlaylist();
1172 if (iPlaylist != PLAYLIST_NONE && CServiceBroker::GetPlaylistPlayer().IsShuffled(iPlaylist))
1173 CServiceBroker::GetPlaylistPlayer().SetShuffle(iPlaylist, false);
1174
1175 CFileItemPtr movieItem(new CFileItem(*item));
1176
1177 // Call the base method to actually queue the items
1178 // and start playing the given item
1179 return CGUIMediaWindow::OnPlayAndQueueMedia(movieItem, player);
1180 }
1181
PlayMovie(const CFileItem * item,const std::string & player)1182 void CGUIWindowVideoBase::PlayMovie(const CFileItem *item, const std::string &player)
1183 {
1184 if(m_thumbLoader.IsLoading())
1185 m_thumbLoader.StopAsync();
1186
1187 CServiceBroker::GetPlaylistPlayer().Play(std::make_shared<CFileItem>(*item), player);
1188
1189 if(!g_application.GetAppPlayer().IsPlayingVideo())
1190 m_thumbLoader.Load(*m_vecItems);
1191 }
1192
OnDeleteItem(int iItem)1193 void CGUIWindowVideoBase::OnDeleteItem(int iItem)
1194 {
1195 if ( iItem < 0 || iItem >= m_vecItems->Size())
1196 return;
1197
1198 OnDeleteItem(m_vecItems->Get(iItem));
1199
1200 Refresh(true);
1201 m_viewControl.SetSelectedItem(iItem);
1202 }
1203
OnDeleteItem(const CFileItemPtr & item)1204 void CGUIWindowVideoBase::OnDeleteItem(const CFileItemPtr& item)
1205 {
1206 // HACK: stacked files need to be treated as folders in order to be deleted
1207 if (item->IsStack())
1208 item->m_bIsFolder = true;
1209
1210 const std::shared_ptr<CProfileManager> profileManager = CServiceBroker::GetSettingsComponent()->GetProfileManager();
1211
1212 if (profileManager->GetCurrentProfile().getLockMode() != LOCK_MODE_EVERYONE &&
1213 profileManager->GetCurrentProfile().filesLocked())
1214 {
1215 if (!g_passwordManager.IsMasterLockUnlocked(true))
1216 return;
1217 }
1218
1219 if ((CServiceBroker::GetSettingsComponent()->GetSettings()->GetBool(CSettings::SETTING_FILELISTS_ALLOWFILEDELETION) ||
1220 m_vecItems->IsPath("special://videoplaylists/")) &&
1221 CUtil::SupportsWriteFileOperations(item->GetPath()))
1222 {
1223 CGUIComponent *gui = CServiceBroker::GetGUI();
1224 if (gui && gui->ConfirmDelete(item->GetPath()))
1225 CFileUtils::DeleteItem(item);
1226 }
1227 }
1228
LoadPlayList(const std::string & strPlayList,int iPlayList)1229 void CGUIWindowVideoBase::LoadPlayList(const std::string& strPlayList, int iPlayList /* = PLAYLIST_VIDEO */)
1230 {
1231 // if partymode is active, we disable it
1232 if (g_partyModeManager.IsEnabled())
1233 g_partyModeManager.Disable();
1234
1235 // load a playlist like .m3u, .pls
1236 // first get correct factory to load playlist
1237 std::unique_ptr<CPlayList> pPlayList (CPlayListFactory::Create(strPlayList));
1238 if (pPlayList)
1239 {
1240 // load it
1241 if (!pPlayList->Load(strPlayList))
1242 {
1243 HELPERS::ShowOKDialogText(CVariant{6}, CVariant{477});
1244 return; //hmmm unable to load playlist?
1245 }
1246 }
1247
1248 if (g_application.ProcessAndStartPlaylist(strPlayList, *pPlayList, iPlayList))
1249 {
1250 if (m_guiState)
1251 m_guiState->SetPlaylistDirectory("playlistvideo://");
1252 }
1253 }
1254
PlayItem(int iItem,const std::string & player)1255 void CGUIWindowVideoBase::PlayItem(int iItem, const std::string &player)
1256 {
1257 // restrictions should be placed in the appropriate window code
1258 // only call the base code if the item passes since this clears
1259 // the currently playing temp playlist
1260
1261 const CFileItemPtr pItem = m_vecItems->Get(iItem);
1262 // if its a folder, build a temp playlist
1263 if (pItem->m_bIsFolder && !pItem->IsPlugin())
1264 {
1265 // take a copy so we can alter the queue state
1266 CFileItemPtr item(new CFileItem(*m_vecItems->Get(iItem)));
1267
1268 // Allow queuing of unqueueable items
1269 // when we try to queue them directly
1270 if (!item->CanQueue())
1271 item->SetCanQueue(true);
1272
1273 // skip ".."
1274 if (item->IsParentFolder())
1275 return;
1276
1277 // recursively add items to list
1278 CFileItemList queuedItems;
1279 AddItemToPlayList(item, queuedItems);
1280
1281 CServiceBroker::GetPlaylistPlayer().ClearPlaylist(PLAYLIST_VIDEO);
1282 CServiceBroker::GetPlaylistPlayer().Reset();
1283 CServiceBroker::GetPlaylistPlayer().Add(PLAYLIST_VIDEO, queuedItems);
1284 CServiceBroker::GetPlaylistPlayer().SetCurrentPlaylist(PLAYLIST_VIDEO);
1285 CServiceBroker::GetPlaylistPlayer().Play();
1286 }
1287 else if (pItem->IsPlayList())
1288 {
1289 // load the playlist the old way
1290 LoadPlayList(pItem->GetPath(), PLAYLIST_VIDEO);
1291 }
1292 else
1293 {
1294 // single item, play it
1295 OnClick(iItem, player);
1296 }
1297 }
1298
Update(const std::string & strDirectory,bool updateFilterPath)1299 bool CGUIWindowVideoBase::Update(const std::string &strDirectory, bool updateFilterPath /* = true */)
1300 {
1301 if (m_thumbLoader.IsLoading())
1302 m_thumbLoader.StopThread();
1303
1304 if (!CGUIMediaWindow::Update(strDirectory, updateFilterPath))
1305 return false;
1306
1307 // might already be running from GetGroupedItems
1308 if (!m_thumbLoader.IsLoading())
1309 m_thumbLoader.Load(*m_vecItems);
1310
1311 return true;
1312 }
1313
GetDirectory(const std::string & strDirectory,CFileItemList & items)1314 bool CGUIWindowVideoBase::GetDirectory(const std::string &strDirectory, CFileItemList &items)
1315 {
1316 bool bResult = CGUIMediaWindow::GetDirectory(strDirectory, items);
1317
1318 // add in the "New Playlist" item if we're in the playlists folder
1319 if ((items.GetPath() == "special://videoplaylists/") && !items.Contains("newplaylist://"))
1320 {
1321 const std::shared_ptr<CProfileManager> profileManager = CServiceBroker::GetSettingsComponent()->GetProfileManager();
1322
1323 CFileItemPtr newPlaylist(new CFileItem(profileManager->GetUserDataItem("PartyMode-Video.xsp"),false));
1324 newPlaylist->SetLabel(g_localizeStrings.Get(16035));
1325 newPlaylist->SetLabelPreformatted(true);
1326 newPlaylist->SetArt("icon", "DefaultPartyMode.png");
1327 newPlaylist->m_bIsFolder = true;
1328 items.Add(newPlaylist);
1329
1330 /* newPlaylist.reset(new CFileItem("newplaylist://", false));
1331 newPlaylist->SetLabel(g_localizeStrings.Get(525));
1332 newPlaylist->SetLabelPreformatted(true);
1333 items.Add(newPlaylist);
1334 */
1335 newPlaylist.reset(new CFileItem("newsmartplaylist://video", false));
1336 newPlaylist->SetLabel(g_localizeStrings.Get(21437)); // "new smart playlist..."
1337 newPlaylist->SetArt("icon", "DefaultAddSource.png");
1338 newPlaylist->SetLabelPreformatted(true);
1339 items.Add(newPlaylist);
1340 }
1341
1342 m_stackingAvailable = StackingAvailable(items);
1343 // we may also be in a tvshow files listing
1344 // (ideally this should be removed, and our stack regexps tidied up if necessary
1345 // No "normal" episodes should stack, and multi-parts should be supported)
1346 ADDON::ScraperPtr info = m_database.GetScraperForPath(strDirectory);
1347 if (info && info->Content() == CONTENT_TVSHOWS)
1348 m_stackingAvailable = false;
1349
1350 if (m_stackingAvailable && !items.IsStack() && CServiceBroker::GetSettingsComponent()->GetSettings()->GetBool(CSettings::SETTING_MYVIDEOS_STACKVIDEOS))
1351 items.Stack();
1352
1353 return bResult;
1354 }
1355
StackingAvailable(const CFileItemList & items)1356 bool CGUIWindowVideoBase::StackingAvailable(const CFileItemList &items)
1357 {
1358 CURL url(items.GetPath());
1359 return !(items.IsPlugin() || items.IsAddonsPath() ||
1360 items.IsRSS() || items.IsInternetStream() ||
1361 items.IsVideoDb() || url.IsProtocol("playlistvideo"));
1362 }
1363
GetGroupedItems(CFileItemList & items)1364 void CGUIWindowVideoBase::GetGroupedItems(CFileItemList &items)
1365 {
1366 CGUIMediaWindow::GetGroupedItems(items);
1367
1368 std::string group;
1369 bool mixed = false;
1370 if (items.HasProperty(PROPERTY_GROUP_BY))
1371 group = items.GetProperty(PROPERTY_GROUP_BY).asString();
1372 if (items.HasProperty(PROPERTY_GROUP_MIXED))
1373 mixed = items.GetProperty(PROPERTY_GROUP_MIXED).asBoolean();
1374
1375 // group == "none" completely suppresses any grouping
1376 if (!StringUtils::EqualsNoCase(group, "none"))
1377 {
1378 CQueryParams params;
1379 CVideoDatabaseDirectory dir;
1380 dir.GetQueryParams(items.GetPath(), params);
1381 VIDEODATABASEDIRECTORY::NODE_TYPE nodeType = CVideoDatabaseDirectory::GetDirectoryChildType(m_strFilterPath);
1382 const std::shared_ptr<CSettings> settings = CServiceBroker::GetSettingsComponent()->GetSettings();
1383 if (items.GetContent() == "movies" && params.GetSetId() <= 0 &&
1384 nodeType == NODE_TYPE_TITLE_MOVIES &&
1385 (settings->GetBool(CSettings::SETTING_VIDEOLIBRARY_GROUPMOVIESETS) || (StringUtils::EqualsNoCase(group, "sets") && mixed)))
1386 {
1387 CFileItemList groupedItems;
1388 GroupAttribute groupAttributes = settings->GetBool(CSettings::SETTING_VIDEOLIBRARY_GROUPSINGLEITEMSETS) ? GroupAttributeNone : GroupAttributeIgnoreSingleItems;
1389 if (GroupUtils::GroupAndMix(GroupBySet, m_strFilterPath, items, groupedItems, groupAttributes))
1390 {
1391 items.ClearItems();
1392 items.Append(groupedItems);
1393 }
1394 }
1395 }
1396
1397 // reload thumbs after filtering and grouping
1398 if (m_thumbLoader.IsLoading())
1399 m_thumbLoader.StopThread();
1400
1401 m_thumbLoader.Load(items);
1402 }
1403
CheckFilterAdvanced(CFileItemList & items) const1404 bool CGUIWindowVideoBase::CheckFilterAdvanced(CFileItemList &items) const
1405 {
1406 const std::string& content = items.GetContent();
1407 if ((items.IsVideoDb() || CanContainFilter(m_strFilterPath)) &&
1408 (StringUtils::EqualsNoCase(content, "movies") ||
1409 StringUtils::EqualsNoCase(content, "tvshows") ||
1410 StringUtils::EqualsNoCase(content, "episodes") ||
1411 StringUtils::EqualsNoCase(content, "musicvideos")))
1412 return true;
1413
1414 return false;
1415 }
1416
CanContainFilter(const std::string & strDirectory) const1417 bool CGUIWindowVideoBase::CanContainFilter(const std::string &strDirectory) const
1418 {
1419 return URIUtils::IsProtocol(strDirectory, "videodb://");
1420 }
1421
1422 /// \brief Search the current directory for a string got from the virtual keyboard
OnSearch()1423 void CGUIWindowVideoBase::OnSearch()
1424 {
1425 std::string strSearch;
1426 if (!CGUIKeyboardFactory::ShowAndGetInput(strSearch, CVariant{g_localizeStrings.Get(16017)}, false))
1427 return ;
1428
1429 StringUtils::ToLower(strSearch);
1430 if (m_dlgProgress)
1431 {
1432 m_dlgProgress->SetHeading(CVariant{194});
1433 m_dlgProgress->SetLine(0, CVariant{strSearch});
1434 m_dlgProgress->SetLine(1, CVariant{""});
1435 m_dlgProgress->SetLine(2, CVariant{""});
1436 m_dlgProgress->Open();
1437 m_dlgProgress->Progress();
1438 }
1439 CFileItemList items;
1440 DoSearch(strSearch, items);
1441
1442 if (m_dlgProgress)
1443 m_dlgProgress->Close();
1444
1445 if (items.Size())
1446 {
1447 CGUIDialogSelect* pDlgSelect = CServiceBroker::GetGUI()->GetWindowManager().GetWindow<CGUIDialogSelect>(WINDOW_DIALOG_SELECT);
1448 pDlgSelect->Reset();
1449 pDlgSelect->SetHeading(CVariant{283});
1450
1451 for (int i = 0; i < items.Size(); i++)
1452 {
1453 CFileItemPtr pItem = items[i];
1454 pDlgSelect->Add(pItem->GetLabel());
1455 }
1456
1457 pDlgSelect->Open();
1458
1459 int iItem = pDlgSelect->GetSelectedItem();
1460 if (iItem < 0)
1461 return;
1462
1463 OnSearchItemFound(items[iItem].get());
1464 }
1465 else
1466 {
1467 HELPERS::ShowOKDialogText(CVariant{194}, CVariant{284});
1468 }
1469 }
1470
1471 /// \brief React on the selected search item
1472 /// \param pItem Search result item
OnSearchItemFound(const CFileItem * pSelItem)1473 void CGUIWindowVideoBase::OnSearchItemFound(const CFileItem* pSelItem)
1474 {
1475 if (pSelItem->m_bIsFolder)
1476 {
1477 std::string strPath = pSelItem->GetPath();
1478 std::string strParentPath;
1479 URIUtils::GetParentPath(strPath, strParentPath);
1480
1481 Update(strParentPath);
1482
1483 if (pSelItem->IsVideoDb() && CServiceBroker::GetSettingsComponent()->GetSettings()->GetBool(CSettings::SETTING_MYVIDEOS_FLATTEN))
1484 SetHistoryForPath("");
1485 else
1486 SetHistoryForPath(strParentPath);
1487
1488 strPath = pSelItem->GetPath();
1489 CURL url(strPath);
1490 if (pSelItem->IsSmb() && !URIUtils::HasSlashAtEnd(strPath))
1491 strPath += "/";
1492
1493 for (int i = 0; i < m_vecItems->Size(); i++)
1494 {
1495 CFileItemPtr pItem = m_vecItems->Get(i);
1496 if (pItem->GetPath() == strPath)
1497 {
1498 m_viewControl.SetSelectedItem(i);
1499 break;
1500 }
1501 }
1502 }
1503 else
1504 {
1505 std::string strPath = URIUtils::GetDirectory(pSelItem->GetPath());
1506
1507 Update(strPath);
1508
1509 if (pSelItem->IsVideoDb() && CServiceBroker::GetSettingsComponent()->GetSettings()->GetBool(CSettings::SETTING_MYVIDEOS_FLATTEN))
1510 SetHistoryForPath("");
1511 else
1512 SetHistoryForPath(strPath);
1513
1514 for (int i = 0; i < m_vecItems->Size(); i++)
1515 {
1516 CFileItemPtr pItem = m_vecItems->Get(i);
1517 CURL url(pItem->GetPath());
1518 if (pSelItem->IsVideoDb())
1519 url.SetOptions("");
1520 if (url.Get() == pSelItem->GetPath())
1521 {
1522 m_viewControl.SetSelectedItem(i);
1523 break;
1524 }
1525 }
1526 }
1527 m_viewControl.SetFocused();
1528 }
1529
GetScraperForItem(CFileItem * item,ADDON::ScraperPtr & info,SScanSettings & settings)1530 int CGUIWindowVideoBase::GetScraperForItem(CFileItem *item, ADDON::ScraperPtr &info, SScanSettings& settings)
1531 {
1532 if (!item)
1533 return 0;
1534
1535 if (m_vecItems->IsPlugin() || m_vecItems->IsRSS())
1536 {
1537 info.reset();
1538 return 0;
1539 }
1540 else if(m_vecItems->IsLiveTV())
1541 {
1542 info.reset();
1543 return 0;
1544 }
1545
1546 bool foundDirectly = false;
1547 info = m_database.GetScraperForPath(item->HasVideoInfoTag() && !item->GetVideoInfoTag()->m_strPath.empty() ? item->GetVideoInfoTag()->m_strPath : item->GetPath(), settings, foundDirectly);
1548 return foundDirectly ? 1 : 0;
1549 }
1550
OnScan(const std::string & strPath,bool scanAll)1551 void CGUIWindowVideoBase::OnScan(const std::string& strPath, bool scanAll)
1552 {
1553 g_application.StartVideoScan(strPath, true, scanAll);
1554 }
1555
GetStartFolder(const std::string & dir)1556 std::string CGUIWindowVideoBase::GetStartFolder(const std::string &dir)
1557 {
1558 std::string lower(dir); StringUtils::ToLower(lower);
1559 if (lower == "$playlists" || lower == "playlists")
1560 return "special://videoplaylists/";
1561 else if (lower == "plugins" || lower == "addons")
1562 return "addons://sources/video/";
1563 return CGUIMediaWindow::GetStartFolder(dir);
1564 }
1565
AppendAndClearSearchItems(CFileItemList & searchItems,const std::string & prependLabel,CFileItemList & results)1566 void CGUIWindowVideoBase::AppendAndClearSearchItems(CFileItemList &searchItems, const std::string &prependLabel, CFileItemList &results)
1567 {
1568 if (!searchItems.Size())
1569 return;
1570
1571 searchItems.Sort(SortByLabel, SortOrderAscending, CServiceBroker::GetSettingsComponent()->GetSettings()->GetBool(CSettings::SETTING_FILELISTS_IGNORETHEWHENSORTING) ? SortAttributeIgnoreArticle : SortAttributeNone);
1572 for (int i = 0; i < searchItems.Size(); i++)
1573 searchItems[i]->SetLabel(prependLabel + searchItems[i]->GetLabel());
1574 results.Append(searchItems);
1575
1576 searchItems.Clear();
1577 }
1578
OnUnAssignContent(const std::string & path,int header,int text)1579 bool CGUIWindowVideoBase::OnUnAssignContent(const std::string &path, int header, int text)
1580 {
1581 bool bCanceled;
1582 CVideoDatabase db;
1583 db.Open();
1584 if (CGUIDialogYesNo::ShowAndGetInput(CVariant{header}, CVariant{text}, bCanceled, CVariant{ "" }, CVariant{ "" }, CGUIDialogYesNo::NO_TIMEOUT))
1585 {
1586 CGUIDialogProgress *progress = CServiceBroker::GetGUI()->GetWindowManager().GetWindow<CGUIDialogProgress>(WINDOW_DIALOG_PROGRESS);
1587 db.RemoveContentForPath(path, progress);
1588 db.Close();
1589 CUtil::DeleteVideoDatabaseDirectoryCache();
1590 return true;
1591 }
1592 else
1593 {
1594 if (!bCanceled)
1595 {
1596 ADDON::ScraperPtr info;
1597 SScanSettings settings;
1598 settings.exclude = true;
1599 db.SetScraperForPath(path,info,settings);
1600 }
1601 }
1602 db.Close();
1603
1604 return false;
1605 }
1606
OnAssignContent(const std::string & path)1607 void CGUIWindowVideoBase::OnAssignContent(const std::string &path)
1608 {
1609 bool bScan=false;
1610 CVideoDatabase db;
1611 db.Open();
1612
1613 SScanSettings settings;
1614 ADDON::ScraperPtr info = db.GetScraperForPath(path, settings);
1615
1616 ADDON::ScraperPtr info2(info);
1617
1618 if (CGUIDialogContentSettings::Show(info, settings))
1619 {
1620 if(settings.exclude || (!info && info2))
1621 {
1622 OnUnAssignContent(path, 20375, 20340);
1623 }
1624 else if (info != info2)
1625 {
1626 if (OnUnAssignContent(path, 20442, 20443))
1627 bScan = true;
1628 }
1629 db.SetScraperForPath(path, info, settings);
1630 }
1631
1632 if (bScan)
1633 {
1634 g_application.StartVideoScan(path, true, true);
1635 }
1636 }
1637