2 * Copyright (C) 2005-2013 Team XBMC
5 * This Program is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation; either version 2, or (at your option)
10 * This Program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
15 * You should have received a copy of the GNU General Public License
16 * along with XBMC; see the file COPYING. If not, see
17 * <http://www.gnu.org/licenses/>.
22 #include "GUIUserMessages.h"
23 #include "Application.h"
24 #include "GUIDialogSubtitles.h"
25 #include "addons/AddonManager.h"
26 #include "cores/IPlayer.h"
27 #include "dialogs/GUIDialogKaiToast.h"
28 #include "filesystem/AddonsDirectory.h"
29 #include "filesystem/File.h"
30 #include "filesystem/PluginDirectory.h"
31 #include "filesystem/SpecialProtocol.h"
32 #include "filesystem/StackDirectory.h"
33 #include "guilib/GUIImage.h"
34 #include "guilib/GUIKeyboardFactory.h"
35 #include "guilib/Key.h"
36 #include "settings/MediaSettings.h"
37 #include "settings/Settings.h"
38 #include "settings/VideoSettings.h"
39 #include "settings/lib/Setting.h"
40 #include "utils/JobManager.h"
41 #include "utils/LangCodeExpander.h"
42 #include "utils/log.h"
43 #include "utils/StringUtils.h"
44 #include "utils/URIUtils.h"
47 #include "video/VideoDatabase.h"
49 using namespace ADDON;
50 using namespace XFILE;
52 #define CONTROL_NAMELABEL 100
53 #define CONTROL_NAMELOGO 110
54 #define CONTROL_SUBLIST 120
55 #define CONTROL_SUBSEXIST 130
56 #define CONTROL_SUBSTATUS 140
57 #define CONTROL_SERVICELIST 150
58 #define CONTROL_MANUALSEARCH 160
60 /*! \brief simple job to retrieve a directory and store a string (language)
62 class CSubtitlesJob: public CJob
65 CSubtitlesJob(const CURL &url, const std::string &language) : m_url(url), m_language(language)
67 m_items = new CFileItemList;
69 virtual ~CSubtitlesJob()
75 CDirectory::GetDirectory(m_url.Get(), *m_items);
76 // Sort items by path so they properly order for eg. stacks
77 m_items->Sort(SortByPath, SortOrderAscending);
80 virtual bool operator==(const CJob *job) const
82 if (strcmp(job->GetType(),GetType()) == 0)
84 const CSubtitlesJob* rjob = dynamic_cast<const CSubtitlesJob*>(job);
87 return m_url.Get() == rjob->m_url.Get() &&
88 m_language == rjob->m_language;
93 const CFileItemList *GetItems() const { return m_items; }
94 const CURL &GetURL() const { return m_url; }
95 const std::string &GetLanguage() const { return m_language; }
98 CFileItemList *m_items;
99 std::string m_language;
102 CGUIDialogSubtitles::CGUIDialogSubtitles(void)
103 : CGUIDialog(WINDOW_DIALOG_SUBTITLES, "DialogSubtitles.xml")
105 m_loadType = KEEP_IN_MEMORY;
106 m_subtitles = new CFileItemList;
107 m_serviceItems = new CFileItemList;
108 m_pausedOnRun = false;
109 m_updateSubsList = false;
112 CGUIDialogSubtitles::~CGUIDialogSubtitles(void)
116 delete m_serviceItems;
119 bool CGUIDialogSubtitles::OnMessage(CGUIMessage& message)
121 if (message.GetMessage() == GUI_MSG_CLICKED)
123 int iControl = message.GetSenderId();
124 bool selectAction = (message.GetParam1() == ACTION_SELECT_ITEM ||
125 message.GetParam1() == ACTION_MOUSE_LEFT_CLICK);
127 if (selectAction && iControl == CONTROL_SUBLIST)
129 CGUIMessage msg(GUI_MSG_ITEM_SELECTED, GetID(), CONTROL_SUBLIST);
132 int item = msg.GetParam1();
133 if (item >= 0 && item < m_subtitles->Size())
134 Download(*m_subtitles->Get(item));
137 else if (selectAction && iControl == CONTROL_SERVICELIST)
139 CGUIMessage msg(GUI_MSG_ITEM_SELECTED, GetID(), CONTROL_SERVICELIST);
142 int item = msg.GetParam1();
143 if (item >= 0 && item < m_serviceItems->Size() &&
144 SetService(m_serviceItems->Get(item)->GetProperty("Addon.ID").asString()))
149 else if (iControl == CONTROL_MANUALSEARCH)
152 if (CGUIKeyboardFactory::ShowAndGetInput(m_strManualSearch, g_localizeStrings.Get(24121), true))
154 Search(m_strManualSearch);
159 else if (message.GetMessage() == GUI_MSG_WINDOW_DEINIT)
161 // Resume the video if the user has requested it
162 if (g_application.m_pPlayer->IsPaused() && m_pausedOnRun)
163 g_application.m_pPlayer->Pause();
165 CGUIDialog::OnMessage(message);
171 return CGUIDialog::OnMessage(message);
174 void CGUIDialogSubtitles::OnInitWindow()
176 // Pause the video if the user has requested it
177 m_pausedOnRun = false;
178 if (CSettings::Get().GetBool("subtitles.pauseonsearch") && !g_application.m_pPlayer->IsPaused())
180 g_application.m_pPlayer->Pause();
181 m_pausedOnRun = true;
185 CGUIWindow::OnInitWindow();
189 void CGUIDialogSubtitles::Process(unsigned int currentTime, CDirtyRegionList &dirtyregions)
193 // take copies of our variables to ensure we don't hold the lock for long.
197 CSingleLock lock(m_critsection);
199 subs.Assign(*m_subtitles);
201 SET_CONTROL_LABEL(CONTROL_SUBSTATUS, status);
203 if (m_updateSubsList)
205 CGUIMessage message(GUI_MSG_LABEL_BIND, GetID(), CONTROL_SUBLIST, 0, 0, &subs);
207 m_updateSubsList = false;
210 int control = GetFocusedControlID();
214 CGUIMessage msg(GUI_MSG_SETFOCUS, GetID(), m_subtitles->IsEmpty() ?
215 CONTROL_SERVICELIST : CONTROL_SUBLIST);
218 // subs list is focused but we have no subs
219 else if (control == CONTROL_SUBLIST && m_subtitles->IsEmpty())
221 CGUIMessage msg(GUI_MSG_SETFOCUS, GetID(), CONTROL_SERVICELIST);
225 CGUIDialog::Process(currentTime, dirtyregions);
228 void CGUIDialogSubtitles::FillServices()
233 ADDON::CAddonMgr::Get().GetAddons(ADDON_SUBTITLE_MODULE, addons, true);
237 UpdateStatus(NO_SERVICES);
241 std::string defaultService;
242 const CFileItem &item = g_application.CurrentUnstackedItem();
243 if (item.GetVideoContentType() == VIDEODB_CONTENT_TVSHOWS ||
244 item.GetVideoContentType() == VIDEODB_CONTENT_EPISODES)
245 // Set default service for tv shows
246 defaultService = CSettings::Get().GetString("subtitles.tv");
248 // Set default service for filemode and movies
249 defaultService = CSettings::Get().GetString("subtitles.movie");
251 std::string service = addons.front()->ID();
252 for (VECADDONS::const_iterator addonIt = addons.begin(); addonIt != addons.end(); addonIt++)
254 CFileItemPtr item(CAddonsDirectory::FileItemFromAddon(*addonIt, "plugin://", false));
255 m_serviceItems->Add(item);
256 if ((*addonIt)->ID() == defaultService)
257 service = (*addonIt)->ID();
260 // Bind our services to the UI
261 CGUIMessage msg(GUI_MSG_LABEL_BIND, GetID(), CONTROL_SERVICELIST, 0, 0, m_serviceItems);
267 bool CGUIDialogSubtitles::SetService(const std::string &service)
269 if (service != m_currentService)
271 m_currentService = service;
272 CLog::Log(LOGDEBUG, "New Service [%s] ", m_currentService.c_str());
274 CFileItemPtr currentService = GetService();
275 // highlight this item in the skin
276 for (int i = 0; i < m_serviceItems->Size(); i++)
278 CFileItemPtr pItem = m_serviceItems->Get(i);
279 pItem->Select(pItem == currentService);
282 SET_CONTROL_LABEL(CONTROL_NAMELABEL, currentService->GetLabel());
284 CGUIImage* image = (CGUIImage*)GetControl(CONTROL_NAMELOGO);
287 std::string icon = URIUtils::AddFileToFolder(currentService->GetProperty("Addon.Path").asString(), "logo.png");
288 image->SetFileName(icon);
290 if (g_application.m_pPlayer->GetSubtitleCount() == 0)
291 SET_CONTROL_HIDDEN(CONTROL_SUBSEXIST);
293 SET_CONTROL_VISIBLE(CONTROL_SUBSEXIST);
300 const CFileItemPtr CGUIDialogSubtitles::GetService() const
302 for (int i = 0; i < m_serviceItems->Size(); i++)
304 if (m_serviceItems->Get(i)->GetProperty("Addon.ID") == m_currentService)
305 return m_serviceItems->Get(i);
307 return CFileItemPtr();
310 void CGUIDialogSubtitles::Search(const std::string &search/*=""*/)
312 if (m_currentService.empty())
313 return; // no services available
315 UpdateStatus(SEARCHING);
318 CURL url("plugin://" + m_currentService + "/");
321 url.SetOption("action", "manualsearch");
322 url.SetOption("searchstring", search);
325 url.SetOption("action", "search");
327 const CSetting *setting = CSettings::Get().GetSetting("subtitles.languages");
329 url.SetOption("languages", setting->ToString());
331 // Check for stacking
332 if (g_application.CurrentFileItem().IsStack())
333 url.SetOption("stack", "1");
335 AddJob(new CSubtitlesJob(url, ""));
338 void CGUIDialogSubtitles::OnJobComplete(unsigned int jobID, bool success, CJob *job)
340 const CURL &url = ((CSubtitlesJob *)job)->GetURL();
341 const CFileItemList *items = ((CSubtitlesJob *)job)->GetItems();
342 const std::string &language = ((CSubtitlesJob *)job)->GetLanguage();
343 if (url.GetOption("action") == "search" || url.GetOption("action") == "manualsearch")
344 OnSearchComplete(items);
346 OnDownloadComplete(items, language);
347 CJobQueue::OnJobComplete(jobID, success, job);
350 void CGUIDialogSubtitles::OnSearchComplete(const CFileItemList *items)
352 CSingleLock lock(m_critsection);
353 m_subtitles->Assign(*items);
354 UpdateStatus(SEARCH_COMPLETE);
355 m_updateSubsList = true;
359 void CGUIDialogSubtitles::UpdateStatus(STATUS status)
361 CSingleLock lock(m_critsection);
366 label = g_localizeStrings.Get(24114);
369 label = g_localizeStrings.Get(24107);
371 case SEARCH_COMPLETE:
372 if (!m_subtitles->IsEmpty())
373 label = StringUtils::Format(g_localizeStrings.Get(24108).c_str(), m_subtitles->Size());
375 label = g_localizeStrings.Get(24109);
378 label = g_localizeStrings.Get(24110);
383 if (label != m_status)
390 void CGUIDialogSubtitles::Download(const CFileItem &subtitle)
392 UpdateStatus(DOWNLOADING);
394 // subtitle URL should be of the form plugin://<addonid>/?param=foo¶m=bar
395 // we just append (if not already present) the action=download parameter.
396 CURL url(subtitle.GetAsUrl());
397 if (url.GetOption("action").empty())
398 url.SetOption("action", "download");
400 AddJob(new CSubtitlesJob(url, subtitle.GetLabel()));
403 void CGUIDialogSubtitles::OnDownloadComplete(const CFileItemList *items, const std::string &language)
405 if (items->IsEmpty())
407 CFileItemPtr service = GetService();
409 CGUIDialogKaiToast::QueueNotification(CGUIDialogKaiToast::Error, service->GetLabel(), g_localizeStrings.Get(24113));
410 UpdateStatus(SEARCH_COMPLETE);
414 SUBTITLE_STORAGEMODE storageMode = (SUBTITLE_STORAGEMODE) CSettings::Get().GetInt("subtitles.storagemode");
416 // Get (unstacked) path
417 const CStdString &strCurrentFile = g_application.CurrentUnstackedItem().GetPath();
419 CStdString strDownloadPath = "special://temp";
420 CStdString strDestPath;
421 std::vector<CStdString> vecFiles;
423 CStdString strCurrentFilePath = URIUtils::GetDirectory(strCurrentFile);
424 if (StringUtils::StartsWith(strCurrentFilePath, "http://"))
426 vecFiles.push_back("TemporarySubs");
430 CStdString subPath = CSpecialProtocol::TranslatePath("special://subtitles");
431 if (!subPath.empty())
432 strDownloadPath = subPath;
435 if (g_application.CurrentFileItem().IsStack() && items->Size() > 1)
437 CStackDirectory::GetPaths(g_application.CurrentFileItem().GetPath(), vecFiles);
438 // Make sure (stack) size is the same as the size of the items handed to us, else fallback to single item
439 if (items->Size() != (int) vecFiles.size())
442 vecFiles.push_back(strCurrentFile);
447 vecFiles.push_back(strCurrentFile);
450 if (storageMode == SUBTITLE_STORAGEMODE_MOVIEPATH &&
451 CUtil::SupportsWriteFileOperations(strCurrentFilePath))
453 strDestPath = strCurrentFilePath;
458 if (strDestPath.empty())
459 strDestPath = strDownloadPath;
461 // Extract the language and appropriate extension
462 CStdString strSubLang;
463 g_LangCodeExpander.ConvertToTwoCharCode(strSubLang, language);
465 // Iterate over all items to transfer
466 for (unsigned int i = 0; i < vecFiles.size() && i < (unsigned int) items->Size(); i++)
468 CStdString strUrl = items->Get(i)->GetPath();
469 CStdString strFileName = URIUtils::GetFileName(vecFiles[i]);
470 URIUtils::RemoveExtension(strFileName);
472 // construct subtitle path
473 CStdString strSubExt = URIUtils::GetExtension(strUrl);
474 CStdString strSubName = StringUtils::Format("%s.%s%s", strFileName.c_str(), strSubLang.c_str(), strSubExt.c_str());
476 // Handle URL decoding/slash correction:
477 CStdString strDownloadFile = URIUtils::ChangeBasePath(strCurrentFilePath, strSubName, strDownloadPath);
478 CStdString strDestFile = strDownloadFile;
480 if (!CFile::Cache(strUrl, strDownloadFile))
482 CGUIDialogKaiToast::QueueNotification(CGUIDialogKaiToast::Error, strSubName, g_localizeStrings.Get(24113));
486 if (strDestPath != strDownloadPath)
488 CStdString strTryDestFile = URIUtils::ChangeBasePath(strCurrentFilePath, strSubName, strDestPath);
490 /* Copy the file from temp to our final destination, if that fails fallback to download path
491 * (ie. special://subtitles or use special://temp). Note that after the first item strDownloadPath equals strDestpath
492 * so that all remaining items (including the .idx below) are copied directly to their final destination and thus all
493 * items end up in the same folder
495 CLog::Log(LOGDEBUG, "%s - Saving subtitle %s to %s", __FUNCTION__, strDownloadFile.c_str(), strDestPath.c_str());
496 if (CFile::Cache(strDownloadFile, strTryDestFile))
498 CFile::Delete(strDownloadFile);
499 strDestFile = strTryDestFile;
500 strDownloadPath = strDestPath; // Update download path so all the other items get directly downloaded to our final destination
504 CLog::Log(LOGWARNING, "%s - Saving of subtitle %s to %s failed. Falling back to %s", __FUNCTION__, strDownloadFile.c_str(), strDestPath.c_str(), strDownloadPath.c_str());
505 strDestPath = strDownloadPath; // Copy failed, use fallback for the rest of the items
509 // for ".sub" subtitles we check if ".idx" counterpart exists and copy that as well
510 if (strSubExt.Equals(".sub"))
512 strUrl = URIUtils::ReplaceExtension(strUrl, ".idx");
513 if(CFile::Exists(strUrl))
515 CStdString strSubNameIdx = StringUtils::Format("%s.%s.idx", strFileName.c_str(), strSubLang.c_str());
516 // Handle URL decoding/slash correction:
517 strDestFile = URIUtils::ChangeBasePath(strCurrentFilePath, strSubNameIdx, strDestPath);
518 CFile::Cache(strUrl, strDestFile);
522 // Set sub for currently playing (stack) item
523 if (vecFiles[i] == strCurrentFile)
524 SetSubtitles(strDestFile);
532 void CGUIDialogSubtitles::ClearSubtitles()
534 CGUIMessage msg(GUI_MSG_LABEL_RESET, GetID(), CONTROL_SUBLIST);
536 CSingleLock lock(m_critsection);
537 m_subtitles->Clear();
540 void CGUIDialogSubtitles::ClearServices()
542 CGUIMessage msg(GUI_MSG_LABEL_RESET, GetID(), CONTROL_SERVICELIST);
544 m_serviceItems->Clear();
545 m_currentService.clear();
548 void CGUIDialogSubtitles::SetSubtitles(const std::string &subtitle)
550 if (g_application.m_pPlayer)
552 int nStream = g_application.m_pPlayer->AddSubtitle(subtitle);
555 g_application.m_pPlayer->SetSubtitle(nStream);
556 g_application.m_pPlayer->SetSubtitleVisible(true);
557 CMediaSettings::Get().GetCurrentVideoSettings().m_SubtitleDelay = 0.0f;
558 g_application.m_pPlayer->SetSubTitleDelay(0);