mirror of
https://github.com/tomahawk-player/tomahawk.git
synced 2025-08-31 09:32:03 +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 "" "")
|
macro_log_feature(QTSPARKLE_FOUND "qtsparkle" "Library for creating auto updaters written in Qt" "https://github.com/davidsansome/qtsparkle" FALSE "" "")
|
||||||
endif( WIN32 )
|
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
|
#TODO: support external qxt
|
||||||
set(QXTWEB_FOUND TRUE)
|
set(QXTWEB_FOUND TRUE)
|
||||||
set(QXTWEB_LIBRARIES qxtweb-standalone)
|
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/LastFmAccount.cpp
|
||||||
accounts/lastfm/LastFmConfig.cpp
|
accounts/lastfm/LastFmConfig.cpp
|
||||||
accounts/lastfm/LastFmInfoPlugin.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)
|
ENDIF(LIBLASTFM_FOUND)
|
||||||
|
|
||||||
@@ -521,6 +523,11 @@ TARGET_LINK_LIBRARIES( tomahawklib
|
|||||||
${QT_QTCORE_LIBRARY}
|
${QT_QTCORE_LIBRARY}
|
||||||
${OS_SPECIFIC_LINK_LIBRARIES}
|
${OS_SPECIFIC_LINK_LIBRARIES}
|
||||||
${CMAKE_THREAD_LIBS_INIT}
|
${CMAKE_THREAD_LIBS_INIT}
|
||||||
|
#Temporary only
|
||||||
|
${mad_PKGCONF_LDFLAGS}
|
||||||
|
#CMake should also find fp library
|
||||||
|
lastfm_fingerprint
|
||||||
|
|
||||||
${LINK_LIBRARIES}
|
${LINK_LIBRARIES}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@@ -230,6 +230,8 @@ namespace Tomahawk
|
|||||||
InfoUnLove = 91,
|
InfoUnLove = 91,
|
||||||
InfoShareTrack = 92,
|
InfoShareTrack = 92,
|
||||||
|
|
||||||
|
InfoFingerprintTrack = 95,
|
||||||
|
|
||||||
InfoNotifyUser = 100,
|
InfoNotifyUser = 100,
|
||||||
|
|
||||||
InfoInboxReceived = 101,
|
InfoInboxReceived = 101,
|
||||||
|
@@ -36,8 +36,14 @@
|
|||||||
|
|
||||||
#include <lastfm/ws.h>
|
#include <lastfm/ws.h>
|
||||||
#include <lastfm/XmlQuery.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 <qjson/parser.h>
|
||||||
|
#include <boost/concept_check.hpp>
|
||||||
|
|
||||||
using namespace Tomahawk::Accounts;
|
using namespace Tomahawk::Accounts;
|
||||||
using namespace Tomahawk::InfoSystem;
|
using namespace Tomahawk::InfoSystem;
|
||||||
@@ -48,7 +54,7 @@ LastFmInfoPlugin::LastFmInfoPlugin( LastFmAccount* account )
|
|||||||
, m_account( account )
|
, m_account( account )
|
||||||
, m_scrobbler( 0 )
|
, 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;
|
m_supportedPushTypes << InfoSubmitScrobble << InfoSubmitNowPlaying << InfoLove << InfoUnLove;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -137,6 +143,10 @@ LastFmInfoPlugin::getInfo( Tomahawk::InfoSystem::InfoRequestData requestData )
|
|||||||
fetchSimilarTracks( requestData );
|
fetchSimilarTracks( requestData );
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
case InfoFingerprintTrack:
|
||||||
|
fetchFingerprint( requestData );
|
||||||
|
break;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
dataError( requestData );
|
dataError( requestData );
|
||||||
}
|
}
|
||||||
@@ -408,6 +418,78 @@ LastFmInfoPlugin::fetchAlbumInfo( Tomahawk::InfoSystem::InfoRequestData requestD
|
|||||||
emit getCachedInfo( criteria, Q_INT64_C(2419200000), requestData );
|
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
|
void
|
||||||
LastFmInfoPlugin::notInCacheSlot( QHash<QString, QString> criteria, Tomahawk::InfoSystem::InfoRequestData requestData )
|
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
|
void
|
||||||
LastFmInfoPlugin::settingsChanged()
|
LastFmInfoPlugin::settingsChanged()
|
||||||
{
|
{
|
||||||
|
@@ -27,10 +27,18 @@
|
|||||||
#include <lastfm/Track.h>
|
#include <lastfm/Track.h>
|
||||||
#include <lastfm/Audioscrobbler.h>
|
#include <lastfm/Audioscrobbler.h>
|
||||||
#include <lastfm/ScrobblePoint.h>
|
#include <lastfm/ScrobblePoint.h>
|
||||||
|
#include <lastfm/Fingerprint.h>
|
||||||
|
|
||||||
#include <QObject>
|
#include <QObject>
|
||||||
|
|
||||||
class QNetworkReply;
|
class QNetworkReply;
|
||||||
|
class QFileInfo;
|
||||||
|
|
||||||
|
/*namespace lastm
|
||||||
|
{
|
||||||
|
class Fingerprint;
|
||||||
|
class FingerprintId;
|
||||||
|
}*/
|
||||||
|
|
||||||
namespace Tomahawk
|
namespace Tomahawk
|
||||||
{
|
{
|
||||||
@@ -65,6 +73,8 @@ public slots:
|
|||||||
void albumInfoReturned();
|
void albumInfoReturned();
|
||||||
void chartReturned();
|
void chartReturned();
|
||||||
void similarTracksReturned();
|
void similarTracksReturned();
|
||||||
|
void fingerprintReturned();
|
||||||
|
void trackInfoReturned();
|
||||||
|
|
||||||
protected slots:
|
protected slots:
|
||||||
virtual void init();
|
virtual void init();
|
||||||
@@ -81,6 +91,9 @@ private:
|
|||||||
void fetchChart( Tomahawk::InfoSystem::InfoRequestData requestData );
|
void fetchChart( Tomahawk::InfoSystem::InfoRequestData requestData );
|
||||||
void fetchChartCapabilities( Tomahawk::InfoSystem::InfoRequestData requestData );
|
void fetchChartCapabilities( Tomahawk::InfoSystem::InfoRequestData requestData );
|
||||||
void fetchSimilarTracks( 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 createScrobbler();
|
||||||
void nowPlaying( const QVariant& input );
|
void nowPlaying( const QVariant& input );
|
||||||
@@ -97,6 +110,8 @@ private:
|
|||||||
QString m_pw;
|
QString m_pw;
|
||||||
|
|
||||||
QList< QUrl > m_badUrls;
|
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>
|
#include <QCoreApplication>
|
||||||
|
|
||||||
|
|
||||||
using namespace Tomahawk;
|
using namespace Tomahawk;
|
||||||
|
|
||||||
void
|
void
|
||||||
@@ -101,7 +100,7 @@ DirLister::scanDir( QDir dir, int depth )
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
DirListerThreadController::DirListerThreadController( QObject *parent )
|
DirListerThreadController::DirListerThreadController( QObject* parent )
|
||||||
: QThread( parent )
|
: QThread( parent )
|
||||||
{
|
{
|
||||||
tDebug() << Q_FUNC_INFO;
|
tDebug() << Q_FUNC_INFO;
|
||||||
@@ -139,6 +138,7 @@ MusicScanner::MusicScanner( MusicScanner::ScanMode scanMode, const QStringList&
|
|||||||
, m_paths( paths )
|
, m_paths( paths )
|
||||||
, m_batchsize( bs )
|
, m_batchsize( bs )
|
||||||
, m_dirListerThreadController( 0 )
|
, m_dirListerThreadController( 0 )
|
||||||
|
, m_fingerprint( true )
|
||||||
{
|
{
|
||||||
m_ext2mime.insert( "mp3", TomahawkUtils::extensionToMimetype( "mp3" ) );
|
m_ext2mime.insert( "mp3", TomahawkUtils::extensionToMimetype( "mp3" ) );
|
||||||
m_ext2mime.insert( "ogg", TomahawkUtils::extensionToMimetype( "ogg" ) );
|
m_ext2mime.insert( "ogg", TomahawkUtils::extensionToMimetype( "ogg" ) );
|
||||||
@@ -174,8 +174,9 @@ void
|
|||||||
MusicScanner::startScan()
|
MusicScanner::startScan()
|
||||||
{
|
{
|
||||||
tDebug( LOGVERBOSE ) << "Loading mtimes...";
|
tDebug( LOGVERBOSE ) << "Loading mtimes...";
|
||||||
m_scanned = m_skipped = m_cmdQueue = 0;
|
m_scanned = m_skipped = m_fingerprinting = m_cmdQueue = 0;
|
||||||
m_skippedFiles.clear();
|
m_skippedFiles.clear();
|
||||||
|
m_fingerprintingFiles.clear();
|
||||||
|
|
||||||
SourceList::instance()->getLocal()->scanningProgress( m_scanned );
|
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...
|
//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
|
//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
|
//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 > > ) ),
|
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 ) );
|
Database::instance()->enqueue( dbcmd_ptr( cmd ) );
|
||||||
return;
|
return;
|
||||||
@@ -207,7 +208,7 @@ MusicScanner::scan()
|
|||||||
tDebug( LOGEXTRA ) << "Num saved file mtimes from last scan:" << m_filemtimes.size();
|
tDebug( LOGEXTRA ) << "Num saved file mtimes from last scan:" << m_filemtimes.size();
|
||||||
|
|
||||||
connect( this, SIGNAL( batchReady( QVariantList, QVariantList ) ),
|
connect( this, SIGNAL( batchReady( QVariantList, QVariantList ) ),
|
||||||
SLOT( commitBatch( QVariantList, QVariantList ) ), Qt::DirectConnection );
|
SLOT( commitBatch( QVariantList, QVariantList ) ), Qt::DirectConnection );
|
||||||
|
|
||||||
if ( m_scanMode == MusicScanner::FileScan )
|
if ( m_scanMode == MusicScanner::FileScan )
|
||||||
{
|
{
|
||||||
@@ -225,7 +226,7 @@ void
|
|||||||
MusicScanner::scanFilePaths()
|
MusicScanner::scanFilePaths()
|
||||||
{
|
{
|
||||||
tDebug( LOGVERBOSE ) << Q_FUNC_INFO;
|
tDebug( LOGVERBOSE ) << Q_FUNC_INFO;
|
||||||
foreach( QString path, m_paths )
|
foreach ( QString path, m_paths )
|
||||||
{
|
{
|
||||||
QFileInfo fi( path );
|
QFileInfo fi( path );
|
||||||
if ( fi.exists() && fi.isReadable() )
|
if ( fi.exists() && fi.isReadable() )
|
||||||
@@ -244,7 +245,7 @@ MusicScanner::postOps()
|
|||||||
if ( m_scanMode == MusicScanner::DirScan )
|
if ( m_scanMode == MusicScanner::DirScan )
|
||||||
{
|
{
|
||||||
// any remaining stuff that wasnt emitted as a batch:
|
// 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() )
|
if ( !m_filemtimes[ key ].keys().isEmpty() )
|
||||||
m_filesToDelete << m_filemtimes[ key ].keys().first();
|
m_filesToDelete << m_filemtimes[ key ].keys().first();
|
||||||
@@ -281,8 +282,13 @@ MusicScanner::cleanup()
|
|||||||
m_dirListerThreadController = 0;
|
m_dirListerThreadController = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
tDebug() << Q_FUNC_INFO << "emitting finished!";
|
tDebug() << "FP: m_fingerprinting: " << m_fingerprinting;
|
||||||
emit finished();
|
|
||||||
|
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
|
void
|
||||||
MusicScanner::scanFile( const QFileInfo& fi )
|
MusicScanner::scanFile( const QFileInfo& fi )
|
||||||
{
|
{
|
||||||
@@ -345,13 +409,7 @@ MusicScanner::scanFile( const QFileInfo& fi )
|
|||||||
if ( m.toMap().isEmpty() )
|
if ( m.toMap().isEmpty() )
|
||||||
return;
|
return;
|
||||||
|
|
||||||
m_scannedfiles << m;
|
commitFile( m );
|
||||||
if ( m_batchsize != 0 && (quint32)m_scannedfiles.length() >= m_batchsize )
|
|
||||||
{
|
|
||||||
emit batchReady( m_scannedfiles, m_filesToDelete );
|
|
||||||
m_scannedfiles.clear();
|
|
||||||
m_filesToDelete.clear();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -371,31 +429,16 @@ MusicScanner::readFile( const QFileInfo& fi )
|
|||||||
if ( m_scanned % 100 == 0 )
|
if ( m_scanned % 100 == 0 )
|
||||||
tDebug( LOGINFO ) << "Scan progress:" << m_scanned << fi.canonicalFilePath();
|
tDebug( LOGINFO ) << "Scan progress:" << m_scanned << fi.canonicalFilePath();
|
||||||
|
|
||||||
#ifdef COMPLEX_TAGLIB_FILENAME
|
TagLib::FileRef f = getTagLibFileRef( fi );
|
||||||
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 );
|
if ( !m_fingerprint && ( f.isNull() || !f.tag() ) )
|
||||||
if ( f.isNull() || !f.tag() )
|
|
||||||
{
|
{
|
||||||
m_skippedFiles << fi.canonicalFilePath();
|
m_skippedFiles << fi.canonicalFilePath();
|
||||||
m_skipped++;
|
m_skipped++;
|
||||||
return QVariantMap();
|
return QVariantMap();
|
||||||
}
|
}
|
||||||
|
|
||||||
int bitrate = 0;
|
|
||||||
int duration = 0;
|
|
||||||
|
|
||||||
Tag *tag = Tag::fromFile( f );
|
Tag *tag = Tag::fromFile( f );
|
||||||
if ( f.audioProperties() )
|
|
||||||
{
|
|
||||||
TagLib::AudioProperties *properties = f.audioProperties();
|
|
||||||
duration = properties->length();
|
|
||||||
bitrate = properties->bitrate();
|
|
||||||
}
|
|
||||||
|
|
||||||
QString artist, album, track;
|
QString artist, album, track;
|
||||||
if ( tag )
|
if ( tag )
|
||||||
@@ -406,33 +449,129 @@ MusicScanner::readFile( const QFileInfo& fi )
|
|||||||
}
|
}
|
||||||
if ( !tag || artist.isEmpty() || track.isEmpty() )
|
if ( !tag || artist.isEmpty() || track.isEmpty() )
|
||||||
{
|
{
|
||||||
// FIXME: do some clever filename guessing
|
if ( m_fingerprint )
|
||||||
m_skippedFiles << fi.canonicalFilePath();
|
{
|
||||||
m_skipped++;
|
m_fingerprintingFiles << fi.canonicalFilePath();
|
||||||
|
m_fingerprinting++;
|
||||||
|
fingerprintFile( fi );
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
m_skippedFiles << fi.canonicalFilePath();
|
||||||
|
m_skipped++;
|
||||||
|
}
|
||||||
return QVariantMap();
|
return QVariantMap();
|
||||||
}
|
}
|
||||||
|
|
||||||
QString mimetype = m_ext2mime.value( suffix );
|
m_scanned++;
|
||||||
QString url( "file://%1" );
|
|
||||||
|
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["artist"] = artist;
|
||||||
m["album"] = album;
|
m["album"] = album;
|
||||||
m["track"] = track;
|
m["track"] = track;
|
||||||
|
|
||||||
m["albumpos"] = tag->track();
|
m["albumpos"] = tag->track();
|
||||||
m["year"] = tag->year();
|
m["year"] = tag->year();
|
||||||
m["albumartist"] = tag->albumArtist();
|
m["albumartist"] = tag->albumArtist();
|
||||||
m["composer"] = tag->composer();
|
m["composer"] = tag->composer();
|
||||||
m["discnumber"] = tag->discNumber();
|
m["discnumber"] = tag->discNumber();
|
||||||
m["hash"] = ""; // TODO
|
|
||||||
|
|
||||||
m_scanned++;
|
|
||||||
return m;
|
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 <QTimer>
|
||||||
#include <QVariantMap>
|
#include <QVariantMap>
|
||||||
|
|
||||||
|
namespace Tomahawk
|
||||||
|
{
|
||||||
|
namespace InfoSystem
|
||||||
|
{
|
||||||
|
class InfoRequestData;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// descend dir tree comparing dir mtimes to last known mtime
|
// descend dir tree comparing dir mtimes to last known mtime
|
||||||
// emit signal for any dir with new content, so we can scan it.
|
// emit signal for any dir with new content, so we can scan it.
|
||||||
// finally, emit the list of new mtimes we observed.
|
// finally, emit the list of new mtimes we observed.
|
||||||
@@ -113,6 +121,7 @@ signals:
|
|||||||
private:
|
private:
|
||||||
QVariant readFile( const QFileInfo& fi );
|
QVariant readFile( const QFileInfo& fi );
|
||||||
void executeCommand( Tomahawk::dbcmd_ptr cmd );
|
void executeCommand( Tomahawk::dbcmd_ptr cmd );
|
||||||
|
void fingerprintFile ( const QFileInfo& fi );
|
||||||
|
|
||||||
private slots:
|
private slots:
|
||||||
void postOps();
|
void postOps();
|
||||||
@@ -124,16 +133,27 @@ private slots:
|
|||||||
void commitBatch( const QVariantList& tracks, const QVariantList& deletethese );
|
void commitBatch( const QVariantList& tracks, const QVariantList& deletethese );
|
||||||
void commandFinished();
|
void commandFinished();
|
||||||
|
|
||||||
|
void infoSystemInfo( Tomahawk::InfoSystem::InfoRequestData requestData, QVariant output );
|
||||||
|
void infoSystemFinished( QString target );
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
TagLib::FileRef getTagLibFileRef( const QFileInfo& fi ) const;
|
||||||
void scanFilePaths();
|
void scanFilePaths();
|
||||||
|
void commitFile( const QVariant& m );
|
||||||
|
QString infoid() const;
|
||||||
|
|
||||||
|
|
||||||
|
QVariantMap readAdditionalMetadata( const QFileInfo& fi ) const;
|
||||||
|
|
||||||
MusicScanner::ScanMode m_scanMode;
|
MusicScanner::ScanMode m_scanMode;
|
||||||
QStringList m_paths;
|
QStringList m_paths;
|
||||||
QMap<QString, QString> m_ext2mime; // eg: mp3 -> audio/mpeg
|
QMap<QString, QString> m_ext2mime; // eg: mp3 -> audio/mpeg
|
||||||
unsigned int m_scanned;
|
unsigned int m_scanned;
|
||||||
unsigned int m_skipped;
|
unsigned int m_skipped;
|
||||||
|
unsigned int m_fingerprinting;
|
||||||
|
|
||||||
QList<QString> m_skippedFiles;
|
QList<QString> m_skippedFiles;
|
||||||
|
QList<QString> m_fingerprintingFiles;
|
||||||
QMap<QString, QMap< unsigned int, unsigned int > > m_filemtimes;
|
QMap<QString, QMap< unsigned int, unsigned int > > m_filemtimes;
|
||||||
|
|
||||||
unsigned int m_cmdQueue;
|
unsigned int m_cmdQueue;
|
||||||
@@ -143,6 +163,8 @@ private:
|
|||||||
quint32 m_batchsize;
|
quint32 m_batchsize;
|
||||||
|
|
||||||
DirListerThreadController* m_dirListerThreadController;
|
DirListerThreadController* m_dirListerThreadController;
|
||||||
|
|
||||||
|
bool m_fingerprint;
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
Reference in New Issue
Block a user