CDImage: Support sub-images, use subimages for m3u

This commit is contained in:
Connor McLaughlin
2021-03-27 02:19:23 +10:00
parent 573aa6d9cc
commit 50d712c3fe
23 changed files with 443 additions and 312 deletions

View File

@@ -16,6 +16,7 @@ add_library(common
cd_image_ecm.cpp
cd_image_hasher.cpp
cd_image_hasher.h
cd_image_m3u.cpp
cd_image_memory.cpp
cd_image_mds.cpp
cd_image_pbp.cpp

View File

@@ -1,5 +1,6 @@
#include "cd_image.h"
#include "assert.h"
#include "file_system.h"
#include "log.h"
#include <array>
Log_SetChannel(CDImage);
@@ -54,6 +55,10 @@ std::unique_ptr<CDImage> CDImage::Open(const char* filename, Common::Error* erro
{
return OpenPBPImage(filename, error);
}
else if (CASE_COMPARE(extension, ".m3u") == 0)
{
return OpenM3uImage(filename, error);
}
#undef CASE_COMPARE
@@ -254,12 +259,12 @@ bool CDImage::ReadRawSector(void* buffer)
bool CDImage::ReadSubChannelQ(SubChannelQ* subq)
{
// handle case where we're at the end of the track/index
if (!m_current_index || m_position_in_index == m_current_index->length)
return GenerateSubChannelQ(subq, m_position_on_disc);
return ReadSubChannelQ(subq, *m_current_index, m_position_in_index);
}
// otherwise save the index lookup
GenerateSubChannelQ(subq, m_current_index, m_position_in_index);
bool CDImage::ReadSubChannelQ(SubChannelQ* subq, const Index& index, LBA lba_in_index)
{
GenerateSubChannelQ(subq, index, lba_in_index);
return true;
}
@@ -268,6 +273,51 @@ bool CDImage::HasNonStandardSubchannel() const
return false;
}
std::string CDImage::GetMetadata(const std::string_view& type) const
{
std::string result;
if (type == "title")
result = FileSystem::GetFileTitleFromPath(m_filename);
return result;
}
bool CDImage::HasSubImages() const
{
return false;
}
u32 CDImage::GetSubImageCount() const
{
return 0;
}
u32 CDImage::GetCurrentSubImage() const
{
return 0;
}
bool CDImage::SwitchSubImage(u32 index, Common::Error* error)
{
return false;
}
std::string CDImage::GetSubImageMetadata(u32 index, const std::string_view& type) const
{
return {};
}
void CDImage::CopyTOC(const CDImage* image)
{
m_lba_count = image->m_lba_count;
m_indices = image->m_indices;
m_tracks = image->m_tracks;
m_current_index = nullptr;
m_position_in_index = 0;
m_position_in_track = 0;
m_position_on_disc = 0;
}
const CDImage::Index* CDImage::GetIndexForDiscPosition(LBA pos)
{
for (const Index& index : m_indices)
@@ -304,23 +354,23 @@ bool CDImage::GenerateSubChannelQ(SubChannelQ* subq, LBA lba)
return false;
const u32 index_offset = index->start_lba_on_disc - lba;
GenerateSubChannelQ(subq, index, index_offset);
GenerateSubChannelQ(subq, *index, index_offset);
return true;
}
void CDImage::GenerateSubChannelQ(SubChannelQ* subq, const Index* index, u32 index_offset)
void CDImage::GenerateSubChannelQ(SubChannelQ* subq, const Index& index, u32 index_offset)
{
subq->control.bits = index->control.bits;
subq->control.bits = index.control.bits;
subq->track_number_bcd =
(index->track_number <= m_tracks.size() ? BinaryToBCD(index->track_number) : index->track_number);
subq->index_number_bcd = BinaryToBCD(index->index_number);
(index.track_number <= m_tracks.size() ? BinaryToBCD(index.track_number) : index.track_number);
subq->index_number_bcd = BinaryToBCD(index.index_number);
const Position relative_position =
Position::FromLBA(std::abs(static_cast<s32>(index->start_lba_in_track + index_offset)));
Position::FromLBA(std::abs(static_cast<s32>(index.start_lba_in_track + index_offset)));
std::tie(subq->relative_minute_bcd, subq->relative_second_bcd, subq->relative_frame_bcd) = relative_position.ToBCD();
subq->reserved = 0;
const Position absolute_position = Position::FromLBA(index->start_lba_on_disc + index_offset);
const Position absolute_position = Position::FromLBA(index.start_lba_on_disc + index_offset);
std::tie(subq->absolute_minute_bcd, subq->absolute_second_bcd, subq->absolute_frame_bcd) = absolute_position.ToBCD();
subq->crc = SubChannelQ::ComputeCRC(subq->data);
}

View File

@@ -135,6 +135,8 @@ public:
BitField<u8, bool, 5, 1> digital_copy_permitted;
BitField<u8, bool, 6, 1> data;
BitField<u8, bool, 7, 1> four_channel_audio;
Control& operator=(const Control& c) { bits = c.bits; return *this; }
};
struct
@@ -202,6 +204,7 @@ public:
static std::unique_ptr<CDImage> OpenEcmImage(const char* filename, Common::Error* error);
static std::unique_ptr<CDImage> OpenMdsImage(const char* filename, Common::Error* error);
static std::unique_ptr<CDImage> OpenPBPImage(const char* filename, Common::Error* error);
static std::unique_ptr<CDImage> OpenM3uImage(const char* filename, Common::Error* error);
static std::unique_ptr<CDImage>
CreateMemoryImage(CDImage* image, ProgressCallback* progress = ProgressCallback::NullProgressCallback);
@@ -247,7 +250,10 @@ public:
bool ReadRawSector(void* buffer);
// Reads sub-channel Q for the current LBA.
virtual bool ReadSubChannelQ(SubChannelQ* subq);
bool ReadSubChannelQ(SubChannelQ* subq);
// Reads sub-channel Q for the specified index+LBA.
virtual bool ReadSubChannelQ(SubChannelQ* subq, const Index& index, LBA lba_in_index);
// Returns true if the image has replacement subchannel data.
virtual bool HasNonStandardSubchannel() const;
@@ -255,7 +261,27 @@ public:
// Reads a single sector from an index.
virtual bool ReadSectorFromIndex(void* buffer, const Index& index, LBA lba_in_index) = 0;
// Retrieve image metadata.
virtual std::string GetMetadata(const std::string_view& type) const;
// Returns true if this image type has sub-images (e.g. m3u).
virtual bool HasSubImages() const;
// Returns the number of sub-images in this image, if the format supports multiple.
virtual u32 GetSubImageCount() const;
// Returns the current sub-image index, if any.
virtual u32 GetCurrentSubImage() const;
// Changes the current sub-image. If this fails, the image state is unchanged.
virtual bool SwitchSubImage(u32 index, Common::Error* error);
// Retrieve sub-image metadata.
virtual std::string GetSubImageMetadata(u32 index, const std::string_view& type) const;
protected:
void CopyTOC(const CDImage* image);
const Index* GetIndexForDiscPosition(LBA pos);
const Index* GetIndexForTrackPosition(u32 track_number, LBA track_pos);
@@ -263,7 +289,7 @@ protected:
bool GenerateSubChannelQ(SubChannelQ* subq, LBA lba);
/// Generates sub-channel Q from the given index and index-offset.
void GenerateSubChannelQ(SubChannelQ* subq, const Index* index, u32 index_offset);
void GenerateSubChannelQ(SubChannelQ* subq, const Index& index, u32 index_offset);
/// Synthesis of lead-out data.
void AddLeadOutIndex();
@@ -274,6 +300,7 @@ protected:
std::vector<Track> m_tracks;
std::vector<Index> m_indices;
private:
// Position on disc.
LBA m_position_on_disc = 0;

