/* * MPD SACD Decoder plugin * Copyright (c) 2011-2023 Maxim V.Anisiutkin * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with FFmpeg; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #include "config.h" #include #include #include "sacd_metabase.h" #include "lib/crypto/Base64.hxx" #include "lib/crypto/MD5.hxx" #include "util/ASCII.hxx" #include "tag/Handler.hxx" #include "tag/Names.hxx" #include static auto utf2xml = [](auto src) { auto dst{ std::string() }; for (auto i = 0; src[i] != 0; i++) { if (src[i] == '\r') { dst += " "; } else if (src[i] == '\n') { dst += " "; } else { dst += string(&src[i], 1); } } return dst; }; static auto xml2utf = [](auto src) { auto dst{ std::string() }; for (auto i = 0; src[i] != 0; i++) { if (strncmp(&src[i], " ", 5) == 0) { dst += "\r"; i += 4; } else if (strncmp(&src[i], " ", 5) == 0) { dst += "\n"; i += 4; } else { dst += std::string(&src[i], 1); } } return dst; }; static std::string get_md5(sacd_disc_t* p_disc) { auto md5_string{ std::string()}; std::vector md5_source(MASTER_TOC_LEN * SACD_LSN_SIZE); if (p_disc->read_blocks_raw(START_OF_MASTER_TOC, MASTER_TOC_LEN, (uint8_t*)md5_source.data())) { GlobalInitMD5(); for (auto md5_hash_value : MD5(md5_source)) { char hex_byte[3]; sprintf(hex_byte, "%02X", (uint8_t)md5_hash_value); md5_string += hex_byte; } } return md5_string; } sacd_metabase_t::sacd_metabase_t(sacd_disc_t* sacd_disc, const char* tags_path, const char* tags_file) { initialized = false; store_id = get_md5(sacd_disc); if (store_id.empty()) { return; } if (tags_path) { store_path = tags_path; store_file = store_path; store_file += "/"; store_file += store_id; store_file += ".xml"; if (access(store_file.c_str(), F_OK) == 0) { if (tags_file && access(tags_file, F_OK) == -1) { auto s = fopen(store_file.c_str(), "rb"); auto d = fopen(tags_file, "wb"); if (s) { if (d) { char buf[64]; size_t size; while ((size = fread(buf, 1, sizeof(buf), s)) > 0) { fwrite(buf, 1, size, d); } fclose(d); } fclose(s); } } } } xmlfile = tags_file ? tags_file : store_file; initialized = init_xmldoc(); } sacd_metabase_t::~sacd_metabase_t() { if (xmldoc) { ixmlDocument_free(xmldoc); } } bool sacd_metabase_t::get_track_info(unsigned track_number, TagHandler& handler) { if (!initialized) { return false; } auto node_track {get_node(MB_TAG_TRACK, std::to_string(track_number).c_str())}; if (!node_track) { return false; } auto list_tags {ixmlNode_getChildNodes(node_track)}; if (!list_tags) { return false; } for (auto tag_index = 0u; tag_index < ixmlNodeList_length(list_tags); tag_index++) { auto node_tag {ixmlNodeList_item(list_tags, tag_index)}; if (node_tag) { std::string node_name {ixmlNode_getNodeName(node_tag)}; if (node_name == MB_TAG_META) { auto attr_tag {ixmlNode_getAttributes(node_tag)}; if (attr_tag) { std::string tag_name; std::string tag_value; auto att_name {ixmlNamedNodeMap_getNamedItem(attr_tag, MB_ATT_NAME)}; if (att_name) { tag_name = ixmlNode_getNodeValue(att_name); } auto att_value {ixmlNamedNodeMap_getNamedItem(attr_tag, MB_ATT_VALUE)}; if (att_value) { tag_value = xml2utf(ixmlNode_getNodeValue(att_value)); } if (tag_name.length() > 0) { TagType tag_type = TAG_NUM_OF_ITEM_TYPES; for (auto i = 0; i < TAG_NUM_OF_ITEM_TYPES; i++) { if (StringEqualsCaseASCII(tag_item_names[i], tag_name.c_str())) { tag_type = static_cast(i); break; } } if (tag_type != TAG_NUM_OF_ITEM_TYPES) { handler.OnTag(tag_type, {tag_value.c_str(), tag_value.size()}); } } } } } } return true; } bool sacd_metabase_t::get_albumart(TagHandler& handler) { if (!initialized) { return false; } IXML_Node* node_albumart = nullptr; for (auto& att_id : {"3", "4", "6", "2", "8"}) { node_albumart = get_node(MB_TAG_ALBUMART , att_id); if (node_albumart) { break; } } if (!node_albumart) { return false; } auto node_cdata {ixmlNode_getFirstChild(node_albumart)}; if (!node_cdata) { return false; } auto cdata_value {ixmlNode_getNodeValue(node_cdata)}; if (!cdata_value) { return false; } auto debase64_size = CalculateBase64OutputSize(strlen(cdata_value)); std::vector debase64_data(debase64_size); debase64_size = DecodeBase64(debase64_data, cdata_value); handler.OnPicture(nullptr, debase64_data); return true; } bool sacd_metabase_t::init_xmldoc() { xmldoc = ixmlLoadDocument(xmlfile.c_str()); return xmldoc != nullptr; } IXML_Node* sacd_metabase_t::get_node(const char* tag_name, const char* att_id) { IXML_NodeList* list_item = nullptr; IXML_Node* node_item_id = nullptr; auto node_root {ixmlNodeList_item(ixmlDocument_getElementsByTagName(xmldoc, MB_TAG_ROOT), 0)}; if (node_root) { auto list_store {ixmlNode_getChildNodes(node_root)}; if (list_store) { for (auto i = 0u; i < ixmlNodeList_length(list_store); i++) { auto node_store {ixmlNodeList_item(list_store, i)}; if (node_store) { auto attr_store {ixmlNode_getAttributes(node_store)}; if (attr_store) { IXML_Node* node_attr; std::string attr_id; std::string attr_type; node_attr = ixmlNamedNodeMap_getNamedItem(attr_store, MB_ATT_ID); if (node_attr) { attr_id = ixmlNode_getNodeValue(node_attr); } node_attr = ixmlNamedNodeMap_getNamedItem(attr_store, MB_ATT_TYPE); if (node_attr) { attr_type = ixmlNode_getNodeValue(node_attr); } if (attr_id == store_id) { list_item = ixmlNode_getChildNodes(node_store); break; } } } } } } if (list_item) { for (auto i = 0u; i < ixmlNodeList_length(list_item); i++) { auto node_item {ixmlNodeList_item(list_item, i)}; if (node_item) { std::string node_name {ixmlNode_getNodeName(node_item)}; if (node_name == tag_name) { auto attr_item {ixmlNode_getAttributes(node_item)}; if (attr_item) { auto node_attr {ixmlNamedNodeMap_getNamedItem(attr_item, MB_ATT_ID)}; if (node_attr) { std::string attr_value = ixmlNode_getNodeValue(node_attr); if (attr_value == att_id) { node_item_id = node_item; break; } } } } } } } return node_item_id; }