[video] Ask the user for confirmation when turning a version into an extra or vice...
[xbmc.git] / xbmc / video / dialogs / GUIDialogVideoManagerVersions.cpp
blob1b8290be94649e1a7b58f6ca0a0e8734feb28ba1
1 /*
2 * Copyright (C) 2023 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 "GUIDialogVideoManagerVersions.h"
11 #include "FileItem.h"
12 #include "GUIUserMessages.h"
13 #include "ServiceBroker.h"
14 #include "URL.h"
15 #include "cores/VideoPlayer/DVDFileInfo.h"
16 #include "dialogs/GUIDialogFileBrowser.h"
17 #include "dialogs/GUIDialogOK.h"
18 #include "dialogs/GUIDialogSelect.h"
19 #include "dialogs/GUIDialogYesNo.h"
20 #include "guilib/GUIComponent.h"
21 #include "guilib/GUIWindowManager.h"
22 #include "guilib/LocalizeStrings.h"
23 #include "settings/MediaSourceSettings.h"
24 #include "settings/Settings.h"
25 #include "settings/SettingsComponent.h"
26 #include "storage/MediaManager.h"
27 #include "utils/FileExtensionProvider.h"
28 #include "utils/StringUtils.h"
29 #include "utils/log.h"
30 #include "video/VideoManagerTypes.h"
31 #include "video/VideoThumbLoader.h"
33 #include <algorithm>
34 #include <string>
36 static constexpr unsigned int CONTROL_BUTTON_ADD_VERSION = 22;
37 static constexpr unsigned int CONTROL_BUTTON_SET_DEFAULT = 25;
39 CGUIDialogVideoManagerVersions::CGUIDialogVideoManagerVersions()
40 : CGUIDialogVideoManager(WINDOW_DIALOG_MANAGE_VIDEO_VERSIONS),
41 m_defaultVideoVersion(std::make_shared<CFileItem>())
45 VideoAssetType CGUIDialogVideoManagerVersions::GetVideoAssetType()
47 return VideoAssetType::VERSION;
50 bool CGUIDialogVideoManagerVersions::OnMessage(CGUIMessage& message)
52 switch (message.GetMessage())
54 case GUI_MSG_CLICKED:
56 const int control{message.GetSenderId()};
57 if (control == CONTROL_BUTTON_ADD_VERSION)
59 if (AddVideoVersion())
61 // refresh data and controls
62 Refresh();
63 UpdateControls();
64 m_hasUpdatedItems = true;
67 else if (control == CONTROL_BUTTON_SET_DEFAULT)
69 SetDefault();
71 break;
75 return CGUIDialogVideoManager::OnMessage(message);
78 void CGUIDialogVideoManagerVersions::Clear()
80 m_defaultVideoVersion = std::make_shared<CFileItem>();
81 CGUIDialogVideoManager::Clear();
84 void CGUIDialogVideoManagerVersions::UpdateButtons()
86 CGUIDialogVideoManager::UpdateButtons();
88 // Always anabled
89 CONTROL_ENABLE(CONTROL_BUTTON_ADD_VERSION);
91 // Enabled for non-default version only
92 if (m_selectedVideoAsset->GetVideoInfoTag()->m_iDbId ==
93 m_defaultVideoVersion->GetVideoInfoTag()->m_iDbId)
95 DisableRemove();
96 CONTROL_DISABLE(CONTROL_BUTTON_SET_DEFAULT);
98 else
100 EnableRemove();
101 CONTROL_ENABLE(CONTROL_BUTTON_SET_DEFAULT);
104 // Enabled if list not empty
105 if (m_videoAssetsList->IsEmpty())
107 SET_CONTROL_FOCUS(CONTROL_BUTTON_ADD_VERSION, 0);
111 void CGUIDialogVideoManagerVersions::UpdateDefaultVideoVersionSelection()
113 // find new item in list and select it
114 const int defaultDbId{m_defaultVideoVersion->GetVideoInfoTag()->m_iDbId};
115 for (const auto& item : *m_videoAssetsList)
117 item->Select(item->GetVideoInfoTag()->m_iDbId == defaultDbId);
121 void CGUIDialogVideoManagerVersions::Refresh()
123 CGUIDialogVideoManager::Refresh();
125 // update default video version
126 const int dbId{m_videoAsset->GetVideoInfoTag()->m_iDbId};
127 const VideoDbContentType itemType{m_videoAsset->GetVideoContentType()};
128 m_database.GetDefaultVideoVersion(itemType, dbId, *m_defaultVideoVersion);
130 UpdateDefaultVideoVersionSelection();
133 void CGUIDialogVideoManagerVersions::SetVideoAsset(const std::shared_ptr<CFileItem>& item)
135 CGUIDialogVideoManager::SetVideoAsset(item);
137 SetSelectedVideoAsset(m_defaultVideoVersion);
140 void CGUIDialogVideoManagerVersions::Remove()
142 const MediaType mediaType{m_videoAsset->GetVideoInfoTag()->m_type};
144 // default video version is not allowed
145 if (m_database.IsDefaultVideoVersion(m_selectedVideoAsset->GetVideoInfoTag()->m_iDbId))
147 CGUIDialogOK::ShowAndGetInput(CVariant{40018}, CVariant{40019});
148 return;
151 CGUIDialogVideoManager::Remove();
154 void CGUIDialogVideoManagerVersions::SetDefault()
156 // set the selected video version as default
157 SetDefaultVideoVersion(*m_selectedVideoAsset);
159 const int dbId{m_videoAsset->GetVideoInfoTag()->m_iDbId};
160 const VideoDbContentType itemType{m_videoAsset->GetVideoContentType()};
162 // update our default video version
163 m_database.GetDefaultVideoVersion(itemType, dbId, *m_defaultVideoVersion);
165 UpdateControls();
166 UpdateDefaultVideoVersionSelection();
169 void CGUIDialogVideoManagerVersions::SetDefaultVideoVersion(const CFileItem& version)
171 const int dbId{m_videoAsset->GetVideoInfoTag()->m_iDbId};
172 const VideoDbContentType itemType{m_videoAsset->GetVideoContentType()};
174 // set the specified video version as default
175 m_database.SetDefaultVideoVersion(itemType, dbId, version.GetVideoInfoTag()->m_iDbId);
177 // update the video item
178 m_videoAsset->SetPath(version.GetPath());
179 m_videoAsset->SetDynPath(version.GetPath());
181 // update video details since we changed the video file for the item
182 m_database.GetDetailsByTypeAndId(*m_videoAsset, itemType, dbId);
184 // notify all windows to update the file item
185 CGUIMessage msg{GUI_MSG_NOTIFY_ALL, 0, 0, GUI_MSG_UPDATE_ITEM,
186 GUI_MSG_FLAG_FORCE_UPDATE, m_videoAsset};
187 CServiceBroker::GetGUI()->GetWindowManager().SendMessage(msg);
190 bool CGUIDialogVideoManagerVersions::AddVideoVersion()
192 if (!m_videoAsset || !m_videoAsset->HasVideoInfoTag())
194 CLog::LogF(LOGERROR, "invalid video asset");
195 return false;
198 CVideoDatabase videoDb;
199 if (!videoDb.Open())
201 CLog::LogF(LOGERROR, "Failed to open video database!");
202 return false;
205 CFileItemList items;
206 if (!GetSimilarMovies(m_videoAsset, items, videoDb))
207 return false;
209 if (items.Size() == 0)
211 // No button = browse library
212 // Yes button = browse files
213 // Custom button = cancel
215 const int dlgResult{CGUIDialogYesNo::ShowAndGetInput(
216 CVariant{40030}, CVariant{40032}, CVariant{40029}, CVariant{40028}, CVariant{222},
217 CGUIDialogYesNo::NO_TIMEOUT)};
219 switch (dlgResult)
221 case CGUIDialogYesNo::DIALOG_RESULT_CANCEL:
222 case CGUIDialogYesNo::DIALOG_RESULT_CUSTOM:
223 // Dialog dismissed or Cancel button
224 return false;
226 case CGUIDialogYesNo::DIALOG_RESULT_NO:
228 // Browse library
229 if (!GetAllOtherMovies(m_videoAsset, items, videoDb))
230 return false;
232 const auto tag{m_videoAsset->GetVideoInfoTag()};
234 return ChooseVideoAndConvertToVideoVersion(items, m_videoAsset->GetVideoContentType(),
235 tag->m_type, tag->m_iDbId, videoDb,
236 MediaRole::Parent);
239 case CGUIDialogYesNo::DIALOG_RESULT_YES:
240 // Browse files
241 return AddVideoVersionFilePicker();
244 CLog::LogF(LOGERROR, "Unknown return value {} from CGUIDialogYesNo", dlgResult);
245 return false;
248 CGUIDialogSelect* dialog{CServiceBroker::GetGUI()->GetWindowManager().GetWindow<CGUIDialogSelect>(
249 WINDOW_DIALOG_SELECT)};
251 if (!dialog)
253 CLog::LogF(LOGERROR, "Unable to get WINDOW_DIALOG_SELECT instance!");
254 return false;
257 // Load thumbs async
258 CVideoThumbLoader loader;
259 loader.Load(items);
261 dialog->Reset();
262 dialog->SetItems(items);
263 dialog->SetHeading(40030);
264 dialog->SetUseDetails(true);
265 dialog->EnableButton(true, 40028); // Browse files
266 dialog->EnableButton2(true, 40029); // Browse library
267 dialog->Open();
269 if (loader.IsLoading())
270 loader.StopThread();
272 if (dialog->IsConfirmed())
274 // A similar movie was selected
275 return AddSimilarMovieAsVersion(dialog->GetSelectedFileItem());
277 else if (dialog->IsButtonPressed())
279 // User wants to browse the files
280 return AddVideoVersionFilePicker();
282 else if (dialog->IsButton2Pressed())
284 // User wants to browse the library
285 if (!GetAllOtherMovies(m_videoAsset, items, videoDb))
286 return false;
288 const auto tag{m_videoAsset->GetVideoInfoTag()};
290 return ChooseVideoAndConvertToVideoVersion(items, m_videoAsset->GetVideoContentType(),
291 tag->m_type, tag->m_iDbId, videoDb,
292 MediaRole::Parent);
294 return false;
297 bool CGUIDialogVideoManagerVersions::ManageVideoVersions(const std::shared_ptr<CFileItem>& item)
299 CGUIDialogVideoManagerVersions* dialog{
300 CServiceBroker::GetGUI()->GetWindowManager().GetWindow<CGUIDialogVideoManagerVersions>(
301 WINDOW_DIALOG_MANAGE_VIDEO_VERSIONS)};
302 if (!dialog)
304 CLog::LogF(LOGERROR, "Unable to get WINDOW_DIALOG_MANAGE_VIDEO_VERSIONS instance!");
305 return false;
308 dialog->SetVideoAsset(item);
309 dialog->Open();
310 return dialog->HasUpdatedItems();
313 bool CGUIDialogVideoManagerVersions::ChooseVideoAndConvertToVideoVersion(
314 CFileItemList& items,
315 VideoDbContentType itemType,
316 const std::string& mediaType,
317 int dbId,
318 CVideoDatabase& videoDb,
319 MediaRole role)
321 if (items.Size() == 0)
323 CGUIDialogOK::ShowAndGetInput(role == MediaRole::NewVersion ? 40002 : 40030, 40031);
324 return false;
327 // choose a video
328 CGUIDialogSelect* dialog{CServiceBroker::GetGUI()->GetWindowManager().GetWindow<CGUIDialogSelect>(
329 WINDOW_DIALOG_SELECT)};
330 if (!dialog)
332 CLog::LogF(LOGERROR, "Unable to get WINDOW_DIALOG_SELECT instance!");
333 return false;
336 // Load thumbs async
337 CVideoThumbLoader loader;
338 loader.Load(items);
340 dialog->Reset();
341 dialog->SetItems(items);
342 dialog->SetHeading(role == MediaRole::NewVersion ? 40002 : 40030);
343 dialog->SetUseDetails(true);
344 dialog->Open();
346 if (loader.IsLoading())
347 loader.StopThread();
349 if (!dialog->IsConfirmed())
350 return false;
352 const std::shared_ptr<CFileItem> selectedItem{dialog->GetSelectedFileItem()};
353 if (!selectedItem)
354 return false;
356 // choose a video version for the video
357 const int idVideoVersion{ChooseVideoAsset(selectedItem, VideoAssetType::VERSION)};
358 if (idVideoVersion < 0)
359 return false;
361 int sourceDbId, targetDbId;
362 switch (role)
364 case MediaRole::NewVersion:
365 sourceDbId = dbId;
366 targetDbId = selectedItem->GetVideoInfoTag()->m_iDbId;
367 break;
368 case MediaRole::Parent:
369 sourceDbId = selectedItem->GetVideoInfoTag()->m_iDbId;
370 targetDbId = dbId;
371 break;
372 default:
373 return false;
376 return videoDb.ConvertVideoToVersion(itemType, sourceDbId, targetDbId, idVideoVersion);
379 bool CGUIDialogVideoManagerVersions::GetAllOtherMovies(const std::shared_ptr<CFileItem>& item,
380 CFileItemList& list,
381 CVideoDatabase& videoDb)
383 if (!item || !item->HasVideoInfoTag())
384 return false;
386 // get video list
387 const std::string videoTitlesDir{StringUtils::Format(
388 "videodb://{}/titles", CMediaTypes::ToPlural(item->GetVideoInfoTag()->m_type))};
390 list.Clear();
392 if (item->GetVideoContentType() == VideoDbContentType::MOVIES)
393 videoDb.GetMoviesNav(videoTitlesDir, list);
394 else
395 return false;
397 if (list.Size() < 2)
398 return false;
400 list.Sort(SortByLabel, SortOrderAscending,
401 CServiceBroker::GetSettingsComponent()->GetSettings()->GetBool(
402 CSettings::SETTING_FILELISTS_IGNORETHEWHENSORTING)
403 ? SortAttributeIgnoreArticle
404 : SortAttributeNone);
406 if (!PostProcessList(list, item->GetVideoInfoTag()->m_iDbId))
407 return false;
409 return true;
412 bool CGUIDialogVideoManagerVersions::ProcessVideoVersion(VideoDbContentType itemType, int dbId)
414 if (itemType != VideoDbContentType::MOVIES)
415 return false;
417 CVideoDatabase videodb;
418 if (!videodb.Open())
420 CLog::LogF(LOGERROR, "Failed to open video database!");
421 return false;
424 CFileItem item;
425 if (!videodb.GetDetailsByTypeAndId(item, itemType, dbId))
426 return false;
428 CFileItemList list;
429 videodb.GetSameVideoItems(item, list);
431 if (list.Size() < 2)
432 return false;
434 const MediaType mediaType{item.GetVideoInfoTag()->m_type};
436 std::string path;
437 videodb.GetFilePathById(dbId, path, itemType);
439 if (!CGUIDialogYesNo::ShowAndGetInput(
440 CVariant{40008}, StringUtils::Format(g_localizeStrings.Get(40009),
441 item.GetVideoInfoTag()->GetTitle(), path)))
443 return false;
446 if (!PostProcessList(list, dbId))
447 return false;
449 return ChooseVideoAndConvertToVideoVersion(list, itemType, mediaType, dbId, videodb,
450 MediaRole::NewVersion);
453 bool CGUIDialogVideoManagerVersions::AddVideoVersionFilePicker()
455 // @todo: combine with extras add file logic, structured similarly and sharing most logic.
457 const MediaType mediaType{m_videoAsset->GetVideoInfoTag()->m_type};
459 // prompt to choose a video file
460 VECSOURCES sources{*CMediaSourceSettings::GetInstance().GetSources("files")};
462 CServiceBroker::GetMediaManager().GetLocalDrives(sources);
463 CServiceBroker::GetMediaManager().GetNetworkLocations(sources);
464 AppendItemFolderToFileBrowserSources(sources);
466 std::string path;
467 if (CGUIDialogFileBrowser::ShowAndGetFile(
468 sources, CServiceBroker::GetFileExtensionProvider().GetVideoExtensions(),
469 g_localizeStrings.Get(40014), path))
471 const int dbId{m_videoAsset->GetVideoInfoTag()->m_iDbId};
472 const VideoDbContentType itemType{m_videoAsset->GetVideoContentType()};
474 const VideoAssetInfo newAsset{m_database.GetVideoVersionInfo(path)};
476 // @todo look only for a version identified by idFile instead of retrieving all versions
477 if (newAsset.m_idFile != -1 && newAsset.m_assetTypeId != -1)
479 // The video already is an asset of the movie
480 if (newAsset.m_idMedia == dbId &&
481 newAsset.m_mediaType == m_videoAsset->GetVideoInfoTag()->m_type)
483 unsigned int msgid{};
485 if (newAsset.m_assetType == VideoAssetType::VERSION)
486 msgid = 40016; // video is a version of the movie
487 else if (newAsset.m_assetType == VideoAssetType::EXTRA)
488 msgid = 40026; // video is an extra of the movie
489 else
491 CLog::LogF(LOGERROR, "unexpected asset type {}", static_cast<int>(newAsset.m_assetType));
492 return false;
495 CGUIDialogOK::ShowAndGetInput(
496 CVariant{40014},
497 StringUtils::Format(g_localizeStrings.Get(msgid), newAsset.m_assetTypeName));
498 return false;
501 // The video is an asset of another movie
503 // The video is an extra, ask for confirmation
504 if (newAsset.m_assetType == VideoAssetType::EXTRA &&
505 !CGUIDialogYesNo::ShowAndGetInput(CVariant{40014},
506 StringUtils::Format(g_localizeStrings.Get(40035))))
508 return false;
511 std::string videoTitle;
512 if (newAsset.m_mediaType == MediaTypeMovie)
514 videoTitle = m_database.GetMovieTitle(newAsset.m_idMedia);
516 else
517 return false;
520 unsigned int msgid{};
522 if (newAsset.m_assetType == VideoAssetType::VERSION)
523 msgid = 40017; // video is a version of another movie
524 else if (newAsset.m_assetType == VideoAssetType::EXTRA)
525 msgid = 40027; // video is an extra of another movie
526 else
528 CLog::LogF(LOGERROR, "unexpected asset type {}", static_cast<int>(newAsset.m_assetType));
529 return false;
532 if (!CGUIDialogYesNo::ShowAndGetInput(
533 CVariant{40014}, StringUtils::Format(g_localizeStrings.Get(msgid),
534 newAsset.m_assetTypeName, videoTitle)))
536 return false;
540 // Additional constraints for the conversion of a movie version
541 if (newAsset.m_assetType == VideoAssetType::VERSION &&
542 m_database.IsDefaultVideoVersion(newAsset.m_idFile))
544 CFileItemList list;
545 m_database.GetVideoVersions(itemType, newAsset.m_idMedia, list, newAsset.m_assetType);
547 if (list.Size() > 1)
549 // cannot add the default version of a movie with multiple versions to another movie
550 CGUIDialogOK::ShowAndGetInput(CVariant{40014}, CVariant{40033});
551 return false;
553 else
555 if (newAsset.m_mediaType == MediaTypeMovie)
557 // @todo: should be in a database transaction with the addition as a new asset below
558 m_database.DeleteMovie(newAsset.m_idMedia);
560 else
561 return false;
564 else
566 // @todo: should be in a database transaction with the addition as a new asset below
567 if (!m_database.RemoveVideoVersion(newAsset.m_idFile))
568 return false;
572 CFileItem item{path, false};
574 if (CServiceBroker::GetSettingsComponent()->GetSettings()->GetBool(
575 CSettings::SETTING_MYVIDEOS_EXTRACTFLAGS))
577 CDVDFileInfo::GetFileStreamDetails(&item);
578 CLog::LogF(LOGDEBUG, "Extracted filestream details from video file {}",
579 CURL::GetRedacted(item.GetPath()));
582 const int idNewVideoVersion{ChooseVideoAsset(m_videoAsset, VideoAssetType::VERSION)};
583 if (idNewVideoVersion == -1)
584 return false;
586 m_database.AddPrimaryVideoVersion(itemType, dbId, idNewVideoVersion, item);
588 return true;
590 return false;
593 bool CGUIDialogVideoManagerVersions::GetSimilarMovies(const std::shared_ptr<CFileItem>& item,
594 CFileItemList& list,
595 CVideoDatabase& videoDb)
597 list.Clear();
599 videoDb.GetSameVideoItems(*item, list);
601 if (list.Size() < 2)
603 list.Clear();
604 return true;
607 if (!PostProcessList(list, item->GetVideoInfoTag()->m_iDbId))
608 return false;
610 return true;
613 bool CGUIDialogVideoManagerVersions::AddSimilarMovieAsVersion(
614 const std::shared_ptr<CFileItem> itemMovie)
616 // A movie with versions cannot be turned into a version
617 if (itemMovie->GetVideoInfoTag()->HasVideoVersions())
619 CGUIDialogOK::ShowAndGetInput(CVariant{40005}, CVariant{40006});
620 return false;
623 // choose a video version type for the video
624 const int idVideoVersion{ChooseVideoAsset(itemMovie, VideoAssetType::VERSION)};
625 if (idVideoVersion < 0)
626 return false;
628 CVideoDatabase videoDb;
629 if (!videoDb.Open())
631 CLog::LogF(LOGERROR, "Failed to open video database!");
632 return false;
635 const int sourceDbId{itemMovie->GetVideoInfoTag()->m_iDbId};
636 const int targetDbId{m_videoAsset->GetVideoInfoTag()->m_iDbId};
637 return videoDb.ConvertVideoToVersion(VideoDbContentType::MOVIES, sourceDbId, targetDbId,
638 idVideoVersion);
641 bool CGUIDialogVideoManagerVersions::PostProcessList(CFileItemList& list, int dbId)
643 // Exclude the provided dbId and decorate the items
645 int i = 0;
646 while (i < list.Size())
648 const auto item{list[i]};
649 const auto itemtag{item->GetVideoInfoTag()};
651 if (itemtag->m_iDbId == dbId)
653 list.Remove(i);
654 // i is not incremented for the next iteration because the removal shifted what would have
655 // been the next item into the current position.
656 continue;
659 item->SetLabel2(itemtag->m_strFileNameAndPath);
660 ++i;
663 return true;