Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Cover art support 3 #2

Merged
merged 49 commits into from
Aug 4, 2014
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
49 commits
Select commit Hold shift + click to select a range
b499f11
Add cover art column to librarymodel - it will show the covers
cardinot Jul 15, 2014
3380e4c
Add CoverArtDelegate class - it will be responsible for drawing the c…
cardinot Jul 15, 2014
76a06d5
CoverArtCache::requestPixmap(..) - returning QPixmap
cardinot Jul 16, 2014
35d8446
CoverCache - new argument to check if it must emit "pixmapFound" signals
cardinot Jul 16, 2014
5fd0c7e
CoverDelegate - requesting pixmap from CoverCache
cardinot Jul 16, 2014
4d6ba16
covercache - wrong order of member initialization
cardinot Jul 16, 2014
5a0d984
Merge branch 'coverArtSupport_2' of https://github.com/cardinot/mixxx…
cardinot Jul 16, 2014
2268187
CoverDelegate - small fixes + highlight selected rows
cardinot Jul 16, 2014
a5e1c31
Changes return of CoverCache::requestPixmap(..) to null pixmap + do n…
cardinot Jul 16, 2014
44717e8
CoverDelegate - setting render hint to use SmoothPixmapTransform
cardinot Jul 16, 2014
ecc2e1f
CoverDelegate - avoids drawing default_cover
cardinot Jul 16, 2014
03781d7
CoverDelegate - avoids calling "requestPixmap(..)" when the cover is …
cardinot Jul 16, 2014
f8d93c8
tablemodel - using cover_art field as readOnly (disable text edition)
cardinot Jul 17, 2014
7c8a639
Merge branch 'coverArtSupport_2' of https://github.com/cardinot/mixxx…
cardinot Jul 17, 2014
c6aff88
Add cover_art column to playlistmodel
cardinot Jul 17, 2014
abb218f
Cosmetic fixes to playlistmodel - using sql words in capital letters …
cardinot Jul 17, 2014
dd0525c
librarytablemodel - fixing columns of TEMPORARY VIEW table
cardinot Jul 17, 2014
2113213
Add cover_art column to cratetablemodel
cardinot Jul 17, 2014
85e7c18
Cosmetic fixes for cratetablemodel - using sql words in capital lette…
cardinot Jul 17, 2014
53f302f
Merge branch 'coverArtSupport_2' of https://github.com/cardinot/mixxx…
cardinot Jul 21, 2014
c3b67d6
CoverDeletage - cropping and adjusting the cover to fit in a cell
cardinot Jul 22, 2014
b5f36be
Fix bug when cover column is smaller than cover.width
cardinot Jul 22, 2014
06680de
Merge branch 'coverArtSupport_2' of https://github.com/cardinot/mixxx…
cardinot Jul 22, 2014
0933edd
CoverArtDelegate - drawing rows before doing the coverpixmap stuff
cardinot Jul 23, 2014
ab81473
CoverArtDelegate - using previous cover_delay to lock/unlock the cove…
cardinot Jul 23, 2014
8e82305
CoverArtDelegate - extending same logic used to delay cover_loadings …
cardinot Jul 23, 2014
9614f5e
Cropped Image - storing cropped images in pixmapcache + cropping them…
cardinot Jul 24, 2014
aeb0323
Merge branch 'coverArtSupport_2' of https://github.com/cardinot/mixxx…
cardinot Jul 24, 2014
c447457
Improving CoverArtDelegate::paint() - remove unnecessary QStyleOption…
cardinot Jul 28, 2014
c88e16c
Improving CoverArtDelegate::paint() - using trackmodel as a member to…
cardinot Jul 28, 2014
5b8dfc3
Improving CoverArtDelegate::paint() - using defaultCoverLocation as a…
cardinot Jul 28, 2014
67300c3
Improving CoverArtDelegate::paint()
cardinot Jul 29, 2014
2b579de
CoverArtCache - checking if the cover found is different from the cov…
cardinot Jul 29, 2014
5fadd65
CoverArtDAO - add method to save multi cover arts at once (from qlist)
cardinot Jul 29, 2014
9e64660
TrackDAO - updating multi cover_art rows at once (needs more work)
cardinot Jul 30, 2014
806e476
CoverArtCache - accumulate covers for half second and then update the…
cardinot Jul 30, 2014
abc114d
TrackDAO::updateCoverArt() - updating multiple coverIds in a single q…
cardinot Jul 31, 2014
de2ed0d
TrackDAO::updateCoverArt() - trackIds and coverIds must have the same…
cardinot Jul 31, 2014
19fa25e
TrackDAO - add new signal to updateTracksInBTC
cardinot Jul 31, 2014
afcfa02
CoverCache - set maximum cover size from 400px to 300px
cardinot Jul 31, 2014
4bfc18c
CoverCache - rescaling covers before calcule md5 hash
cardinot Jul 31, 2014
961b8a2
Fixing bug in searchImage() - the md5Hash string must start with the …
cardinot Jul 31, 2014
e324d70
cosmetic fix - fixing commented line
cardinot Jul 31, 2014
ec6d73e
Merge branch 'coverArtSupport_2' of https://github.com/cardinot/mixxx…
cardinot Aug 1, 2014
f662331
CoverDAO::getCoverInfo() - improving performance by removing "indexOf…
cardinot Aug 1, 2014
1239eca
WTrackTableView - getting coverLocation and md5 column numbers just once
cardinot Aug 1, 2014
fde5cd7
improving the management of the "covercache::m_queueOfUpdates" member
cardinot Aug 2, 2014
747ad97
CoverArtDelegate - set initial column width
cardinot Aug 2, 2014
5dfd3b8
Remove unnecessary cover_art query from TrackDAO::getTrackFromDB
cardinot Aug 3, 2014
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions build/depends.py
Original file line number Diff line number Diff line change
Expand Up @@ -809,6 +809,7 @@ def sources(self, build):
"library/bpmdelegate.cpp",
"library/bpmeditor.cpp",
"library/previewbuttondelegate.cpp",
"library/coverartdelegate.cpp",
"audiotagger.cpp",

