Merge remote-tracking branch 'upstream/master' into qt5-update

This commit is contained in:
Jonas Kvinge 2018-11-11 22:46:07 +01:00
commit d4fab4a2ea
131 changed files with 22569 additions and 20159 deletions

View File

@ -47,6 +47,8 @@ include_directories(
${CMAKE_CURRENT_SOURCE_DIR}/s3m
${CMAKE_CURRENT_SOURCE_DIR}/it
${CMAKE_CURRENT_SOURCE_DIR}/xm
${CMAKE_CURRENT_SOURCE_DIR}/dsf
${CMAKE_CURRENT_SOURCE_DIR}/dsdiff
${CMAKE_SOURCE_DIR}/3rdparty
)
@ -161,6 +163,11 @@ set(tag_HDRS
s3m/s3mproperties.h
xm/xmfile.h
xm/xmproperties.h
dsf/dsffile.h
dsf/dsfproperties.h
dsdiff/dsdifffile.h
dsdiff/dsdiffproperties.h
dsdiff/dsdiffdiintag.h
)
set(mpeg_SRCS
@ -316,6 +323,17 @@ set(xm_SRCS
xm/xmproperties.cpp
)
set(dsf_SRCS
dsf/dsffile.cpp
dsf/dsfproperties.cpp
)
set(dsdiff_SRCS
dsdiff/dsdifffile.cpp
dsdiff/dsdiffproperties.cpp
dsdiff/dsdiffdiintag.cpp
)
set(toolkit_SRCS
toolkit/tstring.cpp
toolkit/tstringlist.cpp
@ -347,7 +365,7 @@ set(tag_LIB_SRCS
${mpeg_SRCS} ${id3v1_SRCS} ${id3v2_SRCS} ${frames_SRCS} ${ogg_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}
${asf_SRCS} ${mp4_SRCS} ${mod_SRCS} ${s3m_SRCS} ${it_SRCS} ${xm_SRCS} ${opus_SRCS} ${dsf_SRCS} ${dsdiff_SRCS}
${zlib_SRCS}
tag.cpp
tagunion.cpp

View File

@ -38,6 +38,8 @@
#include "vorbisproperties.h"
#include "wavproperties.h"
#include "wavpackproperties.h"
#include "dsfproperties.h"
#include "dsdiffproperties.h"
#include "audioproperties.h"
@ -73,6 +75,10 @@ using namespace TagLib;
return dynamic_cast<const Vorbis::Properties*>(this)->function_name(); \
else if(dynamic_cast<const WavPack::Properties*>(this)) \
return dynamic_cast<const WavPack::Properties*>(this)->function_name(); \
else if(dynamic_cast<const DSF::Properties*>(this)) \
return dynamic_cast<const DSF::Properties*>(this)->function_name(); \
else if(dynamic_cast<const DSDIFF::Properties*>(this)) \
return dynamic_cast<const DSDIFF::Properties*>(this)->function_name(); \
else \
return (default_value);

162
3rdparty/taglib/dsdiff/dsdiffdiintag.cpp vendored Normal file
View File

@ -0,0 +1,162 @@
/***************************************************************************
copyright : (C) 2016 by Damien Plisson, Audirvana
email : damien78@audirvana.com
***************************************************************************/
/***************************************************************************
* 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 "dsdiffdiintag.h"
#include "tstringlist.h"
#include "tpropertymap.h"
using namespace TagLib;
using namespace DSDIFF::DIIN;
class DSDIFF::DIIN::Tag::TagPrivate
{
public:
TagPrivate()
{
}
String title;
String artist;
};
DSDIFF::DIIN::Tag::Tag() : TagLib::Tag()
{
d = new TagPrivate;
}
DSDIFF::DIIN::Tag::~Tag()
{
delete d;
}
String DSDIFF::DIIN::Tag::title() const
{
return d->title;
}
String DSDIFF::DIIN::Tag::artist() const
{
return d->artist;
}
String DSDIFF::DIIN::Tag::album() const
{
return String();
}
String DSDIFF::DIIN::Tag::comment() const
{
return String();
}
String DSDIFF::DIIN::Tag::genre() const
{
return String();
}
unsigned int DSDIFF::DIIN::Tag::year() const
{
return 0;
}
unsigned int DSDIFF::DIIN::Tag::track() const
{
return 0;
}
void DSDIFF::DIIN::Tag::setTitle(const String &title)
{
if(title.isNull() || title.isEmpty())
d->title = String();
else
d->title = title;
}
void DSDIFF::DIIN::Tag::setArtist(const String &artist)
{
if(artist.isNull() || artist.isEmpty())
d->artist = String();
else
d->artist = artist;
}
void DSDIFF::DIIN::Tag::setAlbum(const String &)
{
}
void DSDIFF::DIIN::Tag::setComment(const String &)
{
}
void DSDIFF::DIIN::Tag::setGenre(const String &)
{
}
void DSDIFF::DIIN::Tag::setYear(unsigned int)
{
}
void DSDIFF::DIIN::Tag::setTrack(unsigned int)
{
}
PropertyMap DSDIFF::DIIN::Tag::properties() const
{
PropertyMap properties;
properties["TITLE"] = d->title;
properties["ARTIST"] = d->artist;
return properties;
}
PropertyMap DSDIFF::DIIN::Tag::setProperties(const PropertyMap &origProps)
{
PropertyMap properties(origProps);
properties.removeEmpty();
StringList oneValueSet;
if(properties.contains("TITLE")) {
d->title = properties["TITLE"].front();
oneValueSet.append("TITLE");
} else
d->title = String();
if(properties.contains("ARTIST")) {
d->artist = properties["ARTIST"].front();
oneValueSet.append("ARTIST");
} else
d->artist = String();
// for each tag that has been set above, remove the first entry in the corresponding
// value list. The others will be returned as unsupported by this format.
for(StringList::Iterator it = oneValueSet.begin(); it != oneValueSet.end(); ++it) {
if(properties[*it].size() == 1)
properties.erase(*it);
else
properties[*it].erase(properties[*it].begin());
}
return properties;
}

150
3rdparty/taglib/dsdiff/dsdiffdiintag.h vendored Normal file
View File

@ -0,0 +1,150 @@
/***************************************************************************
copyright : (C) 2016 by Damien Plisson, Audirvana
email : damien78@audirvana.com
***************************************************************************/
/***************************************************************************
* 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_DSDIFFDIINTAG_H
#define TAGLIB_DSDIFFDIINTAG_H
#include "tag.h"
namespace TagLib {
namespace DSDIFF {
namespace DIIN {
/*!
* Tags from the Edited Master Chunk Info
*
* Only Title and Artist tags are supported
*/
class TAGLIB_EXPORT Tag : public TagLib::Tag
{
public:
Tag();
virtual ~Tag();
/*!
* Returns the track name; if no track name is present in the tag
* String() will be returned.
*/
String title() const;
/*!
* Returns the artist name; if no artist name is present in the tag
* String() will be returned.
*/
String artist() const;
/*!
* Not supported. Therefore always returns String().
*/
String album() const;
/*!
* Not supported. Therefore always returns String().
*/
String comment() const;
/*!
* Not supported. Therefore always returns String().
*/
String genre() const;
/*!
* Not supported. Therefore always returns 0.
*/
unsigned int year() const;
/*!
* Not supported. Therefore always returns 0.
*/
unsigned int track() const;
/*!
* Sets the title to \a title. If \a title is String() then this
* value will be cleared.
*/
void setTitle(const String &title);
/*!
* Sets the artist to \a artist. If \a artist is String() then this
* value will be cleared.
*/
void setArtist(const String &artist);
/*!
* Not supported and therefore ignored.
*/
void setAlbum(const String &album);
/*!
* Not supported and therefore ignored.
*/
void setComment(const String &comment);
/*!
* Not supported and therefore ignored.
*/
void setGenre(const String &genre);
/*!
* Not supported and therefore ignored.
*/
void setYear(unsigned int year);
/*!
* Not supported and therefore ignored.
*/
void setTrack(unsigned int track);
/*!
* Implements the unified property interface -- export function.
* Since the DIIN tag is very limited, the exported map is as well.
*/
PropertyMap properties() const;
/*!
* Implements the unified property interface -- import function.
* Because of the limitations of the DIIN file tag, any tags besides
* TITLE and ARTIST, will be
* returned. Additionally, if the map contains tags with multiple values,
* all but the first will be contained in the returned map of unsupported
* properties.
*/
PropertyMap setProperties(const PropertyMap &);
private:
Tag(const Tag &);
Tag &operator=(const Tag &);
class TagPrivate;
TagPrivate *d;
};
}
}
}
#endif

812
3rdparty/taglib/dsdiff/dsdifffile.cpp vendored Normal file
View File