View File

@@ -14,7 +14,7 @@ public:
bool Open(const char* filename, Common::Error* error);
bool ReadSubChannelQ(SubChannelQ* subq) override;
bool ReadSubChannelQ(SubChannelQ* subq, const Index& index, LBA lba_in_index) override;
bool HasNonStandardSubchannel() const override;
protected:
@@ -99,12 +99,12 @@ bool CDImageBin::Open(const char* filename, Common::Error* error)
return Seek(1, Position{0, 0, 0});
}
bool CDImageBin::ReadSubChannelQ(SubChannelQ* subq)
bool CDImageBin::ReadSubChannelQ(SubChannelQ* subq, const Index& index, LBA lba_in_index)
{
if (m_sbi.GetReplacementSubChannelQ(m_position_on_disc, subq))
if (m_sbi.GetReplacementSubChannelQ(index.start_lba_on_disc + lba_in_index, subq))
return true;
return CDImage::ReadSubChannelQ(subq);
return CDImage::ReadSubChannelQ(subq, index, lba_in_index);
}
bool CDImageBin::HasNonStandardSubchannel() const

View File

@@ -49,7 +49,7 @@ public:
bool Open(const char* filename, Common::Error* error);
bool ReadSubChannelQ(SubChannelQ* subq) override;
bool ReadSubChannelQ(SubChannelQ* subq, const Index& index, LBA lba_in_index) override;
bool HasNonStandardSubchannel() const override;
protected:
@@ -284,14 +284,14 @@ bool CDImageCHD::Open(const char* filename, Common::Error* error)
return Seek(1, Position{0, 0, 0});
}
bool CDImageCHD::ReadSubChannelQ(SubChannelQ* subq)
bool CDImageCHD::ReadSubChannelQ(SubChannelQ* subq, const Index& index, LBA lba_in_index)
{
if (m_sbi.GetReplacementSubChannelQ(m_position_on_disc, subq))
if (m_sbi.GetReplacementSubChannelQ(index.start_lba_on_disc + lba_in_index, subq))
return true;
// TODO: Read subchannel data from CHD
return CDImage::ReadSubChannelQ(subq);
return CDImage::ReadSubChannelQ(subq, index, lba_in_index);
}
bool CDImageCHD::HasNonStandardSubchannel() const