"library/treeitemmodel.cpp",
Expand Down
8 changes: 7 additions & 1 deletion src/library/basesqltablemodel.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@

#include "library/basesqltablemodel.h"

#include "library/coverartdelegate.h"
#include "library/stardelegate.h"
#include "library/starrating.h"
#include "library/bpmdelegate.h"
Expand Down Expand Up @@ -91,6 +92,8 @@ void BaseSqlTableModel::initHeaderData() {
Qt::Horizontal, tr("BPM Lock"));
setHeaderData(fieldIndex(ColumnCache::COLUMN_LIBRARYTABLE_PREVIEW),
Qt::Horizontal, tr("Preview"));
setHeaderData(fieldIndex(ColumnCache::COLUMN_LIBRARYTABLE_COVERART),
Qt::Horizontal, tr("Cover Art"));
}

QSqlDatabase BaseSqlTableModel::database() const {
Expand Down Expand Up @@ -637,7 +640,8 @@ Qt::ItemFlags BaseSqlTableModel::readWriteFlags(
column == fieldIndex(ColumnCache::COLUMN_LIBRARYTABLE_LOCATION) ||
column == fieldIndex(ColumnCache::COLUMN_LIBRARYTABLE_DURATION) ||
column == fieldIndex(ColumnCache::COLUMN_LIBRARYTABLE_BITRATE) ||
column == fieldIndex(ColumnCache::COLUMN_LIBRARYTABLE_DATETIMEADDED)) {
column == fieldIndex(ColumnCache::COLUMN_LIBRARYTABLE_DATETIMEADDED) ||
column == fieldIndex(ColumnCache::COLUMN_LIBRARYTABLE_COVERART)) {
return defaultFlags;
} else if (column == fieldIndex(ColumnCache::COLUMN_LIBRARYTABLE_TIMESPLAYED)) {
return defaultFlags | Qt::ItemIsUserCheckable;
Expand Down Expand Up @@ -870,6 +874,8 @@ QAbstractItemDelegate* BaseSqlTableModel::delegateForColumn(const int i, QObject
return new BPMDelegate(pParent, i, fieldIndex(ColumnCache::COLUMN_LIBRARYTABLE_BPM_LOCK));
} else if (PlayerManager::numPreviewDecks() > 0 && i == fieldIndex(ColumnCache::COLUMN_LIBRARYTABLE_PREVIEW)) {
return new PreviewButtonDelegate(pParent, i);
} else if (i == fieldIndex(ColumnCache::COLUMN_LIBRARYTABLE_COVERART)) {
return new CoverArtDelegate(pParent);
}
return NULL;
}
Expand Down
7 changes: 7 additions & 0 deletions src/library/basetrackcache.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,13 @@ void BaseTrackCache::slotUpdateTrack(int trackId) {
updateTrackInIndex(trackId);
}

void BaseTrackCache::slotUpdateTracks(QSet<int> trackIds) {
if (sDebug) {
qDebug() << this << "slotUpdateTracks" << trackIds.size();
}
updateTracksInIndex(trackIds);
}

bool BaseTrackCache::isCached(int trackId) const {
return m_trackInfo.contains(trackId);
}
Expand Down
1 change: 1 addition & 0 deletions src/library/basetrackcache.h
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@ class BaseTrackCache : public QObject {
void slotTrackChanged(int trackId);
void slotDbTrackAdded(TrackPointer pTrack);
void slotUpdateTrack(int trackId);
void slotUpdateTracks(QSet<int> trackId);

private:
TrackPointer lookupCachedTrack(int trackId) const;
Expand Down
1 change: 1 addition & 0 deletions src/library/columncache.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ void ColumnCache::setColumns(const QStringList& columns) {
m_columnIndexByEnum[COLUMN_LIBRARYTABLE_KEY_ID] = fieldIndex(LIBRARYTABLE_KEY_ID);
m_columnIndexByEnum[COLUMN_LIBRARYTABLE_BPM_LOCK] = fieldIndex(LIBRARYTABLE_BPM_LOCK);
m_columnIndexByEnum[COLUMN_LIBRARYTABLE_PREVIEW] = fieldIndex(LIBRARYTABLE_PREVIEW);
m_columnIndexByEnum[COLUMN_LIBRARYTABLE_COVERART] = fieldIndex(LIBRARYTABLE_COVERART);
m_columnIndexByEnum[COLUMN_LIBRARYTABLE_COVERART_LOCATION] = fieldIndex(LIBRARYTABLE_COVERART_LOCATION);
m_columnIndexByEnum[COLUMN_LIBRARYTABLE_COVERART_MD5] = fieldIndex(LIBRARYTABLE_COVERART_MD5);

Expand Down
1 change: 1 addition & 0 deletions src/library/columncache.h
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ class ColumnCache {
COLUMN_LIBRARYTABLE_KEY_ID,
COLUMN_LIBRARYTABLE_BPM_LOCK,
COLUMN_LIBRARYTABLE_PREVIEW,
COLUMN_LIBRARYTABLE_COVERART,
COLUMN_LIBRARYTABLE_COVERART_LOCATION,
COLUMN_LIBRARYTABLE_COVERART_MD5,

Expand Down
173 changes: 141 additions & 32 deletions src/library/coverartcache.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,19 @@
#include <QPixmap>
#include <QStringBuilder>
#include <QtConcurrentRun>
#include <QTimer>

#include "coverartcache.h"
#include "soundsourceproxy.h"

CoverArtCache::CoverArtCache()
: m_pCoverArtDAO(NULL),
m_pTrackDAO(NULL),
m_defaultCover(":/images/library/default_cover.png"),
m_sDefaultCoverLocation(":/images/library/default_cover.png") {
m_sDefaultCoverLocation(":/images/library/default_cover.png"),
m_defaultCover(m_sDefaultCoverLocation),
m_timer(new QTimer(this)) {
m_timer->setSingleShot(true);
connect(m_timer, SIGNAL(timeout()), SLOT(updateDB()));
}

CoverArtCache::~CoverArtCache() {
Expand Down Expand Up @@ -54,51 +58,88 @@ bool CoverArtCache::changeCoverArt(int trackId,
return true;
}

void CoverArtCache::requestPixmap(int trackId,
const QString& coverLocation,
const QString& md5Hash) {
QPixmap CoverArtCache::requestPixmap(int trackId,
const QString& coverLocation,
const QString& md5Hash,
const bool tryLoadAndSearch,
const bool croppedPixmap,
const bool emitSignals) {
if (trackId < 1) {
return;
return QPixmap();
}

// keep a list of trackIds for which a future is currently running
// to avoid loading the same picture again while we are loading it
if (m_runningIds.contains(trackId)) {
return;
return QPixmap();
}

// check if we have already found a cover for this track
// and if it is just waiting to be inserted/updated in the DB.
if (m_queueOfUpdates.contains(trackId)) {
return QPixmap();
}

// If this request comes from CoverDelegate (table view),
// it'll want to get a cropped cover which is ready to be drawn
// in the table view (cover art column).
// It's very important to keep the cropped covers in cache because it avoids
// having to rescale+crop it ALWAYS (which brings a lot of performance issues).
QString cacheKey = croppedPixmap ? md5Hash % "_cropped" : md5Hash;

QPixmap pixmap;
if (QPixmapCache::find(md5Hash, &pixmap)) {
emit(pixmapFound(trackId, pixmap));
return;
if (QPixmapCache::find(cacheKey, &pixmap)) {
if (emitSignals) {
emit(pixmapFound(trackId, pixmap));
}
return pixmap;
}

if (!tryLoadAndSearch) {
return QPixmap();
}

QFuture<FutureResult> future;
QFutureWatcher<FutureResult>* watcher = new QFutureWatcher<FutureResult>(this);
if (coverLocation.isEmpty() || !QFile::exists(coverLocation)) {
CoverArtDAO::CoverArtInfo coverInfo;
coverInfo = m_pCoverArtDAO->getCoverArtInfo(trackId);
future = QtConcurrent::run(this, &CoverArtCache::searchImage, coverInfo);
future = QtConcurrent::run(this, &CoverArtCache::searchImage,
coverInfo, croppedPixmap, emitSignals);
connect(watcher, SIGNAL(finished()), this, SLOT(imageFound()));
} else {
future = QtConcurrent::run(this, &CoverArtCache::loadImage,
trackId, coverLocation, md5Hash);
trackId, coverLocation, md5Hash,
croppedPixmap, emitSignals);
connect(watcher, SIGNAL(finished()), this, SLOT(imageLoaded()));
}
m_runningIds.insert(trackId);
watcher->setFuture(future);

return QPixmap();
}

// Load cover from path stored in DB.
// It is executed in a separate thread via QtConcurrent::run
CoverArtCache::FutureResult CoverArtCache::loadImage(
int trackId, const QString& coverLocation, const QString& md5Hash) {
CoverArtCache::FutureResult CoverArtCache::loadImage(int trackId,
const QString& coverLocation,
const QString& md5Hash,
const bool croppedPixmap,
const bool emitSignals) {
FutureResult res;
res.trackId = trackId;
res.coverLocation = coverLocation;
res.img = QImage(coverLocation);
res.img = rescaleBigImage(res.img);
res.md5Hash = md5Hash;
res.croppedImg = croppedPixmap;
res.emitSignals = emitSignals;

if (res.croppedImg) {
res.img = cropImage(res.img);
} else {
res.img = rescaleBigImage(res.img);
}

return res;
}

Expand All @@ -109,12 +150,15 @@ void CoverArtCache::imageLoaded() {
FutureResult res = watcher->result();

QPixmap pixmap;
if (QPixmapCache::find(res.md5Hash, &pixmap)) {
QString cacheKey = res.croppedImg ? res.md5Hash % "_cropped" : res.md5Hash;
if (QPixmapCache::find(cacheKey, &pixmap) && res.emitSignals) {
emit(pixmapFound(res.trackId, pixmap));
} else if (!res.img.isNull()) {
pixmap.convertFromImage(res.img);
if (QPixmapCache::insert(res.md5Hash, pixmap)) {
emit(pixmapFound(res.trackId, pixmap));
if (QPixmapCache::insert(cacheKey, pixmap)) {
if (res.emitSignals) {
emit(pixmapFound(res.trackId, pixmap));
}
}
}
m_runningIds.remove(res.trackId);
Expand All @@ -124,9 +168,15 @@ void CoverArtCache::imageLoaded() {
// that could block the main thread. Therefore, this method
// is executed in a separate thread via QtConcurrent::run
CoverArtCache::FutureResult CoverArtCache::searchImage(
CoverArtDAO::CoverArtInfo coverInfo) {
CoverArtDAO::CoverArtInfo coverInfo,
const bool croppedPixmap,
const bool emitSignals) {
FutureResult res;
res.trackId = coverInfo.trackId;
res.md5Hash = coverInfo.md5Hash;
res.croppedImg = croppedPixmap;
res.emitSignals = emitSignals;
res.newImgFound = false;

// Looking for embedded cover art.
//
Expand All @@ -138,17 +188,32 @@ CoverArtCache::FutureResult CoverArtCache::searchImage(
// so we need to recalculate the md5 hash.
res.md5Hash = calculateMD5(res.img);
}
return res;
res.newImgFound = true;
}

// Looking for cover stored in track diretory.
//
res.coverLocation = searchInTrackDirectory(coverInfo.trackDirectory,
coverInfo.trackBaseName,
coverInfo.album);
res.img = QImage(res.coverLocation);
res.img = rescaleBigImage(res.img);
res.md5Hash = calculateMD5(res.img);
if (!res.newImgFound) {
res.coverLocation = searchInTrackDirectory(coverInfo.trackDirectory,
coverInfo.trackBaseName,
coverInfo.album);
res.img = rescaleBigImage(QImage(res.coverLocation));
res.md5Hash = calculateMD5(res.img);
res.newImgFound = true;
}

// adjusting the cover size according to the final purpose
if (res.newImgFound && res.croppedImg) {
res.img = cropImage(res.img);
}

// check if this image is really a new one
// (different from the one that we have in db)
if (coverInfo.md5Hash == res.md5Hash)
{
res.newImgFound = false;
}

return res;
}

Expand Down Expand Up @@ -232,25 +297,69 @@ void CoverArtCache::imageFound() {
FutureResult res = watcher->result();

QPixmap pixmap;
if (QPixmapCache::find(res.md5Hash, &pixmap)) {
QString cacheKey = res.croppedImg ? res.md5Hash % "_cropped" : res.md5Hash;
if (QPixmapCache::find(cacheKey, &pixmap) && res.emitSignals) {
emit(pixmapFound(res.trackId, pixmap));
} else if (!res.img.isNull()) {
pixmap.convertFromImage(res.img);
if (QPixmapCache::insert(res.md5Hash, pixmap)) {
emit(pixmapFound(res.trackId, pixmap));
if (QPixmapCache::insert(cacheKey, pixmap)) {
if (res.emitSignals) {
emit(pixmapFound(res.trackId, pixmap));
}
}
}

// update DB
int coverId = m_pCoverArtDAO->saveCoverArt(res.coverLocation, res.md5Hash);
m_pTrackDAO->updateCoverArt(res.trackId, coverId);
if (res.newImgFound && !m_queueOfUpdates.contains(res.trackId)) {
m_queueOfUpdates.insert(res.trackId,
qMakePair(res.coverLocation, res.md5Hash));
}

if (m_queueOfUpdates.size() == 1 && !m_timer->isActive()) {
m_timer->start(500); // after 0.5s, it will call `updateDB()`
}

m_runningIds.remove(res.trackId);
}

// sqlite can't do a huge number of updates in a very short time,
// so it is important to collect all new covers and write them at once.
void CoverArtCache::updateDB() {
if (m_queueOfUpdates.isEmpty()) {
return;
}
QSet<QPair<int, int> > covers;
covers = m_pCoverArtDAO->saveCoverArt(m_queueOfUpdates);
m_pTrackDAO->updateCoverArt(covers);
m_queueOfUpdates.clear();
}

// It will return a cropped cover that is ready to be
// used by the tableview-cover_column (CoverDelegate).
// As QImage is optimized to manipulate images, we will do it here
// instead of rescale it directly on the CoverDelegate::paint()
// because it would be much slower and could easily freeze the UI...
// Also, this method will run in separate thread
// (via Qtconcurrent - called by searchImage() or loadImage())
QImage CoverArtCache::cropImage(QImage img) {
if (img.isNull()) {
return QImage();
}

// it defines the maximum width of the covers displayed
// in the cover art column (tableviews).
// (if you want to increase it - you have to change JUST it.)
const int WIDTH = 100;
const int CELL_HEIGHT = 20;

img = img.scaledToWidth(WIDTH, Qt::SmoothTransformation);
return img.copy(0, 0, img.width(), CELL_HEIGHT);
}

// if it's too big, we have to scale it.
// big images would be quickly removed from cover cache.
QImage CoverArtCache::rescaleBigImage(QImage img) {
const int MAXSIZE = 400;
const int MAXSIZE = 300;
QSize size = img.size();
if (size.height() > MAXSIZE || size.width() > MAXSIZE) {
return img.scaled(MAXSIZE, MAXSIZE,
Expand Down
Loading