@ -0,0 +1,812 @@
/***************************************************************************
copyright : (C) 2016 by Damien Plisson, Audirvana
email : damien78@audirvana.com
***************************************************************************/
/***************************************************************************
* 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 <tbytevector.h>
#include <tdebug.h>
#include <id3v2tag.h>
#include <tstringlist.h>
#include <tpropertymap.h>
#include <tagutils.h>
#include "tagunion.h"
#include "dsdifffile.h"
using namespace TagLib;
struct Chunk64
{
ByteVector name;
unsigned long long offset;
unsigned long long size;
char padding;
};
namespace
{
enum {
ID3v2Index = 0,
DIINIndex = 1
};
enum {
PROPChunk = 0,
DIINChunk = 1
};
}
class DSDIFF::File::FilePrivate
{
public:
FilePrivate() :
endianness(BigEndian),
size(0),
isID3InPropChunk(false),
duplicateID3V2chunkIndex(-1),
properties(0),
id3v2TagChunkID("ID3 "),
hasID3v2(false),
hasDiin(false)
{
childChunkIndex[ID3v2Index] = -1;
childChunkIndex[DIINIndex] = -1;
}
~FilePrivate()
{
delete properties;
}
Endianness endianness;
ByteVector type;
unsigned long long size;
ByteVector format;
std::vector<Chunk64> chunks;
std::vector<Chunk64> childChunks[2];
int childChunkIndex[2];
bool isID3InPropChunk; // Two possibilities can be found: ID3V2 chunk inside PROP chunk or at root level
int duplicateID3V2chunkIndex; // 2 ID3 chunks are present. This is then the index of the one in
// PROP chunk that will be removed upon next save to remove duplicates.
Properties *properties;
TagUnion tag;
ByteVector id3v2TagChunkID;
bool hasID3v2;
bool hasDiin;
};
////////////////////////////////////////////////////////////////////////////////
// static members
////////////////////////////////////////////////////////////////////////////////
bool DSDIFF::File::isSupported(IOStream *stream)
{
// A DSDIFF file has to start with "FRM8????????DSD ".
const ByteVector id = Utils::readHeader(stream, 16, false);
return (id.startsWith("FRM8") && id.containsAt("DSD ", 12));
}
////////////////////////////////////////////////////////////////////////////////
// public members
////////////////////////////////////////////////////////////////////////////////
DSDIFF::File::File(FileName file, bool readProperties,
Properties::ReadStyle propertiesStyle) : TagLib::File(file)
{
d = new FilePrivate;
d->endianness = BigEndian;
if(isOpen())
read(readProperties, propertiesStyle);
}
DSDIFF::File::File(IOStream *stream, bool readProperties,
Properties::ReadStyle propertiesStyle) : TagLib::File(stream)
{
d = new FilePrivate;
d->endianness = BigEndian;
if(isOpen())
read(readProperties, propertiesStyle);
}
DSDIFF::File::~File()
{
delete d;
}
TagLib::Tag *DSDIFF::File::tag() const
{
return &d->tag;
}
ID3v2::Tag *DSDIFF::File::ID3v2Tag() const
{
return d->tag.access<ID3v2::Tag>(ID3v2Index, false);
}
bool DSDIFF::File::hasID3v2Tag() const
{
return d->hasID3v2;
}
DSDIFF::DIIN::Tag *DSDIFF::File::DIINTag() const
{
return d->tag.access<DSDIFF::DIIN::Tag>(DIINIndex, false);
}
bool DSDIFF::File::hasDIINTag() const
{
return d->hasDiin;
}
PropertyMap DSDIFF::File::properties() const
{
if(d->hasID3v2)
return d->tag.access<ID3v2::Tag>(ID3v2Index, false)->properties();
return PropertyMap();
}
void DSDIFF::File::removeUnsupportedProperties(const StringList &unsupported)
{
if(d->hasID3v2)
d->tag.access<ID3v2::Tag>(ID3v2Index, false)->removeUnsupportedProperties(unsupported);
if(d->hasDiin)
d->tag.access<DSDIFF::DIIN::Tag>(DIINIndex, false)->removeUnsupportedProperties(unsupported);
}
PropertyMap DSDIFF::File::setProperties(const PropertyMap &properties)
{
return d->tag.access<ID3v2::Tag>(ID3v2Index, true)->setProperties(properties);
}
DSDIFF::Properties *DSDIFF::File::audioProperties() const
{
return d->properties;
}
bool DSDIFF::File::save()
{
if(readOnly()) {
debug("DSDIFF::File::save() -- File is read only.");
return false;
}
if(!isValid()) {
debug("DSDIFF::File::save() -- Trying to save invalid file.");
return false;
}
// First: save ID3V2 chunk
ID3v2::Tag *id3v2Tag = d->tag.access<ID3v2::Tag>(ID3v2Index, false);
if(d->isID3InPropChunk) {
if(id3v2Tag != NULL && !id3v2Tag->isEmpty()) {
setChildChunkData(d->id3v2TagChunkID, id3v2Tag->render(), PROPChunk);
d->hasID3v2 = true;
}
else {
// Empty tag: remove it
setChildChunkData(d->id3v2TagChunkID, ByteVector(), PROPChunk);
d->hasID3v2 = false;
}
}
else {
if(id3v2Tag != NULL && !id3v2Tag->isEmpty()) {
setRootChunkData(d->id3v2TagChunkID, id3v2Tag->render());
d->hasID3v2 = true;
}
else {
// Empty tag: remove it
setRootChunkData(d->id3v2TagChunkID, ByteVector());
d->hasID3v2 = false;
}
}
// Second: save the DIIN chunk
if(d->hasDiin) {
DSDIFF::DIIN::Tag *diinTag = d->tag.access<DSDIFF::DIIN::Tag>(DIINIndex, false);
if(!diinTag->title().isNull() && !diinTag->title().isEmpty()) {
ByteVector diinTitle;
diinTitle.append(ByteVector::fromUInt(diinTag->title().size(), d->endianness == BigEndian));
diinTitle.append(ByteVector::fromCString(diinTag->title().toCString()));
setChildChunkData("DITI", diinTitle, DIINChunk);
}
else
setChildChunkData("DITI", ByteVector(), DIINChunk);
if(!diinTag->artist().isNull() && !diinTag->artist().isEmpty()) {
ByteVector diinArtist;
diinArtist.append(ByteVector::fromUInt(diinTag->artist().size(), d->endianness == BigEndian));
diinArtist.append(ByteVector::fromCString(diinTag->artist().toCString()));
setChildChunkData("DIAR", diinArtist, DIINChunk);
}
else
setChildChunkData("DIAR", ByteVector(), DIINChunk);
}
// Third: remove the duplicate ID3V2 chunk (inside PROP chunk) if any
if(d->duplicateID3V2chunkIndex>=0) {
setChildChunkData(d->duplicateID3V2chunkIndex, ByteVector(), PROPChunk);
d->duplicateID3V2chunkIndex = -1;
}
return true;
}
////////////////////////////////////////////////////////////////////////////////
// private members
////////////////////////////////////////////////////////////////////////////////
void DSDIFF::File::setRootChunkData(unsigned int i, const ByteVector &data)
{
if(data.isNull() || data.isEmpty()) {
// Null data: remove chunk
// Update global size
unsigned long long removedChunkTotalSize = d->chunks[i].size + d->chunks[i].padding + 12;
d->size -= removedChunkTotalSize;
insert(ByteVector::fromLongLong(d->size, d->endianness == BigEndian), 4, 8);
removeBlock(d->chunks[i].offset - 12, removedChunkTotalSize);
// Update the internal offsets
for(unsigned long r = i + 1; r < d->chunks.size(); r++)
d->chunks[r].offset = d->chunks[r - 1].offset + 12
+ d->chunks[r - 1].size + d->chunks[r - 1].padding;
d->chunks.erase(d->chunks.begin() + i);
}
else {
// Non null data: update chunk
// First we update the global size
d->size += ((data.size() + 1) & ~1) - (d->chunks[i].size + d->chunks[i].padding);
insert(ByteVector::fromLongLong(d->size, d->endianness == BigEndian), 4, 8);
// Now update the specific chunk
writeChunk(d->chunks[i].name,
data,
d->chunks[i].offset - 12,
d->chunks[i].size + d->chunks[i].padding + 12);
d->chunks[i].size = data.size();
d->chunks[i].padding = (data.size() & 0x01) ? 1 : 0;
// Finally update the internal offsets
updateRootChunksStructure(i + 1);
}
}
void DSDIFF::File::setRootChunkData(const ByteVector &name, const ByteVector &data)
{
if(d->chunks.size() == 0) {
debug("DSDIFF::File::setPropChunkData - No valid chunks found.");
return;
}
for(unsigned int i = 0; i < d->chunks.size(); i++) {
if(d->chunks[i].name == name) {
setRootChunkData(i, data);
return;
}
}
// Couldn't find an existing chunk, so let's create a new one.
unsigned int i = d->chunks.size() - 1;
unsigned long offset = d->chunks[i].offset + d->chunks[i].size + d->chunks[i].padding;
// First we update the global size
d->size += (offset & 1) + ((data.size() + 1) & ~1) + 12;
insert(ByteVector::fromLongLong(d->size, d->endianness == BigEndian), 4, 8);
// Now add the chunk to the file
writeChunk(name,
data,
offset,
std::max<unsigned long long>(0, length() - offset),
(offset & 1) ? 1 : 0);
Chunk64 chunk;
chunk.name = name;
chunk.size = data.size();
chunk.offset = offset + 12;
chunk.padding = (data.size() & 0x01) ? 1 : 0;
d->chunks.push_back(chunk);
}
void DSDIFF::File::setChildChunkData(unsigned int i,
const ByteVector &data,
unsigned int childChunkNum)
{
std::vector<Chunk64> &childChunks = d->childChunks[childChunkNum];
if(data.isNull() || data.isEmpty()) {
// Null data: remove chunk
// Update global size
unsigned long long removedChunkTotalSize = childChunks[i].size + childChunks[i].padding + 12;
d->size -= removedChunkTotalSize;
insert(ByteVector::fromLongLong(d->size, d->endianness == BigEndian), 4, 8);
// Update child chunk size
d->chunks[d->childChunkIndex[childChunkNum]].size -= removedChunkTotalSize;
insert(ByteVector::fromLongLong(d->chunks[d->childChunkIndex[childChunkNum]].size,
d->endianness == BigEndian),
d->chunks[d->childChunkIndex[childChunkNum]].offset - 8, 8);
// Remove the chunk
removeBlock(childChunks[i].offset - 12, removedChunkTotalSize);
// Update the internal offsets
// For child chunks
if((i + 1) < childChunks.size()) {
childChunks[i + 1].offset = childChunks[i].offset;
i++;
for(i++; i < childChunks.size(); i++)
childChunks[i].offset = childChunks[i - 1].offset + 12
+ childChunks[i - 1].size + childChunks[i - 1].padding;
}
// And for root chunks
for(i = d->childChunkIndex[childChunkNum] + 1; i < d->chunks.size(); i++)
d->chunks[i].offset = d->chunks[i - 1].offset + 12
+ d->chunks[i - 1].size + d->chunks[i - 1].padding;
childChunks.erase(childChunks.begin() + i);
}
else {
// Non null data: update chunk
// First we update the global size
d->size += ((data.size() + 1) & ~1) - (childChunks[i].size + childChunks[i].padding);
insert(ByteVector::fromLongLong(d->size, d->endianness == BigEndian), 4, 8);
// And the PROP chunk size
d->chunks[d->childChunkIndex[childChunkNum]].size += ((data.size() + 1) & ~1)
- (childChunks[i].size + childChunks[i].padding);
insert(ByteVector::fromLongLong(d->chunks[d->childChunkIndex[childChunkNum]].size,
d->endianness == BigEndian),
d->chunks[d->childChunkIndex[childChunkNum]].offset - 8, 8);
// Now update the specific chunk
writeChunk(childChunks[i].name,
data,
childChunks[i].offset - 12,
childChunks[i].size + childChunks[i].padding + 12);
childChunks[i].size = data.size();
childChunks[i].padding = (data.size() & 0x01) ? 1 : 0;
// Now update the internal offsets
// For child Chunks
for(i++; i < childChunks.size(); i++)
childChunks[i].offset = childChunks[i - 1].offset + 12
+ childChunks[i - 1].size + childChunks[i - 1].padding;
// And for root chunks
updateRootChunksStructure(d->childChunkIndex[childChunkNum] + 1);
}
}
void DSDIFF::File::setChildChunkData(const ByteVector &name,
const ByteVector &data,
unsigned int childChunkNum)
{
std::vector<Chunk64> &childChunks = d->childChunks[childChunkNum];
if(childChunks.size() == 0) {
debug("DSDIFF::File::setPropChunkData - No valid chunks found.");
return;
}
for(unsigned int i = 0; i < childChunks.size(); i++) {
if(childChunks[i].name == name) {
setChildChunkData(i, data, childChunkNum);
return;
}
}
// Do not attempt to remove a non existing chunk
if(data.isNull() || data.isEmpty())
return;
// Couldn't find an existing chunk, so let's create a new one.
unsigned int i = childChunks.size() - 1;
unsigned long offset = childChunks[i].offset + childChunks[i].size + childChunks[i].padding;
// First we update the global size
d->size += (offset & 1) + ((data.size() + 1) & ~1) + 12;
insert(ByteVector::fromLongLong(d->size, d->endianness == BigEndian), 4, 8);
// And the child chunk size
d->chunks[d->childChunkIndex[childChunkNum]].size += (offset & 1)
+ ((data.size() + 1) & ~1) + 12;
insert(ByteVector::fromLongLong(d->chunks[d->childChunkIndex[childChunkNum]].size,
d->endianness == BigEndian),
d->chunks[d->childChunkIndex[childChunkNum]].offset - 8, 8);
// Now add the chunk to the file
unsigned long long nextRootChunkIdx = length();
if((d->childChunkIndex[childChunkNum] + 1) < static_cast<int>(d->chunks.size()))
nextRootChunkIdx = d->chunks[d->childChunkIndex[childChunkNum] + 1].offset - 12;
writeChunk(name, data, offset,
std::max<unsigned long long>(0, nextRootChunkIdx - offset),
(offset & 1) ? 1 : 0);
// For root chunks
updateRootChunksStructure(d->childChunkIndex[childChunkNum] + 1);
Chunk64 chunk;
chunk.name = name;
chunk.size = data.size();
chunk.offset = offset + 12;
chunk.padding = (data.size() & 0x01) ? 1 : 0;
childChunks.push_back(chunk);
}
static bool isValidChunkID(const ByteVector &name)
{
if(name.size() != 4)
return false;
for(int i = 0; i < 4; i++) {
if(name[i] < 32 || name[i] > 127)
return false;
}
return true;
}
void DSDIFF::File::updateRootChunksStructure(unsigned int startingChunk)
{
for(unsigned int i = startingChunk; i < d->chunks.size(); i++)
d->chunks[i].offset = d->chunks[i - 1].offset + 12
+ d->chunks[i - 1].size + d->chunks[i - 1].padding;
// Update childchunks structure as well
if(d->childChunkIndex[PROPChunk] >= static_cast<int>(startingChunk)) {
std::vector<Chunk64> &childChunksToUpdate = d->childChunks[PROPChunk];
if(childChunksToUpdate.size() > 0) {
childChunksToUpdate[0].offset = d->chunks[d->childChunkIndex[PROPChunk]].offset + 12;
for(unsigned int i = 1; i < childChunksToUpdate.size(); i++)
childChunksToUpdate[i].offset = childChunksToUpdate[i - 1].offset + 12
+ childChunksToUpdate[i - 1].size + childChunksToUpdate[i - 1].padding;
}
}
if(d->childChunkIndex[DIINChunk] >= static_cast<int>(startingChunk)) {
std::vector<Chunk64> &childChunksToUpdate = d->childChunks[DIINChunk];
if(childChunksToUpdate.size() > 0) {
childChunksToUpdate[0].offset = d->chunks[d->childChunkIndex[DIINChunk]].offset + 12;
for(unsigned int i = 1; i < childChunksToUpdate.size(); i++)
childChunksToUpdate[i].offset = childChunksToUpdate[i - 1].offset + 12
+ childChunksToUpdate[i - 1].size + childChunksToUpdate[i - 1].padding;
}
}
}
void DSDIFF::File::read(bool readProperties, Properties::ReadStyle propertiesStyle)
{
bool bigEndian = (d->endianness == BigEndian);
d->type = readBlock(4);
d->size = readBlock(8).toLongLong(bigEndian);
d->format = readBlock(4);
// + 12: chunk header at least, fix for additional junk bytes
while(tell() + 12 <= length()) {
ByteVector chunkName = readBlock(4);
unsigned long long chunkSize = readBlock(8).toLongLong(bigEndian);
if(!isValidChunkID(chunkName)) {
debug("DSDIFF::File::read() -- Chunk '" + chunkName + "' has invalid ID");
setValid(false);
break;
}
if(static_cast<unsigned long long>(tell()) + chunkSize > static_cast<unsigned long long>(length())) {
debug("DSDIFF::File::read() -- Chunk '" + chunkName
+ "' has invalid size (larger than the file size)");
setValid(false);
break;
}
Chunk64 chunk;
chunk.name = chunkName;
chunk.size = chunkSize;
chunk.offset = tell();
seek(chunk.size, Current);
// Check padding
chunk.padding = 0;
long uPosNotPadded = tell();
if((uPosNotPadded & 0x01) != 0) {
ByteVector iByte = readBlock(1);
if((iByte.size() != 1) || (iByte[0] != 0))
// Not well formed, re-seek
seek(uPosNotPadded, Beginning);
else
chunk.padding = 1;
}
d->chunks.push_back(chunk);
}
unsigned long long lengthDSDSamplesTimeChannels = 0; // For DSD uncompressed
unsigned long long audioDataSizeinBytes = 0; // For computing bitrate
unsigned long dstNumFrames = 0; // For DST compressed frames
unsigned short dstFrameRate = 0; // For DST compressed frames
for(unsigned int i = 0; i < d->chunks.size(); i++) {
if(d->chunks[i].name == "DSD ") {
lengthDSDSamplesTimeChannels = d->chunks[i].size * 8;
audioDataSizeinBytes = d->chunks[i].size;
}
else if(d->chunks[i].name == "DST ") {
// Now decode the chunks inside the DST chunk to read the DST Frame Information one
long long dstChunkEnd = d->chunks[i].offset + d->chunks[i].size;
seek(d->chunks[i].offset);
audioDataSizeinBytes = d->chunks[i].size;
while(tell() + 12 <= dstChunkEnd) {
ByteVector dstChunkName = readBlock(4);
long long dstChunkSize = readBlock(8).toLongLong(bigEndian);
if(!isValidChunkID(dstChunkName)) {
debug("DSDIFF::File::read() -- DST Chunk '" + dstChunkName + "' has invalid ID");
setValid(false);
break;
}
if(static_cast<long long>(tell()) + dstChunkSize > dstChunkEnd) {
debug("DSDIFF::File::read() -- DST Chunk '" + dstChunkName
+ "' has invalid size (larger than the DST chunk)");
setValid(false);
break;
}
if(dstChunkName == "FRTE") {
// Found the DST frame information chunk
dstNumFrames = readBlock(4).toUInt(bigEndian);
dstFrameRate = readBlock(2).toUShort(bigEndian);
break; // Found the wanted one, no need to look at the others
}
seek(dstChunkSize, Current);
// Check padding
long uPosNotPadded = tell();
if((uPosNotPadded & 0x01) != 0) {
ByteVector iByte = readBlock(1);
if((iByte.size() != 1) || (iByte[0] != 0))
// Not well formed, re-seek
seek(uPosNotPadded, Beginning);
}
}
}
else if(d->chunks[i].name == "PROP") {
d->childChunkIndex[PROPChunk] = i;
// Now decodes the chunks inside the PROP chunk
long long propChunkEnd = d->chunks[i].offset + d->chunks[i].size;
seek(d->chunks[i].offset + 4); // +4 to remove the 'SND ' marker at beginning of 'PROP' chunk
while(tell() + 12 <= propChunkEnd) {
ByteVector propChunkName = readBlock(4);
long long propChunkSize = readBlock(8).toLongLong(bigEndian);
if(!isValidChunkID(propChunkName)) {
debug("DSDIFF::File::read() -- PROP Chunk '" + propChunkName + "' has invalid ID");
setValid(false);
break;
}
if(static_cast<long long>(tell()) + propChunkSize > propChunkEnd) {
debug("DSDIFF::File::read() -- PROP Chunk '" + propChunkName
+ "' has invalid size (larger than the PROP chunk)");
setValid(false);
break;
}
Chunk64 chunk;
chunk.name = propChunkName;
chunk.size = propChunkSize;
chunk.offset = tell();
seek(chunk.size, Current);
// Check padding
chunk.padding = 0;
long uPosNotPadded = tell();
if((uPosNotPadded & 0x01) != 0) {
ByteVector iByte = readBlock(1);
if((iByte.size() != 1) || (iByte[0] != 0))
// Not well formed, re-seek
seek(uPosNotPadded, Beginning);
else
chunk.padding = 1;
}
d->childChunks[PROPChunk].push_back(chunk);
}
}
else if(d->chunks[i].name == "DIIN") {
d->childChunkIndex[DIINChunk] = i;
d->hasDiin = true;
// Now decode the chunks inside the DIIN chunk
long long diinChunkEnd = d->chunks[i].offset + d->chunks[i].size;
seek(d->chunks[i].offset);
while(tell() + 12 <= diinChunkEnd) {
ByteVector diinChunkName = readBlock(4);
long long diinChunkSize = readBlock(8).toLongLong(bigEndian);
if(!isValidChunkID(diinChunkName)) {
debug("DSDIFF::File::read() -- DIIN Chunk '" + diinChunkName + "' has invalid ID");
setValid(false);
break;
}
if(static_cast<long long>(tell()) + diinChunkSize > diinChunkEnd) {
debug("DSDIFF::File::read() -- DIIN Chunk '" + diinChunkName
+ "' has invalid size (larger than the DIIN chunk)");
setValid(false);
break;
}
Chunk64 chunk;
chunk.name = diinChunkName;
chunk.size = diinChunkSize;
chunk.offset = tell();
seek(chunk.size, Current);
// Check padding
chunk.padding = 0;
long uPosNotPadded = tell();
if((uPosNotPadded & 0x01) != 0) {
ByteVector iByte = readBlock(1);
if((iByte.size() != 1) || (iByte[0] != 0))
// Not well formed, re-seek
seek(uPosNotPadded, Beginning);
else
chunk.padding = 1;
}
d->childChunks[DIINChunk].push_back(chunk);
}
}
else if(d->chunks[i].name == "ID3 " || d->chunks[i].name == "id3 ") {
d->id3v2TagChunkID = d->chunks[i].name;
d->tag.set(ID3v2Index, new ID3v2::Tag(this, d->chunks[i].offset));
d->isID3InPropChunk = false;
d->hasID3v2 = true;
}
}
if(!isValid())
return;
if(d->childChunkIndex[PROPChunk] < 0) {
debug("DSDIFF::File::read() -- no PROP chunk found");
setValid(false);
return;
}
// Read properties
unsigned int sampleRate=0;
unsigned short channels=0;
for(unsigned int i = 0; i < d->childChunks[PROPChunk].size(); i++) {
if(d->childChunks[PROPChunk][i].name == "ID3 " || d->childChunks[PROPChunk][i].name == "id3 ") {
if(d->hasID3v2) {
d->duplicateID3V2chunkIndex = i;
continue; // ID3V2 tag has already been found at root level
}
d->id3v2TagChunkID = d->childChunks[PROPChunk][i].name;
d->tag.set(ID3v2Index, new ID3v2::Tag(this, d->childChunks[PROPChunk][i].offset));
d->isID3InPropChunk = true;
d->hasID3v2 = true;
}
else if(d->childChunks[PROPChunk][i].name == "FS ") {
// Sample rate
seek(d->childChunks[PROPChunk][i].offset);
sampleRate = readBlock(4).toUInt(0, 4, bigEndian);
}
else if(d->childChunks[PROPChunk][i].name == "CHNL") {
// Channels
seek(d->childChunks[PROPChunk][i].offset);
channels = readBlock(2).toShort(0, bigEndian);
}
}
// Read title & artist from DIIN chunk
d->tag.access<DSDIFF::DIIN::Tag>(DIINIndex, true);
if(d->hasDiin) {
for(unsigned int i = 0; i < d->childChunks[DIINChunk].size(); i++) {
if(d->childChunks[DIINChunk][i].name == "DITI") {
seek(d->childChunks[DIINChunk][i].offset);
unsigned int titleStrLength = readBlock(4).toUInt(0, 4, bigEndian);
if(titleStrLength <= d->childChunks[DIINChunk][i].size) {
ByteVector titleStr = readBlock(titleStrLength);
d->tag.access<DSDIFF::DIIN::Tag>(DIINIndex, false)->setTitle(titleStr);
}
}
else if(d->childChunks[DIINChunk][i].name == "DIAR") {
seek(d->childChunks[DIINChunk][i].offset);
unsigned int artistStrLength = readBlock(4).toUInt(0, 4, bigEndian);
if(artistStrLength <= d->childChunks[DIINChunk][i].size) {
ByteVector artistStr = readBlock(artistStrLength);
d->tag.access<DSDIFF::DIIN::Tag>(DIINIndex, false)->setArtist(artistStr);
}
}
}
}
if(readProperties) {
if(lengthDSDSamplesTimeChannels == 0) {
// DST compressed signal : need to compute length of DSD uncompressed frames
if(dstFrameRate > 0)
lengthDSDSamplesTimeChannels = (unsigned long long)dstNumFrames
* (unsigned long long)sampleRate / (unsigned long long)dstFrameRate;
else
lengthDSDSamplesTimeChannels = 0;
}
else {
// In DSD uncompressed files, the read number of samples is the total for each channel
if(channels > 0)
lengthDSDSamplesTimeChannels /= channels;
}
int bitrate = 0;
if(lengthDSDSamplesTimeChannels > 0)
bitrate = (audioDataSizeinBytes*8*sampleRate) / lengthDSDSamplesTimeChannels / 1000;
d->properties = new Properties(sampleRate,
channels,
lengthDSDSamplesTimeChannels,
bitrate,
propertiesStyle);
}
if(!ID3v2Tag()) {
d->tag.access<ID3v2::Tag>(ID3v2Index, true);
d->isID3InPropChunk = false; // By default, ID3 chunk is at root level
d->hasID3v2 = false;
}
}
void DSDIFF::File::writeChunk(const ByteVector &name, const ByteVector &data,
unsigned long long offset, unsigned long replace,
unsigned int leadingPadding)
{
ByteVector combined;
if(leadingPadding)
combined.append(ByteVector(leadingPadding, '\x00'));
combined.append(name);
combined.append(ByteVector::fromLongLong(data.size(), d->endianness == BigEndian));
combined.append(data);
if((data.size() & 0x01) != 0)
combined.append('\x00');
insert(combined, offset, replace);
}

260
3rdparty/taglib/dsdiff/dsdifffile.h vendored Normal file
View File

@ -0,0 +1,260 @@
/***************************************************************************
copyright : (C) 2016 by Damien Plisson, Audirvana
email : damien78@audirvana.com
***************************************************************************/
/***************************************************************************
* 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_DSDIFFFILE_H
#define TAGLIB_DSDIFFFILE_H
#include "rifffile.h"
#include "id3v2tag.h"
#include "dsdiffproperties.h"
#include "dsdiffdiintag.h"
namespace TagLib {
//! An implementation of DSDIFF metadata
/*!
* This is implementation of DSDIFF metadata.
*
* This supports an ID3v2 tag as well as reading stream from the ID3 RIFF
* chunk as well as properties from the file.
* Description of the DSDIFF format is available
* at http://dsd-guide.com/sites/default/files/white-papers/DSDIFF_1.5_Spec.pdf
* DSDIFF standard does not explictly specify the ID3V2 chunk
* It can be found at the root level, but also sometimes inside the PROP chunk
* In addition, title and artist info are stored as part of the standard
*/
namespace DSDIFF {
//! An implementation of TagLib::File with DSDIFF specific methods
/*!
* This implements and provides an interface for DSDIFF files to the
* TagLib::Tag and TagLib::AudioProperties interfaces by way of implementing
* the abstract TagLib::File API as well as providing some additional
* information specific to DSDIFF files.
*/
class TAGLIB_EXPORT File : public TagLib::File
{
public:
/*!
* Constructs an DSDIFF file from \a file. If \a readProperties is true
* the file's audio properties will also be read.
*
* \note In the current implementation, \a propertiesStyle is ignored.
*/
File(FileName file, bool readProperties = true,
Properties::ReadStyle propertiesStyle = Properties::Average);
/*!
* Constructs an DSDIFF file from \a stream. If \a readProperties is true
* the file's audio properties will also be read.
*
* \note TagLib will *not* take ownership of the stream, the caller is
* responsible for deleting it after the File object.
*
* \note In the current implementation, \a propertiesStyle is ignored.
*/
File(IOStream *stream, bool readProperties = true,
Properties::ReadStyle propertiesStyle = Properties::Average);
/*!
* Destroys this instance of the File.
*/
virtual ~File();
/*!
* Returns a pointer to a tag that is the union of the ID3v2 and DIIN
* tags. The ID3v2 tag is given priority in reading the information -- if
* requested information exists in both the ID3v2 tag and the ID3v1 tag,
* the information from the ID3v2 tag will be returned.
*
* If you would like more granular control over the content of the tags,
* with the concession of generality, use the tag-type specific calls.
*
* \note As this tag is not implemented as an ID3v2 tag or a DIIN tag,
* but a union of the two this pointer may not be cast to the specific
* tag types.
*
* \see ID3v2Tag()
* \see DIINTag()
*/
virtual Tag *tag() const;
/*!
* Returns the ID3V2 Tag for this file.
*
* \note This always returns a valid pointer regardless of whether or not
* the file on disk has an ID3v2 tag. Use hasID3v2Tag() to check if the
* file on disk actually has an ID3v2 tag.
*
* \see hasID3v2Tag()
*/
virtual ID3v2::Tag *ID3v2Tag() const;
/*!
* Returns the DSDIFF DIIN Tag for this file
*
*/
DSDIFF::DIIN::Tag *DIINTag() const;
/*!
* Implements the unified property interface -- export function.
* This method forwards to ID3v2::Tag::properties().
*/
PropertyMap properties() const;
void removeUnsupportedProperties(const StringList &properties);
/*!
* Implements the unified property interface -- import function.
* This method forwards to ID3v2::Tag::setProperties().
*/
PropertyMap setProperties(const PropertyMap &);
/*!
* Returns the AIFF::Properties for this file. If no audio properties
* were read then this will return a null pointer.
*/
virtual Properties *audioProperties() const;
/*!
* Save the file. If at least one tag -- ID3v1 or DIIN -- exists this
* will duplicate its content into the other tag. This returns true
* if saving was successful.
*
* If neither exists or if both tags are empty, this will strip the tags
* from the file.
*
* This is the same as calling save(AllTags);
*
* If you would like more granular control over the content of the tags,
* with the concession of generality, use paramaterized save call below.
*
* \see save(int tags)
*/
virtual bool save();
/*!
* Save the file. This will attempt to save all of the tag types that are
* specified by OR-ing together TagTypes values. The save() method above
* uses AllTags. This returns true if saving was successful.
*
* This strips all tags not included in the mask, but does not modify them
* in memory, so later calls to save() which make use of these tags will
* remain valid. This also strips empty tags.
*/
bool save(int tags);
/*!
* Returns whether or not the file on disk actually has an ID3v2 tag.
*
* \see ID3v2Tag()
*/
bool hasID3v2Tag() const;
/*!
* Returns whether or not the file on disk actually has the DSDIFF
* Title & Artist tag.
*
* \see DIINTag()
*/
bool hasDIINTag() const;
/*!
* Returns whether or not the given \a stream can be opened as a DSDIFF
* file.
*
* \note This method is designed to do a quick check. The result may
* not necessarily be correct.
*/
static bool isSupported(IOStream *stream);
protected:
enum Endianness { BigEndian, LittleEndian };
File(FileName file, Endianness endianness);
File(IOStream *stream, Endianness endianness);
private:
File(const File &);
File &operator=(const File &);
/*!
* Sets the data for the the specified chunk at root level to \a data.
*
* \warning This will update the file immediately.
*/
void setRootChunkData(unsigned int i, const ByteVector &data);
/*!
* Sets the data for the root-level chunk \a name to \a data.
* If a root-level chunk with the given name already exists
* it will be overwritten, otherwise it will be
* created after the existing chunks.
*
* \warning This will update the file immediately.
*/
void setRootChunkData(const ByteVector &name, const ByteVector &data);
/*!
* Sets the data for the the specified child chunk to \a data.
*
* If data is null, then remove the chunk
*
* \warning This will update the file immediately.
*/
void setChildChunkData(unsigned int i, const ByteVector &data,
unsigned int childChunkNum);
/*!
* Sets the data for the child chunk \a name to \a data. If a chunk with
* the given name already exists it will be overwritten, otherwise it will
* be created after the existing chunks inside child chunk.
*
* If data is null, then remove the chunks with \a name name
*
* \warning This will update the file immediately.
*/
void setChildChunkData(const ByteVector &name, const ByteVector &data,
unsigned int childChunkNum);
void updateRootChunksStructure(unsigned int startingChunk);
void read(bool readProperties, Properties::ReadStyle propertiesStyle);
void writeChunk(const ByteVector &name, const ByteVector &data,
unsigned long long offset, unsigned long replace = 0,
unsigned int leadingPadding = 0);
class FilePrivate;
FilePrivate *d;
};
}
}
#endif

