Merge lp:~max-linke/mixxx/chromaprint into lp:~mixxxdevelopers/mixxx/trunk

Proposed by Max Linke
Status: Merged
Merged at revision: 3397
Proposed branch: lp:~max-linke/mixxx/chromaprint
Merge into: lp:~mixxxdevelopers/mixxx/trunk
Diff against target: 2462 lines (+2023/-87)
24 files modified
mixxx/build/depends.py (+23/-1)
mixxx/build/qtcreator/mixxx.pro (+1/-0)
mixxx/src/dlgtagfetcher.cpp (+159/-0)
mixxx/src/dlgtagfetcher.h (+57/-0)
mixxx/src/dlgtagfetcher.ui (+441/-0)
mixxx/src/dlgtrackinfo.cpp (+22/-9)
mixxx/src/dlgtrackinfo.h (+5/-2)
mixxx/src/dlgtrackinfo.ui (+78/-71)
mixxx/src/musicbrainz/acoustidclient.cpp (+109/-0)
mixxx/src/musicbrainz/acoustidclient.h (+67/-0)
mixxx/src/musicbrainz/chromaprinter.cpp (+88/-0)
mixxx/src/musicbrainz/chromaprinter.h (+25/-0)
mixxx/src/musicbrainz/crc.c (+133/-0)
mixxx/src/musicbrainz/crc.h (+83/-0)
mixxx/src/musicbrainz/gzip.cpp (+49/-0)
mixxx/src/musicbrainz/gzip.h (+12/-0)
mixxx/src/musicbrainz/musicbrainzclient.cpp (+180/-0)
mixxx/src/musicbrainz/musicbrainzclient.h (+130/-0)
mixxx/src/musicbrainz/tagfetcher.cpp (+121/-0)
mixxx/src/musicbrainz/tagfetcher.h (+58/-0)
mixxx/src/network.cpp (+73/-0)
mixxx/src/network.h (+49/-0)
mixxx/src/widget/wtracktableview.cpp (+53/-4)
mixxx/src/widget/wtracktableview.h (+7/-0)
To merge this branch: bzr merge lp:~max-linke/mixxx/chromaprint
Reviewer Review Type Date Requested Status
RJ Skerry-Ryan Approve
Review via email: mp+163406@code.launchpad.net

Description of the change

Hi

I decided to split up my library_features branch in smaller branches. In here are all changes relevant to fingerprinting and retreiving tags from MusicBrainz

To post a comment you must log in.
lp:~max-linke/mixxx/chromaprint updated
3375. By Max Linke <email address hidden>

merge with trunk

3376. By Max Linke <email address hidden>

merge with trunk

Revision history for this message
RJ Skerry-Ryan (rryan) wrote :

Looks good to me on my first skim. I need to take a look src/chromaprint more closely but for now I'm assuming it works as expected.

* void WTrackTableView::showDlgTagFetcher(QModelIndex index)

What about when multiple tracks are selected?

Revision history for this message
RJ Skerry-Ryan (rryan) wrote :

Doesn't MB have more data than artist/title/album/track/year?

When you set them, what about checking whether the existing data is blank or the replacement data is blank and keeping the old one if the new one is blank?

Revision history for this message
Max Linke (max-linke) wrote :

'reload from MB' is grayed out if more then one song is selected. The tagfetcher class is able to handle to fetch tags from several songs at once but I haven't found a good way to display several tracks+results to the user and by this I follow the same logic as 'properties' does.

I didn't check to keep the old one if the new data is blank but I honestly also never encountered that with any track I own. I sure I can write some code that checks for this but I don't know how I'm supposed to test if it works.

lp:~max-linke/mixxx/chromaprint updated
3377. By Max Linke <email address hidden>

removed musicbrainz url that is not used

3378. By Max Linke <email address hidden>

merge with trunk

3379. By Max Linke <email address hidden>

fixed signal connection

Revision history for this message
RJ Skerry-Ryan (rryan) wrote :

Looks good -- thanks for the feature Max!

review: Approve
lp:~max-linke/mixxx/chromaprint updated
3380. By Max Linke <email address hidden>

trackpointer->year/tracknumber can also be 0 check for that as well

3381. By Max Linke <email address hidden>

merge with trunk

3382. By Max Linke <email address hidden>

