Add Shorten (SHN) support (#1257)

* Add Shorten (SHN) support

* Add `<cmath>` include and use `std::log2`

* Use `uintptr_t` for buffer size calculations

* Work around `byteSwap` not using fixed width types

* Remove four-character codes

* Attempt to fix `static_assert`

* Revert previous commit

* Update `read_uint`* functions

* Use ByteVector for byte swaps

* Use different ByteVector ctor

* Rework variable-length input to use ByteVector

* Rename some variables

* Naming and formatting cleanup

* Add basic Shorten tests

* Rename a constant

* Rename `internalFileType` to `fileType`

* Add documentation on `fileType` meaning

* Add DO_NOT_DOCUMENT guard

* Fix shadowVariable issues reported by cppcheck

cppcheck --enable=all --inline-suppr \
  --suppress=noExplicitConstructor --suppress=unusedFunction \
  --suppress=missingIncludeSystem --project=compile_commands.json

* Formatting cleanup

* More explicit types

Reason for these changes: getRiceGolombCode(k, uInt32CodeSize) was
called with int k for uint32_t& argument.
There was also a warning from MSVC for line 299:
warning C4267: 'argument': conversion from 'size_t' to 'int'

* Additional explicit types

* Rename `SHN` namespace to `Shorten`

Also rename files to match

---------

Co-authored-by: Urs Fleisch <ufleisch@users.sourceforge.net>
This commit is contained in:
Stephen Booth 2024-12-30 07:23:11 -06:00 committed by GitHub
parent c1c60ebeea
commit 648f5e5882
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
17 changed files with 1261 additions and 2 deletions

View File

@ -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/shorten
)
set(tag_c_HDRS tag_c.h)

View File

@ -55,6 +55,7 @@
#include "opusfile.h"
#include "dsffile.h"
#include "dsdifffile.h"
#include "shortenfile.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_SHORTEN:
file = new Shorten::File(filename);
break;
default:
break;
}

View File