View File

@@ -18,7 +18,7 @@ public:
bool OpenAndParse(const char* filename, Common::Error* error);
bool ReadSubChannelQ(SubChannelQ* subq) override;
bool ReadSubChannelQ(SubChannelQ* subq, const Index& index, LBA lba_in_index) override;
bool HasNonStandardSubchannel() const override;
protected:
@@ -277,12 +277,12 @@ bool CDImageCueSheet::OpenAndParse(const char* filename, Common::Error* error)
return Seek(1, Position{0, 0, 0});
}
bool CDImageCueSheet::ReadSubChannelQ(SubChannelQ* subq)
bool CDImageCueSheet::ReadSubChannelQ(SubChannelQ* subq, const Index& index, LBA lba_in_index)
{
if (m_sbi.GetReplacementSubChannelQ(m_position_on_disc, subq))
if (m_sbi.GetReplacementSubChannelQ(index.start_lba_on_disc + lba_in_index, subq))
return true;
return CDImage::ReadSubChannelQ(subq);
return CDImage::ReadSubChannelQ(subq, index, lba_in_index);
}
bool CDImageCueSheet::HasNonStandardSubchannel() const

View File

@@ -164,7 +164,7 @@ public:
bool Open(const char* filename, Common::Error* error);
bool ReadSubChannelQ(SubChannelQ* subq) override;
bool ReadSubChannelQ(SubChannelQ* subq, const Index& index, LBA lba_in_index) override;
bool HasNonStandardSubchannel() const override;
protected:
@@ -492,12 +492,12 @@ bool CDImageEcm::ReadChunks(u32 disc_offset, u32 size)
return true;
}
bool CDImageEcm::ReadSubChannelQ(SubChannelQ* subq)
bool CDImageEcm::ReadSubChannelQ(SubChannelQ* subq, const Index& index, LBA lba_in_index)
{
if (m_sbi.GetReplacementSubChannelQ(m_position_on_disc, subq))
if (m_sbi.GetReplacementSubChannelQ(index.start_lba_on_disc + lba_in_index, subq))
return true;
return CDImage::ReadSubChannelQ(subq);
return CDImage::ReadSubChannelQ(subq, index, lba_in_index);
}
bool CDImageEcm::HasNonStandardSubchannel() const

173
src/common/cd_image_m3u.cpp Normal file
View File

