1
0
mirror of https://github.com/tomahawk-player/tomahawk.git synced 2025-07-31 03:10:12 +02:00

Add liblastfm2 and make tomahawk build against it

This commit is contained in:
Jeff Mitchell
2011-03-24 19:18:42 -04:00
parent 1780781e12
commit b7a1cb8d99
114 changed files with 14711 additions and 3 deletions

View File

@@ -29,8 +29,10 @@ INCLUDE( MacroOptionalFindPackage )
INCLUDE( MacroLogFeature )
# required
macro_optional_find_package(LibLastFm 0.3.3)
macro_log_feature(LIBLASTFM_FOUND "LastFm" "Qt library for the Last.fm webservices" "https://github.com/mxcl/liblastfm" FALSE "" "liblastfm is needed for scrobbling tracks to Last.fm and fetching cover artwork")
#While we distribute our own liblastfm2, don't need to look for it
#macro_optional_find_package(LibLastFm 0.3.3)
#macro_log_feature(LIBLASTFM_FOUND "LastFm" "Qt library for the Last.fm webservices" "https://github.com/mxcl/liblastfm" FALSE "" "liblastfm is needed for scrobbling tracks to Last.fm and fetching cover artwork")
set(LIBLASTFM_FOUND true)
macro_optional_find_package(LibEchonest 1.1.1)
macro_log_feature(LIBECHONEST_FOUND "Echonest" "Qt library for communicating with The Echo Nest" "http://projects.kde.org/libechonest" TRUE "" "libechonest is needed for dynamic playlists and the infosystem")

View File

@@ -123,6 +123,7 @@ INCLUDE_DIRECTORIES(
.
${TOMAHAWK_INC_DIR}
${CMAKE_CURRENT_BINARY_DIR}
${CMAKE_BINARY_DIR}/thirdparty/liblastfm2/src
audio
database
@@ -205,7 +206,7 @@ MESSAGE( STATUS "OS_SPECIFIC_LINK_LIBRARIES: ${OS_SPECIFIC_LINK_LIBRARIES}" )
SET(LINK_LIBRARIES "")
IF(LIBLASTFM_FOUND)
SET(LINK_LIBRARIES ${LINK_LIBRARIES} ${LIBLASTFM_LIBRARY} )
SET(LINK_LIBRARIES ${LINK_LIBRARIES} tomahawk_lastfm2 )
ENDIF(LIBLASTFM_FOUND)
IF(GLOOX_FOUND)
SET(LINK_LIBRARIES ${LINK_LIBRARIES} ${GLOOX_LIBRARIES} )

View File

@@ -2,6 +2,7 @@ add_subdirectory( jdns )
add_subdirectory( qtweetlib )
ADD_SUBDIRECTORY( libportfwd )
ADD_SUBDIRECTORY( qxt )
ADD_SUBDIRECTORY( liblastfm2 )
IF( UNIX AND NOT APPLE )
ADD_SUBDIRECTORY( alsa-playback )

27
thirdparty/liblastfm2/CMakeLists.txt vendored Normal file
View File

@@ -0,0 +1,27 @@
cmake_minimum_required(VERSION 2.6)
set( CMAKE_MODULE_PATH
${CMAKE_MODULE_PATH}
${CMAKE_CURRENT_SOURCE_DIR}/cmake/modules
)
if(${CMAKE_BUILD_TYPE} MATCHES "Release")
add_definitions(-DNDEBUG)
endif(${CMAKE_BUILD_TYPE} MATCHES "Release")
# Set up definitions and paths
add_definitions(${QT_DEFINITIONS})
include(${QT_USE_FILE})
# Main Library
add_subdirectory(src)
# Optionally build the fingerprint library
option(BUILD_FINGERPRINT "Build the lastfm-fingerprint library" ON)
find_package(LibSamplerate)
find_package(LibFFTW3)
if (BUILD_FINGERPRINT AND LIBFFTW3_FOUND AND LIBSAMPLERATE_FOUND)
add_subdirectory(src/fingerprint)
endif (BUILD_FINGERPRINT AND LIBFFTW3_FOUND AND LIBSAMPLERATE_FOUND)

621
thirdparty/liblastfm2/COPYING vendored Normal file
View File

@@ -0,0 +1,621 @@
GNU GENERAL PUBLIC LICENSE
Version 3, 29 June 2007
Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>
Everyone is permitted to copy and distribute verbatim copies
of this license document, but changing it is not allowed.
Preamble
The GNU General Public License is a free, copyleft license for
software and other kinds of works.
The licenses for most software and other practical works are designed
to take away your freedom to share and change the works. By contrast,
the GNU General Public License is intended to guarantee your freedom to
share and change all versions of a program--to make sure it remains free
software for all its users. We, the Free Software Foundation, use the
GNU General Public License for most of our software; it applies also to
any other work released this way by its authors. You can apply it to
your programs, too.
When we speak of free software, we are referring to freedom, not
price. Our General Public Licenses are designed to make sure that you
have the freedom to distribute copies of free software (and charge for
them if you wish), that you receive source code or can get it if you
want it, that you can change the software or use pieces of it in new
free programs, and that you know you can do these things.
To protect your rights, we need to prevent others from denying you
these rights or asking you to surrender the rights. Therefore, you have
certain responsibilities if you distribute copies of the software, or if
you modify it: responsibilities to respect the freedom of others.
For example, if you distribute copies of such a program, whether
gratis or for a fee, you must pass on to the recipients the same
freedoms that you received. You must make sure that they, too, receive
or can get the source code. And you must show them these terms so they
know their rights.
Developers that use the GNU GPL protect your rights with two steps:
(1) assert copyright on the software, and (2) offer you this License
giving you legal permission to copy, distribute and/or modify it.
For the developers' and authors' protection, the GPL clearly explains
that there is no warranty for this free software. For both users' and
authors' sake, the GPL requires that modified versions be marked as
changed, so that their problems will not be attributed erroneously to
authors of previous versions.
Some devices are designed to deny users access to install or run
modified versions of the software inside them, although the manufacturer
can do so. This is fundamentally incompatible with the aim of
protecting users' freedom to change the software. The systematic
pattern of such abuse occurs in the area of products for individuals to
use, which is precisely where it is most unacceptable. Therefore, we
have designed this version of the GPL to prohibit the practice for those
products. If such problems arise substantially in other domains, we
stand ready to extend this provision to those domains in future versions
of the GPL, as needed to protect the freedom of users.
Finally, every program is threatened constantly by software patents.
States should not allow patents to restrict development and use of
software on general-purpose computers, but in those that do, we wish to
avoid the special danger that patents applied to a free program could
make it effectively proprietary. To prevent this, the GPL assures that
patents cannot be used to render the program non-free.
The precise terms and conditions for copying, distribution and
modification follow.
TERMS AND CONDITIONS
0. Definitions.
"This License" refers to version 3 of the GNU General Public License.
"Copyright" also means copyright-like laws that apply to other kinds of
works, such as semiconductor masks.
"The Program" refers to any copyrightable work licensed under this
License. Each licensee is addressed as "you". "Licensees" and
"recipients" may be individuals or organizations.
To "modify" a work means to copy from or adapt all or part of the work
in a fashion requiring copyright permission, other than the making of an
exact copy. The resulting work is called a "modified version" of the
earlier work or a work "based on" the earlier work.
A "covered work" means either the unmodified Program or a work based
on the Program.
To "propagate" a work means to do anything with it that, without
permission, would make you directly or secondarily liable for
infringement under applicable copyright law, except executing it on a
computer or modifying a private copy. Propagation includes copying,
distribution (with or without modification), making available to the
public, and in some countries other activities as well.
To "convey" a work means any kind of propagation that enables other
parties to make or receive copies. Mere interaction with a user through
a computer network, with no transfer of a copy, is not conveying.
An interactive user interface displays "Appropriate Legal Notices"
to the extent that it includes a convenient and prominently visible
feature that (1) displays an appropriate copyright notice, and (2)
tells the user that there is no warranty for the work (except to the
extent that warranties are provided), that licensees may convey the
work under this License, and how to view a copy of this License. If
the interface presents a list of user commands or options, such as a
menu, a prominent item in the list meets this criterion.
1. Source Code.
The "source code" for a work means the preferred form of the work
for making modifications to it. "Object code" means any non-source
form of a work.
A "Standard Interface" means an interface that either is an official
standard defined by a recognized standards body, or, in the case of
interfaces specified for a particular programming language, one that
is widely used among developers working in that language.
The "System Libraries" of an executable work include anything, other
than the work as a whole, that (a) is included in the normal form of
packaging a Major Component, but which is not part of that Major
Component, and (b) serves only to enable use of the work with that
Major Component, or to implement a Standard Interface for which an
implementation is available to the public in source code form. A
"Major Component", in this context, means a major essential component
(kernel, window system, and so on) of the specific operating system
(if any) on which the executable work runs, or a compiler used to
produce the work, or an object code interpreter used to run it.
The "Corresponding Source" for a work in object code form means all
the source code needed to generate, install, and (for an executable
work) run the object code and to modify the work, including scripts to
control those activities. However, it does not include the work's
System Libraries, or general-purpose tools or generally available free
programs which are used unmodified in performing those activities but
which are not part of the work. For example, Corresponding Source
includes interface definition files associated with source files for
the work, and the source code for shared libraries and dynamically
linked subprograms that the work is specifically designed to require,
such as by intimate data communication or control flow between those
subprograms and other parts of the work.
The Corresponding Source need not include anything that users
can regenerate automatically from other parts of the Corresponding
Source.
The Corresponding Source for a work in source code form is that
same work.
2. Basic Permissions.
All rights granted under this License are granted for the term of
copyright on the Program, and are irrevocable provided the stated
conditions are met. This License explicitly affirms your unlimited
permission to run the unmodified Program. The output from running a
covered work is covered by this License only if the output, given its
content, constitutes a covered work. This License acknowledges your
rights of fair use or other equivalent, as provided by copyright law.
You may make, run and propagate covered works that you do not
convey, without conditions so long as your license otherwise remains
in force. You may convey covered works to others for the sole purpose
of having them make modifications exclusively for you, or provide you
with facilities for running those works, provided that you comply with
the terms of this License in conveying all material for which you do
not control copyright. Those thus making or running the covered works
for you must do so exclusively on your behalf, under your direction
and control, on terms that prohibit them from making any copies of
your copyrighted material outside their relationship with you.
Conveying under any other circumstances is permitted solely under
the conditions stated below. Sublicensing is not allowed; section 10
makes it unnecessary.
3. Protecting Users' Legal Rights From Anti-Circumvention Law.
No covered work shall be deemed part of an effective technological
measure under any applicable law fulfilling obligations under article
11 of the WIPO copyright treaty adopted on 20 December 1996, or
similar laws prohibiting or restricting circumvention of such
measures.
When you convey a covered work, you waive any legal power to forbid
circumvention of technological measures to the extent such circumvention
is effected by exercising rights under this License with respect to
the covered work, and you disclaim any intention to limit operation or
modification of the work as a means of enforcing, against the work's
users, your or third parties' legal rights to forbid circumvention of
technological measures.
4. Conveying Verbatim Copies.
You may convey verbatim copies of the Program's source code as you
receive it, in any medium, provided that you conspicuously and
appropriately publish on each copy an appropriate copyright notice;
keep intact all notices stating that this License and any
non-permissive terms added in accord with section 7 apply to the code;
keep intact all notices of the absence of any warranty; and give all
recipients a copy of this License along with the Program.
You may charge any price or no price for each copy that you convey,
and you may offer support or warranty protection for a fee.
5. Conveying Modified Source Versions.
You may convey a work based on the Program, or the modifications to
produce it from the Program, in the form of source code under the
terms of section 4, provided that you also meet all of these conditions:
a) The work must carry prominent notices stating that you modified
it, and giving a relevant date.
b) The work must carry prominent notices stating that it is
released under this License and any conditions added under section
7. This requirement modifies the requirement in section 4 to
"keep intact all notices".
c) You must license the entire work, as a whole, under this
License to anyone who comes into possession of a copy. This
License will therefore apply, along with any applicable section 7
additional terms, to the whole of the work, and all its parts,
regardless of how they are packaged. This License gives no
permission to license the work in any other way, but it does not
invalidate such permission if you have separately received it.
d) If the work has interactive user interfaces, each must display
Appropriate Legal Notices; however, if the Program has interactive
interfaces that do not display Appropriate Legal Notices, your
work need not make them do so.
A compilation of a covered work with other separate and independent
works, which are not by their nature extensions of the covered work,
and which are not combined with it such as to form a larger program,
in or on a volume of a storage or distribution medium, is called an
"aggregate" if the compilation and its resulting copyright are not
used to limit the access or legal rights of the compilation's users
beyond what the individual works permit. Inclusion of a covered work
in an aggregate does not cause this License to apply to the other
parts of the aggregate.
6. Conveying Non-Source Forms.
You may convey a covered work in object code form under the terms
of sections 4 and 5, provided that you also convey the
machine-readable Corresponding Source under the terms of this License,
in one of these ways:
a) Convey the object code in, or embodied in, a physical product
(including a physical distribution medium), accompanied by the
Corresponding Source fixed on a durable physical medium
customarily used for software interchange.
b) Convey the object code in, or embodied in, a physical product
(including a physical distribution medium), accompanied by a
written offer, valid for at least three years and valid for as
long as you offer spare parts or customer support for that product
model, to give anyone who possesses the object code either (1) a
copy of the Corresponding Source for all the software in the
product that is covered by this License, on a durable physical
medium customarily used for software interchange, for a price no
more than your reasonable cost of physically performing this
conveying of source, or (2) access to copy the
Corresponding Source from a network server at no charge.
c) Convey individual copies of the object code with a copy of the
written offer to provide the Corresponding Source. This
alternative is allowed only occasionally and noncommercially, and
only if you received the object code with such an offer, in accord
with subsection 6b.
d) Convey the object code by offering access from a designated
place (gratis or for a charge), and offer equivalent access to the
Corresponding Source in the same way through the same place at no
further charge. You need not require recipients to copy the
Corresponding Source along with the object code. If the place to
copy the object code is a network server, the Corresponding Source
may be on a different server (operated by you or a third party)
that supports equivalent copying facilities, provided you maintain
clear directions next to the object code saying where to find the
Corresponding Source. Regardless of what server hosts the
Corresponding Source, you remain obligated to ensure that it is
available for as long as needed to satisfy these requirements.
e) Convey the object code using peer-to-peer transmission, provided
you inform other peers where the object code and Corresponding
Source of the work are being offered to the general public at no
charge under subsection 6d.
A separable portion of the object code, whose source code is excluded
from the Corresponding Source as a System Library, need not be
included in conveying the object code work.
A "User Product" is either (1) a "consumer product", which means any
tangible personal property which is normally used for personal, family,
or household purposes, or (2) anything designed or sold for incorporation
into a dwelling. In determining whether a product is a consumer product,
doubtful cases shall be resolved in favor of coverage. For a particular
product received by a particular user, "normally used" refers to a
typical or common use of that class of product, regardless of the status
of the particular user or of the way in which the particular user
actually uses, or expects or is expected to use, the product. A product
is a consumer product regardless of whether the product has substantial
commercial, industrial or non-consumer uses, unless such uses represent
the only significant mode of use of the product.
"Installation Information" for a User Product means any methods,
procedures, authorization keys, or other information required to install
and execute modified versions of a covered work in that User Product from
a modified version of its Corresponding Source. The information must
suffice to ensure that the continued functioning of the modified object
code is in no case prevented or interfered with solely because
modification has been made.
If you convey an object code work under this section in, or with, or
specifically for use in, a User Product, and the conveying occurs as
part of a transaction in which the right of possession and use of the
User Product is transferred to the recipient in perpetuity or for a
fixed term (regardless of how the transaction is characterized), the
Corresponding Source conveyed under this section must be accompanied
by the Installation Information. But this requirement does not apply
if neither you nor any third party retains the ability to install
modified object code on the User Product (for example, the work has
been installed in ROM).
The requirement to provide Installation Information does not include a
requirement to continue to provide support service, warranty, or updates
for a work that has been modified or installed by the recipient, or for
the User Product in which it has been modified or installed. Access to a
network may be denied when the modification itself materially and
adversely affects the operation of the network or violates the rules and
protocols for communication across the network.
Corresponding Source conveyed, and Installation Information provided,
in accord with this section must be in a format that is publicly
documented (and with an implementation available to the public in
source code form), and must require no special password or key for
unpacking, reading or copying.
7. Additional Terms.
"Additional permissions" are terms that supplement the terms of this
License by making exceptions from one or more of its conditions.
Additional permissions that are applicable to the entire Program shall
be treated as though they were included in this License, to the extent
that they are valid under applicable law. If additional permissions
apply only to part of the Program, that part may be used separately
under those permissions, but the entire Program remains governed by
this License without regard to the additional permissions.
When you convey a copy of a covered work, you may at your option
remove any additional permissions from that copy, or from any part of
it. (Additional permissions may be written to require their own
removal in certain cases when you modify the work.) You may place
additional permissions on material, added by you to a covered work,
for which you have or can give appropriate copyright permission.
Notwithstanding any other provision of this License, for material you
add to a covered work, you may (if authorized by the copyright holders of
that material) supplement the terms of this License with terms:
a) Disclaiming warranty or limiting liability differently from the
terms of sections 15 and 16 of this License; or
b) Requiring preservation of specified reasonable legal notices or
author attributions in that material or in the Appropriate Legal
Notices displayed by works containing it; or
c) Prohibiting misrepresentation of the origin of that material, or
requiring that modified versions of such material be marked in
reasonable ways as different from the original version; or
d) Limiting the use for publicity purposes of names of licensors or
authors of the material; or
e) Declining to grant rights under trademark law for use of some
trade names, trademarks, or service marks; or
f) Requiring indemnification of licensors and authors of that
material by anyone who conveys the material (or modified versions of
it) with contractual assumptions of liability to the recipient, for
any liability that these contractual assumptions directly impose on
those licensors and authors.
All other non-permissive additional terms are considered "further
restrictions" within the meaning of section 10. If the Program as you
received it, or any part of it, contains a notice stating that it is
governed by this License along with a term that is a further
restriction, you may remove that term. If a license document contains
a further restriction but permits relicensing or conveying under this
License, you may add to a covered work material governed by the terms
of that license document, provided that the further restriction does
not survive such relicensing or conveying.
If you add terms to a covered work in accord with this section, you
must place, in the relevant source files, a statement of the
additional terms that apply to those files, or a notice indicating
where to find the applicable terms.
Additional terms, permissive or non-permissive, may be stated in the
form of a separately written license, or stated as exceptions;
the above requirements apply either way.
8. Termination.
You may not propagate or modify a covered work except as expressly
provided under this License. Any attempt otherwise to propagate or
modify it is void, and will automatically terminate your rights under
this License (including any patent licenses granted under the third
paragraph of section 11).
However, if you cease all violation of this License, then your
license from a particular copyright holder is reinstated (a)
provisionally, unless and until the copyright holder explicitly and
finally terminates your license, and (b) permanently, if the copyright
holder fails to notify you of the violation by some reasonable means
prior to 60 days after the cessation.
Moreover, your license from a particular copyright holder is
reinstated permanently if the copyright holder notifies you of the
violation by some reasonable means, this is the first time you have
received notice of violation of this License (for any work) from that
copyright holder, and you cure the violation prior to 30 days after
your receipt of the notice.
Termination of your rights under this section does not terminate the
licenses of parties who have received copies or rights from you under
this License. If your rights have been terminated and not permanently
reinstated, you do not qualify to receive new licenses for the same
material under section 10.
9. Acceptance Not Required for Having Copies.
You are not required to accept this License in order to receive or
run a copy of the Program. Ancillary propagation of a covered work
occurring solely as a consequence of using peer-to-peer transmission
to receive a copy likewise does not require acceptance. However,
nothing other than this License grants you permission to propagate or
modify any covered work. These actions infringe copyright if you do
not accept this License. Therefore, by modifying or propagating a
covered work, you indicate your acceptance of this License to do so.
10. Automatic Licensing of Downstream Recipients.
Each time you convey a covered work, the recipient automatically
receives a license from the original licensors, to run, modify and
propagate that work, subject to this License. You are not responsible
for enforcing compliance by third parties with this License.
An "entity transaction" is a transaction transferring control of an
organization, or substantially all assets of one, or subdividing an
organization, or merging organizations. If propagation of a covered
work results from an entity transaction, each party to that
transaction who receives a copy of the work also receives whatever
licenses to the work the party's predecessor in interest had or could
give under the previous paragraph, plus a right to possession of the
Corresponding Source of the work from the predecessor in interest, if
the predecessor has it or can get it with reasonable efforts.
You may not impose any further restrictions on the exercise of the
rights granted or affirmed under this License. For example, you may
not impose a license fee, royalty, or other charge for exercise of
rights granted under this License, and you may not initiate litigation
(including a cross-claim or counterclaim in a lawsuit) alleging that
any patent claim is infringed by making, using, selling, offering for
sale, or importing the Program or any portion of it.
11. Patents.
A "contributor" is a copyright holder who authorizes use under this
License of the Program or a work on which the Program is based. The
work thus licensed is called the contributor's "contributor version".
A contributor's "essential patent claims" are all patent claims
owned or controlled by the contributor, whether already acquired or
hereafter acquired, that would be infringed by some manner, permitted
by this License, of making, using, or selling its contributor version,
but do not include claims that would be infringed only as a
consequence of further modification of the contributor version. For
purposes of this definition, "control" includes the right to grant
patent sublicenses in a manner consistent with the requirements of
this License.
Each contributor grants you a non-exclusive, worldwide, royalty-free
patent license under the contributor's essential patent claims, to
make, use, sell, offer for sale, import and otherwise run, modify and
propagate the contents of its contributor version.
In the following three paragraphs, a "patent license" is any express
agreement or commitment, however denominated, not to enforce a patent
(such as an express permission to practice a patent or covenant not to
sue for patent infringement). To "grant" such a patent license to a
party means to make such an agreement or commitment not to enforce a
patent against the party.
If you convey a covered work, knowingly relying on a patent license,
and the Corresponding Source of the work is not available for anyone
to copy, free of charge and under the terms of this License, through a
publicly available network server or other readily accessible means,
then you must either (1) cause the Corresponding Source to be so
available, or (2) arrange to deprive yourself of the benefit of the
patent license for this particular work, or (3) arrange, in a manner
consistent with the requirements of this License, to extend the patent
license to downstream recipients. "Knowingly relying" means you have
actual knowledge that, but for the patent license, your conveying the
covered work in a country, or your recipient's use of the covered work
in a country, would infringe one or more identifiable patents in that
country that you have reason to believe are valid.
If, pursuant to or in connection with a single transaction or
arrangement, you convey, or propagate by procuring conveyance of, a
covered work, and grant a patent license to some of the parties
receiving the covered work authorizing them to use, propagate, modify
or convey a specific copy of the covered work, then the patent license
you grant is automatically extended to all recipients of the covered
work and works based on it.
A patent license is "discriminatory" if it does not include within
the scope of its coverage, prohibits the exercise of, or is
conditioned on the non-exercise of one or more of the rights that are
specifically granted under this License. You may not convey a covered
work if you are a party to an arrangement with a third party that is
in the business of distributing software, under which you make payment
to the third party based on the extent of your activity of conveying
the work, and under which the third party grants, to any of the
parties who would receive the covered work from you, a discriminatory
patent license (a) in connection with copies of the covered work
conveyed by you (or copies made from those copies), or (b) primarily
for and in connection with specific products or compilations that
contain the covered work, unless you entered into that arrangement,
or that patent license was granted, prior to 28 March 2007.
Nothing in this License shall be construed as excluding or limiting
any implied license or other defenses to infringement that may
otherwise be available to you under applicable patent law.
12. No Surrender of Others' Freedom.
If conditions are imposed on you (whether by court order, agreement or
otherwise) that contradict the conditions of this License, they do not
excuse you from the conditions of this License. If you cannot convey a
covered work so as to satisfy simultaneously your obligations under this
License and any other pertinent obligations, then as a consequence you may
not convey it at all. For example, if you agree to terms that obligate you
to collect a royalty for further conveying from those to whom you convey
the Program, the only way you could satisfy both those terms and this
License would be to refrain entirely from conveying the Program.
13. Use with the GNU Affero General Public License.
Notwithstanding any other provision of this License, you have
permission to link or combine any covered work with a work licensed
under version 3 of the GNU Affero General Public License into a single
combined work, and to convey the resulting work. The terms of this
License will continue to apply to the part which is the covered work,
but the special requirements of the GNU Affero General Public License,
section 13, concerning interaction through a network will apply to the
combination as such.
14. Revised Versions of this License.
The Free Software Foundation may publish revised and/or new versions of
the GNU General Public License from time to time. Such new versions will
be similar in spirit to the present version, but may differ in detail to
address new problems or concerns.
Each version is given a distinguishing version number. If the
Program specifies that a certain numbered version of the GNU General
Public License "or any later version" applies to it, you have the
option of following the terms and conditions either of that numbered
version or of any later version published by the Free Software
Foundation. If the Program does not specify a version number of the
GNU General Public License, you may choose any version ever published
by the Free Software Foundation.
If the Program specifies that a proxy can decide which future
versions of the GNU General Public License can be used, that proxy's
public statement of acceptance of a version permanently authorizes you
to choose that version for the Program.
Later license versions may give you additional or different
permissions. However, no additional obligations are imposed on any
author or copyright holder as a result of your choosing to follow a
later version.
15. Disclaimer of Warranty.
THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
16. Limitation of Liability.
IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
SUCH DAMAGES.
17. Interpretation of Sections 15 and 16.
If the disclaimer of warranty and limitation of liability provided
above cannot be given local legal effect according to their terms,
reviewing courts shall apply local law that most closely approximates
an absolute waiver of all civil liability in connection with the
Program, unless a warranty or assumption of liability accompanies a
copy of the Program in return for a fee.
END OF TERMS AND CONDITIONS

138
thirdparty/liblastfm2/README vendored Normal file
View File

@@ -0,0 +1,138 @@
liblastfm
=========
liblastfm is a collection of libraries to help you integrate Last.fm services
into your rich desktop software. It is officially supported software developed
by Last.fm staff.
Max Howell http://twitter.com/mxcl
Jono Cole http://twitter.com/jonocole
Doug Mansell http://twitter.com/dougma
Fork it: http://github.com/mxcl/liblastfm
Dependencies
============
liblastfm dynamically links to:
* Qt 4.4
http://www.qtsoftware.com
* FFTW 3.2
Compiled with single precision
http://www.fftw.org
* Secret Rabbit code (aka libsamplerate)
http://www.mega-nerd.com/SRC
Additionally, to build you will need Ruby and GNU make (or Microsoft nmake).
Mac OS X
--------
sudo port selfupdate
sudo port upgrade installed
sudo port install libsamplerate fftw-3 qt4-mac-devel
qt4-mac-devel will take a long time to build. So you may want to install the
Trolltech binary package instead.
MacPorts carries liblastfm now, so you may have downloaded this package simply
to perform this next line:
sudo port install liblastfm
Linux/*NIX
----------
Do something like this:
sudo apt-get install qt4-qmake pkg-config libsamplerate-dev libfftw3-dev ruby g++ libqt4-dev
Additionally on Linux the configure process requires lsb_release. This is
usually already installed (correct me if I'm wrong).
Please note, we have only tested on Linux, but we think it'll work on all
varieties of UNIX. If it doesn't, report the bug to mxcl on GitHub.
Windows
-------
Install Ruby. Install Visual Studio 2005 or higher. Install Qt. Install the
Windows Server 2003 Platform SDK r2:
http://www.microsoft.com/Downloads/details.aspx?FamilyID=484269e2-3b89-47e3-8eb7-1f2be6d7123a
Set up your environment variables so all include paths and tools are
available.
Build and install FFTW and Secret Rabbit Code.
Open a plain Windows shell (Cygwin will work but we don't recommend it), and
see the next section.
Installing liblastfm
====================
ruby configure --release --prefix /usr/local && make && sudo make install
Packaging liblastfm
-------------------
DESTDIR is supported.
liblastfm builds to two dynamic libraries (liblastfm.so and
liblastfm_fingerprint.so). liblastfm.so links only to Qt, but the
fingerprinting part has additional dependencies. So ideally, you would
distribute two packages.
Using liblastfm
===============
We have copied the API at http://last.fm/api onto C++, so like you find
artist.getInfo there you will find an lastfm::Artist::getInfo function in our
C++ API. lastfm is a namespace, Artist a class and getInfo a function.
Thus the API is quite easy to learn. We suggest installing and checking the
include/lastfm/* directory to find out all capabilities.
The demos directory shows some further basic usage including Audioscrobbling
and getting metadata for music via our fingerprinting technology.
You need an API key from http://last.fm/api to use the webservice API.
Your link line needs to include the following:
-llastfm -lQtCore -lQtNetwork -lQtXml
Radio
-----
Please set an identifiable UserAgent on your HTTP requests for the actual MP3s,
in extreme cases we'll contact you directly and demand you do so :P
HTTP & Networking
-----------------
You can specify your own QNetworkAccessManager derived class for liblastfm to
use with lastfm::setNetworkAccessManager(). Our default is pretty good
though, auto-determining proxy settings on Windows and OS X for instance.
Using liblastfm_fingerprint
===========================
The liblastfm_fingerprint library does not decode audio files. We anticipate
that Phonon will soon do that work for us. In the meantime, sample *Source
files for MP3, Ogg Vorbis, FLAC, and AAC/MP4 are available in
src/fingerprint/contrib. If you want to fingerprint files or get metadata
suggestions, you either need to add the *Source files to your project, or
implement your own.
Development
===========
Public Headers
--------------
1. Header guards should be prefixed with LASTFM, eg. LASTFM_WS_REPLY_H
2. #includes should be to the system path eg. #include <lastfm/Scrobbler>
3. Don't make a header public unless it is absolutely required!
4. To make the header public edit the headers.files line in the pro file
Private Headers
---------------
1. For consistency and to make it more obvious it is a private header, don't
prefix the header guard with LASTFM
2. #includes should be the full source tree path, eg.
#include "../core/UrlBuilder.h"

5
thirdparty/liblastfm2/admin/lastfm.h.rb vendored Executable file
View File

@@ -0,0 +1,5 @@
#!/usr/bin/ruby
f = File.new(ARGV[0], "w")
Dir["_include/lastfm/*"].each do |h|
f.write %Q{#include "lastfm/#{File.basename h}"\n}
end

101
thirdparty/liblastfm2/admin/platform.rb vendored Normal file
View File

@@ -0,0 +1,101 @@
#
# platform.rb: naive platform detection for Ruby
# author: Matt Mower <self@mattmower.com>
#
# == Platform
#
# Platform is a simple module which parses the Ruby constant
# RUBY_PLATFORM and works out the OS, it's implementation,
# and the architecture it's running on.
#
# The motivation for writing this was coming across a case where
#
# +if RUBY_PLATFORM =~ /win/+
#
# didn't behave as expected (i.e. on powerpc-darwin-8.1.0)
#
# It is hoped that providing a library for parsing the platform
# means that we can cover all the cases and have something which
# works reliably 99% of the time.
#
# Please report any anomalies or new combinations to the author(s).
#
# == Use
#
# require "platform"
#
# defines
#
# Platform::OS (:unix,:win32,:vms,:os2)
# Platform::IMPL (:macosx,:linux,:mswin)
# Platform::ARCH (:powerpc,:x86,:alpha)
#
# if an unknown configuration is encountered any (or all) of
# these constant may have the value :unknown.
#
# To display the combination for your setup run
#
# ruby platform.rb
#
module Platform
if RUBY_PLATFORM =~ /darwin/i
OS = :unix
IMPL = :macosx
elsif RUBY_PLATFORM =~ /linux/i
OS = :unix
IMPL = :linux
elsif RUBY_PLATFORM =~ /freebsd/i
OS = :unix
IMPL = :freebsd
elsif RUBY_PLATFORM =~ /netbsd/i
OS = :unix
IMPL = :netbsd
elsif RUBY_PLATFORM =~ /mswin/i
OS = :win32
IMPL = :mswin
elsif RUBY_PLATFORM =~ /cygwin/i
OS = :win32
IMPL = :mswin
elsif RUBY_PLATFORM =~ /mingw/i
OS = :win32
IMPL = :mingw
elsif RUBY_PLATFORM =~ /bccwin/i
OS = :win32
IMPL = :bccwin
elsif RUBY_PLATFORM =~ /wince/i
OS = :win32
IMPL = :wince
elsif RUBY_PLATFORM =~ /vms/i
OS = :vms
IMPL = :vms
elsif RUBY_PLATFORM =~ /os2/i
OS = :os2
IMPL = :os2 # maybe there is some better choice here?
else
OS = :unknown
IMPL = :unknown
end
# whither AIX, SOLARIS, and the other unixen?
if RUBY_PLATFORM =~ /(i\d86)/i
ARCH = :x86
elsif RUBY_PLATFORM =~ /ia64/i
ARCH = :ia64
elsif RUBY_PLATFORM =~ /powerpc/i
ARCH = :powerpc
elsif RUBY_PLATFORM =~ /alpha/i
ARCH = :alpha
else
ARCH = :unknown
end
# What about AMD, Turion, Motorola, etc..?
end
if __FILE__ == $0
puts "Platform OS=#{Platform::OS}, IMPL=#{Platform::IMPL}, ARCH=#{Platform::ARCH}"
end

79
thirdparty/liblastfm2/admin/qpp vendored Executable file
View File

@@ -0,0 +1,79 @@
#!/usr/bin/ruby
# Usage examples:
# qpp foo.pro => ./_file.qmake
# qpp foo/bar/ => ./_file.qmake
#
cwd = File.dirname( __FILE__ )
require 'find'
require 'ftools'
require "#{cwd}/platform.rb"
def find_sources()
Find.find( '.' ) do |path|
if File.directory?( path )
excludes = ['.svn', 'tests', '_build', 'contrib']
case Platform::IMPL
when :macosx then excludes << 'win' << 'linux'
when :mswin, :cygwin then excludes << 'mac' << 'linux'
when :linux, :freebsd then excludes << 'win' << 'mac'
else excludes << 'win' << 'mac' << 'linux'
end
Find.prune if excludes.include?( File.basename( path ) ) or (path != "." and not Dir["#{path}/*.pro"].empty? )
elsif File.file?( path )
case Platform::IMPL
when :macosx then next if /_(linux|win)\.(cpp|h)$/.match( path )
when :mswin, :cygwin then next if /_(mac|linux)\.(cpp|h)$/.match( path )
when :linux, :freebsd then next if /_(mac|win)\.(cpp|h)$/.match( path )
end
yield( path, File.extname( path ) ) unless File.basename(path) == 'EXAMPLE.cpp'
end
end
end
########################################################################### impl
sources = Array.new
headers = Array.new
forms = Array.new
resources = Array.new
abort "usage: qpp file.pro.in" unless File.file? ARGV[0]
File.open( ARGV[0] ).each_line do |line|
line.chomp!
matches = /^\s*TEMPLATE += (.*)$/.match( line )
if !matches.nil?
exit if matches[1].downcase == 'subdirs'
end
matches = /^\s*VERSION += +((\d\.){0,2}\d)/.match( line )
if !matches.nil? && !File.file?( "_version.h" )
File.open( "_version.h", 'w' ) { |f| f.write( "#define VERSION \"#{matches[1]}\"\n" ) }
end
end
Dir.chdir File.dirname(ARGV[0]) do
find_sources do |path, ext|
path.sub!( /^.\//, '' )
case ext
when ".h" then headers << path
when ".ui" then forms << path
when ".qrc" then resources << path
when ".cpp" then sources << path
when ".mm" then sources << path if Platform::IMPL == :macosx
when ".m" then sources << path if Platform::IMPL == :macosx
end
end
end
def write_section( section, array )
return if array.empty?
print "#{section} +="
array.each { |path| print " \\\n\t#{path}" }
puts
end
write_section( "SOURCES", sources )
write_section( "HEADERS", headers )
write_section( "FORMS", forms )
write_section( "RESOURCES", resources )

40
thirdparty/liblastfm2/admin/utils.rb vendored Normal file
View File

@@ -0,0 +1,40 @@
cwd = File.dirname( __FILE__ )
require "#{cwd}/platform.rb"
def h(s, n)
case Platform::IMPL
when :mswin
puts '==> '+s
else
puts "\033[0;#{n}m==>\033[0;0;1m #{s} \033[0;0m"
end
end
def h1 s
h(s, 34)
end
def h2 s
h(s, 33)
yield
end
def qmake_env(env, qenv)
env=Array.new(1,env) if env.instance_of? String
values=Array.new
env.each { |x| values << ENV[x] if ENV[x] }
if values.size > 0
"#{qenv} = #{values.join(' ')}\n"
else
nil
end
end
class PkgConfigNotFound < RuntimeError; end
class PkgNotFound < RuntimeError; end
def pkgconfig pkg, prettyname
system "pkg-config --exists '#{pkg}'"
raise PkgConfigNotFound if $? == 127
raise PkgNotFound.new(prettyname) if $? != 0
end

View File

@@ -0,0 +1,38 @@
require "#{File.dirname __FILE__}/platform.rb"
class QMakeNotFound < RuntimeError; end
class QMakeTooOld < RuntimeError; end
def which_qmake
args = '-v'
args += ' 2> /dev/null' unless Platform::IMPL == :mswin
versions = Hash.new
['qmake','qmake-qt4'].each do |qmake|
begin
/^Using Qt version (\d\.\d\.\d)(-(.+))?/.match( `#{qmake} #{args}` )
rescue
end
versions[qmake] = $1 unless $1.nil?
end
raise QMakeNotFound if versions.empty?
versions.each do |key, v|
i = 1
j = 0
v.split( '.' ).reverse.each {|n| j += (n.to_i * i); i *= 100}
versions[key] = j
end
versions.sort {|a,b| a[1]<=>b[1]}
versions.each do |k, v|
if v >= 40400
return k
end
raise QMakeTooOld
end
end
puts which_qmake if __FILE__ == $0

View File

@@ -0,0 +1,45 @@
# This file is copyrighted under the BSD-license for buildsystem files of KDE
# copyright 2010, Patrick von Reth <patrick.vonreth@gmail.com>
#
#
# - Try to find the LIBFFTW3 library
# Once done this will define
#
# LIBFFTW3_FOUND Set to TRUE if LIBFFTW3 librarys and include directory is found
# LIBFFTW3_INCLUDE_DIR The libfftw3 include directory
# LIBFFTW3_LIBRARY The libfftw3 librarys
if(NOT LIBFFTW3_PRECISION)
message(STATUS "Searching for LIBFFTW3, using default precision float")
set(LIBFFTW3_PRECISION FLOAT)
endif(NOT LIBFFTW3_PRECISION)
find_path(LIBFFTW3_INCLUDE_DIR fftw3.h)
if(LIBFFTW3_PRECISION STREQUAL FLOAT)
set(LIBFFTW3_PRECISION_SUFFIX f)
endif(LIBFFTW3_PRECISION STREQUAL FLOAT)
if(LIBFFTW3_PRECISION STREQUAL DOUBLE)
set(LIBFFTW3_PRECISION_SUFFIX "")
endif(LIBFFTW3_PRECISION STREQUAL DOUBLE)
if(LIBFFTW3_PRECISION STREQUAL LDOUBLE)
set(LIBFFTW3_PRECISION_SUFFIX l)
endif(LIBFFTW3_PRECISION STREQUAL LDOUBLE)
find_library(LIBFFTW3_LIBRARY NAMES fftw3${LIBFFTW3_PRECISION_SUFFIX} libfftw3${LIBFFTW3_PRECISION_SUFFIX}-3 fftw3${LIBFFTW3_PRECISION_SUFFIX}-3)
if(FIND_LIBFFTW3_VERBOSE)
message(STATUS
"LIBFFTW3_PRECISION ${LIBFFTW3_PRECISION}, searched for fftw3${LIBFFTW3_PRECISION_SUFFIX} libfftw3${LIBFFTW3_PRECISION_SUFFIX}-3 fftw3${LIBFFTW3_PRECISION_SUFFIX}-3
and found ${LIBFFTW3_LIBRARY}"
)
endif(FIND_LIBFFTW3_VERBOSE)
if(LIBFFTW3_LIBRARY AND LIBFFTW3_INCLUDE_DIR)
set(LIBFFTW3_FOUND TRUE)
message(STATUS "Found libfftw3 ${LIBFFTW3_LIBRARY}")
else(LIBFFTW3_LIBRARY AND LIBFFTW3_PLUGIN_PATH)
message(STATUS "Could not find libfftw3, get it http://www.fftw.org/")
endif(LIBFFTW3_LIBRARY AND LIBFFTW3_INCLUDE_DIR)

View File

@@ -0,0 +1,22 @@
# This file is copyrighted under the BSD-license for buildsystem files of KDE
# copyright 2010, Patrick von Reth <patrick.vonreth@gmail.com>
#
#
# - Try to find the libsamplerate library
# Once done this will define
#
# LIBSAMPLERATE_FOUND Set to TRUE if libsamplerate librarys and include directory is found
# LIBSAMPLERATE_LIBRARY The libsamplerate librarys
# LIBSAMPLERATE_INCLUDE_DIR The libsamplerate include directory
find_library(LIBSAMPLERATE_LIBRARY NAMES samplerate libsamplerate-0 samplerate-0)
find_path(LIBSAMPLERATE_INCLUDE_DIR samplerate.h)
if(LIBSAMPLERATE_LIBRARY AND LIBSAMPLERATE_INCLUDE_DIR)
set(LIBSAMPLERATE_FOUND TRUE)
message(STATUS "Found libsamplerate ${LIBSAMPLERATE_LIBRARY}")
else(LIBSAMPLERATE_LIBRARY AND LIBSAMPLERATE_PLUGIN_PATH)
message(STATUS "Could not find libsamplerate, get it http://www.mega-nerd.com/SRC/")
endif(LIBSAMPLERATE_LIBRARY AND LIBSAMPLERATE_INCLUDE_DIR)

127
thirdparty/liblastfm2/configure vendored Executable file
View File

@@ -0,0 +1,127 @@
#!/usr/bin/ruby
if ARGV.include? '--help'
puts "usage: ./configure [--prefix <dir>] [--release] [--no-strip] [--skip-checks]"
exit
end
cwd = File.dirname( __FILE__ )
require "#{cwd}/admin/platform.rb"
require "#{cwd}/admin/which_qmake.rb"
require "#{cwd}/admin/utils.rb"
begin
IO.read("#{cwd}/src/global.h") =~ /LASTFM_VERSION_STRING\s+"((\d\.)*\d)"/
abort "Couldn't determine our version!" if $1.nil?
LFM_VERSION=$1
ENV['LFM_VERSION']=LFM_VERSION
h1 "Configuring liblastfm-#{LFM_VERSION}..."
unless ARGV.include? '--skip-checks'
$qmake=which_qmake
pkgconfig 'samplerate', 'libsamplerate'
pkgconfig 'fftw3f', 'fftw'
puts 'Using '+`which #{$qmake}` unless Platform::IMPL == :mswin
else
$qmake='qmake'
end
h2 'Determining installation prefix' do
if ARGV.include? '--prefix'
n=ARGV.index '--prefix'
ENV['LFM_PREFIX'] = ARGV[n+1]
end
ENV['LFM_PREFIX'] = '/usr/local' if ENV['LFM_PREFIX'].nil?
if File.exists? ENV['LFM_PREFIX'] and !File.directory? ENV['LFM_PREFIX']
abort "Installation prefix exists but isn't a directory: "+ENV['LFM_PREFIX']
end
puts "Will install to: "+ENV['LFM_PREFIX']
end
h1 'Generating Build System'
h2 'Generating .qmake.env' do
f = File.new("#{cwd}/.qmake.env", 'w')
f.write qmake_env('CC', 'QMAKE_CC')
f.write qmake_env('CXX', 'QMAKE_CXX')
f.write qmake_env('LDFLAGS', 'QMAKE_LFLAGS_RELEASE')
f.write qmake_env(['CFLAGS', 'CPPFLAGS'], 'QMAKE_CFLAGS_RELEASE')
f.write qmake_env(['CXXFLAGS', 'CPPFLAGS'], 'QMAKE_CXXFLAGS_RELEASE')
f.close
end unless Platform::IMPL == :mswin
h2 "Running qpp..." do
['src/lastfm.pro','src/fingerprint/fingerprint.pro'].each do |p|
d="#{cwd}/#{File.dirname p}"
f=File.new "#{d}/_files.qmake", 'w'
f.write `ruby admin/qpp #{p}`
# on Windows VERSION produces lastfm0.dll, the 0 breaks the build
f.puts "VERSION = #{LFM_VERSION}" unless Platform::OS == :win32
end
end
h2 "Configuring qmake..." do
args=Array.new
args << '-spec macx-g++' if Platform::IMPL == :macosx
if ARGV.include? '--release'
args << '-config release'
args << '"CONFIG += app_bundle"' if Platform::IMPL == :macosx and ARGV.include? '--bundle'
else
args << '-config debug'
end
if ARGV.include? '--no-strip'
args << '"CONFIG += nostrip"'
end
ENV['LFM_QMAKE'] = "#{$qmake} #{args.join(' ')}"
end
h2 "Generating Makefile..." do
hs = Array.new
hs << 'global.h'
hs << 'core/UrlBuilder.h' << 'core/XmlQuery.h' << 'core/misc.h'
hs << 'fingerprint/Fingerprint.h' << 'fingerprint/FingerprintableSource.h'
hs << 'radio/RadioStation.h' << 'radio/RadioTuner.h'
hs << 'scrobble/Audioscrobbler.h' << 'scrobble/ScrobblePoint.h' << 'scrobble/ScrobbleCache.h'
hs << 'types/AbstractType.h' << 'types/Track.h' << 'types/Mbid.h' << 'types/Artist.h' << 'types/Album.h' << 'types/FingerprintId.h' << 'types/Playlist.h' << 'types/Tag.h' << 'types/User.h' << 'types/Xspf.h'
hs << 'ws/ws.h' << 'ws/InternetConnectionMonitor.h' << 'ws/NetworkAccessManager.h'
File.new("#{cwd}/Makefile", 'w').write `ruby admin/Makefile.rb #{hs.join(' ')}`
end
case Platform::IMPL
when :mswin then make='nmake all'
else make='make' # NOTE only tested with GNU make, sorry :(
end
puts
puts "Good, your configure is finished! Now type: #{make}"
rescue QMakeTooOld
puts <<-sput
Your version of Qt seems to be too old, we require Qt 4.4 or above.
It is possible you have Qt3 and Qt4 both installed. Locate your Qt4
installation and ensure it is placed first in the path, eg:
PATH=/opt/qt4/bin:\$PATH ./configure
sput
exit 1
rescue QMakeNotFound
puts "Sorry, qmake was not found, is Qt4 installed?"
exit 2
rescue PkgNotFound => e
puts <<-sput
Sorry, we couldn't find #{e}.
You can try to compile anyway by forcing configure to finish:
./configure --skip-checks
sput
exit 3
rescue PkgConfigNotFound
puts "Sorry, pkg-config could not be found. You should install it!"
exit 4
end

95
thirdparty/liblastfm2/demos/demo1.cpp vendored Normal file
View File

@@ -0,0 +1,95 @@
/*
This software is in the public domain, furnished "as is", without technical
support, and with no warranty, express or implied, as to its usefulness for
any purpose.
*/
#include <lastfm.h> // this includes everything in liblastfm, you may prefer
#include <QtCore> // to just include what you need with your project. Still
#include <QtGui> // we've given you the option.
#include <QPointer>
#include <QNetworkReply>
class ArtistList : public QListWidget
{
Q_OBJECT
QPointer<QNetworkReply> reply;
QString artist;
public:
ArtistList()
{
connect( this,
SIGNAL(itemActivated( QListWidgetItem* )),
SLOT(onItemActivated( QListWidgetItem* )) );
}
void getSimilar( const QString& artist )
{
this->artist = artist;
setWindowTitle( "Loading " + artist + "..." );
// deleting a reply cancels the request and disconnects all signals
delete reply;
reply = lastfm::Artist( artist ).getSimilar();
connect( reply, SIGNAL(finished()), SLOT(onGotSimilar()) );
}
private slots:
void onGotSimilar()
{
QNetworkReply* r = static_cast<QNetworkReply*>(sender());
// always enclose retrieval functions in a try block, as they will
// throw if they can't parse the data
try
{
// you decode the response using the equivalent static function
QMap<int, QString> artists = lastfm::Artist::getSimilar( r );
clear();
// we iterate backwards because best match is last because the map
// sorts itself by key
QStringListIterator i( artists.values() );
i.toBack();
while (i.hasPrevious())
addItem( i.previous() );
setWindowTitle( artist );
}
catch (std::runtime_error& e)
{
// if getSimilar() failed to parse the QNetworkReply, then e will
// be of type lastfm::ws::ParseError, which derives
// std::runtime_error
qWarning() << e.what();
}
}
void onItemActivated( QListWidgetItem* item )
{
getSimilar( item->text() );
}
};
int main( int argc, char** argv )
{
QApplication app( argc, argv );
app.setApplicationName( "liblastfm" ); // used to generate UserAgent
// all you need for non-authenticated webservices is your API key
// this one is a public one, it can only do artist.getSimilar calls, so
// I suggest you don't use it :P
lastfm::ws::ApiKey = "b25b959554ed76058ac220b7b2e0a026";
ArtistList artists;
artists.getSimilar( "nirvana" );
artists.resize( 300, 400 ); // Qt picks truly asanine default sizes for its widgets
artists.show();
return app.exec();
}
#include "demo1.moc"

114
thirdparty/liblastfm2/demos/demo2.cpp vendored Normal file
View File

@@ -0,0 +1,114 @@
/*
This software is in the public domain, furnished "as is", without technical
support, and with no warranty, express or implied, as to its usefulness for
any purpose.
*/
#include <lastfm.h>
#include <QtCore>
struct MyCoreApp : QCoreApplication
{
Q_OBJECT
public:
MyCoreApp( int& argc, char**& argv ) : QCoreApplication( argc, argv )
{}
private slots:
void onWsError( lastfm::ws::Error e )
{
// QNetworkReply will invoke this slot on application level errors
// mostly this is only stuff like Ws::InvalidSessionKey and
// Ws::InvalidApiKey
qWarning() << e;
}
};
int main( int argc, char** argv )
{
MyCoreApp app( argc, argv );
// this is used to generate the UserAgent for webservice requests
// please set it to something sensible in your application
app.setApplicationName( "liblastfm" );
////// you'll need to fill these in for this demo to work
lastfm::ws::Username =
lastfm::ws::ApiKey =
lastfm::ws::SharedSecret =
QString password =
////// Usually you never have to construct an Last.fm WS API call manually
// eg. Track.getTopTags() just returns a QNetworkReply* but authentication is
// different.
// We're using getMobileSession here as we're a console app, but you
// should use getToken if you can as the user will be presented with a
// route that feels my trustworthy to them than entering their password
// into some random app they just downloaded... ;)
QMap<QString, QString> params;
params["method"] = "auth.getMobileSession";
params["username"] = lastfm::ws::Username;
params["authToken"] = lastfm::md5( (lastfm::ws::Username + lastfm::md5( password.toUtf8() )).toUtf8() );
QNetworkReply* reply = lastfm::ws::post( params );
// never do this when an event loop is running it's a real HACK
QEventLoop loop;
loop.connect( reply, SIGNAL(finished()), SLOT(quit()) );
loop.exec();
try
{
////// Usually there is a convenience function to decode the output from
// ws calls too, but again, authentication is different. We think you
// need to handle it yourselves :P Also conveniently it means you
// can learn more about what our webservices return, eg. this service
// will return an XML document like this:
//
// <lfm status="ok">
// <session>
// <name>mxcl</name>
// <key>d580d57f32848f5dcf574d1ce18d78b2</key>
// <subscriber>1</subscriber>
// </session>
// </lfm>
//
// If status is not "ok" then this function throws
lastfm::XmlQuery const lfm = lastfm::ws::parse( reply );
// replace username; because eg. perhaps the user typed their
// username with the wrong case
lastfm::ws::Username = lfm["session"]["name"].text();
// we now have a session key, you should save this, forever! Really.
// DO NOT AUTHENTICATE EVERY TIME THE APP STARTS! You only have to do
// this once. Or again if the user deletes your key on the site. If
// that happens you'll get notification to your onWsError() function,
// see above.
lastfm::ws::SessionKey = lfm["session"]["key"].text();
qDebug() << "sk:" << lastfm::ws::SessionKey;
////// because the SessionKey is now set, the AuthenticatedUser class will
// work. And we can call authenticated calls
QNetworkReply* reply = lastfm::AuthenticatedUser().getRecommendedArtists();
// again, you shouldn't do this.. ;)
QEventLoop loop;
loop.connect( reply, SIGNAL(finished()), SLOT(quit()) );
loop.exec();
// yay, a list rec'd artists to stderr :)
qDebug() << lastfm::Artist::list( reply );
}
catch (std::runtime_error& e)
{
// lastfm::ws::parse() can throw lastfm::ws::ParseError, this
// exception derives std::runtime_error
qWarning() << e.what();
return 1;
}
}
#include "demo2.moc"

63
thirdparty/liblastfm2/demos/demo3.cpp vendored Normal file
View File

@@ -0,0 +1,63 @@
/*
This software is in the public domain, furnished "as is", without technical
support, and with no warranty, express or implied, as to its usefulness for
any purpose.
*/
#include <lastfm.h>
#include <QtCore>
#include <QtNetwork>
#include "src/_version.h"
struct MyCoreApp : QCoreApplication
{
Q_OBJECT
public:
MyCoreApp( int& argc, char** argv ) : QCoreApplication( argc, argv )
{}
public slots:
void onStatus( int status )
{
qDebug() << lastfm::Audioscrobbler::Status(status);
}
};
int main( int argc, char** argv )
{
// all 6 of these lines are REQUIRED in order to scrobble
// this demo requires you to fill in the blanks as well...
lastfm::ws::Username =
lastfm::ws::ApiKey =
lastfm::ws::SharedSecret =
lastfm::ws::SessionKey = // you need to auth to get this... try demo2
QCoreApplication::setApplicationName( "liblastfm" );
QCoreApplication::setApplicationVersion( VERSION );
MyCoreApp app( argc, argv );
lastfm::MutableTrack t;
t.setArtist( "Max Howell" );
t.setTitle( "I Told You Not To Trust Me With Your Daughter" );
t.setDuration( 30 );
t.stamp(); //sets track start time
lastfm::Audioscrobbler as( "ass" );
as.nowPlaying( t );
// Audioscrobbler will submit whatever is in the cache when you call submit.
// And the cache is persistent between sessions. So you should cache at the
// scrobble point usually, not before
as.cache( t );
//FIXME I don't get it, but the timer never triggers! pls fork and fix!
QTimer::singleShot( 31*1000, &as, SLOT(submit()) );
app.connect( &as, SIGNAL(status(int)), SLOT(onStatus(int)) );
return app.exec();
}
#include "demo3.moc"

3
thirdparty/liblastfm2/demos/demos.pro vendored Normal file
View File

@@ -0,0 +1,3 @@
QT = core gui network xml
LIBS += -llastfm -L$$DESTDIR
SOURCES = demo1.cpp # change to demo2.cpp (etc.) to compile that demo

114
thirdparty/liblastfm2/src/CMakeLists.txt vendored Normal file
View File

@@ -0,0 +1,114 @@
cmake_minimum_required(VERSION 2.6)
# Macro to copy and rename headers
macro(copy_header from to)
configure_file(
${CMAKE_CURRENT_SOURCE_DIR}/${from}
${CMAKE_CURRENT_BINARY_DIR}/lastfm/${to}
COPY_ONLY
)
install(
FILES ${CMAKE_CURRENT_BINARY_DIR}/lastfm/${to}
DESTINATION include/lastfm/
)
endmacro(copy_header)
# Copy headers
copy_header(core/misc.h misc.h)
copy_header(core/XmlQuery.h XmlQuery)
copy_header(core/UrlBuilder.h UrlBuilder)
copy_header(global.h global.h)
copy_header(radio/RadioTuner.h RadioTuner)
copy_header(radio/RadioStation.h RadioStation)
copy_header(scrobble/Audioscrobbler.h Audioscrobbler)
copy_header(scrobble/ScrobbleCache.h ScrobbleCache)
copy_header(scrobble/ScrobblePoint.h ScrobblePoint)
copy_header(types/AbstractType.h AbstractType)
copy_header(types/Album.h Album)
copy_header(types/Artist.h Artist)
copy_header(types/FingerprintId.h FingerprintId)
copy_header(types/Mbid.h Mbid)
copy_header(types/Playlist.h Playlist)
copy_header(types/Tag.h Tag)
copy_header(types/Track.h Track)
copy_header(types/User.h User)
copy_header(types/User.h UserList)
copy_header(types/Xspf.h Xspf)
copy_header(ws/ws.h ws.h)
copy_header(ws/InternetConnectionMonitor.h InternetConnectionMonitor)
copy_header(ws/NetworkAccessManager.h NetworkAccessManager)
include_directories(${QT_INCLUDES} ${CMAKE_CURRENT_BINARY_DIR})
set(SOURCES
scrobble/ScrobbleCache.cpp
scrobble/Audioscrobbler.cpp
types/FingerprintId.cpp
types/Artist.cpp
types/Tag.cpp
types/Track.cpp
types/User.cpp
types/Xspf.cpp
types/Album.cpp
types/Playlist.cpp
types/Mbid.cpp
radio/RadioTuner.cpp
radio/RadioStation.cpp
core/UrlBuilder.cpp
core/misc.cpp
core/XmlQuery.cpp
ws/NetworkAccessManager.cpp
ws/ws.cpp
ws/InternetConnectionMonitor.cpp
ws/NetworkConnectionMonitor.cpp
)
set(MOC_HEADERS
scrobble/Audioscrobbler.h
types/Track.h
radio/RadioTuner.h
ws/NetworkConnectionMonitor.h
ws/InternetConnectionMonitor.h
ws/NetworkAccessManager.h
)
if(UNIX)
if(APPLE)
set(SOURCES ${SOURCES} ws/mac/MNetworkConnectionMonitor_mac.cpp)
set(MOC_HEADERS ${MOC_HEADERS} ws/mac/MNetworkConnectionMonitor.h)
else(APPLE)
set(SOURCES ${SOURCES} ws/linux/LNetworkConnectionMonitor_linux.cpp)
set(MOC_HEADERS ${MOC_HEADERS} ws/linux/LNetworkConnectionMonitor.h)
endif(APPLE)
endif(UNIX)
if(WIN32)
set(SOURCES ${SOURCES} ws/win/WNetworkConnectionMonitor_win.cpp)
set(MOC_HEADERS ${MOC_HEADERS} ws/win/WNetworkConnectionMonitor.h)
endif(WIN32)
qt4_wrap_cpp(MOC_SOURCES ${MOC_HEADERS})
add_library(tomahawk_lastfm2 SHARED
${SOURCES}
${MOC_SOURCES}
)
target_link_libraries(tomahawk_lastfm2
${QT_LIBRARIES}
${QT_QTDBUS_LIBRARY}
)
set_target_properties(tomahawk_lastfm2 PROPERTIES COMPILE_FLAGS "-DLASTFM_OHAI_QMAKE" )
if(APPLE)
target_link_libraries(tomahawk_lastfm2
/System/Library/Frameworks/CoreFoundation.framework
/System/Library/Frameworks/SystemConfiguration.framework
)
endif(APPLE)
install(TARGETS tomahawk_lastfm2
RUNTIME DESTINATION bin
LIBRARY DESTINATION lib${LIB_SUFFIX}
ARCHIVE DESTINATION lib${LIB_SUFFIX}
)

8
thirdparty/liblastfm2/src/core/README vendored Normal file
View File

@@ -0,0 +1,8 @@
Files in lastfm-core are basically extensions to fundamental Qt classes.
They may be useful to you, but mainly they are here because they are useful to
liblastfm in general.
A lot of the time they are convenience functions that hopefully at some point
Qt will make obsolete.
<max@last.fm>

View File

@@ -0,0 +1,83 @@
/*
Copyright 2009 Last.fm Ltd.
- Primarily authored by Max Howell, Jono Cole and Doug Mansell
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 "UrlBuilder.h"
#include <QRegExp>
#include <QStringList>
QUrl
lastfm::UrlBuilder::url() const
{
QUrl url;
url.setScheme( "http" );
url.setHost( host() );
url.setEncodedPath( path );
return url;
}
QByteArray //static
lastfm::UrlBuilder::encode( QString s )
{
foreach (QChar c, QList<QChar>() << '&' << '/' << ';' << '+' << '#' << '%')
if (s.contains( c ))
// the middle step may seem odd but this is what the site does
// eg. search for the exact string "Radiohead 2 + 2 = 5"
return QUrl::toPercentEncoding( s ).replace( "%20", "+" ).toPercentEncoding( "", "+" );;
return QUrl::toPercentEncoding( s.replace( ' ', '+' ), "+" );
}
QString //static
lastfm::UrlBuilder::host( const QLocale& locale )
{
switch (locale.language())
{
case QLocale::Portuguese: return "www.lastfm.com.br";
case QLocale::Turkish: return "www.lastfm.com.tr";
case QLocale::French: return "www.lastfm.fr";
case QLocale::Italian: return "www.lastfm.it";
case QLocale::German: return "www.lastfm.de";
case QLocale::Spanish: return "www.lastfm.es";
case QLocale::Polish: return "www.lastfm.pl";
case QLocale::Russian: return "www.lastfm.ru";
case QLocale::Japanese: return "www.lastfm.jp";
case QLocale::Swedish: return "www.lastfm.se";
case QLocale::Chinese: return "cn.last.fm";
default: return "www.last.fm";
}
}
QUrl //static
lastfm::UrlBuilder::localize( QUrl url)
{
url.setHost( url.host().replace( QRegExp("^(www.)?last.fm"), host() ) );
return url;
}
QUrl //static
lastfm::UrlBuilder::mobilize( QUrl url )
{
url.setHost( url.host().replace( QRegExp("^(www.)?last"), "m.last" ) );
return url;
}

View File

@@ -0,0 +1,69 @@
/*
Copyright 2009 Last.fm Ltd.
- Primarily authored by Max Howell, Jono Cole and Doug Mansell
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 LASTFM_URL_BUILDER_H
#define LASTFM_URL_BUILDER_H
#include <lastfm/global.h>
#include <QLocale>
#include <QString>
#include <QUrl>
namespace lastfm
{
/** For building www.last.fm urls. We have special rules for encoding and that */
class LASTFM_DLLEXPORT UrlBuilder
{
QByteArray path;
public:
/** Careful, the base is not encoded at all, we assume it is ASCII!
* If you need it encoded at all you must use the slash function.
* eg. UrlBuilder( "user" ).slash( "mxcl" ) ==> http://last.fm/user/mxcl
*/
UrlBuilder( const QString& base ) : path( '/' + base.toAscii() )
{}
UrlBuilder& slash( const QString& path ) { this->path += '/' + encode( path ); return *this; }
QUrl url() const;
/** www.last.fm becomes the local version, eg www.lastfm.de */
static QUrl localize( QUrl );
/** www.last.fm becomes m.last.fm, localisation is preserved */
static QUrl mobilize( QUrl );
/** Use this to URL encode any database item (artist, track, album). It
* internally calls UrlEncodeSpecialChars to double encode some special
* symbols according to the same pattern as that used on the website.
*
* &, /, ;, +, #
*
* Use for any urls that go to www.last.fm
* Do not use for ws.audioscrobbler.com
*/
static QByteArray encode( QString );
/** returns eg. www.lastfm.de */
static QString host( const QLocale& = QLocale() );
};
}
#endif

View File

@@ -0,0 +1,64 @@
/*
Copyright 2009 Last.fm Ltd.
- Primarily authored by Max Howell, Jono Cole and Doug Mansell
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 "XmlQuery.h"
#include <QStringList>
using lastfm::XmlQuery;
XmlQuery::XmlQuery( const QByteArray& bytes )
{
domdoc.setContent(bytes);
e = domdoc.documentElement();
}
XmlQuery
XmlQuery::operator[]( const QString& name ) const
{
QStringList parts = name.split( ' ' );
if (parts.size() >= 2)
{
QString tagName = parts[0];
parts = parts[1].split( '=' );
QString attributeName = parts.value( 0 );
QString attributeValue = parts.value( 1 );
foreach (XmlQuery e, children( tagName ))
if (e.e.attribute( attributeName ) == attributeValue)
return e;
}
XmlQuery xq( e.firstChildElement( name ), name.toUtf8().data() );
xq.domdoc = this->domdoc;
return xq;
}
QList<XmlQuery>
XmlQuery::children( const QString& named ) const
{
QList<XmlQuery> elements;
QDomNodeList nodes = e.elementsByTagName( named );
for (int x = 0; x < nodes.count(); ++x) {
XmlQuery xq( nodes.at( x ).toElement() );
xq.domdoc = this->domdoc;
elements += xq;
}
return elements;
}

View File

@@ -0,0 +1,77 @@
/*
Copyright 2009 Last.fm Ltd.
- Primarily authored by Max Howell, Jono Cole and Doug Mansell
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 LASTFM_XMLQUERY_H
#define LASTFM_XMLQUERY_H
#include <lastfm/global.h>
#include <QDomDocument>
#include <QDomElement>
namespace lastfm
{
/** Qt's XmlQuery implementation is totally unimpressive, so this is a
* hack that feels like jQuery */
class LASTFM_DLLEXPORT XmlQuery
{
QDomDocument domdoc;
QDomElement e;
public:
/** we assume the bytearray is an XML document, this object will then
* represent the documentElement of that document, eg. if this is a
* Last.fm webservice response:
*
* XmlQuery xq = lastfm::ws::parse(response);
* qDebug() << xq["artist"].text()
*
* Notice the lfm node is not referenced, that is because it is the
* document-element of the XML document.
*/
XmlQuery( const QByteArray& );
XmlQuery( const QDomElement& e, const char* name = "" ) : e( e )
{
if (e.isNull()) qWarning() << "Expected node absent:" << name;
}
/** Selects a DIRECT child element, you can specify attributes like so:
*
* e["element"]["element attribute=value"].text();
*/
XmlQuery operator[]( const QString& name ) const;
QString text() const { return e.text(); }
QString attribute( const QString& name ) const{ return e.attribute( name ); }
/** selects all children with specified name, recursively */
QList<XmlQuery> children( const QString& named ) const;
operator QDomElement() const { return e; }
};
}
inline QDebug operator<<( QDebug d, const lastfm::XmlQuery& xq )
{
QString s;
QTextStream t( &s, QIODevice::WriteOnly );
QDomElement(xq).save( t, 2 );
return d << s;
}
#endif

205
thirdparty/liblastfm2/src/core/misc.cpp vendored Normal file
View File

@@ -0,0 +1,205 @@
/*
Copyright 2009 Last.fm Ltd.
- Primarily authored by Max Howell, Jono Cole and Doug Mansell
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 "misc.h"
#include <QDir>
#ifdef WIN32
#include <shlobj.h>
#endif
#ifdef Q_WS_MAC
#include <Carbon/Carbon.h>
#endif
#ifdef Q_WS_MAC
QDir
lastfm::dir::bundle()
{
// Trolltech provided example
CFURLRef appUrlRef = CFBundleCopyBundleURL( CFBundleGetMainBundle() );
CFStringRef macPath = CFURLCopyFileSystemPath( appUrlRef, kCFURLPOSIXPathStyle );
QString path = CFStringToQString( macPath );
CFRelease(appUrlRef);
CFRelease(macPath);
return QDir( path );
}
#endif
static QDir dataDotDot()
{
#ifdef WIN32
if ((QSysInfo::WindowsVersion & QSysInfo::WV_DOS_based) == 0)
{
// Use this for non-DOS-based Windowses
char path[MAX_PATH];
HRESULT h = SHGetFolderPathA( NULL,
CSIDL_LOCAL_APPDATA | CSIDL_FLAG_CREATE,
NULL,
0,
path );
if (h == S_OK)
return QString::fromLocal8Bit( path );
}
return QDir::home();
#elif defined(Q_WS_MAC)
#define EIT( x ) { OSErr err = x; if (err != noErr) throw 1; }
try
{
short vRefNum = 0;
long dirId;
EIT( ::FindFolder( kOnAppropriateDisk,
kApplicationSupportFolderType,
kDontCreateFolder,
&vRefNum,
&dirId ) );
// Now we have a vRefNum and a dirID - but *not* an Unix-Path as string.
// Lets make one based from this:
FSSpec fsspec;
EIT( ::FSMakeFSSpec( vRefNum, dirId, NULL, &fsspec ) );
// ...and build an FSRef based on thes FSSpec.
FSRef fsref;
EIT( ::FSpMakeFSRef( &fsspec, &fsref ) );
// ...then extract the Unix Path as a C-String from the FSRef
unsigned char path[512];
EIT( ::FSRefMakePath( &fsref, path, 512 ) );
return QDir::homePath() + QString::fromUtf8( (char*)path );
}
catch (int)
{
return QDir::home().filePath( "Library/Application Support" );
}
#elif defined(Q_WS_X11)
return QDir::home().filePath( ".local/share" );
#else
return QDir::home();
#endif
}
QDir
lastfm::dir::runtimeData()
{
return dataDotDot().filePath( "Last.fm" );
}
QDir
lastfm::dir::logs()
{
#ifdef Q_WS_MAC
return QDir::home().filePath( "Library/Logs/Last.fm" );
#else
return runtimeData();
#endif
}
QDir
lastfm::dir::cache()
{
#ifdef Q_WS_MAC
return QDir::home().filePath( "Library/Caches/Last.fm" );
#else
return runtimeData().filePath( "cache" );
#endif
}
#ifdef WIN32
QDir
lastfm::dir::programFiles()
{
char path[MAX_PATH];
// TODO: this call is dependant on a specific version of shell32.dll.
// Need to degrade gracefully. Need to bundle SHFolder.exe with installer
// and execute it on install for this to work on Win98.
HRESULT h = SHGetFolderPathA( NULL,
CSIDL_PROGRAM_FILES,
NULL,
0, // current path
path );
if (h != S_OK)
{
qCritical() << "Couldn't get Program Files dir. Possibly Win9x?";
return QDir();
}
return QString::fromLocal8Bit( path );
}
#endif
#ifdef Q_WS_MAC
CFStringRef
lastfm::QStringToCFString( const QString &s )
{
return CFStringCreateWithCharacters( 0, (UniChar*)s.unicode(), s.length() );
}
QByteArray
lastfm::CFStringToUtf8( CFStringRef s )
{
QByteArray result;
if (s != NULL)
{
CFIndex length;
length = CFStringGetLength( s );
length = CFStringGetMaximumSizeForEncoding( length, kCFStringEncodingUTF8 ) + 1;
char* buffer = new char[length];
if (CFStringGetCString( s, buffer, length, kCFStringEncodingUTF8 ))
result = QByteArray( buffer );
else
qWarning() << "CFString conversion failed.";
delete[] buffer;
}
return result;
}
#endif
#if 0
// this is a Qt implementation I found
QString cfstring2qstring(CFStringRef str)
{
if(!str)
return QString();
CFIndex length = CFStringGetLength(str);
if(const UniChar *chars = CFStringGetCharactersPtr(str))
return QString((QChar *)chars, length);
UniChar *buffer = (UniChar*)malloc(length * sizeof(UniChar));
CFStringGetCharacters(str, CFRangeMake(0, length), buffer);
QString ret((QChar *)buffer, length);
free(buffer);
return ret;
}
#endif

111
thirdparty/liblastfm2/src/core/misc.h vendored Normal file
View File

@@ -0,0 +1,111 @@
/*
Copyright 2009 Last.fm Ltd.
- Primarily authored by Max Howell, Jono Cole and Doug Mansell
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 LASTFM_MISC_H
#define LASTFM_MISC_H
#include <lastfm/global.h>
#include <QCryptographicHash>
#include <QDir>
#include <QString>
#ifdef Q_WS_MAC
typedef const struct __CFString* CFStringRef;
#endif
namespace lastfm
{
namespace dir
{
#ifdef Q_WS_WIN
LASTFM_DLLEXPORT QDir programFiles();
#endif
#ifdef Q_WS_MAC
LASTFM_DLLEXPORT QDir bundle();
#endif
LASTFM_DLLEXPORT QDir runtimeData();
LASTFM_DLLEXPORT QDir cache();
LASTFM_DLLEXPORT QDir logs();
}
#ifdef Q_WS_MAC
LASTFM_DLLEXPORT QByteArray CFStringToUtf8( CFStringRef );
LASTFM_DLLEXPORT CFStringRef QStringToCFString( const QString& );
inline QString CFStringToQString( CFStringRef s );
#endif
inline const char* platform()
{
#ifdef Q_WS_WIN
switch (QSysInfo::WindowsVersion)
{
case QSysInfo::WV_32s: return "Windows 3.1 with Win32s";
case QSysInfo::WV_95: return "Windows 95";
case QSysInfo::WV_98: return "Windows 98";
case QSysInfo::WV_Me: return "Windows Me";
case QSysInfo::WV_DOS_based: return "MS-DOS-based Windows";
case QSysInfo::WV_NT: return "Windows NT";
case QSysInfo::WV_2000: return "Windows 2000";
case QSysInfo::WV_XP: return "Windows XP";
case QSysInfo::WV_2003: return "Windows Server 2003";
case QSysInfo::WV_VISTA: return "Windows Vista";
case QSysInfo::WV_NT_based: return "NT-based Windows";
case QSysInfo::WV_CE: return "Windows CE";
case QSysInfo::WV_CENET: return "Windows CE.NET";
case QSysInfo::WV_CE_based: return "CE-based Windows";
default: return "Unknown";
}
#elif defined Q_WS_MAC
switch (QSysInfo::MacintoshVersion)
{
case QSysInfo::MV_Unknown: return "Unknown Mac";
case QSysInfo::MV_9: return "Mac OS 9";
case QSysInfo::MV_10_0: return "Mac OS X 10.0";
case QSysInfo::MV_10_1: return "Mac OS X 10.1";
case QSysInfo::MV_10_2: return "Mac OS X 10.2";
case QSysInfo::MV_10_3: return "Mac OS X 10.3";
case QSysInfo::MV_10_4: return "Mac OS X 10.4";
case QSysInfo::MV_10_5: return "Mac OS X 10.5";
default: return "Unknown";
}
#elif defined Q_WS_X11
return "UNIX X11";
#else
return "Unknown";
#endif
}
inline QString md5( const QByteArray& src )
{
QByteArray const digest = QCryptographicHash::hash( src, QCryptographicHash::Md5 );
return QString::fromLatin1( digest.toHex() ).rightJustified( 32, '0' ).toLower();
}
}
#ifdef Q_WS_MAC
inline QString lastfm::CFStringToQString( CFStringRef s )
{
return QString::fromUtf8( CFStringToUtf8( s ) );
}
#endif
#endif //LASTFM_MISC_H

View File

@@ -0,0 +1,32 @@
cmake_minimum_required(VERSION 2.6)
include_directories(${LIBFFTW3_INCLUDE_DIRS})
include_directories(${LIBSAMPLERATE_INCLUDE_DIRS})
include_directories(${QT_INCLUDES})
include_directories(${CMAKE_CURRENT_BINARY_DIR}/..)
link_directories(${LIBFFTW3_LIBRARY_DIRS})
link_directories(${LIBSAMPLERATE_LIBRARY_DIRS})
set(SOURCES
Collection.cpp
Fingerprint.cpp
Sha256.cpp
fplib/Filter.cpp
fplib/FingerprintExtractor.cpp
fplib/OptFFT.cpp
)
add_library(tomahawk_lastfm2_fingerprint SHARED
${SOURCES}
)
target_link_libraries(tomahawk_lastfm2_fingerprint
${QT_LIBRARIES}
${QT_QTSQL_LIBRARY}
${LIBFFTW3_LIBRARY}
${LIBSAMPLERATE_LIBRARY}
tomahawk_lastfm2
)
set_target_properties(tomahawk_lastfm2_fingerprint PROPERTIES COMPILE_FLAGS "-DLASTFM_FINGERPRINT_OHAI_QMAKE" )

View File

@@ -0,0 +1,267 @@
/*
Copyright 2009 Last.fm Ltd.
- Primarily authored by Max Howell, Jono Cole and Doug Mansell
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 "Collection.h"
#include "../core/misc.h"
#include <QCoreApplication>
#include <QFileInfo>
#include <QMutexLocker>
#include <QSqlQuery>
#include <QSqlError>
#include <QStringList>
#include <QVariant>
static const int k_collectionDbVersion = 1;
// Singleton instance needs to be initialised
Collection* Collection::s_instance = NULL;
Collection::Collection()
{
m_db = QSqlDatabase::addDatabase( "QSQLITE", "collection" );
m_db.setDatabaseName( lastfm::dir::runtimeData().filePath( "collection.db" ) );
if (!m_db.open()) {
qDebug() << m_db.lastError();
return;
}
if (!m_db.isValid()) {
qWarning() << "collection.db connection is not valid";
return;
}
if (!m_db.tables().contains( "files" ))
{
qDebug() << "Creating Collection database";
query( "CREATE TABLE artists ("
"id INTEGER PRIMARY KEY AUTOINCREMENT,"
"serverUid INTEGER,"
"lcName TEXT NOT NULL,"
"displayName TEXT NOT NULL );" );
query( "CREATE TABLE albums ("
"id INTEGER PRIMARY KEY AUTOINCREMENT,"
"serverUid INTEGER,"
"lcName TEXT NOT NULL,"
"displayName TEXT NOT NULL,"
"primaryArtist INTEGER NOT NULL );" );
query( "CREATE UNIQUE INDEX album_names_idx ON albums ( primaryArtist, lcName );" );
query( "CREATE TABLE tracks ("
"id INTEGER PRIMARY KEY AUTOINCREMENT,"
"lcName TEXT NOT NULL,"
"displayName TEXT NOT NULL,"
"primaryArtist INTEGER NOT NULL,"
"primaryAlbum INTEGER );" );
query( "CREATE UNIQUE INDEX track_names_idx ON tracks ( primaryArtist, lcName );" );
query( "CREATE TABLE files ("
"id INTEGER PRIMARY KEY AUTOINCREMENT,"
"uri TEXT NOT NULL,"
"track INTEGER NOT NULL,"
"bitrate INTEGER,"
"samplerate INTEGER,"
"duration INTEGER,"
"filesize INTEGER,"
"source INTEGER,"
"modificationDate INTEGER,"
"lastPlayDate INTEGER,"
"playCounter INTEGER,"
"mbId VARCHAR( 36 ),"
"fpId INTEGER );" );
query( "CREATE UNIQUE INDEX files_uri_idx ON files ( uri );" );
query( "CREATE INDEX files_track_idx ON files ( track );" );
query( "CREATE INDEX files_fpId_idx ON files ( fpId );" );
query( "CREATE INDEX files_source_idx ON files ( source );" );
query( "CREATE TABLE sources ("
"id INTEGER PRIMARY KEY AUTOINCREMENT,"
"name TEXT UNIQUE,"
"available INTEGER,"
"host TEXT,"
"cost INTEGER );" );
query( "CREATE TABLE genres ("
"id INTEGER PRIMARY KEY AUTOINCREMENT,"
"name TEXT UNIQUE );" );
query( "CREATE TABLE labels ("
"id INTEGER PRIMARY KEY AUTOINCREMENT,"
"serverUid INTEGER UNIQUE,"
"name TEXT );" );
}
int const v = version();
if ( v < k_collectionDbVersion )
{
qDebug() << "Upgrading Collection::db from" << v << "to" << k_collectionDbVersion;
/**********************************************
* README!!!!!!! *
* Ensure you use v < x *
* Ensure you do upgrades in ascending order! *
**********************************************/
if ( v < 1 )
{
// Norman discovered that he stored some fpId's wrong prior to 17th December 2007
// So we have to wipe the fpIds for databases without the metadata table
// we didn't store version information before that, which was a bad decision wasn't it?
// this will trigger refingerprinting of every track
query( "UPDATE files SET fpId = NULL;" );
query( "CREATE TABLE metadata ("
"key TEXT UNIQUE NOT NULL,"
"value TEXT );" );
query( "INSERT INTO metadata (key, value) VALUES ('version', '1');" );
}
// do last, update DB version number
query( "UPDATE metadata set key='version', value='"
+ QString::number( k_collectionDbVersion ) + "';" );
}
}
Collection& //static
Collection::instance()
{
static QMutex mutex;
QMutexLocker locker( &mutex );
if ( !s_instance )
{
s_instance = new Collection;
qAddPostRoutine(destroy);
}
return *s_instance;
}
void //static
Collection::destroy()
{
delete s_instance;
QSqlDatabase::removeDatabase( "collection" );
}
int
Collection::version() const
{
QSqlQuery sql( m_db );
sql.exec( "SELECT value FROM metadata WHERE key='version';" );
if ( sql.next() )
{
return sql.value( 0 ).toInt();
}
return 0;
}
bool
Collection::query( const QString& queryToken )
{
QSqlQuery query( m_db );
query.exec( queryToken );
if ( query.lastError().isValid() )
{
qDebug() << "SQL query failed:" << query.lastQuery() << endl
<< "SQL error was:" << query.lastError().databaseText() << endl
<< "SQL error type:" << query.lastError().type();
return false;
}
return true;
}
QString
Collection::fileURI( const QString& filePath )
{
QString prefix( "file:/" );
#ifdef WIN32
prefix = "file://";
#endif
return prefix + QFileInfo( filePath ).absoluteFilePath();
}
QString
Collection::getFingerprintId( const QString& filePath )
{
QSqlQuery query( m_db );
query.prepare( "SELECT fpId FROM files WHERE uri = :uri" );
query.bindValue( ":uri", fileURI( filePath ) );
query.exec();
if ( query.lastError().isValid() )
{
qDebug() << "SQL query failed:" << query.lastQuery() << endl
<< "SQL error was:" << query.lastError().databaseText() << endl
<< "SQL error type:" << query.lastError().type();
}
else if (query.next())
return query.value( 0 ).toString();
return "";
}
bool
Collection::setFingerprintId( const QString& filePath, QString fpId )
{
bool isNumeric;
int intFpId = fpId.toInt( &isNumeric );
Q_ASSERT( isNumeric );
QSqlQuery query( m_db );
query.prepare( "REPLACE INTO files ( uri, track, fpId ) VALUES ( :uri, 0, :fpId )" );
query.bindValue( ":uri", fileURI( filePath ) );
query.bindValue( ":fpId", intFpId );
query.exec();
if ( query.lastError().isValid() )
{
qDebug() << "SQL query failed:" << query.lastQuery() << endl
<< "SQL error was:" << query.lastError().databaseText() << endl
<< "SQL error type:" << query.lastError().type();
return false;
}
return true;
}

View File

@@ -0,0 +1,59 @@
/*
Copyright 2009 Last.fm Ltd.
- Primarily authored by Max Howell, Jono Cole and Doug Mansell
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/>.
*/
/** Class that we use to store fingerprints, basically
*/
#ifndef COLLECTION_H
#define COLLECTION_H
#include <QObject>
#include <QSqlDatabase>
/** @author: <chris@last.fm> */
class Collection
{
public:
static Collection& instance();
/** \brief Temp method: Gets a fingerprint id. Returns "" if none found. */
QString getFingerprintId( const QString& filePath );
/** \brief Temp method: Sets a fingerprint id. */
bool setFingerprintId( const QString& filePath, QString fpId );
private:
Collection();
/** the database version
* version 0: up until 1.4.1
* version 1: from 1.4.2 */
int version() const;
bool query( const QString& queryToken );
QString fileURI( const QString& filePath );
static void destroy();
static Collection* s_instance;
QSqlDatabase m_db;
};
#endif // COLLECTION_H

View File

@@ -0,0 +1,76 @@
/*
Copyright 2009 Last.fm Ltd.
- Primarily authored by Max Howell, Jono Cole and Doug Mansell
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 <lastfm/Fingerprint>
#include <lastfm/FingerprintId>
#include <QtCore>
#include <QtNetwork>
using namespace lastfm;
static void finish( QNetworkReply* reply )
{
QEventLoop loop;
loop.connect( reply, SIGNAL(finished()), SLOT(quit()) );
loop.exec();
}
int main( int argc, char** argv )
{
QCoreApplication app( argc, argv );
// these fields are required
MutableTrack t;
t.setArtist( "Air" );
t.setTitle( "Redhead Girl" );
t.setAlbum( "Pocket Symphony" );
t.setUrl( QUrl::fromLocalFile( "/Users/mxcl/Music/iTunes/iTunes Music/Air/Pocket Symphony/1-11 Redhead Girl.mp3") );
try
{
Fingerprint fp( t );
// we cache FingerprintIds in an sqlite3 db, as the generate() function
// is expensive
if (fp.id().isNull())
{
// this generates the full fingerprint hash, which is about 20kB
fp.generate();
// this asks Last.fm for a FingerprintId
// the finish function is a Qt hack to allow syncronous HTTP
finish( fp.submit() );
// the decode step sets the FingerprintId
// the FingerprintId is required to obtain suggestions
// id will now be valid, or this function throws
fp.decode( reply );
}
finish( fp.id().getSuggestions() );
qDebug() << FingerprintId::getSuggestions( reply );
}
catch (Fingerprint::Error e)
{
qWarning() << e; //TODO enum debug thing
}
}

View File

@@ -0,0 +1,300 @@
/*
Copyright 2009 Last.fm Ltd.
- Primarily authored by Max Howell, Jono Cole and Doug Mansell
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 "Fingerprint.h"
#include "FingerprintableSource.h"
#include "Collection.h"
#include "Sha256.h"
#include "fplib/FingerprintExtractor.h"
#include "../ws/ws.h"
#include <QFileInfo>
#include <QNetworkAccessManager>
#include <QNetworkRequest>
#include <QNetworkReply>
#include <QStringList>
#include <fstream>
using lastfm::Track;
static const uint k_bufferSize = 1024 * 8;
static const int k_minTrackDuration = 30;
lastfm::Fingerprint::Fingerprint( const Track& t )
: m_track( t )
, m_id( -1 ), m_duration( 0 )
, m_complete( false )
{
QString id = Collection::instance().getFingerprintId( t.url().toLocalFile() );
if (id.size()) {
bool b;
m_id = id.toInt( &b );
if (!b) m_id = -1;
}
}
void
lastfm::Fingerprint::generate( FingerprintableSource* ms ) throw( Error )
{
//TODO throw if we can't get required metadata from the track object
//TODO if (!QFileInfo( path ).isReadable())
//TODO throw ReadError;
int sampleRate, bitrate, numChannels;
if ( !ms )
throw ReadError;
try
{
ms->init( m_track.url().toLocalFile() );
ms->getInfo( m_duration, sampleRate, bitrate, numChannels );
}
catch (std::exception& e)
{
qWarning() << e.what();
throw HeadersError;
}
if (m_duration < k_minTrackDuration)
throw TrackTooShortError;
ms->skipSilence();
bool fpDone = false;
fingerprint::FingerprintExtractor* extractor;
try
{
extractor = new fingerprint::FingerprintExtractor;
if (m_complete)
{
extractor->initForFullSubmit( sampleRate, numChannels );
}
else
{
extractor->initForQuery( sampleRate, numChannels, m_duration );
// Skippety skip for as long as the skipper sez (optimisation)
ms->skip( extractor->getToSkipMs() );
float secsToSkip = extractor->getToSkipMs() / 1000.0f;
fpDone = extractor->process( 0,
(size_t) sampleRate * numChannels * secsToSkip,
false );
}
}
catch (std::exception& e)
{
qWarning() << e.what();
throw DecodeError;
}
const size_t PCMBufSize = 131072;
short* pPCMBuffer = new short[PCMBufSize];
while (!fpDone)
{
size_t readData = ms->updateBuffer( pPCMBuffer, PCMBufSize );
if (readData == 0)
break;
try
{
fpDone = extractor->process( pPCMBuffer, readData, ms->eof() );
}
catch ( const std::exception& e )
{
qWarning() << e.what();
delete ms;
delete[] pPCMBuffer;
throw InternalError;
}
}
delete[] pPCMBuffer;
if (!fpDone)
throw InternalError;
// We succeeded
std::pair<const char*, size_t> fpData = extractor->getFingerprint();
if (fpData.first == NULL || fpData.second == 0)
throw InternalError;
// Make a deep copy before extractor gets deleted
m_data = QByteArray( fpData.first, fpData.second );
delete extractor;
}
static QString sha256( const QString& path )
{
// no clue why this is static, there was no comment when I refactored it
// initially --mxcl
static uint8_t pBuffer[SHA_BUFFER_SIZE+7];
unsigned char hash[SHA256_HASH_SIZE];
{
QByteArray path8 = QFile::encodeName( path );
std::ifstream inFile( path8.data(), std::ios::binary);
SHA256Context sha256;
SHA256Init( &sha256 );
uint8_t* pMovableBuffer = pBuffer;
// Ensure it is on a 64-bit boundary.
INTPTR offs;
if ((offs = reinterpret_cast<INTPTR>(pBuffer) & 7L))
pMovableBuffer += 8 - offs;
unsigned int len;
for (;;)
{
inFile.read( reinterpret_cast<char*>(pMovableBuffer), SHA_BUFFER_SIZE );
len = inFile.gcount();
if (len == 0)
break;
SHA256Update( &sha256, pMovableBuffer, len );
}
SHA256Final( &sha256, hash );
}
QString sha;
for (int i = 0; i < SHA256_HASH_SIZE; ++i)
{
QString hex = QString("%1").arg(uchar(hash[i]), 2, 16,
QChar('0'));
sha.append(hex);
}
return sha;
}
static QByteArray number( uint n )
{
return n ? QByteArray::number( n ) : "";
}
QNetworkReply*
lastfm::Fingerprint::submit() const
{
if (m_data.isEmpty())
return 0;
//Parameters understood by the server according to the MIR team:
//{ "trackid", "recordingid", "artist", "album", "track", "duration",
// "tracknum", "username", "sha256", "ip", "fpversion", "mbid",
// "filename", "genre", "year", "samplerate", "noupdate", "fulldump" }
Track const t = m_track;
QString const path = t.url().toLocalFile();
QFileInfo const fi( path );
#define e( x ) QUrl::toPercentEncoding( x )
QUrl url( "http://www.last.fm/fingerprint/query/" );
url.addEncodedQueryItem( "artist", e(t.artist()) );
url.addEncodedQueryItem( "album", e(t.album()) );
url.addEncodedQueryItem( "track", e(t.title()) );
url.addEncodedQueryItem( "duration", number( m_duration > 0 ? m_duration : t.duration() ) );
url.addEncodedQueryItem( "mbid", e(t.mbid()) );
url.addEncodedQueryItem( "filename", e(fi.completeBaseName()) );
url.addEncodedQueryItem( "fileextension", e(fi.completeSuffix()) );
url.addEncodedQueryItem( "tracknum", number( t.trackNumber() ) );
url.addEncodedQueryItem( "sha256", sha256( path ).toAscii() );
url.addEncodedQueryItem( "time", number(QDateTime::currentDateTime().toTime_t()) );
url.addEncodedQueryItem( "fpversion", QByteArray::number((int)fingerprint::FingerprintExtractor::getVersion()) );
url.addEncodedQueryItem( "fulldump", m_complete ? "true" : "false" );
url.addEncodedQueryItem( "noupdate", "false" );
#undef e
//FIXME: talk to mir about submitting fplibversion
QNetworkRequest request( url );
request.setHeader( QNetworkRequest::ContentTypeHeader, "multipart/form-data; boundary=----------------------------8e61d618ca16" );
QByteArray bytes;
bytes += "------------------------------8e61d618ca16\r\n";
bytes += "Content-Disposition: ";
bytes += "form-data; name=\"fpdata\"";
bytes += "\r\n\r\n";
bytes += m_data;
bytes += "\r\n";
bytes += "------------------------------8e61d618ca16--\r\n";
qDebug() << url;
qDebug() << "Fingerprint size:" << bytes.size() << "bytes";
return lastfm::nam()->post( request, bytes );
}
void
lastfm::Fingerprint::decode( QNetworkReply* reply, bool* complete_fingerprint_requested ) throw( Error )
{
// The response data will consist of a number and a string.
// The number is the fpid and the string is either FOUND or NEW
// (or NOT FOUND when noupdate was used). NEW means we should
// schedule a full fingerprint.
//
// In the case of an error, there will be no initial number, just
// an error string.
QString const response( reply->readAll() );
QStringList const list = response.split( ' ' );
QString const fpid = list.value( 0 );
QString const status = list.value( 1 );
if (response.isEmpty() || list.count() < 2 || response == "No response to client error")
goto bad_response;
if (list.count() != 2)
qWarning() << "Response looks bad but continuing anyway:" << response;
{
// so variables go out of scope before jump to label
// otherwise compiler error on GCC 4.2
bool b;
uint fpid_as_uint = fpid.toUInt( &b );
if (!b) goto bad_response;
Collection::instance().setFingerprintId( m_track.url().toLocalFile(), fpid );
if (complete_fingerprint_requested)
*complete_fingerprint_requested = (status == "NEW");
m_id = (int)fpid_as_uint;
return;
}
bad_response:
qWarning() << "Response is bad:" << response;
throw BadResponseError;
}

View File

@@ -0,0 +1,116 @@
/*
Copyright 2009 Last.fm Ltd.
- Primarily authored by Max Howell, Jono Cole and Doug Mansell
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 LASTFM_FINGERPRINT_H
#define LASTFM_FINGERPRINT_H
#include <lastfm/FingerprintId>
#include <lastfm/Track>
namespace lastfm
{
class LASTFM_FINGERPRINT_DLLEXPORT Fingerprint
{
lastfm::Track m_track;
QByteArray m_data;
int m_id;
int m_duration;
protected:
bool m_complete;
public:
/** represents a partial fingerprint of 20 seconds of music, this is
* considered 99.9999...9999% unique and so we use it for most stuff as
* it is much quicker than a complete fingerprint, still though, you
* should do the generate step in a thread. */
Fingerprint( const lastfm::Track& );
/** if the id isNull(), then you'll need to do generate, submit and decode */
FingerprintId id() const { return m_id; }
/** The actual data that is the fingerprint, this is about 70kB or so,
* there isn't anything in it until you call generate. */
QByteArray data() const { return m_data; }
enum Error
{
ReadError = 0,
/** failed to extract samplerate, bitrate, channels, duration etc */
HeadersError,
DecodeError,
/** there is a minimum track duration for fingerprinting */
TrackTooShortError,
/** the fingerprint service went wrong, or we submitted bad data,
* or myabe the request failed, whatever, we couldn't parse the
* result */
BadResponseError,
/** sorry, liblastfm sucks, report bug with log! */
InternalError
};
/** This is CPU intensive, do it in a thread in your GUI application */
void generate( FingerprintableSource* ) throw( Error );
/** Submits the fingerprint data to Last.fm in order to get a FingerprintId
* back. You need to wait for the QNetworkReply to finish before you can
* pass it to decode clearly. */
QNetworkReply* submit() const;
/** Pass a finished reply from submit(), if the response is sound, id()
* will be valid. Otherwise we will throw. You always get a valid id
* or a throw.
*/
void decode( QNetworkReply*, bool* lastfm_needs_a_complete_fingerprint = 0 ) throw( Error );
};
class CompleteFingerprint : public Fingerprint
{
public:
CompleteFingerprint( const lastfm::Track& t ) : Fingerprint( t )
{
m_complete = true;
}
};
}
inline QDebug operator<<( QDebug d, lastfm::Fingerprint::Error e )
{
#define CASE(x) case lastfm::Fingerprint::x: return d << #x;
switch (e)
{
CASE(ReadError)
CASE(HeadersError)
CASE(DecodeError)
CASE(TrackTooShortError)
CASE(BadResponseError)
CASE(InternalError)
}
#undef CASE
}
#endif

View File

@@ -0,0 +1,48 @@
/*
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 LASTFM_FINGERPRINTABLE_SOURCE_H
#define LASTFM_FINGERPRINTABLE_SOURCE_H
#include <lastfm/global.h>
#include <QString>
namespace lastfm
{
class LASTFM_FINGERPRINT_DLLEXPORT FingerprintableSource
{
public:
/** do all initialisation here and throw if there is problems */
virtual void init( const QString& path ) = 0;
virtual void getInfo( int& lengthSecs, int& samplerate, int& bitrate, int& nchannels ) = 0;
/** put a chunk of PCM data in pBuffer, don't exceed size, return the
* number of bytes you put in the buffer */
virtual int updateBuffer( signed short* buffer, size_t bufferSize ) = 0;
virtual void skip( const int mSecs ) = 0;
virtual void skipSilence( double silenceThreshold = 0.0001 ) = 0;
virtual bool eof() const = 0;
};
}
#endif

View File

@@ -0,0 +1,480 @@
/*-
* Copyright (c) 2001-2003 Allan Saddi <allan@saddi.com>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY ALLAN SADDI AND HIS CONTRIBUTORS ``AS IS''
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL ALLAN SADDI OR HIS CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*
* $Id: sha256.c 680 2003-07-25 21:57:49Z asaddi $
*/
/*
* Define WORDS_BIGENDIAN if compiling on a big-endian architecture.
*
* Define SHA256_TEST to test the implementation using the NIST's
* sample messages. The output should be:
*
* ba7816bf 8f01cfea 414140de 5dae2223 b00361a3 96177a9c b410ff61 f20015ad
* 248d6a61 d20638b8 e5c02693 0c3e6039 a33ce459 64ff2167 f6ecedd4 19db06c1
* cdc76e5c 9914fb92 81a1c7e2 84d73e67 f1809a48 a497200e 046d39cc c7112cd0
*/
#ifdef HAVE_CONFIG_H
#include <config.h>
#endif /* HAVE_CONFIG_H */
#if HAVE_INTTYPES_H
# include <inttypes.h>
#else
# if HAVE_STDINT_H
# include <stdint.h>
# endif
#endif
#include <string.h>
#include "Sha256.h"
#ifndef lint
static const char rcsid[] =
"$Id: sha256.c 680 2003-07-25 21:57:49Z asaddi $";
#endif /* !lint */
#define ROTL(x, n) (((x) << (n)) | ((x) >> (32 - (n))))
#define ROTR(x, n) (((x) >> (n)) | ((x) << (32 - (n))))
#define Ch(x, y, z) ((z) ^ ((x) & ((y) ^ (z))))
#define Maj(x, y, z) (((x) & ((y) | (z))) | ((y) & (z)))
#define SIGMA0(x) (ROTR((x), 2) ^ ROTR((x), 13) ^ ROTR((x), 22))
#define SIGMA1(x) (ROTR((x), 6) ^ ROTR((x), 11) ^ ROTR((x), 25))
#define sigma0(x) (ROTR((x), 7) ^ ROTR((x), 18) ^ ((x) >> 3))
#define sigma1(x) (ROTR((x), 17) ^ ROTR((x), 19) ^ ((x) >> 10))
#define DO_ROUND() { \
t1 = h + SIGMA1(e) + Ch(e, f, g) + *(Kp++) + *(W++); \
t2 = SIGMA0(a) + Maj(a, b, c); \
h = g; \
g = f; \
f = e; \
e = d + t1; \
d = c; \
c = b; \
b = a; \
a = t1 + t2; \
}
static const uint32_t K[64] = {
0x428a2f98L, 0x71374491L, 0xb5c0fbcfL, 0xe9b5dba5L,
0x3956c25bL, 0x59f111f1L, 0x923f82a4L, 0xab1c5ed5L,
0xd807aa98L, 0x12835b01L, 0x243185beL, 0x550c7dc3L,
0x72be5d74L, 0x80deb1feL, 0x9bdc06a7L, 0xc19bf174L,
0xe49b69c1L, 0xefbe4786L, 0x0fc19dc6L, 0x240ca1ccL,
0x2de92c6fL, 0x4a7484aaL, 0x5cb0a9dcL, 0x76f988daL,
0x983e5152L, 0xa831c66dL, 0xb00327c8L, 0xbf597fc7L,
0xc6e00bf3L, 0xd5a79147L, 0x06ca6351L, 0x14292967L,
0x27b70a85L, 0x2e1b2138L, 0x4d2c6dfcL, 0x53380d13L,
0x650a7354L, 0x766a0abbL, 0x81c2c92eL, 0x92722c85L,
0xa2bfe8a1L, 0xa81a664bL, 0xc24b8b70L, 0xc76c51a3L,
0xd192e819L, 0xd6990624L, 0xf40e3585L, 0x106aa070L,
0x19a4c116L, 0x1e376c08L, 0x2748774cL, 0x34b0bcb5L,
0x391c0cb3L, 0x4ed8aa4aL, 0x5b9cca4fL, 0x682e6ff3L,
0x748f82eeL, 0x78a5636fL, 0x84c87814L, 0x8cc70208L,
0x90befffaL, 0xa4506cebL, 0xbef9a3f7L, 0xc67178f2L
};
#ifndef RUNTIME_ENDIAN
#ifdef WORDS_BIGENDIAN
#define BYTESWAP(x) (x)
#define BYTESWAP64(x) (x)
#else /* WORDS_BIGENDIAN */
#define BYTESWAP(x) ((ROTR((x), 8) & 0xff00ff00L) | \
(ROTL((x), 8) & 0x00ff00ffL))
#define BYTESWAP64(x) _byteswap64(x)
static inline uint64_t _byteswap64(uint64_t x)
{
uint32_t a = x >> 32;
uint32_t b = (uint32_t) x;
return ((uint64_t) BYTESWAP(b) << 32) | (uint64_t) BYTESWAP(a);
}
#endif /* WORDS_BIGENDIAN */
#else /* !RUNTIME_ENDIAN */
#define BYTESWAP(x) _byteswap(sc->littleEndian, x)
#define BYTESWAP64(x) _byteswap64(sc->littleEndian, x)
#define _BYTESWAP(x) ((ROTR((x), 8) & 0xff00ff00L) | \
(ROTL((x), 8) & 0x00ff00ffL))
#define _BYTESWAP64(x) __byteswap64(x)
static inline uint64_t __byteswap64(uint64_t x)
{
uint32_t a = x >> 32;
uint32_t b = (uint32_t) x;
return ((uint64_t) _BYTESWAP(b) << 32) | (uint64_t) _BYTESWAP(a);
}
static inline uint32_t _byteswap(int littleEndian, uint32_t x)
{
if (!littleEndian)
return x;
else
return _BYTESWAP(x);
}
static inline uint64_t _byteswap64(int littleEndian, uint64_t x)
{
if (!littleEndian)
return x;
else
return _BYTESWAP64(x);
}
static inline void setEndian(int *littleEndianp)
{
union {
uint32_t w;
uint8_t b[4];
} endian;
endian.w = 1L;
*littleEndianp = endian.b[0] != 0;
}
#endif /* !RUNTIME_ENDIAN */
static const uint8_t padding[64] = {
0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
};
void
SHA256Init (SHA256Context *sc)
{
#ifdef RUNTIME_ENDIAN
setEndian (&sc->littleEndian);
#endif /* RUNTIME_ENDIAN */
sc->totalLength = 0LL;
sc->hash[0] = 0x6a09e667L;
sc->hash[1] = 0xbb67ae85L;
sc->hash[2] = 0x3c6ef372L;
sc->hash[3] = 0xa54ff53aL;
sc->hash[4] = 0x510e527fL;
sc->hash[5] = 0x9b05688cL;
sc->hash[6] = 0x1f83d9abL;
sc->hash[7] = 0x5be0cd19L;
sc->bufferLength = 0L;
}
static void
burnStack (int size)
{
char buf[128];
memset (buf, 0, sizeof (buf));
size -= sizeof (buf);
if (size > 0)
burnStack (size);
}
static void
SHA256Guts (SHA256Context *sc, const uint32_t *cbuf)
{
uint32_t buf[64];
uint32_t *W, *W2, *W7, *W15, *W16;
uint32_t a, b, c, d, e, f, g, h;
uint32_t t1, t2;
const uint32_t *Kp;
int i;
W = buf;
for (i = 15; i >= 0; i--) {
*(W++) = BYTESWAP(*cbuf);
cbuf++;
}
W16 = &buf[0];
W15 = &buf[1];
W7 = &buf[9];
W2 = &buf[14];
for (i = 47; i >= 0; i--) {
*(W++) = sigma1(*W2) + *(W7++) + sigma0(*W15) + *(W16++);
W2++;
W15++;
}
a = sc->hash[0];
b = sc->hash[1];
c = sc->hash[2];
d = sc->hash[3];
e = sc->hash[4];
f = sc->hash[5];
g = sc->hash[6];
h = sc->hash[7];
Kp = K;
W = buf;
#ifndef SHA256_UNROLL
#define SHA256_UNROLL 1
#endif /* !SHA256_UNROLL */
#if SHA256_UNROLL == 1
for (i = 63; i >= 0; i--)
DO_ROUND();
#elif SHA256_UNROLL == 2
for (i = 31; i >= 0; i--) {
DO_ROUND(); DO_ROUND();
}
#elif SHA256_UNROLL == 4
for (i = 15; i >= 0; i--) {
DO_ROUND(); DO_ROUND(); DO_ROUND(); DO_ROUND();
}
#elif SHA256_UNROLL == 8
for (i = 7; i >= 0; i--) {
DO_ROUND(); DO_ROUND(); DO_ROUND(); DO_ROUND();
DO_ROUND(); DO_ROUND(); DO_ROUND(); DO_ROUND();
}
#elif SHA256_UNROLL == 16
for (i = 3; i >= 0; i--) {
DO_ROUND(); DO_ROUND(); DO_ROUND(); DO_ROUND();
DO_ROUND(); DO_ROUND(); DO_ROUND(); DO_ROUND();
DO_ROUND(); DO_ROUND(); DO_ROUND(); DO_ROUND();
DO_ROUND(); DO_ROUND(); DO_ROUND(); DO_ROUND();
}
#elif SHA256_UNROLL == 32
for (i = 1; i >= 0; i--) {
DO_ROUND(); DO_ROUND(); DO_ROUND(); DO_ROUND();
DO_ROUND(); DO_ROUND(); DO_ROUND(); DO_ROUND();
DO_ROUND(); DO_ROUND(); DO_ROUND(); DO_ROUND();
DO_ROUND(); DO_ROUND(); DO_ROUND(); DO_ROUND();
DO_ROUND(); DO_ROUND(); DO_ROUND(); DO_ROUND();
DO_ROUND(); DO_ROUND(); DO_ROUND(); DO_ROUND();
DO_ROUND(); DO_ROUND(); DO_ROUND(); DO_ROUND();
DO_ROUND(); DO_ROUND(); DO_ROUND(); DO_ROUND();
}
#elif SHA256_UNROLL == 64
DO_ROUND(); DO_ROUND(); DO_ROUND(); DO_ROUND();
DO_ROUND(); DO_ROUND(); DO_ROUND(); DO_ROUND();
DO_ROUND(); DO_ROUND(); DO_ROUND(); DO_ROUND();
DO_ROUND(); DO_ROUND(); DO_ROUND(); DO_ROUND();
DO_ROUND(); DO_ROUND(); DO_ROUND(); DO_ROUND();
DO_ROUND(); DO_ROUND(); DO_ROUND(); DO_ROUND();
DO_ROUND(); DO_ROUND(); DO_ROUND(); DO_ROUND();
DO_ROUND(); DO_ROUND(); DO_ROUND(); DO_ROUND();
DO_ROUND(); DO_ROUND(); DO_ROUND(); DO_ROUND();
DO_ROUND(); DO_ROUND(); DO_ROUND(); DO_ROUND();
DO_ROUND(); DO_ROUND(); DO_ROUND(); DO_ROUND();
DO_ROUND(); DO_ROUND(); DO_ROUND(); DO_ROUND();
DO_ROUND(); DO_ROUND(); DO_ROUND(); DO_ROUND();
DO_ROUND(); DO_ROUND(); DO_ROUND(); DO_ROUND();
DO_ROUND(); DO_ROUND(); DO_ROUND(); DO_ROUND();
DO_ROUND(); DO_ROUND(); DO_ROUND(); DO_ROUND();
#else
#error "SHA256_UNROLL must be 1, 2, 4, 8, 16, 32, or 64!"
#endif
sc->hash[0] += a;
sc->hash[1] += b;
sc->hash[2] += c;
sc->hash[3] += d;
sc->hash[4] += e;
sc->hash[5] += f;
sc->hash[6] += g;
sc->hash[7] += h;
}
void
SHA256Update (SHA256Context *sc, const void *vdata, uint32_t len)
{
const uint8_t *data = (const uint8_t*)vdata;
uint32_t bufferBytesLeft;
uint32_t bytesToCopy;
int needBurn = 0;
#ifdef SHA256_FAST_COPY
if (sc->bufferLength) {
bufferBytesLeft = 64L - sc->bufferLength;
bytesToCopy = bufferBytesLeft;
if (bytesToCopy > len)
bytesToCopy = len;
memcpy (&sc->buffer.bytes[sc->bufferLength], data, bytesToCopy);
sc->totalLength += bytesToCopy * 8L;
sc->bufferLength += bytesToCopy;
data += bytesToCopy;
len -= bytesToCopy;
if (sc->bufferLength == 64L) {
SHA256Guts (sc, sc->buffer.words);
needBurn = 1;
sc->bufferLength = 0L;
}
}
while (len > 63L) {
sc->totalLength += 512L;
SHA256Guts (sc, data);
needBurn = 1;
data += 64L;
len -= 64L;
}
if (len) {
memcpy (&sc->buffer.bytes[sc->bufferLength], data, len);
sc->totalLength += len * 8L;
sc->bufferLength += len;
}
#else /* SHA256_FAST_COPY */
while (len) {
bufferBytesLeft = 64L - sc->bufferLength;
bytesToCopy = bufferBytesLeft;
if (bytesToCopy > len)
bytesToCopy = len;
memcpy (&sc->buffer.bytes[sc->bufferLength], data, bytesToCopy);
sc->totalLength += bytesToCopy * 8L;
sc->bufferLength += bytesToCopy;
data += bytesToCopy;
len -= bytesToCopy;
if (sc->bufferLength == 64L) {
SHA256Guts (sc, sc->buffer.words);
needBurn = 1;
sc->bufferLength = 0L;
}
}
#endif /* SHA256_FAST_COPY */
if (needBurn)
burnStack (sizeof (uint32_t[74]) + sizeof (uint32_t *[6]) + sizeof (int));
}
void
SHA256Final (SHA256Context *sc, uint8_t hash[SHA256_HASH_SIZE])
{
uint32_t bytesToPad;
uint64_t lengthPad;
int i;
bytesToPad = 120L - sc->bufferLength;
if (bytesToPad > 64L)
bytesToPad -= 64L;
lengthPad = BYTESWAP64(sc->totalLength);
SHA256Update (sc, padding, bytesToPad);
SHA256Update (sc, &lengthPad, 8L);
if (hash) {
for (i = 0; i < SHA256_HASH_WORDS; i++) {
#ifdef SHA256_FAST_COPY
*((uint32_t *) hash) = BYTESWAP(sc->hash[i]);
#else /* SHA256_FAST_COPY */
hash[0] = (uint8_t) (sc->hash[i] >> 24);
hash[1] = (uint8_t) (sc->hash[i] >> 16);
hash[2] = (uint8_t) (sc->hash[i] >> 8);
hash[3] = (uint8_t) sc->hash[i];
#endif /* SHA256_FAST_COPY */
hash += 4;
}
}
}
#ifdef SHA256_TEST
#include <stdio.h>
#include <stdlib.h>
int
main (int argc, char *argv[])
{
SHA256Context foo;
uint8_t hash[SHA256_HASH_SIZE];
char buf[1000];
int i;
SHA256Init (&foo);
SHA256Update (&foo, "abc", 3);
SHA256Final (&foo, hash);
for (i = 0; i < SHA256_HASH_SIZE;) {
printf ("%02x", hash[i++]);
if (!(i % 4))
printf (" ");
}
printf ("\n");
SHA256Init (&foo);
SHA256Update (&foo,
"abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq",
56);
SHA256Final (&foo, hash);
for (i = 0; i < SHA256_HASH_SIZE;) {
printf ("%02x", hash[i++]);
if (!(i % 4))
printf (" ");
}
printf ("\n");
SHA256Init (&foo);
memset (buf, 'a', sizeof (buf));
for (i = 0; i < 1000; i++)
SHA256Update (&foo, buf, sizeof (buf));
SHA256Final (&foo, hash);
for (i = 0; i < SHA256_HASH_SIZE;) {
printf ("%02x", hash[i++]);
if (!(i % 4))
printf (" ");
}
printf ("\n");
exit (0);
}
#endif /* SHA256_TEST */

View File

@@ -0,0 +1,180 @@
/*-
* Copyright (c) 2001-2003 Allan Saddi <allan@saddi.com>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY ALLAN SADDI AND HIS CONTRIBUTORS ``AS IS''
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL ALLAN SADDI OR HIS CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*
* $Id: sha256.h 348 2003-02-23 22:12:06Z asaddi $
*/
//
/////////// EXAMPLE /////////////////////////////////
//
// SHA256Context sha256;
// SHA256Init (&sha256);
//
// uint8_t* pBuffer = new uint8_t[SHA_BUFFER_SIZE + 7];
// // Ensure it is on a 64-bit boundary.
// INTPTR offs;
// if ((offs = reinterpret_cast<INTPTR>(pBuffer) & 7L))
// pBuffer += 8 - offs;
//
// unsigned int len;
//
// ifstream inFile("test.txt", ios::binary);
//
// for (;;)
// {
// inFile.read( reinterpret_cast<char*>(pBuffer), SHA_BUFFER_SIZE );
// len = inFile.gcount();
//
// if ( len == 0)
// break;
//
// SHA256Update (&sha256, pBuffer, len);
// }
//
// uint8_t hash[SHA256_HASH_SIZE];
// SHA256Final (&sha256, hash);
//
// cout << "Hash: ";
// for (int i = 0; i < SHA256_HASH_SIZE; ++i)
// printf ("%02x", hash[i]);
// cout << endl;
#ifndef _SHA256_H
#define _SHA256_H
// ----------------------------------------------------------------------------
// ----------------------------------------------------------------------------
/* Define to 1 if you have the <inttypes.h> header file. */
#ifndef WIN32
#define HAVE_INTTYPES_H 1
#endif
/* Define to 1 if you have the <memory.h> header file. */
#define HAVE_MEMORY_H 1
/* Define to 1 if you have the <stdint.h> header file. */
#ifndef WIN32
#define HAVE_STDINT_H 1
#endif
/* Define to 1 if you have the <stdlib.h> header file. */
#define HAVE_STDLIB_H 1
/* Define to 1 if you have the <strings.h> header file. */
#define HAVE_STRINGS_H 1
/* Define to 1 if you have the <string.h> header file. */
#define HAVE_STRING_H 1
/* Define to 1 if you have the `strerror' function. */
#ifndef WIN32
#define HAVE_STRERROR 1
#endif
/* Define to 1 if you have the <sys/stat.h> header file. */
#define HAVE_SYS_STAT_H 1
/* Define to 1 if you have the <sys/types.h> header file. */
#ifndef WIN32
#define HAVE_SYS_TYPES_H 1
#endif
/* Define to 1 if you have the <unistd.h> header file. */
#ifndef WIN32
#define HAVE_UNISTD_H 1
#endif
/* Define to 1 if you have the ANSI C header files. */
#define STDC_HEADERS 1
/* Define as `__inline' if that's what the C compiler calls it, or to nothing
if it is not supported. */
#ifdef WIN32
#define inline __inline
#endif
/* Define to `unsigned' if <sys/types.h> does not define. */
/* #undef size_t */
#ifdef WIN32
#define uint64_t unsigned __int64
#define uint32_t unsigned int
#define uint8_t unsigned char
#endif // WIN32
#ifdef WIN32
#define INTPTR intptr_t
#else
#define INTPTR long
#endif
#define SHA_BUFFER_SIZE 65536
// ----------------------------------------------------------------------------
// ----------------------------------------------------------------------------
#if HAVE_INTTYPES_H
# include <inttypes.h>
#else
# if HAVE_STDINT_H
# include <stdint.h>
# endif
#endif
#define SHA256_HASH_SIZE 32
/* Hash size in 32-bit words */
#define SHA256_HASH_WORDS 8
struct _SHA256Context {
uint64_t totalLength;
uint32_t hash[SHA256_HASH_WORDS];
uint32_t bufferLength;
union {
uint32_t words[16];
uint8_t bytes[64];
} buffer;
#ifdef RUNTIME_ENDIAN
int littleEndian;
#endif /* RUNTIME_ENDIAN */
};
typedef struct _SHA256Context SHA256Context;
#ifdef __cplusplus
extern "C" {
#endif
void SHA256Init (SHA256Context *sc);
void SHA256Update (SHA256Context *sc, const void *data, uint32_t len);
void SHA256Final (SHA256Context *sc, uint8_t hash[SHA256_HASH_SIZE]);
#ifdef __cplusplus
}
#endif
#endif /* !_SHA256_H */

View File

@@ -0,0 +1,953 @@
/*
Copyright 2009 Last.fm Ltd.
Copyright 2009 John Stamp <jstamp@users.sourceforge.net>
Portions Copyright 2003-2005 M. Bakker, Nero AG, http://www.nero.com
- Adapted from main.c found in the FAAD2 source tarball.
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 "AacSource.h"
#include "AacSource_p.h"
#include <QFile>
#include <algorithm>
#include <cassert>
#include <limits>
#include <iostream>
#include <stdexcept>
#include <errno.h>
#include <string.h>
////////////////////////////////////////////////////////////////////////
//
// AAC_File
//
////////////////////////////////////////////////////////////////////////
AAC_File::AAC_File(const QString& fileName, int headerType)
: m_fileName(fileName)
, m_inBuf(NULL)
, m_inBufSize(0)
, m_decoder(0)
, m_overflow(static_cast<unsigned char*>(malloc( sizeof(unsigned char) * 1024 )))
, m_overflowSize(0)
, m_header(headerType)
{
}
AAC_File::~AAC_File()
{
// common
if ( m_decoder )
{
NeAACDecClose( m_decoder );
m_decoder = NULL;
}
if ( m_inBuf )
{
free( m_inBuf );
m_inBufSize = 0;
m_inBuf = NULL;
}
if ( m_overflow )
{
free( m_overflow );
m_overflowSize = 0;
m_overflow = NULL;
}
}
////////////////////////////////////////////////////////////////////////
//
// AAC with ADTS or ADIF headers
//
////////////////////////////////////////////////////////////////////////
#define MAX_CHANNELS 6 // Output will get mixed down to 2 channels
#define ADTS_HEADER_SIZE 8
static int adts_sample_rates[] =
{
96000,
88200,
64000,
48000,
44100,
32000,
24000,
22050,
16000,
12000,
11025,
8000,
7350,
0,
0,
0
};
AAC_ADTS_File::AAC_ADTS_File( const QString& fileName, int headerType ) : AAC_File(fileName, headerType)
, m_file( NULL )
, m_adifSamplerate( 0 )
, m_adifChannels( 0 )
{
}
AAC_ADTS_File::~AAC_ADTS_File()
{
if ( m_file )
{
fclose( m_file );
}
}
void AAC_ADTS_File::fillBuffer( FILE*& fp, unsigned char*& buf, size_t& bufSize, const size_t bytesConsumed )
{
size_t bread;
if ( bytesConsumed > 0 )
{
if ( bufSize )
memmove( (void*)buf, (void*)(buf + bytesConsumed), bufSize*sizeof(unsigned char) );
bread = fread( (void*)(buf + bufSize), 1, bytesConsumed, fp );
bufSize += bread;
if ( bufSize > 3 )
{
if ( memcmp( buf, "TAG", 3 ) == 0 )
bufSize = 0;
}
if ( bufSize > 11 )
{
if ( memcmp( buf, "LYRICSBEGIN", 11 ) == 0 )
bufSize = 0;
}
if ( bufSize > 8 )
{
if ( memcmp( buf, "APETAGEX", 8 ) == 0 )
bufSize = 0;
}
}
}
void AAC_ADTS_File::parse( FILE*& fp, unsigned char*& buf, size_t& bufSize, int &bitrate, double &length )
{
unsigned int frames, frame_length = 0;
int t_framelength = 0;
int samplerate = 0;
double frames_per_sec, bytes_per_frame;
// Read all frames to ensure correct time and bitrate
for ( frames = 0; /* */; frames++ )
{
fillBuffer( fp, buf, bufSize, frame_length );
if ( bufSize > 7 )
{
/* check syncword */
if ( !( (buf[0] == 0xFF) && ((buf[1] & 0xF6) == 0xF0) ) )
break;
if ( frames == 0 )
samplerate = adts_sample_rates[ (buf[2] & 0x3c) >> 2 ];
frame_length = ( ((buf[3] & 0x3) << 11)
| ((buf[4]) << 3)
| (buf[5] >> 5) );
t_framelength += frame_length - ADTS_HEADER_SIZE;
if ( frame_length > bufSize )
break;
bufSize -= frame_length;
}
else
{
break;
}
}
frames_per_sec = samplerate / 1024.0;
if ( frames != 0 )
bytes_per_frame = t_framelength / frames;
else
bytes_per_frame = 0;
bitrate = static_cast<int>(8 * bytes_per_frame * frames_per_sec + 0.5);
if ( frames_per_sec != 0 )
length = frames / frames_per_sec;
else
length = 1;
}
int32_t AAC_ADTS_File::commonSetup( FILE*& fp, NeAACDecHandle& decoder, unsigned char*& buf, size_t& bufSize, uint32_t& samplerate, uint8_t& channels )
{
samplerate = 0;
channels = 0;
fp = fopen(QFile::encodeName(m_fileName), "rb" );
if( !fp )
{
std::cerr << "ERROR: Failed to open " << strerror( errno ) << std::endl;
return -1;
}
if ( !(buf = static_cast<unsigned char*>( malloc(FAAD_MIN_STREAMSIZE*MAX_CHANNELS)) ) )
{
std::cerr << "Memory allocation error" << std::endl;
fclose ( fp );
return -1;
}
memset( buf, 0, FAAD_MIN_STREAMSIZE*MAX_CHANNELS );
bufSize = fread( buf, 1, FAAD_MIN_STREAMSIZE * MAX_CHANNELS, fp );
int tagsize = 0;
if ( !memcmp( buf, "ID3", 3 ) )
{
/* high bit is not used */
tagsize = (buf[6] << 21) | (buf[7] << 14) |
(buf[8] << 7) | (buf[9] << 0);
tagsize += 10;
bufSize -= tagsize;
fillBuffer( fp, buf, bufSize, tagsize );
}
decoder = NeAACDecOpen();
/* Set configuration */
NeAACDecConfigurationPtr config;
config = NeAACDecGetCurrentConfiguration(decoder);
config->outputFormat = FAAD_FMT_16BIT;
config->downMatrix = 1; // Turn 5.1 channels into 2
NeAACDecSetConfiguration( decoder, config);
int32_t initval = 0;
if ((initval = NeAACDecInit(decoder, buf,
FAAD_MIN_STREAMSIZE*MAX_CHANNELS, &samplerate, &channels)) < 0)
{
std::cerr << "Error: could not set up AAC decoder" << std::endl;
if ( buf )
free( buf );
buf = NULL;
NeAACDecClose( decoder );
decoder = NULL;
fclose( fp );
fp = NULL;
}
return initval;
}
bool AAC_ADTS_File::init()
{
uint32_t initSamplerate = 0;
uint8_t initChannels = 0;
int32_t initval = commonSetup( m_file, m_decoder, m_inBuf, m_inBufSize, initSamplerate, initChannels );
if ( initval >= 0 )
{
m_inBufSize -= initval;
fillBuffer( m_file, m_inBuf, m_inBufSize, initval );
// These two only needed for skipping AAC ADIF files
m_adifSamplerate = initSamplerate;
m_adifChannels = initChannels;
return true;
}
throw std::runtime_error( "ERROR: Could not initialize AAC file reader!" );
return false;
}
/*QString AAC_ADTS_File::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 AAC_ADTS_File::getInfo( int& lengthSecs, int& samplerate, int& bitrate, int& nchannels )
{
long fileread;
uint32_t initSamplerate;
uint8_t initChannels;
double initLength = 0;
unsigned char *tempBuf = NULL;
size_t tempBufSize;
FILE *fp = NULL;
NeAACDecHandle decoder = NULL;
commonSetup( fp, decoder, tempBuf, tempBufSize, initSamplerate, initChannels );
long origpos = ftell( fp );
fseek( fp, 0, SEEK_END );
fileread = ftell( fp );
fseek( fp, origpos, SEEK_SET );
if ( (tempBuf[0] == 0xFF) && ((tempBuf[1] & 0xF6) == 0xF0) )
{
parse( fp, tempBuf, tempBufSize, bitrate, initLength );
}
else if (memcmp(tempBuf, "ADIF", 4) == 0)
{
int skip_size = (tempBuf[4] & 0x80) ? 9 : 0;
bitrate = ((tempBuf[4 + skip_size] & 0x0F)<<19) |
(tempBuf[5 + skip_size]<<11) |
(tempBuf[6 + skip_size]<<3) |
(tempBuf[7 + skip_size] & 0xE0);
if ( fileread != 0)
{
initLength = static_cast<double>(fileread) * 8 / bitrate + 0.5;
}
}
lengthSecs = static_cast<int>(initLength);
nchannels = initChannels;
samplerate = initSamplerate;
if ( decoder )
NeAACDecClose( decoder );
if ( fp )
fclose( fp );
if ( tempBuf )
free( tempBuf );
}
void AAC_ADTS_File::skip( const int mSecs )
{
if ( m_header == AAC_ADTS )
{
// As AAC is VBR we need to check all ADTS headers to enable seeking...
// There is no other solution
unsigned char header[8];
unsigned int frameCount, frameLength;
double seconds = 0;
// We need to find the ATDS syncword so rewind to the beginning
// of the unprocessed data.
if ( m_inBufSize > 0 )
{
fseek ( m_file, -m_inBufSize, SEEK_CUR );
m_inBufSize = 0;
}
for( frameCount = 1; seconds * 1000 < mSecs; frameCount++ )
{
if ( fread( header, 1, ADTS_HEADER_SIZE, m_file ) != ADTS_HEADER_SIZE )
{
break;
}
if ( !strncmp( (char*)header, "ID3", 3 ) )
{
// high bit is not used
unsigned char rest[2];
fread( rest, 1, 2, m_file );
int tagsize = (header[6] << 21) | (header[7] << 14) |
(rest[0] << 7) | (rest[1] << 0);
fseek( m_file, tagsize, SEEK_CUR );
fread( header, 1, ADTS_HEADER_SIZE, m_file );
}
if ( !((header[0] == 0xFF) && ((header[1] & 0xF6) == 0xF0)) )
{
std::cerr << "Error: Bad frame header; file may be corrupt!" << std::endl;
break;
}
int samplerate = adts_sample_rates[ (header[2] & 0x3c) >> 2 ];
frameLength = ( ( header[3] & 0x3 ) << 11 )
| ( header[4] << 3 )
| ( header[5] >> 5 );
if ( samplerate > 0 )
seconds += 1024.0 / samplerate;
else
{
std::cerr << "Error: Bad frame header; file may be corrupt!" << std::endl;
break;
}
if ( fseek( m_file, frameLength - ADTS_HEADER_SIZE, SEEK_CUR ) == -1 )
break;
}
m_inBufSize = fread( m_inBuf, 1, FAAD_MIN_STREAMSIZE * MAX_CHANNELS, m_file );
}
else if ( m_header == AAC_ADIF )
{
// AAC ADIF is even worse. There's only the one header at the
// beginning of the file. If you want to skip forward, you have to
// decode block by block and check how far along you are. Lovely, eh?
unsigned long totalSamples = 0;
void *sampleBuffer = NULL;
do
{
NeAACDecFrameInfo frameInfo;
sampleBuffer = NeAACDecDecode(m_decoder, &frameInfo, m_inBuf, static_cast<uint32_t>(m_inBufSize) );
totalSamples += frameInfo.samples;
if ( frameInfo.bytesconsumed > 0 )
{
m_inBufSize -= frameInfo.bytesconsumed;
fillBuffer( m_file, m_inBuf, m_inBufSize, frameInfo.bytesconsumed );
}
if ( totalSamples >= ( mSecs * m_adifSamplerate * m_adifChannels / 1000 ) )
break;
} while ( sampleBuffer != NULL );
}
}
void AAC_ADTS_File::postDecode(unsigned long bytesConsumed)
{
m_inBufSize -= bytesConsumed;
fillBuffer( m_file, m_inBuf, m_inBufSize, bytesConsumed );
}
////////////////////////////////////////////////////////////////////////
//
// AAC in an MP4 wrapper
//
////////////////////////////////////////////////////////////////////////
uint32_t read_callback( void *user_data, void *buffer, uint32_t length )
{
return static_cast<uint32_t>(fread( buffer, 1, length, static_cast<FILE*>(user_data) ));
}
uint32_t seek_callback( void *user_data, uint64_t position )
{
return fseek( static_cast<FILE*>(user_data), static_cast<long>(position), SEEK_SET );
}
AAC_MP4_File::AAC_MP4_File( const QString& fileName, int headerType ) : AAC_File(fileName, headerType)
, m_mp4AudioTrack( -1 )
, m_mp4SampleId( 0 )
, m_mp4File ( NULL )
, m_mp4cb ( NULL )
{
}
int32_t AAC_MP4_File::readSample()
{
unsigned int bsize;
int32_t rc = mp4ff_read_sample( m_mp4File, m_mp4AudioTrack, m_mp4SampleId, &m_inBuf, &bsize );
m_inBufSize = bsize;
// Not necessarily an error. Could just mean end of file.
//if ( rc == 0 )
// std::cerr << "Reading samples failed." << std::endl;
return rc;
}
int32_t AAC_MP4_File::getTrack( const mp4ff_t *f )
{
// find AAC track
int32_t numTracks = mp4ff_total_tracks( f );
for ( int32_t i = 0; i < numTracks; i++ )
{
unsigned char *buff = NULL;
unsigned int buff_size = 0;
mp4AudioSpecificConfig mp4ASC;
mp4ff_get_decoder_config( f, i, &buff, &buff_size );
if ( buff )
{
int8_t rc = NeAACDecAudioSpecificConfig( buff, buff_size, &mp4ASC );
free( buff );
if ( rc < 0 )
continue;
return i;
}
}
// can't decode this, probably DRM
return -1;
}
bool AAC_MP4_File::commonSetup( NeAACDecHandle& decoder, mp4ff_callback_t*& cb, FILE*& fp, mp4ff_t*& mp4, int32_t& audioTrack )
{
fp = fopen(QFile::encodeName(m_fileName), "rb");
if ( !fp )
{
throw std::runtime_error( "Error: failed to open AAC file!" );
return false;
}
decoder = NeAACDecOpen();
// Set configuration
NeAACDecConfigurationPtr config;
config = NeAACDecGetCurrentConfiguration( decoder );
config->outputFormat = FAAD_FMT_16BIT;
config->downMatrix = 1; // Turn 5.1 channels into 2
NeAACDecSetConfiguration( decoder, config );
// initialise the callback structure
cb = static_cast<mp4ff_callback_t*>( malloc( sizeof(mp4ff_callback_t) ) );
cb->read = read_callback;
cb->seek = seek_callback;
cb->user_data = fp;
mp4 = mp4ff_open_read( cb );
if ( !mp4 )
{
// unable to open file
free( cb );
cb = NULL;
NeAACDecClose( decoder );
decoder = NULL;
fclose( fp );
fp = NULL;
throw std::runtime_error( "Error: failed to set up AAC decoder!" );
return false;
}
if ( ( audioTrack = getTrack( mp4 )) < 0 )
{
free( cb );
cb = NULL;
NeAACDecClose( decoder );
decoder = NULL;
fclose( fp );
fp = NULL;
mp4ff_close( mp4 );
mp4 = NULL;
audioTrack = 0;
throw std::runtime_error( "Error: Unable to find an audio track. Is the file DRM protected?" );
return false;
}
return true;
}
/*QString AAC_MP4_File::getMbid()
{
int j = mp4ff_meta_get_num_items( m_mp4File );
if ( j > 0 )
{
int k;
for ( k = 0; k < j; k++ )
{
char *tag = NULL, *item = NULL;
if ( mp4ff_meta_get_by_index( m_mp4File, k, &item, &tag ) )
{
if ( item != NULL && tag != NULL )
{
QString key(item);
if ( key.toLower() == "musicbrainz track id" )
{
QString ret(tag);
free( item );
free( tag );
return ret;
}
free( item );
free( tag );
}
}
}
}
return QString();
}*/
void AAC_MP4_File::getInfo( int& lengthSecs, int& samplerate, int& bitrate, int& nchannels )
{
FILE* fp = NULL;
mp4ff_callback_t *cb = NULL;
NeAACDecHandle decoder = NULL;
mp4ff_t* mp4 = NULL;
int32_t audioTrack;
bool success = commonSetup( decoder, cb, fp, mp4, audioTrack );
if ( success )
{
// get basic file info
mp4AudioSpecificConfig mp4ASC;
unsigned char* buffer = NULL;
unsigned int buffer_size = 0;
double f = 1024.0;
unsigned int framesize = 1024;
int32_t samples = mp4ff_num_samples( mp4, audioTrack );
if ( buffer )
{
if ( NeAACDecAudioSpecificConfig(buffer, buffer_size, &mp4ASC) >= 0 )
{
if ( mp4ASC.frameLengthFlag == 1 )
framesize = 960;
if ( mp4ASC.sbr_present_flag == 1 )
framesize *= 2;
if ( mp4ASC.sbr_present_flag == 1 )
f = f * 2.0;
}
free( buffer );
}
samplerate = mp4ff_get_sample_rate( mp4, audioTrack );
if ( samplerate > 0 )
lengthSecs = static_cast<int>(samples * f / samplerate + 0.5);
bitrate = mp4ff_get_avg_bitrate( mp4, audioTrack );
nchannels = mp4ff_get_channel_count( mp4, audioTrack );
mp4ff_close( mp4 );
NeAACDecClose( decoder );
free( cb );
fclose( fp );
}
}
bool AAC_MP4_File::init()
{
FILE* fp = NULL;
bool success = commonSetup( m_decoder, m_mp4cb, fp, m_mp4File, m_mp4AudioTrack );
if ( !success )
return false;
unsigned char* buffer = NULL;
unsigned int buffer_size = 0;
uint32_t samplerate;
uint8_t channels;
mp4ff_get_decoder_config( m_mp4File, m_mp4AudioTrack, &buffer, &buffer_size );
if( NeAACDecInit2( m_decoder, buffer, buffer_size, &samplerate, &channels) < 0 )
{
// If some error initializing occured, skip the file
if ( fp )
fclose( fp );
throw std::runtime_error( "Error: unable to initialize AAC decoder library!" );
return false;
}
if ( buffer )
free( buffer );
return true;
}
void AAC_MP4_File::postDecode(unsigned long)
{
free( m_inBuf );
m_inBuf = NULL;
m_mp4SampleId++;
}
void AAC_MP4_File::skip( const int mSecs )
{
double dur = 0.0;
int f = 1;
unsigned char *buff = NULL;
unsigned int buff_size = 0;
uint32_t totalSamples = mp4ff_num_samples( m_mp4File, m_mp4AudioTrack );
mp4AudioSpecificConfig mp4ASC;
mp4ff_get_decoder_config( m_mp4File, m_mp4AudioTrack, &buff, &buff_size );
if ( buff )
{
int8_t rc = NeAACDecAudioSpecificConfig( buff, buff_size, &mp4ASC );
free( buff );
if ( rc >= 0 && mp4ASC.sbr_present_flag == 1 )
f = 2;
// I think the f multiplier is needed here.
while ( dur * 1000.0 * f / static_cast<double>(mp4ASC.samplingFrequency) < mSecs && m_mp4SampleId < totalSamples )
{
dur += mp4ff_get_sample_duration( m_mp4File, m_mp4AudioTrack, m_mp4SampleId );
m_mp4SampleId++;
}
}
else
std::cerr << "Error: could not skip " << mSecs << " milliseconds" << std::endl;
}
AAC_MP4_File::~AAC_MP4_File()
{
if ( m_mp4File )
mp4ff_close( m_mp4File );
if ( m_mp4cb )
{
free( m_mp4cb );
}
}
////////////////////////////////////////////////////////////////////////
//
// AacSource
//
////////////////////////////////////////////////////////////////////////
AacSource::AacSource()
: m_eof( false )
, m_aacFile( NULL )
{}
AacSource::~AacSource()
{
delete m_aacFile;
}
int AacSource::checkHeader()
{
FILE *fp = NULL;
unsigned char header[10];
// check for mp4 file
fp = fopen(QFile::encodeName(m_fileName), "rb");
if ( !fp )
{
std::cerr << "Error: failed to open " << strerror( errno ) << std::endl;
return AAC_File::AAC_UNKNOWN;
}
fread( header, 1, 10, fp );
// MP4 headers
if ( !memcmp( &header[4], "ftyp", 4 ) )
{
fclose( fp );
return AAC_File::AAC_MP4;
}
// Skip id3 tags
int tagsize = 0;
if ( !memcmp( header, "ID3", 3 ) )
{
/* high bit is not used */
tagsize = (header[6] << 21) | (header[7] << 14) |
(header[8] << 7) | (header[9] << 0);
tagsize += 10;
fseek( fp, tagsize, SEEK_SET );
fread( header, 1, 10, fp );
}
// Check for ADTS OR ADIF headers
if ( (header[0] == 0xFF) && ((header[1] & 0xF6) == 0xF0) )
{
fclose( fp );
return AAC_File::AAC_ADTS;
}
else if (memcmp(header, "ADIF", 4) == 0)
{
fclose( fp );
return AAC_File::AAC_ADIF;
}
fclose( fp );
return AAC_File::AAC_UNKNOWN;
}
void AacSource::getInfo( int& lengthSecs, int& samplerate, int& bitrate, int& nchannels )
{
// get the header plus some other stuff..
m_aacFile->getInfo( lengthSecs, samplerate, bitrate, nchannels );
}
void AacSource::init(const QString& fileName)
{
m_fileName = fileName;
int headerType = checkHeader();
if ( headerType != AAC_File::AAC_UNKNOWN )
{
if ( headerType == AAC_File::AAC_MP4 )
m_aacFile = new AAC_MP4_File(m_fileName, headerType);
else
m_aacFile = new AAC_ADTS_File( m_fileName, headerType );
}
if ( m_aacFile )
m_aacFile->init();
else
throw std::runtime_error( "ERROR: No suitable AAC decoder found!" );
}
/*QString AacSource::getMbid()
{
QString mbid = m_aacFile->getMbid();
return mbid;
}*/
void AacSource::skip( const int mSecs )
{
if ( mSecs < 0 || !m_aacFile->m_decoder )
return;
m_aacFile->skip( mSecs );
}
void AacSource::skipSilence(double silenceThreshold /* = 0.0001 */)
{
if ( !m_aacFile->m_decoder )
return;
silenceThreshold *= static_cast<double>( std::numeric_limits<short>::max() );
for (;;)
{
if ( m_aacFile->m_header == AAC_File::AAC_MP4 )
{
if ( !static_cast<AAC_MP4_File*>(m_aacFile)->readSample() )
break;
}
NeAACDecFrameInfo frameInfo;
void* sampleBuffer = NeAACDecDecode(m_aacFile->m_decoder, &frameInfo, m_aacFile->m_inBuf, static_cast<uint32_t>(m_aacFile->m_inBufSize) );
m_aacFile->postDecode( frameInfo.bytesconsumed );
if ( frameInfo.error > 0 )
{
break;
}
else if ( frameInfo.samples > 0 )
{
double sum = 0;
int16_t *buf = static_cast<int16_t*>(sampleBuffer);
switch ( frameInfo.channels )
{
case 1:
for (size_t j = 0; j < frameInfo.samples; ++j)
sum += abs( buf[j] );
break;
case 2:
for (size_t j = 0; j < frameInfo.samples; j+=2)
sum += abs( (buf[j] >> 1) + (buf[j+1] >> 1) );
break;
}
if ( (sum >= silenceThreshold * static_cast<short>(frameInfo.samples/frameInfo.channels) ) )
break;
}
}
}
int AacSource::updateBuffer( signed short *pBuffer, size_t bufferSize )
{
size_t nwrit = 0; //number of samples written to the output buffer
if ( m_aacFile->m_overflowSize > 0 )
{
size_t samples_to_use = bufferSize < m_aacFile->m_overflowSize ? bufferSize : m_aacFile->m_overflowSize;
memcpy( pBuffer, m_aacFile->m_overflow, samples_to_use * sizeof(signed short) );
nwrit += samples_to_use;
m_aacFile->m_overflowSize -= samples_to_use;
memmove( (void*)(m_aacFile->m_overflow), (void*)(m_aacFile->m_overflow + samples_to_use*sizeof(signed short)), samples_to_use*sizeof(signed short) );
}
if ( !m_aacFile->m_decoder )
return 0;
for (;;)
{
signed short* pBufferIt = pBuffer + nwrit;
void* sampleBuffer;
assert( nwrit <= bufferSize );
if ( m_aacFile->m_header == AAC_File::AAC_MP4 )
{
if ( !static_cast<AAC_MP4_File*>(m_aacFile)->readSample() )
{
m_eof = true;
return static_cast<int>(nwrit);
}
}
NeAACDecFrameInfo frameInfo;
sampleBuffer = NeAACDecDecode(m_aacFile->m_decoder, &frameInfo, m_aacFile->m_inBuf, static_cast<uint32_t>(m_aacFile->m_inBufSize) );
size_t samples_to_use = (bufferSize - nwrit) < frameInfo.samples ? bufferSize-nwrit : frameInfo.samples;
if ( samples_to_use > 0 && sampleBuffer != NULL )
{
memcpy( pBufferIt, sampleBuffer, samples_to_use * sizeof(signed short) );
nwrit += samples_to_use;
}
if ( samples_to_use < frameInfo.samples )
{
m_aacFile->m_overflow = static_cast<unsigned char*>(realloc( m_aacFile->m_overflow, (frameInfo.samples - samples_to_use) * sizeof(signed short) ) );
memcpy( m_aacFile->m_overflow, static_cast<signed short*>(sampleBuffer) + samples_to_use, (frameInfo.samples - samples_to_use) * sizeof(signed short) );
m_aacFile->m_overflowSize = frameInfo.samples - samples_to_use;
}
m_aacFile->postDecode( frameInfo.bytesconsumed );
if ( sampleBuffer == NULL )
{
m_eof = true;
break;
}
if ( frameInfo.error > 0 )
{
std::cerr << "Error: " << NeAACDecGetErrorMessage(frameInfo.error) << std::endl;
break;
}
if ( nwrit == bufferSize )
break;
}
return static_cast<int>(nwrit);
}

View File

@@ -0,0 +1,46 @@
/*
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 __AAC_SOURCE_H__
#define __AAC_SOURCE_H__
#include <lastfm/FingerprintableSource>
class AacSource : public lastfm::FingerprintableSource
{
public:
AacSource();
~AacSource();
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_eof; }
private:
int checkHeader();
QString m_fileName;
bool m_eof;
class AAC_File *m_aacFile;
};
#endif

View File

@@ -0,0 +1,94 @@
/*
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 <faad.h>
#include <mp4ff.h>
class AAC_File
{
public:
AAC_File(const QString&, int headerType);
virtual ~AAC_File();
virtual void getInfo( int& lengthSecs, int& samplerate, int& bitrate, int& nchannels ) = 0;
virtual bool init() = 0;
//virtual QString getMbid() = 0;
virtual void skip( const int mSecs ) = 0;
virtual void postDecode(unsigned long) = 0;
enum HeaderType
{
AAC_UNKNOWN = 0,
AAC_ADIF,
AAC_ADTS,
AAC_MP4
};
QString m_fileName;
unsigned char *m_inBuf;
size_t m_inBufSize;
NeAACDecHandle m_decoder;
unsigned char *m_overflow;
size_t m_overflowSize;
int m_header;
};
class AAC_MP4_File : public AAC_File
{
public:
AAC_MP4_File(const QString&, int headerType = AAC_MP4 );
~AAC_MP4_File();
virtual void getInfo( int& lengthSecs, int& samplerate, int& bitrate, int& nchannels );
virtual bool init();
//virtual QString getMbid();
virtual void skip( const int mSecs );
virtual void postDecode(unsigned long);
int32_t readSample();
private:
bool commonSetup( NeAACDecHandle& handle, mp4ff_callback_t*& cb, FILE*& fp, mp4ff_t*& mp4, int32_t& audioTrack );
virtual int32_t getTrack( const mp4ff_t* f );
int m_mp4AudioTrack;
uint32_t m_mp4SampleId;
mp4ff_t *m_mp4File;
mp4ff_callback_t *m_mp4cb;
};
class AAC_ADTS_File : public AAC_File
{
public:
AAC_ADTS_File( const QString& fileName, int headerType );
~AAC_ADTS_File();
virtual void getInfo( int& lengthSecs, int& samplerate, int& bitrate, int& nchannels );
virtual bool init();
//virtual QString getMbid();
virtual void skip( const int mSecs );
virtual void postDecode(unsigned long bytesconsumed );
private:
int32_t commonSetup( FILE*& fp, NeAACDecHandle& decoder, unsigned char*& buf, size_t& bufSize, uint32_t& samplerate, uint8_t& channels );
void parse( FILE*& fp, unsigned char*& buf, size_t& bufSize, int &bitrate, double &length );
void fillBuffer( FILE*& fp, unsigned char*& buf, size_t& bufSize, const size_t m_bytesConsumed );
FILE* m_file;
// These two only needed for skipping AAC ADIF files
uint32_t m_adifSamplerate;
int m_adifChannels;
};

View File

@@ -0,0 +1,339 @@
/*
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 "FlacSource.h"
#include <algorithm>
#include <cassert>
#include <errno.h>
#include <iostream>
#include <limits>
#include <algorithm>
#include <stdexcept>
#include <QFile>
FLAC__StreamDecoderWriteStatus FlacSource::_write_callback(const FLAC__StreamDecoder *, const FLAC__Frame *frame, const FLAC__int32 * const buffer[], void *client_data)
{
assert(client_data != NULL);
FlacSource *instance = reinterpret_cast<FlacSource *>(client_data);
assert(instance != NULL);
return instance->write_callback(frame, buffer);
}
FLAC__StreamDecoderWriteStatus FlacSource::write_callback(const FLAC__Frame *frame, const FLAC__int32 * const buffer[])
{
m_outBufLen = 0;
if ( m_outBuf )
{
size_t i;
for(i = 0; i < frame->header.blocksize; i++)
{
switch ( m_channels )
{
case 1:
m_outBuf[m_outBufLen] = (FLAC__int16)buffer[0][i]; // mono
m_outBufLen++;
break;
case 2:
m_outBuf[m_outBufLen] = (FLAC__int16)buffer[0][i]; // left channel
m_outBuf[m_outBufLen+1] = (FLAC__int16)buffer[1][i]; // right channel
m_outBufLen += 2;
break;
}
}
m_samplePos += frame->header.blocksize;
}
return FLAC__STREAM_DECODER_WRITE_STATUS_CONTINUE;
}
// ---------------------------------------------------------------------
void FlacSource::_metadata_callback(const FLAC__StreamDecoder *, const FLAC__StreamMetadata *metadata, void *client_data)
{
assert(client_data != NULL);
FlacSource *instance = reinterpret_cast<FlacSource *>(client_data);
assert(instance != NULL);
instance->metadata_callback(metadata);
}
void FlacSource::metadata_callback( const FLAC__StreamMetadata *metadata )
{
switch ( metadata->type )
{
case FLAC__METADATA_TYPE_STREAMINFO:
m_channels = metadata->data.stream_info.channels;
m_totalSamples = metadata->data.stream_info.total_samples;
m_samplerate = metadata->data.stream_info.sample_rate;
m_bps = metadata->data.stream_info.bits_per_sample;
m_maxFrameSize = metadata->data.stream_info.max_framesize;
break;
case FLAC__METADATA_TYPE_VORBIS_COMMENT:
m_commentData = FLAC__metadata_object_clone(metadata);
break;
default:
break;
}
}
// ---------------------------------------------------------------------
void FlacSource::_error_callback(const FLAC__StreamDecoder *, FLAC__StreamDecoderErrorStatus status, void *client_data)
{
assert(client_data != NULL);
FlacSource *instance = reinterpret_cast<FlacSource *>(client_data);
assert(instance != NULL);
instance->error_callback(status);
}
void FlacSource::error_callback(FLAC__StreamDecoderErrorStatus status)
{
std::cerr << "Got FLAC error: " << FLAC__StreamDecoderErrorStatusString[status] << std::endl;
}
// ---------------------------------------------------------------------
FlacSource::FlacSource()
: m_decoder( 0 )
, m_fileSize( 0 )
, m_outBuf( 0 )
, m_outBufLen( 0 )
, m_outBufPos( 0 )
, m_samplePos( 0 )
, m_maxFrameSize( 0 )
, m_commentData( 0 )
, m_bps( 0 )
, m_channels( 0 )
, m_samplerate( 0 )
, m_totalSamples( 0 )
, m_eof( false )
{
}
// ---------------------------------------------------------------------
FlacSource::~FlacSource()
{
if ( m_decoder )
{
FLAC__stream_decoder_finish( m_decoder );
FLAC__stream_decoder_delete( m_decoder );
}
if ( m_commentData )
FLAC__metadata_object_delete( m_commentData );
if ( m_outBuf )
free( m_outBuf );
}
// ---------------------------------------------------------------------
void FlacSource::init(const QString& fileName)
{
m_fileName = fileName;
if ( !m_decoder )
{
FILE *f = fopen(QFile::encodeName(m_fileName), "rb" );
if ( f )
{
// Need to check which init call to use; flac doesn't do that for us
unsigned char header[35];
bool isOgg = false;
fread( header, 1, 35, f );
if ( memcmp(header, "OggS", 4) == 0 &&
memcmp(&header[29], "FLAC", 4) == 0 )
isOgg = true;
// getInfo() will need this to calculate bitrate
fseek( f, 0, SEEK_END );
m_fileSize = ftell(f);
rewind( f );
m_decoder = FLAC__stream_decoder_new();
FLAC__stream_decoder_set_metadata_respond(m_decoder, FLAC__METADATA_TYPE_STREAMINFO);
FLAC__stream_decoder_set_metadata_respond(m_decoder, FLAC__METADATA_TYPE_VORBIS_COMMENT);
int init_status;
if ( FLAC_API_SUPPORTS_OGG_FLAC && isOgg )
init_status = FLAC__stream_decoder_init_ogg_FILE( m_decoder, f, _write_callback, _metadata_callback, _error_callback, this );
else
init_status = FLAC__stream_decoder_init_FILE( m_decoder, f, _write_callback, _metadata_callback, _error_callback, this );
if(init_status != FLAC__STREAM_DECODER_INIT_STATUS_OK)
return;
FLAC__stream_decoder_process_until_end_of_metadata( m_decoder );
m_outBuf = static_cast<signed short*>(malloc( sizeof(signed short)*m_maxFrameSize));
if ( m_bps != 16 )
{
FLAC__stream_decoder_finish( m_decoder );
FLAC__stream_decoder_delete( m_decoder );
FLAC__metadata_object_delete( m_commentData );
m_decoder = 0;
m_commentData = 0;
throw std::runtime_error( "ERROR: only 16 bit FLAC files are currently supported!" );
}
}
else
throw std::runtime_error( "ERROR: cannot load FLAC file!" );
}
}
// ---------------------------------------------------------------------
/*QString FlacSource::getMbid()
{
if ( m_commentData )
{
FLAC__StreamMetadata_VorbisComment *vc;
vc = &m_commentData->data.vorbis_comment;
for ( unsigned int i = 0; i < vc->num_comments; ++i )
{
QByteArray key( (char*)(vc->comments[i].entry), vc->comments[i].length );
if ( key.left(20).toLower() == "musicbrainz_trackid=" )
{
QString val = key.mid(20);
return val;
}
}
}
return QString();
}*/
// ---------------------------------------------------------------------
void FlacSource::getInfo(int& lengthSecs, int& samplerate, int& bitrate, int& nchannels)
{
lengthSecs = 0;
samplerate = 0;
bitrate = 0;
nchannels = 0;
if ( m_decoder )
{
samplerate = m_samplerate;
nchannels = m_channels;
if ( samplerate > 0 )
lengthSecs = static_cast<int>( static_cast<double>(m_totalSamples)/m_samplerate + 0.5);
// Calcuate bitrate
if ( lengthSecs > 0 )
{
FLAC__Metadata_SimpleIterator *it = FLAC__metadata_simple_iterator_new();
FLAC__metadata_simple_iterator_init( it, QFile::encodeName(m_fileName), true, true );
while( !FLAC__metadata_simple_iterator_is_last( it ) )
{
FLAC__metadata_simple_iterator_next( it );
}
off_t audioOffset = FLAC__metadata_simple_iterator_get_block_offset( it ) +
FLAC__metadata_simple_iterator_get_block_length( it );
FLAC__metadata_simple_iterator_delete( it );
bitrate = static_cast<int>( static_cast<double>(m_fileSize - audioOffset) * 8 / lengthSecs + 0.5 );
}
}
}
// ---------------------------------------------------------------------
void FlacSource::skip( const int mSecs )
{
FLAC__uint64 absSample = mSecs * m_samplerate / 1000 + m_samplePos;
if ( !FLAC__stream_decoder_seek_absolute(m_decoder, absSample) )
FLAC__stream_decoder_reset( m_decoder );
m_outBufLen = 0;
}
// ---------------------------------------------------------------------
void FlacSource::skipSilence(double silenceThreshold /* = 0.0001 */)
{
silenceThreshold *= static_cast<double>( std::numeric_limits<short>::max() );
for ( ;; )
{
double sum = 0;
bool result = FLAC__stream_decoder_process_single( m_decoder );
// there was a fatal read
if ( !result )
break;
switch ( m_channels )
{
case 1:
for (size_t j = 0; j < m_outBufLen; ++j)
sum += abs( m_outBuf[j] );
break;
case 2:
for ( size_t j = 0; j < m_outBufLen; j+=2 )
sum += abs( (m_outBuf[j] >> 1)
+ (m_outBuf[j+1] >> 1) );
break;
}
if ( (sum >= silenceThreshold * static_cast<double>(m_outBufLen) ) )
break;
}
m_outBufLen = 0;
}
// ---------------------------------------------------------------------
int FlacSource::updateBuffer( signed short *pBuffer, size_t bufferSize )
{
size_t nwrit = 0;
for ( ;; )
{
size_t samples_to_use = std::min (bufferSize - nwrit, m_outBufLen - m_outBufPos);
signed short* pBufferIt = pBuffer + nwrit;
nwrit += samples_to_use;
assert( nwrit <= bufferSize );
memcpy( pBufferIt, m_outBuf + m_outBufPos, sizeof(signed short)*samples_to_use );
if ( samples_to_use < m_outBufLen - m_outBufPos )
m_outBufPos = samples_to_use;
else
{
m_outBufPos = 0;
bool result = FLAC__stream_decoder_process_single( m_decoder );
// there was a fatal read
if ( !result )
{
std::cerr << "Fatal error decoding FLAC" << std::endl;
return 0;
}
else if ( FLAC__stream_decoder_get_state( m_decoder ) == FLAC__STREAM_DECODER_END_OF_STREAM )
{
m_eof = true;
break;
}
}
if ( nwrit == bufferSize )
return static_cast<int>(nwrit);
}
return static_cast<int>(nwrit);
}
// -----------------------------------------------------------------------------

View File

@@ -0,0 +1,74 @@
/*
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 __FLAC_SOURCE_H__
#define __FLAC_SOURCE_H__
#include <lastfm/FingerprintableSource>
#include <FLAC/stream_decoder.h>
#include <FLAC/metadata.h>
class FlacSource : public lastfm::FingerprintableSource
{
public:
FlacSource();
virtual ~FlacSource();
virtual void getInfo(int& lengthSecs, int& samplerate, int& bitrate, int& nchannels);
virtual void init(const QString& fileName);
// return a chunk of PCM data from the FLAC file
virtual int updateBuffer(signed short* pBuffer, size_t bufferSize);
virtual void skip(const int mSecs);
virtual void skipSilence(double silenceThreshold = 0.0001);
//QString getMbid();
bool eof() const { return m_eof; }
private:
static FLAC__StreamDecoderWriteStatus _write_callback(const FLAC__StreamDecoder *decoder, const FLAC__Frame *frame, const FLAC__int32 * const buffer[], void *client_data);
static void _metadata_callback(const FLAC__StreamDecoder *decoder, const FLAC__StreamMetadata *metadata, void *client_data);
static void _error_callback(const ::FLAC__StreamDecoder *decoder, ::FLAC__StreamDecoderErrorStatus status, void *client_data);
FLAC__StreamDecoderWriteStatus write_callback(const FLAC__Frame *frame, const FLAC__int32 * const buffer[]);
void metadata_callback( const FLAC__StreamMetadata *metadata );
void error_callback(FLAC__StreamDecoderErrorStatus status);
FLAC__StreamDecoder *m_decoder;
QString m_fileName;
size_t m_fileSize;
short *m_outBuf;
size_t m_outBufLen;
size_t m_outBufPos;
FLAC__uint64 m_samplePos;
unsigned m_maxFrameSize;
FLAC__StreamMetadata* m_commentData;
unsigned m_bps;
unsigned m_channels;
unsigned m_samplerate;
FLAC__uint64 m_totalSamples;
bool m_eof;
};
#endif

View 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);
}
// -----------------------------------------------------------------------------

View 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>
#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

View File

@@ -0,0 +1,204 @@
/*
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 "VorbisSource.h"
#include <QFile>
#include <cassert>
#include <cstdlib>
#include <iostream>
#include <limits>
#include <stdexcept>
#include <errno.h>
// These specify the output format
static const int wordSize = 2; // 16 bit output
static const int isSigned = 1;
#if __BIG_ENDIAN__
static const int isBigEndian = 1;
#else
static const int isBigEndian = 0;
#endif
VorbisSource::VorbisSource()
: m_channels( 0 )
, m_samplerate( 0 )
, m_eof( false )
{
memset( &m_vf, 0, sizeof(m_vf) );
}
// ---------------------------------------------------------------------
VorbisSource::~VorbisSource()
{
// ov_clear() also closes the file
ov_clear( &m_vf );
}
// ---------------------------------------------------------------------
void VorbisSource::init(const QString& fileName)
{
m_fileName = fileName;
if ( m_vf.datasource )
{
std::cerr << "Warning: file already appears to be open";
return;
}
FILE *fp = fopen(QFile::encodeName(m_fileName), "rb" );
if( !fp )
throw std::runtime_error( "ERROR: Cannot open ogg file!" );
// See the warning about calling ov_open on Windows
if ( ov_test_callbacks( fp, &m_vf, NULL, 0, OV_CALLBACKS_DEFAULT ) < 0 )
{
fclose( fp );
throw std::runtime_error( "ERROR: This is not an ogg vorbis file!" );
}
ov_test_open( &m_vf );
// Don't fingerprint files with more than one logical bitstream
// They most likely contain more than one track
if ( ov_streams( &m_vf ) != 1 )
throw std::runtime_error( "ERROR: ogg file contains multiple bitstreams" );
m_channels = ov_info( &m_vf, 0 )->channels;
m_samplerate = static_cast<int>(ov_info( &m_vf, 0 )->rate);
m_eof = false;
}
void VorbisSource::getInfo( int& lengthSecs, int& samplerate, int& bitrate, int& nchannels)
{
// stream info
nchannels = ov_info( &m_vf, -1 )->channels;
samplerate = static_cast<int>(ov_info( &m_vf, -1 )->rate);
lengthSecs = static_cast<int>(ov_time_total( &m_vf, -1 ) + 0.5);
bitrate = static_cast<int>(ov_bitrate( &m_vf, -1 ));
}
// ---------------------------------------------------------------------
void VorbisSource::skip( const int mSecs )
{
if ( mSecs < 0 )
return;
double ts = mSecs / 1000.0 + ov_time_tell( &m_vf );
ov_time_seek( &m_vf, ts );
}
// ---------------------------------------------------------------------
void VorbisSource::skipSilence(double silenceThreshold /* = 0.0001 */)
{
silenceThreshold *= static_cast<double>( std::numeric_limits<short>::max() );
char sampleBuffer[4096];
int bs = 0;
for (;;)
{
long charReadBytes = ov_read( &m_vf, sampleBuffer, 4096, isBigEndian, wordSize, isSigned, &bs );
// eof
if ( !charReadBytes )
{
m_eof = true;
break;
}
if ( charReadBytes < 0 )
{
// a bad bit of data: OV_HOLE || OV_EBADLINK
continue;
}
else if ( charReadBytes > 0 )
{
double sum = 0;
int16_t *buf = reinterpret_cast<int16_t*>(sampleBuffer);
switch ( m_channels )
{
case 1:
for (long j = 0; j < charReadBytes/wordSize; j++)
sum += abs( buf[j] );
break;
case 2:
for (long j = 0; j < charReadBytes/wordSize; j+=2)
sum += abs( (buf[j] >> 1) + (buf[j+1] >> 1) );
break;
}
if ( sum >= silenceThreshold * static_cast<double>(charReadBytes/wordSize/m_channels) )
break;
}
}
}
// ---------------------------------------------------------------------
int VorbisSource::updateBuffer( signed short *pBuffer, size_t bufferSize )
{
char buf[ bufferSize * wordSize ];
int bs = 0;
size_t charwrit = 0; //number of samples written to the output buffer
for (;;)
{
long charReadBytes = ov_read( &m_vf, buf, static_cast<int>(bufferSize * wordSize - charwrit),
isBigEndian, wordSize, isSigned, &bs );
if ( !charReadBytes )
{
m_eof = true;
break; // nothing else to read
}
// Don't really need this though since we're excluding files that have
// more than one logical bitstream
if ( bs != 0 )
{
vorbis_info *vi = ov_info( &m_vf, -1 );
if ( m_channels != vi->channels || m_samplerate != vi->rate )
{
std::cerr << "Files that change channel parameters or samplerate are currently not supported" << std::endl;
return 0;
}
}
if( charReadBytes < 0 )
{
std::cerr << "Warning: corrupt section of data, attempting to continue..." << std::endl;
continue;
}
char* pBufferIt = reinterpret_cast<char*>(pBuffer) + charwrit;
charwrit += charReadBytes;
assert( charwrit <= bufferSize * wordSize );
memcpy( pBufferIt, buf, charReadBytes );
if (charwrit == bufferSize * wordSize)
return static_cast<int>(charwrit/wordSize);
}
return static_cast<int>(charwrit/wordSize);
}
// -----------------------------------------------------------------------------

View File

@@ -0,0 +1,47 @@
/*
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 __VORBIS_SOURCE_H__
#define __VORBIS_SOURCE_H__
#include <FingerprintableSource.h>
#include <vorbis/vorbisfile.h>
class VorbisSource : public lastfm::FingerprintableSource
{
public:
VorbisSource();
~VorbisSource();
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_eof; }
private:
OggVorbis_File m_vf;
QString m_fileName;
int m_channels;
int m_samplerate;
bool m_eof;
};
#endif

View File

@@ -0,0 +1,13 @@
QT = core xml network
LIBS += -L$$DESTDIR -llastfm -llastfm_fingerprint
LIBS += -lvorbisfile -lFLAC -lfaad -lmp4ff -lmad
SOURCES = AacSource.cpp FlacSource.cpp MadSource.cpp VorbisSource.cpp main.cpp
mac {
INCLUDEPATH += /opt/local/include
LIBS += -L/opt/local/lib
DEFINES += MACPORTS_SUCKS
SOURCES -= AacSource.cpp
LIBS -= -lmp4ff
}

View File

@@ -0,0 +1,173 @@
/*
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/>.
*/
// ubuntu 9.04: sudo apt-get install libmad0-dev libvorbis-dev libflac-dev libfaac-dev
// macports: sudo port install libmad libvorbis libflac
// Windows: lol
#include "MadSource.h"
#include "VorbisSource.h"
#include "FlacSource.h"
#include "AacSource.h"
#include <lastfm.h>
#include <QCoreApplication>
#include <QFile>
#include <QStringList>
#include <iostream>
int typeOf(const QString& path);
lastfm::FingerprintableSource* factory(int type);
enum { MP3, OGG, FLAC, AAC, UNKNOWN };
namespace lastfm { Track taglib(const QString& path); }
int main(int argc, char** argv) try
{
if (argc < 2) {
std::cerr << "usage: " << argv[0] << " path" << std::endl;
return 1;
}
QCoreApplication app(argc, argv);
QEventLoop loop;
QString const path = QFile::decodeName(argv[1]);
lastfm::Track t = lastfm::taglib(path); //see contrib //TODO mbid
lastfm::Fingerprint fp(t);
if (fp.id().isNull()) {
lastfm::FingerprintableSource* src = factory(typeOf(path));
fp.generate(src);
QNetworkReply* reply = fp.submit();
loop.connect(reply, SIGNAL(finished()), SLOT(quit()));
fp.decode(reply);
}
QNetworkReply* reply = fp.id().getSuggestions();
loop.connect(reply, SIGNAL(finished()), SLOT(quit()));
std::cout << reply->readAll().data() << std::endl; //returns XML
return 0;
}
catch (std::exception& e)
{
std::cerr << e.what() << std::endl;
}
lastfm::FingerprintableSource* factory(int type)
{
switch (type) {
case MP3: return new MadSource;
case OGG: return new VorbisSource;
case FLAC: return new FlacSource;
#ifndef MACPORTS_SUCKS
case AAC: return new AacSource;
#endif
default: throw std::runtime_error("Cannot handle filetype");
}
}
int typeOf(const QString& fileName)
{
QStringList parts = fileName.split( "." );
QString extension;
if ( parts.size() > 1 )
extension = parts.last();
// Let's be trusting about extensions
if ( extension.toLower() == "mp3" )
return MP3;
else if ( extension.toLower() == "ogg" )
return OGG;
else if ( extension.toLower() == "oga" )
return FLAC;
else if ( extension.toLower() == "flac" )
return FLAC;
else if ( extension.toLower() == "aac" )
return AAC;
else if ( extension.toLower() == "m4a" )
return AAC;
// So much for relying on extensions. Let's try file magic instead.
FILE *fp = NULL;
unsigned char header[35];
fp = fopen(QFile::encodeName(fileName), "rb");
if ( !fp )
{
return UNKNOWN;
}
int fType = UNKNOWN;
fread( header, 1, 35, fp );
// Some formats can have ID3 tags (or not), so let's just
// get them out of the way first before we check what we have.
if ( memcmp( header, "ID3", 3) == 0 )
{
int tagsize = 0;
/* high bit is not used */
tagsize = (header[6] << 21) | (header[7] << 14) |
(header[8] << 7) | (header[9] << 0);
tagsize += 10;
fseek( fp, tagsize, SEEK_SET );
fread( header, 1, 35, fp );
}
if ( (header[0] == 0xFF) && ((header[1] & 0xFE) == 0xFA ) )
{
fType = MP3;
}
else if ( memcmp(header, "OggS", 4) == 0 )
{
if ( memcmp(&header[29], "vorbis", 6) == 0 )
{
// ogg vorbis (.ogg)
fType = OGG;
}
else if ( memcmp(&header[29], "FLAC", 4) == 0 )
{
// ogg flac (.oga)
fType = FLAC;
}
}
else if ( memcmp(header, "fLaC", 4 ) == 0 )
{
// flac file
fType = FLAC;
}
else if ( (header[0] == 0xFF) && ((header[1] & 0xF6) == 0xF0) )
{
// aac adts
fType = AAC;
}
else if (memcmp(header, "ADIF", 4) == 0)
{
// aac adif
fType = AAC;
}
else if ( memcmp( &header[4], "ftyp", 4 ) == 0 )
{
// mp4 header: aac
fType = AAC;
}
fclose(fp);
return fType;
}

View File

@@ -0,0 +1,25 @@
TEMPLATE = lib
TARGET = lastfm_fingerprint
LIBS += -L$$DESTDIR -llastfm
QT = core xml network sql
include( _files.qmake )
DEFINES += LASTFM_FINGERPRINT_LIB
INSTALLS = target
target.path = /lib
mac:CONFIG( app_bundle ) {
LIBS += libfftw3f.a libsamplerate.a -L/opt/local/include
INCLUDEPATH += /opt/local/include:/opt/qt/qt-current/lib/QtSql.framework/Include/
}else{
INCLUDEPATH += /opt/qt/qt-current/lib/QtSql.framework/Include/
CONFIG += link_pkgconfig
PKGCONFIG += samplerate
win32 {
CONFIG += link_pkgconfig
DEFINES += __NO_THREAD_CHECK
QMAKE_LFLAGS_DEBUG += /NODEFAULTLIB:msvcrt.lib /NODEFAULTLIB:libcmt.lib
}
PKGCONFIG += fftw3f
}

View File

@@ -0,0 +1,292 @@
/*
Copyright 2005-2009 Last.fm Ltd. <mir@last.fm>
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 __CIRCULAR_ARRAY_H
#define __CIRCULAR_ARRAY_H
#include <iostream>
#include <iterator>
#include <vector>
#include <algorithm>
#include <cstdlib> // for memset
#include <algorithm> // for max
#ifndef NULL
#define NULL 0
#endif
template< typename T >
class CircularArray
{
public:
typedef size_t size_type;
/////////////////////////////////////////////////////////////
// IMPORTANT: The distance must be redefined!!
// See declaration of iterator from stl_iterator_base_types.h:
// template<typename _Category, typename _Tp, typename _Distance = ptrdiff_t,
// typename _Pointer = _Tp*, typename _Reference = _Tp&>
// struct iterator { ...
// ---------- Forward declarations
class iterator :
public std::iterator<std::random_access_iterator_tag, T>
{
// it should be by default because is an inner class, but I put it just to be sure..
friend class CircularArray;
private:
iterator( size_type idx, T* pData, size_type size ) : _idx(idx), _pData(pData), _size(size) {}
public:
//typedef random_access_iterator_tag iterator_category;
iterator() : _idx(0), _pData(NULL) {}
iterator& operator++()
{ // preincrement
_idx = (_idx + 1) % _size;
return (*this);
}
iterator operator++(int)
{ // postincrement
iterator _Tmp = *this;
_idx = (_idx + 1) % _size;
return (_Tmp);
}
void operator+=(size_type offs)
{
this->_idx = (_idx + offs) % _size;
}
iterator operator+(size_type offs) const
{
size_type newIdx = (_idx + offs) % _size;
iterator _Tmp(newIdx, _pData, _size);
return _Tmp;
}
// return the distance between this iterator and it
size_t operator-(const iterator& it) const
{
if ( this->_idx > it._idx )
return this->_idx - it._idx;
else
return this->_idx + (_size - it._idx);
}
iterator operator-(size_type offs) const
{
size_type newIdx;
if ( offs <= _idx )
newIdx = _idx - offs;
else
newIdx = _size - ((_idx - offs) % _size); // note: should be ok, but to be checked better
iterator _Tmp(newIdx, _pData, _size);
return _Tmp;
}
iterator& operator--()
{ // predecrement
if (_idx == 0)
_idx = _size - 1;
else
--_idx;
return (*this);
}
iterator operator--(int)
{ // postdecrement
iterator _Tmp = *this;
if (_idx == 0)
_idx = _size - 1;
else
--_idx;
return (_Tmp);
}
T& operator*() const
{ // return designated object
return _pData[_idx];
}
T* operator->() const
{ // return pointer to class object
return &_pData[_idx];
}
/* T& operator=(const T& right)
{ // assign reference right to _val
return ( this->_idx = right._idx );
}*/
bool operator==(const iterator& right) const
{ // test for iterator equality
return ( this->_idx == right._idx );
}
bool operator!=(const iterator& right) const
{ // test for iterator inequality
return ( this->_idx != right._idx );
}
protected:
size_type _idx;
T* _pData;
size_type _size;
};
/////////////////////////////////////////////////////////////
CircularArray()
: _headIdx(0), _pData(NULL), _size(0)
{ }
CircularArray( size_type size )
: _headIdx(0), _pData(NULL)
{
this->resize(size);
}
CircularArray( size_type size, const T& init )
: _headIdx(0), _pData(NULL)
{
this->resize(size, init);
}
~CircularArray()
{
this->clear();
}
// remember: it is not working (yet!) with negative numbers!
T& operator[](size_type offset)
{
return _pData[ (_headIdx + offset) % _size ];
}
void resize( size_type size )
{
_headIdx = 0;
if ( size == _size )
return;
this->clear();
_pData = new T[size];
_size = size;
}
void resize( size_type size, const T& init )
{
this->resize(size, false);
this->fill(init);
}
void fill( const T& val )
{
for (size_type i=0; i<_size; ++i)
_pData[i] = val;
}
void zero_fill()
{
memset( _pData, 0, _size * sizeof(T) );
}
bool empty() const
{
return ( _pData == NULL );
}
void clear()
{
if (_pData)
delete [] _pData;
_pData = NULL;
_headIdx = 0;
_size = 0;
}
iterator head() const
{
if (_pData == NULL)
std::cerr << "WARNING: iterator in CircularArray points to an empty CircularArray" << std::endl;
return iterator(_headIdx, _pData, _size);
}
void shift_head( int offset )
{
if ( offset < 0)
{
int mod = (-offset) % (int)_size;
mod -= (int)_headIdx;
_headIdx = _size - mod;
}
else
_headIdx = (_headIdx + offset) % _size;
}
size_type size() const
{
return _size;
}
//// to be changed to an input forward iterator
//template <typename TIterator>
//void get_data( TIterator toFillIt, size_type size = 0 )
//{
// if ( size == 0 )
// size = _size;
// iterator it = head();
//
// for (size_type i = 0; i < size; ++i)
// *(toFillIt++) = *(it++);
//}
// IMPORTANT! Destination buffer MUST be the same size!
void copy_buffer( T* pDest )
{
memcpy( pDest, _pData, sizeof(T)*_size );
}
// returns the buffer
T* get_buffer() const
{
return _pData;
}
private:
size_type _headIdx; // index
T* _pData; // array of data
size_type _size; // size of data
};
#endif // __CIRCULAR_ARRAY_H

View File

@@ -0,0 +1,128 @@
/*
Copyright 2005-2009 Last.fm Ltd. <mir@last.fm>
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 <algorithm> // for max
#include <vector>
#include "Filter.h"
#include "fp_helper_fun.h"
using namespace std;
namespace fingerprint
{
Filter::Filter(unsigned int id, float threshold, float weight)
: id(id), threshold(threshold), weight(weight)
{
float time_rate = 1.5;
unsigned int t = 1;
vector<unsigned int> time_lengths;
while (t < KEYWIDTH)
{
time_lengths.push_back(t);
t = max( static_cast<unsigned int>( round__(time_rate*t) ) +
static_cast<unsigned int>( round__(time_rate*t) % 2),
t+1 );
}
unsigned int filter_count = 0;
for (wt = 1; wt <= time_lengths.size(); wt++)
{
for (wb = 1; wb <= NBANDS; wb++)
{
for (first_band = 1; first_band <= NBANDS - wb + 1;
first_band++)
{
unsigned int time = time_lengths[wt-1];
filter_count++;
if (filter_count == id)
{
wt = time_lengths[wt-1];
filter_type = 1;
return;
}
if (time > 1)
{
filter_count++;
if (filter_count == id)
{
wt = time_lengths[wt-1];
filter_type = 2;
return;
}
}
if (wb > 1)
{
filter_count++;
if (filter_count == id)
{
wt = time_lengths[wt-1];
filter_type = 3;
return;
}
}
if (time > 1 && wb > 1)
{
filter_count++;
if (filter_count == id)
{
wt = time_lengths[wt-1];
filter_type = 4;
return;
}
}
if (time > 3)
{
filter_count++;
if (filter_count == id)
{
wt = time_lengths[wt-1];
filter_type = 5;
return;
}
}
if (wb > 3)
{
filter_count++;
if (filter_count == id)
{
wt = time_lengths[wt-1];
filter_type = 6;
return;
}
}
} // for first_band
} // for wb
} // for wt
}
} // end of namespace fingerprint
// -----------------------------------------------------------------------------

View File

@@ -0,0 +1,47 @@
/*
Copyright 2005-2009 Last.fm Ltd. <mir@last.fm>
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 __FILTER_H
#define __FILTER_H
namespace fingerprint
{
struct Filter
{
/// Constructs a new filter with id.
Filter(unsigned int id, float threshold, float weight);
unsigned int id; //< filter id
unsigned int wt; //< time width
unsigned int first_band; //< first band
unsigned int wb; //< band width
unsigned int filter_type; //< filter type
float threshold; //< threshold for filter
float weight; //< filter weight
// number of frames in time
static const unsigned int KEYWIDTH = 100;
// number of bands to divide the signal (log step)
static const unsigned int NBANDS = 33;
};
}
#endif // __FILTER_H

View File

@@ -0,0 +1,786 @@
/*
Copyright 2005-2009 Last.fm Ltd. <mir@last.fm>
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 <limits>
#include <bitset>
#include <deque>
#include <vector>
#include <stdexcept>
#include <cmath>
#include <cstring>
#include <samplerate.h> // libsamplerate
#include "FingerprintExtractor.h"
#include "fp_helper_fun.h" // for GroupData
#include "Filter.h"
#include "FloatingAverage.h"
#include "OptFFT.h"
//////////////////////////////////////////////////////////////////////////
namespace fingerprint
{
using namespace std;
static const int NUM_FRAMES_CLIENT = 32; // ~= 10 secs.
enum eProcessType
{
PT_UNKNOWN,
PT_FOR_QUERY,
PT_FOR_FULLSUBMIT
};
//////////////////////////////////////////////////////////////////////////
class PimplData
{
public:
PimplData()
: m_pDownsampledPCM(NULL), m_pDownsampledCurrIt(NULL),
m_normalizedWindowMs(static_cast<unsigned int>(NORMALIZATION_SKIP_SECS * 1000 * 2)),
m_compensateBufferSize(FRAMESIZE-OVERLAPSAMPLES + Filter::KEYWIDTH * OVERLAPSAMPLES),
m_downsampledProcessSize(NUM_FRAMES_CLIENT*FRAMESIZE),
// notice that the buffer has extra space on either side for the normalization window
m_fullDownsampledBufferSize( m_downsampledProcessSize + // the actual processed part
m_compensateBufferSize + // a compensation buffer for the fft
((m_normalizedWindowMs * DFREQ / 1000) / 2) ), // a compensation buffer for the normalization
m_normWindow(m_normalizedWindowMs * DFREQ / 1000),
m_pFFT(NULL), m_pDownsampleState(NULL), m_processType(PT_UNKNOWN)
{
m_pFFT = new OptFFT(m_downsampledProcessSize + m_compensateBufferSize);
m_pDownsampledPCM = new float[m_fullDownsampledBufferSize];
// the end of ||-------m_bufferSize-------|-cb-|---norm/2---||
// ^-- pEndDownsampledBuf
m_pEndDownsampledBuf = m_pDownsampledPCM + m_fullDownsampledBufferSize;
// loading filters
size_t numFilters = sizeof(rFilters) / sizeof(RawFilter) ;
for (size_t i = 0; i < numFilters; ++i)
m_filters.push_back( Filter( rFilters[i].ftid, rFilters[i].thresh, rFilters[i].weight ) );
}
~PimplData()
{
if ( m_pFFT )
delete m_pFFT;
m_pFFT = NULL;
if ( m_pDownsampledPCM )
delete [] m_pDownsampledPCM;
m_pDownsampledPCM = NULL;
if ( m_pDownsampleState )
src_delete(m_pDownsampleState) ;
}
float* m_pDownsampledPCM;
float* m_pDownsampledCurrIt;
const unsigned int m_normalizedWindowMs;
const size_t m_compensateBufferSize;
const size_t m_downsampledProcessSize;
const size_t m_fullDownsampledBufferSize;
FloatingAverage<double> m_normWindow;
OptFFT* m_pFFT;
//////////////////////////////////////////////////////////////////////////
// libsamplerate
SRC_STATE* m_pDownsampleState;
SRC_DATA m_downsampleData;
vector<float> m_floatInData;
//////////////////////////////////////////////////////////////////////////
bool m_groupsReady;
bool m_preBufferPassed;
eProcessType m_processType;
size_t m_toSkipSize;
size_t m_toSkipMs;
size_t m_skippedSoFar;
bool m_skipPassed;
float* m_pEndDownsampledBuf;
int m_freq;
int m_nchannels;
unsigned int m_lengthMs;
int m_minUniqueKeys;
unsigned int m_uniqueKeyWindowMs;
unsigned int m_toProcessKeys;
unsigned int m_totalWindowKeys;
vector<Filter> m_filters;
deque<GroupData> m_groupWindow;
vector<GroupData> m_groups;
unsigned int m_processedKeys;
vector<unsigned int> m_partialBits; // here just to avoid reallocation
#if __BIG_ENDIAN__
#define reorderbits(X) ((((unsigned int)(X) & 0xff000000) >> 24) | \
(((unsigned int)(X) & 0x00ff0000) >> 8) | \
(((unsigned int)(X) & 0x0000ff00) << 8) | \
(((unsigned int)(X) & 0x000000ff) << 24))
vector<GroupData> m_bigEndianGroups;
#endif
};
//////////////////////////////////////////////////////////////////////////
void initCustom( PimplData& pd,
int freq, int nchannels,
unsigned int lengthMs, unsigned int skipMs,
int minUniqueKeys, unsigned int uniqueKeyWindowMs, int duration );
inline float getRMS( const FloatingAverage<double>& signal );
unsigned int processKeys( deque<GroupData>& groups, size_t size, PimplData& pd );
void integralImage( float** ppFrames, unsigned int nFrames );
void computeBits( vector<unsigned int>& bits,
const vector<Filter>& f,
float ** frames, unsigned int nframes );
void src_short_to_float_and_mono_array(const short *in, float *out, int srclen, int nchannels);
//////////////////////////////////////////////////////////////////////////
// -----------------------------------------------------------------------------
FingerprintExtractor::FingerprintExtractor()
: m_pPimplData(NULL)
{
m_pPimplData = new PimplData();
}
// -----------------------------------------------------------------------------
FingerprintExtractor::~FingerprintExtractor()
{
if ( m_pPimplData )
delete m_pPimplData;
}
// -----------------------------------------------------------------------------
size_t FingerprintExtractor::getToSkipMs()
{ return m_pPimplData->m_toSkipMs; }
// -----------------------------------------------------------------------------
size_t FingerprintExtractor::getMinimumDurationMs()
{
return static_cast<size_t>( (QUERY_SIZE_SECS + NORMALIZATION_SKIP_SECS * 2 + GUARD_SIZE_SECS) * 1000 );
}
// -----------------------------------------------------------------------------
size_t FingerprintExtractor::getVersion()
{ return FINGERPRINT_LIB_VERSION; }
// -----------------------------------------------------------------------------
void FingerprintExtractor::initForQuery(int freq, int nchannels, int duration )
{
m_pPimplData->m_skipPassed = false;
m_pPimplData->m_processType = PT_FOR_QUERY;
if ( !m_pPimplData )
throw std::runtime_error("Not enough RAM to allocate the fingerprinter!");
initCustom( *m_pPimplData,
freq, nchannels,
static_cast<unsigned int>(QUERY_SIZE_SECS * 1000),
static_cast<unsigned int>(QUERY_START_SECS * 1000),
MIN_UNIQUE_KEYS,
static_cast<unsigned int>(UPDATE_SIZE_SECS * 1000), duration );
}
// -----------------------------------------------------------------------------
void FingerprintExtractor::initForFullSubmit(int freq, int nchannels )
{
m_pPimplData->m_skipPassed = true;
m_pPimplData->m_processType = PT_FOR_FULLSUBMIT;
if ( !m_pPimplData )
throw std::runtime_error("Not enough RAM to allocate the fingerprinter!");
initCustom( *m_pPimplData,
freq, nchannels,
numeric_limits<unsigned int>::max(),
0, MIN_UNIQUE_KEYS, 0, -1 );
}
// -----------------------------------------------------------------------------
void initCustom( PimplData& pd,
int freq, int nchannels,
unsigned int lengthMs,
unsigned int skipMs,
int minUniqueKeys,
unsigned int uniqueKeyWindowMs, int duration )
{
//////////////////////////////////////////////////////////////////////////
pd.m_freq = freq;
pd.m_nchannels = nchannels;
pd.m_lengthMs = lengthMs;
pd.m_minUniqueKeys = minUniqueKeys;
pd.m_uniqueKeyWindowMs = uniqueKeyWindowMs;
//////////////////////////////////////////////////////////////////////////
// ***********************************************************************
if ( pd.m_pDownsampleState )
pd.m_pDownsampleState = src_delete(pd.m_pDownsampleState) ;
pd.m_pDownsampleState = src_new (SRC_SINC_FASTEST, 1, NULL) ;
pd.m_downsampleData.src_ratio = FDFREQ / freq;
// ***********************************************************************
//////////////////////////////////////////////////////////////////////////
if ( pd.m_processType == PT_FOR_FULLSUBMIT )
skipMs = 0; // make sure
else if ( duration > 0 )
{
// skip + size + right normalization window + FFT guard
//
int stdDurationMs = static_cast<int>((QUERY_START_SECS + QUERY_SIZE_SECS + NORMALIZATION_SKIP_SECS + GUARD_SIZE_SECS) * 1000);
int actualDurationMs = duration * 1000;
// compute the actual skipMs depending on the duration
if ( actualDurationMs < stdDurationMs )
skipMs -= max( stdDurationMs - actualDurationMs, 0 );
}
pd.m_toSkipMs = max( static_cast<int>(skipMs) - static_cast<int>((pd.m_normalizedWindowMs/2)), 0 );
pd.m_toSkipSize = static_cast<size_t>( freq * nchannels *
(pd.m_toSkipMs / 1000.0) ); // half the norm window in secs;
//if ( pd.m_processType == PT_FOR_QUERY && skipMs > pd.m_normalizedWindowMs/2 )
//{
// pd.m_toSkipMs = skipMs - (pd.m_normalizedWindowMs/2);
// pd.m_toSkipSize = static_cast<size_t>( freq * nchannels *
// (pd.m_toSkipMs / 1000.0) ); // half the norm window in secs
//}
//else
//{
// pd.m_toSkipMs = 0;
// pd.m_toSkipSize = 0; // half of the normalization window will be skipped in ANY case
//}
pd.m_skippedSoFar = 0;
pd.m_groupsReady = false;
pd.m_preBufferPassed = false;
// prepare the position for pre-buffering
pd.m_pDownsampledCurrIt = pd.m_pDownsampledPCM + (pd.m_downsampledProcessSize - (pd.m_normWindow.size() / 2) );
pd.m_toProcessKeys = fingerprint::getTotalKeys(pd.m_lengthMs);// (m_lengthMs * DFREQ) / (1000 * OVERLAPSAMPLES) + 1;
pd.m_totalWindowKeys = fingerprint::getTotalKeys(pd.m_uniqueKeyWindowMs); //(m_uniqueKeyWindowMs * DFREQ) / (1000 * OVERLAPSAMPLES) + 1;
if (pd.m_toProcessKeys == 1)
pd.m_toProcessKeys = 0;
if (pd.m_totalWindowKeys == 1)
pd.m_totalWindowKeys = 0;
pd.m_processedKeys = 0;
pd.m_groupWindow.clear();
pd.m_processedKeys = 0;
}
// -----------------------------------------------------------------------------
// * cb = compensate buffer size
// * norm = floating normalization window size
//
// PREBUFFER:
// (-------m_bufferSize-------)
// || EMPTY |---norm/2---|-cb-|---norm/2---||
// 1. {--------read frames-----------}
// 2. {--read normalize window--}
// 3. {----} normalize
//
// 1. read [norm + cb] frames to m_bufferSize - norm/2
// 2. read [m_buffersize - norm/2...m_buffersize + norm/2] into normalize window
// 3. normalize [m_bufferSize..m_bufferSize+cb]
//
// PROCESS:
//
// ||-------m_bufferSize-------|-cb-|---norm/2---||
// 1. <--------------------------{------copy-------}
// 2. {--------read frames-------}
// 3. {---------normalize--------}
// 4. {------fft/process/whatevs------}
//
// 1. copy [m_bufferSize..m_bufferSize + cb + norm/2] to beginning
// 2. read m_bufferSize frames to cb + norm/2
// 3. normalize [cb..m_bufferSize+cb]
// 4. fft/process/whatevs [0...m_bufferSize+cb]
//
// repeat until enough blocks processed and enough groups!
//
bool FingerprintExtractor::process( const short* pPCM, size_t num_samples, bool end_of_stream )
{
if ( num_samples == 0 )
return false;
// easier read
PimplData& pd = *m_pPimplData;
if ( pd.m_processType == PT_UNKNOWN )
throw std::runtime_error("Please call initForQuery() or initForFullSubmit() before process()!");
const short* pSourcePCMIt = pPCM;
const short* pSourcePCMIt_end = pPCM + num_samples;
if ( !pd.m_skipPassed )
{
// needs to skip data? (reminder: the query needs to skip QUERY_START_SECS (- half of the normalization window)
if ( pd.m_skippedSoFar + num_samples > pd.m_toSkipSize )
{
pSourcePCMIt = pPCM + (pd.m_toSkipSize - pd.m_skippedSoFar);
pd.m_skipPassed = true;
}
else
{
// need more data
pd.m_skippedSoFar += num_samples;
return false;
}
}
pair<size_t, size_t> readData(0,0);
pd.m_downsampleData.end_of_input = end_of_stream ? 1 : 0;
//////////////////////////////////////////////////////////////////////////
// PREBUFFER:
if ( !pd.m_preBufferPassed )
{
// 1. downsample [norm + cb] frames to m_bufferSize - norm/2
pd.m_floatInData.resize( (pSourcePCMIt_end - pSourcePCMIt) / pd.m_nchannels);
src_short_to_float_and_mono_array( pSourcePCMIt,
&(pd.m_floatInData[0]), static_cast<int>(pSourcePCMIt_end - pSourcePCMIt),
pd.m_nchannels);
pd.m_downsampleData.data_in = &(pd.m_floatInData[0]);
pd.m_downsampleData.input_frames = static_cast<long>(pd.m_floatInData.size());
pd.m_downsampleData.data_out = pd.m_pDownsampledCurrIt;
pd.m_downsampleData.output_frames = static_cast<long>(pd.m_pEndDownsampledBuf - pd.m_pDownsampledCurrIt);
int err = src_process(pd.m_pDownsampleState, &(pd.m_downsampleData));
if ( err )
throw std::runtime_error( src_strerror(err) );
pd.m_pDownsampledCurrIt += pd.m_downsampleData.output_frames_gen;
if ( pd.m_pDownsampledCurrIt != pd.m_pEndDownsampledBuf )
return false; // NEED MORE DATA
pSourcePCMIt += pd.m_downsampleData.input_frames_used * pd.m_nchannels;
size_t pos = pd.m_downsampledProcessSize;
size_t window_pos = pd.m_downsampledProcessSize - pd.m_normWindow.size() / 2;
const size_t end_window_pos = window_pos + pd.m_normWindow.size();
// 2. read [m_buffersize - norm/2...m_buffersize + norm/2] into normalize window
for (; window_pos < end_window_pos ; ++window_pos)
pd.m_normWindow.add(pd.m_pDownsampledPCM[window_pos] * pd.m_pDownsampledPCM[window_pos]);
// 3. normalize [m_bufferSize..m_bufferSize+cb]
for (; pos < pd.m_downsampledProcessSize + pd.m_compensateBufferSize; ++pos, ++window_pos)
{
pd.m_pDownsampledPCM[pos] /= getRMS(pd.m_normWindow);
pd.m_normWindow.add(pd.m_pDownsampledPCM[window_pos] * pd.m_pDownsampledPCM[window_pos]);
}
pd.m_preBufferPassed = true;
}
//////////////////////////////////////////////////////////////////////////
// PROCESS:
bool found_enough_unique_keys = false;
while (pd.m_toProcessKeys == 0 || pd.m_processedKeys < pd.m_toProcessKeys || !found_enough_unique_keys)
{
// 1. copy [m_bufferSize..m_bufferSize + cb + norm/2] to beginning
if ( pd.m_pDownsampledCurrIt == pd.m_pEndDownsampledBuf )
{
memcpy( pd.m_pDownsampledPCM, pd.m_pDownsampledPCM + pd.m_downsampledProcessSize,
(pd.m_compensateBufferSize + (pd.m_normWindow.size() / 2)) * sizeof(float));
pd.m_pDownsampledCurrIt = pd.m_pDownsampledPCM + (pd.m_compensateBufferSize + (pd.m_normWindow.size() / 2));
}
// 2. read m_bufferSize frames to cb + norm/2
pd.m_floatInData.resize( (pSourcePCMIt_end - pSourcePCMIt) / pd.m_nchannels);
if ( pd.m_floatInData.empty() )
return false;
src_short_to_float_and_mono_array( pSourcePCMIt,
&(pd.m_floatInData[0]), static_cast<int>(pSourcePCMIt_end - pSourcePCMIt),
pd.m_nchannels);
pd.m_downsampleData.data_in = &(pd.m_floatInData[0]);
pd.m_downsampleData.input_frames = static_cast<long>(pd.m_floatInData.size());
pd.m_downsampleData.data_out = pd.m_pDownsampledCurrIt;
pd.m_downsampleData.output_frames = static_cast<long>(pd.m_pEndDownsampledBuf - pd.m_pDownsampledCurrIt);
int err = src_process(pd.m_pDownsampleState, &(pd.m_downsampleData));
if ( err )
throw std::runtime_error( src_strerror(err) );
pd.m_pDownsampledCurrIt += pd.m_downsampleData.output_frames_gen;
if ( pd.m_pDownsampledCurrIt != pd.m_pEndDownsampledBuf && !end_of_stream )
return false; // NEED MORE DATA
//pSourcePCMIt += readData.second;
pSourcePCMIt += pd.m_downsampleData.input_frames_used * pd.m_nchannels;
// ********************************************************************
// 3. normalize [cb..m_bufferSize+cb]
size_t pos = static_cast<unsigned int>(pd.m_compensateBufferSize);
size_t window_pos = static_cast<unsigned int>(pd.m_compensateBufferSize + (pd.m_normWindow.size() / 2));
for(; pos < pd.m_downsampledProcessSize + pd.m_compensateBufferSize /* m_fullDownsampledBufferSize*/; ++pos, ++window_pos)
{
pd.m_pDownsampledPCM[pos] /= getRMS(pd.m_normWindow);
pd.m_normWindow.add(pd.m_pDownsampledPCM[window_pos] * pd.m_pDownsampledPCM[window_pos]);
}
// 4. fft/process/whatevs [0...m_bufferSize+cb]
pd.m_processedKeys += processKeys(pd.m_groupWindow, pos, pd);
// we have too many keys, now we have to chop either one end or the other
if (pd.m_toProcessKeys != 0 && pd.m_processedKeys > pd.m_toProcessKeys)
{
// set up window begin and end
deque<GroupData>::iterator itBeg = pd.m_groupWindow.begin(), itEnd = pd.m_groupWindow.end();
unsigned int offset_left, offset_right;
found_enough_unique_keys =
fingerprint::findSignificantGroups( itBeg, itEnd, offset_left, offset_right, pd.m_toProcessKeys,
pd.m_totalWindowKeys, pd.m_minUniqueKeys);
// if we're happy with this set, snip the beginning and end of the grouped keys
if (found_enough_unique_keys)
{
itBeg->count -= offset_left;
if (offset_right > 0 && itEnd != pd.m_groupWindow.end())
{
itEnd->count = offset_right;
++itEnd;
}
}
// chop the deque
copy(itBeg, itEnd, pd.m_groupWindow.begin());
pd.m_groupWindow.resize(itEnd - itBeg);
// recalc keys
pd.m_processedKeys = 0;
for (deque<GroupData>::const_iterator it = pd.m_groupWindow.begin(); it != pd.m_groupWindow.end(); ++it)
pd.m_processedKeys += it->count;
}
if ( end_of_stream )
break;
} // while (totalKeys == 0 || keys < totalKeys || !found_enough_unique_keys)
if (pd.m_toProcessKeys != 0 && pd.m_processedKeys < pd.m_toProcessKeys)
throw std::runtime_error("Couldn't deliver the requested number of keys (it's the file too short?)");
if ((pd.m_toProcessKeys != 0 && !found_enough_unique_keys) ||
(pd.m_toProcessKeys == 0 && !enoughUniqueGoodGroups(pd.m_groupWindow.begin(), pd.m_groupWindow.end(), pd.m_minUniqueKeys)))
{
throw std::runtime_error("Not enough unique keys (it's the file too short?)");
}
// copy to a vector so that they can be returned as contiguous data
pd.m_groups.resize(pd.m_groupWindow.size());
copy(pd.m_groupWindow.begin(), pd.m_groupWindow.end(), pd.m_groups.begin());
pd.m_groupsReady = true;
pd.m_processType = PT_UNKNOWN;
return true;
}
// -----------------------------------------------------------------------------
pair<const char*, size_t> FingerprintExtractor::getFingerprint()
{
// easier read
PimplData& pd = *m_pPimplData;
if ( pd.m_groupsReady )
{
#if __BIG_ENDIAN__
pd.m_bigEndianGroups.resize(pd.m_groups.size());
for ( size_t i = 0; i < pd.m_groups.size(); ++i )
{
pd.m_bigEndianGroups[i].key = reorderbits(pd.m_groups[i].key);
pd.m_bigEndianGroups[i].count = reorderbits(pd.m_groups[i].count);
}
return make_pair(reinterpret_cast<const char*>(&pd.m_bigEndianGroups[0]), pd.m_bigEndianGroups.size() * sizeof(GroupData) );
#else
return make_pair(reinterpret_cast<const char*>(&pd.m_groups[0]), pd.m_groups.size() * sizeof(GroupData) );
#endif
}
else
return make_pair(reinterpret_cast<const char*>(0), 0); // here's where null_ptr would become useful!
}
// -----------------------------------------------------------------------------
// -----------------------------------------------------------------------------
// -----------------------------------------------------------------------------
float getRMS(const FloatingAverage<double>& signal)
{
// we don't want to normalize by the real rms, because excessive clipping will occur
float rms = sqrtf(static_cast<float>(signal.getAverage())) * 10.0F;
if (rms < 0.1F)
rms = 0.1F;
else if (rms > 3.0F)
rms = 3.0F;
return rms;
}
// -----------------------------------------------------------------------------
unsigned int processKeys( deque<GroupData>& groups, size_t size, PimplData& pd )
{
size_t read_size = min(size, pd.m_downsampledProcessSize + pd.m_compensateBufferSize);
unsigned int numFrames = pd.m_pFFT->process(pd.m_pDownsampledPCM, read_size);
if ( numFrames <= Filter::KEYWIDTH )
return 0; // skip it when the number of frames is too small
float** ppFrames = pd.m_pFFT->getFrames();
integralImage(ppFrames, numFrames);
computeBits(pd.m_partialBits, pd.m_filters, ppFrames, numFrames);
fingerprint::keys2GroupData(pd.m_partialBits, groups, false);
return static_cast<unsigned int>(pd.m_partialBits.size());
}
// -----------------------------------------------------------------------------
void integralImage(float** ppFrames, unsigned int nFrames)
{
for (unsigned int y = 1; y < nFrames; y++)
{
ppFrames[y][0] += ppFrames[y-1][0];
}
for (unsigned int x = 1; x < Filter::NBANDS; x++)
{
ppFrames[0][x] += ppFrames[0][x-1];
}
for (unsigned int y = 1; y < nFrames; y++)
{
for (unsigned int x = 1; x < Filter::NBANDS; x++)
{
ppFrames[y][x] += static_cast<float>( static_cast<double>(ppFrames[y-1][x]) +
static_cast<double>(ppFrames[y][x-1]) -
static_cast<double>(ppFrames[y-1][x-1]) );
}
}
}
// ---------------------------------------------------------------------
//
/// Convert bands to bits, using the supplied filters
void computeBits( vector<unsigned int>& bits,
const vector<Filter>& f,
float ** frames, unsigned int nframes )
{
unsigned int first_time = Filter::KEYWIDTH / 2 + 1;
unsigned int last_time = nframes - Filter::KEYWIDTH / 2;
unsigned int numBits = last_time - first_time + 1;
bits.resize(numBits);
const unsigned int fSize = static_cast<unsigned int>(f.size());
std::bitset<32> bt;
double X = 0;
for (unsigned int t2 = first_time; t2 <= last_time; ++t2)
{
for (unsigned int i = 0; i < fSize; ++i)
{
// we subtract 1 from t1 and b1 because we use integral images
unsigned int t1 = (unsigned int) ((float) t2 - f[i].wt / 2.0 - 1);
unsigned int t3 = (unsigned int) ((float) t2 + f[i].wt / 2.0 - 1);
unsigned int b1 = f[i].first_band;
unsigned int b2 = (unsigned int) round__((float) b1 + f[i].wb / 2.0) - 1;
unsigned int b3 = b1 + f[i].wb - 1;
--b1;
unsigned int t_1q = (t1 + t2) / 2; // one quarter time
unsigned int t_3q = t_1q + (t3 - t1 + 1) / 2; // three quarter time
unsigned int b_1q = (b1 + b2) / 2; // one quarter band
unsigned int b_3q = b_1q + (b3 - b1) / 2; // three quarter band
X = 0;
// we should check from t1 > 0, but in practice, this doesn't happen
// we subtract 1 from everything because this came from matlab where indices start from 1
switch (f[i].filter_type) {
case 1: { // total energy
if (b1 > 0)
X = static_cast<double>(frames[t3-1][b3-1]) - static_cast<double>(frames[t3-1][b1-1])
- static_cast<double>(frames[t1-1][b3-1]) + static_cast<double>(frames[t1-1][b1-1]);
else
X = static_cast<double>(frames[t3-1][b3-1]) - static_cast<double>(frames[t1-1][b3-1]);
break;
}
case 2: { // energy difference over time
if (b1 > 0)
X = static_cast<double>(frames[t1-1][b1-1]) - 2*static_cast<double>(frames[t2-2][b1-1])
+ static_cast<double>(frames[t3-1][b1-1]) - static_cast<double>(frames[t1-1][b3-1])
+ 2*static_cast<double>(frames[t2-2][b3-1]) - static_cast<double>(frames[t3-1][b3-1]);
else
X = - static_cast<double>(frames[t1-1][b3-1]) + 2*static_cast<double>(frames[t2-2][b3-1])
- static_cast<double>(frames[t3-1][b3-1]);
break;
}
case 3: { // energy difference over bands
if (b1 > 0)
X = static_cast<double>(frames[t1-1][b1-1]) - static_cast<double>(frames[t3-1][b1-1])
- 2*static_cast<double>(frames[t1-1][b2-1]) + 2*static_cast<double>(frames[t3-1][b2-1])
+ static_cast<double>(frames[t1-1][b3-1]) - static_cast<double>(frames[t3-1][b3-1]);
else
X = - 2*static_cast<double>(frames[t1-1][b2-1]) + 2*static_cast<double>(frames[t3-1][b2-1])
+ static_cast<double>(frames[t1-1][b3-1]) - static_cast<double>(frames[t3-1][b3-1]);
break;
}
case 4: {
// energy difference over time and bands
if (b1 > 0)
X = static_cast<double>(frames[t1-1][b1-1]) - 2*static_cast<double>(frames[t2-2][b1-1])
+ static_cast<double>(frames[t3-1][b1-1]) - 2*static_cast<double>(frames[t1-1][b2-1])
+ 4*static_cast<double>(frames[t2-2][b2-1]) - 2*static_cast<double>(frames[t3-1][b2-1])
+ static_cast<double>(frames[t1-1][b3-1]) - 2*static_cast<double>(frames[t2-2][b3-1])
+ static_cast<double>(frames[t3-1][b3-1]);
else
X = - 2*static_cast<double>(frames[t1-1][b2-1]) + 4*static_cast<double>(frames[t2-2][b2-1])
- 2*static_cast<double>(frames[t3-1][b2-1]) + static_cast<double>(frames[t1-1][b3-1])
- 2*static_cast<double>(frames[t2-2][b3-1]) + static_cast<double>(frames[t3-1][b3-1]);
break;
}
case 5: { // time peak
if (b1 > 0)
X = - static_cast<double>(frames[t1-1][b1-1]) + 2*static_cast<double>(frames[t_1q-1][b1-1])
- 2*static_cast<double>(frames[t_3q-1][b1-1]) + static_cast<double>(frames[t3-1][b1-1])
+ static_cast<double>(frames[t1-1][b3-1]) - 2*static_cast<double>(frames[t_1q-1][b3-1])
+ 2*static_cast<double>(frames[t_3q-1][b3-1]) - static_cast<double>(frames[t3-1][b3-1]);
else
X = static_cast<double>(frames[t1-1][b3-1]) - 2*static_cast<double>(frames[t_1q-1][b3-1])
+ 2*static_cast<double>(frames[t_3q-1][b3-1]) - static_cast<double>(frames[t3-1][b3-1]);
break;
}
case 6: { // band beak
if (b1 > 0)
X = - static_cast<double>(frames[t1-1][b1-1]) + static_cast<double>(frames[t3-1][b1-1])
+ 2*static_cast<double>(frames[t1-1][b_1q-1]) - 2*static_cast<double>(frames[t3-1][b_1q-1])
- 2*static_cast<double>(frames[t1-1][b_3q-1]) + 2*static_cast<double>(frames[t3-1][b_3q-1])
+ static_cast<double>(frames[t1-1][b3-1]) - static_cast<double>(frames[t3-1][b3-1]);
else
X = + 2*static_cast<double>(frames[t1-1][b_1q-1]) - 2*static_cast<double>(frames[t3-1][b_1q-1])
- 2*static_cast<double>(frames[t1-1][b_3q-1]) + 2*static_cast<double>(frames[t3-1][b_3q-1])
+ static_cast<double>(frames[t1-1][b3-1]) - static_cast<double>(frames[t3-1][b3-1]);
break;
}
}
bt[i] = X > f[i].threshold;
}
bits[t2 - first_time] = bt.to_ulong();
}
}
// -----------------------------------------------------------------------------
void src_short_to_float_and_mono_array( const short *in, float *out, int srclen, int nchannels )
{
switch ( nchannels )
{
case 1:
src_short_to_float_array(in, out, srclen);
break;
case 2:
{
// this can be optimized
int j = 0;
const double div = numeric_limits<short>::max() * nchannels;
for ( int i = 0; i < srclen; i += 2, ++j )
{
out[j] = static_cast<float>( static_cast<double>(static_cast<int>(in[i]) + static_cast<int>(in[i+1])) / div );
}
}
break;
default:
throw( std::runtime_error("Unsupported number of channels!") );
}
}
// -----------------------------------------------------------------------------
} // end of namespace
// -----------------------------------------------------------------------------

View File

@@ -0,0 +1,77 @@
/*
Copyright 2005-2009 Last.fm Ltd. <mir@last.fm>
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 __FINGERPRINT_EXTRACTOR_H
#define __FINGERPRINT_EXTRACTOR_H
#include <utility> // for pair
#include <cstddef> // for size_t
namespace fingerprint {
// -----------------------------------------------------------------------------
class PimplData;
class FingerprintExtractor
{
public:
FingerprintExtractor(); // ctor
~FingerprintExtractor(); // dtor
// duration (in seconds!) is optional, but if you want to submit tracks <34 secs
// it must be provided.
void initForQuery(int freq, int nchannels, int duration = -1);
void initForFullSubmit(int freq, int nchannels);
// return false if it needs more data, otherwise true
// IMPORTANT: num_samples specify the size of the *short* array pPCM, that is
// the number of samples that are in the buffer. This includes
// the stereo samples, i.e.
// [L][R][L][R][L][R][L][R] would be num_samples=8
bool process(const short* pPCM, size_t num_samples, bool end_of_stream = false);
// returns pair<NULL, 0> if the data is not ready
std::pair<const char*, size_t> getFingerprint();
//////////////////////////////////////////////////////////////////////////
// The FingerprintExtractor assumes that the file start from the beginning
// but since the first SkipMs are ignored, it's possible to feed it with NULL.
// In order to know how much must be skipped (in milliseconds) call this function.
// Remark: this is only for "advanced" users!
size_t getToSkipMs();
// Return the minimum duration of the file (in ms)
// Any file with a length smaller than this value will be discarded
static size_t getMinimumDurationMs();
// return the version of the fingerprint
static size_t getVersion();
private:
PimplData* m_pPimplData;
};
// -----------------------------------------------------------------------------
} // end of namespace fingerprint
#endif // __FINGERPRINT_EXTRACTOR_H

View File

@@ -0,0 +1,106 @@
/*
Copyright 2005-2009 Last.fm Ltd. <mir@last.fm>
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 __FLOAT_AVERAGE_H__
#define __FLOAT_AVERAGE_H__
//#include <deque>
#include <limits>
#include "CircularArray.h"
template <typename T>
class FloatingAverage
{
public:
FloatingAverage(size_t size)
{
m_values.resize(size);
m_valIt = m_values.head();
m_sum = 0;
m_bufferFilled = false;
}
void purge()
{
m_sum = 0;
const T* pCircularBuffer = m_values.get_buffer();
const int size = m_values.size();
for ( int i = 0; i < size; ++i )
m_sum += pCircularBuffer[i];
}
void add(const T& value)
{
m_sum += value;
if ( m_bufferFilled )
{
m_sum -= *m_valIt;
*m_valIt = value;
++m_valIt;
}
else
{
*m_valIt = value;
++m_valIt;
if ( m_valIt == m_values.head() )
m_bufferFilled = true;
}
}
T getAverage() const
{
if ( !m_bufferFilled )
return m_sum / (m_valIt - m_values.head());
else
return m_sum / m_values.size();
}
T getError() const
{
T real_sum = 0;
const T* pCircularBuffer = m_values.get_buffer();
for ( int i = 0; i < size; ++i )
real_sum += pCircularBuffer[i];
return abs(real_sum - m_sum) / this->size();
}
size_t size() const
{
return m_values.size();
}
void clear()
{
m_bufferFilled = false;
m_values.zero_fill();
m_valIt = m_values.head();
m_sum = 0;
}
private:
//std::deque<T> m_values;
CircularArray<T> m_values;
typename CircularArray<T>::iterator m_valIt;
bool m_bufferFilled;
T m_sum;
};
#endif // __FLOAT_AVERAGE_H__

View File

@@ -0,0 +1,411 @@
/*
Copyright 2005-2009 Last.fm Ltd. <mir@last.fm>
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 "OptFFT.h"
#include "fp_helper_fun.h"
#include "Filter.h" // for NBANDS
#include <cmath>
#include <cassert>
#include <cstdlib>
#include <iostream>
#include <sstream>
#include <cstdlib>
#include <stdexcept>
#include <cstring>
using namespace std;
// ----------------------------------------------------------------------
namespace fingerprint
{
static const float hann[] = {
0.000000f,0.000002f,0.000009f,0.000021f,0.000038f,0.000059f,0.000085f,0.000115f,0.000151f,0.000191f,0.000236f,
0.000285f,0.000339f,0.000398f,0.000462f,0.000530f,0.000603f,0.000681f,0.000763f,0.000850f,0.000942f,0.001038f,
0.001140f,0.001245f,0.001356f,0.001471f,0.001591f,0.001716f,0.001845f,0.001980f,0.002118f,0.002262f,0.002410f,
0.002563f,0.002720f,0.002883f,0.003049f,0.003221f,0.003397f,0.003578f,0.003764f,0.003954f,0.004149f,0.004349f,
0.004553f,0.004762f,0.004976f,0.005194f,0.005417f,0.005645f,0.005877f,0.006114f,0.006355f,0.006602f,0.006853f,
0.007108f,0.007368f,0.007633f,0.007903f,0.008177f,0.008455f,0.008739f,0.009027f,0.009319f,0.009617f,0.009919f,
0.010225f,0.010536f,0.010852f,0.011172f,0.011497f,0.011827f,0.012161f,0.012499f,0.012843f,0.013191f,0.013543f,
0.013900f,0.014262f,0.014628f,0.014999f,0.015374f,0.015754f,0.016139f,0.016528f,0.016921f,0.017320f,0.017722f,
0.018130f,0.018541f,0.018958f,0.019379f,0.019804f,0.020234f,0.020668f,0.021107f,0.021551f,0.021999f,0.022451f,
0.022908f,0.023370f,0.023836f,0.024306f,0.024781f,0.025260f,0.025744f,0.026233f,0.026725f,0.027223f,0.027724f,
0.028231f,0.028741f,0.029256f,0.029776f,0.030300f,0.030828f,0.031361f,0.031898f,0.032440f,0.032986f,0.033536f,
0.034091f,0.034650f,0.035214f,0.035781f,0.036354f,0.036930f,0.037512f,0.038097f,0.038687f,0.039281f,0.039879f,
0.040482f,0.041089f,0.041701f,0.042316f,0.042936f,0.043561f,0.044189f,0.044822f,0.045460f,0.046101f,0.046747f,
0.047397f,0.048052f,0.048710f,0.049373f,0.050040f,0.050711f,0.051387f,0.052067f,0.052751f,0.053439f,0.054132f,
0.054828f,0.055529f,0.056234f,0.056943f,0.057657f,0.058374f,0.059096f,0.059822f,0.060552f,0.061286f,0.062024f,
0.062767f,0.063513f,0.064264f,0.065019f,0.065777f,0.066540f,0.067307f,0.068078f,0.068854f,0.069633f,0.070416f,
0.071204f,0.071995f,0.072790f,0.073590f,0.074393f,0.075201f,0.076012f,0.076828f,0.077647f,0.078470f,0.079298f,
0.080129f,0.080964f,0.081804f,0.082647f,0.083494f,0.084345f,0.085200f,0.086059f,0.086922f,0.087788f,0.088659f,
0.089533f,0.090412f,0.091294f,0.092180f,0.093070f,0.093963f,0.094861f,0.095762f,0.096667f,0.097576f,0.098489f,
0.099406f,0.100326f,0.101250f,0.102178f,0.103109f,0.104045f,0.104984f,0.105926f,0.106873f,0.107823f,0.108777f,
0.109734f,0.110696f,0.111661f,0.112629f,0.113601f,0.114577f,0.115557f,0.116540f,0.117526f,0.118517f,0.119511f,
0.120508f,0.121509f,0.122514f,0.123522f,0.124534f,0.125549f,0.126568f,0.127590f,0.128616f,0.129645f,0.130678f,
0.131714f,0.132754f,0.133797f,0.134844f,0.135894f,0.136948f,0.138005f,0.139065f,0.140129f,0.141196f,0.142266f,
0.143340f,0.144418f,0.145498f,0.146582f,0.147670f,0.148760f,0.149854f,0.150951f,0.152052f,0.153156f,0.154263f,
0.155373f,0.156487f,0.157603f,0.158723f,0.159847f,0.160973f,0.162103f,0.163236f,0.164372f,0.165511f,0.166653f,
0.167799f,0.168947f,0.170099f,0.171254f,0.172411f,0.173572f,0.174737f,0.175904f,0.177074f,0.178247f,0.179423f,
0.180603f,0.181785f,0.182970f,0.184158f,0.185350f,0.186544f,0.187741f,0.188941f,0.190144f,0.191350f,0.192559f,
0.193771f,0.194986f,0.196203f,0.197423f,0.198647f,0.199873f,0.201102f,0.202333f,0.203568f,0.204805f,0.206045f,
0.207288f,0.208534f,0.209782f,0.211033f,0.212287f,0.213544f,0.214803f,0.216065f,0.217329f,0.218597f,0.219867f,
0.221139f,0.222414f,0.223692f,0.224972f,0.226255f,0.227541f,0.228829f,0.230120f,0.231413f,0.232709f,0.234007f,
0.235308f,0.236611f,0.237917f,0.239225f,0.240536f,0.241849f,0.243165f,0.244483f,0.245803f,0.247126f,0.248451f,
0.249779f,0.251108f,0.252441f,0.253775f,0.255112f,0.256451f,0.257793f,0.259137f,0.260483f,0.261831f,0.263182f,
0.264534f,0.265889f,0.267247f,0.268606f,0.269967f,0.271331f,0.272697f,0.274065f,0.275435f,0.276808f,0.278182f,
0.279558f,0.280937f,0.282318f,0.283700f,0.285085f,0.286472f,0.287861f,0.289251f,0.290644f,0.292039f,0.293435f,
0.294834f,0.296235f,0.297637f,0.299041f,0.300448f,0.301856f,0.303266f,0.304678f,0.306091f,0.307507f,0.308924f,
0.310343f,0.311764f,0.313187f,0.314611f,0.316038f,0.317466f,0.318895f,0.320327f,0.321760f,0.323194f,0.324631f,
0.326069f,0.327509f,0.328950f,0.330393f,0.331837f,0.333283f,0.334731f,0.336180f,0.337631f,0.339083f,0.340537f,
0.341993f,0.343449f,0.344908f,0.346368f,0.347829f,0.349291f,0.350755f,0.352221f,0.353688f,0.355156f,0.356626f,
0.358097f,0.359569f,0.361042f,0.362517f,0.363994f,0.365471f,0.366950f,0.368430f,0.369911f,0.371394f,0.372877f,
0.374362f,0.375848f,0.377336f,0.378824f,0.380314f,0.381804f,0.383296f,0.384789f,0.386283f,0.387778f,0.389274f,
0.390771f,0.392269f,0.393768f,0.395269f,0.396770f,0.398272f,0.399775f,0.401279f,0.402784f,0.404290f,0.405797f,
0.407305f,0.408813f,0.410322f,0.411833f,0.413344f,0.414856f,0.416368f,0.417882f,0.419396f,0.420911f,0.422427f,
0.423944f,0.425461f,0.426979f,0.428497f,0.430017f,0.431537f,0.433057f,0.434578f,0.436100f,0.437623f,0.439146f,
0.440669f,0.442193f,0.443718f,0.445243f,0.446769f,0.448295f,0.449822f,0.451349f,0.452877f,0.454405f,0.455934f,
0.457463f,0.458992f,0.460522f,0.462052f,0.463582f,0.465113f,0.466644f,0.468176f,0.469708f,0.471240f,0.472772f,
0.474305f,0.475837f,0.477370f,0.478904f,0.480437f,0.481971f,0.483505f,0.485039f,0.486573f,0.488107f,0.489641f,
0.491176f,0.492710f,0.494245f,0.495780f,0.497314f,0.498849f,0.500384f,0.501918f,0.503453f,0.504988f,0.506522f,
0.508057f,0.509591f,0.511126f,0.512660f,0.514194f,0.515728f,0.517262f,0.518796f,0.520330f,0.521863f,0.523396f,
0.524929f,0.526462f,0.527994f,0.529526f,0.531058f,0.532590f,0.534121f,0.535652f,0.537183f,0.538713f,0.540243f,
0.541773f,0.543302f,0.544831f,0.546359f,0.547887f,0.549414f,0.550941f,0.552468f,0.553994f,0.555519f,0.557044f,
0.558569f,0.560093f,0.561616f,0.563139f,0.564661f,0.566182f,0.567703f,0.569223f,0.570743f,0.572262f,0.573780f,
0.575298f,0.576815f,0.578331f,0.579846f,0.581361f,0.582875f,0.584388f,0.585900f,0.587412f,0.588922f,0.590432f,
0.591941f,0.593449f,0.594957f,0.596463f,0.597968f,0.599473f,0.600977f,0.602479f,0.603981f,0.605482f,0.606981f,
0.608480f,0.609978f,0.611474f,0.612970f,0.614464f,0.615958f,0.617450f,0.618941f,0.620431f,0.621920f,0.623408f,
0.624895f,0.626380f,0.627865f,0.629348f,0.630830f,0.632310f,0.633790f,0.635268f,0.636745f,0.638220f,0.639695f,
0.641167f,0.642639f,0.644109f,0.645578f,0.647046f,0.648512f,0.649977f,0.651440f,0.652902f,0.654363f,0.655822f,
0.657279f,0.658735f,0.660190f,0.661643f,0.663094f,0.664544f,0.665993f,0.667440f,0.668885f,0.670329f,0.671771f,
0.673212f,0.674650f,0.676088f,0.677523f,0.678957f,0.680389f,0.681820f,0.683249f,0.684676f,0.686101f,0.687525f,
0.688946f,0.690366f,0.691785f,0.693201f,0.694616f,0.696029f,0.697439f,0.698849f,0.700256f,0.701661f,0.703065f,
0.704466f,0.705866f,0.707263f,0.708659f,0.710053f,0.711444f,0.712834f,0.714222f,0.715608f,0.716991f,0.718373f,
0.719752f,0.721130f,0.722505f,0.723879f,0.725250f,0.726619f,0.727986f,0.729351f,0.730714f,0.732074f,0.733432f,
0.734788f,0.736142f,0.737494f,0.738843f,0.740191f,0.741536f,0.742878f,0.744219f,0.745557f,0.746892f,0.748226f,
0.749557f,0.750886f,0.752212f,0.753536f,0.754857f,0.756177f,0.757493f,0.758808f,0.760120f,0.761429f,0.762736f,
0.764041f,0.765343f,0.766642f,0.767939f,0.769234f,0.770526f,0.771815f,0.773102f,0.774386f,0.775668f,0.776947f,
0.778224f,0.779497f,0.780769f,0.782037f,0.783303f,0.784567f,0.785827f,0.787085f,0.788340f,0.789593f,0.790842f,
0.792089f,0.793334f,0.794575f,0.795814f,0.797050f,0.798283f,0.799513f,0.800741f,0.801965f,0.803187f,0.804406f,
0.805622f,0.806835f,0.808046f,0.809253f,0.810458f,0.811659f,0.812858f,0.814054f,0.815246f,0.816436f,0.817623f,
0.818807f,0.819987f,0.821165f,0.822340f,0.823512f,0.824680f,0.825846f,0.827008f,0.828168f,0.829324f,0.830477f,
0.831628f,0.832775f,0.833918f,0.835059f,0.836197f,0.837331f,0.838462f,0.839591f,0.840715f,0.841837f,0.842955f,
0.844071f,0.845183f,0.846291f,0.847397f,0.848499f,0.849598f,0.850693f,0.851786f,0.852874f,0.853960f,0.855042f,
0.856121f,0.857197f,0.858269f,0.859338f,0.860404f,0.861466f,0.862524f,0.863580f,0.864631f,0.865680f,0.866725f,
0.867766f,0.868804f,0.869839f,0.870870f,0.871897f,0.872922f,0.873942f,0.874959f,0.875973f,0.876983f,0.877989f,
0.878992f,0.879991f,0.880987f,0.881979f,0.882967f,0.883952f,0.884934f,0.885911f,0.886885f,0.887856f,0.888822f,
0.889785f,0.890745f,0.891701f,0.892653f,0.893601f,0.894545f,0.895486f,0.896423f,0.897357f,0.898287f,0.899213f,
0.900135f,0.901053f,0.901968f,0.902879f,0.903786f,0.904689f,0.905588f,0.906484f,0.907376f,0.908264f,0.909148f,
0.910028f,0.910904f,0.911777f,0.912645f,0.913510f,0.914371f,0.915228f,0.916081f,0.916930f,0.917775f,0.918616f,
0.919454f,0.920287f,0.921116f,0.921942f,0.922763f,0.923581f,0.924394f,0.925204f,0.926009f,0.926810f,0.927608f,
0.928401f,0.929191f,0.929976f,0.930757f,0.931534f,0.932308f,0.933077f,0.933842f,0.934603f,0.935359f,0.936112f,
0.936861f,0.937605f,0.938345f,0.939082f,0.939814f,0.940542f,0.941265f,0.941985f,0.942701f,0.943412f,0.944119f,
0.944822f,0.945521f,0.946215f,0.946906f,0.947592f,0.948274f,0.948951f,0.949625f,0.950294f,0.950959f,0.951620f,
0.952276f,0.952928f,0.953576f,0.954220f,0.954859f,0.955495f,0.956125f,0.956752f,0.957374f,0.957992f,0.958606f,
0.959215f,0.959820f,0.960420f,0.961017f,0.961609f,0.962196f,0.962780f,0.963358f,0.963933f,0.964503f,0.965069f,
0.965630f,0.966187f,0.966740f,0.967288f,0.967832f,0.968371f,0.968906f,0.969437f,0.969963f,0.970485f,0.971002f,
0.971515f,0.972023f,0.972527f,0.973027f,0.973522f,0.974012f,0.974498f,0.974980f,0.975457f,0.975930f,0.976398f,
0.976862f,0.977321f,0.977776f,0.978226f,0.978672f,0.979113f,0.979549f,0.979982f,0.980409f,0.980832f,0.981251f,
0.981665f,0.982075f,0.982480f,0.982880f,0.983276f,0.983667f,0.984054f,0.984436f,0.984814f,0.985187f,0.985556f,
0.985919f,0.986279f,0.986634f,0.986984f,0.987329f,0.987670f,0.988007f,0.988339f,0.988666f,0.988989f,0.989307f,
0.989620f,0.989929f,0.990233f,0.990532f,0.990827f,0.991118f,0.991403f,0.991684f,0.991961f,0.992233f,0.992500f,
0.992762f,0.993020f,0.993273f,0.993522f,0.993766f,0.994005f,0.994240f,0.994470f,0.994695f,0.994916f,0.995132f,
0.995343f,0.995550f,0.995752f,0.995949f,0.996142f,0.996329f,0.996513f,0.996691f,0.996865f,0.997035f,0.997199f,
0.997359f,0.997514f,0.997665f,0.997810f,0.997952f,0.998088f,0.998220f,0.998347f,0.998469f,0.998587f,0.998700f,
0.998808f,0.998912f,0.999010f,0.999105f,0.999194f,0.999279f,0.999359f,0.999434f,0.999505f,0.999571f,0.999632f,
0.999689f,0.999740f,0.999787f,0.999830f,0.999868f,0.999900f,0.999929f,0.999952f,0.999971f,0.999985f,0.999995f,
0.999999f,0.999999f,0.999995f,0.999985f,0.999971f,0.999952f,0.999929f,0.999900f,0.999868f,0.999830f,0.999787f,
0.999740f,0.999689f,0.999632f,0.999571f,0.999505f,0.999434f,0.999359f,0.999279f,0.999194f,0.999105f,0.999010f,
0.998912f,0.998808f,0.998700f,0.998587f,0.998469f,0.998347f,0.998220f,0.998088f,0.997952f,0.997810f,0.997665f,
0.997514f,0.997359f,0.997199f,0.997035f,0.996865f,0.996691f,0.996513f,0.996329f,0.996142f,0.995949f,0.995752f,
0.995550f,0.995343f,0.995132f,0.994916f,0.994695f,0.994470f,0.994240f,0.994005f,0.993766f,0.993522f,0.993273f,
0.993020f,0.992762f,0.992500f,0.992233f,0.991961f,0.991684f,0.991403f,0.991118f,0.990827f,0.990532f,0.990233f,
0.989929f,0.989620f,0.989307f,0.988989f,0.988666f,0.988339f,0.988007f,0.987670f,0.987329f,0.986984f,0.986634f,
0.986279f,0.985919f,0.985556f,0.985187f,0.984814f,0.984436f,0.984054f,0.983667f,0.983276f,0.982880f,0.982480f,
0.982075f,0.981665f,0.981251f,0.980832f,0.980409f,0.979982f,0.979549f,0.979113f,0.978672f,0.978226f,0.977776f,
0.977321f,0.976862f,0.976398f,0.975930f,0.975457f,0.974980f,0.974498f,0.974012f,0.973522f,0.973027f,0.972527f,
0.972023f,0.971515f,0.971002f,0.970485f,0.969963f,0.969437f,0.968906f,0.968371f,0.967832f,0.967288f,0.966740f,
0.966187f,0.965630f,0.965069f,0.964503f,0.963933f,0.963358f,0.962780f,0.962196f,0.961609f,0.961017f,0.960420f,
0.959820f,0.959215f,0.958606f,0.957992f,0.957374f,0.956752f,0.956125f,0.955495f,0.954859f,0.954220f,0.953576f,
0.952928f,0.952276f,0.951620f,0.950959f,0.950294f,0.949625f,0.948951f,0.948274f,0.947592f,0.946906f,0.946215f,
0.945521f,0.944822f,0.944119f,0.943412f,0.942701f,0.941985f,0.941265f,0.940542f,0.939814f,0.939082f,0.938345f,
0.937605f,0.936861f,0.936112f,0.935359f,0.934603f,0.933842f,0.933077f,0.932308f,0.931534f,0.930757f,0.929976f,
0.929191f,0.928401f,0.927608f,0.926810f,0.926009f,0.925204f,0.924394f,0.923581f,0.922763f,0.921942f,0.921116f,
0.920287f,0.919454f,0.918616f,0.917775f,0.916930f,0.916081f,0.915228f,0.914371f,0.913510f,0.912645f,0.911777f,
0.910904f,0.910028f,0.909148f,0.908264f,0.907376f,0.906484f,0.905588f,0.904689f,0.903786f,0.902879f,0.901968f,
0.901053f,0.900135f,0.899213f,0.898287f,0.897357f,0.896423f,0.895486f,0.894545f,0.893601f,0.892653f,0.891701f,
0.890745f,0.889785f,0.888822f,0.887856f,0.886885f,0.885911f,0.884934f,0.883952f,0.882967f,0.881979f,0.880987f,
0.879991f,0.878992f,0.877989f,0.876983f,0.875973f,0.874959f,0.873942f,0.872922f,0.871897f,0.870870f,0.869839f,
0.868804f,0.867766f,0.866725f,0.865680f,0.864631f,0.863580f,0.862524f,0.861466f,0.860404f,0.859338f,0.858269f,
0.857197f,0.856121f,0.855042f,0.853960f,0.852874f,0.851786f,0.850693f,0.849598f,0.848499f,0.847397f,0.846291f,
0.845183f,0.844071f,0.842955f,0.841837f,0.840715f,0.839591f,0.838462f,0.837331f,0.836197f,0.835059f,0.833918f,
0.832775f,0.831628f,0.830477f,0.829324f,0.828168f,0.827008f,0.825846f,0.824680f,0.823512f,0.822340f,0.821165f,
0.819987f,0.818807f,0.817623f,0.816436f,0.815246f,0.814054f,0.812858f,0.811659f,0.810458f,0.809253f,0.808046f,
0.806835f,0.805622f,0.804406f,0.803187f,0.801965f,0.800741f,0.799513f,0.798283f,0.797050f,0.795814f,0.794575f,
0.793334f,0.792089f,0.790842f,0.789593f,0.788340f,0.787085f,0.785827f,0.784567f,0.783303f,0.782037f,0.780769f,
0.779497f,0.778224f,0.776947f,0.775668f,0.774386f,0.773102f,0.771815f,0.770526f,0.769234f,0.767939f,0.766642f,
0.765343f,0.764041f,0.762736f,0.761429f,0.760120f,0.758808f,0.757493f,0.756177f,0.754857f,0.753536f,0.752212f,
0.750886f,0.749557f,0.748226f,0.746892f,0.745557f,0.744219f,0.742878f,0.741536f,0.740191f,0.738843f,0.737494f,
0.736142f,0.734788f,0.733432f,0.732074f,0.730714f,0.729351f,0.727986f,0.726619f,0.725250f,0.723879f,0.722505f,
0.721130f,0.719752f,0.718373f,0.716991f,0.715608f,0.714222f,0.712834f,0.711444f,0.710053f,0.708659f,0.707263f,
0.705866f,0.704466f,0.703065f,0.701661f,0.700256f,0.698849f,0.697439f,0.696029f,0.694616f,0.693201f,0.691785f,
0.690366f,0.688946f,0.687525f,0.686101f,0.684676f,0.683249f,0.681820f,0.680389f,0.678957f,0.677523f,0.676088f,
0.674650f,0.673212f,0.671771f,0.670329f,0.668885f,0.667440f,0.665993f,0.664544f,0.663094f,0.661643f,0.660190f,
0.658735f,0.657279f,0.655822f,0.654363f,0.652902f,0.651440f,0.649977f,0.648512f,0.647046f,0.645578f,0.644109f,
0.642639f,0.641167f,0.639695f,0.638220f,0.636745f,0.635268f,0.633790f,0.632310f,0.630830f,0.629348f,0.627865f,
0.626380f,0.624895f,0.623408f,0.621920f,0.620431f,0.618941f,0.617450f,0.615958f,0.614464f,0.612970f,0.611474f,
0.609978f,0.608480f,0.606981f,0.605482f,0.603981f,0.602479f,0.600977f,0.599473f,0.597968f,0.596463f,0.594957f,
0.593449f,0.591941f,0.590432f,0.588922f,0.587412f,0.585900f,0.584388f,0.582875f,0.581361f,0.579846f,0.578331f,
0.576815f,0.575298f,0.573780f,0.572262f,0.570743f,0.569223f,0.567703f,0.566182f,0.564661f,0.563139f,0.561616f,
0.560093f,0.558569f,0.557044f,0.555519f,0.553994f,0.552468f,0.550941f,0.549414f,0.547887f,0.546359f,0.544831f,
0.543302f,0.541773f,0.540243f,0.538713f,0.537183f,0.535652f,0.534121f,0.532590f,0.531058f,0.529526f,0.527994f,
0.526462f,0.524929f,0.523396f,0.521863f,0.520330f,0.518796f,0.517262f,0.515728f,0.514194f,0.512660f,0.511126f,
0.509591f,0.508057f,0.506522f,0.504988f,0.503453f,0.501918f,0.500384f,0.498849f,0.497314f,0.495780f,0.494245f,
0.492710f,0.491176f,0.489641f,0.488107f,0.486573f,0.485039f,0.483505f,0.481971f,0.480437f,0.478904f,0.477370f,
0.475837f,0.474305f,0.472772f,0.471240f,0.469708f,0.468176f,0.466644f,0.465113f,0.463582f,0.462052f,0.460522f,
0.458992f,0.457463f,0.455934f,0.454405f,0.452877f,0.451349f,0.449822f,0.448295f,0.446769f,0.445243f,0.443718f,
0.442193f,0.440669f,0.439146f,0.437623f,0.436100f,0.434578f,0.433057f,0.431537f,0.430017f,0.428497f,0.426979f,
0.425461f,0.423944f,0.422427f,0.420911f,0.419396f,0.417882f,0.416368f,0.414856f,0.413344f,0.411833f,0.410322f,
0.408813f,0.407305f,0.405797f,0.404290f,0.402784f,0.401279f,0.399775f,0.398272f,0.396770f,0.395269f,0.393768f,
0.392269f,0.390771f,0.389274f,0.387778f,0.386283f,0.384789f,0.383296f,0.381804f,0.380314f,0.378824f,0.377336f,
0.375848f,0.374362f,0.372877f,0.371394f,0.369911f,0.368430f,0.366950f,0.365471f,0.363994f,0.362517f,0.361042f,
0.359569f,0.358097f,0.356626f,0.355156f,0.353688f,0.352221f,0.350755f,0.349291f,0.347829f,0.346368f,0.344908f,
0.343449f,0.341993f,0.340537f,0.339083f,0.337631f,0.336180f,0.334731f,0.333283f,0.331837f,0.330393f,0.328950f,
0.327509f,0.326069f,0.324631f,0.323194f,0.321760f,0.320327f,0.318895f,0.317466f,0.316038f,0.314611f,0.313187f,
0.311764f,0.310343f,0.308924f,0.307507f,0.306091f,0.304678f,0.303266f,0.301856f,0.300448f,0.299041f,0.297637f,
0.296235f,0.294834f,0.293435f,0.292039f,0.290644f,0.289251f,0.287861f,0.286472f,0.285085f,0.283700f,0.282318f,
0.280937f,0.279558f,0.278182f,0.276808f,0.275435f,0.274065f,0.272697f,0.271331f,0.269967f,0.268606f,0.267247f,
0.265889f,0.264534f,0.263182f,0.261831f,0.260483f,0.259137f,0.257793f,0.256451f,0.255112f,0.253775f,0.252441f,
0.251108f,0.249779f,0.248451f,0.247126f,0.245803f,0.244483f,0.243165f,0.241849f,0.240536f,0.239225f,0.237917f,
0.236611f,0.235308f,0.234007f,0.232709f,0.231413f,0.230120f,0.228829f,0.227541f,0.226255f,0.224972f,0.223692f,
0.222414f,0.221139f,0.219867f,0.218597f,0.217329f,0.216065f,0.214803f,0.213544f,0.212287f,0.211033f,0.209782f,
0.208534f,0.207288f,0.206045f,0.204805f,0.203568f,0.202333f,0.201102f,0.199873f,0.198647f,0.197423f,0.196203f,
0.194986f,0.193771f,0.192559f,0.191350f,0.190144f,0.188941f,0.187741f,0.186544f,0.185350f,0.184158f,0.182970f,
0.181785f,0.180603f,0.179423f,0.178247f,0.177074f,0.175904f,0.174737f,0.173572f,0.172411f,0.171254f,0.170099f,
0.168947f,0.167799f,0.166653f,0.165511f,0.164372f,0.163236f,0.162103f,0.160973f,0.159847f,0.158723f,0.157603f,
0.156487f,0.155373f,0.154263f,0.153156f,0.152052f,0.150951f,0.149854f,0.148760f,0.147670f,0.146582f,0.145498f,
0.144418f,0.143340f,0.142266f,0.141196f,0.140129f,0.139065f,0.138005f,0.136948f,0.135894f,0.134844f,0.133797f,
0.132754f,0.131714f,0.130678f,0.129645f,0.128616f,0.127590f,0.126568f,0.125549f,0.124534f,0.123522f,0.122514f,
0.121509f,0.120508f,0.119511f,0.118517f,0.117526f,0.116540f,0.115557f,0.114577f,0.113601f,0.112629f,0.111661f,
0.110696f,0.109734f,0.108777f,0.107823f,0.106873f,0.105926f,0.104984f,0.104045f,0.103109f,0.102178f,0.101250f,
0.100326f,0.099406f,0.098489f,0.097576f,0.096667f,0.095762f,0.094861f,0.093963f,0.093070f,0.092180f,0.091294f,
0.090412f,0.089533f,0.088659f,0.087788f,0.086922f,0.086059f,0.085200f,0.084345f,0.083494f,0.082647f,0.081804f,
0.080964f,0.080129f,0.079298f,0.078470f,0.077647f,0.076828f,0.076012f,0.075201f,0.074393f,0.073590f,0.072790f,
0.071995f,0.071204f,0.070416f,0.069633f,0.068854f,0.068078f,0.067307f,0.066540f,0.065777f,0.065019f,0.064264f,
0.063513f,0.062767f,0.062024f,0.061286f,0.060552f,0.059822f,0.059096f,0.058374f,0.057657f,0.056943f,0.056234f,
0.055529f,0.054828f,0.054132f,0.053439f,0.052751f,0.052067f,0.051387f,0.050711f,0.050040f,0.049373f,0.048710f,
0.048052f,0.047397f,0.046747f,0.046101f,0.045460f,0.044822f,0.044189f,0.043561f,0.042936f,0.042316f,0.041701f,
0.041089f,0.040482f,0.039879f,0.039281f,0.038687f,0.038097f,0.037512f,0.036930f,0.036354f,0.035781f,0.035214f,
0.034650f,0.034091f,0.033536f,0.032986f,0.032440f,0.031898f,0.031361f,0.030828f,0.030300f,0.029776f,0.029256f,
0.028741f,0.028231f,0.027724f,0.027223f,0.026725f,0.026233f,0.025744f,0.025260f,0.024781f,0.024306f,0.023836f,
0.023370f,0.022908f,0.022451f,0.021999f,0.021551f,0.021107f,0.020668f,0.020234f,0.019804f,0.019379f,0.018958f,
0.018541f,0.018130f,0.017722f,0.017320f,0.016921f,0.016528f,0.016139f,0.015754f,0.015374f,0.014999f,0.014628f,
0.014262f,0.013900f,0.013543f,0.013191f,0.012843f,0.012499f,0.012161f,0.011827f,0.011497f,0.011172f,0.010852f,
0.010536f,0.010225f,0.009919f,0.009617f,0.009319f,0.009027f,0.008739f,0.008455f,0.008177f,0.007903f,0.007633f,
0.007368f,0.007108f,0.006853f,0.006602f,0.006355f,0.006114f,0.005877f,0.005645f,0.005417f,0.005194f,0.004976f,
0.004762f,0.004553f,0.004349f,0.004149f,0.003954f,0.003764f,0.003578f,0.003397f,0.003221f,0.003049f,0.002883f,
0.002720f,0.002563f,0.002410f,0.002262f,0.002118f,0.001980f,0.001845f,0.001716f,0.001591f,0.001471f,0.001356f,
0.001245f,0.001140f,0.001038f,0.000942f,0.000850f,0.000763f,0.000681f,0.000603f,0.000530f,0.000462f,0.000398f,
0.000339f,0.000285f,0.000236f,0.000191f,0.000151f,0.000115f,0.000085f,0.000059f,0.000038f,0.000021f,0.000009f,
0.000002f,0.000000f };
// -----------------------------------------------------------------------------
OptFFT::OptFFT(const size_t maxDataSize)
{
assert( maxDataSize % OVERLAPSAMPLES == 0 );
// DOUBLE
//m_pIn = static_cast<double*>( fftw_malloc(sizeof(double) * FRAMESIZE) );
//m_pOut = static_cast<fftw_complex*>( fftw_malloc(sizeof(fftw_complex) * (FRAMESIZE/2 + 1)) );
//m_p = fftw_plan_dft_r2c_1f(FRAMESIZE, m_pIn, m_pOut, FFTW_ESTIMATE); // FFTW_ESTIMATE or FFTW_MEASURE
// FLOAT
// m_pIn = static_cast<float*>( fftwf_malloc(sizeof(float) * FRAMESIZE) );
// m_pOut = static_cast<fftwf_complex*>( fftwf_malloc(sizeof(fftwf_complex) * (FRAMESIZE/2 + 1)) );
//// in destroyed when line executed
//m_p = fftwf_plan_dft_r2c_1d(FRAMESIZE, m_pIn, m_pOut, FFTW_ESTIMATE); // FFTW_ESTIMATE or FFTW_MEASURE
//-----------------------------------------------------------------
int numSamplesPerFrame = FRAMESIZE;
int numSamplesPerFrameOut = FRAMESIZE/2+1;
m_maxFrames = static_cast<int> ( (maxDataSize - FRAMESIZE) / OVERLAPSAMPLES + 1 );
m_pIn = static_cast<float*> ( fftwf_malloc(sizeof(float) * (numSamplesPerFrame * m_maxFrames) ) );
if ( !m_pIn )
{
ostringstream oss;
oss << "fftwf_malloc failed on m_pIn. Trying to allocate <"
<< sizeof(float) * (numSamplesPerFrame * m_maxFrames)
<< "> bytes";
throw std::runtime_error(oss.str());
}
m_pOut = static_cast<fftwf_complex*>( fftwf_malloc(sizeof(fftwf_complex) * (numSamplesPerFrameOut* m_maxFrames) ) );
if ( !m_pOut )
{
ostringstream oss;
oss << "fftwf_malloc failed on m_pOut. Trying to allocate <"
<< sizeof(fftwf_complex) * (numSamplesPerFrameOut* m_maxFrames)
<< "> bytes";
throw std::runtime_error(oss.str());
}
// in destroyed when line executed
m_p = fftwf_plan_many_dft_r2c(1, &numSamplesPerFrame, m_maxFrames,
m_pIn, &numSamplesPerFrame, 1, numSamplesPerFrame,
m_pOut, &numSamplesPerFrameOut,
1, numSamplesPerFrameOut,
FFTW_ESTIMATE | FFTW_DESTROY_INPUT);
if ( !m_p )
throw std::runtime_error ("fftwf_plan_many_dft_r2c failed");
double base = exp( log( static_cast<double>(MAXFREQ) / static_cast<double>(MINFREQ) ) /
static_cast<double>(Filter::NBANDS)
);
m_powTable.resize( Filter::NBANDS+1 );
for ( unsigned int i = 0; i < Filter::NBANDS + 1; ++i )
m_powTable[i] = static_cast<unsigned int>( (pow(base, static_cast<double>(i)) - 1.0) * MINCOEF );
m_pFrames = new float*[m_maxFrames];
if ( !m_pFrames )
{
ostringstream oss;
oss << "Allocation failed on m_pFrames. Trying to allocate <"
<< sizeof(float*) * m_maxFrames
<< "> bytes";
throw std::runtime_error(oss.str());
}
for (int i = 0; i < m_maxFrames; ++i)
{
m_pFrames[i] = new float[Filter::NBANDS];
if ( !m_pFrames[i] )
throw std::runtime_error("Allocation failed on m_pFrames");
}
}
// ----------------------------------------------------------------------
OptFFT::~OptFFT()
{
fftwf_destroy_plan(m_p);
fftwf_free(m_pIn);
fftwf_free(m_pOut);
for (int i = 0; i < m_maxFrames; ++i)
delete [] m_pFrames[i];
delete [] m_pFrames;
}
// ----------------------------------------------------------------------
int OptFFT::process(float* pInData, const size_t dataSize)
{
// generally is the same of the one we used in the constructor (m_maxFrames) but
// might be less at the end of the stream
int nFrames = static_cast<int>( (dataSize - FRAMESIZE) / OVERLAPSAMPLES + 1 );
float* pIn_It = m_pIn;
for (int i = 0; i < nFrames; ++i)
{
memcpy( pIn_It, &pInData[i*OVERLAPSAMPLES], sizeof(float) * FRAMESIZE);
// apply hanning window
applyHann(pIn_It, FRAMESIZE);
pIn_It += FRAMESIZE;
}
// fill the rest with zeroes
if ( nFrames < m_maxFrames )
memset( pIn_It, 0, sizeof(float) * (m_maxFrames-nFrames) * FRAMESIZE );
fftwf_execute(m_p);
int totSamples = (FRAMESIZE/2+1) * // numSamplesPerFrameOut
nFrames; // the frames actually in the input
// scaling (?)
float scalingFactor = static_cast<float>(FRAMESIZE) / 2.0f;
for (int k = 0; k < totSamples; ++k)
{
m_pOut[k][0] /= scalingFactor;
m_pOut[k][1] /= scalingFactor;
}
int frameStart;
unsigned int outBlocStart;
unsigned int outBlocEnd;
for (int i = 0; i < nFrames; ++i)
{
frameStart = i * (FRAMESIZE/2+1);
// compute bands
for (unsigned int j = 0; j < Filter::NBANDS; j++)
{
outBlocStart = m_powTable[j] + frameStart;
outBlocEnd = m_powTable[j+1] + frameStart;
m_pFrames[i][j] = 0;
// WARNING: We're double counting the last one here.
// this bug is to match matlab's implementation bug in power2band.m
unsigned int end_k = outBlocEnd + static_cast<unsigned int>(MINCOEF);
for (unsigned int k = outBlocStart + static_cast<unsigned int>(MINCOEF); k <= end_k; k++)
{
m_pFrames[i][j] += m_pOut[k][0] * m_pOut[k][0] +
m_pOut[k][1] * m_pOut[k][1];
}
// WARNING: if we change the k<=end to k<end above, we need to change the following line
m_pFrames[i][j] /= static_cast<float>(outBlocEnd - outBlocStart + 1);
}
}
return nFrames;
}
// -----------------------------------------------------------------------------
void OptFFT::applyHann( float* pInData, const size_t dataSize )
{
assert (dataSize == 2048);
for ( size_t i = 0; i < dataSize; ++i )
pInData[i] *= hann[i];
}
// -----------------------------------------------------------------------------
} // end of namespace
// ----------------------------------------------------------------------

View File

@@ -0,0 +1,63 @@
/*
Copyright 2005-2009 Last.fm Ltd. <mir@last.fm>
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 __OPT_FFT_H
#define __OPT_FFT_H
#include <fftw3.h>
#include <vector>
namespace fingerprint
{
class OptFFT
{
public:
OptFFT(const size_t maxDataSize);
~OptFFT();
int
process(float* pInData, const size_t dataSize);
float**
getFrames() { return m_pFrames; }
private:
void applyHann(float* pInData, const size_t dataSize);
fftwf_plan m_p;
fftwf_complex * m_pOut;
float* m_pIn;
//float m_base;
int m_numSamples;
int m_numOutSamples;
float** m_pFrames;
int m_maxFrames;
std::vector<int> m_powTable;
};
} // end of namespace
#endif // OPT_FFT

View File

@@ -0,0 +1,443 @@
/*
Copyright 2005-2009 Last.fm Ltd. <mir@last.fm>
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 __FINGERPRINT_HELPER_FUNCTIONS_H
#define __FINGERPRINT_HELPER_FUNCTIONS_H
#include <vector>
#include <deque>
#include <set>
namespace fingerprint
{
// -----------------------------------------------------------------------------
static const size_t FINGERPRINT_LIB_VERSION = 1;
static const float QUERY_START_SECS = 20;
static const float QUERY_SIZE_SECS = 14;
static const float UPDATE_SIZE_SECS = 10;
//FFT needs also a buffer that depends on the input freq. 3 secs should be enough up to 48Khz
static const float GUARD_SIZE_SECS = 3;
static const float NORMALIZATION_SKIP_SECS = 2.5;
static const int MIN_UNIQUE_KEYS = 75;
static const unsigned int MAX_GOOD_GROUP_SIZE = 200;
static const int SHA_SIZE = 32;
/////////////////////////////////////////////////////
// For FFT. DO NOT TOUCH THEM!
// number of samples in a frame
static const int FRAMESIZE = 2048;
static const int OVERLAP = 32;
static const int OVERLAPSAMPLES = (FRAMESIZE/OVERLAP); // 64
// down-sampled frequency
static const int DFREQ = 5512;
static const float FDFREQ = 5512.5f;
// -----------------------------------------------------------------------------
struct GroupData
{
unsigned int key; // the key (or local descriptor)
unsigned int count; // the number of frames sharing this key
};
// -----------------------------------------------------------------------------
// -----------------------------------------------------------------------------
inline
unsigned int getTotalKeys(
int mSecs )
{
return static_cast<unsigned int>((static_cast<double>(mSecs) / (1000.0 * OVERLAPSAMPLES) ) * DFREQ ) + 1;
}
// -----------------------------------------------------------------------------
template <typename GroupDataIt>
void simpleSkip(
GroupDataIt& begIt, const GroupDataIt& endIt,
unsigned int numSkipKeys )
{
if ( numSkipKeys <= 0 )
return;
unsigned int nKeys;
for ( nKeys = 0; nKeys < numSkipKeys && begIt != endIt; ++begIt )
nKeys += begIt->count;
// clear crop at the end
if ( nKeys > numSkipKeys )
{
--begIt;
begIt->count = nKeys - numSkipKeys;
}
}
// -----------------------------------------------------------------------------
template <typename TGroupData>
void cutGroups(
std::vector<TGroupData>& groups,
const unsigned int startMS,
const unsigned int lengthMS )
{
typename std::vector<TGroupData>::iterator itBeg = groups.begin(), itEnd = groups.begin();
unsigned int keys_begin, keys_end;
for (keys_begin = getTotalKeys(startMS);
itBeg != groups.end() && keys_begin > itBeg->count; ++itBeg)
keys_begin -= itBeg->count;
for (keys_end = getTotalKeys(startMS + lengthMS);
itEnd != groups.end() && keys_end > itEnd->count; ++itEnd)
keys_end -= itEnd->count;
if (itBeg == groups.end()) // in the umpossible scenario that you try to cut past the size of the groups
{
groups.clear();
return;
}
itBeg->count -= keys_begin;
if (keys_end > 0 && itEnd != groups.end())
{
itEnd->count = keys_end;
++itEnd;
}
copy(itBeg, itEnd, groups.begin());
groups.resize(itEnd - itBeg);
keys_begin = getTotalKeys(lengthMS);
for (typename std::vector<TGroupData>::iterator it = groups.begin(); it != groups.end(); ++it)
keys_begin -= it->count;
}
// -------------------------------------------------------------------------
template <typename TGroupData>
void keys2GroupData(
const std::vector<unsigned int>& keys, // in
std::vector<TGroupData>& groupData,
bool clearDst = true ) // out
{
if (clearDst)
groupData.clear();
if (keys.empty())
return;
TGroupData tmpGroup;
std::vector<unsigned int>::const_iterator it = keys.begin();
if ( !groupData.empty() )
{
// get the last group
tmpGroup = groupData.back();
groupData.pop_back();
}
else
{
// new group!
tmpGroup.key = *it;
tmpGroup.count = 1;
++it; // move to the next key
}
for (; it != keys.end(); ++it)
{
if ( *it != tmpGroup.key )
{
// new group ready!
groupData.push_back( tmpGroup );
tmpGroup.key = *it;
tmpGroup.count = 0;
}
++tmpGroup.count;
}
// last group
groupData.push_back( tmpGroup );
}
// -------------------------------------------------------------------------
template <typename TGroupData>
void keys2GroupData(
const std::vector<unsigned int>& keys, // in
std::deque<TGroupData>& groupData,
bool clearDst = true ) // out
{
if (clearDst)
groupData.clear();
if (keys.empty())
return;
TGroupData tmpGroup;
std::vector<unsigned int>::const_iterator it = keys.begin();
if ( !groupData.empty() )
{
// get the last group
tmpGroup = groupData.back();
groupData.pop_back();
}
else
{
// new group!
tmpGroup.key = *it;
tmpGroup.count = 1;
++it; // move to the next key
}
for (; it != keys.end(); ++it)
{
if ( *it != tmpGroup.key )
{
// new group ready!
groupData.push_back( tmpGroup );
tmpGroup.key = *it;
tmpGroup.count = 0;
}
++tmpGroup.count;
}
// last group
groupData.push_back( tmpGroup );
}
// -------------------------------------------------------------------------
template <typename TGroupData>
inline
void groupData2Keys(
const std::vector<TGroupData>& groupData, // in
std::vector<unsigned int>& keys ) // out
{
keys.clear();
typename std::vector<TGroupData>::const_iterator it;
for (it = groupData.begin(); it != groupData.end(); ++it)
{
for (unsigned int j = 0; j < it->count; ++j)
keys.push_back(it->key);
}
}
// -------------------------------------------------------------------------
template <typename GroupDataIt>
bool findSignificantGroups(
GroupDataIt& beg, GroupDataIt& end, unsigned int& offset_left, unsigned int& offset_right,
unsigned int windowKeySize, unsigned int subWindowKeySize, unsigned int minUniqueKeys)
{
GroupDataIt itBeg = beg, itEnd = beg, itWindowBeg = beg, itWindowEnd = beg;
offset_left = 0;
unsigned int window_offset_left;
unsigned int window_offset_right;
// this amounts to around a 500 ms hop for, say, a 20 second sub-window
unsigned int key_hop_size = subWindowKeySize / 40;
// trail out itEnd
for (offset_right = windowKeySize; itEnd != end && offset_right > itEnd->count; ++itEnd)
offset_right -= itEnd->count;
// dang man, we don't even have enough groups to span the window size
if (itEnd == end && offset_right > 0)
return false;
// 0 window size means just scan the whole range
if (windowKeySize == 0)
itEnd = end;
// trail out itWindowBeg
for (window_offset_left = (windowKeySize - subWindowKeySize) / 2;
window_offset_left > itWindowBeg->count; ++itWindowBeg)
window_offset_left -= itWindowBeg->count;
// trail out itWindowEnd
for (window_offset_right = (windowKeySize + subWindowKeySize) / 2;
window_offset_right > itWindowEnd->count; ++itWindowEnd)
window_offset_right -= itWindowEnd->count;
while (itEnd != end)
{
if (enoughUniqueGoodGroups(itWindowBeg, itWindowEnd, minUniqueKeys))
{
beg = itBeg;
end = itEnd;
return true;
}
// okay, jump key_hop_size on end iterator
for (offset_right += key_hop_size; itEnd != end && offset_right > itEnd->count; ++itEnd)
offset_right -= itEnd->count;
// if we didn't hop the full hop size, modify the hop size to only hop as far as we hopped
if (itEnd == end)
key_hop_size -= offset_right;
for (offset_left += key_hop_size; offset_left > itBeg->count; ++itBeg)
offset_left -= itBeg->count;
for (window_offset_right += key_hop_size; window_offset_right > itWindowEnd->count; ++itWindowEnd)
window_offset_right -= itWindowEnd->count;
for (window_offset_left += key_hop_size; window_offset_left > itWindowBeg->count; ++itWindowBeg)
window_offset_left -= itWindowBeg->count;
}
beg = itBeg;
end = itEnd;
return enoughUniqueGoodGroups(itWindowBeg, itWindowEnd, minUniqueKeys);
}
// -----------------------------------------------------------------------------
template <typename TGroupData>
bool
reduceGroups(
std::vector<TGroupData>& groups, unsigned int startKeySize,
unsigned int windowKeySize, unsigned int subWindowKeySize, unsigned int minUniqueKeys )
{
unsigned int offset_left = 0;
unsigned int offset_right = 0;
typename std::vector<TGroupData>::iterator begIt = groups.begin();
typename std::vector<TGroupData>::iterator endIt = groups.end();
simpleSkip(begIt, endIt, startKeySize);
bool result = findSignificantGroups( begIt, endIt,
offset_left, offset_right,
windowKeySize, subWindowKeySize, minUniqueKeys );
if ( !result )
{
groups.clear();
return false;
}
begIt->count -= offset_left;
if (offset_right > 0 && endIt != groups.end())
{
endIt->count = offset_right;
++endIt;
}
std::vector<TGroupData> resGrups(begIt, endIt);
groups.swap(resGrups);
return true;
}
// -------------------------------------------------------------------------
template <typename GroupDataIt>
inline bool enoughUniqueGoodGroups(
const GroupDataIt& beg,
const GroupDataIt& end,
unsigned int minUniqueKeys)
{
std::set<unsigned int> groupKeys;
for (GroupDataIt it = beg; it != end && static_cast<unsigned int>(groupKeys.size()) < minUniqueKeys; ++it)
{
if (it->count > MAX_GOOD_GROUP_SIZE)
return false;
groupKeys.insert(it->key);
}
return static_cast<unsigned int>(groupKeys.size()) >= minUniqueKeys;
}
// -----------------------------------------------------------------------------
//////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////
// Used by the fingerprint keys operation
// minimum and maximum frequency to consider
#define MINFREQ 300
#define MAXFREQ 2000
// amount of time in a frame
#define FRAME_TLEN ((float) FRAMESIZE / (float) DFREQ)
#define MINCOEF (FRAME_TLEN * MINFREQ)
#define round__(x) ((int)(x + .5))
struct RawFilter
{
unsigned int ftid;
float thresh;
float weight;
};
const RawFilter rFilters[] = {
{ 26752, -4.37515e-07f, 0.260836f }, // filterID, threshold, alpha (weight)
{ 23871, -2.44615e-05f, 0.263986f },
{ 26777, -3.69244e-08f, 0.267763f },
{ 4635, -1.13672e-05f, 0.269428f },
{ 2937, 5.28804e-09f, 0.271896f },
{ 27405, -0.000126494f, 0.272362f },
{ 10782, 4.27478e-08f, 0.272609f },
{ 21033, -6.7912e-07f, 0.276099f },
{ 27117, 8.07178e-06f, 0.277762f },
{ 27072, 2.46044e-05f, 0.27883f },
{ 24228, 4.11255e-07f, 0.281743f },
{ 23838, 0.000228396f, 0.284479f },
{ 17165, -1.19495e-07f, 0.286304f },
{ 25263, 0.000398279f, 0.287066f },
{ 20721, 7.15095e-07f, 0.288913f },
{ 8502, -2.78361e-07f, 0.290424f },
{ 17175, -1.08429e-08f, 0.292219f },
{ 17811, -3.29527e-08f, 0.292554f },
{ 27495, -4.47575e-07f, 0.290119f },
{ 23538, -3.04273e-09f, 0.294539f },
{ 8205, 4.02691e-07f, 0.293525f },
{ 12177, 1.16873e-06f, 0.293832f },
{ 27051, -0.000902544f, 0.296453f },
{ 27111, -2.38425e-05f, 0.297428f },
{ 21779, -1.0669e-07f, 0.297302f },
{ 14817, -9.52849e-09f, 0.299f },
{ 27087, 1.22163e-05f, 0.296502f },
{ 27081, -2.8758e-09f, 0.300112f },
{ 20394, 1.28237e-06f, 0.298693f },
{ 28209, 0.000624447f, 0.29812f },
{ 23533, -2.19406e-06f, 0.299773f },
{ 23865, -1.28037e-08f, 0.300777f } // this is iteration 1
};
// -----------------------------------------------------------------------------
}
// -----------------------------------------------------------------------------
#endif // __FINGERPRINT_HELPER_FUNCTIONS_H

136
thirdparty/liblastfm2/src/global.h vendored Normal file
View File

@@ -0,0 +1,136 @@
/*
Copyright 2009 Last.fm Ltd.
- Primarily authored by Max Howell, Jono Cole and Doug Mansell
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 LASTFM_GLOBAL_H
#define LASTFM_GLOBAL_H
#define LASTFM_VERSION 0x00000400
#define LASTFM_VERSION_STRING "0.4.0"
#define LASTFM_MAJOR_VERSION 0
#define LASTFM_MINOR_VERSION 4
#define LASTFM_PATCH_VERSION 0
#include <QtGlobal>
#ifdef Q_CC_MSVC
#ifdef LASTFM_LIB
#define LASTFM_DLLEXPORT __declspec(dllexport)
#else
#define LASTFM_DLLEXPORT __declspec(dllimport)
#endif
#ifdef LASTFM_FINGERPRINT_LIB
#define LASTFM_FINGERPRINT_DLLEXPORT __declspec(dllexport)
#else
#define LASTFM_FINGERPRINT_DLLEXPORT __declspec(dllimport)
#endif
#elif __GNUC__ >= 4
#define LASTFM_DLLEXPORT __attribute__ ((visibility("default")))
#define LASTFM_FINGERPRINT_DLLEXPORT __attribute__ ((visibility("default")))
#else
#define LASTFM_DLLEXPORT
#define LASTFM_FINGERPRINT_DLLEXPORT
#endif
#include <QMetaEnum>
#include <QString>
namespace lastfm
{
/** http://labs.trolltech.com/blogs/2008/10/09/coding-tip-pretty-printing-enum-values
* Tips for making this take a single parameter welcome! :)
*
* eg. lastfm::qMetaEnumString<QNetworkReply>( error, "NetworkError" );
*/
template <typename T> static inline QString qMetaEnumString( int enum_value, const char* enum_name )
{
QMetaObject meta = T::staticMetaObject;
for (int i=0; i < meta.enumeratorCount(); ++i)
{
QMetaEnum m = meta.enumerator(i);
if (m.name() == QLatin1String(enum_name))
return QLatin1String(m.valueToKey(enum_value));
}
return QString("Unknown enum value for \"%1\": %2").arg( enum_name ).arg( enum_value );
}
enum ImageSize
{
Small,
Medium,
Large, /** seemingly 174x174 */
ExtraLarge,
Mega
};
//convenience
class Album;
class Artist;
class Audioscrobbler;
class AuthenticatedUser;
class Fingerprint;
class FingerprintableSource;
class FingerprintId;
class Mbid;
class MutableTrack;
class NetworkAccessManager;
class Playlist;
class User;
class RadioStation;
class Tag;
class Track;
class XmlQuery;
class Xspf;
}
#ifdef LASTFM_COLLAPSE_NAMESPACE
using lastfm::Album;
using lastfm::Artist;
using lastfm::Audioscrobbler;
using lastfm::AuthenticatedUser;
using lastfm::Fingerprint;
using lastfm::FingerprintId;
using lastfm::Mbid;
using lastfm::MutableTrack;
using lastfm::Playlist;
using lastfm::User;
using lastfm::RadioStation;
using lastfm::Tag;
using lastfm::Track;
using lastfm::XmlQuery;
using lastfm::Xspf;
#endif
//convenience
class QDomDocument;
class QNetworkAccessManager;
class QNetworkReply;
//convenience for development
#include <QDebug>
#endif //LASTFM_GLOBAL_H

20
thirdparty/liblastfm2/src/lastfm.pro vendored Normal file
View File

@@ -0,0 +1,20 @@
TEMPLATE = lib
QT = core network xml
include( _files.qmake )
INSTALLS = target
target.path = /lib
win32{
DEFINES += LASTFM_LIB _ATL_DLL
LIBS += winhttp.lib wbemuuid.lib # ws configuration
}
mac{
LIBS += -framework SystemConfiguration # ws configuration
#TODO we should only use these with the carbon version of Qt!
LIBS += -framework Carbon -framework CoreFoundation # various
}
linux*{
QT += dbus
}

View File

@@ -0,0 +1,201 @@
/*
Copyright 2009 Last.fm Ltd.
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 <QRegExp>
#include <QStringList>
#include "RadioStation.h"
#include "../core/XmlQuery.h"
QRegExp rxDisco("opt:discovery\\|(\\S+)", Qt::CaseSensitive, QRegExp::RegExp2);
QRegExp rxRep("opt:rep\\|([\\d\\.]+)", Qt::CaseSensitive, QRegExp::RegExp2);
QRegExp rxMainstr("opt:mainstr\\|([\\d\\.]+)", Qt::CaseSensitive, QRegExp::RegExp2);
const float k_defaultRep(0.5);
const float k_defaultMainstr(0.5);
const bool k_defaultDisco(false);
//static
QList<lastfm::RadioStation>
lastfm::RadioStation::list( QNetworkReply* r )
{
QList<lastfm::RadioStation> result;
try {
foreach (XmlQuery xq, XmlQuery(ws::parse(r)).children("station")) {
lastfm::RadioStation rs( QUrl::fromPercentEncoding( xq["url"].text().toUtf8() ) );
rs.setTitle(xq["name"].text());
result.append(rs);
}
}
catch (ws::ParseError& e)
{
qWarning() << e.what();
}
return result;
}
void
lastfm::RadioStation::setString( const QString& string )
{
QString replaceString( string );
QString decodedString = QUrl::fromPercentEncoding( replaceString.replace( QChar('+'), QChar(' ') ).toUtf8() );
QRegExp rxRql( "lastfm:\\/\\/rql\\/(.+)$" );
QRegExp rxPersonal( "lastfm:\\/\\/user\\/(.+)\\/personal" );
QRegExp rxRecommended( "lastfm://user/(.+)\\/recommended" );
QRegExp rxNeighbours( "lastfm:\\/\\/user\\/(.+)\\/neighbours" );
QRegExp rxLoved( "lastfm:\\/\\/user\\/(.+)\\/loved" );
QRegExp rxGlobalTags( "lastfm:\\/\\/globaltags\\/(.+)" );
QRegExp rxSimilarArtists( "lastfm:\\/\\/artist\\/(.+)\\/similarartists" );
QRegExp rxUserTags( "lastfm:\\/\\/usertags\\/(.+)\\/(.+)" );
QRegExp rxPlaylist( "lastfm:\\/\\/playlist/(.+)\\/shuffle" );
if (rxRql.indexIn(decodedString) == 0)
setRql( QByteArray::fromBase64( rxRql.capturedTexts()[1].toAscii() ) );
else if (rxPersonal.indexIn(decodedString) == 0)
setRql( libraryStr( rxPersonal.capturedTexts()[1] ) );
else if ( rxRecommended.indexIn(decodedString) == 0)
setRql( recommendationsStr( rxRecommended.capturedTexts()[1] ) );
else if ( rxNeighbours.indexIn(decodedString) == 0)
setRql( neighbourhoodStr( rxNeighbours.capturedTexts()[1] ) );
else if ( rxLoved.indexIn(decodedString) == 0)
setRql( lovedTracksStr( rxLoved.capturedTexts()[1] ) );
else if ( rxGlobalTags.indexIn(decodedString) == 0)
setRql( globalTagStr( rxGlobalTags.capturedTexts()[1] ) );
else if ( rxSimilarArtists.indexIn(decodedString) == 0)
setRql( similarStr( rxSimilarArtists.capturedTexts()[1] ) );
else if ( rxUserTags.indexIn(decodedString) == 0)
setRql( userTagStr( rxUserTags.capturedTexts()[1], rxUserTags.capturedTexts()[2] ) );
else if ( rxPlaylist.indexIn(decodedString) == 0)
setRql( playlistStr( rxPlaylist.capturedTexts()[1].toInt() ) );
else
{
m_url = string;
}
}
bool
lastfm::RadioStation::setRep(float rep)
{
if ( m_rql.isEmpty() )
return false;
int indexIn = rxRep.indexIn(m_rql);
if ( indexIn != -1 )
{
if (rep != k_defaultRep)
m_rql.replace( indexIn, rxRep.capturedTexts()[0].length(), QString("opt:rep|%1").arg(rep) );
else
m_rql.replace( indexIn, rxRep.capturedTexts()[0].length(), "" );
}
else
{
// the rql doesn't have rep in it
// so append it to the end
if (rep != k_defaultRep)
m_rql.append( QString(" opt:rep|%1").arg(rep) );
}
setRql(m_rql);
return true;
}
bool
lastfm::RadioStation::setMainstr(float mainstr)
{
if ( m_rql.isEmpty() )
return false;
int indexIn = rxMainstr.indexIn(m_rql);
if ( indexIn != -1 )
{
if (mainstr != k_defaultMainstr)
m_rql.replace( indexIn, rxMainstr.capturedTexts()[0].length(), QString("opt:mainstr|%1").arg(mainstr) );
else
m_rql.replace( indexIn, rxMainstr.capturedTexts()[0].length(), "" );
}
else
{
// the rql doesn't have rep in it
// so append it to the end
if ( mainstr != k_defaultMainstr )
m_rql.append( QString(" opt:mainstr|%1").arg(mainstr) );
}
setRql(m_rql);
return true;
}
bool
lastfm::RadioStation::setDisco(bool disco)
{
if ( m_rql.isEmpty() )
return false;
int indexIn = rxDisco.indexIn(m_rql);
if ( indexIn != -1 )
{
if (disco)
m_rql.replace( indexIn, rxDisco.capturedTexts()[0].length(), "opt:discovery|true" );
else
m_rql.replace( indexIn, rxDisco.capturedTexts()[0].length(), "" );
}
else
{
// the rql doesn't have disco in it
// so append it to the end if it is set
if (disco)
m_rql.append( " opt:discovery|true" );
}
setRql(m_rql);
return true;
}
float lastfm::RadioStation::rep() const
{
if ( rxRep.indexIn(m_rql) != -1 )
return rxRep.capturedTexts()[1].toFloat();
return k_defaultRep;
}
float lastfm::RadioStation::mainstr() const
{
if ( rxMainstr.indexIn(m_rql) != -1 )
return rxMainstr.capturedTexts()[1].toFloat();
return k_defaultMainstr;
}
bool lastfm::RadioStation::disco() const
{
if ( rxDisco.indexIn(m_rql) != -1 )
return rxDisco.capturedTexts()[1] == "true";
return k_defaultDisco;
}

View File

@@ -0,0 +1,123 @@
/*
Copyright 2009 Last.fm Ltd.
- Primarily authored by Max Howell, Jono Cole and Doug Mansell
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 LASTFM_RADIO_STATION_H
#define LASTFM_RADIO_STATION_H
#include <lastfm/User>
#include <lastfm/Tag>
#include <lastfm/Artist>
namespace lastfm
{
/** @author <jono@last.fm>
*/
class LASTFM_DLLEXPORT RadioStation
{
public:
RadioStation()
{}
RadioStation( const QString& s )
{
setString( s );
}
static RadioStation library( const lastfm::User& user ) { return rql( libraryStr( user ) ); }
static RadioStation recommendations( const lastfm::User& user ) { return rql( recommendationsStr( user ) ); }
static RadioStation neighbourhood( const lastfm::User& user ) { return rql( neighbourhoodStr( user ) ); }
static RadioStation lovedTracks( const lastfm::User& user ) { return rql( lovedTracksStr( user ) ); }
static RadioStation globalTag( const lastfm::Tag& tag ) { return rql( globalTagStr( tag ) ); }
static RadioStation similar( const lastfm::Artist& artist ) { return rql( similarStr( artist ) ); }
static RadioStation userTag( const lastfm::User& user, const lastfm::Tag& tag) { return rql( userTagStr( user, tag ) ); }
static RadioStation playlist( int playlistId ) { return rql( playlistStr( playlistId ) ); }
static RadioStation adventure( const lastfm::User& user ) { return rql( adventureStr( user ) ); }
static RadioStation rql( const QString& rql )
{
RadioStation station;
station.setRql( rql );
return station;
}
/** eg. "mxcl's Loved Tracks"
* It is worth noting that the Radio doesn't set the title of RadioStation
* object until we have tuned to it, and then we only set the one we give
* you back.
*/
QString title() const { return m_title; }
/** the Last.fm url, eg. lastfm://user/mxcl/loved */
QString url() const { return m_url; }
QString rql() const { return m_rql; }
void setTitle( const QString& s ) { m_title = s; }
bool setRep(float rep);
bool setMainstr(float mainstr);
bool setDisco(bool disco);
float rep() const;
float mainstr() const;
bool disco() const;
bool isLegacyPlaylist() const
{
return m_url.startsWith( "lastfm://play/" ) ||
m_url.startsWith( "lastfm://preview/" ) ||
m_url.startsWith( "lastfm://track/" ) ||
m_url.startsWith( "lastfm://playlist/" );
}
// good for getRecentStations:
static QList<RadioStation> list( QNetworkReply* );
private:
void setRql( const QString& rql )
{
m_rql = rql;
m_url = "lastfm://rql/" + QString(rql.toUtf8().toBase64());
}
void setString( const QString& s );
static QString libraryStr( const lastfm::User& user ) { return "library:" + user ; }
static QString recommendationsStr( const lastfm::User& user ) { return "rec:" + user ; }
static QString neighbourhoodStr( const lastfm::User& user ) { return "neigh:" + user ; }
static QString lovedTracksStr( const lastfm::User& user ) { return "loved:" + user ; }
static QString globalTagStr( const lastfm::Tag& tag ) { return "tag:\"" + tag + "\"" ; }
static QString similarStr( const lastfm::Artist& artist ) { return "simart:\"" + artist + "\""; }
static QString userTagStr( const lastfm::User& user, const lastfm::Tag& tag) { return "ptag:\"" + tag + "\"|" + user ; }
static QString playlistStr( int playlistId ) { return "playlist:" + QString::number(playlistId) ; }
static QString adventureStr( const lastfm::User& user ) { return "adv:" + user ; }
private:
QString m_rql;
QString m_url;
QString m_title;
};
}
Q_DECLARE_METATYPE( lastfm::RadioStation )
inline QDebug operator<<( QDebug d, const lastfm::RadioStation& station )
{
return d << station.url();
}
#endif

View File

@@ -0,0 +1,152 @@
/*
Copyright 2009 Last.fm Ltd.
- Primarily authored by Max Howell, Jono Cole and Doug Mansell
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 "RadioTuner.h"
#include "../core/XmlQuery.h"
#include "../types/Xspf.h"
#include "../ws/ws.h"
using namespace lastfm;
//TODO skips left
//TODO multiple locations for the same track
//TODO set rtp flag in getPlaylist (whether user is scrobbling this radio session or not)
// limit the number of retries following empty playlists:
#define MAX_TUNING_ATTEMPTS 3
RadioTuner::RadioTuner( const RadioStation& station )
: m_retry_counter( 0 )
{
//Empty RadioStation implies that the radio
//should tune to the previous station.
if( station.url().isEmpty() ) {
fetchFiveMoreTracks();
return;
}
QMap<QString, QString> map;
map["method"] = "radio.tune";
map["station"] = station.url();
map["additional_info"] = "1";
QNetworkReply* reply = ws::post(map);
connect( reply, SIGNAL(finished()), SLOT(onTuneReturn()) );
}
void
RadioTuner::retune( const RadioStation& station)
{
m_queue.clear();
QMap<QString, QString> map;
map["method"] = "radio.tune";
map["station"] = station.url();
map["additional_info"] = "1";
QNetworkReply* reply = ws::post(map);
connect( reply, SIGNAL(finished()), SLOT(onTuneReturn()) );
}
void
RadioTuner::onTuneReturn()
{
try {
XmlQuery lfm = ws::parse( (QNetworkReply*)sender() );
// TODO: uncomment this is we are to get a radio station
// name when we tune to an rql radio station
//emit title( lfm["station"]["name"].text() );
qDebug() << lfm;
emit supportsDisco( lfm["station"]["supportsdiscovery"].text() == "1" );
fetchFiveMoreTracks();
}
catch (ws::ParseError& e)
{
emit error( e.enumValue() );
}
}
bool
RadioTuner::fetchFiveMoreTracks()
{
//TODO check documentation, I figure this needs a session key
QMap<QString, QString> map;
map["method"] = "radio.getPlaylist";
map["additional_info"] = "1";
map["rtp"] = "1"; // see above
QNetworkReply* reply = ws::post( map );
connect( reply, SIGNAL(finished()), SLOT(onGetPlaylistReturn()) );
return true;
}
bool
RadioTuner::tryAgain()
{
qDebug() << "Bad response count" << m_retry_counter;
if (++m_retry_counter > MAX_TUNING_ATTEMPTS)
return false;
fetchFiveMoreTracks();
return true;
}
void
RadioTuner::onGetPlaylistReturn()
{
try {
XmlQuery lfm = ws::parse( (QNetworkReply*)sender() );
Xspf xspf( lfm["playlist"] );
QList<Track> tracks( xspf.tracks() );
if (tracks.isEmpty()) {
// give up after too many empty playlists :(
if (!tryAgain())
emit error( ws::NotEnoughContent );
} else {
m_retry_counter = 0;
foreach (Track t, tracks)
MutableTrack( t ).setSource( Track::LastFmRadio );
m_queue += tracks;
emit trackAvailable();
}
}
catch (ws::ParseError& e)
{
qWarning() << e.what();
emit error( e.enumValue() );
}
}
Track
RadioTuner::takeNextTrack()
{
//TODO presumably, we should check if fetchMoreTracks is working?
if (m_queue.isEmpty())
return Track();
Track result = m_queue.takeFirst();
if (m_queue.isEmpty())
fetchFiveMoreTracks();
return result;
}

View File

@@ -0,0 +1,76 @@
/*
Copyright 2009 Last.fm Ltd.
- Primarily authored by Max Howell, Jono Cole and Doug Mansell
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 LASTFM_TUNER_H
#define LASTFM_TUNER_H
#include <lastfm/RadioStation>
#include <lastfm/Track>
#include <lastfm/ws.h>
#include <QList>
namespace lastfm
{
/** With regard to error handling. We handle Ws::TryAgain up to 5 times,
* don't try again after that! Just tell the user to try again later.
*/
class LASTFM_DLLEXPORT RadioTuner : public QObject
{
Q_OBJECT
public:
/** You need to have assigned Ws::* for this to work, creating the tuner
* automatically fetches the first 5 tracks for the station */
explicit RadioTuner( const RadioStation& );
Track takeNextTrack();
void retune( const RadioStation& );
signals:
void title( const QString& );
void supportsDisco( bool supportsDisco );
void trackAvailable();
void error( lastfm::ws::Error );
private slots:
void onTuneReturn();
void onGetPlaylistReturn();
private:
/** Tries again up to 5 times
* @returns true if we tried again, otherwise you should emit error */
bool tryAgain();
/** Will emit 5 tracks from tracks(), they have to played within an hour
* or the streamer will refuse to stream them. Also the previous five are
* invalidated apart from the one that is currently playing, so sorry, you
* can't build up big lists of tracks.
*
* I feel I must point out that asking the user which one they want to play
* is also not allowed according to our terms and conditions, which you
* already agreed to in order to get your API key. Sorry about that dude.
*/
bool fetchFiveMoreTracks();
QList<Track> m_queue;
uint m_retry_counter;
};
}
#endif

View File

@@ -0,0 +1,202 @@
/*
Copyright 2009 Last.fm Ltd.
- Primarily authored by Max Howell, Jono Cole and Doug Mansell
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 <QPointer>
#include "Audioscrobbler.h"
#include "ScrobbleCache.h"
#include "../types/User.h"
#include "../types/Track.h"
#include "../ws/ws.h"
#include "../core/XmlQuery.h"
namespace lastfm
{
class AudioscrobblerPrivate
{
public:
AudioscrobblerPrivate(const QString& id)
: m_id( id )
, m_cache( ws::Username )
{}
~AudioscrobblerPrivate()
{
}
const QString m_id;
ScrobbleCache m_cache;
QList<Track> m_batch;
QPointer<QNetworkReply> m_nowPlayingReply;
QPointer<QNetworkReply> m_scrobbleReply;
Track m_nowPlayingTrack;
};
}
lastfm::Audioscrobbler::Audioscrobbler( const QString& id )
: d( new AudioscrobblerPrivate(id) )
{
submit();
}
lastfm::Audioscrobbler::~Audioscrobbler()
{
delete d;
}
void
lastfm::Audioscrobbler::nowPlaying( const Track& track )
{
if ( d->m_nowPlayingReply.isNull())
{
d->m_nowPlayingTrack = track;
d->m_nowPlayingReply = track.updateNowPlaying();
connect( d->m_nowPlayingReply, SIGNAL(finished()), SLOT(onNowPlayingReturn()));
}
}
void
lastfm::Audioscrobbler::cache( const Track& track )
{
QList<Track> tracks;
tracks.append( track );
cacheBatch( tracks );
}
void
lastfm::Audioscrobbler::cacheBatch( const QList<Track>& tracks )
{
d->m_cache.add( tracks );
foreach ( const Track& track, d->m_cache.tracks() )
MutableTrack( track ).setScrobbleStatus( Track::Cached );
emit scrobblesCached( tracks );
submit();
}
void
lastfm::Audioscrobbler::submit()
{
if (d->m_cache.tracks().isEmpty() // there are no tracks to submit
|| !d->m_scrobbleReply.isNull() ) // we are already submitting scrobbles
return;
// copy tracks to be submitted to a temporary list
d->m_batch = d->m_cache.tracks().mid( 0, 50 );
// if there is only one track use track.scrobble, otherwise use track.scrobbleBatch
if (d->m_batch.count() == 1)
d->m_scrobbleReply = d->m_batch[0].scrobble();
else
d->m_scrobbleReply = lastfm::Track::scrobble( d->m_batch );
connect( d->m_scrobbleReply, SIGNAL(finished()), SLOT(onTrackScrobbleReturn()));
}
void
lastfm::Audioscrobbler::parseTrack( const XmlQuery& trackXml, const Track& track )
{
MutableTrack mTrack = MutableTrack( track );
bool isScrobble = QDomElement(trackXml).tagName() == "scrobble";
if ( trackXml["ignoredMessage"].attribute("code") == "0" )
{
if ( isScrobble ) mTrack.setScrobbleStatus( Track::Submitted );
// corrections!
if ( trackXml["track"].attribute("corrected") == "1"
|| trackXml["artist"].attribute("corrected") == "1"
|| trackXml["album"].attribute("corrected") == "1"
|| trackXml["albumArtist"].attribute("corrected") == "1")
{
mTrack.setCorrections(trackXml["track"].text(),
trackXml["album"].text(),
trackXml["artist"].text(),
trackXml["albumArtist"].text());
}
}
else if ( isScrobble )
{
mTrack.setScrobbleError( static_cast<Track::ScrobbleError>(trackXml["ignoredMessage"].attribute("code").toInt()) );
mTrack.setScrobbleStatus( Track::Error );
}
}
void
lastfm::Audioscrobbler::onNowPlayingReturn()
{
lastfm::XmlQuery lfm = static_cast<QNetworkReply*>(sender())->readAll();
qDebug() << lfm;
if ( lfm.attribute("status") == "ok" )
parseTrack( lfm["nowplaying"], d->m_nowPlayingTrack );
else
emit nowPlayingError( lfm["error"].attribute("code").toInt(), lfm["error"].text() );
d->m_nowPlayingTrack = Track();
d->m_nowPlayingReply = 0;
}
void
lastfm::Audioscrobbler::onTrackScrobbleReturn()
{
lastfm::XmlQuery lfm = d->m_scrobbleReply->readAll();
qDebug() << lfm;
if (lfm.attribute("status") == "ok")
{
int index = 0;
foreach ( const XmlQuery& scrobble, lfm["scrobbles"].children("scrobble") )
parseTrack( scrobble, d->m_batch.at( index++ ) );
d->m_cache.remove( d->m_batch );
d->m_batch.clear();
}
else
{
// The scrobble submission failed
if ( !(lfm["error"].attribute("code") == "9" // Bad session
|| lfm["error"].attribute("code") == "11" // Service offline
|| lfm["error"].attribute("code") == "16") ) // Service temporarily unavailable
{
// clear the cache if it was not one of these error codes
d->m_cache.remove( d->m_batch );
d->m_batch.clear();
}
else
{
Q_ASSERT(false);
}
}
d->m_scrobbleReply = 0;
}

View File

@@ -0,0 +1,75 @@
/*
Copyright 2009 Last.fm Ltd.
- Primarily authored by Max Howell, Jono Cole and Doug Mansell
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 LASTFM_AUDIOSCROBBLER_H
#define LASTFM_AUDIOSCROBBLER_H
#include <lastfm/global.h>
#include <QByteArray>
#include <QList>
#include <QString>
#include <QObject>
#include <QVariant>
namespace lastfm
{
/** @author Max Howell <max@last.fm>
* An implementation of the Audioscrobbler Realtime Submissions Protocol
* version 1.2.1 for a single Last.fm user
* http://www.audioscrobbler.net/development/protocol/
*/
class LASTFM_DLLEXPORT Audioscrobbler : public QObject
{
Q_OBJECT
public:
/** You will need to do QCoreApplication::setVersion and
* QCoreApplication::setApplicationName for this to work, also you will
* need to have set all the keys in the Ws namespace in WsKeys.h */
Audioscrobbler( const QString& clientId );
~Audioscrobbler();
signals:
void scrobblesCached( const QList<lastfm::Track>& tracks );
void nowPlayingError( int code, QString message );
public slots:
/** will ask Last.fm to update the now playing information for the
* authenticated user */
void nowPlaying( const Track& );
/** will cache the track and call submit() */
void cache( const Track& );
void cacheBatch( const QList<Track>& );
/** will submit the submission cache for this user */
void submit();
private slots:
void onNowPlayingReturn();
void onTrackScrobbleReturn();
private:
void parseTrack( const XmlQuery& trackXml, const Track& track );
private:
class AudioscrobblerPrivate* d;
};
}
#endif

View File

@@ -0,0 +1,163 @@
/*
Copyright 2009 Last.fm Ltd.
- Primarily authored by Max Howell, Jono Cole and Doug Mansell
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 "ScrobbleCache.h"
#include "ScrobblePoint.h"
#include <lastfm/misc.h>
#include <QCoreApplication>
#include <QFile>
#include <QDomElement>
#include <QDomDocument>
#if LASTFM_VERSION >= 0x00010000
using lastfm::ScrobbleCache;
#endif
ScrobbleCache::ScrobbleCache( const QString& username )
{
Q_ASSERT( username.length() );
m_path = lastfm::dir::runtimeData().filePath( username + "_subs_cache.xml" );
m_username = username;
QDomDocument xml;
read( xml );
}
bool
ScrobbleCache::isValid( const Track& track, Invalidity* v )
{
#define TEST( test, x ) \
if (test) { \
if (v) *v = x; \
return false; \
}
TEST( track.duration() < ScrobblePoint::kScrobbleMinLength, TooShort );
TEST( !track.timestamp().isValid(), NoTimestamp );
// actual spam prevention is something like 12 hours, but we are only
// trying to weed out obviously bad data, server side criteria for
// "the future" may change, so we should let the server decide, not us
TEST( track.timestamp() > QDateTime::currentDateTime().addMonths( 1 ), FromTheFuture );
TEST( track.timestamp() < QDateTime::fromString( "2003-01-01", Qt::ISODate ), FromTheDistantPast );
// Check if any required fields are empty
TEST( track.artist().isNull(), ArtistNameMissing );
TEST( track.title().isEmpty(), TrackNameMissing );
TEST( (QStringList() << "unknown artist"
<< "unknown"
<< "[unknown]"
<< "[unknown artist]").contains( track.artist().name().toLower() ),
ArtistInvalid );
return true;
}
void
ScrobbleCache::read( QDomDocument& xml )
{
m_tracks.clear();
QFile file( m_path );
file.open( QFile::Text | QFile::ReadOnly );
QTextStream stream( &file );
stream.setCodec( "UTF-8" );
xml.setContent( stream.readAll() );
for (QDomNode n = xml.documentElement().firstChild(); !n.isNull(); n = n.nextSibling())
if (n.nodeName() == "track")
m_tracks += Track( n.toElement() );
}
void
ScrobbleCache::write()
{
if (m_tracks.isEmpty())
{
QFile::remove( m_path );
}
else {
QDomDocument xml;
QDomElement e = xml.createElement( "submissions" );
e.setAttribute( "product", QCoreApplication::applicationName() );
e.setAttribute( "version", "2" );
foreach (Track i, m_tracks)
e.appendChild( i.toDomElement( xml ) );
xml.appendChild( e );
QFile file( m_path );
file.open( QIODevice::WriteOnly | QIODevice::Text );
QTextStream stream( &file );
stream.setCodec( "UTF-8" );
stream << "<?xml version='1.0' encoding='utf-8'?>\n";
stream << xml.toString( 2 );
file.close();
}
}
void
ScrobbleCache::add( const QList<Track>& tracks )
{
foreach (const Track& track, tracks)
{
ScrobbleCache::Invalidity invalidity;
if ( !isValid( track, &invalidity ) )
{
qWarning() << invalidity;
}
else if (track.isNull())
qDebug() << "Will not cache an empty track";
else
m_tracks += track;
}
write();
}
int
ScrobbleCache::remove( const QList<Track>& toremove )
{
QMutableListIterator<Track> i( m_tracks );
while (i.hasNext()) {
Track t = i.next();
for (int x = 0; x < toremove.count(); ++x)
if (toremove[x] == t)
i.remove();
}
write();
// yes we return # remaining, rather # removed, but this is an internal
// function and the behaviour is documented so it's alright imo --mxcl
return m_tracks.count();
}

View File

@@ -0,0 +1,84 @@
/*
Copyright 2009 Last.fm Ltd.
- Primarily authored by Max Howell, Jono Cole and Doug Mansell
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 LASTFM_SCROBBLE_CACHE_H
#define LASTFM_SCROBBLE_CACHE_H
#include "lastfm/Track"
#include <QList>
#include <QString>
#if LASTFM_VERSION >= 0x00010000
namespace lastfm {
#else
using lastfm::Track;
#endif
/** absolutely not thread-safe */
class LASTFM_DLLEXPORT ScrobbleCache
{
QString m_username;
void write(); /// writes m_tracks to m_path
protected:
ScrobbleCache()
{}
QString m_path;
QList<Track> m_tracks;
void read( QDomDocument& xml ); /// reads from m_path into m_tracks
public:
explicit ScrobbleCache( const QString& username );
/** note this is unique for Track::sameAs() and equal timestamps
* obviously playcounts will not be increased for the same timestamp */
void add( const QList<Track>& );
/** returns the number of tracks left in the queue */
int remove( const QList<Track>& );
QList<Track> tracks() const { return m_tracks; }
QString path() const { return m_path; }
QString username() const { return m_username; }
private:
bool operator==( const ScrobbleCache& ); //undefined
enum Invalidity
{
TooShort,
ArtistNameMissing,
TrackNameMissing,
ArtistInvalid,
NoTimestamp,
FromTheFuture,
FromTheDistantPast
};
bool isValid( const Track& track, Invalidity* = 0 );
};
#if LASTFM_VERSION >= 0x00010000
}
#endif
#endif

View File

@@ -0,0 +1,59 @@
/*
Copyright 2009 Last.fm Ltd.
- Primarily authored by Max Howell, Jono Cole and Doug Mansell
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 LASTFM_SCROBBLE_POINT_H
#define LASTFM_SCROBBLE_POINT_H
#include <lastfm/global.h>
#include <QtAlgorithms>
class LASTFM_DLLEXPORT ScrobblePoint
{
uint i;
public:
ScrobblePoint() : i( kScrobbleTimeMax )
{}
/** j is in seconds, and should be 50% the duration of a track */
explicit ScrobblePoint( uint j )
{
// we special case 0, returning kScrobbleTimeMax because we are
// cruel and callous people
if (j == 0) --j;
i = qBound( uint(kScrobbleMinLength),
j,
uint(kScrobbleTimeMax) );
}
operator uint() const { return i; }
// scrobbles can occur between these two percentages of track duration
static const uint kScrobblePointMin = 50;
static const uint kScrobblePointMax = 100;
static const uint kDefaultScrobblePoint = 50;
// Shortest track length allowed to scrobble in seconds
static const uint kScrobbleMinLength = 31;
// Upper limit for scrobble time in seconds
static const uint kScrobbleTimeMax = 240;
};
#endif

View File

@@ -0,0 +1,43 @@
/*
Copyright 2010 Last.fm Ltd.
- Primarily authored by Micahel Coffey and Jono Cole
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 ABSTRACTTYPE_H
#define ABSTRACTTYPE_H
#include <QDomElement>
#include <QString>
#include <QUrl>
#include <lastfm/global.h>
namespace lastfm
{
class LASTFM_DLLEXPORT AbstractType
{
public:
virtual QString toString() const = 0;
virtual QDomElement toDomElement( QDomDocument& ) const = 0;
virtual QUrl www() const = 0;
virtual QUrl imageUrl( ImageSize size, bool square ) const = 0;
virtual ~AbstractType() {;}
};
};
#endif // ABSTRACTTYPE_H

View File

@@ -0,0 +1,87 @@
/*
Copyright 2009-2010 Last.fm Ltd.
- Primarily authored by Max Howell, Jono Cole and Doug Mansell
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 "Album.h"
#include "Artist.h"
#include "User.h"
#include "../core/UrlBuilder.h"
#include "../core/XmlQuery.h"
#include "../ws/ws.h"
#include <QFile>
#include <QStringList>
#include <QTimer>
QNetworkReply*
lastfm::Album::getInfo(const QString& user, const QString& sk) const
{
QMap<QString, QString> map;
map["method"] = "album.getInfo";
map["artist"] = m_artist;
map["album"] = m_title;
if (!user.isEmpty()) map["username"] = user;
if (!sk.isEmpty()) map["sk"] = sk;
return lastfm::ws::get(map);
}
QNetworkReply*
lastfm::Album::getTags() const
{
QMap<QString, QString> map;
map["method"] = "album.getTags";
map["artist"] = m_artist;
map["album"] = m_title;
return lastfm::ws::get(map);
}
QNetworkReply*
lastfm::Album::share( const QStringList& recipients, const QString& message, bool isPublic ) const
{
QMap<QString, QString> map;
map["method"] = "album.share";
map["artist"] = m_artist;
map["album"] = m_title;
map["recipient"] = recipients.join(",");
map["public"] = isPublic ? "1" : "0";
if (message.size()) map["message"] = message;
return lastfm::ws::post(map);
}
QUrl
lastfm::Album::www() const
{
return lastfm::UrlBuilder( "music" ).slash( m_artist ).slash( m_title ).url();
}
QNetworkReply*
lastfm::Album::addTags( const QStringList& tags ) const
{
if (tags.isEmpty())
return 0;
QMap<QString, QString> map;
map["method"] = "album.addTags";
map["artist"] = m_artist;
map["album"] = m_title;
map["tags"] = tags.join( QChar(',') );
return lastfm::ws::post(map);
}

73
thirdparty/liblastfm2/src/types/Album.h vendored Normal file
View File

@@ -0,0 +1,73 @@
/*
Copyright 2009 Last.fm Ltd.
- Primarily authored by Max Howell, Jono Cole and Doug Mansell
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 LASTFM_ALBUM_H
#define LASTFM_ALBUM_H
#include <lastfm/Artist>
#include <lastfm/Mbid>
#include <QString>
#include <QUrl>
namespace lastfm
{
class LASTFM_DLLEXPORT Album
{
Mbid m_mbid;
Artist m_artist;
QString m_title;
public:
Album()
{}
explicit Album( Mbid mbid ) : m_mbid( mbid )
{}
Album( Artist artist, QString title ) : m_artist( artist ), m_title( title )
{}
bool operator==( const Album& that ) const { return m_title == that.m_title && m_artist == that.m_artist; }
bool operator!=( const Album& that ) const { return m_title != that.m_title || m_artist != that.m_artist; }
operator QString() const { return title(); }
QString title() const { return m_title.isEmpty() ? "[unknown]" : m_title; }
Artist artist() const { return m_artist; }
Mbid mbid() const { return m_mbid; }
/** artist may have been set, since we allow that in the ctor, but should we handle untitled albums? */
bool isNull() const { return m_title.isEmpty() && m_mbid.isNull(); }
/** Album.getInfo WebService */
QNetworkReply* getInfo(const QString& user = "", const QString& sk = "") const;
QNetworkReply* share( const QStringList& recipients, const QString& message = "", bool isPublic = true ) const;
/** use Tag::list to get the tag list out of the finished reply */
QNetworkReply* getTags() const;
QNetworkReply* getTopTags() const;
/** Last.fm dictates that you may submit at most 10 of these */
QNetworkReply* addTags( const QStringList& ) const;
/** the Last.fm website url for this album */
QUrl www() const;
};
}
#endif //LASTFM_ALBUM_H

View File

@@ -0,0 +1,202 @@
/*
Copyright 2009 Last.fm Ltd.
- Primarily authored by Max Howell, Jono Cole and Doug Mansell
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 "Artist.h"
#include "User.h"
#include "../core/UrlBuilder.h"
#include "../core/XmlQuery.h"
#include "../ws/ws.h"
#include <QRegExp>
#include <QStringList>
using lastfm::Artist;
using lastfm::User;
using lastfm::ImageSize;
using lastfm::XmlQuery;
QUrl
Artist::imageUrl( ImageSize size, bool square ) const
{
if( !square ) return m_images.value( size );
QUrl url = m_images.value( size );
QRegExp re( "/serve/(\\d*)s?/" );
return QUrl( url.toString().replace( re, "/serve/\\1s/" ));
}
static inline QList<QUrl> images( const lastfm::XmlQuery& e )
{
QList<QUrl> images;
images += e["image size=small"].text();
images += e["image size=medium"].text();
images += e["image size=large"].text();
return images;
}
Artist::Artist( const XmlQuery& xml )
:AbstractType()
{
m_name = xml["name"].text();
m_images = images( xml );
}
QMap<QString, QString> //private
Artist::params( const QString& method ) const
{
QMap<QString, QString> map;
map["method"] = "artist."+method;
map["artist"] = m_name;
return map;
}
QNetworkReply*
Artist::share( const QStringList& recipients, const QString& message, bool isPublic ) const
{
QMap<QString, QString> map = params("share");
map["recipient"] = recipients.join(",");
map["public"] = isPublic ? "1" : "0";
if (message.size()) map["message"] = message;
return lastfm::ws::post(map);
}
QUrl
Artist::www() const
{
return UrlBuilder( "music" ).slash( Artist::name() ).url();
}
QNetworkReply*
Artist::getEvents(int limit) const
{
QMap<QString, QString> map = params("getEvents");
if (limit) map["limit"] = QString::number(limit);
return ws::get( map );
}
QNetworkReply*
Artist::getInfo(const QString& user, const QString& sk) const
{
QMap<QString, QString> map = params("getInfo");
if (!user.isEmpty()) map["username"] = user;
if (!sk.isEmpty()) map["sk"] = sk;
return ws::get( map );
}
QNetworkReply*
Artist::getTags() const
{
return ws::get( params("getTags") );
}
QNetworkReply*
Artist::getTopTags() const
{
return ws::get( params("getTopTags") );
}
QNetworkReply*
Artist::getSimilar() const
{
return ws::get( params("getSimilar") );
}
QNetworkReply*
Artist::search( int limit ) const
{
QMap<QString, QString> map = params("search");
if (limit > 0) map["limit"] = QString::number(limit);
return ws::get(map);
}
QMap<int, QString> /* static */
Artist::getSimilar( QNetworkReply* r )
{
QMap<int, QString> artists;
try
{
XmlQuery lfm = ws::parse(r);
foreach (XmlQuery e, lfm.children( "artist" ))
{
// convert floating percentage to int in range 0 to 10,000
int const match = e["match"].text().toFloat() * 100;
artists.insertMulti( match, e["name"].text() );
}
}
catch (ws::ParseError& e)
{
qWarning() << e.what();
}
return artists;
}
QList<Artist> /* static */
Artist::list( QNetworkReply* r )
{
QList<Artist> artists;
try {
XmlQuery lfm = ws::parse(r);
foreach (XmlQuery xq, lfm.children( "artist" )) {
Artist artist( xq );
artists += artist;
}
}
catch (ws::ParseError& e)
{
qWarning() << e.what();
}
return artists;
}
Artist
Artist::getInfo( QNetworkReply* r )
{
try {
XmlQuery lfm = ws::parse(r);
Artist artist = lfm["artist"]["name"].text();
artist.m_images = images( lfm["artist"] );
return artist;
}
catch (ws::ParseError& e)
{
qWarning() << e.what();
return Artist();
}
}
QNetworkReply*
Artist::addTags( const QStringList& tags ) const
{
if (tags.isEmpty())
return 0;
QMap<QString, QString> map = params("addTags");
map["tags"] = tags.join( QChar(',') );
return ws::post(map);
}

100
thirdparty/liblastfm2/src/types/Artist.h vendored Normal file
View File

@@ -0,0 +1,100 @@
/*
Copyright 2009-2010 Last.fm Ltd.
- Primarily authored by Max Howell, Jono Cole, Doug Mansell and Michael Coffey
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 LASTFM_ARTIST_H
#define LASTFM_ARTIST_H
#include <QMap>
#include <QString>
#include <QUrl>
#include <lastfm/AbstractType>
#include <lastfm/global.h>
namespace lastfm
{
class LASTFM_DLLEXPORT Artist : public AbstractType
{
private:
QString m_name;
QList<QUrl> m_images;
public:
Artist() : AbstractType()
{}
Artist( const QString& name ) : AbstractType(), m_name( name )
{}
Artist( const class XmlQuery& xml );
/** will be QUrl() unless you got this back from a getInfo or something call */
QUrl imageUrl( ImageSize size = Large, bool square = false ) const;
bool isNull() const { return m_name.isEmpty(); }
/** the url for this artist's page at www.last.fm */
QUrl www() const;
bool operator==( const Artist& that ) const { return m_name == that.m_name; }
bool operator!=( const Artist& that ) const { return m_name != that.m_name; }
operator QString() const
{
/** if no artist name is set, return the musicbrainz unknown identifier
* in case some part of the GUI tries to display it anyway. Note isNull
* returns false still. So you should have queried that! */
return m_name.isEmpty() ? "[unknown]" : m_name;
}
QString toString() const { return name(); }
QString name() const { return QString(*this); }
QDomElement toDomElement( QDomDocument& ) const { return QDomElement(); }
QNetworkReply* share( const QStringList& recipients, const QString& message = "", bool isPublic = true ) const;
QNetworkReply* getEvents(int limit = 0) const;
QNetworkReply* getInfo(const QString& user = "", const QString& sk = "") const;
static Artist getInfo( QNetworkReply* );
QNetworkReply* getSimilar() const;
/** The match percentage is returned from last.fm as a 4 significant
* figure floating point value. So we multply it by 100 to make an
* integer in the range of 0 to 10,000. This is possible confusing
* for you, but I felt it best not to lose any precision, and floats
* aren't much fun. */
static QMap<int, QString> getSimilar( QNetworkReply* );
/** use Tag::list to get the tag list out of the finished reply */
QNetworkReply* getTags() const;
QNetworkReply* getTopTags() const;
/** Last.fm dictates that you may submit at most 10 of these */
QNetworkReply* addTags( const QStringList& ) const;
QNetworkReply* search( int limit = -1 ) const;
static QList<Artist> list( QNetworkReply* );
QMap<QString, QString> params( const QString& method ) const;
};
}
#endif

View File

@@ -0,0 +1,55 @@
/*
Copyright 2009 Last.fm Ltd.
- Primarily authored by Max Howell, Jono Cole and Doug Mansell
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 "FingerprintId.h"
#include "../ws/ws.h"
#include <QtNetwork>
#include <QtXml>
QNetworkReply*
lastfm::FingerprintId::getSuggestions() const
{
if (isNull()) return 0;
QUrl const url( "http://ws.audioscrobbler.com/fingerprint/" + QString(*this) + ".xml" );
QNetworkRequest const request( url );
return lastfm::nam()->get( request );
}
QMap<float, lastfm::Track> //static
lastfm::FingerprintId::getSuggestions( QNetworkReply* reply )
{
QDomDocument xml;
xml.setContent( reply->readAll() );
QDomNodeList nodes = xml.documentElement().elementsByTagName( "track" );
QMap<float, Track> tracks;
for (int x = 0; x < nodes.count(); ++x)
{
QDomElement const e = nodes.at(x).toElement();
MutableTrack t;
t.setTitle( e.firstChildElement( "title" ).text() );
t.setArtist( e.firstChildElement( "artist" ).text() );
tracks.insert( e.attribute( "confidence", "0.0" ).toFloat(), t );
}
return tracks;
}

View File

@@ -0,0 +1,62 @@
/*
Copyright 2009 Last.fm Ltd.
- Primarily authored by Max Howell, Jono Cole and Doug Mansell
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 LASTFM_FINGERPRINT_ID_H
#define LASTFM_FINGERPRINT_ID_H
#include <lastfm/Track>
#include <QMap>
namespace lastfm
{
class LASTFM_DLLEXPORT FingerprintId
{
int id;
public:
FingerprintId() : id( -1 )
{}
FingerprintId( uint i ) : id( i )
{}
bool isNull() const { return id == -1; }
/** we query Last.fm for suggested metadata, how awesome is that?
* @returns null if isNull() */
QNetworkReply* getSuggestions() const;
static QMap<float,Track> getSuggestions( QNetworkReply* );
/** -1 if you need to generate it */
operator int() const { return id; }
/** isEmpty() if you need to generate it */
operator QString() const { return id == -1 ? "" : QString::number( id ); }
};
}
inline QDebug operator<<( QDebug d, lastfm::FingerprintId id)
{
if (id.isNull())
return d << "(null)";
else
return d << int(id);
}
#endif

View File

@@ -0,0 +1,36 @@
/*
Copyright 2009 Last.fm Ltd.
- Primarily authored by Max Howell, Jono Cole and Doug Mansell
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 "Mbid.h"
#include "mbid_mp3.c"
#include <QFile>
namespace lastfm
{
Mbid //static
Mbid::fromLocalFile( const QString& path )
{
char out[MBID_BUFFER_SIZE];
QByteArray const bytes = QFile::encodeName( path );
int const r = getMP3_MBID( bytes.data(), out );
Mbid mbid;
if (r == 0) mbid.id = QString::fromLatin1( out );
return mbid;
}
}

45
thirdparty/liblastfm2/src/types/Mbid.h vendored Normal file
View File

@@ -0,0 +1,45 @@
/*
Copyright 2009 Last.fm Ltd.
- Primarily authored by Max Howell, Jono Cole and Doug Mansell
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 LASTFM_MBID_H
#define LASTFM_MBID_H
#include <lastfm/global.h>
#include <QString>
namespace lastfm
{
class LASTFM_DLLEXPORT Mbid
{
QString id;
public:
explicit Mbid( const QString& p = "" ) : id( p )
{}
bool isNull() const { return id.isNull() || id.isEmpty(); }
operator QString() const { return id; }
/** if this is not an mp3 file you will be wasting time, as it won't work
* but we will do what you say anyway because you are the boss */
static Mbid fromLocalFile( const QString& path );
};
}
#endif

View File

@@ -0,0 +1,63 @@
/*
Copyright 2009 Last.fm Ltd.
- Primarily authored by Max Howell, Jono Cole and Doug Mansell
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 "Playlist.h"
#include "Track.h"
#include "../ws/ws.h"
QNetworkReply*
lastfm::Playlist::addTrack( const Track& t ) const
{
QMap<QString, QString> map;
map["method"] = "playlist.addTrack";
map["playlistID"] = m_id;
map["artist"] = t.artist();
map["track"] = t.title();
return lastfm::ws::post(map);
}
QNetworkReply*
lastfm::Playlist::fetch() const
{
return fetch( QUrl("lastfm://playlist/" + QString::number( m_id )) );
}
QNetworkReply* //static
lastfm::Playlist::fetch( const QUrl& url )
{
QMap<QString, QString> map;
map["method"] = "playlist.fetch";
map["playlistURL"] = url.toString();
return lastfm::ws::get(map);
}
QNetworkReply* //static
lastfm::Playlist::create( const QString& title, const QString& description /*=""*/ )
{
QMap<QString, QString> map;
map["method"] = "playlist.create";
map["title"] = title;
if (description.size())
map["description"] = description;
return lastfm::ws::post(map);
}

View File

@@ -0,0 +1,53 @@
/*
Copyright 2009 Last.fm Ltd.
- Primarily authored by Max Howell, Jono Cole and Doug Mansell
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 LASTFM_PLAYLIST_H
#define LASTFM_PLAYLIST_H
#include <lastfm/global.h>
#include <lastfm/Xspf>
#include <QString>
#include <QUrl>
namespace lastfm
{
class LASTFM_DLLEXPORT Playlist
{
int m_id;
Playlist() : m_id( -1 )
{}
public:
Playlist( int id ) : m_id( id )
{}
int id() const { return m_id; }
QNetworkReply* addTrack( const Track& ) const;
QNetworkReply* fetch() const;
static QNetworkReply* create( const QString& title, const QString& description = "" );
static QNetworkReply* fetch( const QUrl& url );
static Xspf fetch( QNetworkReply* );
};
}
#endif

77
thirdparty/liblastfm2/src/types/Tag.cpp vendored Normal file
View File

@@ -0,0 +1,77 @@
/*
Copyright 2009 Last.fm Ltd.
- Primarily authored by Max Howell, Jono Cole and Doug Mansell
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 "Tag.h"
#include "User.h"
#include "../core/UrlBuilder.h"
#include "../core/XmlQuery.h"
#include "../ws/ws.h"
using lastfm::Tag;
using lastfm::User;
QUrl
Tag::www() const
{
return UrlBuilder( "tag" ).slash( m_name ).url();
}
QUrl
Tag::www( const User& user ) const
{
return UrlBuilder( "user" ).slash( user.name() ).slash( "tags" ).slash( Tag::name() ).url();
}
QNetworkReply*
Tag::search() const
{
QMap<QString, QString> map;
map["method"] = "tag.search";
map["tag"] = m_name;
return ws::get(map);
}
//static
QNetworkReply*
Tag::getTopTags()
{
QMap<QString, QString> map;
map["method"] = "tag.getTopTags";
return ws::get(map);
}
QMap<int, QString> //static
Tag::list( QNetworkReply* r )
{
QMap<int, QString> tags;
try {
foreach (XmlQuery xq, XmlQuery(ws::parse(r)).children("tag"))
// we toLower always as otherwise it is ugly mixed case, as first
// ever tag decides case, and Last.fm is case insensitive about it
// anyway
tags.insertMulti( xq["count"].text().toInt(), xq["name"].text().toLower() );
}
catch (ws::ParseError& e)
{
qWarning() << e.what();
}
return tags;
}

60
thirdparty/liblastfm2/src/types/Tag.h vendored Normal file
View File

@@ -0,0 +1,60 @@
/*
Copyright 2009 Last.fm Ltd.
- Primarily authored by Max Howell, Jono Cole and Doug Mansell
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 LASTFM_TAG_H
#define LASTFM_TAG_H
#include <lastfm/global.h>
#include <QMap>
#include <QString>
#include <QUrl>
namespace lastfm
{
class LASTFM_DLLEXPORT Tag
{
QString m_name;
public:
Tag( const QString& name ) : m_name( name )
{}
operator QString() const { return m_name; }
QString name() const { return m_name; }
/** the global tag page at www.last.fm */
QUrl www() const;
/** the tag page for user @p user at www.last.fm */
QUrl www( const class User& user ) const;
/** pass the finished QNetworkReply to Tag::list() */
class QNetworkReply* search() const;
/** the top global tags on Last.fm, sorted by popularity (number of times used) */
static class QNetworkReply* getTopTags();
/** the integer is the weighting, not all list type return requests
* have a weighting, so the int may just be zero, if you don't care
* about the weight just do this:
* QStringList tags = Tag::list( reply ).values();
*/
static QMap<int, QString> list( QNetworkReply* );
};
}
#endif

View File

@@ -0,0 +1,481 @@
/*
Copyright 2009 Last.fm Ltd.
- Primarily authored by Max Howell, Jono Cole and Doug Mansell
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 "Track.h"
#include "User.h"
#include "../core/UrlBuilder.h"
#include "../core/XmlQuery.h"
#include "../ws/ws.h"
#include <QFileInfo>
#include <QStringList>
lastfm::TrackData::TrackData()
: trackNumber( 0 ),
duration( 0 ),
source( Track::Unknown ),
rating( 0 ),
fpid( -1 ),
loved( false ),
null( false ),
scrobbleStatus( Track::Null ),
scrobbleError( Track::None )
{}
lastfm::Track::Track()
:AbstractType()
{
d = new TrackData;
d->null = true;
}
lastfm::Track::Track( const QDomElement& e )
:AbstractType()
{
d = new TrackData;
if (e.isNull()) { d->null = true; return; }
d->artist = e.namedItem( "artist" ).toElement().text();
d->albumArtist = e.namedItem( "albumArtist" ).toElement().text();
d->album = e.namedItem( "album" ).toElement().text();
d->title = e.namedItem( "track" ).toElement().text();
d->correctedArtist = e.namedItem( "correctedArtist" ).toElement().text();
d->correctedAlbumArtist = e.namedItem( "correctedAlbumArtist" ).toElement().text();
d->correctedAlbum = e.namedItem( "correctedAlbum" ).toElement().text();
d->correctedTitle = e.namedItem( "correctedTrack" ).toElement().text();
d->trackNumber = 0;
d->duration = e.namedItem( "duration" ).toElement().text().toInt();
d->url = e.namedItem( "url" ).toElement().text();
d->rating = e.namedItem( "rating" ).toElement().text().toUInt();
d->source = e.namedItem( "source" ).toElement().text().toInt(); //defaults to 0, or lastfm::Track::Unknown
d->time = QDateTime::fromTime_t( e.namedItem( "timestamp" ).toElement().text().toUInt() );
d->loved = e.namedItem( "loved" ).toElement().text().toInt();
d->scrobbleStatus = e.namedItem( "scrobbleStatus" ).toElement().text().toInt();
d->scrobbleError = e.namedItem( "scrobbleError" ).toElement().text().toInt();
for (QDomElement image(e.firstChildElement("image")) ; !image.isNull() ; image = e.nextSiblingElement("image"))
{
d->m_images[static_cast<lastfm::ImageSize>(image.attribute("size").toInt())] = image.text();
}
QDomNodeList nodes = e.namedItem( "extras" ).childNodes();
for (int i = 0; i < nodes.count(); ++i)
{
QDomNode n = nodes.at(i);
QString key = n.nodeName();
d->extras[key] = n.toElement().text();
}
}
void
lastfm::TrackData::onLoveFinished()
{
XmlQuery lfm = static_cast<QNetworkReply*>(sender())->readAll();
if ( lfm.attribute( "status" ) == "ok")
loved = true;
emit loveToggled( loved );
}
void
lastfm::TrackData::onUnloveFinished()
{
XmlQuery lfm = static_cast<QNetworkReply*>(sender())->readAll();
if ( lfm.attribute( "status" ) == "ok")
loved = false;
emit loveToggled( loved );
}
void
lastfm::TrackData::onGotInfo()
{
lastfm::XmlQuery lfm( static_cast<QNetworkReply*>(sender())->readAll() );
QString imageUrl = lfm["track"]["image size=small"].text();
if ( !imageUrl.isEmpty() ) m_images[lastfm::Small] = imageUrl;
imageUrl = lfm["track"]["image size=medium"].text();
if ( !imageUrl.isEmpty() ) m_images[lastfm::Medium] = imageUrl;
imageUrl = lfm["track"]["image size=large"].text();
if ( !imageUrl.isEmpty() ) m_images[lastfm::Large] = imageUrl;
imageUrl = lfm["track"]["image size=extralarge"].text();
if ( !imageUrl.isEmpty() ) m_images[lastfm::ExtraLarge] = imageUrl;
imageUrl = lfm["track"]["image size=mega"].text();
if ( !imageUrl.isEmpty() ) m_images[lastfm::Mega] = imageUrl;
loved = lfm["track"]["userloved"].text().toInt();
emit gotInfo( lfm );
emit loveToggled( loved );
// you should connect everytime you call getInfo
disconnect( this, SIGNAL(gotInfo(const XmlQuery&)), 0, 0);
}
QDomElement
lastfm::Track::toDomElement( QDomDocument& xml ) const
{
QDomElement item = xml.createElement( "track" );
#define makeElement( tagname, getter ) { \
QString v = getter; \
if (!v.isEmpty()) \
{ \
QDomElement e = xml.createElement( tagname ); \
e.appendChild( xml.createTextNode( v ) ); \
item.appendChild( e ); \
} \
}
makeElement( "artist", d->artist );
makeElement( "albumArtist", d->albumArtist );
makeElement( "album", d->album );
makeElement( "track", d->title );
makeElement( "correctedArtist", d->correctedArtist );
makeElement( "correctedAlbumArtist", d->correctedAlbumArtist );
makeElement( "correctedAlbum", d->correctedAlbum );
makeElement( "correctedTrack", d->correctedTitle );
makeElement( "duration", QString::number( d->duration ) );
makeElement( "timestamp", QString::number( d->time.toTime_t() ) );
makeElement( "url", d->url.toString() );
makeElement( "source", QString::number( d->source ) );
makeElement( "rating", QString::number(d->rating) );
makeElement( "fpId", QString::number(d->fpid) );
makeElement( "mbId", mbid() );
makeElement( "loved", QString::number( isLoved() ) );
makeElement( "scrobbleStatus", QString::number( scrobbleStatus() ) );
makeElement( "scrobbleError", QString::number( scrobbleError() ) );
// put the images urls in the dom
QMapIterator<lastfm::ImageSize, QUrl> imageIter( d->m_images );
while (imageIter.hasNext()) {
QDomElement e = xml.createElement( "image" );
e.appendChild( xml.createTextNode( imageIter.next().value().toString() ) );
e.setAttribute( "size", imageIter.key() );
item.appendChild( e );
}
// add the extras to the dom
QDomElement extras = xml.createElement( "extras" );
QMapIterator<QString, QString> extrasIter( d->extras );
while (extrasIter.hasNext()) {
QDomElement e = xml.createElement( extrasIter.next().key() );
e.appendChild( xml.createTextNode( extrasIter.value() ) );
extras.appendChild( e );
}
item.appendChild( extras );
return item;
}
bool
lastfm::Track::corrected() const
{
// If any of the corrected string have been set and they are different
// from the initial strings then this track has been corrected.
return ( (!d->correctedTitle.isEmpty() && (d->correctedTitle != d->title))
|| (!d->correctedAlbum.isEmpty() && (d->correctedAlbum != d->album))
|| (!d->correctedArtist.isEmpty() && (d->correctedArtist != d->artist))
|| (!d->correctedAlbumArtist.isEmpty() && (d->correctedAlbumArtist != d->albumArtist)));
}
lastfm::Artist
lastfm::Track::artist( Corrections corrected ) const
{
if ( corrected == Corrected && !d->correctedArtist.isEmpty() )
return Artist( d->correctedArtist );
return Artist( d->artist );
}
lastfm::Artist
lastfm::Track::albumArtist( Corrections corrected ) const
{
if ( corrected == Corrected && !d->correctedAlbumArtist.isEmpty() )
return Artist( d->correctedAlbumArtist );
return Artist( d->albumArtist );
}
lastfm::Album
lastfm::Track::album( Corrections corrected ) const
{
if ( corrected == Corrected && !d->correctedAlbum.isEmpty() )
return Album( artist( corrected ), d->correctedAlbum );
return Album( artist( corrected ), d->album );
}
QString
lastfm::Track::title( Corrections corrected ) const
{
/** if no title is set, return the musicbrainz unknown identifier
* in case some part of the GUI tries to display it anyway. Note isNull
* returns false still. So you should have queried this! */
if ( corrected == Corrected && !d->correctedTitle.isEmpty() )
return d->correctedTitle;
return d->title.isEmpty() ? "[unknown]" : d->title;
}
QUrl
lastfm::Track::imageUrl( lastfm::ImageSize size, bool square ) const
{
if( !square ) return d->m_images.value( size );
QUrl url = d->m_images.value( size );
QRegExp re( "/serve/(\\d*)s?/" );
return QUrl( url.toString().replace( re, "/serve/\\1s/" ));
}
QString
lastfm::Track::toString( const QChar& separator, Corrections corrections ) const
{
if ( d->artist.isEmpty() )
{
if ( d->title.isEmpty() )
return QFileInfo( d->url.path() ).fileName();
else
return title( corrections );
}
if ( d->title.isEmpty() )
return artist( corrections );
return artist( corrections ) + ' ' + separator + ' ' + title( corrections );
}
QString //static
lastfm::Track::durationString( int const duration )
{
QTime t = QTime().addSecs( duration );
if (duration < 60*60)
return t.toString( "m:ss" );
else
return t.toString( "hh:mm:ss" );
}
QNetworkReply*
lastfm::Track::share( const QStringList& recipients, const QString& message, bool isPublic ) const
{
QMap<QString, QString> map = params("share");
map["recipient"] = recipients.join(",");
map["public"] = isPublic ? "1" : "0";
if (message.size()) map["message"] = message;
return ws::post(map);
}
void
lastfm::MutableTrack::setFromLfm( const XmlQuery& lfm )
{
QString imageUrl = lfm["track"]["image size=small"].text();
if ( !imageUrl.isEmpty() ) d->m_images[lastfm::Small] = imageUrl;
imageUrl = lfm["track"]["image size=medium"].text();
if ( !imageUrl.isEmpty() ) d->m_images[lastfm::Medium] = imageUrl;
imageUrl = lfm["track"]["image size=large"].text();
if ( !imageUrl.isEmpty() ) d->m_images[lastfm::Large] = imageUrl;
imageUrl = lfm["track"]["image size=extralarge"].text();
if ( !imageUrl.isEmpty() ) d->m_images[lastfm::ExtraLarge] = imageUrl;
imageUrl = lfm["track"]["image size=mega"].text();
if ( !imageUrl.isEmpty() ) d->m_images[lastfm::Mega] = imageUrl;
d->loved = lfm["track"]["userloved"].text().toInt();
d->forceLoveToggled( d->loved );
}
void
lastfm::MutableTrack::love()
{
QNetworkReply* reply = ws::post(params("love"));
QObject::connect( reply, SIGNAL(finished()), signalProxy(), SLOT(onLoveFinished()));
}
void
lastfm::MutableTrack::unlove()
{
QNetworkReply* reply = ws::post(params("unlove"));
QObject::connect( reply, SIGNAL(finished()), signalProxy(), SLOT(onUnloveFinished()));
}
QNetworkReply*
lastfm::MutableTrack::ban()
{
d->extras["rating"] = "B";
return ws::post(params("ban"));
}
QMap<QString, QString>
lastfm::Track::params( const QString& method, bool use_mbid ) const
{
QMap<QString, QString> map;
map["method"] = "Track."+method;
if (d->mbid.size() && use_mbid)
map["mbid"] = d->mbid;
else {
map["artist"] = d->artist;
map["track"] = d->title;
}
return map;
}
QNetworkReply*
lastfm::Track::getTopTags() const
{
return ws::get( params("getTopTags", true) );
}
QNetworkReply*
lastfm::Track::getTopFans() const
{
return ws::get( params("getTopFans", true) );
}
QNetworkReply*
lastfm::Track::getTags() const
{
return ws::get( params("getTags", true) );
}
void
lastfm::Track::getInfo(const QString& user, const QString& sk) const
{
QMap<QString, QString> map = params("getInfo", true);
if (!user.isEmpty()) map["username"] = user;
if (!sk.isEmpty()) map["sk"] = sk;
QObject::connect( ws::get( map ), SIGNAL(finished()), d.data(), SLOT(onGotInfo()));
}
QNetworkReply*
lastfm::Track::addTags( const QStringList& tags ) const
{
if (tags.isEmpty())
return 0;
QMap<QString, QString> map = params("addTags");
map["tags"] = tags.join( QChar(',') );
return ws::post(map);
}
QNetworkReply*
lastfm::Track::removeTag( const QString& tag ) const
{
if (tag.isEmpty())
return 0;
QMap<QString, QString> map = params( "removeTag" );
map["tags"] = tag;
return ws::post(map);
}
QNetworkReply*
lastfm::Track::updateNowPlaying() const
{
QMap<QString, QString> map = params("updateNowPlaying");
map["duration"] = QString::number( duration() );
if ( !album().isNull() ) map["album"] = album();
map["context"] = extra("playerId");
qDebug() << map;
return ws::post(map);
}
QNetworkReply*
lastfm::Track::scrobble() const
{
QMap<QString, QString> map = params("scrobble");
map["duration"] = QString::number( d->duration );
map["timestamp"] = QString::number( d->time.toTime_t() );
map["context"] = extra("playerId");
map["albumArtist"] = d->albumArtist;
if ( !d->album.isEmpty() ) map["album"] = d->album;
qDebug() << map;
return ws::post(map);
}
QNetworkReply*
lastfm::Track::scrobble(const QList<lastfm::Track>& tracks)
{
QMap<QString, QString> map;
map["method"] = "track.scrobble";
for ( int i(0) ; i < tracks.count() ; ++i )
{
map["duration[" + QString::number(i) + "]"] = QString::number( tracks[i].duration() );
map["timestamp[" + QString::number(i) + "]"] = QString::number( tracks[i].timestamp().toTime_t() );
map["track[" + QString::number(i) + "]"] = tracks[i].title();
map["context[" + QString::number(i) + "]"] = tracks[i].extra("playerId");
if ( !tracks[i].album().isNull() ) map["album[" + QString::number(i) + "]"] = tracks[i].album();
map["artist[" + QString::number(i) + "]"] = tracks[i].artist();
map["albumArtist[" + QString::number(i) + "]"] = tracks[i].albumArtist();
if ( !tracks[i].mbid().isNull() ) map["mbid[" + QString::number(i) + "]"] = tracks[i].mbid();
}
qDebug() << map;
return ws::post(map);
}
QUrl
lastfm::Track::www() const
{
return UrlBuilder( "music" ).slash( d->artist ).slash( album().isNull() ? QString("_") : album()).slash( d->title ).url();
}
bool
lastfm::Track::isMp3() const
{
//FIXME really we should check the file header?
return d->url.scheme() == "file" &&
d->url.path().endsWith( ".mp3", Qt::CaseInsensitive );
}
void
lastfm::MutableTrack::setCorrections( QString title, QString album, QString artist, QString albumArtist )
{
d->correctedTitle = title;
d->correctedAlbum = album;
d->correctedArtist = artist;
d->correctedAlbumArtist = albumArtist;
d->forceCorrected( toString() );
}

323
thirdparty/liblastfm2/src/types/Track.h vendored Normal file
View File

@@ -0,0 +1,323 @@
/*
Copyright 2009-2010 Last.fm Ltd.
- Primarily authored by Max Howell, Jono Cole, Doug Mansell and Michael Coffey
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 LASTFM_TRACK_H
#define LASTFM_TRACK_H
#include <lastfm/AbstractType>
#include <lastfm/Album>
#include <lastfm/Mbid>
#include <QDateTime>
#include <QDomElement>
#include <QExplicitlySharedDataPointer>
#include <QString>
#include <QMap>
#include <QUrl>
namespace lastfm {
class TrackData : public QObject, public QSharedData
{
Q_OBJECT
friend class Track;
friend class MutableTrack;
public:
TrackData();
public:
QString artist;
QString albumArtist;
QString album;
QString title;
QString correctedArtist;
QString correctedAlbumArtist;
QString correctedAlbum;
QString correctedTitle;
uint trackNumber;
uint duration;
short source;
short rating;
QString mbid; /// musicbrainz id
uint fpid;
QUrl url;
QDateTime time; /// the time the track was started at
bool loved;
QMap<lastfm::ImageSize, QUrl> m_images;
short scrobbleStatus;
short scrobbleError;
//FIXME I hate this, but is used for radio trackauth etc.
QMap<QString,QString> extras;
bool null;
private:
void forceLoveToggled( bool love ) { emit loveToggled( love );}
void forceScrobbleStatusChanged() { emit scrobbleStatusChanged(); }
void forceCorrected( QString correction ) { emit corrected( correction ); }
private slots:
void onLoveFinished();
void onUnloveFinished();
void onGotInfo();
signals:
void loveToggled( bool love );
void loveFinished();
void unlovedFinished();
void gotInfo( const XmlQuery& );
void scrobbleStatusChanged();
void corrected( QString correction );
};
/** Our track type. It's quite good, you may want to use it as your track type
* in general. It is explicitly shared. Which means when you make a copy, they
* both point to the same data still. This is like Qt's implicitly shared
* classes, eg. QString, however if you mod a copy of a QString, the copy
* detaches first, so then you have two copies. Our Track object doesn't
* detach, which is very handy for our usage in the client, but perhaps not
* what you want. If you need a deep copy for eg. work in a thread, call
* clone(). */
class LASTFM_DLLEXPORT Track : public AbstractType
{
public:
friend class TrackSignalProxy;
enum Source
{
// DO NOT UNDER ANY CIRCUMSTANCES CHANGE THE ORDER OR VALUES OF THIS ENUM!
// you will cause broken settings and b0rked scrobbler cache submissions
Unknown = 0,
LastFmRadio,
Player,
MediaDevice,
NonPersonalisedBroadcast, // eg Shoutcast, BBC Radio 1, etc.
PersonalisedRecommendation, // eg Pandora, but not Last.fm
};
enum ScrobbleStatus
{
Null = 0,
Cached,
Submitted,
Error
};
enum Corrections
{
Original = 0,
Corrected
};
enum ScrobbleError
{
None = 0,
FilteredArtistName = 113,
FilteredTrackName = 114,
FilteredAlbumName = 115,
FilteredTimestamp = 116,
ExceededMaxDailyScrobbles = 118,
InvalidStreamAuth = 119
};
Track();
explicit Track( const QDomElement& );
/** this track and that track point to the same object, so they are the same
* in fact. This doesn't do a deep data comparison. So even if all the
* fields are the same it will return false if they aren't in fact spawned
* from the same initial Track object */
bool sameObject( const Track& that )
{
return (this->d == that.d);
}
bool operator==( const Track& that ) const
{
return ( this->title() == that.title() &&
this->album() == that.album() &&
this->artist() == that.artist());
}
bool operator!=( const Track& that ) const
{
return !operator==( that );
}
QObject* signalProxy() const { return d.data(); }
/** only a Track() is null */
bool isNull() const { return d->null; }
bool corrected() const;
Artist artist( Corrections corrected = Original ) const;
Artist albumArtist( Corrections corrected = Original ) const;
Album album( Corrections corrected = Original ) const;
QString title( Corrections corrected = Original ) const;
uint trackNumber() const { return d->trackNumber; }
uint duration() const { return d->duration; } /// in seconds
Mbid mbid() const { return Mbid(d->mbid); }
QUrl url() const { return d->url; }
QDateTime timestamp() const { return d->time; }
Source source() const { return static_cast<Source>(d->source); }
uint fingerprintId() const { return d->fpid; }
bool isLoved() const { return d->loved; }
QUrl imageUrl( lastfm::ImageSize size, bool square ) const;
QString durationString() const { return durationString( d->duration ); }
static QString durationString( int seconds );
ScrobbleStatus scrobbleStatus() const { return static_cast<ScrobbleStatus>(d->scrobbleStatus); }
ScrobbleError scrobbleError() const { return static_cast<ScrobbleError>(d->scrobbleError); }
/** default separator is an en-dash */
QString toString() const { return toString( Corrected ); }
QString toString( Corrections corrections ) const { return toString( QChar(8211), corrections );}
QString toString( const QChar& separator, Corrections corrections = Original ) const;
/** the standard representation of this object as an XML node */
QDomElement toDomElement( class QDomDocument& ) const;
QString extra( const QString& key ) const{ return d->extras[ key ]; }
bool operator<( const Track &that ) const
{
return this->d->time < that.d->time;
}
bool isMp3() const;
operator QVariant() const { return QVariant::fromValue( *this ); }
//////////// lastfm::Ws
/** See last.fm/api Track section */
QNetworkReply* share( const QStringList& recipients, const QString& message = "", bool isPublic = true ) const;
/** you can get any QNetworkReply TagList using Tag::list( QNetworkReply* ) */
QNetworkReply* getTags() const; // for the logged in user
QNetworkReply* getTopTags() const;
QNetworkReply* getTopFans() const;
void getInfo(const QString& user = "", const QString& sk = "") const;
/** you can only add 10 tags, we submit everything you give us, but the
* docs state 10 only. Will return 0 if the list is empty. */
QNetworkReply* addTags( const QStringList& ) const;
/** will return 0 if the string is "" */
QNetworkReply* removeTag( const QString& ) const;
/** scrobble the track */
QNetworkReply* updateNowPlaying() const;
QNetworkReply* scrobble() const;
static QNetworkReply* scrobble(const QList<lastfm::Track>& tracks);
/** the url for this track's page at last.fm */
QUrl www() const;
protected:
QExplicitlySharedDataPointer<TrackData> d;
QMap<QString, QString> params( const QString& method, bool use_mbid = false ) const;
private:
Track( TrackData* that_d ) : d( that_d )
{}
};
/** This class allows you to change Track objects, it is easy to use:
* MutableTrack( some_track_object ).setTitle( "Arse" );
*
* We have a separate MutableTrack class because in our usage, tracks
* only get mutated once, and then after that, very rarely. This pattern
* encourages such usage, which is generally sensible. You can feel more
* comfortable that the data hasn't accidently changed behind your back.
*/
class LASTFM_DLLEXPORT MutableTrack : public Track
{
public:
MutableTrack()
{
d->null = false;
}
/** NOTE that passing a Track() to this ctor will automatically make it non
* null. Which may not be what you want. So be careful
* Rationale: this is the most maintainable way to do it
*/
MutableTrack( const Track& that ) : Track( that )
{
d->null = false;
}
void setFromLfm( const XmlQuery& lfm );
void setArtist( QString artist ) { d->artist = artist.trimmed(); }
void setAlbumArtist( QString albumArtist ) { d->albumArtist = albumArtist.trimmed(); }
void setAlbum( QString album ) { d->album = album.trimmed(); }
void setTitle( QString title ) { d->title = title.trimmed(); }
void setCorrections( QString title, QString album, QString artist, QString albumArtist );
void setTrackNumber( uint n ) { d->trackNumber = n; }
void setDuration( uint duration ) { d->duration = duration; }
void setUrl( QUrl url ) { d->url = url; }
void setSource( Source s ) { d->source = s; }
void setLoved( bool loved ) { d->loved = loved; }
void setMbid( Mbid id ) { d->mbid = id; }
void setFingerprintId( uint id ) { d->fpid = id; }
void setScrobbleStatus( ScrobbleStatus scrobbleStatus )
{
d->scrobbleStatus = scrobbleStatus;
d->forceScrobbleStatusChanged();
}
void setScrobbleError( ScrobbleError scrobbleError ) { d->scrobbleError = scrobbleError; }
/** you also must scrobble this track for the love to become permenant */
void love();
void unlove();
QNetworkReply* ban();
void stamp() { d->time = QDateTime::currentDateTime(); }
void setExtra( const QString& key, const QString& value ) { d->extras[key] = value; }
void removeExtra( QString key ) { d->extras.remove( key ); }
void setTimeStamp( const QDateTime& dt ) { d->time = dt; }
};
} //namespace lastfm
inline QDebug operator<<( QDebug d, const lastfm::Track& t )
{
return !t.isNull()
? d << t.toString( '-' ) << t.url()
: d << "Null Track object";
}
Q_DECLARE_METATYPE( lastfm::Track );
#endif //LASTFM_TRACK_H

286
thirdparty/liblastfm2/src/types/User.cpp vendored Normal file
View File

@@ -0,0 +1,286 @@
/*
Copyright 2009 Last.fm Ltd.
- Primarily authored by Max Howell, Jono Cole and Doug Mansell
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 "User.h"
#include "Track.h"
#include "../core/UrlBuilder.h"
#include "../core/XmlQuery.h"
#include <QStringList>
#include <lastfm/UserList>
using lastfm::User;
using lastfm::UserList;
using lastfm::UserDetails;
using lastfm::XmlQuery;
using lastfm::ImageSize;
User::User( const XmlQuery& xml )
:AbstractType(), m_match( -1.0f )
{
m_name = xml["name"].text();
m_images << xml["image size=small"].text()
<< xml["image size=medium"].text()
<< xml["image size=large"].text();
m_realName = xml["realname"].text();
}
QUrl
User::imageUrl( ImageSize size, bool square ) const
{
if( !square ) return m_images.value( size );
QUrl url = m_images.value( size );
QRegExp re( "/serve/(\\d*)s?/" );
return QUrl( url.toString().replace( re, "/serve/\\1s/" ));
}
QMap<QString, QString>
User::params(const QString& method) const
{
QMap<QString, QString> map;
map["method"] = "user."+method;
map["user"] = m_name;
return map;
}
QNetworkReply*
User::getFriends( int perPage, int page ) const
{
QMap<QString, QString> map = params( "getFriends" );
map["limit"] = QString::number(perPage);
map["page"] = QString::number(page);
return ws::get( map );
}
QNetworkReply*
User::getTopTags() const
{
return ws::get( params( "getTopTags" ) );
}
QNetworkReply*
User::getTopArtists() const
{
return ws::get( params( "getTopArtists" ) );
}
QNetworkReply*
User::getRecentArtists() const
{
return ws::get( params( "getRecentArtists" ) );
}
QNetworkReply*
User::getRecentTracks() const
{
return ws::get( params( "getRecentTracks" ) );
}
QNetworkReply*
User::getRecentStations() const
{
return ws::post( params( "getRecentStations" ) );
}
QNetworkReply*
User::getNeighbours() const
{
return ws::get( params( "getNeighbours" ) );
}
QNetworkReply*
User::getPlaylists() const
{
return ws::get( params( "getPlaylists" ) );
}
UserList //static
User::list( QNetworkReply* r )
{
UserList users;
try {
XmlQuery lfm = ws::parse(r);
foreach (XmlQuery e, lfm.children( "user" ))
{
User u( e );
users += u;
}
users.total = lfm["friends"].attribute("total").toInt();
users.page = lfm["friends"].attribute("page").toInt();
users.perPage = lfm["friends"].attribute("perPage").toInt();
users.totalPages = lfm["friends"].attribute("totalPages").toInt();
}
catch (ws::ParseError& e)
{
qWarning() << e.what();
}
return users;
}
QNetworkReply* //static
UserDetails::getInfo( const QString& username )
{
QMap<QString, QString> map;
map["method"] = "user.getInfo";
map["user"] = username;
return ws::post( map );
}
/*
QNetworkReply* //static
UserDetails::getRecommendedArtists()
{
QMap<QString, QString> map;
map["method"] = "user.getRecommendedArtists";
return ws::post( map );
}
*/
QUrl
User::www() const
{
return UrlBuilder( "user" ).slash( m_name ).url();
}
UserDetails::UserDetails()
: User()
, m_age( 0 )
, m_scrobbles( 0 )
, m_registered( QDateTime() )
, m_isSubscriber( false )
, m_canBootstrap( false )
{}
UserDetails::UserDetails( QNetworkReply* reply )
{
try
{
XmlQuery user = XmlQuery(ws::parse(reply))["user"];
m_age = user["age"].text().toUInt();
m_scrobbles = user["playcount"].text().toUInt();
m_registered = QDateTime::fromTime_t(user["registered"].attribute("unixtime").toUInt());
m_country = user["country"].text();
m_isSubscriber = ( user["subscriber"].text() == "1" );
m_canBootstrap = ( user["bootstrap"].text() == "1" );
m_gender = user["gender"].text();
m_realName = user["realname"].text();
m_name = user["name"].text();
m_images << user["image size=small"].text()
<< user["image size=medium"].text()
<< user["image size=large"].text();
}
catch (ws::ParseError& e)
{
qWarning() << e.what();
}
}
QString
UserDetails::getInfoString() const
{
#define tr QObject::tr
;
QString text;
if (m_gender.known() && m_age > 0 && m_scrobbles > 0)
{
text = tr("A %1, %2 years of age with %L3 scrobbles")
.arg( m_gender.toString() )
.arg( m_age )
.arg( m_scrobbles );
}
else if (m_scrobbles > 0)
{
text = tr("%L1 scrobbles").arg( m_scrobbles );
}
return text;
#undef tr
}
void
UserDetails::setScrobbleCount( quint32 scrobbleCount )
{
m_scrobbles = scrobbleCount;
}
void
UserDetails::setDateRegistered( const QDateTime& date )
{
m_registered = date;
}
void
UserDetails::setImages( const QList<QUrl>& images )
{
m_images = images;
}
void
UserDetails::setRealName( const QString& realName )
{
m_realName = realName;
}
void
UserDetails::setAge( unsigned short age )
{
m_age = age;
}
void
UserDetails::setIsSubscriber( bool subscriber )
{
m_isSubscriber = subscriber;
}
void
UserDetails::setCanBootstrap( bool canBootstrap )
{
m_canBootstrap = canBootstrap;
}
void
UserDetails::setGender( const QString& s )
{
m_gender = Gender( s );
}
void
UserDetails::setCountry( const QString& country )
{
m_country = country;
}

181
thirdparty/liblastfm2/src/types/User.h vendored Normal file
View File

@@ -0,0 +1,181 @@
/*
Copyright 2009-2010 Last.fm Ltd.
- Primarily authored by Max Howell, Jono Cole, Doug Mansell and Michael Coffey
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 LASTFM_USER_H
#define LASTFM_USER_H
#include <QString>
#include <QStringList>
#include <QUrl>
#include <lastfm/AbstractType>
#include <lastfm/ws.h>
namespace lastfm
{
class UserList;
class LASTFM_DLLEXPORT User : public AbstractType
{
public:
User() : AbstractType(), m_name( lastfm::ws::Username ), m_match( -1.0f )
{}
User( const QString& name ) : AbstractType(), m_name( name ), m_match( -1.0f )
{}
User( const class XmlQuery& xml );
bool operator==(const lastfm::User& that) const { return m_name == that.m_name; }
operator QString() const { return m_name; }
QString name() const { return m_name; }
void setName( const QString& name ){ m_name = name; }
/** use Tag::list() on the response to get a WeightedStringList */
QNetworkReply* getTopTags() const;
/** use User::list() on the response to get a QList<User> */
QNetworkReply* getFriends(int perPage = 50, int page = 1) const;
QNetworkReply* getNeighbours() const;
QNetworkReply* getPlaylists() const;
QNetworkReply* getTopArtists() const;
QNetworkReply* getRecentTracks() const;
QNetworkReply* getRecentArtists() const;
QNetworkReply* getRecentStations() const;
static UserList list( QNetworkReply* );
QString toString() const { return name(); }
QDomElement toDomElement( QDomDocument& ) const { return QDomElement(); }
//////
QUrl imageUrl( ImageSize size = Large, bool square = false ) const;
QString realName() const { return m_realName; }
/** the user's profile page at www.last.fm */
QUrl www() const;
/** Returns the match between the logged in user and the user which this
* object represents (if < 0.0f then not set) */
float match() const { return m_match; }
protected:
QString m_name;
QList<QUrl> m_images;
float m_match;
QString m_realName;
QMap<QString, QString> params( const QString& method ) const;
};
/** The Extended User contains extra information about a user's account */
class LASTFM_DLLEXPORT UserDetails : public User
{
public:
UserDetails();
/** User details */
UserDetails( QNetworkReply* );
/** you can only get information about the any user */
static QNetworkReply* getInfo( const QString& username = lastfm::ws::Username );
/** a verbose string, eg. "A man with 36,153 scrobbles" */
QString getInfoString() const;
bool isSubscriber() const{ return m_isSubscriber; }
bool canBootstrap() const{ return m_canBootstrap; }
quint32 scrobbleCount() const{ return m_scrobbles; }
QDateTime dateRegistered() const { return m_registered; }
void setScrobbleCount( quint32 scrobblesCount );
void setDateRegistered( const QDateTime& date );
void setImages( const QList<QUrl>& images );
void setRealName( const QString& realName );
void setAge( unsigned short age );
void setIsSubscriber( bool subscriber );
void setCanBootstrap( bool canBootstrap );
void setGender( const QString& s );
void setCountry( const QString& country );
// pass the result to Artist::list(), if you want the other data
// you have to parse the lfm() yourself members
// http://www.last.fm/api/show?service=388
// static QNetworkReply* getRecommendedArtists();
protected:
class Gender
{
QString s;
public:
Gender() :s(/*confused!*/){}
Gender( const QString& ss ) :s( ss.toLower() )
{}
bool known() const { return male() || female(); }
bool male() const { return s == "m"; }
bool female() const { return s == "f"; }
QString toString() const
{
#define tr QObject::tr
QStringList list;
if (male())
list << tr("boy") << tr("lad") << tr("chap") << tr("guy");
else if (female())
// I'm not sexist, it's just I'm gutless and couldn't think
// of any other non offensive terms for women!
list << tr("girl") << tr("lady") << tr("lass");
else
return tr("person");
return list.value( QDateTime::currentDateTime().toTime_t() % list.count() );
#undef tr
}
} m_gender;
unsigned short m_age;
unsigned int m_scrobbles;
QDateTime m_registered;
QString m_country;
bool m_isSubscriber;
bool m_canBootstrap;
};
class LASTFM_DLLEXPORT UserList : public QList<User>
{
public:
int total;
int page;
int perPage;
int totalPages;
};
}
#endif

View File

@@ -0,0 +1,49 @@
/*
Copyright 2009 Last.fm Ltd.
- Primarily authored by Max Howell, Jono Cole and Doug Mansell
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 "Xspf.h"
#include "../core/XmlQuery.h"
#include <QUrl>
lastfm::Xspf::Xspf( const QDomElement& playlist_node )
{
XmlQuery e( playlist_node );
m_title = e["title"].text();
//FIXME should we use UnicornUtils::urlDecode()?
//The title is url encoded, has + instead of space characters
//and has a + at the begining. So it needs cleaning up:
m_title.replace( '+', ' ' );
m_title = QUrl::fromPercentEncoding( m_title.toAscii());
m_title = m_title.trimmed();
foreach (XmlQuery e, e["trackList"].children( "track" ))
{
MutableTrack t;
t.setUrl( e["location"].text() );
t.setExtra( "trackauth", e["extension"]["trackauth"].text() );
t.setTitle( e["title"].text() );
t.setArtist( e["creator"].text() );
t.setAlbum( e["album"].text() );
t.setDuration( e["duration"].text().toInt() / 1000 );
t.setLoved( e["extension"]["loved"].text() == "1" );
m_tracks += t; // outside try block since location is enough basically
}
}

43
thirdparty/liblastfm2/src/types/Xspf.h vendored Normal file
View File

@@ -0,0 +1,43 @@
/*
Copyright 2009 Last.fm Ltd.
- Primarily authored by Max Howell, Jono Cole and Doug Mansell
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 LASTFM_XSPF_H
#define LASTFM_XSPF_H
#include <lastfm/Track>
#include <QList>
namespace lastfm
{
class LASTFM_DLLEXPORT Xspf
{
public:
/** pass in the playlist node! */
Xspf( const class QDomElement& playlist_node );
QList<Track> tracks() const { return m_tracks; }
QString title() const{ return m_title; }
private:
QList<Track> m_tracks;
QString m_title;
};
}
#endif

View File

@@ -0,0 +1,181 @@
/*
* LICENSE
*
* Copyright (c) 2006, David Nicolson
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* 3. Neither the name of the author nor the names of its contributors
* may be used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT UNLESS REQUIRED BY
* LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER OR CONTRIBUTOR
* BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY,
* OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT
* OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, PROFITS; OR
* BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
* WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
* OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#ifndef __MBID_MP3_H
#define __MBID_MP3_H
#define MBID_BUFFER_SIZE 37
// -----------------------------------------------------------------------------
void mfile(size_t length, char ret[], FILE *fp, int *s) {
size_t bytes = fread(ret,1,length,fp);
if (bytes != length) {
*s = 0;
}
}
// -----------------------------------------------------------------------------
int to_synch_safe(char bytes[]) {
return ((int)bytes[0] << 21) + ((int)bytes[1] << 14) + ((int)bytes[2] << 7) + (int)bytes[3];
}
int to_integer(char bytes[]) {
size_t size = 0;
uint i;
for (i=0; i < sizeof(bytes); i++) {
size = size * 256 + ((int)bytes[i] & 0x000000FF);
}
return static_cast<int>(size);
}
// -----------------------------------------------------------------------------
int getMP3_MBID(const char *path, char mbid[MBID_BUFFER_SIZE])
{
FILE *fp;
static int s = 1;
char head[3];
char version[2];
char flag[1];
char size[4];
char size_extended[4];
int tag_size = 0;
int extended_size = 0;
char frame[4];
char frame_header[4];
int frame_size;
int version_major, version_minor;
if (path == NULL) {
//debug("Received null path\n");
return -1;
}
fp = fopen(path,"rb");
if (fp == NULL) {
//debug("Failed to open music file: %s\n",path);
return -1;
}
while (s) {
mfile(3,head,fp,&s);
if (!strncmp(head,"ID3",3) == 0) {
//debug("No ID3v2 tag found: %s\n",path);
break;
}
mfile(2,version,fp,&s);
version_major = (int)version[0];
version_minor = (int)version[1];
if (version_major == 2) {
//debug("ID3v2.2.0 does not support MBIDs: %s\n",path);
break;
}
if (version_major != 3 && version_major != 4) {
//debug("Unsupported ID3 version: v2.%d.%d\n",version_major,version_minor);
break;
}
mfile(1,flag,fp,&s);
if ((unsigned int)flag[0] & 0x00000040) {
//debug("Extended header found\n");
if (version[0] == 4) {
mfile(4,size_extended,fp,&s);
extended_size = to_synch_safe(size_extended);
} else {
mfile(4,size_extended,fp,&s);
extended_size = to_integer(size_extended);
}
//debug("Extended header size: %d\n",extended_size);
fseek(fp,extended_size,SEEK_CUR);
}
mfile(4,size,fp,&s);
tag_size = to_synch_safe(size);
//debug("Tag size: %d\n",tag_size);
while (s) {
if (ftell(fp) > tag_size || ftell(fp) > 1048576) {
break;
}
mfile(4,frame,fp,&s);
if (frame[0] == 0x00) {
break;
}
if (version_major == 4) {
mfile(4,frame_header,fp,&s);
frame_size = to_synch_safe(frame_header);
} else {
mfile(4,frame_header,fp,&s);
frame_size = to_integer(frame_header);
}
fseek(fp,2,SEEK_CUR);
//debug("Reading %d bytes from frame %s\n",frame_size,frame);
if (strncmp(frame,"UFID",4) == 0) {
//char frame_data[frame_size];
char frame_data[59];
mfile(59,frame_data,fp,&s);
if (frame_size >= 59 && strncmp(frame_data,"http://musicbrainz.org",22) == 0) {
char *tmbid = frame_data;
tmbid = frame_data + 23;
strncpy(mbid,tmbid,MBID_BUFFER_SIZE-1);
mbid[MBID_BUFFER_SIZE-1] = 0x00;
fclose(fp);
return 0;
}
} else {
fseek(fp,frame_size,SEEK_CUR);
}
}
break;
}
if (fp) {
fclose(fp);
}
//if (!s) {
// debug("Failed to read music file: %s\n",path);
//}
return -1;
}
#endif
// -----------------------------------------------------------------------------

View File

@@ -0,0 +1,113 @@
/*
Copyright 2009 Last.fm Ltd.
- Primarily authored by Max Howell, Jono Cole and Doug Mansell
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 "InternetConnectionMonitor.h"
#include "linux/LNetworkConnectionMonitor.h"
#include "mac/MNetworkConnectionMonitor.h"
#include "win/WNetworkConnectionMonitor.h"
#include "NetworkConnectionMonitor.h"
#include "ws.h"
lastfm::InternetConnectionMonitor::InternetConnectionMonitor( QObject *parent )
: QObject( parent )
, m_up( true )
{
m_networkMonitor = createNetworkConnectionMonitor();
if ( m_networkMonitor )
{
connect( m_networkMonitor, SIGNAL( networkUp() ), this, SLOT( onNetworkUp() ) );
connect( m_networkMonitor, SIGNAL( networkDown() ), this, SLOT( onNetworkDown() ) );
}
connect( lastfm::nam(), SIGNAL( finished( QNetworkReply* ) ), this, SLOT( onFinished( QNetworkReply* ) ) );
}
void
lastfm::InternetConnectionMonitor::onFinished( QNetworkReply* reply )
{
switch( reply->error() )
{
case QNetworkReply::NoError:
if ( !m_up )
{
m_up = true;
emit up();
emit connectivityChanged( m_up );
}
break;
case QNetworkReply::HostNotFoundError:
case QNetworkReply::TimeoutError:
case QNetworkReply::ProxyConnectionRefusedError:
case QNetworkReply::ProxyConnectionClosedError:
case QNetworkReply::ProxyNotFoundError:
case QNetworkReply::ProxyTimeoutError:
case QNetworkReply::ProxyAuthenticationRequiredError:
if ( m_up )
{
m_up = false;
emit down();
emit connectivityChanged( m_up );
}
break;
default:
break;
}
}
void
lastfm::InternetConnectionMonitor::onNetworkUp()
{
#ifdef Q_OS_MAC
// We don't need to check on mac as the
// check is done as part of the reach api
m_up = true;
emit up();
emit connectivityChanged( m_up );
#else
qDebug() << "Network seems to be up again. Let's try if there's internet connection!";
lastfm::nam()->head( QNetworkRequest( QUrl( tr( "http://www.last.fm/" ) ) ) );
#endif
}
void
lastfm::InternetConnectionMonitor::onNetworkDown()
{
qDebug() << "Internet is down :( boo!!";
m_up = false;
emit down();
emit connectivityChanged( m_up );
}
NetworkConnectionMonitor*
lastfm::InternetConnectionMonitor::createNetworkConnectionMonitor()
{
NetworkConnectionMonitor* ncm = 0;
#ifdef Q_WS_X11
ncm = new LNetworkConnectionMonitor( this );
#elif defined(Q_WS_WIN)
ncm = new WNetworkConnectionMonitor( this );
#elif defined(Q_WS_MAC)
ncm = new MNetworkConnectionMonitor( this );
#endif
return ncm;
}

View File

@@ -0,0 +1,80 @@
/*
Copyright 2009 Last.fm Ltd.
- Primarily authored by Max Howell, Jono Cole and Doug Mansell
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 LASTFM_CONNECTION_MONITOR_H
#define LASTFM_CONNECTION_MONITOR_H
#include <lastfm/global.h>
#include <QObject>
class NetworkConnectionMonitor;
#ifdef Q_WS_X11
class LNetworkConnectionMonitor;
#endif
namespace lastfm {
class LASTFM_DLLEXPORT InternetConnectionMonitor : public QObject
{
Q_OBJECT
enum NMState
{
Unknown,
Asleep,
Connecting,
Connected,
Disconnected
};
public:
/** if internet is unavailable you will get a down() signal soon, otherwise
* you won't get a signal until the net goes down */
InternetConnectionMonitor( QObject *parent = 0 );
bool isDown() const { return !m_up; }
bool isUp() const { return m_up; }
NetworkConnectionMonitor* createNetworkConnectionMonitor();
signals:
/** yay! internet has returned */
void up( const QString& connectionName = "" );
/** we think the internet is unavailable, but well, still try, but show
* an unhappy face in the statusbar or something */
void down( const QString& connectionName = "" );
/** emitted after the above */
void connectivityChanged( bool );
private slots:
void onFinished( QNetworkReply* reply );
void onNetworkUp();
void onNetworkDown();
private:
bool m_up;
NetworkConnectionMonitor* m_networkMonitor;
};
} //namespace lastfm
#endif

View File

@@ -0,0 +1,159 @@
/*
Copyright 2009 Last.fm Ltd.
- Primarily authored by Max Howell, Jono Cole and Doug Mansell
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 "NetworkAccessManager.h"
#include "InternetConnectionMonitor.h"
#include <lastfm/ws.h>
#include <lastfm/misc.h>
#include <QCoreApplication>
#include <QNetworkRequest>
#ifdef WIN32
#include "win/IeSettings.h"
#include "win/Pac.h"
#endif
#ifdef __APPLE__
#include "mac/ProxyDict.h"
#endif
static struct NetworkAccessManagerInit
{
// We do this upfront because then our Firehose QTcpSocket will have a proxy
// set by default. As well as any plain QNetworkAcessManager stuff, and the
// scrobbler
// In theory we should do this every request in case the configuration
// changes but that is fairly unlikely use case, init? Maybe we should
// anyway..
NetworkAccessManagerInit()
{
#ifdef WIN32
IeSettings s;
// if it's autodetect, we determine the proxy everytime in proxy()
// we don't really want to do a PAC lookup here, as it times out
// at two seconds, so that hangs startup
if (!s.fAutoDetect && s.lpszProxy)
{
QUrl url( QString::fromUtf16(s.lpszProxy) );
QNetworkProxy proxy( QNetworkProxy::HttpProxy );
proxy.setHostName( url.host() );
proxy.setPort( url.port() );
QNetworkProxy::setApplicationProxy( proxy );
}
#endif
#ifdef __APPLE__
ProxyDict dict;
if (dict.isProxyEnabled())
{
QNetworkProxy proxy( QNetworkProxy::HttpProxy );
proxy.setHostName( dict.host );
proxy.setPort( dict.port );
QNetworkProxy::setApplicationProxy( proxy );
}
#endif
}
} init;
namespace lastfm
{
LASTFM_DLLEXPORT QByteArray UserAgent;
}
lastfm::NetworkAccessManager::NetworkAccessManager( QObject* parent )
: QNetworkAccessManager( parent )
#ifdef WIN32
, m_pac( 0 )
, m_monitor( 0 )
#endif
{
// can't be done in above init, as applicationName() won't be set
if (lastfm::UserAgent.isEmpty())
{
QByteArray name = QCoreApplication::applicationName().toUtf8();
QByteArray version = QCoreApplication::applicationVersion().toUtf8();
if (version.size()) version.prepend( ' ' );
lastfm::UserAgent = name + version + " (" + lastfm::platform() + ")";
}
}
lastfm::NetworkAccessManager::~NetworkAccessManager()
{
#ifdef WIN32
delete m_pac;
#endif
}
QNetworkProxy
lastfm::NetworkAccessManager::proxy( const QNetworkRequest& request )
{
Q_UNUSED( request );
#ifdef WIN32
IeSettings s;
if (s.fAutoDetect)
{
if (!m_pac) {
m_pac = new Pac;
if ( !m_monitor )
{
m_monitor = new InternetConnectionMonitor( this );
connect( m_monitor, SIGNAL( connectivityChanged( bool ) ), SLOT( onConnectivityChanged( bool ) ) );
}
}
return m_pac->resolve( request, s.lpszAutoConfigUrl );
}
#endif
return QNetworkProxy::applicationProxy();
}
QNetworkReply*
lastfm::NetworkAccessManager::createRequest( Operation op, const QNetworkRequest& request_, QIODevice* outgoingData )
{
QNetworkRequest request = request_;
request.setAttribute( QNetworkRequest::CacheLoadControlAttribute, QNetworkRequest::PreferCache );
request.setRawHeader( "User-Agent", lastfm::UserAgent );
#ifdef WIN32
// PAC proxies can vary by domain, so we have to check everytime :(
QNetworkProxy proxy = this->proxy( request );
if (proxy.type() != QNetworkProxy::NoProxy)
QNetworkAccessManager::setProxy( proxy );
#endif
return QNetworkAccessManager::createRequest( op, request, outgoingData );
}
void
lastfm::NetworkAccessManager::onConnectivityChanged( bool up )
{
Q_UNUSED( up );
#ifdef WIN32
if (up && m_pac) m_pac->resetFailedState();
#endif
}

View File

@@ -0,0 +1,66 @@
/*
Copyright 2009 Last.fm Ltd.
- Primarily authored by Max Howell, Jono Cole and Doug Mansell
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 LASTFM_WS_ACCESS_MANAGER_H
#define LASTFM_WS_ACCESS_MANAGER_H
#include <lastfm/global.h>
#include <QtNetwork/QNetworkAccessManager>
#include <QNetworkRequest>
#include <QNetworkProxy>
namespace lastfm {
/** Sets useragent and proxy. Auto detecting the proxy where possible. */
class LASTFM_DLLEXPORT NetworkAccessManager : public QNetworkAccessManager
{
Q_OBJECT
#ifdef Q_WS_WIN
class Pac *m_pac;
class InternetConnectionMonitor* m_monitor;
#endif
public:
NetworkAccessManager( QObject *parent = 0 );
~NetworkAccessManager();
/** PAC allows different proxy configurations depending on the request
* URL and even UserAgent! Thus we allow you to pass that in, we
* automatically configure the proxy for every request through
* WsAccessManager */
QNetworkProxy proxy( const QNetworkRequest& = QNetworkRequest() );
protected:
virtual QNetworkReply* createRequest( Operation, const QNetworkRequest&, QIODevice* outgoingdata = 0 );
private slots:
void onConnectivityChanged( bool );
private:
/** this function calls QNetworkAccessManager::setProxy, and thus
* configures the proxy correctly for the next request created by
* createRequest. This is necessary due */
void applyProxy( const QNetworkRequest& );
};
} //namespace lastfm
#endif

View File

@@ -0,0 +1,51 @@
/*
Copyright 2010 Last.fm Ltd.
- Primarily authored by Max Howell, Jono Cole and Doug Mansell
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 "NetworkConnectionMonitor.h"
NetworkConnectionMonitor::NetworkConnectionMonitor( QObject* /*parent*/ )
: m_connected( true )
{
}
NetworkConnectionMonitor::~NetworkConnectionMonitor()
{
}
bool
NetworkConnectionMonitor::isConnected() const
{
return m_connected;
}
void
NetworkConnectionMonitor::setConnected( bool connected )
{
if ( m_connected != connected )
{
m_connected = connected;
if ( connected )
emit networkUp();
else
emit networkDown();
}
}

View File

@@ -0,0 +1,46 @@
/*
Copyright 2010 Last.fm Ltd.
- Primarily authored by Jono Cole, Michael Coffey, and William Viana
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 NETWORK_CONNECTION_MONITOR_H
#define NETWORK_CONNECTION_MONITOR_H
#include <lastfm/global.h>
#include <QObject>
class LASTFM_DLLEXPORT NetworkConnectionMonitor : public QObject
{
Q_OBJECT
public:
NetworkConnectionMonitor( QObject *parent = 0 );
~NetworkConnectionMonitor();
bool isConnected() const;
signals:
void networkUp();
void networkDown();
protected:
void setConnected( bool connected );
private:
bool m_connected;
};
#endif // NETWORK_CONNECTION_MONITOR_H

View File

@@ -0,0 +1,54 @@
/*
Copyright 2010 Last.fm Ltd.
- Primarily authored by Max Howell, Jono Cole and Doug Mansell
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 LNETWORK_CONNECTION_MONITOR_H
#define LNETWORK_CONNECTION_MONITOR_H
#include "../NetworkConnectionMonitor.h"
#include <lastfm/global.h>
#include <QObject>
class QDBusConnection;
class QDBusInterface;
class LNetworkConnectionMonitor : public NetworkConnectionMonitor
{
Q_OBJECT
enum NMState
{
Unknown=1,
Asleep,
Connected,
Disconnected
};
public:
LNetworkConnectionMonitor( QObject* parent = 0 );
~LNetworkConnectionMonitor();
private slots:
void onStateChange( uint newState );
private:
QDBusInterface* m_nmInterface;
};
#endif // LNETWORK_CONNECTION_MONITOR_H

View File

@@ -0,0 +1,86 @@
/*
Copyright 2010 Last.fm Ltd.
- Primarily authored by Max Howell, Jono Cole and Doug Mansell
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 "LNetworkConnectionMonitor.h"
#include <QDBusConnection>
#include <QDBusInterface>
#include <QDBusReply>
LNetworkConnectionMonitor::LNetworkConnectionMonitor( QObject* parent ) :
NetworkConnectionMonitor( parent )
{
m_nmInterface = new QDBusInterface( QString( "org.freedesktop.NetworkManager" ),
QString( "/org/freedesktop/NetworkManager" ),
QString( "org.freedesktop.NetworkManager" ),
QDBusConnection::systemBus(),
this );
//get current connection state
QDBusInterface* dbusInterface = new QDBusInterface( QString( "org.freedesktop.NetworkManager" ),
QString( "/org/freedesktop/NetworkManager" ),
QString( "org.freedesktop.DBus.Properties" ),
QDBusConnection::systemBus(),
this );
QDBusReply<QVariant> reply = dbusInterface->call( "Get", "org.freedesktop.NetworkManager", "state" );
if ( reply.isValid() )
{
if ( reply.value() == Connected )
{
setConnected( true );
}
else if ( reply.value() == Disconnected )
{
setConnected( false );
}
}
else
{
qDebug() << "Error: " << reply.error();
}
delete dbusInterface;
//connect network manager signals
connect( m_nmInterface, SIGNAL( StateChange( uint ) ), this, SLOT( onStateChange( uint ) ) );
}
LNetworkConnectionMonitor::~LNetworkConnectionMonitor()
{
delete m_nmInterface;
}
void
LNetworkConnectionMonitor::onStateChange( uint newState )
{
qDebug() << "Networkmanager state change!";
if ( newState == Disconnected )
{
setConnected( false );
}
else if ( newState == Connected )
{
setConnected( true );
}
}

View File

@@ -0,0 +1,52 @@
/*
Copyright 2009 Last.fm Ltd.
- Primarily authored by Jono Cole and Michael Coffey
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 MNETWORK_CONNECTION_MONITOR_H
#define MNETWORK_CONNECTION_MONITOR_H
#include "../NetworkConnectionMonitor.h"
#include <lastfm/global.h>
#include <QObject>
#ifdef Q_WS_MAC
#include <SystemConfiguration/SCNetwork.h> //TODO remove
#include <SystemConfiguration/SCNetworkReachability.h>
#endif
class __SCNetworkReachability;
class MNetworkConnectionMonitor : public NetworkConnectionMonitor
{
Q_OBJECT
public:
MNetworkConnectionMonitor( QObject* parent = 0 );
~MNetworkConnectionMonitor();
private slots:
private:
#ifdef Q_WS_MAC
static void callback( SCNetworkReachabilityRef target,
SCNetworkConnectionFlags flags,
void *info );
#endif
};
#endif // MNETWORK_CONNECTION_MONITOR_H

View File

@@ -0,0 +1,71 @@
/*
Copyright 2009 Last.fm Ltd.
- Primarily authored by Jono Cole and Michael Coffey
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 "MNetworkConnectionMonitor.h"
#include "ws/ws.h"
#include <QPointer>
#include <SystemConfiguration/SCNetworkReachability.h>
MNetworkConnectionMonitor* context = 0;
MNetworkConnectionMonitor::MNetworkConnectionMonitor( QObject* parent ) :
NetworkConnectionMonitor( parent )
{
context = this;
SCNetworkReachabilityRef ref = SCNetworkReachabilityCreateWithName( NULL, LASTFM_WS_HOSTNAME );
SCNetworkReachabilityScheduleWithRunLoop( ref, CFRunLoopGetCurrent(), kCFRunLoopDefaultMode );
SCNetworkReachabilitySetCallback( ref, callback, NULL );
CFRelease( ref );
}
MNetworkConnectionMonitor::~MNetworkConnectionMonitor()
{
}
void
MNetworkConnectionMonitor::callback( SCNetworkReachabilityRef target,
SCNetworkConnectionFlags flags,
void * )
{
static bool up = true;
// I couldn't find any diffinitive usage examples for these flags
// so I had to guess, since I can't test, eg. dial up :(
bool b;
if (flags & kSCNetworkFlagsConnectionRequired)
b = false;
else
b = flags & (kSCNetworkFlagsReachable | kSCNetworkFlagsTransientConnection | kSCNetworkFlagsConnectionAutomatic);
qDebug() << "Can reach " LASTFM_WS_HOSTNAME ":" << b << ", flags:" << flags;
// basically, avoids telling everyone that we're up already on startup
if (up == b)
return;
up = b;
context->setConnected(b);
}

View File

@@ -0,0 +1,75 @@
/*
Copyright 2009 Last.fm Ltd.
- Primarily authored by Max Howell, Jono Cole and Doug Mansell
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 <SystemConfiguration/SystemConfiguration.h>
struct ProxyDict
{
ProxyDict();
int port;
QString host;
bool isProxyEnabled() const { return port > 0 && host.size(); }
};
inline ProxyDict::ProxyDict() : port( 0 )
{
// Get the dictionary.
CFDictionaryRef proxyDict = SCDynamicStoreCopyProxies( NULL );
bool result = (proxyDict != NULL);
// Get the enable flag. This isn't a CFBoolean, but a CFNumber.
CFNumberRef enableNum;
int enable;
if (result) {
enableNum = (CFNumberRef) CFDictionaryGetValue( proxyDict, kSCPropNetProxiesHTTPEnable );
result = (enableNum != NULL) && (CFGetTypeID(enableNum) == CFNumberGetTypeID());
}
if (result)
result = CFNumberGetValue( enableNum, kCFNumberIntType, &enable ) && (enable != 0);
// Get the proxy host. DNS names must be in ASCII. If you
// put a non-ASCII character in the "Secure Web Proxy"
// field in the Network preferences panel, the CFStringGetCString
// function will fail and this function will return false.
CFStringRef hostStr;
if (result) {
hostStr = (CFStringRef) CFDictionaryGetValue( proxyDict, kSCPropNetProxiesHTTPProxy );
result = (hostStr != NULL) && (CFGetTypeID(hostStr) == CFStringGetTypeID());
}
if (result)
host = lastfm::CFStringToQString( hostStr );
// get the proxy port
CFNumberRef portNum;
if (result) {
portNum = (CFNumberRef) CFDictionaryGetValue( proxyDict, kSCPropNetProxiesHTTPPort );
result = (portNum != NULL) && (CFGetTypeID(portNum) == CFNumberGetTypeID());
}
if (result)
result = CFNumberGetValue( portNum, kCFNumberIntType, &port );
// clean up.
if (proxyDict != NULL)
CFRelease( proxyDict );
}

View File

@@ -0,0 +1,63 @@
/*
Copyright 2009 Last.fm Ltd.
- Primarily authored by Max Howell, Jono Cole and Doug Mansell
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 _WIN32_WINNT
// bring in CoInitializeSecurity from objbase.h
#define _WIN32_WINNT 0x0400
#endif
#include <objbase.h>
#include <atlbase.h>
#include <atlcom.h>
/** @brief WsConnectionMonitor needs Com to work as early as possible so we do this
* @author <doug@last.fm>
*/
class ComSetup
{
public:
ComSetup()
{
HRESULT hr = CoInitialize(0);
m_bComInitialised = SUCCEEDED(hr);
_ASSERT(m_bComInitialised);
if (m_bComInitialised) {
setupSecurity();
}
}
void setupSecurity()
{
CSecurityDescriptor sd;
sd.InitializeFromThreadToken();
HRESULT hr = CoInitializeSecurity(sd, -1, NULL, NULL, RPC_C_AUTHN_LEVEL_PKT, RPC_C_IMP_LEVEL_IMPERSONATE, NULL, EOAC_NONE, NULL);
_ASSERT(SUCCEEDED(hr));
}
~ComSetup()
{
if (m_bComInitialised) {
CoUninitialize();
}
}
private:
bool m_bComInitialised;
};

View File

@@ -0,0 +1,43 @@
/*
Copyright 2009 Last.fm Ltd.
- Primarily authored by Max Howell, Jono Cole and Doug Mansell
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 <windows.h>
#include <winhttp.h>
/** @brief memory managing wrapper for WINHTTP_CURRENT_USER_IE_PROXY_CONFIG
* @author <doug@last.fm>
*/
struct IeSettings : WINHTTP_CURRENT_USER_IE_PROXY_CONFIG
{
IeSettings()
{
if (!WinHttpGetIEProxyConfigForCurrentUser(this)) {
fAutoDetect = FALSE;
lpszAutoConfigUrl = lpszProxy = lpszProxyBypass = 0;
}
}
~IeSettings()
{
if (lpszAutoConfigUrl) GlobalFree(lpszAutoConfigUrl);
if (lpszProxy) GlobalFree(lpszProxy);
if (lpszProxyBypass) GlobalFree(lpszProxyBypass);
}
};

Some files were not shown because too many files have changed in this diff Show More