@ -130,7 +130,8 @@ typedef enum {
TagLib_File_XM,
TagLib_File_Opus,
TagLib_File_DSF,
TagLib_File_DSDIFF
TagLib_File_DSDIFF,
TagLib_File_SHORTEN
} TagLib_File_Type;
/*!

View File

@ -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}/shorten
)
set(tag_HDRS
@ -141,6 +142,9 @@ set(tag_HDRS
dsdiff/dsdifffile.h
dsdiff/dsdiffproperties.h
dsdiff/dsdiffdiintag.h
shorten/shortenfile.h
shorten/shortenproperties.h
shorten/shortentag.h
)
set(mpeg_SRCS
@ -308,6 +312,12 @@ set(dsdiff_SRCS
dsdiff/dsdiffdiintag.cpp
)
set(shorten_SRCS
shorten/shortenfile.cpp
shorten/shortenproperties.cpp
shorten/shortentag.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} ${shorten_SRCS}
tag.cpp
tagunion.cpp
fileref.cpp

View File

@ -57,6 +57,7 @@
#include "xmfile.h"
#include "dsffile.h"
#include "dsdifffile.h"
#include "shortenfile.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 Shorten::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(Shorten::File::isSupported(stream))
file = new Shorten::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;
}

View File

@ -0,0 +1,542 @@
/***************************************************************************
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 "shortenfile.h"
#include <cmath>
#include "shortenutils.h"
#include "tdebug.h"
#include "tutils.h"
#include "tagutils.h"
#include "tpropertymap.h"
using namespace TagLib;
namespace {
// MARK: Constants
constexpr int minSupportedVersion = 1;
constexpr int maxSupportedVersion = 3;
// Possible values of k
constexpr int32_t channelCountCodeSize = 0;
constexpr int32_t functionCodeSize = 2;
constexpr int32_t verbatimChunkSizeCodeSize = 5;
constexpr int32_t verbatimByteCodeSize = 8;
constexpr int32_t uInt32CodeSize = 2;
constexpr int32_t skipBytesCodeSize = 1;
constexpr int32_t lpcqCodeSize = 2;
constexpr int32_t extraByteCodeSize = 7;
constexpr int32_t fileTypeCodeSize = 4;
constexpr int32_t functionVerbatim = 9;
constexpr int32_t canonicalHeaderSize = 44;
constexpr int32_t verbatimChunkMaxSize = 256;
constexpr uint32_t maxChannelCount = 8;
constexpr uint32_t defaultBlockSize = 256;
constexpr uint32_t maxBlockSize = 65535;
constexpr int waveFormatPCMTag = 0x0001;
// MARK: Variable-Length Input
//! Variable-length input using Golomb-Rice coding.
class VariableLengthInput {
public:
//! Creates a new \c VariableLengthInput object.
VariableLengthInput(File *file) : file(file) {}
~VariableLengthInput() = default;
VariableLengthInput() = delete;
VariableLengthInput(const VariableLengthInput &) = delete;
VariableLengthInput(VariableLengthInput &&) = delete;
VariableLengthInput &operator=(const VariableLengthInput &) = delete;
VariableLengthInput &operator=(VariableLengthInput &&) = delete;
bool getRiceGolombCode(int32_t &i32, int k);
bool getUInt(uint32_t &ui32, int version, int32_t k);
private:
//! Refills \c bitBuffer with a single \c uint32_t from \c buffer,
//! refilling \c buffer if necessary.
bool refillBitBuffer();
//! Input stream
File *file { nullptr };
//! Byte buffer
ByteVector buffer;
//! Current position in buffer
unsigned int bufferPosition { 0 };
//! Bit buffer
uint32_t bitBuffer { 0 };
//! Bits available in \c bitBuffer, 0..32
int bitsAvailable { 0 };
};
bool VariableLengthInput::getRiceGolombCode(int32_t &i32, int32_t k)
{
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
};
if(bitsAvailable == 0 && !refillBitBuffer())
return false;
int32_t result;
for(result = 0; !(bitBuffer & (1L << --bitsAvailable)); ++result) {
if(bitsAvailable == 0 && !refillBitBuffer())
return false;
}
while(k != 0) {
if(bitsAvailable >= k) {
result = (result << k) | static_cast<int32_t>(
(bitBuffer >> (bitsAvailable - k)) & sMaskTable[k]);
bitsAvailable -= k;
k = 0;
}
else {
result = (result << bitsAvailable) | static_cast<int32_t>(
bitBuffer & sMaskTable[bitsAvailable]);
k -= bitsAvailable;
if(!refillBitBuffer())
return false;
}
}
i32 = result;
return true;
}
bool VariableLengthInput::getUInt(uint32_t &ui32, int version, int32_t k)
{
if(version > 0 && !getRiceGolombCode(k, uInt32CodeSize))
return false;
int32_t i32;
if(!getRiceGolombCode(i32, k))
return false;
ui32 = static_cast<uint32_t>(i32);
return true;
}
bool VariableLengthInput::refillBitBuffer()
{
if(buffer.size() - bufferPosition < 4) {
static constexpr size_t bufferSize = 512;
auto block = file->readBlock(bufferSize);
if(block.size() < 4)
return false;
buffer = block;
bufferPosition = 0;
}
bitBuffer = buffer.toUInt(bufferPosition, true);
bufferPosition += 4;
bitsAvailable = 32;
return true;
}
} // namespace
class Shorten::File::FilePrivate
{
public:
FilePrivate() = default;
~FilePrivate() = default;
FilePrivate(const FilePrivate &) = delete;
FilePrivate &operator=(const FilePrivate &) = delete;
std::unique_ptr<Properties> properties;
std::unique_ptr<Tag> tag;
};
bool Shorten::File::isSupported(IOStream *stream)
{
// A Shorten file has to start with "ajkg"
const ByteVector id = Utils::readHeader(stream, 4, false);
return id.startsWith("ajkg");
}
Shorten::File::File(FileName file, bool readProperties,
AudioProperties::ReadStyle propertiesStyle) :
TagLib::File(file),
d(std::make_unique<FilePrivate>())
{
if(isOpen())
read(propertiesStyle);
}
Shorten::File::File(IOStream *stream, bool readProperties,
AudioProperties::ReadStyle propertiesStyle) :
TagLib::File(stream),
d(std::make_unique<FilePrivate>())
{
if(isOpen())
read(propertiesStyle);
}
Shorten::File::~File() = default;
Shorten::Tag *Shorten::File::tag() const
{
return d->tag.get();
}
PropertyMap Shorten::File::properties() const
{
return d->tag->properties();
}
PropertyMap Shorten::File::setProperties(const PropertyMap &properties)
{
return d->tag->setProperties(properties);
}
Shorten::Properties *Shorten::File::audioProperties() const
{
return d->properties.get();
}
bool Shorten::File::save()
{
if(readOnly()) {
debug("Shorten::File::save() - Cannot save to a read only file.");
return false;
}
debug("Shorten::File::save() - Saving not supported.");
return false;
}
void Shorten::File::read(AudioProperties::ReadStyle propertiesStyle)
{
if(!isOpen())
return;
// Read magic number
auto magic = readBlock(4);
if(magic != "ajkg") {
debug("Shorten::File::read() -- Not a Shorten file.");
setValid(false);
return;
}
PropertyValues props{};
// Read file version
int version = readBlock(1).toUInt();
if(version < minSupportedVersion || version > maxSupportedVersion) {
debug("Shorten::File::read() -- Unsupported version.");
setValid(false);
return;
}
props.version = version;
// Set up variable length input
VariableLengthInput input(this);
// Read file type
uint32_t fileType;
if(!input.getUInt(fileType, version, fileTypeCodeSize)) {
debug("Shorten::File::read() -- Unable to read file type.");
setValid(false);
return;
}
props.fileType = static_cast<int>(fileType);
// Read number of channels
uint32_t channelCount = 0;
if(!input.getUInt(channelCount, version, channelCountCodeSize) ||
channelCount == 0 || channelCount > maxChannelCount) {
debug("Shorten::File::read() -- Invalid or unsupported channel count.");
setValid(false);
return;
}
props.channelCount = static_cast<int>(channelCount);
// Read block size if version > 0
if(version > 0) {
uint32_t blockSize = 0;
if(!input.getUInt(blockSize, version,
static_cast<int32_t>(std::log2(defaultBlockSize))) ||
blockSize == 0 || blockSize > maxBlockSize) {
debug("Shorten::File::read() -- Invalid or unsupported block size.");
setValid(false);
return;
}
uint32_t maxnlpc = 0;
if(!input.getUInt(maxnlpc, version, lpcqCodeSize) /*|| maxnlpc > 1024*/) {
debug("Shorten::File::read() -- Invalid maximum nlpc.");
setValid(false);
return;
}
uint32_t nmean = 0;
if(!input.getUInt(nmean, version, 0) /*|| nmean > 32768*/) {
debug("Shorten::File::read() -- Invalid nmean.");
setValid(false);
return;
}
uint32_t skipCount;
if(!input.getUInt(skipCount, version, skipBytesCodeSize)) {
setValid(false);
return;
}
for(uint32_t i = 0; i < skipCount; ++i) {
uint32_t dummy;
if(!input.getUInt(dummy, version, extraByteCodeSize)) {
setValid(false);
return;
}
}
}
// Parse the WAVE or AIFF header in the verbatim section
int32_t function;
if(!input.getRiceGolombCode(function, functionCodeSize) ||
function != functionVerbatim) {
debug("Shorten::File::read() -- Missing initial verbatim section.");
setValid(false);
return;
}
int32_t header_size;
if(!input.getRiceGolombCode(header_size, verbatimChunkSizeCodeSize) ||
header_size < canonicalHeaderSize || header_size > verbatimChunkMaxSize) {
debug("Shorten::File::read() -- Incorrect header size.");
setValid(false);
return;
}
ByteVector header(header_size, 0);
auto it = header.begin();
for(int32_t i = 0; i < header_size; ++i) {
int32_t byte;
if(!input.getRiceGolombCode(byte, verbatimByteCodeSize)) {
debug("Shorten::File::read() -- Unable to read header.");
setValid(false);
return;
}
*it++ = static_cast<uint8_t>(byte);
}
// header is at least canonicalHeaderSize (44) bytes in size
auto chunkID = header.toUInt(0, true);
// auto chunkSize = header.toUInt(4, true);
const auto chunkData = ByteVector(header, 8, header.size() - 8);
// WAVE
if(chunkID == 0x52494646 /*'RIFF'*/) {
unsigned int offset = 0;
chunkID = chunkData.toUInt(offset, true);
offset += 4;
if(chunkID != 0x57415645 /*'WAVE'*/) {
debug("Shorten::File::read() -- Missing 'WAVE' in 'RIFF' chunk.");
setValid(false);
return;
}
auto sawFormatChunk = false;
uint32_t dataChunkSize = 0;
uint16_t blockAlign = 0;
while(offset < chunkData.size()) {
chunkID = chunkData.toUInt(offset, true);
offset += 4;
auto chunkSize = chunkData.toUInt(offset, false);
offset += 4;
switch(chunkID) {
case 0x666d7420 /*'fmt '*/:
{
if(chunkSize < 16) {
debug("Shorten::File::read() -- 'fmt ' chunk is too small.");
setValid(false);
return;
}
int formatTag = chunkData.toUShort(offset, false);
offset += 2;
if(formatTag != waveFormatPCMTag) {
debug("Shorten::File::read() -- Unsupported WAVE format tag.");
setValid(false);
return;
}
int fmtChannelCount = chunkData.toUShort(offset, false);
offset += 2;
if(props.channelCount != fmtChannelCount)
debug("Shorten::File::read() -- Channel count mismatch between Shorten and 'fmt ' chunk.");
props.sampleRate = static_cast<int>(chunkData.toUInt(offset, false));
offset += 4;
// Skip average bytes per second
offset += 4;
blockAlign = chunkData.toUShort(offset, false);
offset += 2;
props.bitsPerSample = static_cast<int>(chunkData.toUShort(offset, false));
offset += 2;
sawFormatChunk = true;
break;
}
case 0x64617461 /*'data'*/:
dataChunkSize = chunkSize;
break;
}
}
if(!sawFormatChunk) {
debug("Shorten::File::read() -- Missing 'fmt ' chunk.");
setValid(false);
return;
}
if(dataChunkSize && blockAlign)
props.sampleFrames = static_cast<unsigned long>(dataChunkSize / blockAlign);
}
// AIFF
else if(chunkID == 0x464f524d /*'FORM'*/) {
unsigned int offset = 0;
chunkID = chunkData.toUInt(offset, true);
offset += 4;
if(chunkID != 0x41494646 /*'AIFF'*/ && chunkID != 0x41494643 /*'AIFC'*/) {
debug("Shorten::File::read() -- Missing 'AIFF' or 'AIFC' in 'FORM' chunk.");
setValid(false);
return;
}
// if(chunkID == 0x41494643 /*'AIFC'*/)
// props.big_endian = true;
auto sawCommonChunk = false;
while(offset < chunkData.size()) {
chunkID = chunkData.toUInt(offset, true);
offset += 4;
auto chunkSize = chunkData.toUInt(offset, true);
offset += 4;
// All chunks must have an even length but the pad byte is not included in chunkSize
chunkSize += (chunkSize & 1);
switch(chunkID) {
case 0x434f4d4d /*'COMM'*/:
{
if(chunkSize < 18) {
debug("Shorten::File::read() -- 'COMM' chunk is too small.");
setValid(false);
return;
}
int commChannelCount = chunkData.toUShort(offset, true);
offset += 2;
if(props.channelCount != commChannelCount)
debug("Shorten::File::read() -- Channel count mismatch between Shorten and 'COMM' chunk.");
props.sampleFrames = static_cast<unsigned long>(chunkData.toUInt(offset, true));
offset += 4;
props.bitsPerSample = static_cast<int>(chunkData.toUShort(offset, true));
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<int16_t>(chunkData.toUShort(offset, true)) - 16383 - 63;
offset += 2;
if(exp < -63 || exp > 63) {
debug("Shorten::File::read() -- exp out of range.");
setValid(false);
return;
}
auto frac = chunkData.toULongLong(offset, true);
offset += 8;
if(exp >= 0)
props.sampleRate = static_cast<int>(frac << exp);
else
props.sampleRate = static_cast<int>(
(frac + (static_cast<uint64_t>(1) << (-exp - 1))) >> -exp);
sawCommonChunk = true;
break;
}
// Skip all other chunks
default:
offset += chunkSize;
break;
}
}
if(!sawCommonChunk) {
debug("Shorten::File::read() -- Missing 'COMM' chunk");
setValid(false);
return;
}
}
else {
debug("Shorten::File::read() -- Unsupported data format.");
setValid(false);
return;
}
d->tag = std::make_unique<Tag>();
d->properties = std::make_unique<Properties>(&props, propertiesStyle);
}

