PageRenderTime 38ms CodeModel.GetById 9ms app.highlight 24ms RepoModel.GetById 1ms app.codeStats 0ms

/thirdparty/liblastfm2/src/fingerprint/Fingerprint.cpp

http://github.com/tomahawk-player/tomahawk
C++ | 300 lines | 198 code | 60 blank | 42 comment | 29 complexity | 189e7a7af8cd7b7319424abf3097fea5 MD5 | raw file
  1/*
  2   Copyright 2009 Last.fm Ltd. 
  3      - Primarily authored by Max Howell, Jono Cole and Doug Mansell
  4
  5   This file is part of liblastfm.
  6
  7   liblastfm is free software: you can redistribute it and/or modify
  8   it under the terms of the GNU General Public License as published by
  9   the Free Software Foundation, either version 3 of the License, or
 10   (at your option) any later version.
 11
 12   liblastfm is distributed in the hope that it will be useful,
 13   but WITHOUT ANY WARRANTY; without even the implied warranty of
 14   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 15   GNU General Public License for more details.
 16
 17   You should have received a copy of the GNU General Public License
 18   along with liblastfm.  If not, see <http://www.gnu.org/licenses/>.
 19*/
 20
 21#include "Fingerprint.h"
 22#include "FingerprintableSource.h"
 23#include "Collection.h"
 24#include "Sha256.h"
 25#include "fplib/FingerprintExtractor.h"
 26#include "../ws/ws.h"
 27#include <QFileInfo>
 28#include <QNetworkAccessManager>
 29#include <QNetworkRequest>
 30#include <QNetworkReply>
 31#include <QStringList>
 32#include <fstream>
 33
 34using lastfm::Track;
 35
 36static const uint k_bufferSize = 1024 * 8;
 37static const int k_minTrackDuration = 30;
 38
 39
 40lastfm::Fingerprint::Fingerprint( const Track& t )
 41                   : m_track( t )
 42                   , m_id( -1 ), m_duration( 0 )
 43                   , m_complete( false )
 44{
 45    QString id = Collection::instance().getFingerprintId( t.url().toLocalFile() );   
 46    if (id.size()) {
 47        bool b;
 48        m_id = id.toInt( &b );
 49        if (!b) m_id = -1;
 50    }
 51}
 52
 53
 54void
 55lastfm::Fingerprint::generate( FingerprintableSource* ms ) throw( Error )
 56{
 57    //TODO throw if we can't get required metadata from the track object
 58    
 59//TODO    if (!QFileInfo( path ).isReadable())
 60//TODO        throw ReadError;
 61
 62    int sampleRate, bitrate, numChannels;
 63
 64    if ( !ms )
 65        throw ReadError;
 66
 67    try
 68    {
 69        ms->init( m_track.url().toLocalFile() );
 70        ms->getInfo( m_duration, sampleRate, bitrate, numChannels );
 71    }
 72    catch (std::exception& e)
 73    {
 74        qWarning() << e.what();
 75        throw HeadersError;
 76    }
 77    
 78
 79    if (m_duration < k_minTrackDuration)
 80        throw TrackTooShortError;
 81    
 82    ms->skipSilence();
 83    
 84    bool fpDone = false;
 85    fingerprint::FingerprintExtractor* extractor;
 86    try
 87    {
 88        extractor = new fingerprint::FingerprintExtractor;
 89        
 90        if (m_complete)
 91        {
 92            extractor->initForFullSubmit( sampleRate, numChannels );
 93        }
 94        else 
 95        {
 96            extractor->initForQuery( sampleRate, numChannels, m_duration );
 97            
 98            // Skippety skip for as long as the skipper sez (optimisation)
 99            ms->skip( extractor->getToSkipMs() );
100            float secsToSkip = extractor->getToSkipMs() / 1000.0f;
101            fpDone = extractor->process( 0,
102                                         (size_t) sampleRate * numChannels * secsToSkip,
103                                         false );
104        }
105    }
106    catch (std::exception& e)
107    {
108        qWarning() << e.what();
109        throw DecodeError;
110    }
111    
112    const size_t PCMBufSize = 131072; 
113    short* pPCMBuffer = new short[PCMBufSize];
114    
115    while (!fpDone)
116    {
117        size_t readData = ms->updateBuffer( pPCMBuffer, PCMBufSize );
118        if (readData == 0)
119            break;
120        
121        try
122        {
123            fpDone = extractor->process( pPCMBuffer, readData, ms->eof() );
124        }
125        catch ( const std::exception& e )
126        {
127            qWarning() << e.what();
128            delete ms;
129            delete[] pPCMBuffer;
130            throw InternalError;
131        }
132    }
133    
134    delete[] pPCMBuffer;
135    
136    if (!fpDone)
137        throw InternalError;
138    
139    // We succeeded
140    std::pair<const char*, size_t> fpData = extractor->getFingerprint();
141    
142    if (fpData.first == NULL || fpData.second == 0)
143        throw InternalError;
144    
145    // Make a deep copy before extractor gets deleted
146    m_data = QByteArray( fpData.first, fpData.second );
147    delete extractor;
148}
149
150
151static QString sha256( const QString& path )
152{
153    // no clue why this is static, there was no comment when I refactored it
154    // initially --mxcl
155    static uint8_t pBuffer[SHA_BUFFER_SIZE+7];
156    
157    unsigned char hash[SHA256_HASH_SIZE];
158
159    {
160        QByteArray path8 = QFile::encodeName( path );
161        std::ifstream inFile( path8.data(), std::ios::binary);
162        
163        SHA256Context sha256;
164        SHA256Init( &sha256 );
165        
166        uint8_t* pMovableBuffer = pBuffer;
167        
168        // Ensure it is on a 64-bit boundary. 
169        INTPTR offs;
170        if ((offs = reinterpret_cast<INTPTR>(pBuffer) & 7L))
171            pMovableBuffer += 8 - offs;
172        
173        unsigned int len;
174        
175        for (;;)
176        {
177            inFile.read( reinterpret_cast<char*>(pMovableBuffer), SHA_BUFFER_SIZE );
178            len = inFile.gcount();
179            
180            if (len == 0)
181                break;
182            
183            SHA256Update( &sha256, pMovableBuffer, len );
184        }
185        
186        SHA256Final( &sha256, hash );
187    }
188
189    QString sha;
190    for (int i = 0; i < SHA256_HASH_SIZE; ++i) 
191    {
192        QString hex = QString("%1").arg(uchar(hash[i]), 2, 16,
193                                        QChar('0'));
194        sha.append(hex);
195    }
196
197    return sha;
198}
199
200
201static QByteArray number( uint n )
202{
203    return n ? QByteArray::number( n ) : "";
204}
205
206QNetworkReply*
207lastfm::Fingerprint::submit() const
208{    
209    if (m_data.isEmpty())
210        return 0;
211    
212    //Parameters understood by the server according to the MIR team: 
213    //{ "trackid", "recordingid", "artist", "album", "track", "duration", 
214    //  "tracknum", "username", "sha256", "ip", "fpversion", "mbid", 
215    //  "filename", "genre", "year", "samplerate", "noupdate", "fulldump" }
216    
217    Track const t = m_track;
218    QString const path = t.url().toLocalFile();
219    QFileInfo const fi( path );
220
221    #define e( x ) QUrl::toPercentEncoding( x )
222    QUrl url( "http://www.last.fm/fingerprint/query/" );
223    url.addEncodedQueryItem( "artist", e(t.artist()) );
224    url.addEncodedQueryItem( "album", e(t.album()) );
225    url.addEncodedQueryItem( "track", e(t.title()) );
226    url.addEncodedQueryItem( "duration", number( m_duration > 0 ? m_duration : t.duration() ) );
227    url.addEncodedQueryItem( "mbid", e(t.mbid()) );
228    url.addEncodedQueryItem( "filename", e(fi.completeBaseName()) );
229    url.addEncodedQueryItem( "fileextension", e(fi.completeSuffix()) );
230    url.addEncodedQueryItem( "tracknum", number( t.trackNumber() ) );
231    url.addEncodedQueryItem( "sha256", sha256( path ).toAscii() );
232    url.addEncodedQueryItem( "time", number(QDateTime::currentDateTime().toTime_t()) );
233    url.addEncodedQueryItem( "fpversion", QByteArray::number((int)fingerprint::FingerprintExtractor::getVersion()) );
234    url.addEncodedQueryItem( "fulldump", m_complete ? "true" : "false" );
235    url.addEncodedQueryItem( "noupdate", "false" );
236    #undef e
237
238    //FIXME: talk to mir about submitting fplibversion
239
240    QNetworkRequest request( url );
241    request.setHeader( QNetworkRequest::ContentTypeHeader, "multipart/form-data; boundary=----------------------------8e61d618ca16" );
242
243    QByteArray bytes;
244    bytes += "------------------------------8e61d618ca16\r\n";
245    bytes += "Content-Disposition: ";
246    bytes += "form-data; name=\"fpdata\"";
247    bytes += "\r\n\r\n";
248    bytes += m_data;
249    bytes += "\r\n";
250    bytes += "------------------------------8e61d618ca16--\r\n";
251
252    qDebug() << url;
253    qDebug() << "Fingerprint size:" << bytes.size() << "bytes";
254
255    return lastfm::nam()->post( request, bytes );
256}
257
258
259void
260lastfm::Fingerprint::decode( QNetworkReply* reply, bool* complete_fingerprint_requested ) throw( Error )
261{
262    // The response data will consist of a number and a string.
263    // The number is the fpid and the string is either FOUND or NEW
264    // (or NOT FOUND when noupdate was used). NEW means we should
265    // schedule a full fingerprint.
266    //
267    // In the case of an error, there will be no initial number, just
268    // an error string.
269    
270    QString const response( reply->readAll() );
271    QStringList const list = response.split( ' ' );
272
273    QString const fpid = list.value( 0 );
274    QString const status = list.value( 1 );
275       
276    if (response.isEmpty() || list.count() < 2 || response == "No response to client error")
277        goto bad_response;
278    if (list.count() != 2)
279        qWarning() << "Response looks bad but continuing anyway:" << response;
280
281    {
282        // so variables go out of scope before jump to label
283        // otherwise compiler error on GCC 4.2
284        bool b;
285        uint fpid_as_uint = fpid.toUInt( &b );
286        if (!b) goto bad_response;
287    
288        Collection::instance().setFingerprintId( m_track.url().toLocalFile(), fpid );
289    
290        if (complete_fingerprint_requested)
291            *complete_fingerprint_requested = (status == "NEW");
292
293        m_id = (int)fpid_as_uint;
294        return;
295    }
296
297bad_response:
298    qWarning() << "Response is bad:" << response;
299    throw BadResponseError;
300}