View File

@ -0,0 +1,120 @@
/***************************************************************************
copyright : (C) 2016 by Damien Plisson, Audirvana
email : damien78@audirvana.com
***************************************************************************/
/***************************************************************************
* 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 <tstring.h>
#include <tdebug.h>
#include "dsdiffproperties.h"
using namespace TagLib;
class DSDIFF::Properties::PropertiesPrivate
{
public:
PropertiesPrivate() :
length(0),
bitrate(0),
sampleRate(0),
channels(0),
sampleWidth(0),
sampleCount(0)
{
}
int length;
int bitrate;
int sampleRate;
int channels;
int sampleWidth;
unsigned long long sampleCount;
};
////////////////////////////////////////////////////////////////////////////////
// public members
////////////////////////////////////////////////////////////////////////////////
DSDIFF::Properties::Properties(const unsigned int sampleRate,
const unsigned short channels,
const unsigned long long samplesCount,
const int bitrate,
ReadStyle style) : AudioProperties(style)
{
d = new PropertiesPrivate;
d->channels = channels;
d->sampleCount = samplesCount;
d->sampleWidth = 1;
d->sampleRate = sampleRate;
d->bitrate = bitrate;
d->length = d->sampleRate > 0
? static_cast<int>((d->sampleCount * 1000.0) / d->sampleRate + 0.5)
: 0;
}
DSDIFF::Properties::~Properties()
{
delete d;
}
int DSDIFF::Properties::length() const
{
return lengthInSeconds();
}
int DSDIFF::Properties::lengthInSeconds() const
{
return d->length / 1000;
}
int DSDIFF::Properties::lengthInMilliseconds() const
{
return d->length;
}
int DSDIFF::Properties::bitrate() const
{
return d->bitrate;
}
int DSDIFF::Properties::sampleRate() const
{
return d->sampleRate;
}
int DSDIFF::Properties::channels() const
{
return d->channels;
}
int DSDIFF::Properties::bitsPerSample() const
{
return d->sampleWidth;
}
long long DSDIFF::Properties::sampleCount() const
{
return d->sampleCount;
}

View File

@ -0,0 +1,83 @@
/***************************************************************************
copyright : (C) 2016 by Damien Plisson, Audirvana
email : damien78@audirvana.com
***************************************************************************/
/***************************************************************************
* 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_DSDIFFPROPERTIES_H
#define TAGLIB_DSDIFFPROPERTIES_H
#include "audioproperties.h"
namespace TagLib {
namespace DSDIFF {
class File;
//! An implementation of audio property reading for DSDIFF
/*!
* This reads the data from an DSDIFF stream found in the AudioProperties
* API.
*/
class TAGLIB_EXPORT Properties : public AudioProperties
{
public:
/*!
* Create an instance of DSDIFF::Properties with the data read from the
* ByteVector \a data.
*/
Properties(const unsigned int sampleRate, const unsigned short channels,
const unsigned long long samplesCount, const int bitrate,
ReadStyle style);
/*!
* Destroys this DSDIFF::Properties instance.
*/
virtual ~Properties();
// Reimplementations.
virtual int length() const;
virtual int lengthInSeconds() const;
virtual int lengthInMilliseconds() const;
virtual int bitrate() const;
virtual int sampleRate() const;
virtual int channels() const;
int bitsPerSample() const;
long long sampleCount() const;
private:
Properties(const Properties &);
Properties &operator=(const Properties &);
class PropertiesPrivate;
PropertiesPrivate *d;
};
}
}
#endif