View File

@ -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_SHORTENFILE_H
#define TAGLIB_SHORTENFILE_H
#include <memory>
#include "taglib_export.h"
#include "tfile.h"
#include "shortenproperties.h"
#include "shortentag.h"
namespace TagLib {
//! An implementation of Shorten metadata
/*!
* This is an implementation of Shorten metadata.
*/
namespace Shorten {
//! 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<FilePrivate> d;
};
} // namespace Shorten
} // namespace TagLib
#endif

View File

@ -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 "shortenproperties.h"
#include "shortenutils.h"
using namespace TagLib;
class Shorten::Properties::PropertiesPrivate
{
public:
PropertiesPrivate() = default;
~PropertiesPrivate() = default;
PropertiesPrivate(const PropertiesPrivate &) = delete;
PropertiesPrivate &operator=(const PropertiesPrivate &) = delete;
int version { 0 };
int fileType { 0 };
int channelCount { 0 };
int sampleRate { 0 };
int bitsPerSample { 0 };
unsigned long sampleFrames { 0 };
// Computed
int bitrate { 0 };
int length { 0 };
};
Shorten::Properties::Properties(const PropertyValues *values, ReadStyle style) :
AudioProperties(style),
d(std::make_unique<PropertiesPrivate>())
{
if(values) {
d->version = values->version;
d->fileType = values->fileType;
d->channelCount = values->channelCount;
d->sampleRate = values->sampleRate;
d->bitsPerSample = values->bitsPerSample;
d->sampleFrames = values->sampleFrames;
d->bitrate = static_cast<int>(d->sampleRate * d->bitsPerSample * d->channelCount / 1000.0 + 0.5);
if(d->sampleRate > 0)
d->length = static_cast<int>(d->sampleFrames * 1000.0 / d->sampleRate + 0.5);
}
}
Shorten::Properties::~Properties() = default;
int Shorten::Properties::lengthInMilliseconds() const
{
return d->length;
}
int Shorten::Properties::bitrate() const
{
return d->bitrate;
}
int Shorten::Properties::sampleRate() const
{
return d->sampleRate;
}
int Shorten::Properties::channels() const
{
return d->channelCount;
}
int Shorten::Properties::shortenVersion() const
{
return d->version;
}
int Shorten::Properties::fileType() const
{
return d->fileType;
}
int Shorten::Properties::bitsPerSample() const
{
return d->bitsPerSample;
}
unsigned long Shorten::Properties::sampleFrames() const
{
return d->sampleFrames;
}

