Skip to content

Commit

Permalink
feat: extract album image from ncm file
Browse files Browse the repository at this point in the history
  • Loading branch information
pnck committed Mar 4, 2021
1 parent 174c66b commit 2fc4089
Show file tree
Hide file tree
Showing 11 changed files with 210 additions and 107 deletions.
27 changes: 27 additions & 0 deletions foo_input_ncm/album_art_extractor.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
#include "stdafx.h"
#include "album_art_extractor.h"
#include "ncm_file.h"

using namespace fb2k_ncm;

bool ncm_album_art_extractor::is_our_path(const char *p_path, const char *p_extension) {
return stricmp_utf8(p_extension, "ncm") == 0;
}

album_art_extractor_instance_ptr ncm_album_art_extractor::open(file_ptr p_filehint, const char *p_path,
abort_callback &p_abort) {
if (p_filehint.is_empty()) {
if (is_our_path(p_path, pfc::string_extension(p_path).toString())) {
filesystem::g_open_read(p_filehint, p_path, p_abort);
}
}
auto _ncm_file = fb2k::service_new<ncm_file>(p_filehint, p_path);
_ncm_file->parse(ncm_file::parse_contents::NCM_PARSE_ALBUM);
auto album_arts = fb2k::service_new<album_art_extractor_instance_simple>();
auto image = album_art_data_impl::g_create(_ncm_file->image_data.data(), _ncm_file->image_data.size());
album_arts->set(album_art_ids::cover_front, image);
// album_arts->set(album_art_ids::disc, image);
return album_arts;
}

static service_factory_single_t<ncm_album_art_extractor> g_ncm_album_art_extractor;
18 changes: 18 additions & 0 deletions foo_input_ncm/album_art_extractor.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
#pragma once

#include "stdafx.h"
#include "ncm_file.h"
#include "common/define.h"

namespace fb2k_ncm
{

class ncm_album_art_extractor : public album_art_extractor {
public:
// GUID get_guid() override;
bool is_our_path(const char *p_path, const char *p_extension) override;
album_art_extractor_instance_ptr open(file_ptr p_filehint, const char *p_path,
abort_callback &p_abort) override;
};

} // namespace fb2k_ncm
16 changes: 15 additions & 1 deletion foo_input_ncm/cipher/abnormal_RC4.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@

using namespace fb2k_ncm::cipher;

