Merge pull request #3840 from FernetMenta/linux
[vuplus_xbmc] / xbmc / video / dialogs / GUIDialogSubtitles.cpp
1 /*
2  *      Copyright (C) 2005-2013 Team XBMC
3  *      http://xbmc.org
4  *
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)
8  *  any later version.
9  *
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.
14  *
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/>.
18  *
19  */
20
21 #include "system.h"
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 "guilib/GUIImage.h"
33 #include "settings/MediaSettings.h"
34 #include "settings/Settings.h"
35 #include "settings/VideoSettings.h"
36 #include "settings/lib/Setting.h"
37 #include "utils/JobManager.h"
38 #include "utils/LangCodeExpander.h"
39 #include "utils/log.h"
40 #include "utils/StringUtils.h"
41 #include "utils/URIUtils.h"
42 #include "URL.h"
43 #include "Util.h"
44 #include "video/VideoDatabase.h"
45
46 using namespace ADDON;
47 using namespace XFILE;
48
49 #define CONTROL_NAMELABEL            100
50 #define CONTROL_NAMELOGO             110
51 #define CONTROL_SUBLIST              120
52 #define CONTROL_SUBSEXIST            130
53 #define CONTROL_SUBSTATUS            140
54 #define CONTROL_SERVICELIST          150
55
56 /*! \brief simple job to retrieve a directory and store a string (language)
57  */
58 class CSubtitlesJob: public CJob
59 {
60 public:
61   CSubtitlesJob(const CURL &url, const std::string &language) : m_url(url), m_language(language)
62   {
63     m_items = new CFileItemList;
64   }
65   virtual ~CSubtitlesJob()
66   {
67     delete m_items;
68   }
69   virtual bool DoWork()
70   {
71     CDirectory::GetDirectory(m_url.Get(), *m_items);
72     return true;
73   }
74   virtual bool operator==(const CJob *job) const
75   {
76     if (strcmp(job->GetType(),GetType()) == 0)
77     {
78       const CSubtitlesJob* rjob = dynamic_cast<const CSubtitlesJob*>(job);
79       if (rjob)
80       {
81         return m_url.Get() == rjob->m_url.Get() &&
82                m_language == rjob->m_language;
83       }
84     }
85     return false;
86   }
87   const CFileItemList *GetItems() const { return m_items; }
88   const CURL &GetURL() const { return m_url; }
89   const std::string &GetLanguage() const { return m_language; }
90 private:
91   CURL           m_url;
92   CFileItemList *m_items;
93   std::string    m_language;
94 };
95
96 CGUIDialogSubtitles::CGUIDialogSubtitles(void)
97     : CGUIDialog(WINDOW_DIALOG_SUBTITLES, "DialogSubtitles.xml")
98 {
99   m_loadType  = KEEP_IN_MEMORY;
100   m_subtitles = new CFileItemList;
101   m_serviceItems = new CFileItemList;
102   m_pausedOnRun = false;
103   m_updateSubsList = false;
104 }
105
106 CGUIDialogSubtitles::~CGUIDialogSubtitles(void)
107 {
108   CancelJobs();
109   delete m_subtitles;
110   delete m_serviceItems;
111 }
112
113 bool CGUIDialogSubtitles::OnMessage(CGUIMessage& message)
114 {
115   if  (message.GetMessage() == GUI_MSG_CLICKED)
116   {
117     int iControl = message.GetSenderId();
118
119     if (iControl == CONTROL_SUBLIST)
120     {
121       CGUIMessage msg(GUI_MSG_ITEM_SELECTED, GetID(), CONTROL_SUBLIST);
122       OnMessage(msg);
123
124       int item = msg.GetParam1();
125       if (item >= 0 && item < m_subtitles->Size())
126         Download(*m_subtitles->Get(item));
127       return true;
128     }
129     else if (iControl == CONTROL_SERVICELIST)
130     {
131       CGUIMessage msg(GUI_MSG_ITEM_SELECTED, GetID(), CONTROL_SERVICELIST);
132       OnMessage(msg);
133
134       int item = msg.GetParam1();
135       if (item >= 0 && item < m_serviceItems->Size() &&
136           SetService(m_serviceItems->Get(item)->GetProperty("Addon.ID").asString()))
137         Search();
138
139       return true;
140     }
141   }
142   else if (message.GetMessage() == GUI_MSG_WINDOW_DEINIT)
143   {
144     // Resume the video if the user has requested it
145     if (g_application.m_pPlayer->IsPaused() && m_pausedOnRun)
146       g_application.m_pPlayer->Pause();
147
148     CGUIDialog::OnMessage(message);
149
150     ClearSubtitles();
151     ClearServices();
152     return true;
153   }
154   return CGUIDialog::OnMessage(message);
155 }
156
157 void CGUIDialogSubtitles::OnInitWindow()
158 {
159   // Pause the video if the user has requested it
160   m_pausedOnRun = false;
161   if (CSettings::Get().GetBool("subtitles.pauseonsearch") && !g_application.m_pPlayer->IsPaused())
162   {
163     g_application.m_pPlayer->Pause();
164     m_pausedOnRun = true;
165   }
166
167   FillServices();
168   CGUIWindow::OnInitWindow();
169   Search();
170 }
171
172 void CGUIDialogSubtitles::Process(unsigned int currentTime, CDirtyRegionList &dirtyregions)
173 {
174   if (m_bInvalidated)
175   {
176     // take copies of our variables to ensure we don't hold the lock for long.
177     std::string status;
178     CFileItemList subs;
179     {
180       CSingleLock lock(m_critsection);
181       status = m_status;
182       subs.Assign(*m_subtitles);
183     }
184     SET_CONTROL_LABEL(CONTROL_SUBSTATUS, status);
185
186     if (m_updateSubsList)
187     {
188       CGUIMessage message(GUI_MSG_LABEL_BIND, GetID(), CONTROL_SUBLIST, 0, 0, &subs);
189       OnMessage(message);
190       m_updateSubsList = false;
191     }
192     
193     int control = GetFocusedControlID();
194     // nothing has focus
195     if (!control)
196     {
197       CGUIMessage msg(GUI_MSG_SETFOCUS, GetID(), m_subtitles->IsEmpty() ?
198                       CONTROL_SERVICELIST : CONTROL_SUBLIST);
199       OnMessage(msg);
200     }
201     // subs list is focused but we have no subs
202     else if (control == CONTROL_SUBLIST && m_subtitles->IsEmpty())
203     {
204       CGUIMessage msg(GUI_MSG_SETFOCUS, GetID(), CONTROL_SERVICELIST);
205       OnMessage(msg);
206     }
207   }
208   CGUIDialog::Process(currentTime, dirtyregions);
209 }
210
211 void CGUIDialogSubtitles::FillServices()
212 {
213   ClearServices();
214
215   VECADDONS addons;
216   ADDON::CAddonMgr::Get().GetAddons(ADDON_SUBTITLE_MODULE, addons, true);
217
218   if (addons.empty())
219   {
220     UpdateStatus(NO_SERVICES);
221     return;
222   }
223
224   std::string defaultService;
225   const CFileItem &item = g_application.CurrentFileItem();
226   if (item.GetVideoContentType() == VIDEODB_CONTENT_TVSHOWS ||
227       item.GetVideoContentType() == VIDEODB_CONTENT_EPISODES)
228     // Set default service for tv shows
229     defaultService = CSettings::Get().GetString("subtitles.tv");
230   else
231     // Set default service for filemode and movies
232     defaultService = CSettings::Get().GetString("subtitles.movie");
233   
234   std::string service = addons.front()->ID();
235   for (VECADDONS::const_iterator addonIt = addons.begin(); addonIt != addons.end(); addonIt++)
236   {
237     CFileItemPtr item(CAddonsDirectory::FileItemFromAddon(*addonIt, "plugin://", false));
238     m_serviceItems->Add(item);
239     if ((*addonIt)->ID() == defaultService)
240       service = (*addonIt)->ID();
241   }
242
243   // Bind our services to the UI
244   CGUIMessage msg(GUI_MSG_LABEL_BIND, GetID(), CONTROL_SERVICELIST, 0, 0, m_serviceItems);
245   OnMessage(msg);
246
247   SetService(service);
248 }
249
250 bool CGUIDialogSubtitles::SetService(const std::string &service)
251 {
252   if (service != m_currentService)
253   {
254     m_currentService = service;
255     CLog::Log(LOGDEBUG, "New Service [%s] ", m_currentService.c_str());
256
257     CFileItemPtr currentService = GetService();
258     // highlight this item in the skin
259     for (int i = 0; i < m_serviceItems->Size(); i++)
260     {
261       CFileItemPtr pItem = m_serviceItems->Get(i);
262       pItem->Select(pItem == currentService);
263     }
264
265     SET_CONTROL_LABEL(CONTROL_NAMELABEL, currentService->GetLabel());
266
267     CGUIImage* image = (CGUIImage*)GetControl(CONTROL_NAMELOGO);
268     if (image)
269     {
270       std::string icon = URIUtils::AddFileToFolder(currentService->GetProperty("Addon.Path").asString(), "logo.png");
271       image->SetFileName(icon);
272     }
273     if (g_application.m_pPlayer->GetSubtitleCount() == 0)
274       SET_CONTROL_HIDDEN(CONTROL_SUBSEXIST);
275     else
276       SET_CONTROL_VISIBLE(CONTROL_SUBSEXIST);
277
278     return true;
279   }
280   return false;
281 }
282
283 const CFileItemPtr CGUIDialogSubtitles::GetService() const
284 {
285   for (int i = 0; i < m_serviceItems->Size(); i++)
286   {
287     if (m_serviceItems->Get(i)->GetProperty("Addon.ID") == m_currentService)
288       return m_serviceItems->Get(i);
289   }
290   return CFileItemPtr();
291 }
292
293 void CGUIDialogSubtitles::Search()
294 {
295   if (m_currentService.empty())
296     return; // no services available
297
298   UpdateStatus(SEARCHING);
299   ClearSubtitles();
300
301   CURL url("plugin://" + m_currentService + "/");
302   url.SetOption("action", "search");
303
304   const CSetting *setting = CSettings::Get().GetSetting("subtitles.languages");
305   if (setting)
306     url.SetOption("languages", setting->ToString());
307
308   AddJob(new CSubtitlesJob(url, ""));
309 }
310
311 void CGUIDialogSubtitles::OnJobComplete(unsigned int jobID, bool success, CJob *job)
312 {
313   const CURL &url             = ((CSubtitlesJob *)job)->GetURL();
314   const CFileItemList *items  = ((CSubtitlesJob *)job)->GetItems();
315   const std::string &language = ((CSubtitlesJob *)job)->GetLanguage();
316   if (url.GetOption("action") == "search")
317     OnSearchComplete(items);
318   else
319     OnDownloadComplete(items, language);
320   CJobQueue::OnJobComplete(jobID, success, job);
321 }
322
323 void CGUIDialogSubtitles::OnSearchComplete(const CFileItemList *items)
324 {
325   CSingleLock lock(m_critsection);
326   m_subtitles->Assign(*items);
327   UpdateStatus(SEARCH_COMPLETE);
328   m_updateSubsList = true;
329   SetInvalid();
330 }
331
332 void CGUIDialogSubtitles::UpdateStatus(STATUS status)
333 {
334   CSingleLock lock(m_critsection);
335   std::string label;
336   switch (status)
337   {
338     case NO_SERVICES:
339       label = g_localizeStrings.Get(24114);
340       break;
341     case SEARCHING:
342       label = g_localizeStrings.Get(24107);
343       break;
344     case SEARCH_COMPLETE:
345       if (!m_subtitles->IsEmpty())
346         label = StringUtils::Format(g_localizeStrings.Get(24108).c_str(), m_subtitles->Size());
347       else
348         label = g_localizeStrings.Get(24109);
349       break;
350     case DOWNLOADING:
351       label = g_localizeStrings.Get(24110);
352       break;
353     default:
354       break;
355   }
356   if (label != m_status)
357   {
358     m_status = label;
359     SetInvalid();
360   }
361 }
362
363 void CGUIDialogSubtitles::Download(const CFileItem &subtitle)
364 {
365   UpdateStatus(DOWNLOADING);
366
367   // subtitle URL should be of the form plugin://<addonid>/?param=foo&param=bar
368   // we just append (if not already present) the action=download parameter.
369   CURL url(subtitle.GetAsUrl());
370   if (url.GetOption("action").empty())
371     url.SetOption("action", "download");
372
373   AddJob(new CSubtitlesJob(url, subtitle.GetLabel()));
374 }
375
376 void CGUIDialogSubtitles::OnDownloadComplete(const CFileItemList *items, const std::string &language)
377 {
378   if (items->IsEmpty())
379   {
380     CFileItemPtr service = GetService();
381     if (service)
382       CGUIDialogKaiToast::QueueNotification(CGUIDialogKaiToast::Error, service->GetLabel(), g_localizeStrings.Get(24113));
383     UpdateStatus(SEARCH_COMPLETE);
384     return;
385   }
386
387   CStdString strFileName;
388   CStdString strDestPath;
389   if (g_application.CurrentFileItem().IsStack())
390   {
391     for (int i = 0; i < items->Size(); i++)
392     {
393 //    check for all stack items and match to given subs, item [0] == CD1, item [1] == CD2
394 //    CLog::Log(LOGDEBUG, "Stack Subs [%s} Found", vecItems[i]->GetLabel().c_str());
395     }
396   }
397   else if (StringUtils::StartsWith(g_application.CurrentFile(), "http://"))
398   {
399     strFileName = "TemporarySubs";
400     strDestPath = "special://temp/";
401   }
402   else
403   {
404     strFileName = URIUtils::GetFileName(g_application.CurrentFile());
405     if (CSettings::Get().GetBool("subtitles.savetomoviefolder"))
406     {
407       strDestPath = URIUtils::GetDirectory(g_application.CurrentFile());
408       if (!CUtil::SupportsWriteFileOperations(strDestPath))
409         strDestPath.clear();
410     }
411     if (strDestPath.empty())
412     {
413       if (CSpecialProtocol::TranslatePath("special://subtitles").empty())
414         strDestPath = "special://temp";
415       else
416         strDestPath = "special://subtitles";
417     }
418   }
419   // Extract the language and appropriate extension
420   CStdString strSubLang;
421   g_LangCodeExpander.ConvertToTwoCharCode(strSubLang, language);
422   CStdString strUrl = items->Get(0)->GetPath();
423   CStdString strSubExt = URIUtils::GetExtension(strUrl);
424
425   // construct subtitle path
426   URIUtils::RemoveExtension(strFileName);
427   CStdString strSubName = StringUtils::Format("%s.%s%s", strFileName.c_str(), strSubLang.c_str(), strSubExt.c_str());
428   CStdString strSubPath = URIUtils::AddFileToFolder(strDestPath, strSubName);
429
430   // and copy the file across
431   CFile::Cache(strUrl, strSubPath);
432
433   // for ".sub" subtitles we check if ".idx" counterpart exists and copy that as well
434   if (strSubExt.Equals(".sub"))
435   {
436     strUrl = URIUtils::ReplaceExtension(strUrl, ".idx");
437     if(CFile::Exists(strUrl))
438     {
439       CStdString strSubNameIdx = StringUtils::Format("%s.%s.idx", strFileName.c_str(), strSubLang.c_str());
440       strSubPath = URIUtils::AddFileToFolder(strDestPath, strSubNameIdx);
441       CFile::Cache(strUrl, strSubPath);
442     }
443   }
444
445   SetSubtitles(strSubPath);
446   // Close the window
447   Close();
448 }
449
450 void CGUIDialogSubtitles::ClearSubtitles()
451 {
452   CGUIMessage msg(GUI_MSG_LABEL_RESET, GetID(), CONTROL_SUBLIST);
453   OnMessage(msg);
454   CSingleLock lock(m_critsection);
455   m_subtitles->Clear();
456 }
457
458 void CGUIDialogSubtitles::ClearServices()
459 {
460   CGUIMessage msg(GUI_MSG_LABEL_RESET, GetID(), CONTROL_SERVICELIST);
461   OnMessage(msg);
462   m_serviceItems->Clear();
463   m_currentService.clear();
464 }
465
466 void CGUIDialogSubtitles::SetSubtitles(const std::string &subtitle)
467 {
468   if (g_application.m_pPlayer)
469   {
470     int nStream = g_application.m_pPlayer->AddSubtitle(subtitle);
471     if(nStream >= 0)
472     {
473       g_application.m_pPlayer->SetSubtitle(nStream);
474       g_application.m_pPlayer->SetSubtitleVisible(true);
475       CMediaSettings::Get().GetCurrentVideoSettings().m_SubtitleDelay = 0.0f;
476       g_application.m_pPlayer->SetSubTitleDelay(0);
477     }
478   }
479 }