Commit 69c56877 authored by Daniel Faust's avatar Daniel Faust
Browse files

Some changes to the handling of cover art reading and writing

The maximum dimensions for embedded covers are now configurable.
When writing covers to files, all existing covers will be replaced.

FEATURE: 279493
FIXED-IN: 2.6
REVIEW: 104513
GUI: New configuration option for the maximum cover size in the 'local collection' tab
parent b7fb7034
......@@ -4,6 +4,7 @@ Amarok ChangeLog
Version 2.6-Beta 1
FEATURES:
* The maximum dimensions for embedded covers are now configurable. (BR 279493)
* Small configuration dialog for iPods that shows troubleshooting information
and allows to change iPod name.
* Improved usability of iPod playlists: iPod collection automatically transfers
......@@ -38,6 +39,7 @@ Version 2.6-Beta 1
vice versa. (BR 142579)
CHANGES:
* When writing covers to files, all existing covers will be replaced.
* PulseAudio status in diagnostic dialog.
* optional libgpod dependency raised to 0.8.2 to support newest iPods.
* Amarok now prevents accidental unmounting of iPods in (small) time-frames
......
......@@ -28,167 +28,6 @@
using namespace Meta::Tag;
class ASFPicture
{
public:
enum PictureType
{
Other = 0,
PNGIcon = 1,
OtherIcon = 2,
FrontCover = 3,
BackCover = 4,
Leaflet = 5,
Media = 6,
LeadArtist = 7,
Artist = 8,
Conductor = 9,
Band = 10,
Composer = 11,
Lyricist = 12,
RecordingStudioOrLocation = 13,
RecordingSession = 14,
Performance = 15,
CaptureFromMovieOrVideo = 16,
BrightColoredFish = 17,
Illustration = 18,
BandLogo = 19,
PublisherLogo = 20
};
ASFPicture();
ASFPicture( const TagLib::ByteVector &bv );
ASFPicture &operator=( const ASFPicture &picture );
PictureType type() const;
QString mimeType() const;
QString description() const;
QByteArray data() const;
int size() const;
#ifndef UTILITIES_BUILD
ASFPicture( const QImage &image );
QImage image() const;
TagLib::ByteVector toByteVector() const;
#endif //UTILITIES_BUILD
private:
PictureType m_type;
QString m_mimeType;
QString m_description;
QByteArray m_data;
};
ASFPicture::ASFPicture()
: m_type( PublisherLogo ) // We won't use such
{
}
ASFPicture::ASFPicture( const TagLib::ByteVector &bv )
{
if( bv.isEmpty() || bv.isNull() )
{
m_type = PublisherLogo;
return;
}
qint32 pos = 0;
const char *data = bv.data();
m_type = PictureType( *data );
pos++;
quint32 pictSize = *( quint32 *)(data + pos );
pos += 4;
m_mimeType = QString::fromUtf16( ( const ushort *)( data + pos ) );
pos += 2 * ( m_mimeType.length() + 1 );
m_description = QString::fromUtf16( ( const ushort *)( data + pos ) );
pos += 2 * ( m_description.length() + 1 );
m_data = QByteArray( data + pos, pictSize );
}
ASFPicture &
ASFPicture::operator=( const ASFPicture &picture )
{
m_type = picture.m_type;
m_mimeType = picture.m_mimeType;
m_description = picture.m_description;
m_data = picture.m_data;
return *this;
}
ASFPicture::PictureType
ASFPicture::type() const
{
return m_type;
}
QString
ASFPicture::mimeType() const
{
return m_mimeType;
}
QString
ASFPicture::description() const
{
return m_description;
}
QByteArray
ASFPicture::data() const
{
return m_data;
}
int
ASFPicture::size() const
{
return m_data.size();
}
#ifndef UTILITIES_BUILD
ASFPicture::ASFPicture( const QImage &image )
: m_type( FrontCover )
, m_mimeType( "image/jpeg" )
{
QBuffer buffer( &m_data );
buffer.open( QIODevice::WriteOnly );
image.save( &buffer, "JPEG" );
buffer.close();
}
QImage
ASFPicture::image() const
{
return QImage::fromData( m_data );
}
TagLib::ByteVector
ASFPicture::toByteVector() const
{
QByteArray data;
uchar type = m_type;
qint32 pictSize = m_data.size();
QBuffer buffer( &data );
buffer.open( QIODevice::WriteOnly );
buffer.write( ( const char *)&type, 1 );
buffer.write( ( const char *)&pictSize, 4 );
buffer.write( ( const char *)m_mimeType.utf16(), ( m_mimeType.length() + 1 ) * 2 );
buffer.write( ( const char *)m_description.utf16(), ( m_description.length() + 1 ) * 2 );
buffer.write( m_data.data(), pictSize );
buffer.close();
return TagLib::ByteVector( data.data(), data.size() );
}
#endif //UTILITIES_BUILD
ASFTagHelper::ASFTagHelper( TagLib::Tag *tag, TagLib::ASF::Tag *asfTag, Amarok::FileType fileType )
: TagHelper( tag, fileType )
, m_tag( asfTag )
......@@ -240,10 +79,10 @@ ASFTagHelper::tags() const
if( cover->type() != TagLib::ASF::Attribute::BytesType )
continue;
ASFPicture pict( cover->toByteVector() );
if( ( pict.type() == ASFPicture::FrontCover ||
pict.type() == ASFPicture::Other ) &&
pict.size() > 1024 )
TagLib::ASF::Picture pict = cover->toPicture();
if( ( pict.type() == TagLib::ASF::Picture::FrontCover ||
pict.type() == TagLib::ASF::Picture::Other ) &&
pict.dataSize() > MIN_COVER_SIZE )
{
data.insert( field, true );
break;
......@@ -317,6 +156,7 @@ ASFTagHelper::hasEmbeddedCover() const
TagLib::ASF::AttributeListMap map = m_tag->attributeListMap();
TagLib::String name = fieldName( Meta::valHasCover );
for( TagLib::ASF::AttributeListMap::ConstIterator it = map.begin(); it != map.end(); ++it )
{
if( it->first == name )
{
TagLib::ASF::AttributeList coverList = it->second;
......@@ -325,15 +165,16 @@ ASFTagHelper::hasEmbeddedCover() const
if( cover->type() != TagLib::ASF::Attribute::BytesType )
continue;
ASFPicture pict( cover->toByteVector() );
if( ( pict.type() == ASFPicture::FrontCover ||
pict.type() == ASFPicture::Other ) &&
pict.size() > 1024 )
{
return true;
}
TagLib::ASF::Picture pict = cover->toPicture();
if( ( pict.type() == TagLib::ASF::Picture::FrontCover ||
pict.type() == TagLib::ASF::Picture::Other ) &&
pict.dataSize() > MIN_COVER_SIZE )
{
return true;
}
}
}
}
return false;
}
......@@ -344,76 +185,77 @@ ASFTagHelper::embeddedCover() const
TagLib::ASF::AttributeListMap map = m_tag->attributeListMap();
TagLib::String name = fieldName( Meta::valHasCover );
ASFPicture other, front;
bool hasFront = false, hasOther = false;
int maxSize = 1024;
TagLib::ASF::Picture cover, otherCover;
bool hasCover = false, hasOtherCover = false;
for( TagLib::ASF::AttributeListMap::ConstIterator it = map.begin(); it != map.end(); ++it )
{
if( it->first == name )
{
TagLib::ASF::AttributeList coverList = it->second;
for( TagLib::ASF::AttributeList::ConstIterator cover = coverList.begin(); cover != coverList.end(); ++cover )
for( TagLib::ASF::AttributeList::ConstIterator it = coverList.begin(); it != coverList.end(); ++it )
{
if( cover->type() != TagLib::ASF::Attribute::BytesType )
if( it->type() != TagLib::ASF::Attribute::BytesType )
continue;
ASFPicture pict( cover->toByteVector() );
if( pict.size() < maxSize )
TagLib::ASF::Picture pict = it->toPicture();
if( pict.dataSize() < MIN_COVER_SIZE )
continue;
if( pict.type() == ASFPicture::FrontCover )
if( pict.type() == TagLib::ASF::Picture::FrontCover )
{
front = pict;
maxSize = pict.size();
hasFront = true;
cover = pict;
hasCover = true;
}
else if( pict.type() == ASFPicture::Other )
else if( pict.type() == TagLib::ASF::Picture::Other )
{
other = pict;
maxSize = pict.size();
hasOther = true;
otherCover = pict;
hasOtherCover = true;
}
}
}
}
if( !hasFront && !hasOther )
if( !hasCover && hasOtherCover )
{
cover = otherCover;
hasCover = true;
}
if( !hasCover )
return QImage();
//If Front and Other covers have the same size, we should use the Front one.
if( hasFront && !hasOther )
return front.image();
else if( !hasFront && hasOther )
return other.image();
else if( front.size() >= other.size() )
return front.image();
//else
return other.image();
return QImage::fromData( ( uchar * ) cover.picture().data(), cover.picture().size() );
}
bool
ASFTagHelper::setEmbeddedCover( const QImage &cover )
{
TagLib::String name = fieldName( Meta::valHasCover );
QByteArray bytes;
QBuffer buffer( &bytes );
ASFPicture picture( cover );
bool stored = false;
buffer.open( QIODevice::WriteOnly );
TagLib::ASF::AttributeList coverList = m_tag->attributeListMap()[name];
for( uint i = 0; i < coverList.size(); i++ )
if( !cover.save( &buffer, "JPEG" ) )
{
if( coverList[i].type() != TagLib::ASF::Attribute::BytesType )
continue;
ASFPicture pict( coverList[i].toByteVector() );
if( pict.type() == ASFPicture::FrontCover )
{
coverList[i] = TagLib::ASF::Attribute( picture.toByteVector() );
stored = true;
}
buffer.close();
return false;
}
if( !stored )
m_tag->addAttribute( name, TagLib::ASF::Attribute( picture.toByteVector() ) );
buffer.close();
TagLib::String name = fieldName( Meta::valHasCover );
// remove all covers
m_tag->removeItem( name );
// add new cover
TagLib::ASF::Picture picture;
picture.setPicture( TagLib::ByteVector( bytes.data(), bytes.count() ) );
picture.setType( TagLib::ASF::Picture::FrontCover );
picture.setMimeType( "image/jpeg" );
m_tag->addAttribute( name, TagLib::ASF::Attribute( picture.render() ) );
return true;
}
......
......@@ -102,7 +102,7 @@ ID3v2TagHelper::tags() const
if( ( frame->type() == TagLib::ID3v2::AttachedPictureFrame::FrontCover ||
frame->type() == TagLib::ID3v2::AttachedPictureFrame::Other ) &&
frame->picture().size() > 1024 ) // must be at least 1kb
frame->picture().size() > MIN_COVER_SIZE ) // must be at least 1kb
{
data.insert( Meta::valHasCover, true );
}
......@@ -331,15 +331,21 @@ ID3v2TagHelper::render() const
bool
ID3v2TagHelper::hasEmbeddedCover() const
{
TagLib::ByteVector field = fieldName( Meta::valHasCover ).toCString();
if( m_tag->frameListMap().contains( field ) )
TagLib::ID3v2::FrameList apicList = m_tag->frameListMap()[fieldName( Meta::valHasCover ).toCString()];
for( TagLib::ID3v2::FrameList::ConstIterator it = apicList.begin(); it != apicList.end(); ++it )
{
TagLib::ID3v2::AttachedPictureFrame *frame =
dynamic_cast< TagLib::ID3v2::AttachedPictureFrame * >( m_tag->frameListMap()[field].front() );
return frame && ( frame->picture().size() > 1024 ) &&
( frame->type() == TagLib::ID3v2::AttachedPictureFrame::FrontCover ||
frame->type() == TagLib::ID3v2::AttachedPictureFrame::Other );
TagLib::ID3v2::AttachedPictureFrame *currFrame =
dynamic_cast< TagLib::ID3v2::AttachedPictureFrame * >( *it );
if( currFrame->picture().size() < MIN_COVER_SIZE )
continue;
if( currFrame->type() == TagLib::ID3v2::AttachedPictureFrame::FrontCover ||
currFrame->type() == TagLib::ID3v2::AttachedPictureFrame::Other )
return true;
}
return false;
}
......@@ -347,47 +353,34 @@ QImage
ID3v2TagHelper::embeddedCover() const
{
TagLib::ID3v2::FrameList apicList = m_tag->frameListMap()[fieldName( Meta::valHasCover ).toCString()];
TagLib::ID3v2::AttachedPictureFrame *frontCover = NULL;
TagLib::ID3v2::AttachedPictureFrame *cover = NULL;
TagLib::ID3v2::AttachedPictureFrame *otherCover = NULL;
TagLib::ID3v2::AttachedPictureFrame *coverToUse = NULL;
uint maxSize = 1024; // ignore images that are too small
for( TagLib::ID3v2::FrameList::ConstIterator iter = apicList.begin(); iter != apicList.end(); ++iter )
for( TagLib::ID3v2::FrameList::ConstIterator it = apicList.begin(); it != apicList.end(); ++it )
{
TagLib::ID3v2::AttachedPictureFrame *currFrame =
dynamic_cast< TagLib::ID3v2::AttachedPictureFrame * >( *iter );
dynamic_cast< TagLib::ID3v2::AttachedPictureFrame * >( *it );
if( currFrame->picture().size() < maxSize )
if( currFrame->picture().size() < MIN_COVER_SIZE )
continue;
if( currFrame->type() == TagLib::ID3v2::AttachedPictureFrame::FrontCover )
{
frontCover = currFrame;
maxSize = currFrame->picture().size();
cover = currFrame;
}
else if( currFrame->type() == TagLib::ID3v2::AttachedPictureFrame::Other )
{
otherCover = currFrame;
maxSize = currFrame->picture().size();
}
}
if( !frontCover && !otherCover )
if( !cover && otherCover )
cover = otherCover;
if( !cover )
return QImage();
//If Front and Other covers have the same size, we should use the Front one.
if( frontCover && !otherCover )
coverToUse = frontCover;
else if( !frontCover && otherCover )
coverToUse = otherCover;
else if( frontCover->picture().size() >= otherCover->picture().size() )
coverToUse = frontCover;
else
coverToUse = otherCover;
return QImage::fromData( ( uchar * )( coverToUse->picture().data() ),
coverToUse->picture().size() );
return QImage::fromData( ( uchar * )( cover->picture().data() ), cover->picture().size() );
}
bool
......@@ -410,27 +403,23 @@ ID3v2TagHelper::setEmbeddedCover( const QImage &cover )
TagLib::ID3v2::FrameList apicList = m_tag->frameListMap()[field];
TagLib::ID3v2::AttachedPictureFrame *frontCover = NULL;
for( TagLib::ID3v2::FrameList::ConstIterator iter = apicList.begin(); iter != apicList.end(); ++iter )
// remove covers
TagLib::List<TagLib::ID3v2::AttachedPictureFrame*> backedUpPictures;
for( TagLib::ID3v2::FrameList::ConstIterator it = apicList.begin(); it != apicList.end(); ++it )
{
TagLib::ID3v2::AttachedPictureFrame *currFrame =
dynamic_cast< TagLib::ID3v2::AttachedPictureFrame * >( *iter );
dynamic_cast< TagLib::ID3v2::AttachedPictureFrame * >( *it );
if( currFrame->type() == TagLib::ID3v2::AttachedPictureFrame::FrontCover )
{
frontCover = currFrame;
break;
}
}
if( !frontCover )
{
frontCover = new TagLib::ID3v2::AttachedPictureFrame( field );
frontCover->setType( TagLib::ID3v2::AttachedPictureFrame::FrontCover );
m_tag->addFrame( frontCover );
m_tag->removeFrame( currFrame, false );
}
// add new cover
frontCover = new TagLib::ID3v2::AttachedPictureFrame( field );
frontCover->setMimeType( "image/jpeg" );
frontCover->setPicture( TagLib::ByteVector( bytes.data(), bytes.count() ) );
frontCover->setType( TagLib::ID3v2::AttachedPictureFrame::FrontCover );
m_tag->addFrame( frontCover );
return true;
}
#endif //UTILITIES_BUILD
......@@ -65,7 +65,7 @@ MP4TagHelper::tags() const
{
TagLib::MP4::CoverArtList coverList = it->second.toCoverArtList();
for( TagLib::MP4::CoverArtList::ConstIterator it = coverList.begin(); it != coverList.end(); ++it )
if( it->data().size() > 1024 )
if( it->data().size() > MIN_COVER_SIZE )
{
data.insert( field, true );
break;
......@@ -151,13 +151,17 @@ MP4TagHelper::hasEmbeddedCover() const
TagLib::MP4::ItemListMap map = m_tag->itemListMap();
TagLib::String name = fieldName( Meta::valHasCover );
for( TagLib::MP4::ItemListMap::ConstIterator it = map.begin(); it != map.end(); ++it )
{
if( it->first == name )
{
TagLib::MP4::CoverArtList coverList = it->second.toCoverArtList();
for( TagLib::MP4::CoverArtList::ConstIterator cover = coverList.begin(); cover != coverList.end(); ++cover )
if( cover->data().size() > 1024 )
{
if( cover->data().size() > MIN_COVER_SIZE )
return true;
}
}
}
return false;
}
......@@ -167,24 +171,20 @@ MP4TagHelper::embeddedCover() const
{
TagLib::MP4::ItemListMap map = m_tag->itemListMap();
TagLib::String name = fieldName( Meta::valHasCover );
TagLib::ByteVector coverData;
bool foundCover = false;
quint64 maxSize = 1024;
for( TagLib::MP4::ItemListMap::ConstIterator it = map.begin(); it != map.end(); ++it )
{
if( it->first == name )
{
TagLib::MP4::CoverArtList coverList = it->second.toCoverArtList();
for( TagLib::MP4::CoverArtList::Iterator cover = coverList.begin(); cover != coverList.end(); ++cover )
if( cover->data().size() > maxSize )
{
maxSize = cover->data().size();
foundCover = true;
coverData = cover->data();
}
{
if( cover->data().size() > MIN_COVER_SIZE )
return QImage::fromData( ( uchar * ) cover->data().data(), cover->data().size() );
}
}
}
return foundCover ? QImage::fromData( ( uchar * ) coverData.data(), coverData.size() ) : QImage();
return QImage();
}
bool
......@@ -203,12 +203,12 @@ MP4TagHelper::setEmbeddedCover( const QImage &cover )
buffer.close();
//Or just replace all covers with the new one?
TagLib::MP4::CoverArtList covers = m_tag->itemListMap()[fieldName( Meta::valHasCover )].toCoverArtList();
covers.prepend( TagLib::MP4::CoverArt( TagLib::MP4::CoverArt::JPEG,
TagLib::ByteVector( bytes.data(), bytes.count() ) ) );
TagLib::MP4::CoverArtList covers;
covers.append( TagLib::MP4::CoverArt( TagLib::MP4::CoverArt::JPEG, TagLib::ByteVector( bytes.data(), bytes.count() ) ) );
m_tag->itemListMap()[fieldName( Meta::valHasCover )] = TagLib::MP4::Item( covers );
return true;
}
#endif //UTILITIES_BUILD
......@@ -24,6 +24,8 @@
#define AMAROK_EXPORT
#endif
#define MIN_COVER_SIZE 1024 // Minimum size for an embedded cover to be loaded
#include "MetaValues.h"
#include "FileType.h"
......
......@@ -116,7 +116,7 @@ VorbisCommentTagHelper::tags() const
if( ( picture->type() == TagLib::FLAC::Picture::FrontCover ||
picture->type() == TagLib::FLAC::Picture::Other ) &&
picture->data().size() > 1024 ) // must be at least 1kb
picture->data().size() > MIN_COVER_SIZE ) // must be at least 1kb
{
data.insert( Meta::valHasCover, true );
break;
......@@ -183,7 +183,7 @@ VorbisCommentTagHelper::hasEmbeddedCover() const
if( ( picture->type() == TagLib::FLAC::Picture::FrontCover ||
picture->type() == TagLib::FLAC::Picture::Other ) &&
picture->data().size() > 1024 ) // must be at least 1kb
picture->data().size() > MIN_COVER_SIZE ) // must be at least 1kb
{
return true;
}
......@@ -215,7 +215,7 @@ VorbisCommentTagHelper::embeddedCover() const
if( ( picture->type() == TagLib::FLAC::Picture::FrontCover ||
picture->type() == TagLib::FLAC::Picture::Other ) &&
picture->data().size() > 1024 ) // must be at least 1kb
picture->data().size() > MIN_COVER_SIZE ) // must be at least 1kb
{
QByteArray image_data( picture->data().data(), picture->data().size() );
......@@ -248,7 +248,7 @@ VorbisCommentTagHelper::embeddedCover() const
QByteArray image_data_b64( coverList[i].toCString() );
QByteArray image_data = QByteArray::fromBase64