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