mirror of
https://github.com/tomahawk-player/tomahawk.git
synced 2025-08-30 09:10:53 +02:00
implement Fingerprinting via LastFMInfoPlugin
This commit is contained in:
@@ -267,6 +267,10 @@ if( WIN32 )
|
||||
macro_log_feature(QTSPARKLE_FOUND "qtsparkle" "Library for creating auto updaters written in Qt" "https://github.com/davidsansome/qtsparkle" FALSE "" "")
|
||||
endif( WIN32 )
|
||||
|
||||
#Temporary only - we probably need either a CMake file for mad or something nicer than mad
|
||||
include(LibFindMacros)
|
||||
libfind_pkg_check_modules(mad_PKGCONF mad)
|
||||
|
||||
#TODO: support external qxt
|
||||
set(QXTWEB_FOUND TRUE)
|
||||
set(QXTWEB_LIBRARIES qxtweb-standalone)
|
||||
|
99
CMakeModules/LibFindMacros.cmake
Normal file
99
CMakeModules/LibFindMacros.cmake
Normal file
@@ -0,0 +1,99 @@
|
||||
# Works the same as find_package, but forwards the "REQUIRED" and "QUIET" arguments
|
||||
# used for the current package. For this to work, the first parameter must be the
|
||||
# prefix of the current package, then the prefix of the new package etc, which are
|
||||
# passed to find_package.
|
||||
macro (libfind_package PREFIX)
|
||||
set (LIBFIND_PACKAGE_ARGS ${ARGN})
|
||||
if (${PREFIX}_FIND_QUIETLY)
|
||||
set (LIBFIND_PACKAGE_ARGS ${LIBFIND_PACKAGE_ARGS} QUIET)
|
||||
endif (${PREFIX}_FIND_QUIETLY)
|
||||
if (${PREFIX}_FIND_REQUIRED)
|
||||
set (LIBFIND_PACKAGE_ARGS ${LIBFIND_PACKAGE_ARGS} REQUIRED)
|
||||
endif (${PREFIX}_FIND_REQUIRED)
|
||||
find_package(${LIBFIND_PACKAGE_ARGS})
|
||||
endmacro (libfind_package)
|
||||
|
||||
# CMake developers made the UsePkgConfig system deprecated in the same release (2.6)
|
||||
# where they added pkg_check_modules. Consequently I need to support both in my scripts
|
||||
# to avoid those deprecated warnings. Here's a helper that does just that.
|
||||
# Works identically to pkg_check_modules, except that no checks are needed prior to use.
|
||||
macro (libfind_pkg_check_modules PREFIX PKGNAME)
|
||||
if (${CMAKE_MAJOR_VERSION} EQUAL 2 AND ${CMAKE_MINOR_VERSION} EQUAL 4)
|
||||
include(UsePkgConfig)
|
||||
pkgconfig(${PKGNAME} ${PREFIX}_INCLUDE_DIRS ${PREFIX}_LIBRARY_DIRS ${PREFIX}_LDFLAGS ${PREFIX}_CFLAGS)
|
||||
else (${CMAKE_MAJOR_VERSION} EQUAL 2 AND ${CMAKE_MINOR_VERSION} EQUAL 4)
|
||||
find_package(PkgConfig)
|
||||
if (PKG_CONFIG_FOUND)
|
||||
pkg_check_modules(${PREFIX} ${PKGNAME})
|
||||
endif (PKG_CONFIG_FOUND)
|
||||
endif (${CMAKE_MAJOR_VERSION} EQUAL 2 AND ${CMAKE_MINOR_VERSION} EQUAL 4)
|
||||
endmacro (libfind_pkg_check_modules)
|
||||
|
||||
# Do the final processing once the paths have been detected.
|
||||
# If include dirs are needed, ${PREFIX}_PROCESS_INCLUDES should be set to contain
|
||||
# all the variables, each of which contain one include directory.
|
||||
# Ditto for ${PREFIX}_PROCESS_LIBS and library files.
|
||||
# Will set ${PREFIX}_FOUND, ${PREFIX}_INCLUDE_DIRS and ${PREFIX}_LIBRARIES.
|
||||
# Also handles errors in case library detection was required, etc.
|
||||
macro (libfind_process PREFIX)
|
||||
# Skip processing if already processed during this run
|
||||
if (NOT ${PREFIX}_FOUND)
|
||||
# Start with the assumption that the library was found
|
||||
set (${PREFIX}_FOUND TRUE)
|
||||
|
||||
# Process all includes and set _FOUND to false if any are missing
|
||||
foreach (i ${${PREFIX}_PROCESS_INCLUDES})
|
||||
if (${i})
|
||||
set (${PREFIX}_INCLUDE_DIRS ${${PREFIX}_INCLUDE_DIRS} ${${i}})
|
||||
mark_as_advanced(${i})
|
||||
else (${i})
|
||||
set (${PREFIX}_FOUND FALSE)
|
||||
endif (${i})
|
||||
endforeach (i)
|
||||
|
||||
# Process all libraries and set _FOUND to false if any are missing
|
||||
foreach (i ${${PREFIX}_PROCESS_LIBS})
|
||||
if (${i})
|
||||
set (${PREFIX}_LIBRARIES ${${PREFIX}_LIBRARIES} ${${i}})
|
||||
mark_as_advanced(${i})
|
||||
else (${i})
|
||||
set (${PREFIX}_FOUND FALSE)
|
||||
endif (${i})
|
||||
endforeach (i)
|
||||
|
||||
# Print message and/or exit on fatal error
|
||||
if (${PREFIX}_FOUND)
|
||||
if (NOT ${PREFIX}_FIND_QUIETLY)
|
||||
message (STATUS "Found ${PREFIX} ${${PREFIX}_VERSION}")
|
||||
endif (NOT ${PREFIX}_FIND_QUIETLY)
|
||||
else (${PREFIX}_FOUND)
|
||||
if (${PREFIX}_FIND_REQUIRED)
|
||||
foreach (i ${${PREFIX}_PROCESS_INCLUDES} ${${PREFIX}_PROCESS_LIBS})
|
||||
message("${i}=${${i}}")
|
||||
endforeach (i)
|
||||
message (FATAL_ERROR "Required library ${PREFIX} NOT FOUND.\nInstall the library (dev version) and try again. If the library is already installed, use ccmake to set the missing variables manually.")
|
||||
endif (${PREFIX}_FIND_REQUIRED)
|
||||
endif (${PREFIX}_FOUND)
|
||||
endif (NOT ${PREFIX}_FOUND)
|
||||
endmacro (libfind_process)
|
||||
|
||||
macro(libfind_library PREFIX basename)
|
||||
set(TMP "")
|
||||
if(MSVC80)
|
||||
set(TMP -vc80)
|
||||
endif(MSVC80)
|
||||
if(MSVC90)
|
||||
set(TMP -vc90)
|
||||
endif(MSVC90)
|
||||
set(${PREFIX}_LIBNAMES ${basename}${TMP})
|
||||
if(${ARGC} GREATER 2)
|
||||
set(${PREFIX}_LIBNAMES ${basename}${TMP}-${ARGV2})
|
||||
string(REGEX REPLACE "\\." "_" TMP ${${PREFIX}_LIBNAMES})
|
||||
set(${PREFIX}_LIBNAMES ${${PREFIX}_LIBNAMES} ${TMP})
|
||||
endif(${ARGC} GREATER 2)
|
||||
find_library(${PREFIX}_LIBRARY
|
||||
NAMES ${${PREFIX}_LIBNAMES}
|
||||
PATHS ${${PREFIX}_PKGCONF_LIBRARY_DIRS}
|
||||
)
|
||||
endmacro(libfind_library)
|
||||
|
@@ -371,6 +371,8 @@ IF(LIBLASTFM_FOUND)
|
||||
accounts/lastfm/LastFmAccount.cpp
|
||||
accounts/lastfm/LastFmConfig.cpp
|
||||
accounts/lastfm/LastFmInfoPlugin.cpp
|
||||
#Temporary only - requires libmad - can we do this anyhow with libs we already use?
|
||||
accounts/lastfm/MadSource.cpp
|
||||
)
|
||||
ENDIF(LIBLASTFM_FOUND)
|
||||
|
||||
@@ -521,6 +523,11 @@ TARGET_LINK_LIBRARIES( tomahawklib
|
||||
${QT_QTCORE_LIBRARY}
|
||||
${OS_SPECIFIC_LINK_LIBRARIES}
|
||||
${CMAKE_THREAD_LIBS_INIT}
|
||||
#Temporary only
|
||||
${mad_PKGCONF_LDFLAGS}
|
||||
#CMake should also find fp library
|
||||
lastfm_fingerprint
|
||||
|
||||
${LINK_LIBRARIES}
|
||||
)
|
||||
|
||||
|
@@ -230,6 +230,8 @@ namespace Tomahawk
|
||||
InfoUnLove = 91,
|
||||
InfoShareTrack = 92,
|
||||
|
||||
InfoFingerprintTrack = 95,
|
||||
|
||||
InfoNotifyUser = 100,
|
||||
|
||||
InfoInboxReceived = 101,
|
||||
|
@@ -36,8 +36,14 @@
|
||||
|
||||
#include <lastfm/ws.h>
|
||||
#include <lastfm/XmlQuery.h>
|
||||
#include <lastfm/Track.h>
|
||||
#include <lastfm/Fingerprint.h>
|
||||
#include <lastfm/FingerprintableSource.h>
|
||||
|
||||
#include "MadSource.h"
|
||||
|
||||
#include <qjson/parser.h>
|
||||
#include <boost/concept_check.hpp>
|
||||
|
||||
using namespace Tomahawk::Accounts;
|
||||
using namespace Tomahawk::InfoSystem;
|
||||
@@ -48,7 +54,7 @@ LastFmInfoPlugin::LastFmInfoPlugin( LastFmAccount* account )
|
||||
, m_account( account )
|
||||
, m_scrobbler( 0 )
|
||||
{
|
||||
m_supportedGetTypes << InfoAlbumCoverArt << InfoArtistImages << InfoArtistSimilars << InfoArtistSongs << InfoArtistBiography << InfoChart << InfoChartCapabilities << InfoTrackSimilars;
|
||||
m_supportedGetTypes << InfoAlbumCoverArt << InfoArtistImages << InfoArtistSimilars << InfoArtistSongs << InfoArtistBiography << InfoChart << InfoChartCapabilities << InfoTrackSimilars << InfoFingerprintTrack;
|
||||
m_supportedPushTypes << InfoSubmitScrobble << InfoSubmitNowPlaying << InfoLove << InfoUnLove;
|
||||
}
|
||||
|
||||
@@ -137,6 +143,10 @@ LastFmInfoPlugin::getInfo( Tomahawk::InfoSystem::InfoRequestData requestData )
|
||||
fetchSimilarTracks( requestData );
|
||||
break;
|
||||
|
||||
case InfoFingerprintTrack:
|
||||
fetchFingerprint( requestData );
|
||||
break;
|
||||
|
||||
default:
|
||||
dataError( requestData );
|
||||
}
|
||||
@@ -408,6 +418,78 @@ LastFmInfoPlugin::fetchAlbumInfo( Tomahawk::InfoSystem::InfoRequestData requestD
|
||||
emit getCachedInfo( criteria, Q_INT64_C(2419200000), requestData );
|
||||
}
|
||||
|
||||
void
|
||||
LastFmInfoPlugin::fetchFingerprint( Tomahawk::InfoSystem::InfoRequestData requestData )
|
||||
{
|
||||
if ( !requestData.input.canConvert< Tomahawk::InfoSystem::InfoStringHash >() )
|
||||
{
|
||||
dataError( requestData );
|
||||
return;
|
||||
}
|
||||
InfoStringHash hash = requestData.input.value< Tomahawk::InfoSystem::InfoStringHash >();
|
||||
if ( !hash.contains( "file" ) )
|
||||
{
|
||||
dataError( requestData );
|
||||
return;
|
||||
}
|
||||
|
||||
QFileInfo fi( hash["file"] );
|
||||
|
||||
lastfm::MutableTrack track;
|
||||
track.setUrl( QUrl::fromLocalFile( fi.canonicalFilePath() ) );
|
||||
try
|
||||
{
|
||||
lastfm::Fingerprint* fp = new lastfm::Fingerprint( track );
|
||||
if ( fp->id().isNull() )
|
||||
{
|
||||
//TODO: atm let's only fp mp3, make it later nicer and fp others too
|
||||
if ( fi.fileName().endsWith( "mp3" ) || fi.fileName().endsWith( "MP3" ) )
|
||||
{
|
||||
lastfm::FingerprintableSource* fs = new MadSource( ); //TODO: this should become a PhononSource later
|
||||
fp->generate( fs ); //TODO: put this into own Thread cause it could take long
|
||||
|
||||
QNetworkReply* reply = fp->submit();
|
||||
|
||||
QPair< QFileInfo, lastfm::Fingerprint* > pair;
|
||||
pair.first = fi;
|
||||
pair.second = fp;
|
||||
m_fingerprintMap.insert( reply, pair );
|
||||
reply->setProperty( "requestData", QVariant::fromValue< Tomahawk::InfoSystem::InfoRequestData >( requestData ) );
|
||||
|
||||
connect( reply, SIGNAL( finished() ), this, SLOT( fingerprintReturned() ) );
|
||||
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
int id = fp->id();
|
||||
delete fp;
|
||||
|
||||
fetchTrackInfo( requestData, fi, id );
|
||||
}
|
||||
}
|
||||
catch ( lastfm::Fingerprint::Error e )
|
||||
{
|
||||
tDebug() << "FP Error: " << e;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void
|
||||
LastFmInfoPlugin::fetchTrackInfo( Tomahawk::InfoSystem::InfoRequestData requestData, const QFileInfo& fi, int id )
|
||||
{
|
||||
QMap< QString, QString > query;
|
||||
query["method"] = "track.getFingerprintMetadata";
|
||||
query["fingerprintid"] = QString::number( id );
|
||||
QNetworkReply* trackInfoReply = lastfm::ws::get( query );
|
||||
|
||||
trackInfoReply->setProperty( "requestData", QVariant::fromValue< Tomahawk::InfoSystem::InfoRequestData >( requestData ) );
|
||||
trackInfoReply->setProperty( "file", QVariant::fromValue< QString >( fi.canonicalFilePath() ) );
|
||||
|
||||
connect( trackInfoReply, SIGNAL( finished() ), this, SLOT( trackInfoReturned() ) );
|
||||
|
||||
}
|
||||
|
||||
|
||||
void
|
||||
LastFmInfoPlugin::notInCacheSlot( QHash<QString, QString> criteria, Tomahawk::InfoSystem::InfoRequestData requestData )
|
||||
@@ -895,6 +977,54 @@ LastFmInfoPlugin::artistImagesReturned()
|
||||
}
|
||||
|
||||
|
||||
void
|
||||
LastFmInfoPlugin::fingerprintReturned()
|
||||
{
|
||||
QNetworkReply* reply = qobject_cast<QNetworkReply*>( sender() );
|
||||
|
||||
QFileInfo fi = m_fingerprintMap.value( reply ).first;
|
||||
lastfm::Fingerprint* fp = m_fingerprintMap.value( reply ).second;
|
||||
m_fingerprintMap.remove( reply );
|
||||
|
||||
fp->decode( reply );
|
||||
int id = fp->id();
|
||||
delete fp;
|
||||
|
||||
reply->deleteLater();
|
||||
|
||||
fetchTrackInfo( reply->property( "requestData" ).value< Tomahawk::InfoSystem::InfoRequestData >(), fi, id );
|
||||
}
|
||||
|
||||
|
||||
void
|
||||
LastFmInfoPlugin::trackInfoReturned()
|
||||
{
|
||||
QNetworkReply* reply = qobject_cast<QNetworkReply*>( sender() );
|
||||
QList< lastfm::Track > tracks = parseTrackList( reply );
|
||||
Tomahawk::InfoSystem::InfoRequestData requestData = reply->property( "requestData" ).value< Tomahawk::InfoSystem::InfoRequestData >();
|
||||
QFileInfo fi( reply->property( "file" ).value< QString >() );
|
||||
|
||||
QVariantMap returnedData;
|
||||
returnedData["file"] = fi.canonicalFilePath();
|
||||
|
||||
if ( !tracks.isEmpty() )
|
||||
{
|
||||
lastfm::Track track = tracks.at( 0 );
|
||||
|
||||
returnedData["artist"] = track.artist().toString();
|
||||
returnedData["title"] = track.title();
|
||||
returnedData["album"] = track.album().toString();
|
||||
returnedData["albumartist"] = track.albumArtist().toString();
|
||||
}
|
||||
else
|
||||
{
|
||||
tDebug() << "FP: we didn't find a result for " << fi.canonicalFilePath();
|
||||
}
|
||||
|
||||
emit info( requestData, returnedData );
|
||||
}
|
||||
|
||||
|
||||
void
|
||||
LastFmInfoPlugin::settingsChanged()
|
||||
{
|
||||
|
@@ -27,10 +27,18 @@
|
||||
#include <lastfm/Track.h>
|
||||
#include <lastfm/Audioscrobbler.h>
|
||||
#include <lastfm/ScrobblePoint.h>
|
||||
#include <lastfm/Fingerprint.h>
|
||||
|
||||
#include <QObject>
|
||||
|
||||
class QNetworkReply;
|
||||
class QFileInfo;
|
||||
|
||||
/*namespace lastm
|
||||
{
|
||||
class Fingerprint;
|
||||
class FingerprintId;
|
||||
}*/
|
||||
|
||||
namespace Tomahawk
|
||||
{
|
||||
@@ -65,6 +73,8 @@ public slots:
|
||||
void albumInfoReturned();
|
||||
void chartReturned();
|
||||
void similarTracksReturned();
|
||||
void fingerprintReturned();
|
||||
void trackInfoReturned();
|
||||
|
||||
protected slots:
|
||||
virtual void init();
|
||||
@@ -81,6 +91,9 @@ private:
|
||||
void fetchChart( Tomahawk::InfoSystem::InfoRequestData requestData );
|
||||
void fetchChartCapabilities( Tomahawk::InfoSystem::InfoRequestData requestData );
|
||||
void fetchSimilarTracks( Tomahawk::InfoSystem::InfoRequestData requestData );
|
||||
void fetchFingerprint( Tomahawk::InfoSystem::InfoRequestData requestData );
|
||||
|
||||
void fetchTrackInfo( Tomahawk::InfoSystem::InfoRequestData requestData, const QFileInfo& fi, int id );
|
||||
|
||||
void createScrobbler();
|
||||
void nowPlaying( const QVariant& input );
|
||||
@@ -97,6 +110,8 @@ private:
|
||||
QString m_pw;
|
||||
|
||||
QList< QUrl > m_badUrls;
|
||||
|
||||
QMap< QNetworkReply* , QPair< QFileInfo, lastfm::Fingerprint* > > m_fingerprintMap;
|
||||
};
|
||||
|
||||
}
|
||||
|
514
src/libtomahawk/accounts/lastfm/MadSource.cpp
Normal file
514
src/libtomahawk/accounts/lastfm/MadSource.cpp
Normal file
@@ -0,0 +1,514 @@
|
||||
/*
|
||||
Copyright 2009 Last.fm Ltd.
|
||||
Copyright 2009 John Stamp <jstamp@users.sourceforge.net>
|
||||
|
||||
This file is part of liblastfm.
|
||||
|
||||
liblastfm 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.
|
||||
|
||||
liblastfm 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 liblastfm. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include <iostream>
|
||||
#include <fstream>
|
||||
#include <limits>
|
||||
#include <climits>
|
||||
#include <cstdlib>
|
||||
#include <sstream>
|
||||
#include <cassert>
|
||||
#include <stdexcept>
|
||||
#include "MadSource.h"
|
||||
|
||||
#undef max // was definded in mad
|
||||
|
||||
using namespace std;
|
||||
|
||||
|
||||
// -----------------------------------------------------------
|
||||
|
||||
MadSource::MadSource()
|
||||
: m_pMP3_Buffer ( new unsigned char[m_MP3_BufferSize+MAD_BUFFER_GUARD] )
|
||||
{}
|
||||
|
||||
// -----------------------------------------------------------
|
||||
|
||||
MadSource::~MadSource()
|
||||
{
|
||||
if ( m_inputFile.isOpen() )
|
||||
{
|
||||
m_inputFile.close();
|
||||
mad_synth_finish(&m_mad_synth);
|
||||
mad_frame_finish(&m_mad_frame);
|
||||
mad_stream_finish(&m_mad_stream);
|
||||
}
|
||||
if (m_pMP3_Buffer) delete[] m_pMP3_Buffer;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------
|
||||
|
||||
inline short f2s(mad_fixed_t f)
|
||||
{
|
||||
/* A fixed point number is formed of the following bit pattern:
|
||||
*
|
||||
* SWWWFFFFFFFFFFFFFFFFFFFFFFFFFFFF
|
||||
* MSB LSB
|
||||
* S ==> Sign (0 is positive, 1 is negative)
|
||||
* W ==> Whole part bits
|
||||
* F ==> Fractional part bits
|
||||
*
|
||||
* This pattern contains MAD_F_FRACBITS fractional bits, one
|
||||
* should alway use this macro when working on the bits of a fixed
|
||||
* point number. It is not guaranteed to be constant over the
|
||||
* different platforms supported by libmad.
|
||||
*
|
||||
* The signed short value is formed, after clipping, by the least
|
||||
* significant whole part bit, followed by the 15 most significant
|
||||
* fractional part bits. Warning: this is a quick and dirty way to
|
||||
* compute the 16-bit number, madplay includes much better
|
||||
* algorithms.
|
||||
*/
|
||||
|
||||
/* Clipping */
|
||||
if(f >= MAD_F_ONE)
|
||||
return(SHRT_MAX);
|
||||
if(f <= -MAD_F_ONE)
|
||||
return(-SHRT_MAX);
|
||||
|
||||
/* Conversion. */
|
||||
f = f >> (MAD_F_FRACBITS-15);
|
||||
return (signed short)f;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------
|
||||
|
||||
string MadSource::MadErrorString(const mad_error& error)
|
||||
{
|
||||
switch(error)
|
||||
{
|
||||
/* Generic unrecoverable errors. */
|
||||
case MAD_ERROR_BUFLEN:
|
||||
return("input buffer too small (or EOF)");
|
||||
case MAD_ERROR_BUFPTR:
|
||||
return("invalid (null) buffer pointer");
|
||||
case MAD_ERROR_NOMEM:
|
||||
return("not enough memory");
|
||||
|
||||
/* Frame header related unrecoverable errors. */
|
||||
case MAD_ERROR_LOSTSYNC:
|
||||
return("lost synchronization");
|
||||
case MAD_ERROR_BADLAYER:
|
||||
return("reserved header layer value");
|
||||
case MAD_ERROR_BADBITRATE:
|
||||
return("forbidden bitrate value");
|
||||
case MAD_ERROR_BADSAMPLERATE:
|
||||
return("reserved sample frequency value");
|
||||
case MAD_ERROR_BADEMPHASIS:
|
||||
return("reserved emphasis value");
|
||||
|
||||
/* Recoverable errors */
|
||||
case MAD_ERROR_BADCRC:
|
||||
return("CRC check failed");
|
||||
case MAD_ERROR_BADBITALLOC:
|
||||
return("forbidden bit allocation value");
|
||||
case MAD_ERROR_BADSCALEFACTOR:
|
||||
return("bad scalefactor index");
|
||||
case MAD_ERROR_BADFRAMELEN:
|
||||
return("bad frame length");
|
||||
case MAD_ERROR_BADBIGVALUES:
|
||||
return("bad big_values count");
|
||||
case MAD_ERROR_BADBLOCKTYPE:
|
||||
return("reserved block_type");
|
||||
case MAD_ERROR_BADSCFSI:
|
||||
return("bad scalefactor selection info");
|
||||
case MAD_ERROR_BADDATAPTR:
|
||||
return("bad main_data_begin pointer");
|
||||
case MAD_ERROR_BADPART3LEN:
|
||||
return("bad audio data length");
|
||||
case MAD_ERROR_BADHUFFTABLE:
|
||||
return("bad Huffman table select");
|
||||
case MAD_ERROR_BADHUFFDATA:
|
||||
return("Huffman data overrun");
|
||||
case MAD_ERROR_BADSTEREO:
|
||||
return("incompatible block_type for JS");
|
||||
|
||||
/* Unknown error. This switch may be out of sync with libmad's
|
||||
* defined error codes.
|
||||
*/
|
||||
default:
|
||||
return("Unknown error code");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
bool MadSource::isRecoverable(const mad_error& error, bool log)
|
||||
{
|
||||
if (MAD_RECOVERABLE (error))
|
||||
{
|
||||
/* Do not print a message if the error is a loss of
|
||||
* synchronization and this loss is due to the end of
|
||||
* stream guard bytes. (See the comments marked {3}
|
||||
* supra for more informations about guard bytes.)
|
||||
*/
|
||||
if (error != MAD_ERROR_LOSTSYNC /*|| mad_stream.this_frame != pGuard */ && log)
|
||||
{
|
||||
cerr << "Recoverable frame level error: "
|
||||
<< MadErrorString(error) << endl;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (error == MAD_ERROR_BUFLEN)
|
||||
return true;
|
||||
else
|
||||
{
|
||||
stringstream ss;
|
||||
|
||||
ss << "Unrecoverable frame level error: "
|
||||
<< MadErrorString (error) << endl;
|
||||
throw ss.str();
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------
|
||||
|
||||
void MadSource::init(const QString& fileName)
|
||||
{
|
||||
m_inputFile.setFileName( m_fileName = fileName );
|
||||
bool fine = m_inputFile.open( QIODevice::ReadOnly );
|
||||
|
||||
if ( !fine )
|
||||
{
|
||||
throw std::runtime_error ("Cannot load mp3 file!");
|
||||
}
|
||||
|
||||
mad_stream_init(&m_mad_stream);
|
||||
mad_frame_init (&m_mad_frame);
|
||||
mad_synth_init (&m_mad_synth);
|
||||
mad_timer_reset(&m_mad_timer);
|
||||
|
||||
m_pcmpos = m_mad_synth.pcm.length;
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
/*QString MadSource::getMbid()
|
||||
{
|
||||
char out[MBID_BUFFER_SIZE];
|
||||
int const r = getMP3_MBID( QFile::encodeName( m_fileName ), out );
|
||||
if (r == 0)
|
||||
return QString::fromLatin1( out );
|
||||
return QString();
|
||||
}*/
|
||||
|
||||
void MadSource::getInfo(int& lengthSecs, int& samplerate, int& bitrate, int& nchannels )
|
||||
{
|
||||
// get the header plus some other stuff..
|
||||
QFile inputFile(m_fileName);
|
||||
bool fine = inputFile.open( QIODevice::ReadOnly );
|
||||
|
||||
if ( !fine )
|
||||
{
|
||||
throw std::runtime_error ("ERROR: Cannot load file for getInfo!");
|
||||
return;
|
||||
}
|
||||
|
||||
unsigned char* pMP3_Buffer = new unsigned char[m_MP3_BufferSize+MAD_BUFFER_GUARD];
|
||||
|
||||
mad_stream madStream;
|
||||
mad_header madHeader;
|
||||
mad_timer_t madTimer;
|
||||
|
||||
mad_stream_init(&madStream);
|
||||
mad_timer_reset(&madTimer);
|
||||
|
||||
double avgSamplerate = 0;
|
||||
double avgBitrate = 0;
|
||||
double avgNChannels = 0;
|
||||
int nFrames = 0;
|
||||
|
||||
while ( fetchData( inputFile, pMP3_Buffer, m_MP3_BufferSize, madStream) )
|
||||
{
|
||||
if ( mad_header_decode(&madHeader, &madStream) != 0 )
|
||||
{
|
||||
if ( isRecoverable(madStream.error) )
|
||||
continue;
|
||||
else
|
||||
break;
|
||||
}
|
||||
|
||||
mad_timer_add(&madTimer, madHeader.duration);
|
||||
|
||||
avgSamplerate += madHeader.samplerate;
|
||||
avgBitrate += madHeader.bitrate;
|
||||
|
||||
if ( madHeader.mode == MAD_MODE_SINGLE_CHANNEL )
|
||||
++avgNChannels;
|
||||
else
|
||||
avgNChannels += 2;
|
||||
|
||||
++nFrames;
|
||||
}
|
||||
|
||||
inputFile.close();
|
||||
mad_stream_finish(&madStream);
|
||||
mad_header_finish(&madHeader);
|
||||
delete[] pMP3_Buffer;
|
||||
|
||||
|
||||
lengthSecs = static_cast<int>(madTimer.seconds);
|
||||
samplerate = static_cast<int>( (avgSamplerate/nFrames) + 0.5 );
|
||||
bitrate = static_cast<int>( (avgBitrate/nFrames) + 0.5 );
|
||||
nchannels = static_cast<int>( (avgNChannels/nFrames) + 0.5 );
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------
|
||||
|
||||
|
||||
bool MadSource::fetchData( QFile& mp3File,
|
||||
unsigned char* pMP3_Buffer,
|
||||
const int MP3_BufferSize,
|
||||
mad_stream& madStream )
|
||||
{
|
||||
unsigned char *pReadStart = NULL;
|
||||
unsigned char *pGuard = NULL;
|
||||
|
||||
if ( madStream.buffer == NULL ||
|
||||
madStream.error == MAD_ERROR_BUFLEN )
|
||||
{
|
||||
|
||||
size_t readSize;
|
||||
size_t remaining;
|
||||
|
||||
/* {2} libmad may not consume all bytes of the input
|
||||
* buffer. If the last frame in the buffer is not wholly
|
||||
* contained by it, then that frame's start is pointed by
|
||||
* the next_frame member of the Stream structure. This
|
||||
* common situation occurs when mad_frame_decode() fails,
|
||||
* sets the stream error code to MAD_ERROR_BUFLEN, and
|
||||
* sets the next_frame pointer to a non NULL value. (See
|
||||
* also the comment marked {4} bellow.)
|
||||
*
|
||||
* When this occurs, the remaining unused bytes must be
|
||||
* put back at the beginning of the buffer and taken in
|
||||
* account before refilling the buffer. This means that
|
||||
* the input buffer must be large enough to hold a whole
|
||||
* frame at the highest observable bit-rate (currently 448
|
||||
* kb/s). XXX=XXX Is 2016 bytes the size of the largest
|
||||
* frame? (448000*(1152/32000))/8
|
||||
*/
|
||||
if (madStream.next_frame != NULL)
|
||||
{
|
||||
remaining = madStream.bufend - madStream.next_frame;
|
||||
memmove (pMP3_Buffer, madStream.next_frame, remaining);
|
||||
|
||||
pReadStart = pMP3_Buffer + remaining;
|
||||
readSize = MP3_BufferSize - remaining;
|
||||
}
|
||||
else
|
||||
{
|
||||
readSize = MP3_BufferSize;
|
||||
pReadStart = pMP3_Buffer;
|
||||
remaining = 0;
|
||||
}
|
||||
|
||||
readSize = mp3File.read( reinterpret_cast<char*>(pReadStart), readSize );
|
||||
|
||||
// nothing else to read!
|
||||
if (readSize <= 0)
|
||||
return false;
|
||||
|
||||
if ( mp3File.atEnd() )
|
||||
{
|
||||
pGuard = pReadStart + readSize;
|
||||
|
||||
memset (pGuard, 0, MAD_BUFFER_GUARD);
|
||||
readSize += MAD_BUFFER_GUARD;
|
||||
}
|
||||
|
||||
// Pipe the new buffer content to libmad's stream decoder facility.
|
||||
mad_stream_buffer( &madStream, pMP3_Buffer,
|
||||
static_cast<unsigned int>(readSize + remaining));
|
||||
|
||||
madStream.error = MAD_ERROR_NONE;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
void MadSource::skipSilence(double silenceThreshold /* = 0.0001 */)
|
||||
{
|
||||
mad_frame madFrame;
|
||||
mad_synth madSynth;
|
||||
|
||||
mad_frame_init(&madFrame);
|
||||
mad_synth_init (&madSynth);
|
||||
|
||||
silenceThreshold *= static_cast<double>( numeric_limits<short>::max() );
|
||||
|
||||
for (;;)
|
||||
{
|
||||
if ( !fetchData( m_inputFile, m_pMP3_Buffer, m_MP3_BufferSize, m_mad_stream) )
|
||||
break;
|
||||
|
||||
if ( mad_frame_decode(&madFrame, &m_mad_stream) != 0 )
|
||||
{
|
||||
if ( isRecoverable(m_mad_stream.error) )
|
||||
continue;
|
||||
else
|
||||
break;
|
||||
}
|
||||
|
||||
mad_synth_frame (&madSynth, &madFrame);
|
||||
|
||||
double sum = 0;
|
||||
|
||||
switch (madSynth.pcm.channels)
|
||||
{
|
||||
case 1:
|
||||
for (size_t j = 0; j < madSynth.pcm.length; ++j)
|
||||
sum += abs(f2s(madSynth.pcm.samples[0][j]));
|
||||
break;
|
||||
case 2:
|
||||
for (size_t j = 0; j < madSynth.pcm.length; ++j)
|
||||
sum += abs(f2s(
|
||||
(madSynth.pcm.samples[0][j] >> 1)
|
||||
+ (madSynth.pcm.samples[1][j] >> 1)));
|
||||
break;
|
||||
}
|
||||
|
||||
if ( (sum >= silenceThreshold * madSynth.pcm.length) )
|
||||
break;
|
||||
}
|
||||
|
||||
mad_frame_finish(&madFrame);
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
void MadSource::skip(const int mSecs)
|
||||
{
|
||||
if ( mSecs <= 0 )
|
||||
return;
|
||||
|
||||
mad_header madHeader;
|
||||
mad_header_init(&madHeader);
|
||||
|
||||
for (;;)
|
||||
{
|
||||
if (!fetchData( m_inputFile, m_pMP3_Buffer, m_MP3_BufferSize, m_mad_stream))
|
||||
break;
|
||||
|
||||
if ( mad_header_decode(&madHeader, &m_mad_stream) != 0 )
|
||||
{
|
||||
if ( isRecoverable(m_mad_stream.error) )
|
||||
continue;
|
||||
else
|
||||
break;
|
||||
}
|
||||
|
||||
mad_timer_add(&m_mad_timer, madHeader.duration);
|
||||
|
||||
if ( mad_timer_count(m_mad_timer, MAD_UNITS_MILLISECONDS) >= mSecs )
|
||||
break;
|
||||
}
|
||||
|
||||
mad_header_finish(&madHeader);
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------
|
||||
|
||||
int MadSource::updateBuffer(signed short* pBuffer, size_t bufferSize)
|
||||
{
|
||||
size_t nwrit = 0; //number of samples written to the output buffer
|
||||
|
||||
for (;;)
|
||||
{
|
||||
// get a (valid) frame
|
||||
// m_pcmpos == 0 could mean two things
|
||||
// - we have completely decoded a frame, but the output buffer is still
|
||||
// not full (it would make more sense for pcmpos == pcm.length(), but
|
||||
// the loop assigns pcmpos = 0 at the end and does it this way!
|
||||
// - we are starting a stream
|
||||
if ( m_pcmpos == m_mad_synth.pcm.length )
|
||||
{
|
||||
if ( !fetchData( m_inputFile, m_pMP3_Buffer, m_MP3_BufferSize, m_mad_stream) )
|
||||
{
|
||||
break; // nothing else to read
|
||||
}
|
||||
|
||||
// decode the frame
|
||||
if (mad_frame_decode (&m_mad_frame, &m_mad_stream))
|
||||
{
|
||||
if ( isRecoverable(m_mad_stream.error) )
|
||||
continue;
|
||||
else
|
||||
break;
|
||||
} // if (mad_frame_decode (&madFrame, &madStream))
|
||||
|
||||
mad_timer_add (&m_mad_timer, m_mad_frame.header.duration);
|
||||
mad_synth_frame (&m_mad_synth, &m_mad_frame);
|
||||
|
||||
m_pcmpos = 0;
|
||||
}
|
||||
|
||||
size_t samples_for_mp3 = m_mad_synth.pcm.length - m_pcmpos;
|
||||
size_t samples_for_buf = bufferSize - nwrit;
|
||||
signed short* pBufferIt = pBuffer + nwrit;
|
||||
size_t i = 0, j = 0;
|
||||
|
||||
switch( m_mad_synth.pcm.channels )
|
||||
{
|
||||
case 1:
|
||||
{
|
||||
size_t samples_to_use = min (samples_for_mp3, samples_for_buf);
|
||||
for (i = 0; i < samples_to_use; ++i )
|
||||
pBufferIt[i] = f2s( m_mad_synth.pcm.samples[0][i+m_pcmpos] );
|
||||
}
|
||||
j = i;
|
||||
break;
|
||||
|
||||
case 2:
|
||||
for (; i < samples_for_mp3 && j < samples_for_buf ; ++i, j+=2 )
|
||||
{
|
||||
pBufferIt[j] = f2s( m_mad_synth.pcm.samples[0][i+m_pcmpos] );
|
||||
pBufferIt[j+1] = f2s( m_mad_synth.pcm.samples[1][i+m_pcmpos] );
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
cerr << "wtf kind of mp3 has " << m_mad_synth.pcm.channels << " channels??\n";
|
||||
break;
|
||||
}
|
||||
|
||||
m_pcmpos += i;
|
||||
nwrit += j;
|
||||
|
||||
assert( nwrit <= bufferSize );
|
||||
|
||||
if (nwrit == bufferSize)
|
||||
return static_cast<int>(nwrit);
|
||||
}
|
||||
|
||||
return static_cast<int>(nwrit);
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
|
69
src/libtomahawk/accounts/lastfm/MadSource.h
Normal file
69
src/libtomahawk/accounts/lastfm/MadSource.h
Normal file
@@ -0,0 +1,69 @@
|
||||
/*
|
||||
Copyright 2009 Last.fm Ltd.
|
||||
Copyright 2009 John Stamp <jstamp@users.sourceforge.net>
|
||||
|
||||
This file is part of liblastfm.
|
||||
|
||||
liblastfm 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.
|
||||
|
||||
liblastfm 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 liblastfm. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef __MP3_SOURCE_H__
|
||||
#define __MP3_SOURCE_H__
|
||||
|
||||
#include <lastfm/FingerprintableSource.h>
|
||||
#include <QFile>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <fstream>
|
||||
#include <mad.h>
|
||||
|
||||
|
||||
class MadSource : public lastfm::FingerprintableSource
|
||||
{
|
||||
public:
|
||||
MadSource();
|
||||
~MadSource();
|
||||
|
||||
virtual void getInfo(int& lengthSecs, int& samplerate, int& bitrate, int& nchannels);
|
||||
virtual void init(const QString& fileName);
|
||||
virtual int updateBuffer(signed short* pBuffer, size_t bufferSize);
|
||||
virtual void skip(const int mSecs);
|
||||
virtual void skipSilence(double silenceThreshold = 0.0001);
|
||||
virtual bool eof() const { return m_inputFile.atEnd(); }
|
||||
|
||||
private:
|
||||
static bool fetchData( QFile& mp3File,
|
||||
unsigned char* pMP3_Buffer,
|
||||
const int MP3_BufferSize,
|
||||
mad_stream& madStream );
|
||||
|
||||
static bool isRecoverable(const mad_error& error, bool log = false);
|
||||
|
||||
static std::string MadErrorString(const mad_error& error);
|
||||
|
||||
struct mad_stream m_mad_stream;
|
||||
struct mad_frame m_mad_frame;
|
||||
mad_timer_t m_mad_timer;
|
||||
struct mad_synth m_mad_synth;
|
||||
|
||||
QFile m_inputFile;
|
||||
|
||||
unsigned char* m_pMP3_Buffer;
|
||||
static const int m_MP3_BufferSize = (5*8192);
|
||||
QString m_fileName;
|
||||
|
||||
size_t m_pcmpos;
|
||||
};
|
||||
|
||||
#endif
|
@@ -35,7 +35,6 @@
|
||||
|
||||
#include <QCoreApplication>
|
||||
|
||||
|
||||
using namespace Tomahawk;
|
||||
|
||||
void
|
||||
@@ -101,7 +100,7 @@ DirLister::scanDir( QDir dir, int depth )
|
||||
}
|
||||
|
||||
|
||||
DirListerThreadController::DirListerThreadController( QObject *parent )
|
||||
DirListerThreadController::DirListerThreadController( QObject* parent )
|
||||
: QThread( parent )
|
||||
{
|
||||
tDebug() << Q_FUNC_INFO;
|
||||
@@ -139,6 +138,7 @@ MusicScanner::MusicScanner( MusicScanner::ScanMode scanMode, const QStringList&
|
||||
, m_paths( paths )
|
||||
, m_batchsize( bs )
|
||||
, m_dirListerThreadController( 0 )
|
||||
, m_fingerprint( true )
|
||||
{
|
||||
m_ext2mime.insert( "mp3", TomahawkUtils::extensionToMimetype( "mp3" ) );
|
||||
m_ext2mime.insert( "ogg", TomahawkUtils::extensionToMimetype( "ogg" ) );
|
||||
@@ -174,8 +174,9 @@ void
|
||||
MusicScanner::startScan()
|
||||
{
|
||||
tDebug( LOGVERBOSE ) << "Loading mtimes...";
|
||||
m_scanned = m_skipped = m_cmdQueue = 0;
|
||||
m_scanned = m_skipped = m_fingerprinting = m_cmdQueue = 0;
|
||||
m_skippedFiles.clear();
|
||||
m_fingerprintingFiles.clear();
|
||||
|
||||
SourceList::instance()->getLocal()->scanningProgress( m_scanned );
|
||||
|
||||
@@ -183,9 +184,9 @@ MusicScanner::startScan()
|
||||
//FIXME: For multiple collection support make sure the right prefix gets passed in...or not...
|
||||
//bear in mind that simply passing in the top-level of a defined collection means it will not return items that need
|
||||
//to be removed that aren't in that root any longer -- might have to do the filtering in setMTimes based on strings
|
||||
DatabaseCommand_FileMtimes *cmd = new DatabaseCommand_FileMtimes();
|
||||
DatabaseCommand_FileMtimes* cmd = new DatabaseCommand_FileMtimes();
|
||||
connect( cmd, SIGNAL( done( QMap< QString, QMap< unsigned int, unsigned int > > ) ),
|
||||
SLOT( setFileMtimes( QMap< QString, QMap< unsigned int, unsigned int > > ) ) );
|
||||
SLOT( setFileMtimes( QMap< QString, QMap< unsigned int, unsigned int > > ) ) );
|
||||
|
||||
Database::instance()->enqueue( dbcmd_ptr( cmd ) );
|
||||
return;
|
||||
@@ -207,7 +208,7 @@ MusicScanner::scan()
|
||||
tDebug( LOGEXTRA ) << "Num saved file mtimes from last scan:" << m_filemtimes.size();
|
||||
|
||||
connect( this, SIGNAL( batchReady( QVariantList, QVariantList ) ),
|
||||
SLOT( commitBatch( QVariantList, QVariantList ) ), Qt::DirectConnection );
|
||||
SLOT( commitBatch( QVariantList, QVariantList ) ), Qt::DirectConnection );
|
||||
|
||||
if ( m_scanMode == MusicScanner::FileScan )
|
||||
{
|
||||
@@ -225,7 +226,7 @@ void
|
||||
MusicScanner::scanFilePaths()
|
||||
{
|
||||
tDebug( LOGVERBOSE ) << Q_FUNC_INFO;
|
||||
foreach( QString path, m_paths )
|
||||
foreach ( QString path, m_paths )
|
||||
{
|
||||
QFileInfo fi( path );
|
||||
if ( fi.exists() && fi.isReadable() )
|
||||
@@ -244,7 +245,7 @@ MusicScanner::postOps()
|
||||
if ( m_scanMode == MusicScanner::DirScan )
|
||||
{
|
||||
// any remaining stuff that wasnt emitted as a batch:
|
||||
foreach( const QString& key, m_filemtimes.keys() )
|
||||
foreach ( const QString& key, m_filemtimes.keys() )
|
||||
{
|
||||
if ( !m_filemtimes[ key ].keys().isEmpty() )
|
||||
m_filesToDelete << m_filemtimes[ key ].keys().first();
|
||||
@@ -281,8 +282,13 @@ MusicScanner::cleanup()
|
||||
m_dirListerThreadController = 0;
|
||||
}
|
||||
|
||||
tDebug() << Q_FUNC_INFO << "emitting finished!";
|
||||
emit finished();
|
||||
tDebug() << "FP: m_fingerprinting: " << m_fingerprinting;
|
||||
|
||||
if ( m_fingerprinting == 0 )
|
||||
{
|
||||
tDebug() << Q_FUNC_INFO << "emitting finished!";
|
||||
emit finished();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -323,6 +329,64 @@ MusicScanner::commandFinished()
|
||||
}
|
||||
|
||||
|
||||
void
|
||||
MusicScanner::infoSystemInfo( Tomahawk::InfoSystem::InfoRequestData requestData, QVariant output )
|
||||
{
|
||||
if ( requestData.caller != infoid() )
|
||||
return;
|
||||
|
||||
QVariantMap returnedData = output.value< QVariantMap >();
|
||||
QFileInfo fi( returnedData["file"].value< QString >() );
|
||||
|
||||
switch ( requestData.type )
|
||||
{
|
||||
case InfoSystem::InfoFingerprintTrack:
|
||||
|
||||
if ( --m_fingerprinting == 0 )
|
||||
{
|
||||
tDebug() << "FP: got info for all files, disconnecting info slots";
|
||||
|
||||
disconnect( Tomahawk::InfoSystem::InfoSystem::instance(), SIGNAL( info( Tomahawk::InfoSystem::InfoRequestData, QVariant ) ),
|
||||
this, SLOT( infoSystemInfo( Tomahawk::InfoSystem::InfoRequestData, QVariant ) ) );
|
||||
|
||||
disconnect( Tomahawk::InfoSystem::InfoSystem::instance(), SIGNAL( finished( QString ) ),
|
||||
this, SLOT( infoSystemFinished( QString ) ) );
|
||||
}
|
||||
|
||||
if ( fi.isFile() && returnedData.contains( "artist" ) && returnedData.contains( "title" ) )
|
||||
{
|
||||
tDebug() << "received valid metadata: " << returnedData["artist"].toString() << " - " << returnedData["title"].toString();
|
||||
QVariantMap m = readAdditionalMetadata( fi );
|
||||
m["artist"] = returnedData["artist"];
|
||||
m["track"] = returnedData["title"];
|
||||
m["album"] = returnedData["album"];
|
||||
m["albumartist"] = returnedData["albumartist"];
|
||||
|
||||
commitFile( m );
|
||||
}
|
||||
else
|
||||
{
|
||||
tDebug() << "data is not valid";
|
||||
m_skipped++;
|
||||
m_skippedFiles << fi.canonicalFilePath();
|
||||
}
|
||||
break;
|
||||
default:
|
||||
Q_ASSERT( false );
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void
|
||||
MusicScanner::infoSystemFinished( QString target )
|
||||
{
|
||||
if ( target != infoid() )
|
||||
return;
|
||||
|
||||
QMetaObject::invokeMethod( this, "postOps", Qt::QueuedConnection );
|
||||
}
|
||||
|
||||
|
||||
void
|
||||
MusicScanner::scanFile( const QFileInfo& fi )
|
||||
{
|
||||
@@ -345,13 +409,7 @@ MusicScanner::scanFile( const QFileInfo& fi )
|
||||
if ( m.toMap().isEmpty() )
|
||||
return;
|
||||
|
||||
m_scannedfiles << m;
|
||||
if ( m_batchsize != 0 && (quint32)m_scannedfiles.length() >= m_batchsize )
|
||||
{
|
||||
emit batchReady( m_scannedfiles, m_filesToDelete );
|
||||
m_scannedfiles.clear();
|
||||
m_filesToDelete.clear();
|
||||
}
|
||||
commitFile( m );
|
||||
}
|
||||
|
||||
|
||||
@@ -371,31 +429,16 @@ MusicScanner::readFile( const QFileInfo& fi )
|
||||
if ( m_scanned % 100 == 0 )
|
||||
tDebug( LOGINFO ) << "Scan progress:" << m_scanned << fi.canonicalFilePath();
|
||||
|
||||
#ifdef COMPLEX_TAGLIB_FILENAME
|
||||
const wchar_t *encodedName = reinterpret_cast< const wchar_t * >( fi.canonicalFilePath().utf16() );
|
||||
#else
|
||||
QByteArray fileName = QFile::encodeName( fi.canonicalFilePath() );
|
||||
const char *encodedName = fileName.constData();
|
||||
#endif
|
||||
TagLib::FileRef f = getTagLibFileRef( fi );
|
||||
|
||||
TagLib::FileRef f( encodedName );
|
||||
if ( f.isNull() || !f.tag() )
|
||||
if ( !m_fingerprint && ( f.isNull() || !f.tag() ) )
|
||||
{
|
||||
m_skippedFiles << fi.canonicalFilePath();
|
||||
m_skipped++;
|
||||
return QVariantMap();
|
||||
}
|
||||
|
||||
int bitrate = 0;
|
||||
int duration = 0;
|
||||
|
||||
Tag *tag = Tag::fromFile( f );
|
||||
if ( f.audioProperties() )
|
||||
{
|
||||
TagLib::AudioProperties *properties = f.audioProperties();
|
||||
duration = properties->length();
|
||||
bitrate = properties->bitrate();
|
||||
}
|
||||
|
||||
QString artist, album, track;
|
||||
if ( tag )
|
||||
@@ -406,33 +449,129 @@ MusicScanner::readFile( const QFileInfo& fi )
|
||||
}
|
||||
if ( !tag || artist.isEmpty() || track.isEmpty() )
|
||||
{
|
||||
// FIXME: do some clever filename guessing
|
||||
m_skippedFiles << fi.canonicalFilePath();
|
||||
m_skipped++;
|
||||
if ( m_fingerprint )
|
||||
{
|
||||
m_fingerprintingFiles << fi.canonicalFilePath();
|
||||
m_fingerprinting++;
|
||||
fingerprintFile( fi );
|
||||
}
|
||||
else
|
||||
{
|
||||
m_skippedFiles << fi.canonicalFilePath();
|
||||
m_skipped++;
|
||||
}
|
||||
return QVariantMap();
|
||||
}
|
||||
|
||||
QString mimetype = m_ext2mime.value( suffix );
|
||||
QString url( "file://%1" );
|
||||
m_scanned++;
|
||||
|
||||
QVariantMap m = readAdditionalMetadata( fi );
|
||||
|
||||
QVariantMap m;
|
||||
m["url"] = url.arg( fi.canonicalFilePath() );
|
||||
m["mtime"] = fi.lastModified().toUTC().toTime_t();
|
||||
m["size"] = (unsigned int)fi.size();
|
||||
m["mimetype"] = mimetype;
|
||||
m["duration"] = duration;
|
||||
m["bitrate"] = bitrate;
|
||||
m["artist"] = artist;
|
||||
m["album"] = album;
|
||||
m["track"] = track;
|
||||
|
||||
m["albumpos"] = tag->track();
|
||||
m["year"] = tag->year();
|
||||
m["albumartist"] = tag->albumArtist();
|
||||
m["composer"] = tag->composer();
|
||||
m["discnumber"] = tag->discNumber();
|
||||
m["hash"] = ""; // TODO
|
||||
|
||||
m_scanned++;
|
||||
return m;
|
||||
}
|
||||
|
||||
|
||||
void
|
||||
MusicScanner::commitFile( const QVariant& m )
|
||||
{
|
||||
m_scannedfiles << m;
|
||||
if ( m_batchsize != 0 && ( quint32 )m_scannedfiles.length() >= m_batchsize )
|
||||
{
|
||||
emit batchReady( m_scannedfiles, m_filesToDelete );
|
||||
m_scannedfiles.clear();
|
||||
m_filesToDelete.clear();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
TagLib::FileRef
|
||||
MusicScanner::getTagLibFileRef(const QFileInfo& fi) const
|
||||
{
|
||||
#ifdef COMPLEX_TAGLIB_FILENAME
|
||||
const wchar_t* encodedName = reinterpret_cast< const wchar_t* >( fi.canonicalFilePath().utf16() );
|
||||
#else
|
||||
QByteArray fileName = QFile::encodeName( fi.canonicalFilePath() );
|
||||
const char* encodedName = fileName.constData();
|
||||
#endif
|
||||
|
||||
TagLib::FileRef f( encodedName );
|
||||
return f;
|
||||
}
|
||||
|
||||
|
||||
void
|
||||
MusicScanner::fingerprintFile( const QFileInfo& fi )
|
||||
{
|
||||
tDebug() << "Fingerprinting track: " << fi.canonicalFilePath();
|
||||
|
||||
Tomahawk::InfoSystem::InfoStringHash hash;
|
||||
hash["file"] = fi.canonicalFilePath();
|
||||
|
||||
Tomahawk::InfoSystem::InfoRequestData requestData;
|
||||
requestData.caller = infoid();
|
||||
requestData.input = QVariant::fromValue< Tomahawk::InfoSystem::InfoStringHash >( hash );
|
||||
requestData.type = Tomahawk::InfoSystem::InfoFingerprintTrack;
|
||||
requestData.allSources = true;
|
||||
|
||||
if ( m_fingerprinting == 1 )
|
||||
{
|
||||
connect( Tomahawk::InfoSystem::InfoSystem::instance(),
|
||||
SIGNAL( info( Tomahawk::InfoSystem::InfoRequestData, QVariant ) ),
|
||||
SLOT( infoSystemInfo( Tomahawk::InfoSystem::InfoRequestData, QVariant ) ), Qt::UniqueConnection );
|
||||
|
||||
connect( Tomahawk::InfoSystem::InfoSystem::instance(),
|
||||
SIGNAL( finished( QString ) ),
|
||||
SLOT( infoSystemFinished( QString ) ), Qt::UniqueConnection );
|
||||
}
|
||||
|
||||
Tomahawk::InfoSystem::InfoSystem::instance()->getInfo( requestData );
|
||||
}
|
||||
|
||||
|
||||
QVariantMap
|
||||
MusicScanner::readAdditionalMetadata( const QFileInfo& fi ) const
|
||||
{
|
||||
int bitrate = 0;
|
||||
int duration = 0;
|
||||
|
||||
QString mimetype = m_ext2mime.value( fi.suffix().toLower() );
|
||||
QString url( "file://%1" );
|
||||
|
||||
TagLib::FileRef f = getTagLibFileRef( fi );
|
||||
|
||||
if ( f.audioProperties() )
|
||||
{
|
||||
TagLib::AudioProperties* properties = f.audioProperties();
|
||||
duration = properties->length();
|
||||
bitrate = properties->bitrate();
|
||||
}
|
||||
|
||||
QVariantMap m;
|
||||
m["url"] = url.arg( fi.canonicalFilePath() );
|
||||
m["mtime"] = fi.lastModified().toUTC().toTime_t();
|
||||
m["size"] = ( unsigned int )fi.size();
|
||||
m["mimetype"] = mimetype;
|
||||
m["duration"] = duration;
|
||||
m["bitrate"] = bitrate;
|
||||
m["hash"] = ""; // TODO
|
||||
|
||||
return m;
|
||||
}
|
||||
|
||||
|
||||
|
||||
QString
|
||||
MusicScanner::infoid() const
|
||||
{
|
||||
return "MusicScanner";
|
||||
}
|
||||
|
@@ -39,6 +39,14 @@
|
||||
#include <QTimer>
|
||||
#include <QVariantMap>
|
||||
|
||||
namespace Tomahawk
|
||||
{
|
||||
namespace InfoSystem
|
||||
{
|
||||
class InfoRequestData;
|
||||
}
|
||||
}
|
||||
|
||||
// descend dir tree comparing dir mtimes to last known mtime
|
||||
// emit signal for any dir with new content, so we can scan it.
|
||||
// finally, emit the list of new mtimes we observed.
|
||||
@@ -113,6 +121,7 @@ signals:
|
||||
private:
|
||||
QVariant readFile( const QFileInfo& fi );
|
||||
void executeCommand( Tomahawk::dbcmd_ptr cmd );
|
||||
void fingerprintFile ( const QFileInfo& fi );
|
||||
|
||||
private slots:
|
||||
void postOps();
|
||||
@@ -124,16 +133,27 @@ private slots:
|
||||
void commitBatch( const QVariantList& tracks, const QVariantList& deletethese );
|
||||
void commandFinished();
|
||||
|
||||
void infoSystemInfo( Tomahawk::InfoSystem::InfoRequestData requestData, QVariant output );
|
||||
void infoSystemFinished( QString target );
|
||||
|
||||
private:
|
||||
TagLib::FileRef getTagLibFileRef( const QFileInfo& fi ) const;
|
||||
void scanFilePaths();
|
||||
void commitFile( const QVariant& m );
|
||||
QString infoid() const;
|
||||
|
||||
|
||||
QVariantMap readAdditionalMetadata( const QFileInfo& fi ) const;
|
||||
|
||||
MusicScanner::ScanMode m_scanMode;
|
||||
QStringList m_paths;
|
||||
QMap<QString, QString> m_ext2mime; // eg: mp3 -> audio/mpeg
|
||||
unsigned int m_scanned;
|
||||
unsigned int m_skipped;
|
||||
unsigned int m_fingerprinting;
|
||||
|
||||
QList<QString> m_skippedFiles;
|
||||
QList<QString> m_fingerprintingFiles;
|
||||
QMap<QString, QMap< unsigned int, unsigned int > > m_filemtimes;
|
||||
|
||||
unsigned int m_cmdQueue;
|
||||
@@ -143,6 +163,8 @@ private:
|
||||
quint32 m_batchsize;
|
||||
|
||||
DirListerThreadController* m_dirListerThreadController;
|
||||
|
||||
bool m_fingerprint;
|
||||
};
|
||||
|
||||
#endif
|
||||
|
Reference in New Issue
Block a user