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