View File

@ -0,0 +1,74 @@
/***************************************************************************
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_SHORTENPROPERTIES_H
#define TAGLIB_SHORTENPROPERTIES_H
#include <memory>
#include "taglib_export.h"
#include "audioproperties.h"
namespace TagLib {
namespace Shorten {
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 file type (0-9).
//! 0 = 8-bit µ-law,
//! 1 = signed 8-bit PCM, 2 = unsigned 8-bit PCM,
//! 3 = signed big-endian 16-bit PCM, 4 = unsigned big-endian 16-bit PCM,
//! 5 = signed little-endian 16-bit PCM, 6 = unsigned little-endian 16-bit PCM,
//! 7 = 8-bit ITU-T G.711 µ-law, 8 = 8-bit µ-law,
//! 9 = 8-bit A-law, 10 = 8-bit ITU-T G.711 A-law
int fileType() const;
int bitsPerSample() const;
unsigned long sampleFrames() const;
private:
class PropertiesPrivate;
TAGLIB_MSVC_SUPPRESS_WARNING_NEEDS_TO_HAVE_DLL_INTERFACE
std::unique_ptr<PropertiesPrivate> d;
};
} // namespace Shorten
} // namespace TagLib
#endif

View File

@ -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 "shortentag.h"
#include "tpropertymap.h"
using namespace TagLib;
class Shorten::Tag::TagPrivate
{
};
Shorten::Tag::Tag() :
d(std::make_unique<TagPrivate>())
{
}
Shorten::Tag::~Tag() = default;
String Shorten::Tag::title() const
{
return String();
}
String Shorten::Tag::artist() const
{
return String();
}
String Shorten::Tag::album() const
{
return String();
}
String Shorten::Tag::comment() const
{
return String();
}
String Shorten::Tag::genre() const
{
return String();
}
unsigned int Shorten::Tag::year() const
{
return 0;
}
unsigned int Shorten::Tag::track() const
{
return 0;
}
void Shorten::Tag::setTitle(const String &)
{
}
void Shorten::Tag::setArtist(const String &)
{
}
void Shorten::Tag::setAlbum(const String &)
{
}
void Shorten::Tag::setComment(const String &)
{
}
void Shorten::Tag::setGenre(const String &)
{
}
void Shorten::Tag::setYear(unsigned int)
{
}
void Shorten::Tag::setTrack(unsigned int)
{
}
PropertyMap Shorten::Tag::properties() const
{
return PropertyMap{};
}
PropertyMap Shorten::Tag::setProperties(const PropertyMap &origProps)
{
return PropertyMap{origProps};
}

139
taglib/shorten/shortentag.h Normal file
View File

@ -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_SHORTENTAG_H
#define TAGLIB_SHORTENTAG_H
#include "tag.h"
namespace TagLib {
namespace Shorten {
//! 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<TagPrivate> d;
};
} // namespace Shorten
} // namespace TagLib
#endif

View File

@ -0,0 +1,51 @@
/***************************************************************************
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_SHORTENUTILS_H
#define TAGLIB_SHORTENUTILS_H
// THIS FILE IS NOT A PART OF THE TAGLIB API
#ifndef DO_NOT_DOCUMENT // tell Doxygen not to document this header
namespace TagLib {
namespace Shorten {
/// Values shared with \c Shorten::Properties by \c Shorten::File
struct PropertyValues
{
int version { 0 };
int fileType { 0 };
int channelCount { 0 };
int sampleRate { 0 };
int bitsPerSample { 0 };
unsigned long sampleFrames { 0 };
};
} // namespace Shorten
} // namespace TagLib
#endif
#endif

View File

@ -27,6 +27,7 @@ INCLUDE_DIRECTORIES(
${CMAKE_CURRENT_SOURCE_DIR}/../taglib/xm
${CMAKE_CURRENT_SOURCE_DIR}/../taglib/dsf
${CMAKE_CURRENT_SOURCE_DIR}/../taglib/dsdiff
${CMAKE_CURRENT_SOURCE_DIR}/../taglib/shorten
)
SET(test_runner_SRCS
@ -74,6 +75,7 @@ SET(test_runner_SRCS
test_speex.cpp
test_dsf.cpp
test_dsdiff.cpp
test_shorten.cpp
test_sizes.cpp
test_versionnumber.cpp
)

File diff suppressed because one or more lines are too long

View File

@ -385,6 +385,7 @@ public:
CPPUNIT_ASSERT(extensions.contains("dsf"));
CPPUNIT_ASSERT(extensions.contains("dff"));
CPPUNIT_ASSERT(extensions.contains("dsdiff"));
CPPUNIT_ASSERT(extensions.contains("shn"));
}
void testFileResolver()

44
tests/test_shorten.cpp Normal file
View File

@ -0,0 +1,44 @@
#include <string>
#include <cstdio>
#include "tbytevectorlist.h"
#include "tpropertymap.h"
#include "tag.h"
#include "shortenfile.h"
#include "plainfile.h"
#include <cppunit/extensions/HelperMacros.h>
#include "utils.h"
using namespace std;
using namespace TagLib;
class TestShorten : public CppUnit::TestFixture
{
CPPUNIT_TEST_SUITE(TestShorten);
CPPUNIT_TEST(testBasic);
CPPUNIT_TEST(testTags);
CPPUNIT_TEST_SUITE_END();
public:
void testBasic()
{
Shorten::File f(TEST_FILE_PATH_C("2sec-silence.shn"));
CPPUNIT_ASSERT(f.audioProperties());
CPPUNIT_ASSERT_EQUAL(2, f.audioProperties()->lengthInSeconds());
CPPUNIT_ASSERT_EQUAL(2000, f.audioProperties()->lengthInMilliseconds());
CPPUNIT_ASSERT_EQUAL(1411, f.audioProperties()->bitrate());
CPPUNIT_ASSERT_EQUAL(2, f.audioProperties()->channels());
CPPUNIT_ASSERT_EQUAL(44100, f.audioProperties()->sampleRate());
CPPUNIT_ASSERT_EQUAL(2, f.audioProperties()->shortenVersion());
CPPUNIT_ASSERT_EQUAL(5, f.audioProperties()->fileType());
CPPUNIT_ASSERT_EQUAL(16, f.audioProperties()->bitsPerSample());
CPPUNIT_ASSERT_EQUAL(static_cast<unsigned long>(88200), f.audioProperties()->sampleFrames());
}
void testTags()
{
}
};
CPPUNIT_TEST_SUITE_REGISTRATION(TestShorten);

View File

@ -92,6 +92,9 @@
#include "rifffile.h"
#include "s3mfile.h"
#include "s3mproperties.h"
#include "shortenfile.h"
#include "shortenproperties.h"
#include "shortentag.h"
#include "speexfile.h"
#include "speexproperties.h"
#include "synchronizedlyricsframe.h"
@ -245,6 +248,9 @@ public:
CPPUNIT_ASSERT_EQUAL(classSize(1, true), sizeof(TagLib::RIFF::WAV::Properties));
CPPUNIT_ASSERT_EQUAL(classSize(2, true), sizeof(TagLib::S3M::File));
CPPUNIT_ASSERT_EQUAL(classSize(1, true), sizeof(TagLib::S3M::Properties));
CPPUNIT_ASSERT_EQUAL(classSize(1, true), sizeof(TagLib::Shorten::File));
CPPUNIT_ASSERT_EQUAL(classSize(1, true), sizeof(TagLib::Shorten::Properties));
CPPUNIT_ASSERT_EQUAL(classSize(1, true), sizeof(TagLib::Shorten::Tag));
CPPUNIT_ASSERT_EQUAL(classSize(1, false), sizeof(TagLib::String));
CPPUNIT_ASSERT_EQUAL(classSize(2, false), sizeof(TagLib::StringList));
CPPUNIT_ASSERT_EQUAL(classSize(0, true), sizeof(TagLib::Tag));