/* * Copyright (C) 2005-2018 Team Kodi * This file is part of Kodi - https://kodi.tv * * SPDX-License-Identifier: GPL-2.0-or-later * See LICENSES/README.md for more information. */ #include "BlurayDirectory.h" #include "File.h" #include "FileItem.h" #include "LangInfo.h" #include "URL.h" #include "filesystem/BlurayCallback.h" #include "filesystem/Directory.h" #include "guilib/LocalizeStrings.h" #include "utils/LangCodeExpander.h" #include "utils/RegExp.h" #include "utils/StringUtils.h" #include "utils/URIUtils.h" #include "utils/log.h" #include "video/VideoInfoTag.h" #include #include #include #include #include #include #include #include #include namespace XFILE { #define MAIN_TITLE_LENGTH_PERCENT 70 /** Minimum length of main titles, based on longest title */ CBlurayDirectory::~CBlurayDirectory() { Dispose(); } void CBlurayDirectory::Dispose() { if(m_bd) { bd_close(m_bd); m_bd = nullptr; } } std::string CBlurayDirectory::GetBlurayTitle() { return GetDiscInfoString(DiscInfo::TITLE); } std::string CBlurayDirectory::GetBlurayID() { return GetDiscInfoString(DiscInfo::ID); } std::string CBlurayDirectory::GetDiscInfoString(DiscInfo info) { switch (info) { case XFILE::CBlurayDirectory::DiscInfo::TITLE: { if (!m_blurayInitialized) return ""; const BLURAY_DISC_INFO* disc_info = bd_get_disc_info(m_bd); if (!disc_info || !disc_info->bluray_detected) return ""; std::string title = ""; #if (BLURAY_VERSION > BLURAY_VERSION_CODE(1,0,0)) title = disc_info->disc_name ? disc_info->disc_name : ""; #endif return title; } case XFILE::CBlurayDirectory::DiscInfo::ID: { if (!m_blurayInitialized) return ""; const BLURAY_DISC_INFO* disc_info = bd_get_disc_info(m_bd); if (!disc_info || !disc_info->bluray_detected) return ""; std::string id = ""; #if (BLURAY_VERSION > BLURAY_VERSION_CODE(1,0,0)) id = disc_info->udf_volume_id ? disc_info->udf_volume_id : ""; if (id.empty()) { id = HexToString(disc_info->disc_id, 20); } #endif return id; } default: break; } return ""; } std::shared_ptr CBlurayDirectory::GetTitle(const BLURAY_TITLE_INFO* title, const std::string& label) { std::string buf; std::string chap; CFileItemPtr item(new CFileItem("", false)); CURL path(m_url); buf = StringUtils::Format("BDMV/PLAYLIST/{:05}.mpls", title->playlist); path.SetFileName(buf); item->SetPath(path.Get()); int duration = (int)(title->duration / 90000); item->GetVideoInfoTag()->SetDuration(duration); item->GetVideoInfoTag()->m_iTrack = title->playlist; buf = StringUtils::Format(label, title->playlist); item->m_strTitle = buf; item->SetLabel(buf); chap = StringUtils::Format(g_localizeStrings.Get(25007), title->chapter_count, StringUtils::SecondsToTimeString(duration)); item->SetLabel2(chap); item->m_dwSize = 0; item->SetArt("icon", "DefaultVideo.png"); for(unsigned int i = 0; i < title->clip_count; ++i) item->m_dwSize += title->clips[i].pkt_count * 192; return item; } void CBlurayDirectory::GetTitles(bool main, CFileItemList &items) { std::vector titleList; uint64_t minDuration = 0; // Searching for a user provided list of playlists. if (main) titleList = GetUserPlaylists(); if (!main || titleList.empty()) { uint32_t numTitles = bd_get_titles(m_bd, TITLES_RELEVANT, 0); for (uint32_t i = 0; i < numTitles; i++) { BLURAY_TITLE_INFO* t = bd_get_title_info(m_bd, i, 0); if (!t) { CLog::Log(LOGDEBUG, "CBlurayDirectory - unable to get title {}", i); continue; } if (main && t->duration > minDuration) minDuration = t->duration; titleList.emplace_back(t); } } minDuration = minDuration * MAIN_TITLE_LENGTH_PERCENT / 100; for (auto& title : titleList) { if (title->duration < minDuration) continue; items.Add(GetTitle(title, main ? g_localizeStrings.Get(25004) /* Main Title */ : g_localizeStrings.Get(25005) /* Title */)); bd_free_title_info(title); } } void CBlurayDirectory::GetRoot(CFileItemList &items) { GetTitles(true, items); CURL path(m_url); CFileItemPtr item; path.SetFileName(URIUtils::AddFileToFolder(m_url.GetFileName(), "titles")); item.reset(new CFileItem()); item->SetPath(path.Get()); item->m_bIsFolder = true; item->SetLabel(g_localizeStrings.Get(25002) /* All titles */); item->SetArt("icon", "DefaultVideoPlaylists.png"); items.Add(item); const BLURAY_DISC_INFO* disc_info = bd_get_disc_info(m_bd); if (disc_info && disc_info->no_menu_support) { CLog::Log(LOGDEBUG, "CBlurayDirectory::GetRoot - no menu support, skipping menu entry"); return; } path.SetFileName("menu"); item.reset(new CFileItem()); item->SetPath(path.Get()); item->m_bIsFolder = false; item->SetLabel(g_localizeStrings.Get(25003) /* Menus */); item->SetArt("icon", "DefaultProgram.png"); items.Add(item); } bool CBlurayDirectory::GetDirectory(const CURL& url, CFileItemList &items) { Dispose(); m_url = url; std::string root = m_url.GetHostName(); std::string file = m_url.GetFileName(); URIUtils::RemoveSlashAtEnd(file); URIUtils::RemoveSlashAtEnd(root); if (!InitializeBluray(root)) return false; if(file == "root") GetRoot(items); else if(file == "root/titles") GetTitles(false, items); else { CURL url2 = GetUnderlyingCURL(url); CDirectory::CHints hints; hints.flags = m_flags; if (!CDirectory::GetDirectory(url2, items, hints)) return false; } items.AddSortMethod(SortByTrackNumber, 554, LABEL_MASKS("%L", "%D", "%L", "")); // FileName, Duration | Foldername, empty items.AddSortMethod(SortBySize, 553, LABEL_MASKS("%L", "%I", "%L", "%I")); // FileName, Size | Foldername, Size return true; } CURL CBlurayDirectory::GetUnderlyingCURL(const CURL& url) { assert(url.IsProtocol("bluray")); std::string host = url.GetHostName(); const std::string& filename = url.GetFileName(); return CURL(host.append(filename)); } bool CBlurayDirectory::InitializeBluray(const std::string &root) { bd_set_debug_handler(CBlurayCallback::bluray_logger); bd_set_debug_mask(DBG_CRIT | DBG_BLURAY | DBG_NAV); m_bd = bd_init(); if (!m_bd) { CLog::Log(LOGERROR, "CBlurayDirectory::InitializeBluray - failed to initialize libbluray"); return false; } std::string langCode; g_LangCodeExpander.ConvertToISO6392T(g_langInfo.GetDVDMenuLanguage(), langCode); bd_set_player_setting_str(m_bd, BLURAY_PLAYER_SETTING_MENU_LANG, langCode.c_str()); if (!bd_open_files(m_bd, const_cast(&root), CBlurayCallback::dir_open, CBlurayCallback::file_open)) { CLog::Log(LOGERROR, "CBlurayDirectory::InitializeBluray - failed to open {}", CURL::GetRedacted(root)); return false; } m_blurayInitialized = true; return true; } std::string CBlurayDirectory::HexToString(const uint8_t *buf, int count) { std::array tmp; for (int i = 0; i < count; i++) { sprintf(tmp.data() + (i * 2), "%02x", buf[i]); } return std::string(std::begin(tmp), std::end(tmp)); } std::vector CBlurayDirectory::GetUserPlaylists() { std::string root = m_url.GetHostName(); std::string discInfPath = URIUtils::AddFileToFolder(root, "disc.inf"); std::vector userTitles; CFile file; char buffer[1025]; if (file.Open(discInfPath)) { CLog::Log(LOGDEBUG, "CBlurayDirectory::GetTitles - disc.inf found"); CRegExp pl(true); if (!pl.RegComp("(\\d+)")) { file.Close(); return userTitles; } uint8_t maxLines = 100; while ((maxLines > 0) && file.ReadString(buffer, 1024)) { maxLines--; if (StringUtils::StartsWithNoCase(buffer, "playlists")) { int pos = 0; while ((pos = pl.RegFind(buffer, static_cast(pos))) >= 0) { std::string playlist = pl.GetMatch(0); uint32_t len = static_cast(playlist.length()); if (len <= 5) { unsigned long int plNum = strtoul(playlist.c_str(), nullptr, 10); BLURAY_TITLE_INFO* t = bd_get_playlist_info(m_bd, static_cast(plNum), 0); if (t) userTitles.emplace_back(t); } if (static_cast(pos) + static_cast(len) > INT_MAX) break; else pos += len; } } } file.Close(); } return userTitles; } } /* namespace XFILE */