@@ -0,0 +1,173 @@
#include "assert.h"
#include "cd_image.h"
#include "cd_subchannel_replacement.h"
#include "file_system.h"
#include "log.h"
#include <algorithm>
#include <cerrno>
#include <fstream>
#include <map>
Log_SetChannel(CDImageMemory);
class CDImageM3u : public CDImage
{
public:
CDImageM3u();
~CDImageM3u() override;
bool Open(const char* path, Common::Error* Error);
bool ReadSubChannelQ(SubChannelQ* subq, const Index& index, LBA lba_in_index) override;
bool HasNonStandardSubchannel() const override;
bool HasSubImages() const override;
u32 GetSubImageCount() const override;
u32 GetCurrentSubImage() const override;
std::string GetSubImageMetadata(u32 index, const std::string_view& type) const override;
bool SwitchSubImage(u32 index, Common::Error* error) override;
protected:
bool ReadSectorFromIndex(void* buffer, const Index& index, LBA lba_in_index) override;
private:
struct Entry
{
// TODO: Worth storing any other data?
std::string filename;
std::string title;
};
std::vector<Entry> m_entries;
std::unique_ptr<CDImage> m_current_image;
u32 m_current_image_index = UINT32_C(0xFFFFFFFF);
};
CDImageM3u::CDImageM3u() = default;
CDImageM3u::~CDImageM3u() = default;
bool CDImageM3u::Open(const char* path, Common::Error* error)
{
std::ifstream ifs(path);
if (!ifs.is_open())
{
Log_ErrorPrintf("Failed to open %s", path);
return false;
}
m_filename = path;
std::vector<std::string> entries;
std::string line;
while (std::getline(ifs, line))
{
u32 start_offset = 0;
while (start_offset < line.size() && std::isspace(line[start_offset]))
start_offset++;
// skip comments
if (start_offset == line.size() || line[start_offset] == '#')
continue;
// strip ending whitespace
u32 end_offset = static_cast<u32>(line.size()) - 1;
while (std::isspace(line[end_offset]) && end_offset > start_offset)
end_offset--;
// anything?
if (start_offset == end_offset)
continue;
Entry entry;
entry.filename.assign(line.begin() + start_offset, line.begin() + end_offset + 1);
entry.title = FileSystem::GetFileTitleFromPath(entry.filename);
if (!FileSystem::IsAbsolutePath(entry.filename))
{
SmallString absolute_path;
FileSystem::BuildPathRelativeToFile(absolute_path, path, entry.filename.c_str());
entry.filename = absolute_path;
}
Log_DevPrintf("Read path from m3u: '%s'", entry.filename.c_str());
m_entries.push_back(std::move(entry));
}
Log_InfoPrintf("Loaded %zu paths from m3u '%s'", m_entries.size(), path);
return !m_entries.empty() && SwitchSubImage(0, error);
}
bool CDImageM3u::HasNonStandardSubchannel() const
{
return m_current_image->HasNonStandardSubchannel();
}
bool CDImageM3u::HasSubImages() const
{
return true;
}
u32 CDImageM3u::GetSubImageCount() const
{
return static_cast<u32>(m_entries.size());
}
u32 CDImageM3u::GetCurrentSubImage() const
{
return m_current_image_index;
}
bool CDImageM3u::SwitchSubImage(u32 index, Common::Error* error)
{
if (index >= m_entries.size())
return false;
else if (index == m_current_image_index)
return true;
const Entry& entry = m_entries[index];
std::unique_ptr<CDImage> new_image = CDImage::Open(entry.filename.c_str(), error);
if (!new_image)
{
Log_ErrorPrintf("Failed to load subimage %u (%s)", index, entry.filename.c_str());
return false;
}
CopyTOC(new_image.get());
m_current_image = std::move(new_image);
m_current_image_index = index;
if (!Seek(1, Position{0, 0, 0}))
Panic("Failed to seek to start after sub-image change.");
return true;
}
std::string CDImageM3u::GetSubImageMetadata(u32 index, const std::string_view& type) const
{
if (index > m_entries.size())
return {};
if (type == "title")
return m_entries[index].title;
else if (type == "file_title")
return std::string(FileSystem::GetFileTitleFromPath(m_entries[index].filename));
return CDImage::GetSubImageMetadata(index, type);
}
bool CDImageM3u::ReadSectorFromIndex(void* buffer, const Index& index, LBA lba_in_index)
{
return m_current_image->ReadSectorFromIndex(buffer, index, lba_in_index);
}
bool CDImageM3u::ReadSubChannelQ(SubChannelQ* subq, const Index& index, LBA lba_in_index)
{
return m_current_image->ReadSubChannelQ(subq, index, lba_in_index);
}
std::unique_ptr<CDImage> CDImage::OpenM3uImage(const char* filename, Common::Error* error)
{
std::unique_ptr<CDImageM3u> image = std::make_unique<CDImageM3u>();
if (!image->Open(filename, error))
return {};
return image;
}