241
3rdparty/taglib/dsf/dsffile.cpp vendored Normal file
View File

@ -0,0 +1,241 @@
/***************************************************************************
copyright : (C) 2013 - 2018 by 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 <tbytevector.h>
#include <tdebug.h>
#include <id3v2tag.h>
#include <tstringlist.h>
#include <tpropertymap.h>
#include <tagutils.h>
#include "dsffile.h"
using namespace TagLib;
// The DSF specification is located at http://dsd-guide.com/sites/default/files/white-papers/DSFFileFormatSpec_E.pdf
class DSF::File::FilePrivate
{
public:
FilePrivate() :
properties(0),
tag(0)
{
}
~FilePrivate()
{
delete properties;
delete tag;
}
long long fileSize;
long long metadataOffset;
Properties *properties;
ID3v2::Tag *tag;
};
////////////////////////////////////////////////////////////////////////////////
// static members
////////////////////////////////////////////////////////////////////////////////
bool DSF::File::isSupported(IOStream *stream)
{
// A DSF file has to start with "DSD "
const ByteVector id = Utils::readHeader(stream, 4, false);
return id.startsWith("DSD ");
}
////////////////////////////////////////////////////////////////////////////////
// public members
////////////////////////////////////////////////////////////////////////////////
DSF::File::File(FileName file, bool readProperties,
Properties::ReadStyle propertiesStyle) :
TagLib::File(file),
d(new FilePrivate())
{
if(isOpen())
read(readProperties, propertiesStyle);
}
DSF::File::File(IOStream *stream, bool readProperties,
Properties::ReadStyle propertiesStyle) :
TagLib::File(stream),
d(new FilePrivate())
{
if(isOpen())
read(readProperties, propertiesStyle);
}
DSF::File::~File()
{
delete d;
}
ID3v2::Tag *DSF::File::tag() const
{
return d->tag;
}
PropertyMap DSF::File::properties() const
{
return d->tag->properties();
}
PropertyMap DSF::File::setProperties(const PropertyMap &properties)
{
return d->tag->setProperties(properties);
}
DSF::Properties *DSF::File::audioProperties() const
{
return d->properties;
}
bool DSF::File::save()
{
if(readOnly()) {
debug("DSF::File::save() -- File is read only.");
return false;
}
if(!isValid()) {
debug("DSF::File::save() -- Trying to save invalid file.");
return false;
}
// Three things must be updated: the file size, the tag data, and the metadata offset
if(d->tag->isEmpty()) {
long long newFileSize = d->metadataOffset ? d->metadataOffset : d->fileSize;
// Update the file size
if(d->fileSize != newFileSize) {
insert(ByteVector::fromLongLong(newFileSize, false), 12, 8);
d->fileSize = newFileSize;
}
// Update the metadata offset to 0 since there is no longer a tag
if(d->metadataOffset) {
insert(ByteVector::fromLongLong(0ULL, false), 20, 8);
d->metadataOffset = 0;
}
// Delete the old tag
truncate(newFileSize);
}
else {
ByteVector tagData = d->tag->render();
long long newMetadataOffset = d->metadataOffset ? d->metadataOffset : d->fileSize;
long long newFileSize = newMetadataOffset + tagData.size();
long long oldTagSize = d->fileSize - newMetadataOffset;
// Update the file size
if(d->fileSize != newFileSize) {
insert(ByteVector::fromLongLong(newFileSize, false), 12, 8);
d->fileSize = newFileSize;
}
// Update the metadata offset
if(d->metadataOffset != newMetadataOffset) {
insert(ByteVector::fromLongLong(newMetadataOffset, false), 20, 8);
d->metadataOffset = newMetadataOffset;
}
// Delete the old tag and write the new one
insert(tagData, newMetadataOffset, static_cast<size_t>(oldTagSize));
}
return true;
}
////////////////////////////////////////////////////////////////////////////////
// private members
////////////////////////////////////////////////////////////////////////////////
void DSF::File::read(bool readProperties, Properties::ReadStyle propertiesStyle)
{
// A DSF file consists of four chunks: DSD chunk, format chunk, data chunk, and metadata chunk
// The file format is not chunked in the sense of a RIFF File, though
// DSD chunk
ByteVector chunkName = readBlock(4);
if(chunkName != "DSD ") {
debug("DSF::File::read() -- Not a DSF file.");
setValid(false);
return;
}
long long chunkSize = readBlock(8).toLongLong(false);
// Integrity check
if(28 != chunkSize) {
debug("DSF::File::read() -- File is corrupted, wrong chunk size");
setValid(false);
return;
}
d->fileSize = readBlock(8).toLongLong(false);
// File is malformed or corrupted
if(d->fileSize != length()) {
debug("DSF::File::read() -- File is corrupted wrong length");
setValid(false);
return;
}
d->metadataOffset = readBlock(8).toLongLong(false);
// File is malformed or corrupted
if(d->metadataOffset > d->fileSize) {
debug("DSF::File::read() -- Invalid metadata offset.");
setValid(false);
return;
}
// Format chunk
chunkName = readBlock(4);
if(chunkName != "fmt ") {
debug("DSF::File::read() -- Missing 'fmt ' chunk.");
setValid(false);
return;
}
chunkSize = readBlock(8).toLongLong(false);
d->properties = new Properties(readBlock(chunkSize), propertiesStyle);
// Skip the data chunk
// A metadata offset of 0 indicates the absence of an ID3v2 tag
if(0 == d->metadataOffset)
d->tag = new ID3v2::Tag();
else
d->tag = new ID3v2::Tag(this, d->metadataOffset);
}

128
3rdparty/taglib/dsf/dsffile.h vendored Normal file
View File

@ -0,0 +1,128 @@
/***************************************************************************
copyright : (C) 2013 - 2018 by 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_DSFFILE_H
#define TAGLIB_DSFFILE_H
#include "tfile.h"
#include "id3v2tag.h"
#include "dsfproperties.h"
namespace TagLib {
//! An implementation of DSF metadata
/*!
* This is implementation of DSF metadata.
*
* This supports an ID3v2 tag as well as properties from the file.
*/
namespace DSF {
//! An implementation of TagLib::File with DSF specific methods
/*!
* This implements and provides an interface for DSF files to the
* TagLib::Tag and TagLib::AudioProperties interfaces by way of implementing
* the abstract TagLib::File API as well as providing some additional
* information specific to DSF files.
*/
class TAGLIB_EXPORT File : public TagLib::File
{
public:
/*!
* Contructs an DSF file from \a file. If \a readProperties is true the
* file's audio properties will also be read using \a propertiesStyle. If
* false, \a propertiesStyle is ignored.
*/
File(FileName file, bool readProperties = true,
Properties::ReadStyle propertiesStyle = Properties::Average);
/*!
* Contructs an DSF file from \a file. If \a readProperties is true the
* file's audio properties will also be read using \a propertiesStyle. If
* false, \a propertiesStyle is ignored.
*/
File(IOStream *stream, bool readProperties = true,
Properties::ReadStyle propertiesStyle = Properties::Average);
/*!
* Destroys this instance of the File.
*/
virtual ~File();
/*!
* Returns the Tag for this file.
*/
ID3v2::Tag *tag() const;
/*!
* Implements the unified property interface -- export function.
* This method forwards to ID3v2::Tag::properties().
*/
PropertyMap properties() const;
/*!
* Implements the unified property interface -- import function.
* This method forwards to ID3v2::Tag::setProperties().
*/
PropertyMap setProperties(const PropertyMap &);
/*!
* Returns the DSF::AudioProperties for this file. If no audio properties
* were read then this will return a null pointer.
*/
virtual Properties *audioProperties() const;
/*!
* Saves the file.
*/
virtual bool save();
/*!
* Returns whether or not the given \a stream can be opened as a DSF
* file.
*
* \note This method is designed to do a quick check. The result may
* not necessarily be correct.
*/
static bool isSupported(IOStream *stream);
private:
File(const File &);
File &operator=(const File &);
void read(bool readProperties, Properties::ReadStyle propertiesStyle);
class FilePrivate;
FilePrivate *d;
};
}
}
#endif