fb2k_ncm::cipher::abnormal_RC4::abnormal_RC4(const uint8_t *seed, size_t len) {
fb2k_ncm::cipher::abnormal_RC4::abnormal_RC4(const uint8_t *seed, size_t len) : abnormal_RC4() {
key_seed_.assign(seed, seed + len);
len &= 0xff;
uint8_t key_box[256];
Expand Down Expand Up @@ -34,4 +34,18 @@ std::function<uint8_t(uint8_t, size_t)> fb2k_ncm::cipher::abnormal_RC4::get_tran

bool fb2k_ncm::cipher::abnormal_RC4::is_valid() const {
return key_seed_.size() && key_box_;
}

abnormal_RC4::abnormal_RC4(abnormal_RC4 &c) : key_seed(key_seed_), key_seed_(c.key_seed_), key_box_(c.key_box_) {}
abnormal_RC4 &abnormal_RC4::operator=(abnormal_RC4 &c) {
key_seed_ = c.key_seed_;
key_box_ = c.key_box_;
return *this;
}
abnormal_RC4::abnormal_RC4(abnormal_RC4 &&c)
: key_seed(key_seed_), key_seed_(std::move(c.key_seed_)), key_box_(std::move(c.key_box_)) {}
abnormal_RC4 &abnormal_RC4::operator=(abnormal_RC4 &&c) {
key_seed_ = std::move(c.key_seed_);
key_box_ = std::move(c.key_box_);
return *this;
}
11 changes: 9 additions & 2 deletions foo_input_ncm/cipher/abnormal_RC4.h
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,20 @@ namespace fb2k_ncm::cipher
// special rc4-like BLOCK CIPHER used for ncm files
class abnormal_RC4 {
public:
abnormal_RC4() = default;
abnormal_RC4() : key_seed(key_seed_) {}
abnormal_RC4(const uint8_t *seed, size_t len);
abnormal_RC4(const std::vector<uint8_t> &seed) : abnormal_RC4(seed.data(), seed.size()) {}
std::function<uint8_t(uint8_t, size_t)> get_transform() const;
abnormal_RC4(abnormal_RC4 &c);
abnormal_RC4 &operator=(abnormal_RC4 &c);
abnormal_RC4(abnormal_RC4 &&c);
abnormal_RC4 &operator=(abnormal_RC4 &&c);

public:
bool is_valid() const;
std::function<uint8_t(uint8_t, size_t)> get_transform() const;

public:
const std::vector<uint8_t> &key_seed;

private:
std::vector<uint8_t> key_seed_;
Expand Down
15 changes: 9 additions & 6 deletions foo_input_ncm/common/define.h
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@

constexpr inline GUID guid_candidates[] = {
{0x0ef1cb99, 0x91ad, 0x486c, {0x82, 0x82, 0xda, 0x70, 0x98, 0x4e, 0xa8, 0x51}}, // input_ncm
{0x9c99d51e, 0x1228, 0x4f25, {0x91, 0x63, 0xf1, 0x56, 0x1e, 0x57, 0x5b, 0x13}},
{0xdb2c5ae1, 0x1a4c, 0x4c67, {0xb4, 0x13, 0xc9, 0xd9, 0x46, 0x34, 0xe2, 0xaf}},
{0x9c99d51e, 0x1228, 0x4f25, {0x91, 0x63, 0xf1, 0x56, 0x1e, 0x57, 0x5b, 0x13}},
{0xdb2c5ae1, 0x1a4c, 0x4c67, {0xb4, 0x13, 0xc9, 0xd9, 0x46, 0x34, 0xe2, 0xaf}},
{0xc2cb5fa6, 0x9d9f, 0x47ec, {0xae, 0x3a, 0x18, 0x5f, 0xc7, 0x98, 0xd6, 0x2c}},
};

Expand All @@ -31,12 +31,15 @@ namespace fb2k_ncm
uint64_t magic = ncm_magic;
uint8_t unknown_padding[2];
uint32_t rc4_seed_len;
uint8_t *rc4_seed_;
const uint8_t *rc4_seed_;
uint32_t meta_len;
uint8_t *meta_content;
const uint8_t *meta_content;
uint8_t unknown_data[5];
uint32_t album_image_size[2]; // there are 2 exactly same field
uint8_t *album_image;
uint8_t *audio_file;
const uint8_t *album_image;
const uint8_t *audio_content;
// end of original file structure
uint64_t album_image_offset;
uint64_t audio_content_offset;
};
} // namespace fb2k_ncm
2 changes: 2 additions & 0 deletions foo_input_ncm/foo_input_ncm.vcxproj
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,7 @@ copy $(TargetPath) $(SolutionDir)test\foobar2000\components</Command>
</CustomBuildStep>
</ItemDefinitionGroup>
<ItemGroup>
<ClInclude Include="album_art_extractor.h" />
<ClInclude Include="cipher\abnormal_RC4.h" />
<ClInclude Include="cipher\aes.h" />
<ClInclude Include="cipher\cipher.h" />
Expand All @@ -133,6 +134,7 @@ copy $(TargetPath) $(SolutionDir)test\foobar2000\components</Command>
<ClInclude Include="stdafx.h" />
</ItemGroup>
<ItemGroup>
<ClCompile Include="album_art_extractor.cpp" />
<ClCompile Include="cipher\abnormal_RC4.cpp" />
<ClCompile Include="cipher\aes.cpp">
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">NotUsing</PrecompiledHeader>
Expand Down
6 changes: 6 additions & 0 deletions foo_input_ncm/foo_input_ncm.vcxproj.filters
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,9 @@
<ClInclude Include="common\define.h">
<Filter>Header File</Filter>
</ClInclude>
<ClInclude Include="album_art_extractor.h">
<Filter>Header File</Filter>
</ClInclude>
</ItemGroup>
<ItemGroup>
<ClCompile Include="cipher\aes.cpp">
Expand All @@ -65,5 +68,8 @@
<ClCompile Include="ncm_file.cpp">
<Filter>Source File</Filter>
</ClCompile>
<ClCompile Include="album_art_extractor.cpp">
<Filter>Source File</Filter>
</ClCompile>
</ItemGroup>
</Project>
46 changes: 26 additions & 20 deletions foo_input_ncm/input_ncm.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,10 @@ inline bool fb2k_ncm::input_ncm::g_is_our_content_type(const char *p_content_typ
}

inline void fb2k_ncm::input_ncm::retag(const file_info &p_info, abort_callback &p_abort) {
throw exception_io_unsupported_format("Modification on ncm file not supported.");
if (source_info_writer_.is_valid()) {
source_info_writer_->set_info(0, p_info, p_abort);
}
// throw exception_io_unsupported_format("Modification on ncm file not supported.");
}

inline bool fb2k_ncm::input_ncm::decode_can_seek() {
Expand All @@ -41,17 +44,17 @@ void input_ncm::open(service_ptr_t<file> p_filehint, const char *p_path, t_input
}

// walk through the file structure
ncm_file_->parse();
ncm_file_->parse(ncm_file::parse_contents::NCM_PARSE_META | ncm_file::parse_contents::NCM_PARSE_AUDIO);

// find decoder and info readers
service_list_t<input_entry> input_services;
do {
// there is format hint, so we don't have to find_input twice or more
if (ncm_file_->meta().IsObject() && ncm_file_->meta().HasMember("format")) {
if (ncm_file_->meta()["format"] == "flac") {
if (ncm_file_->meta_info.IsObject() && ncm_file_->meta_info.HasMember("format")) {
if (ncm_file_->meta_info["format"] == "flac") {
input_entry::g_find_inputs_by_content_type(input_services, "audio/flac", false);
break;
} else if (ncm_file_->meta()["format"] == "mp3") {
} else if (ncm_file_->meta_info["format"] == "mp3") {
input_entry::g_find_inputs_by_content_type(input_services, "audio/mpeg", false);
break;
}
Expand Down Expand Up @@ -105,6 +108,8 @@ void input_ncm::open(service_ptr_t<file> p_filehint, const char *p_path, t_input
throw exception_service_not_found("Failed to find proper audio decoder.");
}
input_ptr->open_for_info_read(source_info_reader_, ncm_file_, /*file_path_*/ "", p_abort);
// TODO: support of retagging on audio content (with ncm wrapper and meta info not touched)
// input_ptr->open_for_info_write(source_info_writer_, ncm_file_, /*file_path_*/ "", p_abort);
}

void input_ncm::decode_seek(double p_seconds, abort_callback &p_abort) {
Expand All @@ -117,6 +122,7 @@ bool input_ncm::decode_run(audio_chunk &p_chunk, abort_callback &p_abort) {
}

void input_ncm::decode_initialize(unsigned p_flags, abort_callback &p_abort) {
// initialize should always follow open
ncm_file_->ensure_audio_offset();
decoder_->initialize(0, p_flags, p_abort);
// WTF MAGIC HACK here:
Expand All @@ -133,35 +139,35 @@ void input_ncm::get_info(file_info &p_info, abort_callback &p_abort) {
if (!source_info_reader_.is_valid()) {
return;
}
if (ncm_file_->meta().IsNull()) {
if (ncm_file_->meta_info.IsNull()) {
return;
}
source_info_reader_->get_info(0, p_info, p_abort);
// p_info.set_length(meta()["duration"].GetInt() / 1000.0);
// p_info.info_set_bitrate(meta()["bitrate"].GetUint64() / 1000);
if (ncm_file_->meta().HasMember("artist")) {
if (ncm_file_->meta_info.HasMember("artist")) {
p_info.meta_remove_field("Artist");
for (auto &v : ncm_file_->meta()["artist"].GetArray()) {
for (auto &v : ncm_file_->meta_info["artist"].GetArray()) {
p_info.meta_add("Artist", v[0].GetString());
}
}
if (ncm_file_->meta().HasMember("album")) {
p_info.meta_set("Album", ncm_file_->meta()["album"].GetString());
if (ncm_file_->meta_info.HasMember("album")) {
p_info.meta_set("Album", ncm_file_->meta_info["album"].GetString());
}
if (ncm_file_->meta().HasMember("musicName")) {
p_info.meta_set("Title", ncm_file_->meta()["musicName"].GetString());
if (ncm_file_->meta_info.HasMember("musicName")) {
p_info.meta_set("Title", ncm_file_->meta_info["musicName"].GetString());
}
if (ncm_file_->meta().HasMember("musicId")) {
p_info.info_set("Music ID", PFC_string_formatter() << ncm_file_->meta()["musicId"].GetUint64());
if (ncm_file_->meta_info.HasMember("musicId")) {
p_info.info_set("Music ID", PFC_string_formatter() << ncm_file_->meta_info["musicId"].GetUint64());
}
if (ncm_file_->meta().HasMember("albumId")) {
p_info.info_set("Album ID", PFC_string_formatter() << ncm_file_->meta()["albumId"].GetUint64());
if (ncm_file_->meta_info.HasMember("albumId")) {
p_info.info_set("Album ID", PFC_string_formatter() << ncm_file_->meta_info["albumId"].GetUint64());
}
if (ncm_file_->meta().HasMember("albumPic")) {
p_info.info_set("Album Artwork", ncm_file_->meta()["albumPic"].GetString());
if (ncm_file_->meta_info.HasMember("albumPic")) {
p_info.info_set("Album Artwork", ncm_file_->meta_info["albumPic"].GetString());
}
if (ncm_file_->meta().HasMember("alias")) {
for (auto &v : ncm_file_->meta()["alias"].GetArray()) {
if (ncm_file_->meta_info.HasMember("alias")) {
for (auto &v : ncm_file_->meta_info["alias"].GetArray()) {
p_info.meta_add("Alias", v.GetString());
}
}
Expand Down
1 change: 1 addition & 0 deletions foo_input_ncm/input_ncm.h
Original file line number Diff line number Diff line change
Expand Up @@ -33,5 +33,6 @@ namespace fb2k_ncm
service_ptr_t<ncm_file> ncm_file_;
service_ptr_t<input_decoder> decoder_;
service_ptr_t<input_info_reader> source_info_reader_;
service_ptr_t<input_info_writer> source_info_writer_;
};
} // namespace fb2k_ncm
Loading

0 comments on commit 2fc4089

Please sign in to comment.