View File

@@ -37,7 +37,7 @@ public:
bool OpenAndParse(const char* filename, Common::Error* error);
bool ReadSubChannelQ(SubChannelQ* subq) override;
bool ReadSubChannelQ(SubChannelQ* subq, const Index& index, LBA lba_in_index) override;
bool HasNonStandardSubchannel() const override;
protected:
@@ -255,12 +255,12 @@ bool CDImageMds::OpenAndParse(const char* filename, Common::Error* error)
return Seek(1, Position{0, 0, 0});
}
bool CDImageMds::ReadSubChannelQ(SubChannelQ* subq)
bool CDImageMds::ReadSubChannelQ(SubChannelQ* subq, const Index& index, LBA lba_in_index)
{
if (m_sbi.GetReplacementSubChannelQ(m_position_on_disc, subq))
if (m_sbi.GetReplacementSubChannelQ(index.start_lba_on_disc + lba_in_index, subq))
return true;
return CDImage::ReadSubChannelQ(subq);
return CDImage::ReadSubChannelQ(subq, index, lba_in_index);
}
bool CDImageMds::HasNonStandardSubchannel() const

View File

@@ -17,7 +17,7 @@ public:
bool CopyImage(CDImage* image, ProgressCallback* progress);
bool ReadSubChannelQ(SubChannelQ* subq) override;
bool ReadSubChannelQ(SubChannelQ* subq, const Index& index, LBA lba_in_index) override;
bool HasNonStandardSubchannel() const override;
protected:
@@ -116,12 +116,12 @@ bool CDImageMemory::CopyImage(CDImage* image, ProgressCallback* progress)
return Seek(1, Position{0, 0, 0});
}
bool CDImageMemory::ReadSubChannelQ(SubChannelQ* subq)
bool CDImageMemory::ReadSubChannelQ(SubChannelQ* subq, const Index& index, LBA lba_in_index)
{
if (m_sbi.GetReplacementSubChannelQ(m_position_on_disc, subq))
if (m_sbi.GetReplacementSubChannelQ(index.start_lba_on_disc + lba_in_index, subq))
return true;
return CDImage::ReadSubChannelQ(subq);
return CDImage::ReadSubChannelQ(subq, index, lba_in_index);
}
bool CDImageMemory::HasNonStandardSubchannel() const

View File

@@ -22,7 +22,7 @@ public:
bool Open(const char* filename, Common::Error* error);
bool ReadSubChannelQ(SubChannelQ* subq) override;
bool ReadSubChannelQ(SubChannelQ* subq, const Index& index, LBA lba_in_index) override;
bool HasNonStandardSubchannel() const override;
protected:
@@ -698,12 +698,12 @@ bool CDImagePBP::DecompressBlock(BlockInfo block_info)
return true;
}
bool CDImagePBP::ReadSubChannelQ(SubChannelQ* subq)
bool CDImagePBP::ReadSubChannelQ(SubChannelQ* subq, const Index& index, LBA lba_in_index)
{
if (m_sbi.GetReplacementSubChannelQ(m_position_on_disc, subq))
if (m_sbi.GetReplacementSubChannelQ(index.start_lba_on_disc + lba_in_index, subq))
return true;
return CDImage::ReadSubChannelQ(subq);
return CDImage::ReadSubChannelQ(subq, index, lba_in_index);
}
bool CDImagePBP::HasNonStandardSubchannel() const

View File

@@ -134,6 +134,7 @@
<ClCompile Include="cd_image_cue.cpp" />
<ClCompile Include="cd_image_ecm.cpp" />
<ClCompile Include="cd_image_hasher.cpp" />
<ClCompile Include="cd_image_m3u.cpp" />
<ClCompile Include="cd_image_mds.cpp" />
<ClCompile Include="cd_image_memory.cpp" />
<ClCompile Include="cd_image_pbp.cpp" />

View File

@@ -216,6 +216,7 @@
<ClCompile Include="cd_image_mds.cpp" />
<ClCompile Include="cd_image_pbp.cpp" />
<ClCompile Include="error.cpp" />
<ClCompile Include="cd_image_m3u.cpp" />
</ItemGroup>
<ItemGroup>
<Natvis Include="bitfield.natvis" />