161
3rdparty/taglib/dsf/dsfproperties.cpp vendored Normal file
View File

@ -0,0 +1,161 @@
/***************************************************************************
copyright : (C) 2013 by 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 <tstring.h>
#include <tdebug.h>
#include "dsfproperties.h"
using namespace TagLib;
class DSF::Properties::PropertiesPrivate
{
public:
PropertiesPrivate() :
formatVersion(0),
formatID(0),
channelType(0),
channelNum(0),
samplingFrequency(0),
bitsPerSample(0),
sampleCount(0),
blockSizePerChannel(0),
bitrate(0),
length(0)
{
}
// Nomenclature is from DSF file format specification
unsigned int formatVersion;
unsigned int formatID;
unsigned int channelType;
unsigned int channelNum;
unsigned int samplingFrequency;
unsigned int bitsPerSample;
long long sampleCount;
unsigned int blockSizePerChannel;
// Computed
unsigned int bitrate;
unsigned int length;
};
////////////////////////////////////////////////////////////////////////////////
// public members
////////////////////////////////////////////////////////////////////////////////
DSF::Properties::Properties(const ByteVector &data, ReadStyle style) : TagLib::AudioProperties(style)
{
d = new PropertiesPrivate;
read(data);
}
DSF::Properties::~Properties()
{
delete d;
}
int DSF::Properties::length() const
{
return lengthInSeconds();
}
int DSF::Properties::lengthInSeconds() const
{
return d->length / 1000;
}
int DSF::Properties::lengthInMilliseconds() const
{
return d->length;
}
int DSF::Properties::bitrate() const
{
return d->bitrate;
}
int DSF::Properties::sampleRate() const
{
return d->samplingFrequency;
}
int DSF::Properties::channels() const
{
return d->channelNum;
}
// DSF specific
int DSF::Properties::formatVersion() const
{
return d->formatVersion;
}
int DSF::Properties::formatID() const
{
return d->formatID;
}
int DSF::Properties::channelType() const
{
return d->channelType;
}
int DSF::Properties::bitsPerSample() const
{
return d->bitsPerSample;
}
long long DSF::Properties::sampleCount() const
{
return d->sampleCount;
}
int DSF::Properties::blockSizePerChannel() const
{
return d->blockSizePerChannel;
}
////////////////////////////////////////////////////////////////////////////////
// private members
////////////////////////////////////////////////////////////////////////////////
void DSF::Properties::read(const ByteVector &data)
{
d->formatVersion = data.toUInt(0U,false);
d->formatID = data.toUInt(4U,false);
d->channelType = data.toUInt(8U,false);
d->channelNum = data.toUInt(12U,false);
d->samplingFrequency = data.toUInt(16U,false);
d->bitsPerSample = data.toUInt(20U,false);
d->sampleCount = data.toLongLong(24U,false);
d->blockSizePerChannel = data.toUInt(32U,false);
d->bitrate
= static_cast<unsigned int>((d->samplingFrequency * d->bitsPerSample * d->channelNum) / 1000.0 + 0.5);
d->length
= d->samplingFrequency > 0 ? static_cast<unsigned int>(d->sampleCount * 1000.0 / d->samplingFrequency + 0.5) : 0;
}

92
3rdparty/taglib/dsf/dsfproperties.h vendored Normal file
View File

@ -0,0 +1,92 @@
/***************************************************************************
copyright : (C) 2013 by 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_DSFPROPERTIES_H
#define TAGLIB_DSFPROPERTIES_H
#include "audioproperties.h"
namespace TagLib {
namespace DSF {
class File;
//! An implementation of audio property reading for DSF
/*!
* This reads the data from a DSF stream found in the AudioProperties
* API.
*/
class TAGLIB_EXPORT Properties : public TagLib::AudioProperties
{
public:
/*!
* Create an instance of DSF::AudioProperties with the data read from the
* ByteVector \a data.
*/
Properties(const ByteVector &data, ReadStyle style);
/*!
* Destroys this DSF::AudioProperties instance.
*/
virtual ~Properties();
// Reimplementations.
virtual int length() const;
virtual int lengthInSeconds() const;
virtual int lengthInMilliseconds() const;
virtual int bitrate() const;
virtual int sampleRate() const;
virtual int channels() const;
int formatVersion() const;
int formatID() const;
/*!
* Channel type values: 1 = mono, 2 = stereo, 3 = 3 channels,
* 4 = quad, 5 = 4 channels, 6 = 5 channels, 7 = 5.1 channels
*/
int channelType() const;
int bitsPerSample() const;
long long sampleCount() const;
int blockSizePerChannel() const;
private:
Properties(const Properties &);
Properties &operator=(const Properties &);
void read(const ByteVector &data);
class PropertiesPrivate;
PropertiesPrivate *d;
};
}
}
#endif

View File

@ -52,6 +52,8 @@
#include "s3mfile.h"
#include "itfile.h"
#include "xmfile.h"
#include "dsffile.h"
#include "dsdifffile.h"
using namespace TagLib;
@ -135,6 +137,10 @@ namespace
return new IT::File(stream, readAudioProperties, audioPropertiesStyle);
if(ext == "XM")
return new XM::File(stream, readAudioProperties, audioPropertiesStyle);
if(ext == "DFF" || ext == "DSDIFF")
return new DSDIFF::File(stream, readAudioProperties, audioPropertiesStyle);
if(ext == "DSF")
return new DSF::File(stream, readAudioProperties, audioPropertiesStyle);
return 0;
}
@ -174,6 +180,10 @@ namespace
file = new RIFF::WAV::File(stream, readAudioProperties, audioPropertiesStyle);
else if(APE::File::isSupported(stream))
file = new APE::File(stream, readAudioProperties, audioPropertiesStyle);
else if(DSDIFF::File::isSupported(stream))
file = new DSDIFF::File(stream, readAudioProperties, audioPropertiesStyle);
else if(DSF::File::isSupported(stream))
file = new DSF::File(stream, readAudioProperties, audioPropertiesStyle);
// isSupported() only does a quick check, so double check the file here.
@ -255,6 +265,10 @@ namespace
return new IT::File(fileName, readAudioProperties, audioPropertiesStyle);
if(ext == "XM")
return new XM::File(fileName, readAudioProperties, audioPropertiesStyle);
if(ext == "DFF" || ext == "DSDIFF")
return new DSDIFF::File(fileName, readAudioProperties, audioPropertiesStyle);
if(ext == "DSF")
return new DSF::File(fileName, readAudioProperties, audioPropertiesStyle);
return 0;
}
@ -387,6 +401,9 @@ StringList FileRef::defaultFileExtensions()
l.append("s3m");
l.append("it");
l.append("xm");
l.append("dsf");
l.append("dff");
l.append("dsdiff"); // alias for "dff"
return l;
}

View File

@ -216,7 +216,23 @@ void TableOfContentsFrame::removeEmbeddedFrames(const ByteVector &id)
String TableOfContentsFrame::toString() const
{
return String();
String s = String(d->elementID) +
": top level: " + (d->isTopLevel ? "true" : "false") +
", ordered: " + (d->isOrdered ? "true" : "false");
if(!d->childElements.isEmpty()) {
s+= ", chapters: [ " + String(d->childElements.toByteVector(", ")) + " ]";
}
if(!d->embeddedFrameList.isEmpty()) {
StringList frameIDs;
for(FrameList::ConstIterator it = d->embeddedFrameList.begin();
it != d->embeddedFrameList.end(); ++it)
frameIDs.append((*it)->frameID());
s += ", sub-frames: [ " + frameIDs.toString(", ") + " ]";
}
return s;
}
PropertyMap TableOfContentsFrame::asProperties() const

View File

@ -339,7 +339,13 @@ UserTextIdentificationFrame::UserTextIdentificationFrame(const String &descripti
String UserTextIdentificationFrame::toString() const
{
return "[" + description() + "] " + fieldList().toString();
// first entry is the description itself, drop from values list
StringList l = fieldList();
for(StringList::Iterator it = l.begin(); it != l.end(); ++it) {
l.erase(it);
break;
}
return "[" + description() + "] " + l.toString();
}
String UserTextIdentificationFrame::description() const

View File

@ -231,11 +231,21 @@ void Ogg::FLAC::File::scan()
if(!metadataHeader.startsWith("fLaC")) {
// FLAC 1.1.2+
// See https://xiph.org/flac/ogg_mapping.html for the header specification.
if(metadataHeader.size() < 13)
return;
if(metadataHeader[0] != 0x7f)
return;
if(metadataHeader.mid(1, 4) != "FLAC")
return;
if(metadataHeader[5] != 1)
return; // not version 1
if(metadataHeader[5] != 1 && metadataHeader[6] != 0)
return; // not version 1.0
if(metadataHeader.mid(9, 4) != "fLaC")
return;
metadataHeader = metadataHeader.mid(13);
}

View File

@ -63,6 +63,8 @@
#include "itfile.h"
#include "xmfile.h"
#include "mp4file.h"
#include "dsffile.h"
#include "dsdifffile.h"
using namespace TagLib;
@ -148,6 +150,10 @@ PropertyMap File::properties() const
return dynamic_cast<const MP4::File* >(this)->properties();
if(dynamic_cast<const ASF::File* >(this))
return dynamic_cast<const ASF::File* >(this)->properties();
if(dynamic_cast<const DSF::File* >(this))
return dynamic_cast<const DSF::File* >(this)->properties();
if(dynamic_cast<const DSDIFF::File* >(this))
return dynamic_cast<const DSDIFF::File* >(this)->properties();
return tag()->properties();
}
@ -177,6 +183,10 @@ void File::removeUnsupportedProperties(const StringList &properties)
dynamic_cast<MP4::File* >(this)->removeUnsupportedProperties(properties);
else if(dynamic_cast<ASF::File* >(this))
dynamic_cast<ASF::File* >(this)->removeUnsupportedProperties(properties);
else if(dynamic_cast<DSF::File* >(this))
dynamic_cast<DSF::File* >(this)->removeUnsupportedProperties(properties);
else if(dynamic_cast<DSDIFF::File* >(this))
dynamic_cast<DSDIFF::File* >(this)->removeUnsupportedProperties(properties);
else
tag()->removeUnsupportedProperties(properties);
}
@ -219,6 +229,10 @@ PropertyMap File::setProperties(const PropertyMap &properties)
return dynamic_cast<MP4::File* >(this)->setProperties(properties);
else if(dynamic_cast<ASF::File* >(this))
return dynamic_cast<ASF::File* >(this)->setProperties(properties);
else if(dynamic_cast<DSF::File* >(this))
return dynamic_cast<DSF::File* >(this)->setProperties(properties);
else if(dynamic_cast<DSDIFF::File* >(this))
return dynamic_cast<DSDIFF::File* >(this)->setProperties(properties);
else
return tag()->setProperties(properties);
}

View File