merge with trunk and small changes

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'mixxx/build/depends.py'
2--- mixxx/build/depends.py 2013-05-21 03:02:42 +0000
3+++ mixxx/build/depends.py 2013-05-22 07:42:27 +0000
4@@ -400,6 +400,17 @@
5 if build.platform_is_windows and build.static_dependencies:
6 build.env.Append(CPPDEFINES = 'TAGLIB_STATIC')
7
8+class Chromaprint(Dependence):
9+ def configure(self, build, conf):
10+ if not conf.CheckLib(['chromaprint', 'libchromaprint', 'chromaprint_p', 'libchromaprint_p']):
11+ raise Exception("Could not find libchromaprint or its development headers.")
12+ if build.platform_is_windows and build.static_dependencies:
13+ build.env.Append(CPPDEFINES = 'CHROMAPRINT_NODLL')
14+
15+ # On Windows, we link chromaprint with FFTW3.
16+ if not conf.CheckLib(['fftw', 'libfftw', 'fftw3', 'libfftw3']):
17+ raise Exception("Could not find fftw3 or its development headers.")
18+
19 class ProtoBuf(Dependence):
20 def configure(self, build, conf):
21 libs = ['libprotobuf-lite', 'protobuf-lite', 'libprotobuf', 'protobuf']
22@@ -447,6 +458,7 @@
23 "dlgabout.cpp",
24 "dlgprefeq.cpp",
25 "dlgprefcrossfader.cpp",
26+ "dlgtagfetcher.cpp",
27 "dlgtrackinfo.cpp",
28 "dlgprepare.cpp",
29 "dlgautodj.cpp",
30@@ -545,6 +557,14 @@
31
32 "mathstuff.cpp",
33
34+ "network.cpp",
35+ "musicbrainz/tagfetcher.cpp",
36+ "musicbrainz/gzip.cpp",
37+ "musicbrainz/crc.c",
38+ "musicbrainz/acoustidclient.cpp",
39+ "musicbrainz/chromaprinter.cpp",
40+ "musicbrainz/musicbrainzclient.cpp",
41+
42 "rotary.cpp",
43 "widget/wtracktableview.cpp",
44 "widget/wtracktableviewheader.cpp",
45@@ -756,6 +776,7 @@
46 build.env.Uic4('dlgprefnovinyldlg.ui')
47 build.env.Uic4('dlgprefrecorddlg.ui')
48 build.env.Uic4('dlgaboutdlg.ui')
49+ build.env.Uic4('dlgtagfetcher.ui')
50 build.env.Uic4('dlgtrackinfo.ui')
51 build.env.Uic4('dlgprepare.ui')
52 build.env.Uic4('dlgautodj.ui')
53@@ -910,7 +931,8 @@
54
55 def depends(self, build):
56 return [SoundTouch, ReplayGain, PortAudio, PortMIDI, Qt,
57- FidLib, SndFile, FLAC, OggVorbis, OpenGL, TagLib, ProtoBuf]
58+ FidLib, SndFile, FLAC, OggVorbis, OpenGL, TagLib, ProtoBuf,
59+ Chromaprint]
60
61 def post_dependency_check_configure(self, build, conf):
62 """Sets up additional things in the Environment that must happen
63
64=== modified file 'mixxx/build/qtcreator/mixxx.pro'
65--- mixxx/build/qtcreator/mixxx.pro 2013-05-20 02:18:33 +0000
66+++ mixxx/build/qtcreator/mixxx.pro 2013-05-22 07:42:27 +0000
67@@ -105,6 +105,7 @@
68 $$UI_DIR/ui_dlgpreferencesdlg.h \
69 $$UI_DIR/ui_dlgprefmidibindingsdlg.h \
70 $$UI_DIR/ui_dlgprefplaylistdlg.h \
71+ $$UI_DIR/ui_dlgtagfetcher.h \
72 $$UI_DIR/ui_dlgprefrecorddlg.h \
73 $$UI_DIR/ui_dlgprefsounddlg.h \
74 $$UI_DIR/ui_dlgprefvinyldlg.h \
75
76=== added file 'mixxx/src/dlgtagfetcher.cpp'
77--- mixxx/src/dlgtagfetcher.cpp 1970-01-01 00:00:00 +0000
78+++ mixxx/src/dlgtagfetcher.cpp 2013-05-22 07:42:27 +0000
79@@ -0,0 +1,159 @@
80+#include <QTreeWidget>
81+#include <QDebug>
82+#include <QDesktopServices>
83+#include <QUrl>
84+
85+#include "dlgtagfetcher.h"
86+
87+DlgTagFetcher::DlgTagFetcher(QWidget *parent)
88+ : QWidget(parent),
89+ m_track(NULL),
90+ m_TagFetcher(parent) {
91+ setupUi(this);
92+
93+ connect(btnApply, SIGNAL(clicked()),
94+ this, SLOT(apply()));
95+ connect(btnQuit, SIGNAL(clicked()),
96+ this, SLOT(quit()));
97+ connect(btnPrev, SIGNAL(clicked()),
98+ this, SIGNAL(previous()));
99+ connect(btnNext, SIGNAL(clicked()),
100+ this, SIGNAL(next()));
101+ connect(results, SIGNAL(currentItemChanged(QTreeWidgetItem*,QTreeWidgetItem*)),
102+ this, SLOT(resultSelected()));
103+
104+ connect(&m_TagFetcher, SIGNAL(resultAvailable(const TrackPointer,const QList<TrackPointer>&)),
105+ this, SLOT(fetchTagFinished(const TrackPointer,const QList<TrackPointer>&)));
106+ connect(&m_TagFetcher, SIGNAL(fetchProgress(QString)),
107+ this, SLOT(fetchTagProgress(QString)));
108+
109+ // Resize columns, this can't be set in the ui file
110+ results->setColumnWidth(0, 50); // Track column
111+ results->setColumnWidth(1, 50); // Year column
112+ results->setColumnWidth(2, 160); // Title column
113+ results->setColumnWidth(3, 160); // Artist column
114+ results->setColumnWidth(4, 160); // Album column
115+
116+ progressLabel->setText("");
117+}
118+
119+DlgTagFetcher::~DlgTagFetcher() {
120+}
121+
122+void DlgTagFetcher::init(const TrackPointer track) {
123+ results->clear();
124+ m_track = track;
125+ m_data = Data();
126+ m_TagFetcher.startFetch(m_track);
127+ updateStack();
128+}
129+
130+void DlgTagFetcher::apply() {
131+ int resultIndex = m_data.m_selectedResult;
132+ if (resultIndex > -1) {
133+ if (!m_data.m_results[resultIndex]->getAlbum().isEmpty()) {
134+ m_track->setAlbum(m_data.m_results[resultIndex]->getAlbum());
135+ }
136+ if (!m_data.m_results[resultIndex]->getArtist().isEmpty()) {
137+ m_track->setArtist(m_data.m_results[resultIndex]->getArtist());
138+ }
139+ if (!m_data.m_results[resultIndex]->getTitle().isEmpty()) {
140+ m_track->setTitle(m_data.m_results[resultIndex]->getTitle());
141+ }
142+ if (!m_data.m_results[resultIndex]->getYear().isEmpty() &&
143+ m_data.m_results[resultIndex]->getYear() != "0") {
144+ m_track->setYear(m_data.m_results[resultIndex]->getYear());
145+ }
146+ if (!m_data.m_results[resultIndex]->getTrackNumber().isEmpty() &&
147+ m_data.m_results[resultIndex]->getTrackNumber() != "0") {
148+ m_track->setTrackNumber(m_data.m_results[resultIndex]->getTrackNumber());
149+ }
150+ }
151+}
152+
153+void DlgTagFetcher::quit() {
154+ m_TagFetcher.cancel();
155+ close();
156+}
157+
158+void DlgTagFetcher::fetchTagProgress(QString text) {
159+ loading->setText(text);
160+}
161+
162+void DlgTagFetcher::fetchTagFinished(const TrackPointer track,
163+ const QList<TrackPointer>& tracks) {
164+ // check if the answer is for this track
165+ if (m_track->getLocation() != track->getLocation()) {
166+ return;
167+ }
168+
169+ m_data.m_pending = false;
170+ m_data.m_results = tracks;
171+ // qDebug() << "number of results = " << tracks.size();
172+ updateStack();
173+}
174+
175+void DlgTagFetcher::updateStack() {
176+ if (m_data.m_pending) {
177+ stack->setCurrentWidget(loading_page);
178+ return;
179+ } else if (m_data.m_results.isEmpty()) {
180+ stack->setCurrentWidget(error_page);
181+ return;
182+ }
183+ btnApply->setEnabled(true);
184+ stack->setCurrentWidget(results_page);
185+
186+ results->clear();
187+
188+ addDivider(tr("Original tags"), results);
189+ addTrack(m_track, -1, results);
190+
191+ addDivider(tr("Suggested tags"), results);
192+
193+ int trackIndex = 0;
194+ foreach (const TrackPointer track, m_data.m_results) {
195+ addTrack(track, trackIndex++, results);
196+ }
197+
198+ // Find the item that was selected last time
199+ for (int i=0 ; i<results->model()->rowCount() ; ++i) {
200+ const QModelIndex index = results->model()->index(i, 0);
201+ const QVariant id = index.data(Qt::UserRole);
202+ if (!id.isNull() && id.toInt() == m_data.m_selectedResult) {
203+ results->setCurrentIndex(index);
204+ break;
205+ }
206+ }
207+}
208+
209+void DlgTagFetcher::addTrack(const TrackPointer track, int resultIndex,
210+ QTreeWidget* parent) const {
211+ QStringList values;
212+ values << track->getTrackNumber() << track->getYear() << track->getTitle()
213+ << track->getArtist() << track->getAlbum();
214+
215+ QTreeWidgetItem* item = new QTreeWidgetItem(parent, values);
216+ item->setData(0, Qt::UserRole, resultIndex);
217+ item->setData(0, Qt::TextAlignmentRole, Qt::AlignRight);
218+}
219+
220+void DlgTagFetcher::addDivider(const QString& text, QTreeWidget* parent) const {
221+ QTreeWidgetItem* item = new QTreeWidgetItem(parent);
222+ item->setFirstColumnSpanned(true);
223+ item->setText(0, text);
224+ item->setFlags(Qt::NoItemFlags);
225+ item->setForeground(0, palette().color(QPalette::Disabled, QPalette::Text));
226+
227+ QFont bold_font(font());
228+ bold_font.setBold(true);
229+ item->setFont(0, bold_font);
230+}
231+
232+void DlgTagFetcher::resultSelected() {
233+ if (!results->currentItem())
234+ return;
235+
236+ const int resultIndex = results->currentItem()->data(0, Qt::UserRole).toInt();
237+ m_data.m_selectedResult = resultIndex;
238+}
239
240=== added file 'mixxx/src/dlgtagfetcher.h'
241--- mixxx/src/dlgtagfetcher.h 1970-01-01 00:00:00 +0000
242+++ mixxx/src/dlgtagfetcher.h 2013-05-22 07:42:27 +0000
243@@ -0,0 +1,57 @@
244+#ifndef DLGTAGFETCHER_H
245+#define DLGTAGFETCHER_H
246+
247+#include <QWidget>
248+#include "ui_dlgtagfetcher.h"
249+#include "trackinfoobject.h"
250+#include "musicbrainz/tagfetcher.h"
251+
252+
253+class QTreeWidget;
254+
255+
256+class DlgTagFetcher : public QWidget, public Ui::DlgTagFetcher {
257+ Q_OBJECT
258+
259+ public:
260+ DlgTagFetcher(QWidget *parent);
261+ virtual ~DlgTagFetcher();
262+
263+ void init(const TrackPointer track);
264+
265+ signals:
266+ void next();
267+ void previous();
268+
269+ public slots:
270+ void fetchTagFinished(const TrackPointer,const QList<TrackPointer>& tracks);
271+ void resultSelected();
272+ void fetchTagProgress(QString);
273+
274+ private slots:
275+ void apply();
276+ void quit();
277+
278+ private:
279+
280+ void updateStack();
281+ void addDivider(const QString& text, QTreeWidget* parent) const;
282+ void addTrack(const TrackPointer track, int resultIndex,
283+ QTreeWidget* parent) const;
284+
285+ private:
286+ struct Data {
287+ Data() : m_pending(true), m_selectedResult(0) {}
288+
289+ bool m_pending;
290+ int m_selectedResult;
291+ QList<TrackPointer> m_results;
292+ };
293+
294+ TrackPointer m_track;
295+ Data m_data;
296+ QString m_progress;
297+ TagFetcher m_TagFetcher;
298+};
299+
300+#endif // DLGTAGFETCHER_H
301
302=== added file 'mixxx/src/dlgtagfetcher.ui'
303--- mixxx/src/dlgtagfetcher.ui 1970-01-01 00:00:00 +0000
304+++ mixxx/src/dlgtagfetcher.ui 2013-05-22 07:42:27 +0000
305@@ -0,0 +1,441 @@
306+<?xml version="1.0" encoding="UTF-8"?>
307+<ui version="4.0">
308+ <class>DlgTagFetcher</class>
309+ <widget class="QWidget" name="DlgTagFetcher">
310+ <property name="geometry">
311+ <rect>
312+ <x>0</x>
313+ <y>0</y>
314+ <width>810</width>
315+ <height>393</height>
316+ </rect>
317+ </property>
318+ <property name="windowTitle">
319+ <string>Dialog</string>
320+ </property>
321+ <widget class="QWidget" name="verticalLayoutWidget_3">
322+ <property name="geometry">
323+ <rect>
324+ <x>10</x>
325+ <y>10</y>
326+ <width>791</width>
327+ <height>371</height>
328+ </rect>
329+ </property>
330+ <layout class="QVBoxLayout" name="verticalLayout_3">
331+ <item>
332+ <widget class="QStackedWidget" name="stack">
333+ <property name="currentIndex">
334+ <number>1</number>
335+ </property>
336+ <widget class="QWidget" name="results_page">
337+ <widget class="QWidget" name="verticalLayoutWidget_2">
338+ <property name="geometry">
339+ <rect>
340+ <x>0</x>
341+ <y>0</y>
342+ <width>791</width>
343+ <height>331</height>
344+ </rect>
345+ </property>
346+ <layout class="QVBoxLayout" name="verticalLayout_2">
347+ <item>
348+ <widget class="QLabel" name="label_3">
349+ <property name="styleSheet">
350+ <string notr="true">QLabel { font-weight: bold; }</string>
351+ </property>
352+ <property name="text">
353+ <string>Select best possible match</string>
354+ </property>
355+ </widget>
356+ </item>
357+ <item>
358+ <widget class="QTreeWidget" name="results">
359+ <property name="editTriggers">
360+ <set>QAbstractItemView::DoubleClicked|QAbstractItemView::EditKeyPressed</set>
361+ </property>
362+ <property name="rootIsDecorated">
363+ <bool>false</bool>
364+ </property>
365+ <attribute name="headerDefaultSectionSize">
366+ <number>150</number>
367+ </attribute>
368+ <attribute name="headerMinimumSectionSize">
369+ <number>50</number>
370+ </attribute>
371+ <column>
372+ <property name="text">
373+ <string>Track</string>
374+ </property>
375+ </column>
376+ <column>
377+ <property name="text">
378+ <string>Year</string>
379+ </property>
380+ </column>
381+ <column>
382+ <property name="text">
383+ <string>Title</string>
384+ </property>
385+ </column>
386+ <column>
387+ <property name="text">
388+ <string>Artist</string>
389+ </property>
390+ </column>
391+ <column>
392+ <property name="text">
393+ <string>Album</string>
394+ </property>
395+ </column>
396+ </widget>
397+ </item>
398+ </layout>
399+ </widget>
400+ </widget>
401+ <widget class="QWidget" name="loading_page">
402+ <widget class="QWidget" name="verticalLayoutWidget_4">
403+ <property name="geometry">
404+ <rect>
405+ <x>-1</x>
406+ <y>-1</y>
407+ <width>791</width>
408+ <height>341</height>
409+ </rect>
410+ </property>
411+ <layout class="QVBoxLayout" name="verticalLayout_4">
412+ <item>
413+ <spacer name="verticalSpacer_3">
414+ <property name="orientation">
415+ <enum>Qt::Vertical</enum>
416+ </property>
417+ <property name="sizeHint" stdset="0">
418+ <size>
419+ <width>20</width>
420+ <height>40</height>
421+ </size>
422+ </property>
423+ </spacer>
424+ </item>
425+ <item>
426+ <layout class="QHBoxLayout" name="horizontalLayout_3">
427+ <item>
428+ <spacer name="horizontalSpacer_4">
429+ <property name="orientation">
430+ <enum>Qt::Horizontal</enum>
431+ </property>
432+ <property name="sizeHint" stdset="0">
433+ <size>
434+ <width>40</width>
435+ <height>20</height>
436+ </size>
437+ </property>
438+ </spacer>
439+ </item>
440+ <item>
441+ <widget class="QLabel" name="message1">
442+ <property name="text">
443+ <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; font-weight:600;&quot;&gt;Fetching track data from MusicBrainz&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
444+ </property>
445+ </widget>
446+ </item>
447+ <item>
448+ <spacer name="horizontalSpacer_5">
449+ <property name="orientation">
450+ <enum>Qt::Horizontal</enum>
451+ </property>
452+ <property name="sizeHint" stdset="0">
453+ <size>
454+ <width>40</width>
455+ <height>20</height>
456+ </size>
457+ </property>
458+ </spacer>
459+ </item>
460+ </layout>
461+ </item>
462+ <item>
463+ <layout class="QHBoxLayout" name="horizontalLayout_4">
464+ <item>
465+ <spacer name="horizontalSpacer_6">
466+ <property name="orientation">
467+ <enum>Qt::Horizontal</enum>
468+ </property>
469+ <property name="sizeHint" stdset="0">
470+ <size>
471+ <width>40</width>
472+ <height>20</height>
473+ </size>
474+ </property>
475+ </spacer>
476+ </item>
477+ <item>
478+ <widget class="QLabel" name="status">
479+ <property name="text">
480+ <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; font-weight:600;&quot;&gt;Status:&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
481+ </property>
482+ </widget>
483+ </item>
484+ <item>
485+ <widget class="QLabel" name="loading">
486+ <property name="text">
487+ <string/>
488+ </property>
489+ </widget>
490+ </item>
491+ <item>
492+ <spacer name="horizontalSpacer_7">
493+ <property name="orientation">
494+ <enum>Qt::Horizontal</enum>
495+ </property>
496+ <property name="sizeHint" stdset="0">
497+ <size>
498+ <width>40</width>
499+ <height>20</height>
500+ </size>
501+ </property>
502+ </spacer>
503+ </item>
504+ </layout>
505+ </item>
506+ <item>
507+ <spacer name="verticalSpacer_4">
508+ <property name="orientation">
509+ <enum>Qt::Vertical</enum>
510+ </property>
511+ <property name="sizeHint" stdset="0">
512+ <size>
513+ <width>20</width>
514+ <height>40</height>
515+ </size>
516+ </property>
517+ </spacer>
518+ </item>
519+ </layout>
520+ </widget>
521+ </widget>
522+ <widget class="QWidget" name="error_page">
523+ <widget class="QWidget" name="verticalLayoutWidget">
524+ <property name="geometry">
525+ <rect>
526+ <x>0</x>
527+ <y>0</y>
528+ <width>791</width>
529+ <height>331</height>
530+ </rect>
531+ </property>
532+ <layout class="QVBoxLayout" name="verticalLayout">
533+ <item>
534+ <spacer name="verticalSpacer">
535+ <property name="orientation">
536+ <enum>Qt::Vertical</enum>
537+ </property>
538+ <property name="sizeHint" stdset="0">
539+ <size>
540+ <width>20</width>
541+ <height>40</height>
542+ </size>
543+ </property>
544+ </spacer>
545+ </item>
546+ <item>
547+ <widget class="QLabel" name="label">
548+ <property name="text">
549+ <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p align=&quot;center&quot;&gt;&lt;span style=&quot; font-size:14pt; font-weight:600;&quot;&gt;Sorry&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
550+ </property>
551+ </widget>
552+ </item>
553+ <item>
554+ <widget class="QLabel" name="label_2">
555+ <property name="text">
556+ <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p align=&quot;center&quot;&gt;Mixxx could not find this song&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
557+ </property>
558+ </widget>
559+ </item>
560+ <item>
561+ <spacer name="verticalSpacer_2">
562+ <property name="orientation">
563+ <enum>Qt::Vertical</enum>
564+ </property>
565+ <property name="sizeHint" stdset="0">
566+ <size>
567+ <width>20</width>
568+ <height>40</height>
569+ </size>
570+ </property>
571+ </spacer>
572+ </item>
573+ </layout>
574+ </widget>
575+ </widget>
576+ <widget class="QWidget" name="submit_page">
577+ <widget class="QWidget" name="verticalLayoutWidget_5">
578+ <property name="geometry">
579+ <rect>
580+ <x>1</x>
581+ <y>0</y>
582+ <width>791</width>
583+ <height>331</height>
584+ </rect>
585+ </property>
586+ <layout class="QVBoxLayout" name="verticalLayout_5">
587+ <item>
588+ <layout class="QHBoxLayout" name="horizontalLayout_2">
589+ <item>
590+ <widget class="QLineEdit" name="apikey">
591+ <property name="text">
592+ <string/>
593+ </property>
594+ </widget>
595+ </item>
596+ <item>
597+ <widget class="QPushButton" name="btnApikey">
598+ <property name="text">
599+ <string>get API-Key</string>
600+ </property>
601+ </widget>
602+ </item>
603+ <item>
604+ <spacer name="horizontalSpacer_3">
605+ <property name="orientation">
606+ <enum>Qt::Horizontal</enum>
607+ </property>
608+ <property name="sizeHint" stdset="0">
609+ <size>
610+ <width>40</width>
611+ <height>20</height>
612+ </size>
613+ </property>
614+ </spacer>
615+ </item>
616+ <item>
617+ <spacer name="horizontalSpacer_2">
618+ <property name="orientation">
619+ <enum>Qt::Horizontal</enum>
620+ </property>
621+ <property name="sizeHint" stdset="0">
622+ <size>
623+ <width>40</width>
624+ <height>20</height>
625+ </size>
626+ </property>
627+ </spacer>
628+ </item>
629+ <item>
630+ <widget class="QPushButton" name="btnSubmit">
631+ <property name="text">
632+ <string>Submit</string>
633+ </property>
634+ </widget>
635+ </item>
636+ </layout>
637+ </item>
638+ <item>
639+ <widget class="QTreeWidget" name="submit_tree">
640+ <column>
641+ <property name="text">
642+ <string>Track</string>
643+ </property>
644+ </column>
645+ <column>
646+ <property name="text">
647+ <string>Year</string>
648+ </property>
649+ </column>
650+ <column>
651+ <property name="text">
652+ <string>New Column</string>
653+ </property>
654+ </column>
655+ <column>
656+ <property name="text">
657+ <string>Artist</string>
658+ </property>
659+ </column>
660+ <column>
661+ <property name="text">
662+ <string>Album</string>
663+ </property>
664+ </column>
665+ <item>
666+ <property name="text">
667+ <string>New Item</string>
668+ </property>
669+ <property name="text">
670+ <string/>
671+ </property>
672+ <property name="text">
673+ <string/>
674+ </property>
675+ <property name="text">
676+ <string/>
677+ </property>
678+ <property name="text">
679+ <string/>
680+ </property>
681+ </item>
682+ </widget>
683+ </item>
684+ <item>
685+ <widget class="QLabel" name="progressLabel">
686+ <property name="text">
687+ <string>TextLabel</string>
688+ </property>
689+ </widget>
690+ </item>
691+ </layout>
692+ </widget>
693+ </widget>
694+ </widget>
695+ </item>
696+ <item>
697+ <layout class="QHBoxLayout" name="horizontalLayout">
698+ <item>
699+ <widget class="QPushButton" name="btnPrev">
700+ <property name="text">
701+ <string>Prev</string>
702+ </property>
703+ </widget>
704+ </item>
705+ <item>
706+ <widget class="QPushButton" name="btnNext">
707+ <property name="text">
708+ <string>Next</string>
709+ </property>
710+ </widget>
711+ </item>
712+ <item>
713+ <spacer name="horizontalSpacer">
714+ <property name="orientation">
715+ <enum>Qt::Horizontal</enum>
716+ </property>
717+ <property name="sizeHint" stdset="0">
718+ <size>
719+ <width>40</width>
720+ <height>20</height>
721+ </size>
722+ </property>
723+ </spacer>
724+ </item>
725+ <item>
726+ <widget class="QPushButton" name="btnApply">
727+ <property name="text">
728+ <string>Apply</string>
729+ </property>
730+ </widget>
731+ </item>
732+ <item>
733+ <widget class="QPushButton" name="btnQuit">
734+ <property name="text">
735+ <string>Close</string>
736+ </property>
737+ </widget>
738+ </item>
739+ </layout>
740+ </item>
741+ </layout>
742+ </widget>
743+ </widget>
744+ <resources/>
745+ <connections/>
746+</ui>
747
748=== modified file 'mixxx/src/dlgtrackinfo.cpp'
749--- mixxx/src/dlgtrackinfo.cpp 2012-05-08 16:10:02 +0000
750+++ mixxx/src/dlgtrackinfo.cpp 2013-05-22 07:42:27 +0000
751@@ -7,10 +7,20 @@
752 #include "library/dao/cue.h"
753 #include "trackinfoobject.h"
754
755-DlgTrackInfo::DlgTrackInfo(QWidget* parent) :
756- QDialog(parent),
757- m_pLoadedTrack() {
758-
759+DlgTrackInfo::DlgTrackInfo(QWidget* parent,
760+ DlgTagFetcher& DlgTagFetcher)
761+ : QDialog(parent),
762+ m_pLoadedTrack(NULL),
763+ m_DlgTagFetcher(DlgTagFetcher) {
764+ init();
765+}
766+
767+DlgTrackInfo::~DlgTrackInfo() {
768+ unloadTrack(false);
769+ qDebug() << "~DlgTrackInfo()";
770+}
771+
772+void DlgTrackInfo::init(){
773 setupUi(this);
774
775 cueTable->hideColumn(0);
776@@ -24,6 +34,9 @@
777 connect(btnCancel, SIGNAL(clicked()),
778 this, SLOT(cancel()));
779
780+ connect(btnFetchTag, SIGNAL(clicked()),
781+ this, SLOT(fetchTag()));
782+
783 connect(bpmDouble, SIGNAL(clicked()),
784 this, SLOT(slotBpmDouble()));
785 connect(bpmHalve, SIGNAL(clicked()),
786@@ -43,11 +56,6 @@
787 }
788 }
789
790-DlgTrackInfo::~DlgTrackInfo() {
791- unloadTrack(false);
792- qDebug() << "~DlgTrackInfo()";
793-}
794-
795 void DlgTrackInfo::apply() {
796 unloadTrack(true);
797 accept();
798@@ -320,3 +328,8 @@
799 populateFields(pTrack);
800 }
801 }
802+
803+void DlgTrackInfo::fetchTag() {
804+ m_DlgTagFetcher.init(m_pLoadedTrack);
805+ m_DlgTagFetcher.show();
806+}
807
808=== modified file 'mixxx/src/dlgtrackinfo.h'
809--- mixxx/src/dlgtrackinfo.h 2013-05-14 18:09:35 +0000
810+++ mixxx/src/dlgtrackinfo.h 2013-05-22 07:42:27 +0000
811@@ -8,8 +8,8 @@
812 #include <QList>
813
814 #include "ui_dlgtrackinfo.h"
815-
816 #include "trackinfoobject.h"
817+#include "dlgtagfetcher.h"
818
819 /** Minimum allowed Beat per minute (BPM) */
820 const int minBPM = 30;
821@@ -25,7 +25,7 @@
822 class DlgTrackInfo : public QDialog, public Ui::DlgTrackInfo {
823 Q_OBJECT
824 public:
825- DlgTrackInfo(QWidget* parent);
826+ DlgTrackInfo(QWidget* parent, DlgTagFetcher& DlgTagFetcher);
827 virtual ~DlgTrackInfo();
828
829 public slots:
830@@ -43,6 +43,7 @@
831 void apply();
832 void cancel();
833 void trackUpdated();
834+ void fetchTag();
835
836 void cueActivate();
837 void cueDelete();
838@@ -58,6 +59,7 @@
839 void populateCues(TrackPointer pTrack);
840 void unloadTrack(bool save);
841 void clear();
842+ void init();
843
844 QHash<int, Cue*> m_cueMap;
845 TrackPointer m_pLoadedTrack;
846@@ -66,6 +68,7 @@
847 QTime m_bpmTapTimer;
848
849 QMutex m_mutex;
850+ DlgTagFetcher& m_DlgTagFetcher;
851
852 };
853
854
855=== modified file 'mixxx/src/dlgtrackinfo.ui'
856--- mixxx/src/dlgtrackinfo.ui 2012-05-08 16:10:02 +0000
857+++ mixxx/src/dlgtrackinfo.ui 2013-05-22 07:42:27 +0000
858@@ -61,6 +61,13 @@
859 <item row="1" column="1" colspan="3">
860 <widget class="QLineEdit" name="txtArtist"/>
861 </item>
862+ <item row="8" column="0">
863+ <widget class="QLabel" name="label_7">
864+ <property name="text">
865+ <string>Duration:</string>
866+ </property>
867+ </widget>
868+ </item>
869 <item row="2" column="0">
870 <widget class="QLabel" name="label_6">
871 <property name="text">
872@@ -68,25 +75,12 @@
873 </property>
874 </widget>
875 </item>
876- <item row="2" column="1" colspan="3">
877- <widget class="QLineEdit" name="txtAlbum"/>
878- </item>
879- <item row="3" column="0">
880- <widget class="QLabel" name="label_9">
881- <property name="text">
882- <string>Genre:</string>
883- </property>
884- </widget>
885- </item>
886- <item row="4" column="0">
887- <widget class="QLabel" name="label_11">
888- <property name="text">
889- <string>Composer:</string>
890- </property>
891- </widget>
892- </item>
893- <item row="4" column="1" colspan="3">
894- <widget class="QLineEdit" name="txtComposer"/>
895+ <item row="6" column="0">
896+ <widget class="QLabel" name="label_12">
897+ <property name="text">
898+ <string>Key</string>
899+ </property>
900+ </widget>
901 </item>
902 <item row="3" column="1" colspan="3">
903 <widget class="QLineEdit" name="txtGenre"/>
904@@ -98,33 +92,30 @@
905 </property>
906 </widget>
907 </item>
908- <item row="5" column="3">
909- <widget class="QLineEdit" name="txtTrackNumber"/>
910- </item>
911- <item row="5" column="1">
912- <widget class="QLineEdit" name="txtYear"/>
913- </item>
914- <item row="5" column="0">
915- <widget class="QLabel" name="label_8">
916- <property name="text">
917- <string>Year</string>
918- </property>
919- </widget>
920- </item>
921- <item row="6" column="1">
922- <widget class="QLineEdit" name="txtKey"/>
923- </item>
924- <item row="6" column="0">
925- <widget class="QLabel" name="label_12">
926- <property name="text">
927- <string>Key</string>
928- </property>
929- </widget>
930- </item>
931- <item row="8" column="0">
932- <widget class="QLabel" name="label_7">
933- <property name="text">
934- <string>Duration:</string>
935+ <item row="9" column="3">
936+ <widget class="QLabel" name="txtBitrate">
937+ <property name="text">
938+ <string/>
939+ </property>
940+ <property name="textInteractionFlags">
941+ <set>Qt::LinksAccessibleByMouse|Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse</set>
942+ </property>
943+ </widget>
944+ </item>
945+ <item row="12" column="0" colspan="4">
946+ <widget class="Line" name="line_3">
947+ <property name="orientation">
948+ <enum>Qt::Horizontal</enum>
949+ </property>
950+ </widget>
951+ </item>
952+ <item row="11" column="0">
953+ <widget class="QLabel" name="label_5">
954+ <property name="text">
955+ <string>Location:</string>
956+ </property>
957+ <property name="alignment">
958+ <set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop</set>
959 </property>
960 </widget>
961 </item>
962@@ -155,6 +146,16 @@
963 </property>
964 </widget>
965 </item>
966+ <item row="5" column="0">
967+ <widget class="QLabel" name="label_8">
968+ <property name="text">
969+ <string>Year</string>
970+ </property>
971+ </widget>
972+ </item>
973+ <item row="6" column="1">
974+ <widget class="QLineEdit" name="txtKey"/>
975+ </item>
976 <item row="9" column="0">
977 <widget class="QLabel" name="label_13">
978 <property name="text">
979@@ -172,30 +173,29 @@
980 </property>
981 </widget>
982 </item>
983- <item row="9" column="3">
984- <widget class="QLabel" name="txtBitrate">
985- <property name="text">
986- <string/>
987- </property>
988- <property name="textInteractionFlags">
989- <set>Qt::LinksAccessibleByMouse|Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse</set>
990- </property>
991- </widget>
992- </item>
993- <item row="12" column="0" colspan="4">
994- <widget class="Line" name="line_3">
995- <property name="orientation">
996- <enum>Qt::Horizontal</enum>
997- </property>
998- </widget>
999- </item>
1000- <item row="11" column="0">
1001- <widget class="QLabel" name="label_5">
1002- <property name="text">
1003- <string>Location:</string>
1004- </property>
1005- <property name="alignment">
1006- <set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop</set>
1007+ <item row="4" column="0">
1008+ <widget class="QLabel" name="label_11">
1009+ <property name="text">
1010+ <string>Composer:</string>
1011+ </property>
1012+ </widget>
1013+ </item>
1014+ <item row="4" column="1" colspan="3">
1015+ <widget class="QLineEdit" name="txtComposer"/>
1016+ </item>
1017+ <item row="5" column="3">
1018+ <widget class="QLineEdit" name="txtTrackNumber"/>
1019+ </item>
1020+ <item row="5" column="1">
1021+ <widget class="QLineEdit" name="txtYear"/>
1022+ </item>
1023+ <item row="2" column="1" colspan="3">
1024+ <widget class="QLineEdit" name="txtAlbum"/>
1025+ </item>
1026+ <item row="3" column="0">
1027+ <widget class="QLabel" name="label_9">
1028+ <property name="text">
1029+ <string>Genre:</string>
1030 </property>
1031 </widget>
1032 </item>
1033@@ -212,7 +212,7 @@
1034 </property>
1035 </widget>
1036 </item>
1037- <item row="13" column="1" colspan="3">
1038+ <item row="14" column="1" colspan="3">
1039 <widget class="QPushButton" name="btnReloadFromFile">
1040 <property name="text">
1041 <string>Reload track metadata from file.</string>
1042@@ -275,6 +275,13 @@
1043 </property>
1044 </widget>
1045 </item>
1046+ <item row="13" column="1" colspan="3">
1047+ <widget class="QPushButton" name="btnFetchTag">
1048+ <property name="text">
1049+ <string>Reload track metadata from musicBrainz</string>
1050+ </property>
1051+ </widget>
1052+ </item>
1053 </layout>
1054 </item>
1055 <item>
1056
1057=== added directory 'mixxx/src/musicbrainz'
1058=== added file 'mixxx/src/musicbrainz/acoustidclient.cpp'
1059--- mixxx/src/musicbrainz/acoustidclient.cpp 1970-01-01 00:00:00 +0000
1060+++ mixxx/src/musicbrainz/acoustidclient.cpp 2013-05-22 07:42:27 +0000
1061@@ -0,0 +1,109 @@
1062+/*****************************************************************************
1063+ * Copyright © 2012 John Maguire <john.maguire@gmail.com> *
1064+ * David Sansome <me@davidsansome.com> *
1065+ * This work is free. You can redistribute it and/or modify it under the *
1066+ * terms of the Do What The Fuck You Want To Public License, Version 2, *
1067+ * as published by Sam Hocevar. *
1068+ * See http://www.wtfpl.net/ for more details. *
1069+ *****************************************************************************/
1070+
1071+#include <QCoreApplication>
1072+#include <QNetworkReply>
1073+#include <QXmlStreamReader>
1074+
1075+#include "acoustidclient.h"
1076+#include "gzip.h"
1077+#include "network.h"
1078+
1079+// see API-KEY site here http://acoustid.org/application/496
1080+// I registered the KEY for version 1.12 -- kain88 (may 2013)
1081+const QString CLIENT_APIKEY = "czKxnkyO";
1082+const QString CLIENT_NAME = "Mixxx1.12";
1083+const QString ACOUSTID_URL = "http://api.acoustid.org/v2/lookup";
1084+const int AcoustidClient::m_DefaultTimeout = 5000; // msec
1085+
1086+AcoustidClient::AcoustidClient(QObject* parent)
1087+ : QObject(parent),
1088+ m_network(this),
1089+ m_timeouts(m_DefaultTimeout, this) {
1090+}
1091+
1092+void AcoustidClient::setTimeout(int msec) {
1093+ m_timeouts.setTimeout(msec);
1094+}
1095+
1096+void AcoustidClient::start(int id, const QString& fingerprint, int duration) {
1097+ QUrl url;
1098+ url.addQueryItem("format", "xml");
1099+ url.addQueryItem("client", CLIENT_APIKEY);
1100+ url.addQueryItem("duration", QString::number(duration));
1101+ url.addQueryItem("meta", "recordingids");
1102+ url.addQueryItem("fingerprint", fingerprint);
1103+
1104+ QNetworkRequest req(QUrl::fromEncoded(ACOUSTID_URL.toAscii()));
1105+ req.setHeader(QNetworkRequest::ContentTypeHeader, "application/x-www-form-urlencoded");
1106+ req.setRawHeader("Content-Encoding", "gzip");
1107+ req.setRawHeader("User-Agent", CLIENT_NAME.toAscii());
1108+
1109+ QNetworkReply* reply = m_network.post(req, gzipCompress(url.encodedQuery()));
1110+ connect(reply, SIGNAL(finished()), SLOT(requestFinished()));
1111+ m_requests[reply] = id;
1112+
1113+ m_timeouts.addReply(reply);
1114+}
1115+
1116+void AcoustidClient::cancel(int id) {
1117+ QNetworkReply* reply = m_requests.key(id);
1118+ m_requests.remove(reply);
1119+ delete reply;
1120+}
1121+
1122+void AcoustidClient::cancelAll() {
1123+ qDeleteAll(m_requests.keys());
1124+ m_requests.clear();
1125+}
1126+
1127+void AcoustidClient::requestFinished() {
1128+ QNetworkReply* reply = qobject_cast<QNetworkReply*>(sender());
1129+ if (!reply)
1130+ return;
1131+
1132+ reply->deleteLater();
1133+ if (!m_requests.contains(reply))
1134+ return;
1135+
1136+ int id = m_requests.take(reply);
1137+
1138+ if (reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt() != 200) {
1139+ emit finished(id, QString());
1140+ return;
1141+ }
1142+
1143+ QXmlStreamReader reader(reply);
1144+ QString ID;
1145+ while (!reader.atEnd()) {
1146+ if (reader.readNext() == QXmlStreamReader::StartElement
1147+ && reader.name()== "results") {
1148+ ID = parseResult(reader);
1149+ }
1150+ }
1151+
1152+ emit finished(id, ID);
1153+}
1154+
1155+QString AcoustidClient::parseResult(QXmlStreamReader& reader){
1156+
1157+ while (!reader.atEnd()) {
1158+ QXmlStreamReader::TokenType type = reader.readNext();
1159+ if (type== QXmlStreamReader::StartElement) {
1160+ QStringRef name = reader.name();
1161+ if (name == "id") {
1162+ return reader.readElementText();
1163+ }
1164+ }
1165+ if (type == QXmlStreamReader::EndElement && reader.name()=="result") {
1166+ break;
1167+ }
1168+ }
1169+ return QString();
1170+}
1171
1172=== added file 'mixxx/src/musicbrainz/acoustidclient.h'
1173--- mixxx/src/musicbrainz/acoustidclient.h 1970-01-01 00:00:00 +0000
1174+++ mixxx/src/musicbrainz/acoustidclient.h 2013-05-22 07:42:27 +0000
1175@@ -0,0 +1,67 @@
1176+/*****************************************************************************
1177+ * Copyright © 2012 John Maguire <john.maguire@gmail.com> *
1178+ * David Sansome <me@davidsansome.com> *
1179+ * This work is free. You can redistribute it and/or modify it under the *
1180+ * terms of the Do What The Fuck You Want To Public License, Version 2, *
1181+ * as published by Sam Hocevar. *
1182+ * See http://www.wtfpl.net/ for more details. *
1183+ *****************************************************************************/
1184+
1185+#ifndef ACOUSTIDCLIENT_H
1186+#define ACOUSTIDCLIENT_H
1187+
1188+#include <QMap>
1189+#include <QObject>
1190+#include <QtNetwork>
1191+
1192+#include "network.h"
1193+#include "trackinfoobject.h"
1194+
1195+class QXmlStreamReader;
1196+
1197+class AcoustidClient : public QObject {
1198+ Q_OBJECT
1199+
1200+ // Gets a MBID from a Chromaprint fingerprint.
1201+ // A fingerprint identifies one particular encoding of a song and is created
1202+ // by Fingerprinter. An MBID identifies the actual song and can be passed to
1203+ // Musicbrainz to get metadata.
1204+ // You can create one AcoustidClient and make multiple requests using it.
1205+ // IDs are provided by the caller when a request is started and included in
1206+ // the finished signal - they have no meaning to AcoustidClient.
1207+
1208+ public:
1209+ AcoustidClient(QObject* parent = 0);
1210+
1211+ // Network requests will be aborted after this interval.
1212+ void setTimeout(int msec);
1213+
1214+ // Starts a request and returns immediately. Finished() will be emitted
1215+ // later with the same ID.
1216+ void start(int id, const QString& fingerprint, int duration);
1217+
1218+ // Cancels the request with the given ID. Finished() will never be emitted
1219+ // for that ID. Does nothing if there is no request with the given ID.
1220+ void cancel(int id);
1221+
1222+ // Cancels all requests. Finished() will never be emitted for any pending
1223+ // requests.
1224+ void cancelAll();
1225+
1226+ QString parseResult(QXmlStreamReader& reader);
1227+
1228+ signals:
1229+ void finished(int id, const QString& mbid);
1230+
1231+ private slots:
1232+ void requestFinished();
1233+
1234+ private:
1235+ static const int m_DefaultTimeout;
1236+
1237+ QNetworkAccessManager m_network;
1238+ NetworkTimeouts m_timeouts;
1239+ QMap<QNetworkReply*, int> m_requests;
1240+};
1241+
1242+#endif // ACOUSTIDCLIENT_H
1243
1244=== added file 'mixxx/src/musicbrainz/chromaprinter.cpp'
1245--- mixxx/src/musicbrainz/chromaprinter.cpp 1970-01-01 00:00:00 +0000
1246+++ mixxx/src/musicbrainz/chromaprinter.cpp 2013-05-22 07:42:27 +0000
1247@@ -0,0 +1,88 @@
1248+#include <QtCore>
1249+#include <chromaprint.h>
1250+
1251+#include "musicbrainz/chromaprinter.h"
1252+#include "soundsourceproxy.h"
1253+#include "defs.h"
1254+
1255+chromaprinter::chromaprinter(QObject* parent)
1256+ : QObject(parent){
1257+}
1258+
1259+QString chromaprinter::getFingerPrint(TrackPointer pTrack){
1260+ SoundSourceProxy soundSource(pTrack);
1261+ return calcFingerPrint(soundSource);
1262+}
1263+
1264+QString chromaprinter::getFingerPrint(QString location){
1265+ SoundSourceProxy soundSource(location);
1266+ return calcFingerPrint(soundSource);
1267+}
1268+
1269+QString chromaprinter::calcFingerPrint(SoundSourceProxy& soundSource){
1270+ soundSource.open();
1271+ m_SampleRate = soundSource.getSampleRate();
1272+ unsigned int length = soundSource.length();
1273+ if (m_SampleRate == 0 ){
1274+ qDebug() << "Skipping invalid file:" << soundSource.getFilename();
1275+ return QString();
1276+ }
1277+
1278+ // this is worth 2min of audio, multiply by 2 because we have 2 channels
1279+ // AcoustID only stores a fingerprint for the first two minutes of a song
1280+ // on their server so we need only a fingerprint of the first two minutes
1281+ // --kain88 July 2012
1282+ m_NumSamples = 120*2*m_SampleRate;
1283+ // check that the song is actually longer then the amount of audio we use
1284+ if (m_NumSamples > length) {
1285+ m_NumSamples = length;
1286+ }
1287+
1288+ SAMPLE *pData = new SAMPLE[m_NumSamples];
1289+ QTime timerReadingFile;
1290+ timerReadingFile.start();
1291+ int read = soundSource.read(m_NumSamples, pData);
1292+
1293+ if (read!=m_NumSamples) {
1294+ qDebug() << "oh that's embarrasing I couldn't read the track";
1295+ return QString();
1296+ }
1297+ qDebug("reading file took: %d ms" , timerReadingFile.elapsed());
1298+
1299+ ChromaprintContext* ctx = chromaprint_new(CHROMAPRINT_ALGORITHM_DEFAULT);
1300+ // we have 2 channels in mixxx always
1301+ chromaprint_start(ctx, m_SampleRate, 2);
1302+
1303+ QTime timerGeneratingFingerPrint;
1304+ timerGeneratingFingerPrint.start();
1305+ int success = chromaprint_feed(ctx, pData, m_NumSamples);
1306+ if (!success) {
1307+ qDebug() << "could not generate fingerprint";
1308+ return QString();
1309+ }
1310+ chromaprint_finish(ctx);
1311+
1312+ void* fprint = NULL;
1313+ int size = 0;
1314+ int ret = chromaprint_get_raw_fingerprint(ctx, &fprint, &size);
1315+ QByteArray fingerprint;
1316+ if (ret == 1) {
1317+ void* encoded = NULL;
1318+ int encoded_size = 0;
1319+ chromaprint_encode_fingerprint(fprint, size,
1320+ CHROMAPRINT_ALGORITHM_DEFAULT,
1321+ &encoded,
1322+ &encoded_size, 1);
1323+
1324+ fingerprint.append(reinterpret_cast<char*>(encoded), encoded_size);
1325+
1326+ chromaprint_dealloc(fprint);
1327+ chromaprint_dealloc(encoded);
1328+ }
1329+ chromaprint_free(ctx);
1330+ delete pData;
1331+
1332+ qDebug("generating fingerprint took: %d ms" , timerGeneratingFingerPrint.elapsed());
1333+
1334+ return fingerprint;
1335+}
1336
1337=== added file 'mixxx/src/musicbrainz/chromaprinter.h'
1338--- mixxx/src/musicbrainz/chromaprinter.h 1970-01-01 00:00:00 +0000
1339+++ mixxx/src/musicbrainz/chromaprinter.h 2013-05-22 07:42:27 +0000
1340@@ -0,0 +1,25 @@
1341+#ifndef CHROMAPRINTER_H
1342+#define CHROMAPRINTER_H
1343+
1344+#include <QObject>
1345+
1346+#include "trackinfoobject.h"
1347+
1348+class SoundSourceProxy;
1349+
1350+class chromaprinter: public QObject {
1351+ Q_OBJECT
1352+
1353+ public:
1354+ chromaprinter(QObject* parent=NULL);
1355+ QString getFingerPrint(TrackPointer pTrack);
1356+ QString getFingerPrint(QString location);
1357+
1358+ private:
1359+
1360+ QString calcFingerPrint(SoundSourceProxy& soundSource);
1361+ unsigned int m_NumSamples;
1362+ unsigned int m_SampleRate;
1363+};
1364+
1365+#endif //CHROMAPRINTER_H
1366
1367=== added file 'mixxx/src/musicbrainz/crc.c'
1368--- mixxx/src/musicbrainz/crc.c 1970-01-01 00:00:00 +0000
1369+++ mixxx/src/musicbrainz/crc.c 2013-05-22 07:42:27 +0000
1370@@ -0,0 +1,133 @@
1371+/**
1372+ * \file crc.c
1373+ * Functions and types for CRC checks.
1374+ *
1375+ * Generated on Sun Oct 24 10:58:31 2010,
1376+ * by pycrc v0.7.6, http://www.tty1.net/pycrc/
1377+ * using the configuration:
1378+ * Width = 32
1379+ * Poly = 0x04c11db7
1380+ * XorIn = 0xffffffff
1381+ * ReflectIn = True
1382+ * XorOut = 0xffffffff
1383+ * ReflectOut = True
1384+ * Algorithm = table-driven
1385+ *****************************************************************************/
1386+#include "crc.h"
1387+#include <stdint.h>
1388+#include <stdlib.h>
1389+
1390+/**
1391+ * Static table used for the table_driven implementation.
1392+ *****************************************************************************/
1393+static const crc_t crc_table[256] = {
1394+ 0x00000000, 0x77073096, 0xee0e612c, 0x990951ba,
1395+ 0x076dc419, 0x706af48f, 0xe963a535, 0x9e6495a3,
1396+ 0x0edb8832, 0x79dcb8a4, 0xe0d5e91e, 0x97d2d988,
1397+ 0x09b64c2b, 0x7eb17cbd, 0xe7b82d07, 0x90bf1d91,
1398+ 0x1db71064, 0x6ab020f2, 0xf3b97148, 0x84be41de,
1399+ 0x1adad47d, 0x6ddde4eb, 0xf4d4b551, 0x83d385c7,
1400+ 0x136c9856, 0x646ba8c0, 0xfd62f97a, 0x8a65c9ec,
1401+ 0x14015c4f, 0x63066cd9, 0xfa0f3d63, 0x8d080df5,
1402+ 0x3b6e20c8, 0x4c69105e, 0xd56041e4, 0xa2677172,
1403+ 0x3c03e4d1, 0x4b04d447, 0xd20d85fd, 0xa50ab56b,
1404+ 0x35b5a8fa, 0x42b2986c, 0xdbbbc9d6, 0xacbcf940,
1405+ 0x32d86ce3, 0x45df5c75, 0xdcd60dcf, 0xabd13d59,
1406+ 0x26d930ac, 0x51de003a, 0xc8d75180, 0xbfd06116,
1407+ 0x21b4f4b5, 0x56b3c423, 0xcfba9599, 0xb8bda50f,
1408+ 0x2802b89e, 0x5f058808, 0xc60cd9b2, 0xb10be924,
1409+ 0x2f6f7c87, 0x58684c11, 0xc1611dab, 0xb6662d3d,
1410+ 0x76dc4190, 0x01db7106, 0x98d220bc, 0xefd5102a,
1411+ 0x71b18589, 0x06b6b51f, 0x9fbfe4a5, 0xe8b8d433,
1412+ 0x7807c9a2, 0x0f00f934, 0x9609a88e, 0xe10e9818,
1413+ 0x7f6a0dbb, 0x086d3d2d, 0x91646c97, 0xe6635c01,
1414+ 0x6b6b51f4, 0x1c6c6162, 0x856530d8, 0xf262004e,
1415+ 0x6c0695ed, 0x1b01a57b, 0x8208f4c1, 0xf50fc457,
1416+ 0x65b0d9c6, 0x12b7e950, 0x8bbeb8ea, 0xfcb9887c,
1417+ 0x62dd1ddf, 0x15da2d49, 0x8cd37cf3, 0xfbd44c65,
1418+ 0x4db26158, 0x3ab551ce, 0xa3bc0074, 0xd4bb30e2,
1419+ 0x4adfa541, 0x3dd895d7, 0xa4d1c46d, 0xd3d6f4fb,
1420+ 0x4369e96a, 0x346ed9fc, 0xad678846, 0xda60b8d0,
1421+ 0x44042d73, 0x33031de5, 0xaa0a4c5f, 0xdd0d7cc9,
1422+ 0x5005713c, 0x270241aa, 0xbe0b1010, 0xc90c2086,
1423+ 0x5768b525, 0x206f85b3, 0xb966d409, 0xce61e49f,
1424+ 0x5edef90e, 0x29d9c998, 0xb0d09822, 0xc7d7a8b4,
1425+ 0x59b33d17, 0x2eb40d81, 0xb7bd5c3b, 0xc0ba6cad,
1426+ 0xedb88320, 0x9abfb3b6, 0x03b6e20c, 0x74b1d29a,
1427+ 0xead54739, 0x9dd277af, 0x04db2615, 0x73dc1683,
1428+ 0xe3630b12, 0x94643b84, 0x0d6d6a3e, 0x7a6a5aa8,
1429+ 0xe40ecf0b, 0x9309ff9d, 0x0a00ae27, 0x7d079eb1,
1430+ 0xf00f9344, 0x8708a3d2, 0x1e01f268, 0x6906c2fe,
1431+ 0xf762575d, 0x806567cb, 0x196c3671, 0x6e6b06e7,
1432+ 0xfed41b76, 0x89d32be0, 0x10da7a5a, 0x67dd4acc,
1433+ 0xf9b9df6f, 0x8ebeeff9, 0x17b7be43, 0x60b08ed5,
1434+ 0xd6d6a3e8, 0xa1d1937e, 0x38d8c2c4, 0x4fdff252,
1435+ 0xd1bb67f1, 0xa6bc5767, 0x3fb506dd, 0x48b2364b,
1436+ 0xd80d2bda, 0xaf0a1b4c, 0x36034af6, 0x41047a60,
1437+ 0xdf60efc3, 0xa867df55, 0x316e8eef, 0x4669be79,
1438+ 0xcb61b38c, 0xbc66831a, 0x256fd2a0, 0x5268e236,
1439+ 0xcc0c7795, 0xbb0b4703, 0x220216b9, 0x5505262f,
1440+ 0xc5ba3bbe, 0xb2bd0b28, 0x2bb45a92, 0x5cb36a04,
1441+ 0xc2d7ffa7, 0xb5d0cf31, 0x2cd99e8b, 0x5bdeae1d,
1442+ 0x9b64c2b0, 0xec63f226, 0x756aa39c, 0x026d930a,
1443+ 0x9c0906a9, 0xeb0e363f, 0x72076785, 0x05005713,
1444+ 0x95bf4a82, 0xe2b87a14, 0x7bb12bae, 0x0cb61b38,
1445+ 0x92d28e9b, 0xe5d5be0d, 0x7cdcefb7, 0x0bdbdf21,
1446+ 0x86d3d2d4, 0xf1d4e242, 0x68ddb3f8, 0x1fda836e,
1447+ 0x81be16cd, 0xf6b9265b, 0x6fb077e1, 0x18b74777,
1448+ 0x88085ae6, 0xff0f6a70, 0x66063bca, 0x11010b5c,
1449+ 0x8f659eff, 0xf862ae69, 0x616bffd3, 0x166ccf45,
1450+ 0xa00ae278, 0xd70dd2ee, 0x4e048354, 0x3903b3c2,
1451+ 0xa7672661, 0xd06016f7, 0x4969474d, 0x3e6e77db,
1452+ 0xaed16a4a, 0xd9d65adc, 0x40df0b66, 0x37d83bf0,
1453+ 0xa9bcae53, 0xdebb9ec5, 0x47b2cf7f, 0x30b5ffe9,
1454+ 0xbdbdf21c, 0xcabac28a, 0x53b39330, 0x24b4a3a6,
1455+ 0xbad03605, 0xcdd70693, 0x54de5729, 0x23d967bf,
1456+ 0xb3667a2e, 0xc4614ab8, 0x5d681b02, 0x2a6f2b94,
1457+ 0xb40bbe37, 0xc30c8ea1, 0x5a05df1b, 0x2d02ef8d
1458+};
1459+
1460+/**
1461+ * Reflect all bits of a \a data word of \a data_len bytes.
1462+ *
1463+ * \param data The data word to be reflected.
1464+ * \param data_len The width of \a data expressed in number of bits.
1465+ * \return The reflected data.
1466+ *****************************************************************************/
1467+crc_t crc_reflect(crc_t data, size_t data_len)
1468+{
1469+ unsigned int i;
1470+ crc_t ret;
1471+
1472+ ret = data & 0x01;
1473+ for (i = 1; i < data_len; i++) {
1474+ data >>= 1;
1475+ ret = (ret << 1) | (data & 0x01);
1476+ }
1477+ return ret;
1478+}
1479+
1480+
1481+/**
1482+ * Update the crc value with new data.
1483+ *
1484+ * \param crc The current crc value.
1485+ * \param data Pointer to a buffer of \a data_len bytes.
1486+ * \param data_len Number of bytes in the \a data buffer.
1487+ * \return The updated crc value.
1488+ *****************************************************************************/
1489+crc_t crc_update(crc_t crc, const unsigned char *data, size_t data_len)
1490+{
1491+ unsigned int tbl_idx;
1492+
1493+ while (data_len--) {
1494+ tbl_idx = (crc ^ *data) & 0xff;
1495+ crc = (crc_table[tbl_idx] ^ (crc >> 8)) & 0xffffffff;
1496+
1497+ data++;
1498+ }
1499+ return crc & 0xffffffff;
1500+}
1501+
1502+
1503+
1504
1505=== added file 'mixxx/src/musicbrainz/crc.h'
1506--- mixxx/src/musicbrainz/crc.h 1970-01-01 00:00:00 +0000
1507+++ mixxx/src/musicbrainz/crc.h 2013-05-22 07:42:27 +0000
1508@@ -0,0 +1,83 @@
1509+/**
1510+ * \file crc.h
1511+ * Functions and types for CRC checks.
1512+ *
1513+ * Generated on Sun Oct 24 10:58:41 2010,
1514+ * by pycrc v0.7.6, http://www.tty1.net/pycrc/
1515+ * using the configuration:
1516+ * Width = 32
1517+ * Poly = 0x04c11db7
1518+ * XorIn = 0xffffffff
1519+ * ReflectIn = True
1520+ * XorOut = 0xffffffff
1521+ * ReflectOut = True
1522+ * Algorithm = table-driven
1523+ *****************************************************************************/
1524+#ifndef __CRC_H__
1525+#define __CRC_H__
1526+
1527+#include <stdint.h>
1528+#include <stdlib.h>
1529+
1530+#ifdef __cplusplus
1531+extern "C" {
1532+#endif
1533+
1534+
1535+/**
1536+ * The definition of the used algorithm.
1537+ *****************************************************************************/
1538+#define CRC_ALGO_TABLE_DRIVEN 1
1539+
1540+
1541+/**
1542+ * The type of the CRC values.
1543+ *
1544+ * This type must be big enough to contain at least 32 bits.
1545+ *****************************************************************************/
1546+typedef unsigned long crc_t;
1547+
1548+
1549+/**
1550+ * Reflect all bits of a \a data word of \a data_len bytes.
1551+ *
1552+ * \param data The data word to be reflected.
1553+ * \param data_len The width of \a data expressed in number of bits.
1554+ * \return The reflected data.
1555+ *****************************************************************************/
1556+crc_t crc_reflect(crc_t data, size_t data_len);
1557+
1558+
1559+/**
1560+ * Calculate the initial crc value.
1561+ *
1562+ * \return The initial crc value.
1563+ *****************************************************************************/
1564+#define crc_init() (0xffffffff)
1565+
1566+
1567+/**
1568+ * Update the crc value with new data.
1569+ *
1570+ * \param crc The current crc value.
1571+ * \param data Pointer to a buffer of \a data_len bytes.
1572+ * \param data_len Number of bytes in the \a data buffer.
1573+ * \return The updated crc value.
1574+ *****************************************************************************/
1575+crc_t crc_update(crc_t crc, const unsigned char *data, size_t data_len);
1576+
1577+
1578+/**
1579+ * Calculate the final crc value.
1580+ *
1581+ * \param crc The current crc value.
1582+ * \return The final crc value.
1583+ *****************************************************************************/
1584+#define crc_finalize(crc) (crc ^ 0xffffffff)
1585+
1586+
1587+#ifdef __cplusplus
1588+} /* closing brace for extern "C" */
1589+#endif
1590+
1591+#endif /* __CRC_H__ */
1592
1593=== added file 'mixxx/src/musicbrainz/gzip.cpp'
1594--- mixxx/src/musicbrainz/gzip.cpp 1970-01-01 00:00:00 +0000
1595+++ mixxx/src/musicbrainz/gzip.cpp 2013-05-22 07:42:27 +0000
1596@@ -0,0 +1,49 @@
1597+/*****************************************************************************
1598+ * Author: Lukáš Lalinský <info@acoustid.org> *
1599+ *****************************************************************************/
1600+
1601+#include "crc.h"
1602+#include "gzip.h"
1603+
1604+inline QByteArray render32BitInt(unsigned long value)
1605+{
1606+ unsigned char data[4];
1607+ data[0] = (value ) & 255;
1608+ data[1] = (value >> 8) & 255;
1609+ data[2] = (value >> 16) & 255;
1610+ data[3] = (value >> 24) & 255;
1611+ return QByteArray((char *)data, 4);
1612+}
1613+
1614+inline unsigned long calculateCrc32(const QByteArray &data)
1615+{
1616+ crc_t crc;
1617+ crc = crc_init();
1618+ crc = crc_update(crc, (unsigned char *)data.data(), data.size());
1619+ crc = crc_finalize(crc);
1620+ return crc;
1621+}
1622+
1623+QByteArray gzipCompress(const QByteArray &data)
1624+{
1625+ const char header[10] = {
1626+ 0x1f, 0x8b, // ID1 + ID2
1627+ 8, // Compression Method
1628+ 0, // Flags
1629+ 0, 0, 0, 0, // Modification Time
1630+ 2, // Extra Flags
1631+ 255, // Operating System
1632+ };
1633+
1634+ QByteArray compressedData = qCompress(data);
1635+ compressedData.remove(0, 6); // Qt size + zlib header
1636+ compressedData.remove(compressedData.size() - 4, 4); // zlib footer
1637+
1638+ QByteArray result;
1639+ result.append(header, 10);
1640+ result.append(compressedData);
1641+ result.append(render32BitInt(calculateCrc32(data)));
1642+ result.append(render32BitInt(data.size()));
1643+ return result;
1644+}
1645+
1646
1647=== added file 'mixxx/src/musicbrainz/gzip.h'
1648--- mixxx/src/musicbrainz/gzip.h 1970-01-01 00:00:00 +0000
1649+++ mixxx/src/musicbrainz/gzip.h 2013-05-22 07:42:27 +0000
1650@@ -0,0 +1,12 @@
1651+/*****************************************************************************
1652+ * Author: Lukáš Lalinský <info@acoustid.org> *
1653+ *****************************************************************************/
1654+
1655+#ifndef FPSUBMIT_GZIP_H_
1656+#define FPSUBMIT_GZIP_H_
1657+
1658+#include <QByteArray>
1659+
1660+QByteArray gzipCompress(const QByteArray &data);
1661+
1662+#endif
1663
1664=== added file 'mixxx/src/musicbrainz/musicbrainzclient.cpp'
1665--- mixxx/src/musicbrainz/musicbrainzclient.cpp 1970-01-01 00:00:00 +0000
1666+++ mixxx/src/musicbrainz/musicbrainzclient.cpp 2013-05-22 07:42:27 +0000
1667@@ -0,0 +1,180 @@
1668+/*****************************************************************************
1669+ * Copyright © 2012 John Maguire <john.maguire@gmail.com> *
1670+ * David Sansome <me@davidsansome.com> *
1671+ * This work is free. You can redistribute it and/or modify it under the *
1672+ * terms of the Do What The Fuck You Want To Public License, Version 2, *
1673+ * as published by Sam Hocevar. *
1674+ * See http://www.wtfpl.net/ for more details. *
1675+ *****************************************************************************/
1676+
1677+#include <QCoreApplication>
1678+#include <QNetworkReply>
1679+#include <QtNetwork>
1680+#include <QSet>
1681+#include <QXmlStreamReader>
1682+
1683+#include "musicbrainzclient.h"
1684+
1685+const QString MusicBrainzClient::m_TrackUrl = "http://musicbrainz.org/ws/2/recording/";
1686+const QString MusicBrainzClient::m_DateRegex = "^[12]\\d{3}";
1687+const int MusicBrainzClient::m_DefaultTimeout = 5000; // msec
1688+
1689+MusicBrainzClient::MusicBrainzClient(QObject* parent)
1690+ : QObject(parent),
1691+ m_network(this),
1692+ m_timeouts(m_DefaultTimeout, this){
1693+}
1694+
1695+void MusicBrainzClient::start(int id, const QString& mbid) {
1696+ typedef QPair<QString, QString> Param;
1697+
1698+ QList<Param> parameters;
1699+ parameters << Param("inc", "artists+releases+media");
1700+
1701+ QUrl url(m_TrackUrl + mbid);
1702+ url.setQueryItems(parameters);
1703+ QNetworkRequest req(url);
1704+
1705+ QNetworkReply* reply = m_network.get(req);
1706+ connect(reply, SIGNAL(finished()), SLOT(requestFinished()));
1707+ m_requests[reply] = id;
1708+
1709+ m_timeouts.addReply(reply);
1710+}
1711+
1712+void MusicBrainzClient::cancel(int id) {
1713+ QNetworkReply* reply = m_requests.key(id);
1714+ m_requests.remove(reply);
1715+ delete reply;
1716+}
1717+
1718+void MusicBrainzClient::cancelAll() {
1719+ qDeleteAll(m_requests.keys());
1720+ m_requests.clear();
1721+}
1722+
1723+void MusicBrainzClient::requestFinished() {
1724+ QNetworkReply* reply = qobject_cast<QNetworkReply*>(sender());
1725+ if (!reply)
1726+ return;
1727+
1728+ reply->deleteLater();
1729+ if (!m_requests.contains(reply))
1730+ return;
1731+
1732+ int id = m_requests.take(reply);
1733+ ResultList ret;
1734+
1735+ if (reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt() != 200) {
1736+ emit finished(id, ret);
1737+ return;
1738+ }
1739+
1740+ QXmlStreamReader reader(reply);
1741+ while (!reader.atEnd()) {
1742+ if (reader.readNext() == QXmlStreamReader::StartElement
1743+ && reader.name() == "recording") {
1744+
1745+ ResultList tracks = parseTrack(reader);
1746+ foreach (const Result& track, tracks) {
1747+ if (!track.m_title.isEmpty()) {
1748+ ret << track;
1749+ }
1750+ }
1751+ }
1752+ }
1753+ emit finished(id, uniqueResults(ret));
1754+}
1755+
1756+MusicBrainzClient::ResultList MusicBrainzClient::parseTrack(QXmlStreamReader& reader) {
1757+ Result result;
1758+ QList<Release> releases;
1759+
1760+ while (!reader.atEnd()) {
1761+ QXmlStreamReader::TokenType type = reader.readNext();
1762+
1763+ if (type == QXmlStreamReader::StartElement) {
1764+ QStringRef name = reader.name();
1765+ if (name == "title") {
1766+ result.m_title = reader.readElementText();
1767+ } else if (name == "length") {
1768+ // convert msec to sec
1769+ result.m_duration = reader.readElementText().toInt()*10000000;
1770+ } else if (name == "artist") {
1771+ parseArtist(reader, result.m_artist);
1772+ } else if (name == "release") {
1773+ releases << parseRelease(reader);
1774+ }
1775+ }
1776+
1777+ if (type == QXmlStreamReader::EndElement && reader.name() == "recording") {
1778+ break;
1779+ }
1780+ }
1781+
1782+ ResultList ret;
1783+ foreach (const Release& release, releases) {
1784+ ret << release.CopyAndMergeInto(result);
1785+ }
1786+ return ret;
1787+}
1788+
1789+void MusicBrainzClient::parseArtist(QXmlStreamReader& reader, QString& artist) {
1790+ while (!reader.atEnd()) {
1791+ QXmlStreamReader::TokenType type = reader.readNext();
1792+
1793+ if (type == QXmlStreamReader::StartElement && reader.name() == "name") {
1794+ artist = reader.readElementText();
1795+ }
1796+
1797+ if (type == QXmlStreamReader::EndElement && reader.name() == "artist") {
1798+ return;
1799+ }
1800+ }
1801+}
1802+
1803+MusicBrainzClient::Release MusicBrainzClient::parseRelease(QXmlStreamReader& reader) {
1804+ Release ret;
1805+
1806+ while (!reader.atEnd()) {
1807+ QXmlStreamReader::TokenType type = reader.readNext();
1808+
1809+ if (type == QXmlStreamReader::StartElement) {
1810+ QStringRef name = reader.name();
1811+ if (name == "title") {
1812+ ret.m_album = reader.readElementText();
1813+ } else if (name == "date") {
1814+ QRegExp regex(m_DateRegex);
1815+ if (regex.indexIn(reader.readElementText()) == 0) {
1816+ ret.m_year = regex.cap(0).toInt();
1817+ }
1818+ } else if (name == "track-list") {
1819+ ret.m_track = reader.attributes().value("offset").toString().toInt() + 1;
1820+ consumeCurrentElement(reader);
1821+ }
1822+ }
1823+
1824+ if (type == QXmlStreamReader::EndElement && reader.name() == "release") {
1825+ break;
1826+ }
1827+ }
1828+
1829+ return ret;
1830+}
1831+
1832+MusicBrainzClient::ResultList MusicBrainzClient::uniqueResults(const ResultList& results) {
1833+ ResultList ret = QSet<Result>::fromList(results).toList();
1834+ qSort(ret);
1835+ return ret;
1836+}
1837+
1838+void MusicBrainzClient::consumeCurrentElement(QXmlStreamReader& reader) {
1839+ int level = 1;
1840+ while (level != 0 && !reader.atEnd()) {
1841+ switch (reader.readNext()) {
1842+ case QXmlStreamReader::StartElement: ++level; break;
1843+ case QXmlStreamReader::EndElement: --level; break;
1844+ default: break;
1845+ }
1846+ }
1847+}
1848
1849=== added file 'mixxx/src/musicbrainz/musicbrainzclient.h'
1850--- mixxx/src/musicbrainz/musicbrainzclient.h 1970-01-01 00:00:00 +0000
1851+++ mixxx/src/musicbrainz/musicbrainzclient.h 2013-05-22 07:42:27 +0000
1852@@ -0,0 +1,130 @@
1853+/*****************************************************************************
1854+ * Copyright © 2012 John Maguire <john.maguire@gmail.com> *
1855+ * David Sansome <me@davidsansome.com> *
1856+ * This work is free. You can redistribute it and/or modify it under the *
1857+ * terms of the Do What The Fuck You Want To Public License, Version 2, *
1858+ * as published by Sam Hocevar. *
1859+ * See http://www.wtfpl.net/ for more details. *
1860+ *****************************************************************************/
1861+
1862+#ifndef MUSICBRAINZCLIENT_H
1863+#define MUSICBRAINZCLIENT_H
1864+
1865+#include <QHash>
1866+#include <QMap>
1867+#include <QObject>
1868+#include <QXmlStreamReader>
1869+#include <QtNetwork>
1870+
1871+#include "network.h"
1872+
1873+class MusicBrainzClient : public QObject {
1874+ Q_OBJECT
1875+
1876+ // Gets metadata for a particular MBID.
1877+ // An MBID is created from a fingerprint using chromaprint library.
1878+ // You can create one MusicBrainzClient and make multiple requests using it.
1879+ // IDs are provided by the caller when a request is started and included in
1880+ // the Finished signal - they have no meaning to MusicBrainzClient.
1881+
1882+ public:
1883+ MusicBrainzClient(QObject* parent = 0);
1884+
1885+ struct Result {
1886+ Result() : m_duration(0), m_track(0), m_year(-1) {}
1887+
1888+ bool operator <(const Result& other) const {
1889+ #define cmp(field) \
1890+ if (field < other.field) return true; \
1891+ if (field > other.field) return false;
1892+
1893+ cmp(m_track);
1894+ cmp(m_year);
1895+ cmp(m_title);
1896+ cmp(m_artist);
1897+ return false;
1898+
1899+ #undef cmp
1900+ }
1901+
1902+ bool operator ==(const Result& other) const {
1903+ return m_title == other.m_title &&
1904+ m_artist == other.m_artist &&
1905+ m_album == other.m_album &&
1906+ m_duration == other.m_duration &&
1907+ m_track == other.m_track &&
1908+ m_year == other.m_year;
1909+ }
1910+
1911+ QString m_title;
1912+ QString m_artist;
1913+ QString m_album;
1914+ int m_duration;
1915+ int m_track;
1916+ int m_year;
1917+ };
1918+ typedef QList<Result> ResultList;
1919+
1920+
1921+ // Starts a request and returns immediately. finished() will be emitted
1922+ // later with the same ID.
1923+ void start(int id, const QString& mbid);
1924+ static void consumeCurrentElement(QXmlStreamReader& reader);
1925+
1926+ // Cancels the request with the given ID. Finished() will never be emitted
1927+ // for that ID. Does nothing if there is no request with the given ID.
1928+ void cancel(int id);
1929+
1930+ // Cancels all requests. Finished() will never be emitted for any pending
1931+ // requests.
1932+ void cancelAll();
1933+
1934+ signals:
1935+ // Finished signal emitted when fechting songs tags
1936+ void finished(int id, const MusicBrainzClient::ResultList& result);
1937+
1938+ private slots:
1939+ void requestFinished();
1940+
1941+ private:
1942+ struct Release {
1943+ Release() : m_track(0), m_year(0) {}
1944+
1945+ Result CopyAndMergeInto(const Result& orig) const {
1946+ Result ret(orig);
1947+ ret.m_album = m_album;
1948+ ret.m_track = m_track;
1949+ ret.m_year = m_year;
1950+ return ret;
1951+ }
1952+
1953+ QString m_album;
1954+ int m_track;
1955+ int m_year;
1956+ };
1957+
1958+ static ResultList parseTrack(QXmlStreamReader& reader);
1959+ static void parseArtist(QXmlStreamReader& reader, QString& artist);
1960+ static Release parseRelease(QXmlStreamReader& reader);
1961+ static ResultList uniqueResults(const ResultList& results);
1962+
1963+ private:
1964+ static const QString m_TrackUrl;
1965+ static const QString m_DateRegex;
1966+ static const int m_DefaultTimeout;
1967+
1968+ QNetworkAccessManager m_network;
1969+ NetworkTimeouts m_timeouts;
1970+ QMap<QNetworkReply*, int> m_requests;
1971+};
1972+
1973+inline uint qHash(const MusicBrainzClient::Result& result) {
1974+ return qHash(result.m_album) ^
1975+ qHash(result.m_artist) ^
1976+ result.m_duration ^
1977+ qHash(result.m_title) ^
1978+ result.m_track ^
1979+ result.m_year;
1980+}
1981+
1982+#endif // MUSICBRAINZCLIENT_H
1983
1984=== added file 'mixxx/src/musicbrainz/tagfetcher.cpp'
1985--- mixxx/src/musicbrainz/tagfetcher.cpp 1970-01-01 00:00:00 +0000
1986+++ mixxx/src/musicbrainz/tagfetcher.cpp 2013-05-22 07:42:27 +0000
1987@@ -0,0 +1,121 @@
1988+/*****************************************************************************
1989+ * Copyright © 2012 John Maguire <john.maguire@gmail.com> *
1990+ * David Sansome <me@davidsansome.com> *
1991+ * This work is free. You can redistribute it and/or modify it under the *
1992+ * terms of the Do What The Fuck You Want To Public License, Version 2, *
1993+ * as published by Sam Hocevar. *
1994+ * See http://www.wtfpl.net/ for more details. *
1995+ *****************************************************************************/
1996+
1997+#include <QFuture>
1998+#include <QUrl>
1999+#include <QtConcurrentMap>
2000+
2001+#include "musicbrainz/tagfetcher.h"
2002+#include "musicbrainz/chromaprinter.h"
2003+#include "musicbrainz/musicbrainzclient.h"
2004+
2005+TagFetcher::TagFetcher(QObject* parent)
2006+ : QObject(parent),
2007+ m_pFingerprintWatcher(NULL),
2008+ m_AcoustidClient(this),
2009+ m_MusicbrainzClient(this) {
2010+ connect(&m_AcoustidClient, SIGNAL(finished(int,QString)),
2011+ this, SLOT(mbidFound(int,QString)));
2012+ connect(&m_MusicbrainzClient, SIGNAL(finished(int,MusicBrainzClient::ResultList)),
2013+ this, SLOT(tagsFetched(int,MusicBrainzClient::ResultList)));
2014+}
2015+
2016+QString TagFetcher::getFingerprint(const TrackPointer tio) {
2017+ return chromaprinter(NULL).getFingerPrint(tio);
2018+}
2019+
2020+void TagFetcher::startFetch(const TrackPointer track) {
2021+ cancel();
2022+ // qDebug() << "start to fetch track metadata";
2023+ QList<TrackPointer> tracks;
2024+ tracks.append(track);
2025+ m_tracks = tracks;
2026+
2027+ QFuture<QString> future = QtConcurrent::mapped(m_tracks, getFingerprint);
2028+ m_pFingerprintWatcher = new QFutureWatcher<QString>(this);
2029+ m_pFingerprintWatcher->setFuture(future);
2030+ connect(m_pFingerprintWatcher, SIGNAL(resultReadyAt(int)),
2031+ SLOT(fingerprintFound(int)));
2032+
2033+ foreach (const TrackPointer ptrack, m_tracks) {
2034+ emit fetchProgress(tr("Fingerprinting track"));
2035+ }
2036+}
2037+
2038+void TagFetcher::cancel() {
2039+ // qDebug()<< "Cancel tagfetching";
2040+ if (m_pFingerprintWatcher) {
2041+ m_pFingerprintWatcher->cancel();
2042+
2043+ delete m_pFingerprintWatcher;
2044+ m_pFingerprintWatcher = NULL;
2045+ }
2046+
2047+ m_AcoustidClient.cancelAll();
2048+ m_MusicbrainzClient.cancelAll();
2049+ m_tracks.clear();
2050+}
2051+
2052+void TagFetcher::fingerprintFound(int index) {
2053+ QFutureWatcher<QString>* watcher = reinterpret_cast<QFutureWatcher<QString>*>(sender());
2054+ if (!watcher || index >= m_tracks.count()) {
2055+ return;
2056+ }
2057+
2058+ const QString fingerprint = watcher->resultAt(index);
2059+ const TrackPointer ptrack = m_tracks[index];
2060+
2061+ if (fingerprint.isEmpty()) {
2062+ emit resultAvailable(ptrack, QList<TrackPointer>());
2063+ return;
2064+ }
2065+
2066+ emit fetchProgress(tr("Identifying track"));
2067+ // qDebug() << "start to look up the MBID";
2068+ m_AcoustidClient.start(index, fingerprint, ptrack->getDuration());
2069+}
2070+
2071+void TagFetcher::mbidFound(int index, const QString& mbid) {
2072+ if (index >= m_tracks.count()) {
2073+ return;
2074+ }
2075+
2076+ const TrackPointer pTrack = m_tracks[index];
2077+
2078+ if (mbid.isEmpty()) {
2079+ emit resultAvailable(pTrack, QList<TrackPointer>());
2080+ return;
2081+ }
2082+
2083+ emit fetchProgress(tr("Downloading Metadata"));
2084+ //qDebug() << "start to fetch tags from MB";
2085+ m_MusicbrainzClient.start(index, mbid);
2086+}
2087+
2088+void TagFetcher::tagsFetched(int index, const MusicBrainzClient::ResultList& results) {
2089+ if (index >= m_tracks.count()) {
2090+ return;
2091+ }
2092+ // qDebug() << "Tagfetcher got musicbrainz results and now parses them";
2093+ const TrackPointer originalTrack = m_tracks[index];
2094+ QList<TrackPointer> tracksGuessed;
2095+
2096+ foreach (const MusicBrainzClient::Result& result, results) {
2097+ TrackPointer track(new TrackInfoObject(originalTrack->getLocation(),false),
2098+ &QObject::deleteLater);
2099+ track->setTitle(result.m_title);
2100+ track->setArtist(result.m_artist);
2101+ track->setAlbum(result.m_album);
2102+ track->setDuration(result.m_duration);
2103+ track->setTrackNumber(QString::number(result.m_track));
2104+ track->setYear(QString::number(result.m_year));
2105+ tracksGuessed << track;
2106+ }
2107+ emit resultAvailable(originalTrack, tracksGuessed);
2108+}
2109
2110=== added file 'mixxx/src/musicbrainz/tagfetcher.h'
2111--- mixxx/src/musicbrainz/tagfetcher.h 1970-01-01 00:00:00 +0000
2112+++ mixxx/src/musicbrainz/tagfetcher.h 2013-05-22 07:42:27 +0000
2113@@ -0,0 +1,58 @@
2114+/*****************************************************************************
2115+ * Copyright © 2012 John Maguire <john.maguire@gmail.com> *
2116+ * David Sansome <me@davidsansome.com> *
2117+ * This work is free. You can redistribute it and/or modify it under the *
2118+ * terms of the Do What The Fuck You Want To Public License, Version 2, *
2119+ * as published by Sam Hocevar. *
2120+ * See http://www.wtfpl.net/ for more details. *
2121+ *****************************************************************************/
2122+
2123+#ifndef TAGFETCHER_H
2124+#define TAGFETCHER_H
2125+
2126+#include <QFutureWatcher>
2127+#include <QObject>
2128+
2129+#include "musicbrainz/musicbrainzclient.h"
2130+#include "musicbrainz/acoustidclient.h"
2131+#include "trackinfoobject.h"
2132+
2133+
2134+class TagFetcher : public QObject {
2135+ Q_OBJECT
2136+
2137+ // High level interface to Fingerprinter, AcoustidClient and
2138+ // MusicBrainzClient.
2139+
2140+ public:
2141+ TagFetcher(QObject* parent = 0);
2142+
2143+ void startFetch(const TrackPointer track);
2144+
2145+ public slots:
2146+ void cancel();
2147+
2148+ signals:
2149+ void resultAvailable(const TrackPointer originalTrack,
2150+ const QList<TrackPointer>& tracksGuessed);
2151+ void fetchProgress(QString);
2152+
2153+ private slots:
2154+ void fingerprintFound(int index);
2155+ void mbidFound(int index, const QString& mbid);
2156+ void tagsFetched(int index, const MusicBrainzClient::ResultList& result);
2157+
2158+ private:
2159+ // has to be static so we can call it with QtConcurrent and have a nice
2160+ // responsive UI while the fingerprint is calculated
2161+ static QString getFingerprint(const TrackPointer tio);
2162+
2163+ QFutureWatcher<QString>* m_pFingerprintWatcher;
2164+ AcoustidClient m_AcoustidClient;
2165+ MusicBrainzClient m_MusicbrainzClient;
2166+
2167+ // Code can already be run on an arbitrary number of input tracks
2168+ QList<TrackPointer> m_tracks;
2169+};
2170+
2171+#endif // TAGFETCHER_H
2172
2173=== added file 'mixxx/src/network.cpp'
2174--- mixxx/src/network.cpp 1970-01-01 00:00:00 +0000
2175+++ mixxx/src/network.cpp 2013-05-22 07:42:27 +0000
2176@@ -0,0 +1,73 @@
2177+/*****************************************************************************
2178+ * Copyright © 2012 John Maguire <john.maguire@gmail.com> *
2179+ * David Sansome <me@davidsansome.com> *
2180+ * This work is free. You can redistribute it and/or modify it under the *
2181+ * terms of the Do What The Fuck You Want To Public License, Version 2, *
2182+ * as published by Sam Hocevar. *
2183+ * See http://www.wtfpl.net/ for more details. *
2184+ *****************************************************************************/
2185+
2186+#include <QCoreApplication>
2187+#include <QDir>
2188+#include <QNetworkAccessManager>
2189+#include <QNetworkDiskCache>
2190+#include <QNetworkReply>
2191+
2192+#include "network.h"
2193+
2194+NetworkAccessManager::NetworkAccessManager(QObject* parent)
2195+ : QNetworkAccessManager(parent) {
2196+}
2197+
2198+QNetworkReply* NetworkAccessManager::createRequest(Operation op,
2199+ const QNetworkRequest& request,
2200+ QIODevice* outgoingData) {
2201+ QNetworkRequest new_request(request);
2202+ new_request.setRawHeader("User-Agent", QString("%1 %2").arg(
2203+ QCoreApplication::applicationName(),
2204+ QCoreApplication::applicationVersion()).toUtf8());
2205+
2206+ if (op == QNetworkAccessManager::PostOperation &&
2207+ !new_request.header(QNetworkRequest::ContentTypeHeader).isValid()) {
2208+ new_request.setHeader(QNetworkRequest::ContentTypeHeader,
2209+ "application/x-www-form-urlencoded");
2210+ }
2211+
2212+ // Prefer the cache unless the caller has changed the setting already
2213+ if (request.attribute(QNetworkRequest::CacheLoadControlAttribute).toInt()
2214+ == QNetworkRequest::PreferNetwork) {
2215+ new_request.setAttribute(QNetworkRequest::CacheLoadControlAttribute,
2216+ QNetworkRequest::PreferCache);
2217+ }
2218+
2219+ return QNetworkAccessManager::createRequest(op, new_request, outgoingData);
2220+}
2221+
2222+
2223+NetworkTimeouts::NetworkTimeouts(int timeout_msec, QObject* parent)
2224+ : QObject(parent),
2225+ m_timeout_msec(timeout_msec) {
2226+}
2227+
2228+void NetworkTimeouts::addReply(QNetworkReply* reply) {
2229+ if (m_timers.contains(reply))
2230+ return;
2231+
2232+ connect(reply, SIGNAL(destroyed()), SLOT(replyFinished()));
2233+ connect(reply, SIGNAL(finished()), SLOT(replyFinished()));
2234+ m_timers[reply] = startTimer(m_timeout_msec);
2235+}
2236+
2237+void NetworkTimeouts::replyFinished() {
2238+ QNetworkReply* reply = reinterpret_cast<QNetworkReply*>(sender());
2239+ if (m_timers.contains(reply)) {
2240+ killTimer(m_timers.take(reply));
2241+ }
2242+}
2243+
2244+void NetworkTimeouts::timerEvent(QTimerEvent* e) {
2245+ QNetworkReply* reply = m_timers.key(e->timerId());
2246+ if (reply) {
2247+ reply->abort();
2248+ }
2249+}
2250
2251=== added file 'mixxx/src/network.h'
2252--- mixxx/src/network.h 1970-01-01 00:00:00 +0000
2253+++ mixxx/src/network.h 2013-05-22 07:42:27 +0000
2254@@ -0,0 +1,49 @@
2255+/*****************************************************************************
2256+ * Copyright © 2012 John Maguire <john.maguire@gmail.com> *
2257+ * David Sansome <me@davidsansome.com> *
2258+ * This work is free. You can redistribute it and/or modify it under the *
2259+ * terms of the Do What The Fuck You Want To Public License, Version 2, *
2260+ * as published by Sam Hocevar. *
2261+ * See http://www.wtfpl.net/ for more details. *
2262+ *****************************************************************************/
2263+
2264+#ifndef NETWORK_H
2265+#define NETWORK_H
2266+
2267+#include <QAbstractNetworkCache>
2268+#include <QNetworkAccessManager>
2269+#include <QNetworkReply>
2270+
2271+class NetworkAccessManager : public QNetworkAccessManager {
2272+ Q_OBJECT
2273+
2274+ public:
2275+ NetworkAccessManager(QObject* parent = 0);
2276+
2277+ protected:
2278+ QNetworkReply* createRequest(Operation op, const QNetworkRequest& request,
2279+ QIODevice* outgoingData);
2280+};
2281+
2282+
2283+class NetworkTimeouts : public QObject {
2284+ Q_OBJECT
2285+
2286+ public:
2287+ NetworkTimeouts(int timeout_msec, QObject* parent = 0);
2288+
2289+ void addReply(QNetworkReply* reply);
2290+ void setTimeout(int msec) { m_timeout_msec = msec; }
2291+
2292+ protected:
2293+ void timerEvent(QTimerEvent* e);
2294+
2295+ private slots:
2296+ void replyFinished();
2297+
2298+ private:
2299+ int m_timeout_msec;
2300+ QMap<QNetworkReply*, int> m_timers;
2301+};
2302+
2303+#endif // NETWORK_H
2304
2305=== modified file 'mixxx/src/widget/wtracktableview.cpp'
2306--- mixxx/src/widget/wtracktableview.cpp 2013-05-21 03:02:42 +0000
2307+++ mixxx/src/widget/wtracktableview.cpp 2013-05-22 07:42:27 +0000
2308@@ -25,14 +25,20 @@
2309 WTRACKTABLEVIEW_VSCROLLBARPOS_KEY)),
2310 m_pConfig(pConfig),
2311 m_pTrackCollection(pTrackCollection),
2312+ m_DlgTagFetcher(NULL) ,
2313 m_sorting(sorting) {
2314 // Give a NULL parent because otherwise it inherits our style which can make
2315 // it unreadable. Bug #673411
2316- m_pTrackInfo = new DlgTrackInfo(NULL);
2317+ m_pTrackInfo = new DlgTrackInfo(NULL,m_DlgTagFetcher);
2318 connect(m_pTrackInfo, SIGNAL(next()),
2319 this, SLOT(slotNextTrackInfo()));
2320 connect(m_pTrackInfo, SIGNAL(previous()),
2321 this, SLOT(slotPrevTrackInfo()));
2322+ connect(&m_DlgTagFetcher, SIGNAL(next()),
2323+ this, SLOT(slotNextDlgTagFetcher()));
2324+ connect(&m_DlgTagFetcher, SIGNAL(previous()),
2325+ this, SLOT(slotPrevDlgTagFetcher()));
2326+
2327
2328 connect(&m_loadTrackMapper, SIGNAL(mapped(QString)),
2329 this, SLOT(loadSelectionToGroup(QString)));
2330@@ -75,8 +81,8 @@
2331 this, SLOT(addSelectionToCrate(int)));
2332 }
2333
2334-WTrackTableView::~WTrackTableView()
2335-{
2336+WTrackTableView::~WTrackTableView() {
2337+ qDebug() << "~WTrackTableView()";
2338 WTrackTableViewHeader* pHeader =
2339 dynamic_cast<WTrackTableViewHeader*>(horizontalHeader());
2340 if (pHeader) {
2341@@ -84,6 +90,7 @@
2342 }
2343
2344 delete m_pReloadMetadataAct;
2345+ delete m_pReloadMetadataFromMusicBrainzAct;
2346 delete m_pAddToPreviewDeck;
2347 delete m_pAutoDJAct;
2348 delete m_pAutoDJTopAct;
2349@@ -290,6 +297,10 @@
2350 connect(m_pReloadMetadataAct, SIGNAL(triggered()),
2351 this, SLOT(slotReloadTrackMetadata()));
2352
2353+ m_pReloadMetadataFromMusicBrainzAct = new QAction(tr("Reload from Musicbrainz"),this);
2354+ connect(m_pReloadMetadataFromMusicBrainzAct, SIGNAL(triggered()),
2355+ this, SLOT(slotShowDlgTagFetcher()));
2356+
2357 m_pAddToPreviewDeck = new QAction(tr("Load to Preview Deck"), this);
2358 // currently there is only one preview deck so just map it here.
2359 QString previewDeckGroup = PlayerManager::groupForPreviewDeck(0);
2360@@ -456,6 +467,42 @@
2361 m_pTrackInfo->show();
2362 }
2363
2364+void WTrackTableView::slotNextDlgTagFetcher() {
2365+ QModelIndex nextRow = currentTrackInfoIndex.sibling(
2366+ currentTrackInfoIndex.row()+1, currentTrackInfoIndex.column());
2367+ if (nextRow.isValid())
2368+ showDlgTagFetcher(nextRow);
2369+}
2370+
2371+void WTrackTableView::slotPrevDlgTagFetcher() {
2372+ QModelIndex prevRow = currentTrackInfoIndex.sibling(
2373+ currentTrackInfoIndex.row()-1, currentTrackInfoIndex.column());
2374+ if (prevRow.isValid())
2375+ showDlgTagFetcher(prevRow);
2376+}
2377+
2378+void WTrackTableView::showDlgTagFetcher(QModelIndex index) {
2379+ TrackModel* trackModel = getTrackModel();
2380+
2381+ if (!trackModel) {
2382+ return;
2383+ }
2384+
2385+ TrackPointer pTrack = trackModel->getTrack(index);
2386+ // NULL is fine
2387+ m_DlgTagFetcher.init(pTrack);
2388+ currentTrackInfoIndex = index;
2389+ m_DlgTagFetcher.show();
2390+}
2391+
2392+void WTrackTableView::slotShowDlgTagFetcher(){
2393+ QModelIndexList indices = selectionModel()->selectedRows();
2394+
2395+ if (indices.size() > 0) {
2396+ showDlgTagFetcher(indices[0]);
2397+ }
2398+}
2399+
2400 void WTrackTableView::contextMenuEvent(QContextMenuEvent* event) {
2401 QModelIndexList indices = selectionModel()->selectedRows();
2402
2403@@ -613,8 +660,10 @@
2404 m_pMenu->addSeparator();
2405 if (modelHasCapabilities(TrackModel::TRACKMODELCAPS_RELOADMETADATA)) {
2406 m_pMenu->addAction(m_pReloadMetadataAct);
2407+ m_pReloadMetadataFromMusicBrainzAct->setEnabled(oneSongSelected);
2408+ m_pMenu->addAction(m_pReloadMetadataFromMusicBrainzAct);
2409 }
2410- // REMOVE and HIDE should not be at the first menu position to avoid excitedly clicks
2411+ // REMOVE and HIDE should not be at the first menu position to avoid accidental clicks
2412 if (modelHasCapabilities(TrackModel::TRACKMODELCAPS_REMOVE)) {
2413 m_pRemoveAct->setEnabled(!locked);
2414 m_pMenu->addAction(m_pRemoveAct);
2415
2416=== modified file 'mixxx/src/widget/wtracktableview.h'
2417--- mixxx/src/widget/wtracktableview.h 2013-05-21 03:02:42 +0000
2418+++ mixxx/src/widget/wtracktableview.h 2013-05-22 07:42:27 +0000
2419@@ -9,6 +9,7 @@
2420 #include "library/libraryview.h"
2421 #include "library/trackmodel.h" // Can't forward declare enums
2422 #include "widget/wlibrarytableview.h"
2423+#include "dlgtagfetcher.h"
2424
2425 class ControlObjectThreadMain;
2426 class DlgTrackInfo;
2427@@ -44,8 +45,11 @@
2428 void slotHide();
2429 void slotOpenInFileBrowser();
2430 void slotShowTrackInfo();
2431+ void slotShowDlgTagFetcher();
2432 void slotNextTrackInfo();
2433+ void slotNextDlgTagFetcher();
2434 void slotPrevTrackInfo();
2435+ void slotPrevDlgTagFetcher();
2436 void slotSendToAutoDJ();
2437 void slotSendToAutoDJTop();
2438 void slotReloadTrackMetadata();
2439@@ -61,6 +65,7 @@
2440 private:
2441 void sendToAutoDJ(bool bTop);
2442 void showTrackInfo(QModelIndex index);
2443+ void showDlgTagFetcher(QModelIndex index);
2444 void createActions();
2445 void dragMoveEvent(QDragMoveEvent * event);
2446 void dragEnterEvent(QDragEnterEvent * event);
2447@@ -81,6 +86,7 @@
2448 QSignalMapper m_loadTrackMapper;
2449
2450 DlgTrackInfo* m_pTrackInfo;
2451+ DlgTagFetcher m_DlgTagFetcher;
2452 QModelIndex currentTrackInfoIndex;
2453
2454
2455@@ -94,6 +100,7 @@
2456
2457 // Reload Track Metadata Action:
2458 QAction *m_pReloadMetadataAct;
2459+ QAction *m_pReloadMetadataFromMusicBrainzAct;
2460
2461 // Load Track to PreviewDeck
2462 QAction* m_pAddToPreviewDeck;