diff --git a/bindings/c/CMakeLists.txt b/bindings/c/CMakeLists.txt index 1bd75636..0f90a6b5 100644 --- a/bindings/c/CMakeLists.txt +++ b/bindings/c/CMakeLists.txt @@ -25,6 +25,7 @@ include_directories( ${CMAKE_CURRENT_SOURCE_DIR}/../../taglib/ogg/opus ${CMAKE_CURRENT_SOURCE_DIR}/../../taglib/dsf ${CMAKE_CURRENT_SOURCE_DIR}/../../taglib/dsdiff + ${CMAKE_CURRENT_SOURCE_DIR}/../../taglib/shn ) set(tag_c_HDRS tag_c.h) diff --git a/bindings/c/tag_c.cpp b/bindings/c/tag_c.cpp index b90d17f5..8fc14d67 100644 --- a/bindings/c/tag_c.cpp +++ b/bindings/c/tag_c.cpp @@ -55,6 +55,7 @@ #include "opusfile.h" #include "dsffile.h" #include "dsdifffile.h" +#include "shnfile.h" #include "tag.h" #include "id3v2framefactory.h" @@ -190,6 +191,9 @@ TagLib_File *taglib_file_new_type(const char *filename, TagLib_File_Type type) case TagLib_File_DSDIFF: file = new DSDIFF::File(filename); break; + case TagLib_File_SHN: + file = new SHN::File(filename); + break; default: break; } diff --git a/bindings/c/tag_c.h b/bindings/c/tag_c.h index f3f4e315..8d154bde 100644 --- a/bindings/c/tag_c.h +++ b/bindings/c/tag_c.h @@ -130,7 +130,8 @@ typedef enum { TagLib_File_XM, TagLib_File_Opus, TagLib_File_DSF, - TagLib_File_DSDIFF + TagLib_File_DSDIFF, + TagLib_File_SHN } TagLib_File_Type; /*! diff --git a/taglib/CMakeLists.txt b/taglib/CMakeLists.txt index c6785fb8..668632f8 100644 --- a/taglib/CMakeLists.txt +++ b/taglib/CMakeLists.txt @@ -26,6 +26,7 @@ include_directories( ${CMAKE_CURRENT_SOURCE_DIR}/xm ${CMAKE_CURRENT_SOURCE_DIR}/dsf ${CMAKE_CURRENT_SOURCE_DIR}/dsdiff + ${CMAKE_CURRENT_SOURCE_DIR}/shn ) set(tag_HDRS @@ -141,6 +142,9 @@ set(tag_HDRS dsdiff/dsdifffile.h dsdiff/dsdiffproperties.h dsdiff/dsdiffdiintag.h + shn/shnfile.h + shn/shnproperties.h + shn/shntag.h ) set(mpeg_SRCS @@ -308,6 +312,12 @@ set(dsdiff_SRCS dsdiff/dsdiffdiintag.cpp ) +set(shn_SRCS + shn/shnfile.cpp + shn/shnproperties.cpp + shn/shntag.cpp +) + set(toolkit_SRCS toolkit/tstring.cpp toolkit/tstringlist.cpp @@ -331,7 +341,7 @@ set(tag_LIB_SRCS ${vorbis_SRCS} ${oggflacs_SRCS} ${mpc_SRCS} ${ape_SRCS} ${toolkit_SRCS} ${flacs_SRCS} ${wavpack_SRCS} ${speex_SRCS} ${trueaudio_SRCS} ${riff_SRCS} ${aiff_SRCS} ${wav_SRCS} ${asf_SRCS} ${mp4_SRCS} ${mod_SRCS} ${s3m_SRCS} ${it_SRCS} ${xm_SRCS} ${opus_SRCS} - ${dsf_SRCS} ${dsdiff_SRCS} + ${dsf_SRCS} ${dsdiff_SRCS} ${shn_SRCS} tag.cpp tagunion.cpp fileref.cpp diff --git a/taglib/fileref.cpp b/taglib/fileref.cpp index c59b65f3..2ef8237d 100644 --- a/taglib/fileref.cpp +++ b/taglib/fileref.cpp @@ -57,6 +57,7 @@ #include "xmfile.h" #include "dsffile.h" #include "dsdifffile.h" +#include "shnfile.h" using namespace TagLib; @@ -180,6 +181,8 @@ namespace file = new DSF::File(stream, readAudioProperties, audioPropertiesStyle); else if(ext == "DFF" || ext == "DSDIFF") file = new DSDIFF::File(stream, readAudioProperties, audioPropertiesStyle); + else if(ext == "SHN") + file = new SHN::File(stream, readAudioProperties, audioPropertiesStyle); // if file is not valid, leave it to content-based detection. @@ -231,6 +234,8 @@ namespace file = new DSF::File(stream, readAudioProperties, audioPropertiesStyle); else if(DSDIFF::File::isSupported(stream)) file = new DSDIFF::File(stream, readAudioProperties, audioPropertiesStyle); + else if(SHN::File::isSupported(stream)) + file = new SHN::File(stream, readAudioProperties, audioPropertiesStyle); // isSupported() only does a quick check, so double check the file here. @@ -436,6 +441,7 @@ StringList FileRef::defaultFileExtensions() l.append("dsf"); l.append("dff"); l.append("dsdiff"); // alias for "dff" + l.append("shn"); return l; } diff --git a/taglib/shn/shnfile.cpp b/taglib/shn/shnfile.cpp new file mode 100644 index 00000000..12f61ba0 --- /dev/null +++ b/taglib/shn/shnfile.cpp @@ -0,0 +1,623 @@ +/*************************************************************************** + copyright : (C) 2020-2024 Stephen F. Booth + email : me@sbooth.org + ***************************************************************************/ + +/*************************************************************************** + * This library is free software; you can redistribute it and/or modify * + * it under the terms of the GNU Lesser General Public License version * + * 2.1 as published by the Free Software Foundation. * + * * + * This library 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 this library; if not, write to the Free Software * + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * + * 02110-1301 USA * + * * + * Alternatively, this file is available under the Mozilla Public * + * License Version 1.1. You may obtain a copy of the License at * + * http://www.mozilla.org/MPL/ * + ***************************************************************************/ + +#include + +#include "shnfile.h" +#include "shnutils.h" + +#include "tdebug.h" +#include "tutils.h" +#include "tagutils.h" +#include "tpropertymap.h" + +using namespace TagLib; + +namespace { + +// MARK: Constants + + const auto MIN_SUPPORTED_VERSION { 1 }; + const auto MAX_SUPPORTED_VERSION { 3 }; + + const auto DEFAULT_BLOCK_SIZE { 256 }; + + const auto CHANSIZE { 0 }; + + const auto FNSIZE { 2 }; + const auto FN_VERBATIM { 9 }; + + const auto VERBATIM_CKSIZE_SIZE { 5 }; + const auto VERBATIM_BYTE_SIZE { 8 }; + const auto VERBATIM_CHUNK_MAX { 256 }; + + const auto ULONGSIZE { 2 }; + const auto NSKIPSIZE { 1 }; + const auto LPCQSIZE { 2 }; + const auto XBYTESIZE { 7 }; + + const auto TYPESIZE { 4 }; + + const auto MAX_CHANNELS { 8 }; + const auto MAX_BLOCKSIZE { 65535 }; + + const auto CANONICAL_HEADER_SIZE { 44 }; + + const auto WAVE_FORMAT_PCM { 0x0001 }; + +// MARK: Variable-Length Input + + /// Variable-length input using Golomb-Rice coding + class vario_input { + + public: + + static constexpr uint32_t sMaskTable [] = { + 0x0, + 0x1, 0x3, 0x7, 0xf, + 0x1f, 0x3f, 0x7f, 0xff, + 0x1ff, 0x3ff, 0x7ff, 0xfff, + 0x1fff, 0x3fff, 0x7fff, 0xffff, + 0x1ffff, 0x3ffff, 0x7ffff, 0xfffff, + 0x1fffff, 0x3fffff, 0x7fffff, 0xffffff, + 0x1ffffff, 0x3ffffff, 0x7ffffff, 0xfffffff, + 0x1fffffff, 0x3fffffff, 0x7fffffff, 0xffffffff + }; + + /// Creates a new `vario_input` object with an internal buffer of the specified size + /// - warning: Sizes other than `512` will break seeking + vario_input(File *file, size_t size = 512) noexcept + : file_{file}, size_{size} + { + byte_buffer_ = new (std::nothrow) uint8_t [size_]; + byte_buffer_position_ = byte_buffer_; + } + + ~vario_input() + { + delete [] byte_buffer_; + } + + vario_input() = delete; + vario_input(const vario_input&) = delete; + vario_input(vario_input&&) = delete; + vario_input& operator=(const vario_input&) = delete; + vario_input& operator=(vario_input&&) = delete; + + explicit operator bool() const noexcept + { + return byte_buffer_ != nullptr; + } + + /// Reads a single unsigned value from the specified bin + bool uvar_get(int32_t& i32, size_t bin) + { + if(bits_available_ == 0) { + if(!word_get(bit_buffer_)) + return false; + bits_available_ = 32; + } + + int32_t result; + for(result = 0; !(bit_buffer_ & (1L << --bits_available_)); ++result) { + if(bits_available_ == 0) { + if(!word_get(bit_buffer_)) + return false; + bits_available_ = 32; + } + } + + while(bin != 0) { + if(bits_available_ >= bin) { + result = (result << bin) | static_cast((bit_buffer_ >> (bits_available_ - bin)) & sMaskTable[bin]); + bits_available_ -= bin; + bin = 0; + } + else { + result = (result << bits_available_) | static_cast(bit_buffer_ & sMaskTable[bits_available_]); + bin -= bits_available_; + if(!word_get(bit_buffer_)) + return false; + bits_available_ = 32; + } + } + + i32 = result; + return true; + } + + /// Reads the unsigned Golomb-Rice code + bool ulong_get(uint32_t& ui32) + { + int32_t bitcount; + if(!uvar_get(bitcount, ULONGSIZE)) + return false; + + int32_t i32; + if(!uvar_get(i32, static_cast(bitcount))) + return false; + + ui32 = static_cast(i32); + return true; + } + + bool uint_get(uint32_t& ui32, int version, size_t bin) + { + if(version == 0) { + int32_t i32; + if(!uvar_get(i32, bin)) + return false; + ui32 = static_cast(i32); + return true; + } + else + return ulong_get(ui32); + } + + private: + + /// Input stream + File *file_ { nullptr }; + /// Size of `byte_buffer_` in bytes + size_t size_ { 0 }; + /// Byte buffer + uint8_t *byte_buffer_ { nullptr }; + /// Current position in `byte_buffer_` + uint8_t *byte_buffer_position_ { nullptr }; + /// Bytes available in `byte_buffer_` + size_t bytes_available_ { 0 }; + /// Bit buffer + uint32_t bit_buffer_ { 0 }; + /// Bits available in `bit_buffer_` + size_t bits_available_ { 0 }; + + /// Reads a single `uint32_t` from the byte buffer, refilling if necessary + bool word_get(uint32_t& ui32) + { + if(bytes_available_ < 4) { + auto block = file_->readBlock(size_); + if(block.size() < 4) + return false; + memcpy(byte_buffer_, block.data(), block.size()); + bytes_available_ += block.size(); + byte_buffer_position_ = byte_buffer_; + } + + ui32 = static_cast((static_cast(byte_buffer_position_[0]) << 24) | (static_cast(byte_buffer_position_[1]) << 16) | (static_cast(byte_buffer_position_[2]) << 8) | static_cast(byte_buffer_position_[3])); + + byte_buffer_position_ += 4; + bytes_available_ -= 4; + + return true; + } + }; + +// MARK: Unsigned Integer Reading + + template || std::is_same_v || std::is_same_v>> + T read_uint(void *p, uintptr_t n, bool big) noexcept + { + T value = *reinterpret_cast(reinterpret_cast(p) + n); + + auto system_big = Utils::systemByteOrder() == Utils::BigEndian; + if(big != system_big) + value = Utils::byteSwap(value); + + return value; + } + + uint64_t read_uint_big64(void *p, uintptr_t n) noexcept + { + return read_uint(p, n, true); + } + + uint32_t read_uint_big32(void *p, uintptr_t n) noexcept + { + return read_uint(p, n, true); + } + + uint32_t read_uint_little32(void *p, uintptr_t n) noexcept + { + return read_uint(p, n, false); + } + + uint16_t read_uint_big16(void *p, uintptr_t n) noexcept + { + return read_uint(p, n, true); + } + + uint16_t read_uint_little16(void *p, uintptr_t n) noexcept + { + return read_uint(p, n, false); + } +} // namespace + +class SHN::File::FilePrivate +{ +public: + FilePrivate() = default; + ~FilePrivate() = default; + + FilePrivate(const FilePrivate &) = delete; + FilePrivate &operator=(const FilePrivate &) = delete; + + std::unique_ptr properties; + std::unique_ptr tag; +}; + +bool SHN::File::isSupported(IOStream *stream) +{ + // A Shorten file has to start with "ajkg" + const ByteVector id = Utils::readHeader(stream, 4, false); + return id.startsWith("ajkg"); +} + +SHN::File::File(FileName file, bool readProperties, + AudioProperties::ReadStyle propertiesStyle) : + TagLib::File(file), + d(std::make_unique()) +{ + if(isOpen()) + read(propertiesStyle); +} + +SHN::File::File(IOStream *stream, bool readProperties, + AudioProperties::ReadStyle propertiesStyle) : + TagLib::File(stream), + d(std::make_unique()) +{ + if(isOpen()) + read(propertiesStyle); +} + +SHN::File::~File() = default; + +SHN::Tag *SHN::File::tag() const +{ + return d->tag.get(); +} + +PropertyMap SHN::File::properties() const +{ + return d->tag->properties(); +} + +PropertyMap SHN::File::setProperties(const PropertyMap &properties) +{ + return d->tag->setProperties(properties); +} + +SHN::Properties *SHN::File::audioProperties() const +{ + return d->properties.get(); +} + +bool SHN::File::save() +{ + if(readOnly()) { + debug("SHN::File::save() - Cannot save to a read only file."); + return false; + } + + debug("SHN::File::save() - Saving not supported."); + return false; +} + +void SHN::File::read(AudioProperties::ReadStyle propertiesStyle) +{ + if(!isOpen()) + return; + + // Read magic number + auto magic = readBlock(4); + if(magic != "ajkg") { + debug("SHN::File::read() -- Not a Shorten file."); + setValid(false); + return; + } + + PropertyValues props{}; + + // Read file version + auto version = readBlock(1).toUInt(); + if(version < MIN_SUPPORTED_VERSION || version > MAX_SUPPORTED_VERSION) { + debug("SHN::File::read() -- Unsupported version."); + setValid(false); + return; + } + props.version = version; + + // Set up variable length input + vario_input input{this, 512}; + if(!input) { + debug("SHN::File::read() -- Unable to allocate variable-length input."); + setValid(false); + return; + } + + // Read internal file type + uint32_t ftype; + if(!input.uint_get(ftype, version, TYPESIZE)) { + debug("SHN::File::read() -- Unable to read internal file type."); + setValid(false); + return; + } + props.internal_file_type = static_cast(ftype); + + // Read number of channels + uint32_t nchan = 0; + if(!input.uint_get(nchan, version, CHANSIZE) || nchan == 0 || nchan > MAX_CHANNELS) { + debug("SHN::File::read() -- Invalid or unsupported channel count."); + setValid(false); + return; + } + props.channel_count = static_cast(nchan); + + // Read blocksize if version > 0 + if(version > 0) { + uint32_t blocksize = 0; + if(!input.uint_get(blocksize, version, static_cast(log2(DEFAULT_BLOCK_SIZE))) || blocksize == 0 || blocksize > MAX_BLOCKSIZE) { + debug("SHN::File::read() -- Invalid or unsupported block size."); + setValid(false); + return; + } + + uint32_t maxnlpc = 0; + if(!input.uint_get(maxnlpc, version, LPCQSIZE) /*|| maxnlpc > 1024*/) { + debug("SHN::File::read() -- Invalid maxnlpc."); + setValid(false); + return; + } + + uint32_t nmean = 0; + if(!input.uint_get(nmean, version, 0) /*|| nmean > 32768*/) { + debug("SHN::File::read() -- Invalid nmean."); + setValid(false); + return; + } + + uint32_t nskip; + if(!input.uint_get(nskip, version, NSKIPSIZE)) { + setValid(false); + return; + } + + for(uint32_t i = 0; i < nskip; ++i) { + uint32_t dummy; + if(!input.uint_get(dummy, version, XBYTESIZE)) { + setValid(false); + return; + } + } + } + + // Parse the WAVE or AIFF header in the verbatim section + + int32_t fn; + if(!input.uvar_get(fn, FNSIZE) || fn != FN_VERBATIM) { + debug("SHN::File::read() -- Missing initial verbatim section."); + setValid(false); + return; + } + + int32_t header_size; + if(!input.uvar_get(header_size, VERBATIM_CKSIZE_SIZE) || header_size < CANONICAL_HEADER_SIZE || header_size > VERBATIM_CHUNK_MAX) { + debug("SHN::File::read() -- Incorrect header size."); + setValid(false); + return; + } + + uint8_t header_bytes [header_size]; + for(int32_t i = 0; i < header_size; ++i) { + int32_t byte; + if(!input.uvar_get(byte, VERBATIM_BYTE_SIZE)) { + debug("SHN::File::read() -- Unable to read header."); + setValid(false); + return; + } + + header_bytes[i] = static_cast(byte); + } + + // header_bytes is at least CANONICAL_HEADER_SIZE (44) bytes in size + + auto chunkID = read_uint_big32(header_bytes, 0); +// auto chunkSize = read_uint_big32(header_bytes, 4); + + const auto chunkData = header_bytes + 8; + const auto size = header_size - 8; + + // WAVE + if(chunkID == 'RIFF') { + uintptr_t offset = 0; + + chunkID = read_uint_big32(chunkData, offset); + offset += 4; + if(chunkID != 'WAVE') { + debug("SHN::File::read() -- Missing 'WAVE' in 'RIFF' chunk."); + setValid(false); + return; + } + + auto sawFormatChunk = false; + uint32_t dataChunkSize = 0; + uint16_t blockAlign = 0; + + while(offset < size) { + chunkID = read_uint_big32(chunkData, offset); + offset += 4; + + auto chunkSize = read_uint_little32(chunkData, offset); + offset += 4; + + switch(chunkID) { + case 'fmt ': + { + if(chunkSize < 16) { + debug("SHN::File::read() -- 'fmt ' chunk is too small."); + setValid(false); + return; + } + + auto format_tag = read_uint_little16(chunkData, offset); + offset += 2; + if(format_tag != WAVE_FORMAT_PCM) { + debug("SHN::File::read() -- Unsupported WAVE format tag."); + setValid(false); + return; + } + + auto channels = read_uint_little16(chunkData, offset); + offset += 2; + if(props.channel_count != channels) + debug("SHN::File::read() -- Channel count mismatch between Shorten and 'fmt ' chunk."); + + props.sample_rate = static_cast(read_uint_little32(chunkData, offset)); + offset += 4; + + // Skip average bytes per second + offset += 4; + + blockAlign = read_uint_little16(chunkData, offset); + offset += 2; + + props.bits_per_sample = static_cast(read_uint_little16(chunkData, offset)); + offset += 2; + + if(chunkSize > 16) + debug("SHN::File::read() -- Extra bytes in 'fmt ' chunk not parsed."); + + sawFormatChunk = true; + + break; + } + + case 'data': + dataChunkSize = chunkSize; + break; + } + } + + if(!sawFormatChunk) { + debug("SHN::File::read() -- Missing 'fmt ' chunk."); + setValid(false); + return; + } + + if(dataChunkSize && blockAlign) + props.sample_frames = static_cast(dataChunkSize / blockAlign); + } + // AIFF + else if(chunkID == 'FORM') { + uintptr_t offset = 0; + + auto chunkID = read_uint_big32(chunkData, offset); + offset += 4; + if(chunkID != 'AIFF' && chunkID != 'AIFC') { + debug("SHN::File::read() -- Missing 'AIFF' or 'AIFC' in 'FORM' chunk."); + setValid(false); + return; + } + +// if(chunkID == 'AIFC') +// props.big_endian = true; + + auto sawCommonChunk = false; + while(offset < size) { + chunkID = read_uint_big32(chunkData, offset); + offset += 4; + + auto chunkSize = read_uint_big32(chunkData, offset); + offset += 4; + + // All chunks must have an even length but the pad byte is not included in ckSize + chunkSize += (chunkSize & 1); + + switch(chunkID) { + case 'COMM': + { + if(chunkSize < 18) { + debug("SHN::File::read() -- 'COMM' chunk is too small."); + setValid(false); + return; + } + + auto channels = read_uint_big16(chunkData, offset); + offset += 2; + if(props.channel_count != channels) + debug("SHN::File::read() -- Channel count mismatch between Shorten and 'COMM' chunk."); + + props.sample_frames = static_cast(read_uint_big32(chunkData, offset)); + offset += 4; + + props.bits_per_sample = static_cast(read_uint_big16(chunkData, offset)); + offset += 2; + + // sample rate is IEEE 754 80-bit extended float (16-bit exponent, 1-bit integer part, 63-bit fraction) + auto exp = static_cast(read_uint_big16(chunkData, offset)) - 16383 - 63; + offset += 2; + if(exp < -63 || exp > 63) { + debug("SHN::File::read() -- exp out of range."); + setValid(false); + return; + } + + auto frac = read_uint_big64(chunkData, offset); + offset += 8; + if(exp >= 0) + props.sample_rate = static_cast(frac << exp); + else + props.sample_rate = static_cast((frac + (static_cast(1) << (-exp - 1))) >> -exp); + + if(chunkSize > 18) + debug("SHN::File::read() -- Extra bytes in 'COMM' chunk not parsed."); + + sawCommonChunk = true; + + break; + } + + // Skip all other chunks + default: + offset += chunkSize; + break; + } + } + + if(!sawCommonChunk) { + debug("SHN::File::read() -- Missing 'COMM' chunk"); + setValid(false); + return; + } + } + else { + debug("SHN::File::read() -- Unsupported data format."); + setValid(false); + return; + } + + d->tag = std::make_unique(); + d->properties = std::make_unique(&props, propertiesStyle); +} diff --git a/taglib/shn/shnfile.h b/taglib/shn/shnfile.h new file mode 100644 index 00000000..ee860106 --- /dev/null +++ b/taglib/shn/shnfile.h @@ -0,0 +1,140 @@ +/*************************************************************************** + copyright : (C) 2020-2024 Stephen F. Booth + email : me@sbooth.org + ***************************************************************************/ + +/*************************************************************************** + * This library is free software; you can redistribute it and/or modify * + * it under the terms of the GNU Lesser General Public License version * + * 2.1 as published by the Free Software Foundation. * + * * + * This library 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 this library; if not, write to the Free Software * + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * + * 02110-1301 USA * + * * + * Alternatively, this file is available under the Mozilla Public * + * License Version 1.1. You may obtain a copy of the License at * + * http://www.mozilla.org/MPL/ * + ***************************************************************************/ + +#ifndef TAGLIB_SHNFILE_H +#define TAGLIB_SHNFILE_H + +#include + +#include "taglib_export.h" +#include "tfile.h" + +#include "shnproperties.h" +#include "shntag.h" + +namespace TagLib { + + //! An implementation of Shorten metadata + + /*! + * This is an implementation of Shorten metadata. + */ + + namespace SHN { + + //! An implementation of \c TagLib::File with Shorten specific methods + + /*! + * This implements and provides an interface for Shorten files to the + * \c TagLib::Tag and \c TagLib::AudioProperties interfaces by way of implementing + * the abstract \c TagLib::File API as well as providing some additional + * information specific to Shorten files. + */ + + class TAGLIB_EXPORT File : public TagLib::File { + public: + /*! + * Constructs a Shorten file from \a file. + * + * \note In the current implementation, both \a readProperties and + * \a propertiesStyle are ignored. The audio properties are always + * read. + */ + File(FileName file, bool readProperties = true, + AudioProperties::ReadStyle propertiesStyle = + AudioProperties::Average); + + /*! + * Constructs a Shorten file from \a stream. + * + * \note In the current implementation, both \a readProperties and + * \a propertiesStyle are ignored. The audio properties are always + * read. + * + * \note TagLib will *not* take ownership of the stream, the caller is + * responsible for deleting it after the File object. + */ + File(IOStream *stream, bool readProperties = true, + AudioProperties::ReadStyle propertiesStyle = + AudioProperties::Average); + + /*! + * Destroys this instance of the File. + */ + ~File() override; + + File(const File &) = delete; + File &operator=(const File &) = delete; + + /*! + * Returns the \c Shorten::Tag for this file. + * + * \note While the returned \c Tag instance is non-null Shorten tags are not supported. + */ + Tag *tag() const override; + + /*! + * Implements the unified property interface -- export function. + */ + PropertyMap properties() const override; + + /*! + * Implements the unified property interface -- import function. + */ + PropertyMap setProperties(const PropertyMap &) override; + + /*! + * Returns the \c Shorten::Properties for this file. If no audio properties + * were read then this will return a null pointer. + */ + Properties *audioProperties() const override; + + /*! + * Save the file. + * + * \note Saving Shorten tags is not supported. + */ + bool save() override; + + /*! + * Returns whether or not the given \a stream can be opened as a Shorten + * file. + * + * \note This method is designed to do a quick check. The result may + * not necessarily be correct. + */ + static bool isSupported(IOStream *stream); + + private: + void read(AudioProperties::ReadStyle propertiesStyle); + + class FilePrivate; + TAGLIB_MSVC_SUPPRESS_WARNING_NEEDS_TO_HAVE_DLL_INTERFACE + std::unique_ptr d; + }; + } // namespace SHN +} // namespace TagLib + +#endif diff --git a/taglib/shn/shnproperties.cpp b/taglib/shn/shnproperties.cpp new file mode 100644 index 00000000..6f15edad --- /dev/null +++ b/taglib/shn/shnproperties.cpp @@ -0,0 +1,112 @@ +/*************************************************************************** + copyright : (C) 2020-2024 Stephen F. Booth + email : me@sbooth.org + ***************************************************************************/ + +/*************************************************************************** + * This library is free software; you can redistribute it and/or modify * + * it under the terms of the GNU Lesser General Public License version * + * 2.1 as published by the Free Software Foundation. * + * * + * This library 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 this library; if not, write to the Free Software * + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * + * 02110-1301 USA * + * * + * Alternatively, this file is available under the Mozilla Public * + * License Version 1.1. You may obtain a copy of the License at * + * http://www.mozilla.org/MPL/ * + ***************************************************************************/ + + +#include "shnproperties.h" + +#include "shnutils.h" + +using namespace TagLib; + +class SHN::Properties::PropertiesPrivate +{ +public: + PropertiesPrivate() = default; + ~PropertiesPrivate() = default; + + PropertiesPrivate(const PropertiesPrivate &) = delete; + PropertiesPrivate &operator=(const PropertiesPrivate &) = delete; + + int version { 0 }; + int internalFileType { 0 }; + int channelCount { 0 }; + unsigned long sampleFrames { 0 }; + int sampleRate { 0 }; + int bitsPerSample { 0 }; + + // Computed + int bitrate { 0 }; + int length { 0 }; +}; + +SHN::Properties::Properties(const PropertyValues *values, ReadStyle style) : + AudioProperties(style), + d(std::make_unique()) +{ + if(values) { + d->version = values->version; + d->internalFileType = values->internal_file_type; + d->channelCount = values->channel_count; + d->sampleRate = values->sample_rate; + d->bitsPerSample = values->bits_per_sample; + d->sampleFrames = values->sample_frames; + + d->bitrate = static_cast(d->sampleRate * d->bitsPerSample * d->channelCount / 1000.0 + 0.5); + if(d->sampleRate > 0) + d->length = static_cast(d->sampleFrames * 1000.0 / d->sampleRate + 0.5); + } +} + +SHN::Properties::~Properties() = default; + +int SHN::Properties::lengthInMilliseconds() const +{ + return d->length; +} + +int SHN::Properties::bitrate() const +{ + return d->bitrate; +} + +int SHN::Properties::sampleRate() const +{ + return d->sampleRate; +} + +int SHN::Properties::channels() const +{ + return d->channelCount; +} + +int SHN::Properties::shortenVersion() const +{ + return d->version; +} + +int SHN::Properties::internalFileType() const +{ + return d->internalFileType; +} + +int SHN::Properties::bitsPerSample() const +{ + return d->bitsPerSample; +} + +unsigned long SHN::Properties::sampleFrames() const +{ + return d->sampleFrames; +} diff --git a/taglib/shn/shnproperties.h b/taglib/shn/shnproperties.h new file mode 100644 index 00000000..b523208f --- /dev/null +++ b/taglib/shn/shnproperties.h @@ -0,0 +1,68 @@ +/*************************************************************************** + copyright : (C) 2020-2024 Stephen F. Booth + email : me@sbooth.org + ***************************************************************************/ + +/*************************************************************************** + * This library is free software; you can redistribute it and/or modify * + * it under the terms of the GNU Lesser General Public License version * + * 2.1 as published by the Free Software Foundation. * + * * + * This library 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 this library; if not, write to the Free Software * + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * + * 02110-1301 USA * + * * + * Alternatively, this file is available under the Mozilla Public * + * License Version 1.1. You may obtain a copy of the License at * + * http://www.mozilla.org/MPL/ * + ***************************************************************************/ + +#ifndef TAGLIB_SHNPROPERTIES_H +#define TAGLIB_SHNPROPERTIES_H + +#include + +#include "taglib_export.h" +#include "audioproperties.h" + +namespace TagLib { + namespace SHN { + + struct PropertyValues; + + //! An implementation of audio properties for Shorten + class TAGLIB_EXPORT Properties : public AudioProperties { + public: + Properties(const PropertyValues *values, ReadStyle style = Average); + ~Properties() override; + + Properties(const Properties &) = delete; + Properties &operator=(const Properties &) = delete; + + int lengthInMilliseconds() const override; + int bitrate() const override; + int sampleRate() const override; + int channels() const override; + + //! Returns the Shorten file version (1-3). + int shortenVersion() const; + //! Returns the Shorten internal file type. + int internalFileType() const; + int bitsPerSample() const; + unsigned long sampleFrames() const; + + private: + class PropertiesPrivate; + TAGLIB_MSVC_SUPPRESS_WARNING_NEEDS_TO_HAVE_DLL_INTERFACE + std::unique_ptr d; + }; + } // namespace SHN +} // namespace TagLib + +#endif diff --git a/taglib/shn/shntag.cpp b/taglib/shn/shntag.cpp new file mode 100644 index 00000000..4d599314 --- /dev/null +++ b/taglib/shn/shntag.cpp @@ -0,0 +1,114 @@ +/*************************************************************************** + copyright : (C) 2020-2024 Stephen F. Booth + email : me@sbooth.org + ***************************************************************************/ + +/*************************************************************************** + * This library is free software; you can redistribute it and/or modify * + * it under the terms of the GNU Lesser General Public License version * + * 2.1 as published by the Free Software Foundation. * + * * + * This library 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 this library; if not, write to the Free Software * + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * + * 02110-1301 USA * + * * + * Alternatively, this file is available under the Mozilla Public * + * License Version 1.1. You may obtain a copy of the License at * + * http://www.mozilla.org/MPL/ * + ***************************************************************************/ + +#include "shntag.h" + +#include "tpropertymap.h" + +using namespace TagLib; + +class SHN::Tag::TagPrivate +{ +}; + +SHN::Tag::Tag() : +d(std::make_unique()) +{ +} + +SHN::Tag::~Tag() = default; + +String SHN::Tag::title() const +{ + return String(); +} + +String SHN::Tag::artist() const +{ + return String(); +} + +String SHN::Tag::album() const +{ + return String(); +} + +String SHN::Tag::comment() const +{ + return String(); +} + +String SHN::Tag::genre() const +{ + return String(); +} + +unsigned int SHN::Tag::year() const +{ + return 0; +} + +unsigned int SHN::Tag::track() const +{ + return 0; +} + +void SHN::Tag::setTitle(const String &) +{ +} + +void SHN::Tag::setArtist(const String &) +{ +} + +void SHN::Tag::setAlbum(const String &) +{ +} + +void SHN::Tag::setComment(const String &) +{ +} + +void SHN::Tag::setGenre(const String &) +{ +} + +void SHN::Tag::setYear(unsigned int) +{ +} + +void SHN::Tag::setTrack(unsigned int) +{ +} + +PropertyMap SHN::Tag::properties() const +{ + return PropertyMap{}; +} + +PropertyMap SHN::Tag::setProperties(const PropertyMap &origProps) +{ + return PropertyMap{origProps}; +} diff --git a/taglib/shn/shntag.h b/taglib/shn/shntag.h new file mode 100644 index 00000000..5048c0d0 --- /dev/null +++ b/taglib/shn/shntag.h @@ -0,0 +1,139 @@ +/*************************************************************************** + copyright : (C) 2020-2024 Stephen F. Booth + email : me@sbooth.org + ***************************************************************************/ + +/*************************************************************************** + * This library is free software; you can redistribute it and/or modify * + * it under the terms of the GNU Lesser General Public License version * + * 2.1 as published by the Free Software Foundation. * + * * + * This library 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 this library; if not, write to the Free Software * + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * + * 02110-1301 USA * + * * + * Alternatively, this file is available under the Mozilla Public * + * License Version 1.1. You may obtain a copy of the License at * + * http://www.mozilla.org/MPL/ * + ***************************************************************************/ + +#ifndef TAGLIB_SHNTAG_H +#define TAGLIB_SHNTAG_H + +#include "tag.h" + +namespace TagLib { + namespace SHN { + + //! A Shorten file tag implementation + + /*! + * Tags for Shorten files. + * + * This is a stub class; Shorten files do not support tags. + */ + class TAGLIB_EXPORT Tag : public TagLib::Tag + { + public: + Tag(); + ~Tag() override; + + Tag(const Tag &) = delete; + Tag &operator=(const Tag &) = delete; + + /*! + * Not supported by Shorten files. Therefore always returns an empty string. + */ + String title() const override; + + /*! + * Not supported by Shorten files. Therefore always returns an empty string. + */ + String artist() const override; + + /*! + * Not supported by Shorten files. Therefore always returns an empty string. + */ + String album() const override; + + /*! + * Not supported by Shorten files. Therefore always returns an empty string. + */ + String comment() const override; + + /*! + * Not supported by Shorten files. Therefore always returns an empty string. + */ + String genre() const override; + + /*! + * Not supported by Shorten files. Therefore always returns 0. + */ + unsigned int year() const override; + + /*! + * Not supported by Shorten files. Therefore always returns 0. + */ + unsigned int track() const override; + + /*! + * Not supported by Shorten files and therefore ignored. + */ + void setTitle(const String &title) override; + + /*! + * Not supported by Shorten files and therefore ignored. + */ + void setArtist(const String &artist) override; + + /*! + * Not supported by Shorten files and therefore ignored. + */ + void setAlbum(const String &album) override; + + /*! + * Not supported by Shorten files and therefore ignored. + */ + void setComment(const String &comment) override; + + /*! + * Not supported by Shorten files and therefore ignored. + */ + void setGenre(const String &genre) override; + + /*! + * Not supported by Shorten files and therefore ignored. + */ + void setYear(unsigned int year) override; + + /*! + * Not supported by Shorten files and therefore ignored. + */ + void setTrack(unsigned int track) override; + + /*! + * Implements the unified property interface -- export function. + */ + PropertyMap properties() const override; + + /*! + * Implements the unified property interface -- import function. + */ + PropertyMap setProperties(const PropertyMap &) override; + + private: + class TagPrivate; + TAGLIB_MSVC_SUPPRESS_WARNING_NEEDS_TO_HAVE_DLL_INTERFACE + std::unique_ptr d; + }; + + } // namespace SHN +} // namespace TagLib + +#endif diff --git a/taglib/shn/shnutils.h b/taglib/shn/shnutils.h new file mode 100644 index 00000000..a9ac2df5 --- /dev/null +++ b/taglib/shn/shnutils.h @@ -0,0 +1,45 @@ +/*************************************************************************** + copyright : (C) 2020-2024 Stephen F. Booth + email : me@sbooth.org + ***************************************************************************/ + +/*************************************************************************** + * This library is free software; you can redistribute it and/or modify * + * it under the terms of the GNU Lesser General Public License version * + * 2.1 as published by the Free Software Foundation. * + * * + * This library 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 this library; if not, write to the Free Software * + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * + * 02110-1301 USA * + * * + * Alternatively, this file is available under the Mozilla Public * + * License Version 1.1. You may obtain a copy of the License at * + * http://www.mozilla.org/MPL/ * + ***************************************************************************/ + +#ifndef TAGLIB_SHNUTILS_H +#define TAGLIB_SHNUTILS_H + +namespace TagLib { + namespace SHN { + + /// Values shared with \c SHN::Properties by \c SHN::File + struct PropertyValues + { + int version { 0 }; + int internal_file_type { 0 }; + int channel_count { 0 }; + int sample_rate { 0 }; + int bits_per_sample { 0 }; + unsigned long sample_frames { 0 }; + }; + } // namespace SHN +} // namespace TagLib + +#endif