@ -58,6 +58,11 @@ namespace
#endif
}
FileHandle openFile(const int fileDescriptor, bool readOnly)
{
return InvalidFileHandle;
}
void closeFile(FileHandle file)
{
CloseHandle(file);
@ -98,6 +103,11 @@ namespace
return fopen(path, readOnly ? "rb" : "rb+");
}
FileHandle openFile(const int fileDescriptor, bool readOnly)
{
return fdopen(fileDescriptor, readOnly ? "rb" : "rb+");
}
void closeFile(FileHandle file)
{
fclose(file);
@ -149,13 +159,28 @@ FileStream::FileStream(FileName fileName, bool openReadOnly)
d->file = openFile(fileName, true);
if(d->file == InvalidFileHandle)
{
# ifdef _WIN32
debug("Could not open file " + fileName.toString());
# else
debug("Could not open file " + String(static_cast<const char *>(d->name)));
# endif
}
}
FileStream::FileStream(int fileDescriptor, bool openReadOnly)
: d(new FileStreamPrivate(""))
{
// First try with read / write mode, if that fails, fall back to read only.
if(!openReadOnly)
d->file = openFile(fileDescriptor, false);
if(d->file != InvalidFileHandle)
d->readOnly = false;
else
d->file = openFile(fileDescriptor, true);
if(d->file == InvalidFileHandle)
debug("Could not open file using file descriptor");
}
FileStream::~FileStream()
@ -255,8 +280,7 @@ void FileStream::insert(const ByteVector &data, unsigned long start, unsigned lo
ByteVector buffer = data;
ByteVector aboutToOverwrite(static_cast<unsigned int>(bufferLength));
while(true)
{
while(true) {
// Seek to the current read position and read the data that we're about
// to overwrite. Appropriately increment the readPosition.
@ -304,8 +328,7 @@ void FileStream::removeBlock(unsigned long start, unsigned long length)
ByteVector buffer(static_cast<unsigned int>(bufferLength));
for(unsigned int bytesRead = -1; bytesRead != 0;)
{
for(unsigned int bytesRead = -1; bytesRead != 0;) {
seek(readPosition);
bytesRead = static_cast<unsigned int>(readFile(d->file, buffer));
readPosition += bytesRead;
@ -401,7 +424,8 @@ long FileStream::tell() const
const LARGE_INTEGER zero = {};
LARGE_INTEGER position;
if(SetFilePointerEx(d->file, zero, &position, FILE_CURRENT) && position.QuadPart <= LONG_MAX) {
if(SetFilePointerEx(d->file, zero, &position, FILE_CURRENT) &&
position.QuadPart <= LONG_MAX) {
return static_cast<long>(position.QuadPart);
}
else {
@ -470,9 +494,8 @@ void FileStream::truncate(long length)
#else
const int error = ftruncate(fileno(d->file), length);
if(error != 0) {
if(error != 0)
debug("FileStream::truncate() -- Coundn't truncate the file.");
}
#endif
}

View File

@ -54,6 +54,11 @@ namespace TagLib {
*/
FileStream(FileName file, bool openReadOnly = false);
/*!
* Construct a File object and opens the \a file using file descriptor.
*/
FileStream(int fileDescriptor, bool openReadOnly = false);
/*!
* Destroys this FileStream instance.
*/

View File

@ -94,7 +94,13 @@ find_path(LASTFM51_INCLUDE_DIRS lastfm5/Track.h)
find_path(SPARSEHASH_INCLUDE_DIRS google/sparsetable)
# Only use system taglib if it's greater than 1.11.1 because of audio file detection by content.
# Only use system taglib if it's greater than 1.11.1
# There is a bug in version 1.11.1 corrupting Ogg files, see: https://github.com/taglib/taglib/issues/864
# If you decide to use the systems taglib, make sure it has been patched with the following commit:
# https://github.com/taglib/taglib/commit/9336c82da3a04552168f208cd7a5fa4646701ea4
# The current taglib in 3rdparty also has the following features used by Clementine:
# - Audio file detection by content.
#
if (TAGLIB_VERSION VERSION_GREATER 1.11.1 OR WIN32)
option(USE_SYSTEM_TAGLIB "Use system taglib" ON)
else()
@ -102,7 +108,11 @@ else()
endif()
if (TAGLIB_FOUND AND USE_SYSTEM_TAGLIB)
message(STATUS "Using system taglib library")
if (TAGLIB_VERSION VERSION_GREATER 1.11.1 OR WIN32)
message(STATUS "Using system taglib library")
else()
message(WARNING "Using system taglib library. Version 1.11.1 or less has a bug corrupting Ogg files, make sure your systems version has been patched!")
endif()
set(CMAKE_REQUIRED_INCLUDES "${TAGLIB_INCLUDE_DIRS}")
set(CMAKE_REQUIRED_LIBRARIES "${TAGLIB_LIBRARIES}")
check_cxx_source_compiles("#include <opusfile.h>
@ -110,7 +120,7 @@ if (TAGLIB_FOUND AND USE_SYSTEM_TAGLIB)
set(CMAKE_REQUIRED_INCLUDES)
set(CMAKE_REQUIRED_LIBRARIES)
else()
message(STATUS "Using builtin taglib because your system's version is too old")
message(STATUS "Using builtin taglib library")
set(TAGLIB_VERSION 1.11.1)
set(TAGLIB_INCLUDE_DIRS "${CMAKE_BINARY_DIR}/3rdparty/taglib/headers/taglib/;${CMAKE_BINARY_DIR}/3rdparty/taglib/headers/")
set(TAGLIB_LIBRARY_DIRS "")

View File

@ -22,7 +22,7 @@ Please:
Please:
* Try the latest developer build (http://builds.clementine-player.org/) to see if the bug is still present (**Attention**, those builds aren't stable so they might not work well and could sometimes break things like user settings). If it works like a charm even though you see an open issue, please comment on it and explain that the issue has been fixed
* Try the latest developer build (http://builds.clementine-player.org/) to see if any bug is still present (**Attention**, those builds aren't stable so they might not work well and could sometimes break things like user settings). If it works fine even though you see an open issue, please comment on it and explain that the issue has been fixed
* Check if another person has already opened the same issue to avoid duplicates
* If there already is an open issue you could comment on it to add precisions about the problem or confirm it
* In case there isn't, you can open a new issue with an explicit title and as much information as possible (OS, Clementine version, how to reproduce the problem...)

2
dist/CMakeLists.txt vendored
View File

@ -66,7 +66,7 @@ if (NOT APPLE)
)
install(FILES clementine.appdata.xml
DESTINATION share/appdata
DESTINATION share/metainfo
)
if(INSTALL_UBUNTU_ICONS)

View File

@ -270,6 +270,7 @@ set(SOURCES
internet/podcasts/podcastdeleter.cpp
internet/podcasts/podcastdownloader.cpp
internet/podcasts/podcastepisode.cpp
internet/podcasts/podcastinfodialog.cpp
internet/podcasts/podcastinfowidget.cpp
internet/podcasts/podcastservice.cpp
internet/podcasts/podcastservicemodel.cpp
@ -565,6 +566,7 @@ set(HEADERS
internet/podcasts/podcastdiscoverymodel.h
internet/podcasts/podcastdeleter.h
internet/podcasts/podcastdownloader.h
internet/podcasts/podcastinfodialog.h
internet/podcasts/podcastinfowidget.h
internet/podcasts/podcastservice.h
internet/podcasts/podcastservicemodel.h
@ -711,6 +713,7 @@ set(UI
internet/podcasts/addpodcastdialog.ui
internet/podcasts/gpoddersearchpage.ui
internet/podcasts/itunessearchpage.ui
internet/podcasts/podcastinfodialog.ui
internet/podcasts/podcastinfowidget.ui
internet/podcasts/podcastsettingspage.ui

View File

@ -24,6 +24,7 @@
#include "analyzerbase.h"
#include <algorithm>
#include <cmath>
#include <cstdint>
@ -87,9 +88,9 @@ void Analyzer::Base::transform(Scope& scope) {
QVector<float> aux(fht_->size());
if (aux.size() >= scope.size()) {
qCopy(scope.begin(), scope.end(), aux.begin());
std::copy(scope.begin(), scope.end(), aux.begin());
} else {
qCopy(scope.begin(), scope.begin() + aux.size(), aux.begin());
std::copy(scope.begin(), scope.begin() + aux.size(), aux.begin());
}
fht_->logSpectrum(scope.data(), aux.data());
@ -119,7 +120,7 @@ void Analyzer::Base::paintEvent(QPaintEvent* e) {
transform(lastScope_);
analyze(p, lastScope_, new_frame_);
// scope.resize( fht_->size() );
lastScope_.resize(fht_->size());
break;
}

View File

@ -20,6 +20,8 @@
#ifndef CORE_CACHEDLIST_H_
#define CORE_CACHEDLIST_H_
#include <algorithm>
#include <QDateTime>
#include <QSettings>
@ -81,7 +83,7 @@ class CachedList {
cache_duration_secs_;
}
void Sort() { qSort(data_); }
void Sort() { std::sort(data_.begin(), data_.end()); }
const ListType& Data() const { return data_; }
operator ListType() const { return data_; }

View File

@ -620,7 +620,9 @@ void Player::InvalidSongRequested(const QUrl& url) {
bool stop_playback = s.value("stop_play_if_fail", 0).toBool();
s.endGroup();
if (!stop_playback) {
if (stop_playback) {
Stop();
} else {
NextItem(Engine::Auto);
}
}

View File

@ -486,7 +486,7 @@ int CompareSongsName(const Song& song1, const Song& song2) {
void Song::SortSongsListAlphabetically(SongList* songs) {
Q_ASSERT(songs);
qSort(songs->begin(), songs->end(), CompareSongsName);
std::sort(songs->begin(), songs->end(), CompareSongsName);
}
void Song::Init(const QString& title, const QString& artist,

View File

@ -22,6 +22,7 @@
#include "songloader.h"
#include <algorithm>
#include <memory>
#include <QBuffer>
@ -311,7 +312,7 @@ void SongLoader::LoadLocalDirectory(const QString& filename) {
LoadLocalPartial(it.next());
}
qStableSort(songs_.begin(), songs_.end(), CompareSongs);
std::stable_sort(songs_.begin(), songs_.end(), CompareSongs);
// Load the first song: all songs will be loaded async, but we want the first
// one in our list to be fully loaded, so if the user has the "Start playing

View File

@ -20,6 +20,7 @@
#include "albumcoverfetchersearch.h"
#include <algorithm>
#include <cmath>
#include <QMutexLocker>
@ -138,7 +139,7 @@ void AlbumCoverFetcherSearch::AllProvidersFinished() {
// from each category and use some heuristics to score them. If no images
// are good enough we'll keep loading more images until we find one that is
// or we run out of results.
qStableSort(results_.begin(), results_.end(), CompareProviders);
std::stable_sort(results_.begin(), results_.end(), CompareProviders);
FetchMoreImages();
}

View File

@ -21,6 +21,8 @@
#include "ui_coversearchstatisticsdialog.h"
#include "core/utilities.h"
#include <algorithm>
CoverSearchStatisticsDialog::CoverSearchStatisticsDialog(QWidget* parent)
: QDialog(parent), ui_(new Ui_CoverSearchStatisticsDialog) {
ui_->setupUi(this);
@ -47,7 +49,7 @@ CoverSearchStatisticsDialog::~CoverSearchStatisticsDialog() { delete ui_; }
void CoverSearchStatisticsDialog::Show(
const CoverSearchStatistics& statistics) {
QStringList providers(statistics.total_images_by_provider_.keys());
qSort(providers);
std::sort(providers.begin(), providers.end());
ui_->summary->setText(
tr("Got %1 covers out of %2 (%3 failed)")

View File

@ -22,6 +22,8 @@
#include "ui/settingsdialog.h"
#include "ui_globalsearchsettingspage.h"
#include <algorithm>
#include <QSettings>
GlobalSearchSettingsPage::GlobalSearchSettingsPage(SettingsDialog* dialog)
@ -57,7 +59,7 @@ void GlobalSearchSettingsPage::Load() {
// Sort the list of providers alphabetically (by id) initially, so any that
// aren't in the provider_order list will take this order.
qSort(providers.begin(), providers.end(), CompareProviderId);
std::sort(providers.begin(), providers.end(), CompareProviderId);
// Add the ones in the configured list first
ui_->sources->clear();

View File

@ -22,6 +22,7 @@
#include <QStandardItem>
#include <QTimer>
#include <algorithm>
#include <functional>
#include "globalsearch.h"
@ -224,8 +225,8 @@ void GlobalSearchView::ReloadSettings() {
if (show_providers_) {
// Sort the list of providers
QList<SearchProvider*> providers = engine_->providers();
qSort(providers.begin(), providers.end(),
std::bind(&CompareProvider, std::cref(provider_order), _1, _2));
std::sort(providers.begin(), providers.end(),
std::bind(&CompareProvider, std::cref(provider_order), _1, _2));
bool any_disabled = false;
@ -307,8 +308,8 @@ void GlobalSearchView::AddResults(int id,
void GlobalSearchView::SwapModels() {
art_requests_.clear();
qSwap(front_model_, back_model_);
qSwap(front_proxy_, back_proxy_);
std::swap(front_model_, back_model_);
std::swap(front_proxy_, back_proxy_);
ui_->results->setModel(front_proxy_);

View File

@ -20,6 +20,8 @@
#include "digitallyimportedservicebase.h"
#include <algorithm>
#include <QDesktopServices>
#include <QMenu>
#include <QNetworkReply>
@ -112,7 +114,7 @@ void DigitallyImportedServiceBase::RefreshStreamsFinished(QNetworkReply* reply,
// Parse the list and sort by name
DigitallyImportedClient::ChannelList channels =
api_client_->ParseChannelList(reply);
qSort(channels);
std::sort(channels.begin(), channels.end());
saved_channels_.Update(channels);

View File

@ -32,6 +32,8 @@
#include "lastfmservice.h"
#include <algorithm>
#include <QCryptographicHash>
#include <QDesktopServices>
#include <QMenu>
@ -132,7 +134,7 @@ bool LastFMService::IsSubscriber() const {
namespace {
QByteArray SignApiRequest(QList<QPair<QString, QString>> params) {
qSort(params);
std::sort(params.begin(), params.end());
QString to_sign;
for (const auto& p : params) {
to_sign += p.first;

View File

@ -0,0 +1,39 @@
/* This file is part of Clementine.
Copyright 2018, Jim Broadus <jbroadus@gmail.com>
Clementine is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
Clementine is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with Clementine. If not, see <http://www.gnu.org/licenses/>.
*/
#include "podcastinfodialog.h"
#include "ui_podcastinfodialog.h"
PodcastInfoDialog::PodcastInfoDialog(Application* app, QWidget* parent)
: QDialog(parent),
app_(app),
ui_(new Ui_PodcastInfoDialog) {
ui_->setupUi(this);
ui_->details->SetApplication(app);
}
PodcastInfoDialog::~PodcastInfoDialog() {
delete ui_;
}
void PodcastInfoDialog::ShowPodcast(const Podcast& podcast) {
show();
ui_->podcast_url->setText(podcast.url().toString());
ui_->podcast_url->setReadOnly(true);
ui_->details->SetPodcast(podcast);
}

View File

@ -0,0 +1,42 @@
/* This file is part of Clementine.
Copyright 2018, Jim Broadus <jbroadus@gmail.com>
Clementine is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
Clementine is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with Clementine. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef INTERNET_PODCASTS_PODCASTINFODIALOG_H_
#define INTERNET_PODCASTS_PODCASTINFODIALOG_H_
#include <QDialog>
class Application;
class Podcast;
class Ui_PodcastInfoDialog;
class PodcastInfoDialog : public QDialog {
Q_OBJECT
public:
explicit PodcastInfoDialog(Application* app, QWidget* parent = nullptr);
~PodcastInfoDialog();
void ShowPodcast(const Podcast& podcast);
private:
Application* app_;
Ui_PodcastInfoDialog* ui_;
};
#endif // INTERNET_PODCASTS_PODCASTINFODIALOG_H_

View File

@ -0,0 +1,107 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>PodcastInfoDialog</class>
<widget class="QDialog" name="PodcastInfoDialog">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>493</width>
<height>395</height>
</rect>
</property>
<property name="windowTitle">
<string>Podcast Information</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout_3">
<item>
<widget class="QLineEdit" name="podcast_url"/>
</item>
<item>
<widget class="QScrollArea" name="details_scroll_area">
<property name="minimumSize">
<size>
<width>250</width>
<height>100</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>16777215</width>
<height>16777215</height>
</size>
</property>
<property name="horizontalScrollBarPolicy">
<enum>Qt::ScrollBarAlwaysOff</enum>
</property>
<property name="widgetResizable">
<bool>true</bool>
</property>
<widget class="PodcastInfoWidget" name="details">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>473</width>
<height>313</height>
</rect>
</property>
</widget>
</widget>
</item>
<item>
<widget class="QDialogButtonBox" name="buttonBox">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="standardButtons">
<set>QDialogButtonBox::Close</set>
</property>
</widget>
</item>
</layout>
</widget>
<customwidgets>
<customwidget>
<class>PodcastInfoWidget</class>
<extends>QWidget</extends>
<header>internet/podcasts/podcastinfowidget.h</header>
<container>1</container>
</customwidget>
</customwidgets>
<resources/>
<connections>
<connection>
<sender>buttonBox</sender>
<signal>accepted()</signal>
<receiver>PodcastInfoDialog</receiver>
<slot>accept()</slot>
<hints>
<hint type="sourcelabel">
<x>248</x>
<y>254</y>
</hint>
<hint type="destinationlabel">
<x>157</x>
<y>274</y>
</hint>
</hints>
</connection>
<connection>
<sender>buttonBox</sender>
<signal>rejected()</signal>
<receiver>PodcastInfoDialog</receiver>
<slot>reject()</slot>
<hints>
<hint type="sourcelabel">
<x>316</x>
<y>260</y>
</hint>
<hint type="destinationlabel">
<x>286</x>
<y>274</y>
</hint>
</hints>
</connection>
</connections>
</ui>

View File

@ -26,6 +26,7 @@
#include <QtConcurrentRun>
#include "addpodcastdialog.h"
#include "podcastinfodialog.h"
#include "core/application.h"
#include "core/logging.h"
#include "core/mergedproxymodel.h"
@ -430,6 +431,11 @@ void PodcastService::ShowContextMenu(const QPoint& global_pos) {
download_selected_action_ =
context_menu_->addAction(IconLoader::Load("download", IconLoader::Base),
"", this, SLOT(DownloadSelectedEpisode()));
info_selected_action_ =
context_menu_->addAction(IconLoader::Load("about-info",
IconLoader::Base),
tr("Podcast information"), this,
SLOT(PodcastInfo()));
delete_downloaded_action_ = context_menu_->addAction(
IconLoader::Load("edit-delete", IconLoader::Base),
tr("Delete downloaded data"), this, SLOT(DeleteDownloadedData()));
@ -521,6 +527,12 @@ void PodcastService::ShowContextMenu(const QPoint& global_pos) {
delete_downloaded_action_->setEnabled(episodes);
}
if (selected_podcasts_.count() == 1) {
info_selected_action_->setEnabled(true);
} else {
info_selected_action_->setEnabled(false);
}
if (explicitly_selected_podcasts_.isEmpty() && selected_episodes_.isEmpty()) {
PodcastEpisodeList epis = backend_->GetNewDownloadedEpisodes();
set_listened_action_->setEnabled(!epis.isEmpty());
@ -686,6 +698,15 @@ void PodcastService::DownloadSelectedEpisode() {
}
}
void PodcastService::PodcastInfo() {
if (selected_podcasts_.count() > 0) {
const Podcast podcast =
selected_podcasts_[0].data(Role_Podcast).value<Podcast>();
podcast_info_dialog_.reset(new PodcastInfoDialog(app_));
podcast_info_dialog_->ShowPodcast(podcast);
}
}
void PodcastService::DeleteDownloadedData() {
for (const QModelIndex& index : selected_episodes_) {
app_->podcast_deleter()->DeleteEpisode(

View File

@ -30,6 +30,7 @@
#include <QScopedPointer>
class AddPodcastDialog;
class PodcastInfoDialog;
class OrganiseDialog;
class Podcast;
class PodcastBackend;
@ -76,6 +77,7 @@ class PodcastService : public InternetService {
void ReloadPodcast(const Podcast& podcast);
void RemoveSelectedPodcast();
void DownloadSelectedEpisode();
void PodcastInfo();
void DeleteDownloadedData();
void SetNew();
void SetListened();
@ -148,6 +150,7 @@ class PodcastService : public InternetService {
QAction* update_selected_action_;
QAction* remove_selected_action_;
QAction* download_selected_action_;
QAction* info_selected_action_;
QAction* delete_downloaded_action_;
QAction* set_new_action_;
QAction* set_listened_action_;
@ -164,6 +167,7 @@ class PodcastService : public InternetService {
QMap<int, QStandardItem*> episodes_by_database_id_;
std::unique_ptr<AddPodcastDialog> add_podcast_dialog_;
std::unique_ptr<PodcastInfoDialog> podcast_info_dialog_;
};
#endif // INTERNET_PODCASTS_PODCASTSERVICE_H_

View File

@ -17,6 +17,7 @@
#include "librarymodel.h"
#include <algorithm>
#include <functional>
#include <QFuture>
@ -659,7 +660,7 @@ QVariant LibraryModel::data(const LibraryItem* item, int role) const {
return item->SortText();
case Role_DisplayText:
return item->DisplayText();
return item->DisplayText();
}
return QVariant();
}
@ -1269,8 +1270,8 @@ void LibraryModel::GetChildSongs(LibraryItem* item, QList<QUrl>* urls,
const_cast<LibraryModel*>(this)->LazyPopulate(item);
QList<LibraryItem*> children = item->children;
qSort(children.begin(), children.end(),
std::bind(&LibraryModel::CompareItems, this, _1, _2));
std::sort(children.begin(), children.end(),
std::bind(&LibraryModel::CompareItems, this, _1, _2));
for (LibraryItem* child : children)
GetChildSongs(child, urls, songs, song_ids);

View File

@ -175,8 +175,8 @@ LibraryView::LibraryView(QWidget* parent)
total_song_count_(-1),
context_menu_(nullptr),
is_in_keyboard_search_(false) {
QIcon nocover = IconLoader::Load("nocover", IconLoader::Other);
nomusic_ = nocover.pixmap(nocover.availableSizes().last());
QIcon nomusic = IconLoader::Load("nomusic", IconLoader::Other);
nomusic_ = nomusic.pixmap(nomusic.availableSizes().last());
setItemDelegate(new LibraryItemDelegate(this));
setAttribute(Qt::WA_MacShowFocusRect, false);
setHeaderHidden(true);

View File

@ -17,6 +17,8 @@
#include "acoustidclient.h"
#include <algorithm>
#include <QCoreApplication>
#include <QNetworkReply>
#include <QStringList>
@ -138,7 +140,7 @@ void AcoustidClient::RequestFinished(QNetworkReply* reply, int request_id) {
}
}
qStableSort(id_source_list);
std::stable_sort(id_source_list.begin(), id_source_list.end());
QList<QString> id_list;
for (const IdSource& is : id_source_list) {

View File

@ -17,6 +17,8 @@
#include "musicbrainzclient.h"
#include <algorithm>
#include <QCoreApplication>
#include <QNetworkReply>
#include <QSet>
@ -212,7 +214,7 @@ void MusicBrainzClient::RequestFinished(QNetworkReply* reply, int id,
// Merge the results we have
ResultList ret;
QList<PendingResults> result_list_list = pending_results_.take(id);
qSort(result_list_list);
std::sort(result_list_list.begin(), result_list_list.end());
for (const PendingResults& result_list : result_list_list) {
ret << result_list.results_;
}
@ -317,7 +319,7 @@ MusicBrainzClient::ResultList MusicBrainzClient::ParseTrack(
if (releases.isEmpty()) {
ret << result;
} else {
qStableSort(releases);
std::stable_sort(releases.begin(), releases.end());
for (const Release& release : releases) {
ret << release.CopyAndMergeInto(result);
}
@ -386,7 +388,7 @@ MusicBrainzClient::ResultList MusicBrainzClient::UniqueResults(
ResultList ret;
if (opt == SortResults) {
ret = QSet<Result>::fromList(results).toList();
qSort(ret);
std::sort(ret.begin(), ret.end());
} else { // KeepOriginalOrder
// Qt doesn't provide a ordered set (QSet "stores values in an unspecified
// order" according to Qt documentation).

View File

@ -35,36 +35,37 @@
#include <QtConcurrentRun>
#include <QtDebug>
#include "core/application.h"
#include "core/closure.h"
#include "core/logging.h"
#include "core/player.h"
#include "core/tagreaderclient.h"
#include "core/timeconstants.h"
#include "internet/core/internetmimedata.h"
#include "internet/core/internetmodel.h"
#include "internet/core/internetplaylistitem.h"
#include "internet/core/internetsongmimedata.h"
#include "internet/internetradio/savedradio.h"
#include "internet/jamendo/jamendoplaylistitem.h"
#include "internet/jamendo/jamendoservice.h"
#include "internet/magnatune/magnatuneplaylistitem.h"
#include "internet/magnatune/magnatuneservice.h"
#include "library/library.h"
#include "library/librarybackend.h"
#include "library/librarymodel.h"
#include "library/libraryplaylistitem.h"
#include "playlistbackend.h"
#include "playlistfilter.h"
#include "playlistitemmimedata.h"
#include "playlistundocommands.h"
#include "playlistview.h"
#include "queue.h"
#include "songloaderinserter.h"
#include "songmimedata.h"
#include "songplaylistitem.h"
#include "core/application.h"
#include "core/closure.h"
#include "core/logging.h"
#include "core/tagreaderclient.h"
#include "core/timeconstants.h"
#include "internet/jamendo/jamendoplaylistitem.h"
#include "internet/jamendo/jamendoservice.h"
#include "internet/magnatune/magnatuneplaylistitem.h"
#include "internet/magnatune/magnatuneservice.h"
#include "internet/core/internetmimedata.h"
#include "internet/core/internetmodel.h"
#include "internet/core/internetplaylistitem.h"
#include "internet/core/internetsongmimedata.h"
#include "internet/internetradio/savedradio.h"
#include "library/library.h"
#include "library/librarybackend.h"
#include "library/librarymodel.h"
#include "library/libraryplaylistitem.h"
#include "smartplaylists/generator.h"
#include "smartplaylists/generatorinserter.h"
#include "smartplaylists/generatormimedata.h"
#include "songloaderinserter.h"
#include "songmimedata.h"
#include "songplaylistitem.h"
using std::placeholders::_1;
using std::placeholders::_2;
@ -155,6 +156,22 @@ Playlist::Playlist(PlaylistBackend* backend, TaskManager* task_manager,
connect(queue_, SIGNAL(layoutChanged()), SLOT(QueueLayoutChanged()));
column_alignments_ = PlaylistView::DefaultColumnAlignment();
min_play_count_point_nsecs_ = (31ll * kNsecPerSec); // 30 seconds
QSettings settings;
settings.beginGroup(Player::kSettingsGroup);
if (settings.value("play_count_short_duration").toBool()) {
max_play_count_point_nsecs_ = (60ll * kNsecPerSec); // 1 minute
} else {
max_play_count_point_nsecs_ = (240ll * kNsecPerSec); // 4 minutes
}
settings.endGroup();
qLog(Debug) << "k_max_scrobble_point"
<< (max_play_count_point_nsecs_ / kNsecPerSec);
}
Playlist::~Playlist() {
@ -811,7 +828,8 @@ bool Playlist::dropMimeData(const QMimeData* data, Qt::DropAction action,
pid = !own_pid;
}
qStableSort(source_rows); // Make sure we take them in order
std::stable_sort(source_rows.begin(),
source_rows.end()); // Make sure we take them in order
if (source_playlist == this) {
// Dragged from this playlist - rearrange the items
@ -1463,25 +1481,25 @@ void Playlist::sort(int column, Qt::SortOrder order) {
if (column == Column_Album) {
// When sorting by album, also take into account discs and tracks.
qStableSort(begin, new_items.end(),
std::bind(&Playlist::CompareItems, Column_Track, order, _1, _2,
prefixes));
qStableSort(begin, new_items.end(),
std::bind(&Playlist::CompareItems, Column_Disc, order, _1, _2,
prefixes));
qStableSort(begin, new_items.end(),
std::bind(&Playlist::CompareItems, Column_Album, order, _1, _2,
prefixes));
std::stable_sort(begin, new_items.end(),
std::bind(&Playlist::CompareItems, Column_Track, order, _1,
_2, prefixes));
std::stable_sort(begin, new_items.end(),
std::bind(&Playlist::CompareItems, Column_Disc, order, _1,
_2, prefixes));
std::stable_sort(begin, new_items.end(),
std::bind(&Playlist::CompareItems, Column_Album, order, _1,
_2, prefixes));
} else if (column == Column_Filename) {
// When sorting by full paths we also expect a hierarchical order. This
// returns a breath-first ordering of paths.
qStableSort(begin, new_items.end(),
std::bind(&Playlist::CompareItems, Column_Filename, order, _1,
_2, prefixes));
qStableSort(begin, new_items.end(),
std::bind(&Playlist::ComparePathDepths, order, _1, _2));
std::stable_sort(begin, new_items.end(),
std::bind(&Playlist::CompareItems, Column_Filename, order,
_1, _2, prefixes));
std::stable_sort(begin, new_items.end(),
std::bind(&Playlist::ComparePathDepths, order, _1, _2));
} else {
qStableSort(
std::stable_sort(
begin, new_items.end(),
std::bind(&Playlist::CompareItems, column, order, _1, _2, prefixes));
}
@ -1617,7 +1635,7 @@ void Playlist::RemoveItemsWithoutUndo(const QList<int>& indicesIn) {
// Sort the indices descending because removing elements 'backwards'
// is easier - indices don't 'move' in the process.
QList<int> indices = indicesIn;
qSort(indices.begin(), indices.end(), DescendingIntLessThan);
std::sort(indices.begin(), indices.end(), DescendingIntLessThan);
for (int j = 0; j < indices.count(); j++) {
int beginning = indices[j], end = indices[j];
@ -1660,7 +1678,7 @@ bool Playlist::removeRows(QList<int>& rows) {
// start from the end to be sure that indices won't 'move' during
// the removal process
qSort(rows.begin(), rows.end(), qGreater<int>());
std::sort(rows.begin(), rows.end(), std::greater<int>());
QList<int> part;
while (!rows.isEmpty()) {
@ -1801,6 +1819,15 @@ Song Playlist::current_item_metadata() const {
return current_item()->Metadata();
}
/**
* Last.fm defines a track to be scrobbled when
* - the track is longer than 30 seconds
* - the track has been played for at least half its duration, or for 4 minutes
* (whichever occurs earlier.)
*
* If you seek a track, the scrobble point is recalculated from the point seeked
* to (as 50% or 4 minutes).
*/
void Playlist::UpdateScrobblePoint(qint64 seek_point_nanosec) {
const qint64 length = current_item_metadata().length_nanosec();
@ -1823,6 +1850,41 @@ void Playlist::UpdateScrobblePoint(qint64 seek_point_nanosec) {
}
set_lastfm_status(LastFM_New);
UpdatePlayCountPoint(seek_point_nanosec);
}
/**
* Initially the play count tracking and scrobbling went hand in hand.
* However, it is is possible that someone's preferences for tracking play
* counts are more relaxed than that of scrobbling. For those cases, we use
* the following algorithm to track whether a song should increment it's play
* count or not.
*
* Note that that this is very similar to the scrobbling algorithm with the only
* difference that the requirement of 4 mins worth of listening is now
* configurable via the `max_play_count_point_nsecs_` parameter.
*/
void Playlist::UpdatePlayCountPoint(qint64 seek_point_nanosec) {
const qint64 length = current_item_metadata().length_nanosec();
if (seek_point_nanosec == 0) {
if (length == 0) {
play_count_point_ = max_play_count_point_nsecs_; // 4 minutes
} else {
play_count_point_ = qBound(min_play_count_point_nsecs_, length / 2,
max_play_count_point_nsecs_);
}
} else {
if (length == 0) {
play_count_point_ = seek_point_nanosec + max_play_count_point_nsecs_;
} else {
play_count_point_ =
qBound(seek_point_nanosec + min_play_count_point_nsecs_,
seek_point_nanosec + (length / 2),
seek_point_nanosec + max_play_count_point_nsecs_);
}
}
have_incremented_playcount_ = false;
}

View File

@ -236,6 +236,16 @@ class Playlist : public QAbstractListModel {
void set_have_incremented_playcount() { have_incremented_playcount_ = true; }
void UpdateScrobblePoint(qint64 seek_point_nanosec = 0);
// play count tracking
qint64 play_count_point_nanosec() const { return play_count_point_; }
void set_max_play_count_point_nsecs(qint64 max_play_count_point_nsecs) {
max_play_count_point_nsecs_ = max_play_count_point_nsecs;
}
qint64 get_max_play_count_point_nsecs() const {
return max_play_count_point_nsecs_;
}
void UpdatePlayCountPoint(qint64 seek_point_nanosec = 0);
// Changing the playlist
void InsertItems(const PlaylistItemList& items, int pos = -1,
bool play_now = false, bool enqueue = false,
@ -440,6 +450,8 @@ signals:
LastFMStatus lastfm_status_;
bool have_incremented_playcount_;
qint64 play_count_point_;
PlaylistSequence* playlist_sequence_;
// Hack to stop QTreeView::setModel sorting the playlist
@ -454,6 +466,9 @@ signals:
QString special_type_;
qint64 min_play_count_point_nsecs_;
qint64 max_play_count_point_nsecs_;
// Cancel async restore if songs are already replaced
bool cancel_restore_;
};

View File

@ -15,13 +15,15 @@
along with Clementine. If not, see <http://www.gnu.org/licenses/>.
*/
#include "core/appearance.h"
#include "core/logging.h"
#include "core/player.h"
#include "core/timeconstants.h"
#include "playlistcontainer.h"
#include "playlistmanager.h"
#include "ui_playlistcontainer.h"
#include "core/logging.h"
#include "core/appearance.h"
#include "playlistparsers/playlistparser.h"
#include "ui/iconloader.h"
#include "ui_playlistcontainer.h"
#include <QAction>
#include <QFileDialog>
@ -467,4 +469,16 @@ void PlaylistContainer::ReloadSettings() {
settings.beginGroup(Appearance::kSettingsGroup);
bool hide_toolbar = settings.value("b_hide_filter_toolbar", false).toBool();
ui_->toolbar->setVisible(!hide_toolbar);
settings.endGroup();
settings.beginGroup(Player::kSettingsGroup);
if (settings.value("play_count_short_duration").toBool()) {
playlist_->set_max_play_count_point_nsecs(60ll * kNsecPerSec);
} else {
playlist_->set_max_play_count_point_nsecs(240ll * kNsecPerSec);
}
settings.endGroup();
qLog(Debug) << "new max scrobble point:"
<< (playlist_->get_max_play_count_point_nsecs() / kNsecPerSec);
}

View File

@ -43,6 +43,7 @@
#include <QMimeData>
#include <math.h>
#include <algorithm>
#ifdef HAVE_MOODBAR
#include "moodbar/moodbaritemdelegate.h"
@ -657,7 +658,7 @@ void PlaylistView::RemoveSelected(bool deleting_from_disk) {
// Sort the selection so we remove the items at the *bottom* first, ensuring
// we don't have to mess around with changing row numbers
qSort(selection.begin(), selection.end(), CompareSelectionRanges);
std::sort(selection.begin(), selection.end(), CompareSelectionRanges);
for (const QItemSelectionRange& range : selection) {
if (range.top() < last_row) rows_removed += range.height();
@ -700,7 +701,7 @@ QList<int> PlaylistView::GetEditableColumns() {
QModelIndex index = model()->index(0, col);
if (index.flags() & Qt::ItemIsEditable) columns << h->visualIndex(col);
}
qSort(columns);
std::sort(columns.begin(), columns.end());
return columns;
}

View File

@ -17,6 +17,8 @@
#include "queue.h"
#include <algorithm>
#include <QBuffer>
#include <QMimeData>
#include <QtDebug>
@ -317,7 +319,8 @@ bool Queue::dropMimeData(const QMimeData* data, Qt::DropAction action, int row,
QList<int> proxy_rows;
QDataStream stream(data->data(kRowsMimetype));
stream >> proxy_rows;
qStableSort(proxy_rows); // Make sure we take them in order
// Make sure we take them in order
std::stable_sort(proxy_rows.begin(), proxy_rows.end());
Move(proxy_rows, row);
} else if (data->hasFormat(Playlist::kRowsMimetype)) {
@ -388,7 +391,7 @@ QVariant Queue::headerData(int section, Qt::Orientation orientation,
void Queue::Remove(QList<int>& proxy_rows) {
// order the rows
qStableSort(proxy_rows);
std::stable_sort(proxy_rows.begin(), proxy_rows.end());
// reflects immediately changes in the playlist
layoutAboutToBeChanged();

View File

@ -23,6 +23,8 @@
#include "ui_queuemanager.h"
#include "ui/iconloader.h"
#include <algorithm>
#include <QKeySequence>
#include <QShortcut>
@ -105,7 +107,7 @@ void QueueManager::CurrentPlaylistChanged(Playlist* playlist) {
void QueueManager::MoveUp() {
QModelIndexList indexes = ui_->list->selectionModel()->selectedRows();
qStableSort(indexes);
std::stable_sort(indexes.begin(), indexes.end());
if (indexes.isEmpty() || indexes.first().row() == 0) return;
@ -116,7 +118,7 @@ void QueueManager::MoveUp() {
void QueueManager::MoveDown() {
QModelIndexList indexes = ui_->list->selectionModel()->selectedRows();
qStableSort(indexes);
std::stable_sort(indexes.begin(), indexes.end());
if (indexes.isEmpty() ||
indexes.last().row() == current_playlist_->queue()->rowCount() - 1)

View File

@ -25,6 +25,8 @@
#include "xspfparser.h"
#include "core/logging.h"
#include <algorithm>
#include <QtDebug>
const int PlaylistParser::kMagicSize = 512;
@ -49,7 +51,7 @@ QStringList PlaylistParser::file_extensions() const {
ret << parser->file_extensions();
}
qStableSort(ret);
std::stable_sort(ret.begin(), ret.end());
return ret;
}
@ -60,7 +62,7 @@ QStringList PlaylistParser::mime_types() const {
if (!parser->mime_type().isEmpty()) ret << parser->mime_type();
}
qStableSort(ret);
std::stable_sort(ret.begin(), ret.end());
return ret;
}

View File

@ -17,6 +17,8 @@
#include "ripper/ripcddialog.h"
#include <algorithm>
#include <QCheckBox>
#include <QCloseEvent>
#include <QFileDialog>
@ -107,7 +109,7 @@ RipCDDialog::RipCDDialog(QWidget* parent)
// Get presets
QList<TranscoderPreset> presets = Transcoder::GetAllPresets();
qSort(presets.begin(), presets.end(), ComparePresetsByName);
std::sort(presets.begin(), presets.end(), ComparePresetsByName);
for (const TranscoderPreset& preset : presets) {
ui_->format->addItem(
QString("%1 (.%2)").arg(preset.name_).arg(preset.extension_),

View File

@ -17,6 +17,8 @@
#include "songinfo/songinfoview.h"
#include <algorithm>
#include <QFuture>
#include <QSettings>
#include <QtConcurrentRun>
@ -159,6 +161,6 @@ QList<const UltimateLyricsProvider*> SongInfoView::lyric_providers() const {
ret << lyrics;
}
}
qSort(ret.begin(), ret.end(), CompareLyricProviders);
std::sort(ret.begin(), ret.end(), CompareLyricProviders);
return ret;
}

View File

@ -24,6 +24,8 @@
#include "ui/mainwindow.h"
#include "widgets/fileview.h"
#include <algorithm>
#include <QPushButton>
#include <QFileDialog>
#include <QDirIterator>
@ -63,7 +65,7 @@ TranscodeDialog::TranscodeDialog(QWidget* parent)
// Get presets
QList<TranscoderPreset> presets = Transcoder::GetAllPresets();
qSort(presets.begin(), presets.end(), ComparePresetsByName);
std::sort(presets.begin(), presets.end(), ComparePresetsByName);
for (const TranscoderPreset& preset : presets) {
ui_->format->addItem(
QString("%1 (.%2)").arg(preset.name_, preset.extension_),

View File

@ -17,6 +17,7 @@
#include "transcoder.h"
#include <algorithm>
#include <memory>
#include <QCoreApplication>
@ -138,7 +139,7 @@ GstElement* Transcoder::CreateElementForMimeType(const QString& element_type,
if (suitable_elements_.isEmpty()) return nullptr;
// Sort by rank
qSort(suitable_elements_);
std::sort(suitable_elements_.begin(), suitable_elements_.end());
const SuitableElement& best = suitable_elements_.last();
LogLine(QString("Using '%1' (rank %2)").arg(best.name_).arg(best.rank_));

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

Some files were not shown because too many files have changed in this diff Show More