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 #include "BlurayDirectory.h"
9 
10 #include "File.h"
11 #include "FileItem.h"
12 #include "LangInfo.h"
13 #include "URL.h"
14 #include "filesystem/BlurayCallback.h"
15 #include "guilib/LocalizeStrings.h"
16 #include "utils/LangCodeExpander.h"
17 #include "utils/RegExp.h"
18 #include "utils/StringUtils.h"
19 #include "utils/URIUtils.h"
20 #include "utils/log.h"
21 #include "video/VideoInfoTag.h"
22 
23 #include <array>
24 #include <cassert>
25 #include <climits>
26 #include <stdlib.h>
27 #include <string>
28 
29 #include <libbluray/bluray-version.h>
30 #include <libbluray/bluray.h>
31 #include <libbluray/filesystem.h>
32 #include <libbluray/log_control.h>
33 
34 namespace XFILE
35 {
36 
37 #define MAIN_TITLE_LENGTH_PERCENT 70 /** Minimum length of main titles, based on longest title */
38 
~CBlurayDirectory()39 CBlurayDirectory::~CBlurayDirectory()
40 {
41   Dispose();
42 }
43 
Dispose()44 void CBlurayDirectory::Dispose()
45 {
46   if(m_bd)
47   {
48     bd_close(m_bd);
49     m_bd = nullptr;
50   }
51 }
52 
GetBlurayTitle()53 std::string CBlurayDirectory::GetBlurayTitle()
54 {
55   return GetDiscInfoString(DiscInfo::TITLE);
56 }
57 
GetBlurayID()58 std::string CBlurayDirectory::GetBlurayID()
59 {
60   return GetDiscInfoString(DiscInfo::ID);
61 }
62 
GetDiscInfoString(DiscInfo info)63 std::string CBlurayDirectory::GetDiscInfoString(DiscInfo info)
64 {
65   switch (info)
66   {
67   case XFILE::CBlurayDirectory::DiscInfo::TITLE:
68   {
69     if (!m_blurayInitialized)
70       return "";
71     const BLURAY_DISC_INFO* disc_info = bd_get_disc_info(m_bd);
72     if (!disc_info || !disc_info->bluray_detected)
73       return "";
74 
75     std::string title = "";
76 
77 #if (BLURAY_VERSION > BLURAY_VERSION_CODE(1,0,0))
78     title = disc_info->disc_name ? disc_info->disc_name : "";
79 #endif
80 
81     return title;
82   }
83   case XFILE::CBlurayDirectory::DiscInfo::ID:
84   {
85     if (!m_blurayInitialized)
86       return "";
87 
88     const BLURAY_DISC_INFO* disc_info = bd_get_disc_info(m_bd);
89     if (!disc_info || !disc_info->bluray_detected)
90       return "";
91 
92     std::string id = "";
93 
94 #if (BLURAY_VERSION > BLURAY_VERSION_CODE(1,0,0))
95     id = disc_info->udf_volume_id ? disc_info->udf_volume_id : "";
96 
97     if (id.empty())
98     {
99       id = HexToString(disc_info->disc_id, 20);
100     }
101 #endif
102 
103     return id;
104   }
105   default:
106     break;
107   }
108 
109   return "";
110 }
111 
GetTitle(const BLURAY_TITLE_INFO * title,const std::string & label)112 CFileItemPtr CBlurayDirectory::GetTitle(const BLURAY_TITLE_INFO* title, const std::string& label)
113 {
114   std::string buf;
115   std::string chap;
116   CFileItemPtr item(new CFileItem("", false));
117   CURL path(m_url);
118   buf = StringUtils::Format("BDMV/PLAYLIST/%05d.mpls", title->playlist);
119   path.SetFileName(buf);
120   item->SetPath(path.Get());
121   int duration = (int)(title->duration / 90000);
122   item->GetVideoInfoTag()->SetDuration(duration);
123   item->GetVideoInfoTag()->m_iTrack = title->playlist;
124   buf = StringUtils::Format(label.c_str(), title->playlist);
125   item->m_strTitle = buf;
126   item->SetLabel(buf);
127   chap = StringUtils::Format(g_localizeStrings.Get(25007).c_str(), title->chapter_count, StringUtils::SecondsToTimeString(duration).c_str());
128   item->SetLabel2(chap);
129   item->m_dwSize = 0;
130   item->SetArt("icon", "DefaultVideo.png");
131   for(unsigned int i = 0; i < title->clip_count; ++i)
132     item->m_dwSize += title->clips[i].pkt_count * 192;
133 
134   return item;
135 }
136 
GetTitles(bool main,CFileItemList & items)137 void CBlurayDirectory::GetTitles(bool main, CFileItemList &items)
138 {
139   std::vector<BLURAY_TITLE_INFO*> titleList;
140   uint64_t minDuration = 0;
141 
142   // Searching for a user provided list of playlists.
143   if (main)
144     titleList = GetUserPlaylists();
145 
146   if (!main || titleList.empty())
147   {
148     uint32_t numTitles = bd_get_titles(m_bd, TITLES_RELEVANT, 0);
149 
150     for (uint32_t i = 0; i < numTitles; i++)
151     {
152       BLURAY_TITLE_INFO* t = bd_get_title_info(m_bd, i, 0);
153 
154       if (!t)
155       {
156         CLog::Log(LOGDEBUG, "CBlurayDirectory - unable to get title %d", i);
157         continue;
158       }
159 
160       if (main && t->duration > minDuration)
161           minDuration = t->duration;
162 
163       titleList.emplace_back(t);
164     }
165   }
166 
167   minDuration = minDuration * MAIN_TITLE_LENGTH_PERCENT / 100;
168 
169   for (auto& title : titleList)
170   {
171     if (title->duration < minDuration)
172       continue;
173 
174     items.Add(GetTitle(title, main ? g_localizeStrings.Get(25004) /* Main Title */ : g_localizeStrings.Get(25005) /* Title */));
175     bd_free_title_info(title);
176   }
177 }
178 
GetRoot(CFileItemList & items)179 void CBlurayDirectory::GetRoot(CFileItemList &items)
180 {
181     GetTitles(true, items);
182 
183     CURL path(m_url);
184     CFileItemPtr item;
185 
186     path.SetFileName(URIUtils::AddFileToFolder(m_url.GetFileName(), "titles"));
187     item.reset(new CFileItem());
188     item->SetPath(path.Get());
189     item->m_bIsFolder = true;
190     item->SetLabel(g_localizeStrings.Get(25002) /* All titles */);
191     item->SetArt("icon", "DefaultVideoPlaylists.png");
192     items.Add(item);
193 
194     const BLURAY_DISC_INFO* disc_info = bd_get_disc_info(m_bd);
195     if (disc_info && disc_info->no_menu_support)
196     {
197       CLog::Log(LOGDEBUG, "CBlurayDirectory::GetRoot - no menu support, skipping menu entry");
198       return;
199     }
200 
201     path.SetFileName("menu");
202     item.reset(new CFileItem());
203     item->SetPath(path.Get());
204     item->m_bIsFolder = false;
205     item->SetLabel(g_localizeStrings.Get(25003) /* Menus */);
206     item->SetArt("icon", "DefaultProgram.png");
207     items.Add(item);
208 }
209 
GetDirectory(const CURL & url,CFileItemList & items)210 bool CBlurayDirectory::GetDirectory(const CURL& url, CFileItemList &items)
211 {
212   Dispose();
213   m_url = url;
214   std::string root = m_url.GetHostName();
215   std::string file = m_url.GetFileName();
216   URIUtils::RemoveSlashAtEnd(file);
217   URIUtils::RemoveSlashAtEnd(root);
218 
219   if (!InitializeBluray(root))
220     return false;
221 
222   if(file == "root")
223     GetRoot(items);
224   else if(file == "root/titles")
225     GetTitles(false, items);
226   else
227   {
228     CURL url2 = GetUnderlyingCURL(url);
229     CDirectory::CHints hints;
230     hints.flags = m_flags;
231     if (!CDirectory::GetDirectory(url2, items, hints))
232       return false;
233   }
234 
235   items.AddSortMethod(SortByTrackNumber,  554, LABEL_MASKS("%L", "%D", "%L", ""));    // FileName, Duration | Foldername, empty
236   items.AddSortMethod(SortBySize,         553, LABEL_MASKS("%L", "%I", "%L", "%I"));  // FileName, Size | Foldername, Size
237 
238   return true;
239 }
240 
GetUnderlyingCURL(const CURL & url)241 CURL CBlurayDirectory::GetUnderlyingCURL(const CURL& url)
242 {
243   assert(url.IsProtocol("bluray"));
244   std::string host = url.GetHostName();
245   const std::string& filename = url.GetFileName();
246   return CURL(host.append(filename));
247 }
248 
InitializeBluray(const std::string & root)249 bool CBlurayDirectory::InitializeBluray(const std::string &root)
250 {
251   bd_set_debug_handler(CBlurayCallback::bluray_logger);
252   bd_set_debug_mask(DBG_CRIT | DBG_BLURAY | DBG_NAV);
253 
254   m_bd = bd_init();
255 
256   if (!m_bd)
257   {
258     CLog::Log(LOGERROR, "CBlurayDirectory::InitializeBluray - failed to initialize libbluray");
259     return false;
260   }
261 
262   std::string langCode;
263   g_LangCodeExpander.ConvertToISO6392T(g_langInfo.GetDVDMenuLanguage(), langCode);
264   bd_set_player_setting_str(m_bd, BLURAY_PLAYER_SETTING_MENU_LANG, langCode.c_str());
265 
266   if (!bd_open_files(m_bd, const_cast<std::string*>(&root), CBlurayCallback::dir_open, CBlurayCallback::file_open))
267   {
268     CLog::Log(LOGERROR, "CBlurayDirectory::InitializeBluray - failed to open %s", CURL::GetRedacted(root).c_str());
269     return false;
270   }
271   m_blurayInitialized = true;
272 
273   return true;
274 }
275 
HexToString(const uint8_t * buf,int count)276 std::string CBlurayDirectory::HexToString(const uint8_t *buf, int count)
277 {
278   std::array<char, 42> tmp;
279 
280   for (int i = 0; i < count; i++)
281   {
282     sprintf(tmp.data() + (i * 2), "%02x", buf[i]);
283   }
284 
285   return std::string(std::begin(tmp), std::end(tmp));
286 }
287 
GetUserPlaylists()288 std::vector<BLURAY_TITLE_INFO*> CBlurayDirectory::GetUserPlaylists()
289 {
290   std::string root = m_url.GetHostName();
291   std::string discInfPath = URIUtils::AddFileToFolder(root, "disc.inf");
292   std::vector<BLURAY_TITLE_INFO*> userTitles;
293   CFile file;
294   char buffer[1025];
295 
296   if (file.Open(discInfPath))
297   {
298     CLog::Log(LOGDEBUG, "CBlurayDirectory::GetTitles - disc.inf found");
299 
300     CRegExp pl(true);
301     if (!pl.RegComp("(\\d+)"))
302     {
303       file.Close();
304       return userTitles;
305     }
306 
307     uint8_t maxLines = 100;
308     while ((maxLines > 0) && file.ReadString(buffer, 1024))
309     {
310       maxLines--;
311       if (StringUtils::StartsWithNoCase(buffer, "playlists"))
312       {
313         int pos = 0;
314         while ((pos = pl.RegFind(buffer, static_cast<unsigned int>(pos))) >= 0)
315         {
316           std::string playlist = pl.GetMatch(0);
317           uint32_t len = static_cast<uint32_t>(playlist.length());
318 
319           if (len <= 5)
320           {
321             unsigned long int plNum = strtoul(playlist.c_str(), nullptr, 10);
322 
323             BLURAY_TITLE_INFO* t = bd_get_playlist_info(m_bd, static_cast<uint32_t>(plNum), 0);
324             if (t)
325               userTitles.emplace_back(t);
326           }
327 
328           if (static_cast<int64_t>(pos) + static_cast<int64_t>(len) > INT_MAX)
329             break;
330           else
331             pos += len;
332         }
333       }
334     }
335     file.Close();
336   }
337   return userTitles;
338 }
339 
340 } /* namespace XFILE */
341