You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
355 lines
9.8 KiB
C++
355 lines
9.8 KiB
C++
/*
|
|
* Copyright (C) 2003-2025 The Music Player Daemon Project
|
|
* http://www.musicpd.org
|
|
*
|
|
* This program is free software; you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License as published by
|
|
* the Free Software Foundation; either version 2 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 General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License along
|
|
* with this program; if not, write to the Free Software Foundation, Inc.,
|
|
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
|
*/
|
|
|
|
#include "config.h"
|
|
#include <audio_stream.h>
|
|
#include <audio_track.h>
|
|
#include <stream_buffer.h>
|
|
#include <log_trunk.h>
|
|
#include <dvda_disc.h>
|
|
#include <dvda_metabase.h>
|
|
#include "DvdaIsoDecoderPlugin.hxx"
|
|
#include "../DecoderAPI.hxx"
|
|
#include "input/InputStream.hxx"
|
|
#include "pcm/CheckAudioFormat.hxx"
|
|
#include "tag/Handler.hxx"
|
|
#include "tag/Builder.hxx"
|
|
#include "song/DetachedSong.hxx"
|
|
#include "fs/Path.hxx"
|
|
#include "fs/AllocatedPath.hxx"
|
|
#include "fs/FileSystem.hxx"
|
|
#include "thread/Cond.hxx"
|
|
#include "thread/Mutex.hxx"
|
|
#include "util/BitReverse.hxx"
|
|
#include "util/UriExtract.hxx"
|
|
#include "util/Domain.hxx"
|
|
#include "Log.hxx"
|
|
|
|
#include <assert.h>
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <memory>
|
|
#include <vector>
|
|
|
|
static constexpr Domain dvdaiso_domain("dvdaiso");
|
|
|
|
namespace dvdaiso {
|
|
|
|
constexpr auto DVDA_TRACKXXX_FMT{ "AUDIO_TS__TRACK%03u%c.%3s" };
|
|
constexpr double SHORT_TRACK_SEC{ 2.0 };
|
|
|
|
bool param_no_downmixes;
|
|
bool param_no_short_tracks;
|
|
bool param_no_untagged_tracks;
|
|
chmode_e param_playable_area;
|
|
std::string param_tags_path;
|
|
bool param_tags_with_iso;
|
|
bool param_use_stdio;
|
|
|
|
AllocatedPath dvda_path{ nullptr };
|
|
std::unique_ptr<dvda_media_t> dvda_media;
|
|
std::unique_ptr<dvda_reader_t> dvda_reader;
|
|
std::unique_ptr<dvda_metabase_t> dvda_metabase;
|
|
|
|
static bool
|
|
get_subsong(Path path_fs, unsigned& index, bool& downmix) {
|
|
auto ptr = path_fs.GetBase().c_str();
|
|
char area = '\0';
|
|
char suffix[4];
|
|
auto params = sscanf(ptr, DVDA_TRACKXXX_FMT, &index, &area, suffix);
|
|
index--;
|
|
downmix = area == 'D';
|
|
return params == 3;
|
|
}
|
|
|
|
static bool
|
|
container_update(Path path_fs) {
|
|
auto curr_path = AllocatedPath(path_fs);
|
|
if (dvda_path == curr_path) {
|
|
return true;
|
|
}
|
|
if (dvda_reader) {
|
|
dvda_reader->close();
|
|
dvda_reader.reset();
|
|
}
|
|
if (dvda_media) {
|
|
dvda_media->close();
|
|
dvda_media.reset();
|
|
}
|
|
dvda_metabase.reset();
|
|
dvda_path.SetNull();
|
|
if (FileExists(curr_path)) {
|
|
if (param_use_stdio) {
|
|
dvda_media = std::make_unique<dvda_media_file_t>();
|
|
}
|
|
else {
|
|
dvda_media = std::make_unique<dvda_media_stream_t>();
|
|
}
|
|
dvda_reader = std::make_unique<dvda_disc_t>();
|
|
if (!dvda_media->open(curr_path.c_str())) {
|
|
std::string err;
|
|
err = "dvda_media->open('";
|
|
err += curr_path.c_str();
|
|
err += "') failed";
|
|
LogWarning(dvdaiso_domain, err.c_str());
|
|
return false;
|
|
}
|
|
if (!dvda_reader->open(dvda_media.get())) {
|
|
//LogWarning(dvdaiso_domain, "dvda_reader->open(...) failed");
|
|
return false;
|
|
}
|
|
if (!param_tags_path.empty() || param_tags_with_iso) {
|
|
std::string tags_file;
|
|
if (param_tags_with_iso) {
|
|
tags_file = curr_path.c_str();
|
|
tags_file.resize(tags_file.rfind('.') + 1);
|
|
tags_file.append("xml");
|
|
}
|
|
dvda_metabase = std::make_unique<dvda_metabase_t>(static_cast<dvda_disc_t*>(dvda_reader.get()), param_tags_path.empty() ? nullptr : param_tags_path.c_str(), tags_file.empty() ? nullptr : tags_file.c_str());
|
|
}
|
|
dvda_path = curr_path;
|
|
}
|
|
return static_cast<bool>(dvda_reader);
|
|
}
|
|
|
|
static void
|
|
scan_info(unsigned track_index, bool downmix, TagHandler& handler) {
|
|
auto tag_value = std::to_string(track_index + 1);
|
|
handler.OnTag(TAG_TRACK, tag_value.c_str());
|
|
handler.OnDuration(SongTime::FromS(dvda_reader->get_duration(track_index)));
|
|
if (!dvda_metabase || (dvda_metabase && !dvda_metabase->get_track_info(track_index + 1, downmix, handler))) {
|
|
dvda_reader->get_info(track_index, downmix, handler);
|
|
}
|
|
if (handler.WantPicture()) {
|
|
auto has_albumart{ false };
|
|
if (dvda_metabase) {
|
|
has_albumart = dvda_metabase->get_albumart(handler);
|
|
}
|
|
if (!has_albumart) {
|
|
static constexpr auto art_names = std::array {
|
|
"cover.png",
|
|
"cover.jpg",
|
|
"cover.webp",
|
|
};
|
|
for (const auto art_name : art_names) {
|
|
auto art_file = AllocatedPath::Build(dvda_path.GetDirectoryName(), art_name);
|
|
try {
|
|
Mutex mutex;
|
|
auto is = InputStream::OpenReady(art_file.c_str(), mutex);
|
|
if (is && is->KnownSize()) {
|
|
std::unique_lock lock{mutex};
|
|
std::vector<std::byte> art_data;
|
|
art_data.resize(is->GetSize());
|
|
is->ReadFull(lock, art_data);
|
|
handler.OnPicture(nullptr, art_data);
|
|
break;
|
|
}
|
|
}
|
|
catch (...) {
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
static bool
|
|
init(const ConfigBlock& block) {
|
|
my_av_log_set_callback(mpd_av_log_callback);
|
|
param_no_downmixes = block.GetBlockValue("no_downmixes", true);
|
|
param_no_short_tracks = block.GetBlockValue("no_short_tracks", true);
|
|
param_no_untagged_tracks = block.GetBlockValue("no_untagged_tracks", true);
|
|
auto playable_area = block.GetBlockValue("playable_area", nullptr);
|
|
param_playable_area = CHMODE_BOTH;
|
|
if (playable_area != nullptr) {
|
|
if (strcmp(playable_area, "stereo") == 0) {
|
|
param_playable_area = CHMODE_TWOCH;
|
|
}
|
|
if (strcmp(playable_area, "multichannel") == 0) {
|
|
param_playable_area = CHMODE_MULCH;
|
|
}
|
|
}
|
|
param_tags_path = block.GetBlockValue("tags_path", "");
|
|
param_tags_with_iso = block.GetBlockValue("tags_with_iso", false);
|
|
param_use_stdio = block.GetBlockValue("use_stdio", true);
|
|
return true;
|
|
}
|
|
|
|
static void
|
|
finish() noexcept {
|
|
container_update(nullptr);
|
|
my_av_log_set_default_callback();
|
|
}
|
|
|
|
static std::forward_list<DetachedSong>
|
|
container_scan(Path path_fs) {
|
|
std::forward_list<DetachedSong> list;
|
|
if (!container_update(path_fs)) {
|
|
return list;
|
|
}
|
|
TagBuilder tag_builder;
|
|
auto tail = list.before_begin();
|
|
auto suffix = path_fs.GetExtension();
|
|
for (auto track_index = 0u; track_index < dvda_reader->get_tracks(); track_index++) {
|
|
if (dvda_reader->select_track(track_index)) {
|
|
auto duration = dvda_reader->get_duration();
|
|
if (param_no_short_tracks && duration < SHORT_TRACK_SEC) {
|
|
continue;
|
|
}
|
|
auto add_track = false;
|
|
auto add_downmix = false;
|
|
switch (param_playable_area) {
|
|
case CHMODE_MULCH:
|
|
if (dvda_reader->get_channels() > 2) {
|
|
add_track = true;
|
|
}
|
|
break;
|
|
case CHMODE_TWOCH:
|
|
if (dvda_reader->get_channels() <= 2) {
|
|
add_track = true;
|
|
}
|
|
if (!param_no_downmixes && dvda_reader->can_downmix()) {
|
|
add_downmix = true;
|
|
}
|
|
break;
|
|
default:
|
|
add_track = true;
|
|
if (!param_no_downmixes && dvda_reader->can_downmix()) {
|
|
add_downmix = true;
|
|
}
|
|
break;
|
|
}
|
|
char area;
|
|
char track_name[64];
|
|
if (add_track) {
|
|
AddTagHandler h(tag_builder);
|
|
area = dvda_reader->get_channels() > 2 ? 'M' : 'S';
|
|
scan_info(track_index, false, h);
|
|
std::sprintf(track_name, DVDA_TRACKXXX_FMT, track_index + 1, area, suffix),
|
|
tail = list.emplace_after(
|
|
tail,
|
|
track_name,
|
|
tag_builder.Commit()
|
|
);
|
|
}
|
|
if (add_downmix) {
|
|
AddTagHandler h(tag_builder);
|
|
area = 'D';
|
|
scan_info(track_index, true, h);
|
|
std::sprintf(track_name, DVDA_TRACKXXX_FMT, track_index + 1, area, suffix),
|
|
tail = list.emplace_after(
|
|
tail,
|
|
track_name,
|
|
tag_builder.Commit()
|
|
);
|
|
}
|
|
}
|
|
else {
|
|
LogError(dvdaiso_domain, "cannot select track");
|
|
}
|
|
}
|
|
return list;
|
|
}
|
|
|
|
static void
|
|
file_decode(DecoderClient &client, Path path_fs) {
|
|
if (!container_update(path_fs.GetDirectoryName())) {
|
|
return;
|
|
}
|
|
unsigned track;
|
|
bool downmix;
|
|
if (!get_subsong(path_fs, track, downmix)) {
|
|
LogError(dvdaiso_domain, "cannot get track number");
|
|
return;
|
|
}
|
|
|
|
// initialize reader
|
|
if (!dvda_reader->select_track(track)) {
|
|
LogError(dvdaiso_domain, "cannot select track");
|
|
return;
|
|
}
|
|
if (!dvda_reader->set_downmix(downmix)) {
|
|
LogError(dvdaiso_domain, "cannot downmix track");
|
|
return;
|
|
}
|
|
auto samplerate = dvda_reader->get_samplerate();
|
|
auto channels = dvda_reader->get_downmix() ? 2u : dvda_reader->get_channels();
|
|
std::vector<uint8_t> pcm_data(192000);
|
|
|
|
// initialize decoder
|
|
auto audio_format = CheckAudioFormat(samplerate, SampleFormat::S32, channels);
|
|
auto songtime = SongTime::FromS(dvda_reader->get_duration(track));
|
|
client.Ready(audio_format, true, songtime);
|
|
|
|
// play
|
|
auto cmd = client.GetCommand();
|
|
for (;;) {
|
|
auto pcm_size = pcm_data.size();
|
|
if (dvda_reader->read_frame(pcm_data.data(), &pcm_size)) {
|
|
if (pcm_size > 0) {
|
|
auto kbit_rate = 24 * channels * samplerate / 1000;
|
|
cmd = client.SubmitAudio(nullptr, std::span{ pcm_data.data(), pcm_size }, kbit_rate);
|
|
if (cmd == DecoderCommand::STOP) {
|
|
break;
|
|
}
|
|
if (cmd == DecoderCommand::SEEK) {
|
|
auto seconds = client.GetSeekTime().ToDoubleS();
|
|
if (dvda_reader->seek(seconds)) {
|
|
client.CommandFinished();
|
|
}
|
|
else {
|
|
client.SeekError();
|
|
}
|
|
cmd = client.GetCommand();
|
|
}
|
|
}
|
|
}
|
|
else {
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
static bool
|
|
scan_file(Path path_fs, TagHandler& handler) noexcept {
|
|
if (!container_update(path_fs.GetDirectoryName())) {
|
|
return false;
|
|
}
|
|
unsigned track_index;
|
|
bool downmix;
|
|
if (!get_subsong(path_fs, track_index, downmix)) {
|
|
LogError(dvdaiso_domain, "cannot get track number");
|
|
return false;
|
|
}
|
|
scan_info(track_index, downmix, handler);
|
|
return true;
|
|
}
|
|
|
|
static const char* const suffixes[] {
|
|
"iso",
|
|
nullptr
|
|
};
|
|
|
|
}
|
|
|
|
constexpr DecoderPlugin dvdaiso_decoder_plugin =
|
|
DecoderPlugin("dvdaiso", dvdaiso::file_decode, dvdaiso::scan_file)
|
|
.WithInit(dvdaiso::init, dvdaiso::finish)
|
|
.WithContainer(dvdaiso::container_scan)
|
|
.WithSuffixes(dvdaiso::suffixes);
|
|
|