diff --git a/CMakeLists.txt b/CMakeLists.txt index 6552d24e2..14ac26dd4 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -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") diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index babe44b77..d3d50b2cc 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -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} ) diff --git a/thirdparty/CMakeLists.txt b/thirdparty/CMakeLists.txt index 2d2836537..f28fdff43 100644 --- a/thirdparty/CMakeLists.txt +++ b/thirdparty/CMakeLists.txt @@ -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 ) diff --git a/thirdparty/liblastfm2/CMakeLists.txt b/thirdparty/liblastfm2/CMakeLists.txt new file mode 100644 index 000000000..e8b020b78 --- /dev/null +++ b/thirdparty/liblastfm2/CMakeLists.txt @@ -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) + diff --git a/thirdparty/liblastfm2/COPYING b/thirdparty/liblastfm2/COPYING new file mode 100644 index 000000000..94a045322 --- /dev/null +++ b/thirdparty/liblastfm2/COPYING @@ -0,0 +1,621 @@ + GNU GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + 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 diff --git a/thirdparty/liblastfm2/README b/thirdparty/liblastfm2/README new file mode 100644 index 000000000..d39417ecc --- /dev/null +++ b/thirdparty/liblastfm2/README @@ -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 +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" diff --git a/thirdparty/liblastfm2/admin/lastfm.h.rb b/thirdparty/liblastfm2/admin/lastfm.h.rb new file mode 100755 index 000000000..1582b7086 --- /dev/null +++ b/thirdparty/liblastfm2/admin/lastfm.h.rb @@ -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 \ No newline at end of file diff --git a/thirdparty/liblastfm2/admin/platform.rb b/thirdparty/liblastfm2/admin/platform.rb new file mode 100644 index 000000000..54eda1db1 --- /dev/null +++ b/thirdparty/liblastfm2/admin/platform.rb @@ -0,0 +1,101 @@ +# +# platform.rb: naive platform detection for Ruby +# author: Matt Mower +# + +# == 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 diff --git a/thirdparty/liblastfm2/admin/qpp b/thirdparty/liblastfm2/admin/qpp new file mode 100755 index 000000000..a99c5ce34 --- /dev/null +++ b/thirdparty/liblastfm2/admin/qpp @@ -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 ) diff --git a/thirdparty/liblastfm2/admin/utils.rb b/thirdparty/liblastfm2/admin/utils.rb new file mode 100644 index 000000000..ddcb01917 --- /dev/null +++ b/thirdparty/liblastfm2/admin/utils.rb @@ -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 \ No newline at end of file diff --git a/thirdparty/liblastfm2/admin/which_qmake.rb b/thirdparty/liblastfm2/admin/which_qmake.rb new file mode 100644 index 000000000..f621de3e9 --- /dev/null +++ b/thirdparty/liblastfm2/admin/which_qmake.rb @@ -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 \ No newline at end of file diff --git a/thirdparty/liblastfm2/cmake/modules/FindLibFFTW3.cmake b/thirdparty/liblastfm2/cmake/modules/FindLibFFTW3.cmake new file mode 100644 index 000000000..fa6419a2b --- /dev/null +++ b/thirdparty/liblastfm2/cmake/modules/FindLibFFTW3.cmake @@ -0,0 +1,45 @@ +# This file is copyrighted under the BSD-license for buildsystem files of KDE +# copyright 2010, Patrick von Reth +# +# +# - 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) diff --git a/thirdparty/liblastfm2/cmake/modules/FindLibSamplerate.cmake b/thirdparty/liblastfm2/cmake/modules/FindLibSamplerate.cmake new file mode 100644 index 000000000..d77536b2f --- /dev/null +++ b/thirdparty/liblastfm2/cmake/modules/FindLibSamplerate.cmake @@ -0,0 +1,22 @@ +# This file is copyrighted under the BSD-license for buildsystem files of KDE +# copyright 2010, Patrick von Reth +# +# +# - 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) diff --git a/thirdparty/liblastfm2/configure b/thirdparty/liblastfm2/configure new file mode 100755 index 000000000..757edcbc1 --- /dev/null +++ b/thirdparty/liblastfm2/configure @@ -0,0 +1,127 @@ +#!/usr/bin/ruby +if ARGV.include? '--help' + puts "usage: ./configure [--prefix ] [--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 diff --git a/thirdparty/liblastfm2/demos/demo1.cpp b/thirdparty/liblastfm2/demos/demo1.cpp new file mode 100644 index 000000000..b0fd8df99 --- /dev/null +++ b/thirdparty/liblastfm2/demos/demo1.cpp @@ -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 // this includes everything in liblastfm, you may prefer +#include // to just include what you need with your project. Still +#include // we've given you the option. +#include +#include + +class ArtistList : public QListWidget +{ + Q_OBJECT + + QPointer 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(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 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" diff --git a/thirdparty/liblastfm2/demos/demo2.cpp b/thirdparty/liblastfm2/demos/demo2.cpp new file mode 100644 index 000000000..53c9a7ec9 --- /dev/null +++ b/thirdparty/liblastfm2/demos/demo2.cpp @@ -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 +#include + + +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 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: + // + // + // + // mxcl + // d580d57f32848f5dcf574d1ce18d78b2 + // 1 + // + // + // + // 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" diff --git a/thirdparty/liblastfm2/demos/demo3.cpp b/thirdparty/liblastfm2/demos/demo3.cpp new file mode 100644 index 000000000..2bf3c1db3 --- /dev/null +++ b/thirdparty/liblastfm2/demos/demo3.cpp @@ -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 +#include +#include +#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" diff --git a/thirdparty/liblastfm2/demos/demos.pro b/thirdparty/liblastfm2/demos/demos.pro new file mode 100644 index 000000000..356edff74 --- /dev/null +++ b/thirdparty/liblastfm2/demos/demos.pro @@ -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 diff --git a/thirdparty/liblastfm2/src/CMakeLists.txt b/thirdparty/liblastfm2/src/CMakeLists.txt new file mode 100644 index 000000000..c7d569442 --- /dev/null +++ b/thirdparty/liblastfm2/src/CMakeLists.txt @@ -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} +) diff --git a/thirdparty/liblastfm2/src/core/README b/thirdparty/liblastfm2/src/core/README new file mode 100644 index 000000000..b725eac20 --- /dev/null +++ b/thirdparty/liblastfm2/src/core/README @@ -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. + + diff --git a/thirdparty/liblastfm2/src/core/UrlBuilder.cpp b/thirdparty/liblastfm2/src/core/UrlBuilder.cpp new file mode 100644 index 000000000..0f451c7e5 --- /dev/null +++ b/thirdparty/liblastfm2/src/core/UrlBuilder.cpp @@ -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 . +*/ +#include "UrlBuilder.h" +#include +#include + + +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() << '&' << '/' << ';' << '+' << '#' << '%') + 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; +} diff --git a/thirdparty/liblastfm2/src/core/UrlBuilder.h b/thirdparty/liblastfm2/src/core/UrlBuilder.h new file mode 100644 index 000000000..42014537c --- /dev/null +++ b/thirdparty/liblastfm2/src/core/UrlBuilder.h @@ -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 . +*/ +#ifndef LASTFM_URL_BUILDER_H +#define LASTFM_URL_BUILDER_H + +#include +#include +#include +#include + + +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 diff --git a/thirdparty/liblastfm2/src/core/XmlQuery.cpp b/thirdparty/liblastfm2/src/core/XmlQuery.cpp new file mode 100644 index 000000000..5c68aaa81 --- /dev/null +++ b/thirdparty/liblastfm2/src/core/XmlQuery.cpp @@ -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 . +*/ +#include "XmlQuery.h" +#include +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::children( const QString& named ) const +{ + QList 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; +} diff --git a/thirdparty/liblastfm2/src/core/XmlQuery.h b/thirdparty/liblastfm2/src/core/XmlQuery.h new file mode 100644 index 000000000..a3c22d5c2 --- /dev/null +++ b/thirdparty/liblastfm2/src/core/XmlQuery.h @@ -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 . +*/ +#ifndef LASTFM_XMLQUERY_H +#define LASTFM_XMLQUERY_H + +#include +#include +#include + +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 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 diff --git a/thirdparty/liblastfm2/src/core/misc.cpp b/thirdparty/liblastfm2/src/core/misc.cpp new file mode 100644 index 000000000..13b5d16af --- /dev/null +++ b/thirdparty/liblastfm2/src/core/misc.cpp @@ -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 . +*/ +#include "misc.h" +#include +#ifdef WIN32 + #include +#endif +#ifdef Q_WS_MAC + #include +#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 diff --git a/thirdparty/liblastfm2/src/core/misc.h b/thirdparty/liblastfm2/src/core/misc.h new file mode 100644 index 000000000..3f41ec534 --- /dev/null +++ b/thirdparty/liblastfm2/src/core/misc.h @@ -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 . +*/ +#ifndef LASTFM_MISC_H +#define LASTFM_MISC_H + +#include +#include +#include +#include + +#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 diff --git a/thirdparty/liblastfm2/src/fingerprint/CMakeLists.txt b/thirdparty/liblastfm2/src/fingerprint/CMakeLists.txt new file mode 100644 index 000000000..eae8397a8 --- /dev/null +++ b/thirdparty/liblastfm2/src/fingerprint/CMakeLists.txt @@ -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" ) diff --git a/thirdparty/liblastfm2/src/fingerprint/Collection.cpp b/thirdparty/liblastfm2/src/fingerprint/Collection.cpp new file mode 100644 index 000000000..214e264e2 --- /dev/null +++ b/thirdparty/liblastfm2/src/fingerprint/Collection.cpp @@ -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 . +*/ + +#include "Collection.h" +#include "../core/misc.h" +#include +#include +#include +#include +#include +#include +#include + +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; +} diff --git a/thirdparty/liblastfm2/src/fingerprint/Collection.h b/thirdparty/liblastfm2/src/fingerprint/Collection.h new file mode 100644 index 000000000..9a1f3bd17 --- /dev/null +++ b/thirdparty/liblastfm2/src/fingerprint/Collection.h @@ -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 . +*/ + +/** Class that we use to store fingerprints, basically + */ + +#ifndef COLLECTION_H +#define COLLECTION_H + +#include +#include + + +/** @author: */ +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 diff --git a/thirdparty/liblastfm2/src/fingerprint/EXAMPLE.cpp b/thirdparty/liblastfm2/src/fingerprint/EXAMPLE.cpp new file mode 100644 index 000000000..f86d4bbaf --- /dev/null +++ b/thirdparty/liblastfm2/src/fingerprint/EXAMPLE.cpp @@ -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 . +*/ +#include +#include +#include +#include + +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 + } +} diff --git a/thirdparty/liblastfm2/src/fingerprint/Fingerprint.cpp b/thirdparty/liblastfm2/src/fingerprint/Fingerprint.cpp new file mode 100644 index 000000000..b23805c7d --- /dev/null +++ b/thirdparty/liblastfm2/src/fingerprint/Fingerprint.cpp @@ -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 . +*/ + +#include "Fingerprint.h" +#include "FingerprintableSource.h" +#include "Collection.h" +#include "Sha256.h" +#include "fplib/FingerprintExtractor.h" +#include "../ws/ws.h" +#include +#include +#include +#include +#include +#include + +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 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(pBuffer) & 7L)) + pMovableBuffer += 8 - offs; + + unsigned int len; + + for (;;) + { + inFile.read( reinterpret_cast(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; +} diff --git a/thirdparty/liblastfm2/src/fingerprint/Fingerprint.h b/thirdparty/liblastfm2/src/fingerprint/Fingerprint.h new file mode 100644 index 000000000..b793c4bb9 --- /dev/null +++ b/thirdparty/liblastfm2/src/fingerprint/Fingerprint.h @@ -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 . +*/ +#ifndef LASTFM_FINGERPRINT_H +#define LASTFM_FINGERPRINT_H + +#include +#include + + +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 diff --git a/thirdparty/liblastfm2/src/fingerprint/FingerprintableSource.h b/thirdparty/liblastfm2/src/fingerprint/FingerprintableSource.h new file mode 100644 index 000000000..9954fd368 --- /dev/null +++ b/thirdparty/liblastfm2/src/fingerprint/FingerprintableSource.h @@ -0,0 +1,48 @@ +/* + Copyright 2009 Last.fm Ltd. + Copyright 2009 John Stamp + + 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 . +*/ + +#ifndef LASTFM_FINGERPRINTABLE_SOURCE_H +#define LASTFM_FINGERPRINTABLE_SOURCE_H + +#include +#include + +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 diff --git a/thirdparty/liblastfm2/src/fingerprint/Sha256.cpp b/thirdparty/liblastfm2/src/fingerprint/Sha256.cpp new file mode 100644 index 000000000..9be3c18ac --- /dev/null +++ b/thirdparty/liblastfm2/src/fingerprint/Sha256.cpp @@ -0,0 +1,480 @@ +/*- + * Copyright (c) 2001-2003 Allan Saddi + * 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 +#endif /* HAVE_CONFIG_H */ + +#if HAVE_INTTYPES_H +# include +#else +# if HAVE_STDINT_H +# include +# endif +#endif + +#include + +#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 +#include + +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 */ diff --git a/thirdparty/liblastfm2/src/fingerprint/Sha256.h b/thirdparty/liblastfm2/src/fingerprint/Sha256.h new file mode 100644 index 000000000..433c8f9a6 --- /dev/null +++ b/thirdparty/liblastfm2/src/fingerprint/Sha256.h @@ -0,0 +1,180 @@ +/*- + * Copyright (c) 2001-2003 Allan Saddi + * 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(pBuffer) & 7L)) +// pBuffer += 8 - offs; +// +// unsigned int len; +// +// ifstream inFile("test.txt", ios::binary); +// +// for (;;) +// { +// inFile.read( reinterpret_cast(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 header file. */ +#ifndef WIN32 +#define HAVE_INTTYPES_H 1 +#endif + +/* Define to 1 if you have the header file. */ +#define HAVE_MEMORY_H 1 + +/* Define to 1 if you have the header file. */ +#ifndef WIN32 +#define HAVE_STDINT_H 1 +#endif + +/* Define to 1 if you have the header file. */ +#define HAVE_STDLIB_H 1 + +/* Define to 1 if you have the header file. */ +#define HAVE_STRINGS_H 1 + +/* Define to 1 if you have the 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 header file. */ +#define HAVE_SYS_STAT_H 1 + +/* Define to 1 if you have the header file. */ +#ifndef WIN32 +#define HAVE_SYS_TYPES_H 1 +#endif + +/* Define to 1 if you have the 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 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 +#else +# if HAVE_STDINT_H +# include +# 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 */ diff --git a/thirdparty/liblastfm2/src/fingerprint/contrib/AacSource.cpp b/thirdparty/liblastfm2/src/fingerprint/contrib/AacSource.cpp new file mode 100644 index 000000000..77bacd343 --- /dev/null +++ b/thirdparty/liblastfm2/src/fingerprint/contrib/AacSource.cpp @@ -0,0 +1,953 @@ +/* + Copyright 2009 Last.fm Ltd. + Copyright 2009 John Stamp + 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 . +*/ +#include "AacSource.h" +#include "AacSource_p.h" + +#include +#include +#include +#include +#include +#include +#include +#include + + +//////////////////////////////////////////////////////////////////////// +// +// 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(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(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( 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(fileread) * 8 / bitrate + 0.5; + } + } + + lengthSecs = static_cast(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(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(fread( buffer, 1, length, static_cast(user_data) )); +} + + +uint32_t seek_callback( void *user_data, uint64_t position ) +{ + return fseek( static_cast(user_data), static_cast(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( 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(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(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( std::numeric_limits::max() ); + + for (;;) + { + if ( m_aacFile->m_header == AAC_File::AAC_MP4 ) + { + if ( !static_cast(m_aacFile)->readSample() ) + break; + } + NeAACDecFrameInfo frameInfo; + + void* sampleBuffer = NeAACDecDecode(m_aacFile->m_decoder, &frameInfo, m_aacFile->m_inBuf, static_cast(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(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(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(m_aacFile)->readSample() ) + { + m_eof = true; + return static_cast(nwrit); + } + } + NeAACDecFrameInfo frameInfo; + + sampleBuffer = NeAACDecDecode(m_aacFile->m_decoder, &frameInfo, m_aacFile->m_inBuf, static_cast(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(realloc( m_aacFile->m_overflow, (frameInfo.samples - samples_to_use) * sizeof(signed short) ) ); + memcpy( m_aacFile->m_overflow, static_cast(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(nwrit); +} diff --git a/thirdparty/liblastfm2/src/fingerprint/contrib/AacSource.h b/thirdparty/liblastfm2/src/fingerprint/contrib/AacSource.h new file mode 100644 index 000000000..0fff6f585 --- /dev/null +++ b/thirdparty/liblastfm2/src/fingerprint/contrib/AacSource.h @@ -0,0 +1,46 @@ +/* + Copyright 2009 Last.fm Ltd. + Copyright 2009 John Stamp + + 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 . +*/ +#ifndef __AAC_SOURCE_H__ +#define __AAC_SOURCE_H__ + +#include + +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 + diff --git a/thirdparty/liblastfm2/src/fingerprint/contrib/AacSource_p.h b/thirdparty/liblastfm2/src/fingerprint/contrib/AacSource_p.h new file mode 100644 index 000000000..870482c1f --- /dev/null +++ b/thirdparty/liblastfm2/src/fingerprint/contrib/AacSource_p.h @@ -0,0 +1,94 @@ +/* + Copyright 2009 Last.fm Ltd. + Copyright 2009 John Stamp + + 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 . +*/ +#include +#include + +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; +}; diff --git a/thirdparty/liblastfm2/src/fingerprint/contrib/FlacSource.cpp b/thirdparty/liblastfm2/src/fingerprint/contrib/FlacSource.cpp new file mode 100644 index 000000000..1e1e29900 --- /dev/null +++ b/thirdparty/liblastfm2/src/fingerprint/contrib/FlacSource.cpp @@ -0,0 +1,339 @@ +/* + Copyright 2009 Last.fm Ltd. + Copyright 2009 John Stamp + + 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 . +*/ +#include "FlacSource.h" +#include +#include +#include +#include +#include +#include +#include + +#include + + +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(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(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(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(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( static_cast(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( static_cast(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( std::numeric_limits::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(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(nwrit); + } + return static_cast(nwrit); +} + +// ----------------------------------------------------------------------------- + diff --git a/thirdparty/liblastfm2/src/fingerprint/contrib/FlacSource.h b/thirdparty/liblastfm2/src/fingerprint/contrib/FlacSource.h new file mode 100644 index 000000000..24dd97f11 --- /dev/null +++ b/thirdparty/liblastfm2/src/fingerprint/contrib/FlacSource.h @@ -0,0 +1,74 @@ +/* + Copyright 2009 Last.fm Ltd. + Copyright 2009 John Stamp + + 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 . +*/ +#ifndef __FLAC_SOURCE_H__ +#define __FLAC_SOURCE_H__ + +#include +#include +#include + + +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 + diff --git a/thirdparty/liblastfm2/src/fingerprint/contrib/MadSource.cpp b/thirdparty/liblastfm2/src/fingerprint/contrib/MadSource.cpp new file mode 100644 index 000000000..00e725ae6 --- /dev/null +++ b/thirdparty/liblastfm2/src/fingerprint/contrib/MadSource.cpp @@ -0,0 +1,514 @@ +/* + Copyright 2009 Last.fm Ltd. + Copyright 2009 John Stamp + + 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 . +*/ + +#include +#include +#include +#include +#include +#include +#include +#include +#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(madTimer.seconds); + samplerate = static_cast( (avgSamplerate/nFrames) + 0.5 ); + bitrate = static_cast( (avgBitrate/nFrames) + 0.5 ); + nchannels = static_cast( (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(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(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( numeric_limits::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(nwrit); + } + + return static_cast(nwrit); +} + +// ----------------------------------------------------------------------------- + diff --git a/thirdparty/liblastfm2/src/fingerprint/contrib/MadSource.h b/thirdparty/liblastfm2/src/fingerprint/contrib/MadSource.h new file mode 100644 index 000000000..c22cb6d5d --- /dev/null +++ b/thirdparty/liblastfm2/src/fingerprint/contrib/MadSource.h @@ -0,0 +1,69 @@ +/* + Copyright 2009 Last.fm Ltd. + Copyright 2009 John Stamp + + 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 . +*/ + +#ifndef __MP3_SOURCE_H__ +#define __MP3_SOURCE_H__ + +#include +#include +#include +#include +#include +#include + + +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 diff --git a/thirdparty/liblastfm2/src/fingerprint/contrib/VorbisSource.cpp b/thirdparty/liblastfm2/src/fingerprint/contrib/VorbisSource.cpp new file mode 100644 index 000000000..fd4defb17 --- /dev/null +++ b/thirdparty/liblastfm2/src/fingerprint/contrib/VorbisSource.cpp @@ -0,0 +1,204 @@ +/* + Copyright 2009 Last.fm Ltd. + Copyright 2009 John Stamp + + 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 . +*/ +#include "VorbisSource.h" +#include +#include +#include +#include +#include +#include +#include + +// 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(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(ov_info( &m_vf, -1 )->rate); + lengthSecs = static_cast(ov_time_total( &m_vf, -1 ) + 0.5); + bitrate = static_cast(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( std::numeric_limits::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(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(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(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(pBuffer) + charwrit; + charwrit += charReadBytes; + + assert( charwrit <= bufferSize * wordSize ); + memcpy( pBufferIt, buf, charReadBytes ); + + if (charwrit == bufferSize * wordSize) + return static_cast(charwrit/wordSize); + } + + return static_cast(charwrit/wordSize); +} + +// ----------------------------------------------------------------------------- + diff --git a/thirdparty/liblastfm2/src/fingerprint/contrib/VorbisSource.h b/thirdparty/liblastfm2/src/fingerprint/contrib/VorbisSource.h new file mode 100644 index 000000000..988ce6239 --- /dev/null +++ b/thirdparty/liblastfm2/src/fingerprint/contrib/VorbisSource.h @@ -0,0 +1,47 @@ +/* + Copyright 2009 Last.fm Ltd. + Copyright 2009 John Stamp + + 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 . +*/ +#ifndef __VORBIS_SOURCE_H__ +#define __VORBIS_SOURCE_H__ + +#include +#include + + +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 diff --git a/thirdparty/liblastfm2/src/fingerprint/contrib/lastfm-fingerprint.pro b/thirdparty/liblastfm2/src/fingerprint/contrib/lastfm-fingerprint.pro new file mode 100644 index 000000000..bd615e723 --- /dev/null +++ b/thirdparty/liblastfm2/src/fingerprint/contrib/lastfm-fingerprint.pro @@ -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 +} diff --git a/thirdparty/liblastfm2/src/fingerprint/contrib/main.cpp b/thirdparty/liblastfm2/src/fingerprint/contrib/main.cpp new file mode 100644 index 000000000..3b035f2d8 --- /dev/null +++ b/thirdparty/liblastfm2/src/fingerprint/contrib/main.cpp @@ -0,0 +1,173 @@ +/* + Copyright 2009 Last.fm Ltd. + Copyright 2009 John Stamp + + 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 . +*/ + +// 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 +#include +#include +#include +#include +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; +} diff --git a/thirdparty/liblastfm2/src/fingerprint/fingerprint.pro b/thirdparty/liblastfm2/src/fingerprint/fingerprint.pro new file mode 100644 index 000000000..043ad7bfb --- /dev/null +++ b/thirdparty/liblastfm2/src/fingerprint/fingerprint.pro @@ -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 + +} diff --git a/thirdparty/liblastfm2/src/fingerprint/fplib/CircularArray.h b/thirdparty/liblastfm2/src/fingerprint/fplib/CircularArray.h new file mode 100644 index 000000000..bfec5a8fd --- /dev/null +++ b/thirdparty/liblastfm2/src/fingerprint/fplib/CircularArray.h @@ -0,0 +1,292 @@ +/* + Copyright 2005-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 . +*/ +#ifndef __CIRCULAR_ARRAY_H +#define __CIRCULAR_ARRAY_H + +#include +#include +#include +#include +#include // for memset +#include // 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 + // struct iterator { ... + + // ---------- Forward declarations + + class iterator : + public std::iterator + { + // 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 + //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 diff --git a/thirdparty/liblastfm2/src/fingerprint/fplib/Filter.cpp b/thirdparty/liblastfm2/src/fingerprint/fplib/Filter.cpp new file mode 100644 index 000000000..eed4ea3a3 --- /dev/null +++ b/thirdparty/liblastfm2/src/fingerprint/fplib/Filter.cpp @@ -0,0 +1,128 @@ +/* + Copyright 2005-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 . +*/ +#include +#include // for max +#include + +#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 time_lengths; + + while (t < KEYWIDTH) + { + time_lengths.push_back(t); + t = max( static_cast( round__(time_rate*t) ) + + static_cast( 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 + +// ----------------------------------------------------------------------------- diff --git a/thirdparty/liblastfm2/src/fingerprint/fplib/Filter.h b/thirdparty/liblastfm2/src/fingerprint/fplib/Filter.h new file mode 100644 index 000000000..04185eec2 --- /dev/null +++ b/thirdparty/liblastfm2/src/fingerprint/fplib/Filter.h @@ -0,0 +1,47 @@ +/* + Copyright 2005-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 . +*/ +#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 diff --git a/thirdparty/liblastfm2/src/fingerprint/fplib/FingerprintExtractor.cpp b/thirdparty/liblastfm2/src/fingerprint/fplib/FingerprintExtractor.cpp new file mode 100644 index 000000000..305aad7b5 --- /dev/null +++ b/thirdparty/liblastfm2/src/fingerprint/fplib/FingerprintExtractor.cpp @@ -0,0 +1,786 @@ +/* + Copyright 2005-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 . +*/ +#include +#include +#include +#include +#include +#include +#include +#include + +#include // 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(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 m_normWindow; + OptFFT* m_pFFT; + + ////////////////////////////////////////////////////////////////////////// + + // libsamplerate + SRC_STATE* m_pDownsampleState; + SRC_DATA m_downsampleData; + + vector 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 m_filters; + + deque m_groupWindow; + vector m_groups; + unsigned int m_processedKeys; + + vector 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 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& signal ); +unsigned int processKeys( deque& groups, size_t size, PimplData& pd ); +void integralImage( float** ppFrames, unsigned int nFrames ); +void computeBits( vector& bits, + const vector& 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( (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(QUERY_SIZE_SECS * 1000), + static_cast(QUERY_START_SECS * 1000), + MIN_UNIQUE_KEYS, + static_cast(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::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((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(skipMs) - static_cast((pd.m_normalizedWindowMs/2)), 0 ); + pd.m_toSkipSize = static_cast( 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( 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 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(pSourcePCMIt_end - pSourcePCMIt), + pd.m_nchannels); + + pd.m_downsampleData.data_in = &(pd.m_floatInData[0]); + pd.m_downsampleData.input_frames = static_cast(pd.m_floatInData.size()); + + pd.m_downsampleData.data_out = pd.m_pDownsampledCurrIt; + pd.m_downsampleData.output_frames = static_cast(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(pSourcePCMIt_end - pSourcePCMIt), + pd.m_nchannels); + + pd.m_downsampleData.data_in = &(pd.m_floatInData[0]); + pd.m_downsampleData.input_frames = static_cast(pd.m_floatInData.size()); + + pd.m_downsampleData.data_out = pd.m_pDownsampledCurrIt; + pd.m_downsampleData.output_frames = static_cast(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(pd.m_compensateBufferSize); + size_t window_pos = static_cast(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::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::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 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(&pd.m_bigEndianGroups[0]), pd.m_bigEndianGroups.size() * sizeof(GroupData) ); + +#else + return make_pair(reinterpret_cast(&pd.m_groups[0]), pd.m_groups.size() * sizeof(GroupData) ); +#endif + } + else + return make_pair(reinterpret_cast(0), 0); // here's where null_ptr would become useful! +} + +// ----------------------------------------------------------------------------- +// ----------------------------------------------------------------------------- +// ----------------------------------------------------------------------------- + +float getRMS(const FloatingAverage& signal) +{ + // we don't want to normalize by the real rms, because excessive clipping will occur + float rms = sqrtf(static_cast(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& 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(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( static_cast(ppFrames[y-1][x]) + + static_cast(ppFrames[y][x-1]) - + static_cast(ppFrames[y-1][x-1]) ); + } + } +} + +// --------------------------------------------------------------------- +// +/// Convert bands to bits, using the supplied filters +void computeBits( vector& bits, + const vector& 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(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(frames[t3-1][b3-1]) - static_cast(frames[t3-1][b1-1]) + - static_cast(frames[t1-1][b3-1]) + static_cast(frames[t1-1][b1-1]); + else + X = static_cast(frames[t3-1][b3-1]) - static_cast(frames[t1-1][b3-1]); + break; + } + case 2: { // energy difference over time + if (b1 > 0) + X = static_cast(frames[t1-1][b1-1]) - 2*static_cast(frames[t2-2][b1-1]) + + static_cast(frames[t3-1][b1-1]) - static_cast(frames[t1-1][b3-1]) + + 2*static_cast(frames[t2-2][b3-1]) - static_cast(frames[t3-1][b3-1]); + else + X = - static_cast(frames[t1-1][b3-1]) + 2*static_cast(frames[t2-2][b3-1]) + - static_cast(frames[t3-1][b3-1]); + break; + + } + case 3: { // energy difference over bands + if (b1 > 0) + X = static_cast(frames[t1-1][b1-1]) - static_cast(frames[t3-1][b1-1]) + - 2*static_cast(frames[t1-1][b2-1]) + 2*static_cast(frames[t3-1][b2-1]) + + static_cast(frames[t1-1][b3-1]) - static_cast(frames[t3-1][b3-1]); + else + X = - 2*static_cast(frames[t1-1][b2-1]) + 2*static_cast(frames[t3-1][b2-1]) + + static_cast(frames[t1-1][b3-1]) - static_cast(frames[t3-1][b3-1]); + break; + } + case 4: { + // energy difference over time and bands + if (b1 > 0) + X = static_cast(frames[t1-1][b1-1]) - 2*static_cast(frames[t2-2][b1-1]) + + static_cast(frames[t3-1][b1-1]) - 2*static_cast(frames[t1-1][b2-1]) + + 4*static_cast(frames[t2-2][b2-1]) - 2*static_cast(frames[t3-1][b2-1]) + + static_cast(frames[t1-1][b3-1]) - 2*static_cast(frames[t2-2][b3-1]) + + static_cast(frames[t3-1][b3-1]); + else + X = - 2*static_cast(frames[t1-1][b2-1]) + 4*static_cast(frames[t2-2][b2-1]) + - 2*static_cast(frames[t3-1][b2-1]) + static_cast(frames[t1-1][b3-1]) + - 2*static_cast(frames[t2-2][b3-1]) + static_cast(frames[t3-1][b3-1]); + break; + } + case 5: { // time peak + if (b1 > 0) + X = - static_cast(frames[t1-1][b1-1]) + 2*static_cast(frames[t_1q-1][b1-1]) + - 2*static_cast(frames[t_3q-1][b1-1]) + static_cast(frames[t3-1][b1-1]) + + static_cast(frames[t1-1][b3-1]) - 2*static_cast(frames[t_1q-1][b3-1]) + + 2*static_cast(frames[t_3q-1][b3-1]) - static_cast(frames[t3-1][b3-1]); + else + X = static_cast(frames[t1-1][b3-1]) - 2*static_cast(frames[t_1q-1][b3-1]) + + 2*static_cast(frames[t_3q-1][b3-1]) - static_cast(frames[t3-1][b3-1]); + + break; + } + case 6: { // band beak + if (b1 > 0) + X = - static_cast(frames[t1-1][b1-1]) + static_cast(frames[t3-1][b1-1]) + + 2*static_cast(frames[t1-1][b_1q-1]) - 2*static_cast(frames[t3-1][b_1q-1]) + - 2*static_cast(frames[t1-1][b_3q-1]) + 2*static_cast(frames[t3-1][b_3q-1]) + + static_cast(frames[t1-1][b3-1]) - static_cast(frames[t3-1][b3-1]); + else + X = + 2*static_cast(frames[t1-1][b_1q-1]) - 2*static_cast(frames[t3-1][b_1q-1]) + - 2*static_cast(frames[t1-1][b_3q-1]) + 2*static_cast(frames[t3-1][b_3q-1]) + + static_cast(frames[t1-1][b3-1]) - static_cast(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::max() * nchannels; + for ( int i = 0; i < srclen; i += 2, ++j ) + { + out[j] = static_cast( static_cast(static_cast(in[i]) + static_cast(in[i+1])) / div ); + } + } + break; + + default: + throw( std::runtime_error("Unsupported number of channels!") ); + } + +} + +// ----------------------------------------------------------------------------- + +} // end of namespace + +// ----------------------------------------------------------------------------- diff --git a/thirdparty/liblastfm2/src/fingerprint/fplib/FingerprintExtractor.h b/thirdparty/liblastfm2/src/fingerprint/fplib/FingerprintExtractor.h new file mode 100644 index 000000000..fac9b5887 --- /dev/null +++ b/thirdparty/liblastfm2/src/fingerprint/fplib/FingerprintExtractor.h @@ -0,0 +1,77 @@ +/* + Copyright 2005-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 . +*/ +#ifndef __FINGERPRINT_EXTRACTOR_H +#define __FINGERPRINT_EXTRACTOR_H + +#include // for pair +#include // 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 if the data is not ready + std::pair 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 diff --git a/thirdparty/liblastfm2/src/fingerprint/fplib/FloatingAverage.h b/thirdparty/liblastfm2/src/fingerprint/fplib/FloatingAverage.h new file mode 100644 index 000000000..1be665bd0 --- /dev/null +++ b/thirdparty/liblastfm2/src/fingerprint/fplib/FloatingAverage.h @@ -0,0 +1,106 @@ +/* + Copyright 2005-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 . +*/ +#ifndef __FLOAT_AVERAGE_H__ +#define __FLOAT_AVERAGE_H__ + +//#include +#include +#include "CircularArray.h" + +template +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 m_values; + CircularArray m_values; + typename CircularArray::iterator m_valIt; + + bool m_bufferFilled; + T m_sum; +}; + +#endif // __FLOAT_AVERAGE_H__ diff --git a/thirdparty/liblastfm2/src/fingerprint/fplib/OptFFT.cpp b/thirdparty/liblastfm2/src/fingerprint/fplib/OptFFT.cpp new file mode 100644 index 000000000..3728c974c --- /dev/null +++ b/thirdparty/liblastfm2/src/fingerprint/fplib/OptFFT.cpp @@ -0,0 +1,411 @@ +/* + Copyright 2005-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 . +*/ +#include "OptFFT.h" +#include "fp_helper_fun.h" +#include "Filter.h" // for NBANDS + +#include +#include +#include +#include +#include +#include +#include +#include + +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( fftw_malloc(sizeof(double) * FRAMESIZE) ); + //m_pOut = static_cast( 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( fftwf_malloc(sizeof(float) * FRAMESIZE) ); + // m_pOut = static_cast( 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 ( (maxDataSize - FRAMESIZE) / OVERLAPSAMPLES + 1 ); + + m_pIn = static_cast ( 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_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(MAXFREQ) / static_cast(MINFREQ) ) / + static_cast(Filter::NBANDS) + ); + + m_powTable.resize( Filter::NBANDS+1 ); + for ( unsigned int i = 0; i < Filter::NBANDS + 1; ++i ) + m_powTable[i] = static_cast( (pow(base, static_cast(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( (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(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(MINCOEF); + for (unsigned int k = outBlocStart + static_cast(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(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 + +// ---------------------------------------------------------------------- diff --git a/thirdparty/liblastfm2/src/fingerprint/fplib/OptFFT.h b/thirdparty/liblastfm2/src/fingerprint/fplib/OptFFT.h new file mode 100644 index 000000000..2a704ee73 --- /dev/null +++ b/thirdparty/liblastfm2/src/fingerprint/fplib/OptFFT.h @@ -0,0 +1,63 @@ +/* + Copyright 2005-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 . +*/ +#ifndef __OPT_FFT_H +#define __OPT_FFT_H + +#include +#include + +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 m_powTable; + +}; + +} // end of namespace + +#endif // OPT_FFT diff --git a/thirdparty/liblastfm2/src/fingerprint/fplib/fp_helper_fun.h b/thirdparty/liblastfm2/src/fingerprint/fplib/fp_helper_fun.h new file mode 100644 index 000000000..947b63037 --- /dev/null +++ b/thirdparty/liblastfm2/src/fingerprint/fplib/fp_helper_fun.h @@ -0,0 +1,443 @@ +/* + Copyright 2005-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 . +*/ +#ifndef __FINGERPRINT_HELPER_FUNCTIONS_H +#define __FINGERPRINT_HELPER_FUNCTIONS_H + +#include +#include +#include + +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((static_cast(mSecs) / (1000.0 * OVERLAPSAMPLES) ) * DFREQ ) + 1; +} + +// ----------------------------------------------------------------------------- + +template +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 +void cutGroups( + std::vector& groups, + const unsigned int startMS, + const unsigned int lengthMS ) +{ + typename std::vector::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::iterator it = groups.begin(); it != groups.end(); ++it) + keys_begin -= it->count; +} + +// ------------------------------------------------------------------------- + +template +void keys2GroupData( + const std::vector& keys, // in + std::vector& groupData, + bool clearDst = true ) // out +{ + if (clearDst) + groupData.clear(); + + if (keys.empty()) + return; + + TGroupData tmpGroup; + std::vector::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 +void keys2GroupData( + const std::vector& keys, // in + std::deque& groupData, + bool clearDst = true ) // out +{ + if (clearDst) + groupData.clear(); + + if (keys.empty()) + return; + + TGroupData tmpGroup; + std::vector::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 +inline +void groupData2Keys( + const std::vector& groupData, // in + std::vector& keys ) // out +{ + keys.clear(); + + typename std::vector::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 +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 +bool +reduceGroups( + std::vector& 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::iterator begIt = groups.begin(); + typename std::vector::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 resGrups(begIt, endIt); + groups.swap(resGrups); + + return true; +} + + +// ------------------------------------------------------------------------- + +template +inline bool enoughUniqueGoodGroups( + const GroupDataIt& beg, + const GroupDataIt& end, + unsigned int minUniqueKeys) +{ + std::set groupKeys; + + for (GroupDataIt it = beg; it != end && static_cast(groupKeys.size()) < minUniqueKeys; ++it) + { + if (it->count > MAX_GOOD_GROUP_SIZE) + return false; + + groupKeys.insert(it->key); + } + + return static_cast(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 + diff --git a/thirdparty/liblastfm2/src/global.h b/thirdparty/liblastfm2/src/global.h new file mode 100644 index 000000000..d297ae702 --- /dev/null +++ b/thirdparty/liblastfm2/src/global.h @@ -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 . +*/ + +#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 + +#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 +#include + +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( error, "NetworkError" ); + */ + template 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 + +#endif //LASTFM_GLOBAL_H diff --git a/thirdparty/liblastfm2/src/lastfm.pro b/thirdparty/liblastfm2/src/lastfm.pro new file mode 100644 index 000000000..c0b306f84 --- /dev/null +++ b/thirdparty/liblastfm2/src/lastfm.pro @@ -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 +} diff --git a/thirdparty/liblastfm2/src/radio/RadioStation.cpp b/thirdparty/liblastfm2/src/radio/RadioStation.cpp new file mode 100755 index 000000000..38e9c7d3c --- /dev/null +++ b/thirdparty/liblastfm2/src/radio/RadioStation.cpp @@ -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 . +*/ + +#include +#include + +#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::list( QNetworkReply* r ) +{ + QList 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; +} diff --git a/thirdparty/liblastfm2/src/radio/RadioStation.h b/thirdparty/liblastfm2/src/radio/RadioStation.h new file mode 100644 index 000000000..09ee27743 --- /dev/null +++ b/thirdparty/liblastfm2/src/radio/RadioStation.h @@ -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 . +*/ +#ifndef LASTFM_RADIO_STATION_H +#define LASTFM_RADIO_STATION_H + +#include +#include +#include + +namespace lastfm +{ + /** @author + */ + 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 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 diff --git a/thirdparty/liblastfm2/src/radio/RadioTuner.cpp b/thirdparty/liblastfm2/src/radio/RadioTuner.cpp new file mode 100644 index 000000000..f09b560e1 --- /dev/null +++ b/thirdparty/liblastfm2/src/radio/RadioTuner.cpp @@ -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 . +*/ +#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 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 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 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 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; +} diff --git a/thirdparty/liblastfm2/src/radio/RadioTuner.h b/thirdparty/liblastfm2/src/radio/RadioTuner.h new file mode 100644 index 000000000..7032dfd08 --- /dev/null +++ b/thirdparty/liblastfm2/src/radio/RadioTuner.h @@ -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 . +*/ +#ifndef LASTFM_TUNER_H +#define LASTFM_TUNER_H + +#include +#include +#include +#include + +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 m_queue; + uint m_retry_counter; + }; +} + +#endif diff --git a/thirdparty/liblastfm2/src/scrobble/Audioscrobbler.cpp b/thirdparty/liblastfm2/src/scrobble/Audioscrobbler.cpp new file mode 100644 index 000000000..3606bd1a4 --- /dev/null +++ b/thirdparty/liblastfm2/src/scrobble/Audioscrobbler.cpp @@ -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 . +*/ + +#include + +#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 m_batch; + QPointer m_nowPlayingReply; + QPointer 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 tracks; + tracks.append( track ); + cacheBatch( tracks ); +} + + +void +lastfm::Audioscrobbler::cacheBatch( const QList& 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(trackXml["ignoredMessage"].attribute("code").toInt()) ); + mTrack.setScrobbleStatus( Track::Error ); + } +} + +void +lastfm::Audioscrobbler::onNowPlayingReturn() +{ + lastfm::XmlQuery lfm = static_cast(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; +} diff --git a/thirdparty/liblastfm2/src/scrobble/Audioscrobbler.h b/thirdparty/liblastfm2/src/scrobble/Audioscrobbler.h new file mode 100644 index 000000000..8ad93c026 --- /dev/null +++ b/thirdparty/liblastfm2/src/scrobble/Audioscrobbler.h @@ -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 . +*/ +#ifndef LASTFM_AUDIOSCROBBLER_H +#define LASTFM_AUDIOSCROBBLER_H + +#include +#include +#include +#include +#include +#include + +namespace lastfm +{ + /** @author Max Howell + * 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& 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& ); + + /** 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 diff --git a/thirdparty/liblastfm2/src/scrobble/ScrobbleCache.cpp b/thirdparty/liblastfm2/src/scrobble/ScrobbleCache.cpp new file mode 100644 index 000000000..583d4b4d0 --- /dev/null +++ b/thirdparty/liblastfm2/src/scrobble/ScrobbleCache.cpp @@ -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 . +*/ +#include "ScrobbleCache.h" +#include "ScrobblePoint.h" +#include +#include +#include +#include +#include + +#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 << "\n"; + stream << xml.toString( 2 ); + file.close(); + } +} + + +void +ScrobbleCache::add( const QList& 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& toremove ) +{ + QMutableListIterator 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(); +} diff --git a/thirdparty/liblastfm2/src/scrobble/ScrobbleCache.h b/thirdparty/liblastfm2/src/scrobble/ScrobbleCache.h new file mode 100644 index 000000000..31219263b --- /dev/null +++ b/thirdparty/liblastfm2/src/scrobble/ScrobbleCache.h @@ -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 . +*/ +#ifndef LASTFM_SCROBBLE_CACHE_H +#define LASTFM_SCROBBLE_CACHE_H + +#include "lastfm/Track" +#include +#include + +#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 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& ); + + /** returns the number of tracks left in the queue */ + int remove( const QList& ); + + QList 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 diff --git a/thirdparty/liblastfm2/src/scrobble/ScrobblePoint.h b/thirdparty/liblastfm2/src/scrobble/ScrobblePoint.h new file mode 100644 index 000000000..fce857d46 --- /dev/null +++ b/thirdparty/liblastfm2/src/scrobble/ScrobblePoint.h @@ -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 . +*/ +#ifndef LASTFM_SCROBBLE_POINT_H +#define LASTFM_SCROBBLE_POINT_H + +#include +#include + + +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 diff --git a/thirdparty/liblastfm2/src/types/AbstractType.h b/thirdparty/liblastfm2/src/types/AbstractType.h new file mode 100755 index 000000000..873b5dec9 --- /dev/null +++ b/thirdparty/liblastfm2/src/types/AbstractType.h @@ -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 . +*/ + +#ifndef ABSTRACTTYPE_H +#define ABSTRACTTYPE_H + +#include +#include +#include + +#include + +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 diff --git a/thirdparty/liblastfm2/src/types/Album.cpp b/thirdparty/liblastfm2/src/types/Album.cpp new file mode 100644 index 000000000..fce542911 --- /dev/null +++ b/thirdparty/liblastfm2/src/types/Album.cpp @@ -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 . +*/ +#include "Album.h" +#include "Artist.h" +#include "User.h" +#include "../core/UrlBuilder.h" +#include "../core/XmlQuery.h" +#include "../ws/ws.h" +#include +#include +#include + +QNetworkReply* +lastfm::Album::getInfo(const QString& user, const QString& sk) const +{ + QMap 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 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 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 map; + map["method"] = "album.addTags"; + map["artist"] = m_artist; + map["album"] = m_title; + map["tags"] = tags.join( QChar(',') ); + return lastfm::ws::post(map); +} diff --git a/thirdparty/liblastfm2/src/types/Album.h b/thirdparty/liblastfm2/src/types/Album.h new file mode 100644 index 000000000..afbd52e05 --- /dev/null +++ b/thirdparty/liblastfm2/src/types/Album.h @@ -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 . +*/ +#ifndef LASTFM_ALBUM_H +#define LASTFM_ALBUM_H + +#include +#include +#include +#include + +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 diff --git a/thirdparty/liblastfm2/src/types/Artist.cpp b/thirdparty/liblastfm2/src/types/Artist.cpp new file mode 100644 index 000000000..242834e83 --- /dev/null +++ b/thirdparty/liblastfm2/src/types/Artist.cpp @@ -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 . +*/ +#include "Artist.h" +#include "User.h" +#include "../core/UrlBuilder.h" +#include "../core/XmlQuery.h" +#include "../ws/ws.h" +#include +#include + +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 images( const lastfm::XmlQuery& e ) +{ + QList 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 //private +Artist::params( const QString& method ) const +{ + QMap map; + map["method"] = "artist."+method; + map["artist"] = m_name; + return map; +} + + +QNetworkReply* +Artist::share( const QStringList& recipients, const QString& message, bool isPublic ) const +{ + QMap 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 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 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 map = params("search"); + if (limit > 0) map["limit"] = QString::number(limit); + return ws::get(map); +} + + +QMap /* static */ +Artist::getSimilar( QNetworkReply* r ) +{ + QMap 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 /* static */ +Artist::list( QNetworkReply* r ) +{ + QList 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 map = params("addTags"); + map["tags"] = tags.join( QChar(',') ); + return ws::post(map); +} diff --git a/thirdparty/liblastfm2/src/types/Artist.h b/thirdparty/liblastfm2/src/types/Artist.h new file mode 100644 index 000000000..2bfd6d007 --- /dev/null +++ b/thirdparty/liblastfm2/src/types/Artist.h @@ -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 . +*/ +#ifndef LASTFM_ARTIST_H +#define LASTFM_ARTIST_H + +#include +#include +#include + +#include +#include + + +namespace lastfm +{ + class LASTFM_DLLEXPORT Artist : public AbstractType + { + private: + QString m_name; + QList 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 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 list( QNetworkReply* ); + + QMap params( const QString& method ) const; + }; +} + +#endif diff --git a/thirdparty/liblastfm2/src/types/FingerprintId.cpp b/thirdparty/liblastfm2/src/types/FingerprintId.cpp new file mode 100644 index 000000000..b201534a0 --- /dev/null +++ b/thirdparty/liblastfm2/src/types/FingerprintId.cpp @@ -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 . +*/ +#include "FingerprintId.h" +#include "../ws/ws.h" +#include +#include + + +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 //static +lastfm::FingerprintId::getSuggestions( QNetworkReply* reply ) +{ + QDomDocument xml; + xml.setContent( reply->readAll() ); + QDomNodeList nodes = xml.documentElement().elementsByTagName( "track" ); + + QMap 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; +} diff --git a/thirdparty/liblastfm2/src/types/FingerprintId.h b/thirdparty/liblastfm2/src/types/FingerprintId.h new file mode 100644 index 000000000..5028c7736 --- /dev/null +++ b/thirdparty/liblastfm2/src/types/FingerprintId.h @@ -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 . +*/ +#ifndef LASTFM_FINGERPRINT_ID_H +#define LASTFM_FINGERPRINT_ID_H + +#include +#include + +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 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 diff --git a/thirdparty/liblastfm2/src/types/Mbid.cpp b/thirdparty/liblastfm2/src/types/Mbid.cpp new file mode 100644 index 000000000..1ce1d90c5 --- /dev/null +++ b/thirdparty/liblastfm2/src/types/Mbid.cpp @@ -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 . +*/ +#include "Mbid.h" +#include "mbid_mp3.c" +#include + +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; + } +} diff --git a/thirdparty/liblastfm2/src/types/Mbid.h b/thirdparty/liblastfm2/src/types/Mbid.h new file mode 100644 index 000000000..071d4d1fc --- /dev/null +++ b/thirdparty/liblastfm2/src/types/Mbid.h @@ -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 . +*/ +#ifndef LASTFM_MBID_H +#define LASTFM_MBID_H + +#include +#include + +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 diff --git a/thirdparty/liblastfm2/src/types/Playlist.cpp b/thirdparty/liblastfm2/src/types/Playlist.cpp new file mode 100644 index 000000000..807da1c6a --- /dev/null +++ b/thirdparty/liblastfm2/src/types/Playlist.cpp @@ -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 . +*/ +#include "Playlist.h" +#include "Track.h" +#include "../ws/ws.h" + + +QNetworkReply* +lastfm::Playlist::addTrack( const Track& t ) const +{ + QMap 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 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 map; + map["method"] = "playlist.create"; + map["title"] = title; + if (description.size()) + map["description"] = description; + return lastfm::ws::post(map); +} diff --git a/thirdparty/liblastfm2/src/types/Playlist.h b/thirdparty/liblastfm2/src/types/Playlist.h new file mode 100644 index 000000000..a271e7d5b --- /dev/null +++ b/thirdparty/liblastfm2/src/types/Playlist.h @@ -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 . +*/ +#ifndef LASTFM_PLAYLIST_H +#define LASTFM_PLAYLIST_H + +#include +#include +#include +#include + +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 diff --git a/thirdparty/liblastfm2/src/types/Tag.cpp b/thirdparty/liblastfm2/src/types/Tag.cpp new file mode 100644 index 000000000..40ddfb43b --- /dev/null +++ b/thirdparty/liblastfm2/src/types/Tag.cpp @@ -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 . +*/ +#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 map; + map["method"] = "tag.search"; + map["tag"] = m_name; + return ws::get(map); +} + +//static +QNetworkReply* +Tag::getTopTags() +{ + QMap map; + map["method"] = "tag.getTopTags"; + return ws::get(map); +} + +QMap //static +Tag::list( QNetworkReply* r ) +{ + QMap 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; +} diff --git a/thirdparty/liblastfm2/src/types/Tag.h b/thirdparty/liblastfm2/src/types/Tag.h new file mode 100644 index 000000000..bf4d5cfca --- /dev/null +++ b/thirdparty/liblastfm2/src/types/Tag.h @@ -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 . +*/ +#ifndef LASTFM_TAG_H +#define LASTFM_TAG_H + +#include +#include +#include +#include + +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 list( QNetworkReply* ); + }; +} + +#endif diff --git a/thirdparty/liblastfm2/src/types/Track.cpp b/thirdparty/liblastfm2/src/types/Track.cpp new file mode 100644 index 000000000..cececee1b --- /dev/null +++ b/thirdparty/liblastfm2/src/types/Track.cpp @@ -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 . +*/ +#include "Track.h" +#include "User.h" +#include "../core/UrlBuilder.h" +#include "../core/XmlQuery.h" +#include "../ws/ws.h" +#include +#include + + +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(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(sender())->readAll(); + if ( lfm.attribute( "status" ) == "ok") + loved = true; + emit loveToggled( loved ); +} + + +void +lastfm::TrackData::onUnloveFinished() +{ + XmlQuery lfm = static_cast(sender())->readAll(); + if ( lfm.attribute( "status" ) == "ok") + loved = false; + emit loveToggled( loved ); +} + +void +lastfm::TrackData::onGotInfo() +{ + lastfm::XmlQuery lfm( static_cast(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 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 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 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 +lastfm::Track::params( const QString& method, bool use_mbid ) const +{ + QMap 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 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 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 map = params( "removeTag" ); + map["tags"] = tag; + return ws::post(map); +} + + +QNetworkReply* +lastfm::Track::updateNowPlaying() const +{ + QMap 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 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& tracks) +{ + QMap 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() ); +} + diff --git a/thirdparty/liblastfm2/src/types/Track.h b/thirdparty/liblastfm2/src/types/Track.h new file mode 100644 index 000000000..eb759d0a2 --- /dev/null +++ b/thirdparty/liblastfm2/src/types/Track.h @@ -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 . +*/ +#ifndef LASTFM_TRACK_H +#define LASTFM_TRACK_H + +#include +#include +#include +#include +#include +#include +#include +#include +#include + + +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 m_images; + short scrobbleStatus; + short scrobbleError; + + //FIXME I hate this, but is used for radio trackauth etc. + QMap 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(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(d->scrobbleStatus); } + ScrobbleError scrobbleError() const { return static_cast(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& tracks); + + /** the url for this track's page at last.fm */ + QUrl www() const; + +protected: + QExplicitlySharedDataPointer d; + QMap 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 diff --git a/thirdparty/liblastfm2/src/types/User.cpp b/thirdparty/liblastfm2/src/types/User.cpp new file mode 100644 index 000000000..03e1a1eda --- /dev/null +++ b/thirdparty/liblastfm2/src/types/User.cpp @@ -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 . +*/ +#include "User.h" +#include "Track.h" +#include "../core/UrlBuilder.h" +#include "../core/XmlQuery.h" +#include +#include + +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 +User::params(const QString& method) const +{ + QMap map; + map["method"] = "user."+method; + map["user"] = m_name; + return map; +} + + +QNetworkReply* +User::getFriends( int perPage, int page ) const +{ + QMap 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 map; + map["method"] = "user.getInfo"; + map["user"] = username; + return ws::post( map ); +} + + + + +/* +QNetworkReply* //static +UserDetails::getRecommendedArtists() +{ + QMap 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& 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; +} + diff --git a/thirdparty/liblastfm2/src/types/User.h b/thirdparty/liblastfm2/src/types/User.h new file mode 100644 index 000000000..2d3eb0d84 --- /dev/null +++ b/thirdparty/liblastfm2/src/types/User.h @@ -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 . +*/ +#ifndef LASTFM_USER_H +#define LASTFM_USER_H + +#include +#include +#include + +#include +#include + +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 */ + 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 m_images; + + float m_match; + + QString m_realName; + + QMap 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& 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 + { + public: + int total; + int page; + int perPage; + int totalPages; + }; +} + +#endif diff --git a/thirdparty/liblastfm2/src/types/Xspf.cpp b/thirdparty/liblastfm2/src/types/Xspf.cpp new file mode 100644 index 000000000..2100ec265 --- /dev/null +++ b/thirdparty/liblastfm2/src/types/Xspf.cpp @@ -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 . +*/ +#include "Xspf.h" +#include "../core/XmlQuery.h" +#include + +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 + } +} diff --git a/thirdparty/liblastfm2/src/types/Xspf.h b/thirdparty/liblastfm2/src/types/Xspf.h new file mode 100644 index 000000000..e8bb55a15 --- /dev/null +++ b/thirdparty/liblastfm2/src/types/Xspf.h @@ -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 . +*/ +#ifndef LASTFM_XSPF_H +#define LASTFM_XSPF_H + +#include +#include + +namespace lastfm +{ + class LASTFM_DLLEXPORT Xspf + { + public: + /** pass in the playlist node! */ + Xspf( const class QDomElement& playlist_node ); + + QList tracks() const { return m_tracks; } + QString title() const{ return m_title; } + + private: + QList m_tracks; + QString m_title; + }; +} + +#endif diff --git a/thirdparty/liblastfm2/src/types/mbid_mp3.c b/thirdparty/liblastfm2/src/types/mbid_mp3.c new file mode 100644 index 000000000..ab494a32f --- /dev/null +++ b/thirdparty/liblastfm2/src/types/mbid_mp3.c @@ -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(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 + +// ----------------------------------------------------------------------------- diff --git a/thirdparty/liblastfm2/src/ws/InternetConnectionMonitor.cpp b/thirdparty/liblastfm2/src/ws/InternetConnectionMonitor.cpp new file mode 100644 index 000000000..44eb51b4d --- /dev/null +++ b/thirdparty/liblastfm2/src/ws/InternetConnectionMonitor.cpp @@ -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 . +*/ + +#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; +} diff --git a/thirdparty/liblastfm2/src/ws/InternetConnectionMonitor.h b/thirdparty/liblastfm2/src/ws/InternetConnectionMonitor.h new file mode 100644 index 000000000..3f770125c --- /dev/null +++ b/thirdparty/liblastfm2/src/ws/InternetConnectionMonitor.h @@ -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 . +*/ + +#ifndef LASTFM_CONNECTION_MONITOR_H +#define LASTFM_CONNECTION_MONITOR_H + +#include +#include + +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 diff --git a/thirdparty/liblastfm2/src/ws/NetworkAccessManager.cpp b/thirdparty/liblastfm2/src/ws/NetworkAccessManager.cpp new file mode 100644 index 000000000..2d628b1ad --- /dev/null +++ b/thirdparty/liblastfm2/src/ws/NetworkAccessManager.cpp @@ -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 . +*/ +#include "NetworkAccessManager.h" +#include "InternetConnectionMonitor.h" +#include +#include +#include +#include +#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 +} diff --git a/thirdparty/liblastfm2/src/ws/NetworkAccessManager.h b/thirdparty/liblastfm2/src/ws/NetworkAccessManager.h new file mode 100644 index 000000000..a3022ce25 --- /dev/null +++ b/thirdparty/liblastfm2/src/ws/NetworkAccessManager.h @@ -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 . +*/ +#ifndef LASTFM_WS_ACCESS_MANAGER_H +#define LASTFM_WS_ACCESS_MANAGER_H + +#include +#include +#include +#include + + +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 diff --git a/thirdparty/liblastfm2/src/ws/NetworkConnectionMonitor.cpp b/thirdparty/liblastfm2/src/ws/NetworkConnectionMonitor.cpp new file mode 100644 index 000000000..b4a62732b --- /dev/null +++ b/thirdparty/liblastfm2/src/ws/NetworkConnectionMonitor.cpp @@ -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 . +*/ + +#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(); + } +} + diff --git a/thirdparty/liblastfm2/src/ws/NetworkConnectionMonitor.h b/thirdparty/liblastfm2/src/ws/NetworkConnectionMonitor.h new file mode 100644 index 000000000..a21cf73e3 --- /dev/null +++ b/thirdparty/liblastfm2/src/ws/NetworkConnectionMonitor.h @@ -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 . +*/ + +#ifndef NETWORK_CONNECTION_MONITOR_H +#define NETWORK_CONNECTION_MONITOR_H + +#include +#include + +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 diff --git a/thirdparty/liblastfm2/src/ws/linux/LNetworkConnectionMonitor.h b/thirdparty/liblastfm2/src/ws/linux/LNetworkConnectionMonitor.h new file mode 100644 index 000000000..9f7895bed --- /dev/null +++ b/thirdparty/liblastfm2/src/ws/linux/LNetworkConnectionMonitor.h @@ -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 . +*/ + + +#ifndef LNETWORK_CONNECTION_MONITOR_H +#define LNETWORK_CONNECTION_MONITOR_H + +#include "../NetworkConnectionMonitor.h" +#include +#include + +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 + diff --git a/thirdparty/liblastfm2/src/ws/linux/LNetworkConnectionMonitor_linux.cpp b/thirdparty/liblastfm2/src/ws/linux/LNetworkConnectionMonitor_linux.cpp new file mode 100644 index 000000000..d8dea9dd6 --- /dev/null +++ b/thirdparty/liblastfm2/src/ws/linux/LNetworkConnectionMonitor_linux.cpp @@ -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 . +*/ + + +#include "LNetworkConnectionMonitor.h" + +#include +#include +#include + +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 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 ); + } +} diff --git a/thirdparty/liblastfm2/src/ws/mac/MNetworkConnectionMonitor.h b/thirdparty/liblastfm2/src/ws/mac/MNetworkConnectionMonitor.h new file mode 100644 index 000000000..96aca0767 --- /dev/null +++ b/thirdparty/liblastfm2/src/ws/mac/MNetworkConnectionMonitor.h @@ -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 . +*/ + +#ifndef MNETWORK_CONNECTION_MONITOR_H +#define MNETWORK_CONNECTION_MONITOR_H + +#include "../NetworkConnectionMonitor.h" +#include +#include + +#ifdef Q_WS_MAC +#include //TODO remove +#include +#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 + diff --git a/thirdparty/liblastfm2/src/ws/mac/MNetworkConnectionMonitor_mac.cpp b/thirdparty/liblastfm2/src/ws/mac/MNetworkConnectionMonitor_mac.cpp new file mode 100644 index 000000000..f9a03f47a --- /dev/null +++ b/thirdparty/liblastfm2/src/ws/mac/MNetworkConnectionMonitor_mac.cpp @@ -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 . +*/ + +#include "MNetworkConnectionMonitor.h" +#include "ws/ws.h" + +#include +#include + +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); +} + diff --git a/thirdparty/liblastfm2/src/ws/mac/ProxyDict.h b/thirdparty/liblastfm2/src/ws/mac/ProxyDict.h new file mode 100644 index 000000000..2d873f88a --- /dev/null +++ b/thirdparty/liblastfm2/src/ws/mac/ProxyDict.h @@ -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 . +*/ +#include + + +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 ); +} diff --git a/thirdparty/liblastfm2/src/ws/win/ComSetup.h b/thirdparty/liblastfm2/src/ws/win/ComSetup.h new file mode 100644 index 000000000..fc3816bd7 --- /dev/null +++ b/thirdparty/liblastfm2/src/ws/win/ComSetup.h @@ -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 . +*/ +#ifndef _WIN32_WINNT +// bring in CoInitializeSecurity from objbase.h +#define _WIN32_WINNT 0x0400 +#endif + +#include +#include +#include + + +/** @brief WsConnectionMonitor needs Com to work as early as possible so we do this + * @author + */ +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; +}; diff --git a/thirdparty/liblastfm2/src/ws/win/IeSettings.h b/thirdparty/liblastfm2/src/ws/win/IeSettings.h new file mode 100644 index 000000000..5d756a5ea --- /dev/null +++ b/thirdparty/liblastfm2/src/ws/win/IeSettings.h @@ -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 . +*/ +#include +#include + + +/** @brief memory managing wrapper for WINHTTP_CURRENT_USER_IE_PROXY_CONFIG + * @author + */ +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); + } +}; diff --git a/thirdparty/liblastfm2/src/ws/win/NdisEvents.cpp b/thirdparty/liblastfm2/src/ws/win/NdisEvents.cpp new file mode 100644 index 000000000..7263af24b --- /dev/null +++ b/thirdparty/liblastfm2/src/ws/win/NdisEvents.cpp @@ -0,0 +1,87 @@ +/* + 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 . +*/ +#include "NdisEvents.h" +#include "WmiSink.h" + +// see http://msdn.microsoft.com/en-us/magazine/cc301850.aspx for +// more about Ndis and wmi and getting these events + +// Link to wbemuuid.lib to resolve IWbemObjectSink and IWbemClassObject +// interface definitions. + +NdisEvents::NdisEvents() + : m_pSink(0) +{} + +NdisEvents::~NdisEvents() +{ + if (m_pSink) + m_pSink->disconnect(); + if (m_pServices && m_pSink) + m_pServices->CancelAsyncCall(m_pSink); + // and reference counting will take care of the WmiSink object +} + +HRESULT +NdisEvents::registerForNdisEvents() +{ + HRESULT hr = m_pLocator.CoCreateInstance(CLSID_WbemLocator); + if (FAILED(hr)) + return hr; + + // Connect to the root\wmi namespace with the current user. + hr = m_pLocator->ConnectServer(CComBSTR("ROOT\\WMI"), // strNetworkResource + NULL, // strUser + NULL, // strPassword + NULL, // strLocale + 0, // lSecurityFlags + CComBSTR(""), // strAuthority + NULL, // pCtx + &m_pServices + ); + if (FAILED(hr)) + return hr; + + m_pSink = new WmiSink(this); + + ////////////////////////// + + // other notifications we're not interested in right now include... + // MSNdis_NotifyAdapterArrival \DEVICE\ + // MSNdis_NotifyAdapterRemoval + // MSNdis_StatusLinkSpeedChange + // MSNdis_NotifyVcArrival + // MSNdis_NotifyVcRemoval + // MSNdis_StatusResetStart + // MSNdis_StatusResetEnd + // MSNdis_StatusProtocolBind + // MSNdis_StatusProtocolUnbind + // MSNdis_StatusMediaSpecificIndication + + CComBSTR wql("WQL"); + CComBSTR query("SELECT * FROM MSNdis_StatusMediaDisconnect"); + hr = m_pServices->ExecNotificationQueryAsync(wql, query, 0, 0, m_pSink); + + query = "SELECT * FROM MSNdis_StatusMediaConnect"; + hr = m_pServices->ExecNotificationQueryAsync(wql, query, 0, 0, m_pSink); + + return S_OK; +} + diff --git a/thirdparty/liblastfm2/src/ws/win/NdisEvents.h b/thirdparty/liblastfm2/src/ws/win/NdisEvents.h new file mode 100644 index 000000000..1e1cfcf9c --- /dev/null +++ b/thirdparty/liblastfm2/src/ws/win/NdisEvents.h @@ -0,0 +1,44 @@ +/* + 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 . +*/ +#ifndef NDIS_EVENTS_H +#define NDIS_EVENTS_H + +#include +#include +#include + +class NdisEvents +{ +public: + NdisEvents(); + ~NdisEvents(); + HRESULT registerForNdisEvents(); + + virtual void onConnectionUp(BSTR name) = 0; + virtual void onConnectionDown(BSTR name) = 0; + +private: + CComPtr m_pLocator; + CComPtr m_pServices; + class WmiSink *m_pSink; +}; + +#endif + diff --git a/thirdparty/liblastfm2/src/ws/win/Pac.cpp b/thirdparty/liblastfm2/src/ws/win/Pac.cpp new file mode 100644 index 000000000..3e3d72b12 --- /dev/null +++ b/thirdparty/liblastfm2/src/ws/win/Pac.cpp @@ -0,0 +1,128 @@ +/* + 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 . +*/ +#include "Pac.h" +#include +#include +#include +#include +#include + + +static bool +parsePacServer(const QString &s, QNetworkProxy &p) +{ + // remove optional leading "scheme=" portion + int start = s.indexOf('='); + QUrl url(s.mid(start+1), QUrl::TolerantMode); + + if (url.isValid()) + { + p.setHostName(url.host()); + p.setPort(url.port()); + return true; + } + return false; +} + + +static QList +parsePacResult(const QString &pacResult) +{ + // msdn says: "The proxy server list contains one or more of the + // following strings separated by semicolons or whitespace." + // ([=]["://"][":"]) + + QList result; + QStringList proxies = pacResult.split(QRegExp("[\\s;]"), QString::SkipEmptyParts); + foreach(const QString &s, proxies) + { + QNetworkProxy proxy( QNetworkProxy::HttpProxy ); + if (parsePacServer(s, proxy)) + { + result << proxy; + } + } + return result; +} + + +//////////////// + + +lastfm::Pac::Pac() + : m_bFailed( false ) + , m_hSession( 0 ) +{} + +lastfm::Pac::~Pac() +{ + if (m_hSession) + WinHttpCloseHandle(m_hSession); +} + +QNetworkProxy +lastfm::Pac::resolve(const QNetworkRequest &request, const wchar_t* pacUrl) +{ + QNetworkProxy out; + if (m_bFailed) return out; + + if (!m_hSession) + { + QByteArray user_agent = request.rawHeader("user-agent"); + m_hSession = WinHttpOpen(CA2W(user_agent), WINHTTP_ACCESS_TYPE_NO_PROXY, 0, 0, WINHTTP_FLAG_ASYNC); + } + if (m_hSession) + { + WINHTTP_PROXY_INFO info; + WINHTTP_AUTOPROXY_OPTIONS opts; + memset(&opts, 0, sizeof(opts)); + if (pacUrl) + { + opts.dwFlags = WINHTTP_AUTOPROXY_CONFIG_URL; + opts.lpszAutoConfigUrl = pacUrl; + } + else + { + opts.dwFlags = WINHTTP_AUTOPROXY_AUTO_DETECT; + opts.dwAutoDetectFlags = WINHTTP_AUTO_DETECT_TYPE_DHCP | WINHTTP_AUTO_DETECT_TYPE_DNS_A; + } + opts.fAutoLogonIfChallenged = TRUE; + + if (WinHttpGetProxyForUrl(m_hSession, request.url().toString().utf16(), &opts, &info)) { + if (info.lpszProxy) + { + QList proxies = parsePacResult(QString::fromUtf16(info.lpszProxy)); + if (!proxies.empty()) + { + out = proxies.at(0); + } + GlobalFree(info.lpszProxy); + } + if (info.lpszProxyBypass) + { + GlobalFree(info.lpszProxyBypass); + } + } else { + m_bFailed = true; + } + } + + return out; +} diff --git a/thirdparty/liblastfm2/src/ws/win/Pac.h b/thirdparty/liblastfm2/src/ws/win/Pac.h new file mode 100644 index 000000000..075d128f6 --- /dev/null +++ b/thirdparty/liblastfm2/src/ws/win/Pac.h @@ -0,0 +1,52 @@ +/* + 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 . +*/ +#ifndef WS_AUTOPROXY_H +#define WS_AUTOPROXY_H + +#include +#include +#include +class QNetworkRequest; + +namespace lastfm +{ + /** @brief simple wrapper to do per url automatic proxy detection + * @author + */ + class Pac + { + HINTERNET m_hSession; + bool m_bFailed; + + public: + Pac(); + ~Pac(); + + QNetworkProxy resolve( const QNetworkRequest& url, const wchar_t* pacUrl ); + + void resetFailedState() { m_bFailed = false; } + + private: + Pac( const Pac& ); //undefined + Pac operator=( const Pac& ); //undefined + }; +} + +#endif \ No newline at end of file diff --git a/thirdparty/liblastfm2/src/ws/win/WNetworkConnectionMonitor.h b/thirdparty/liblastfm2/src/ws/win/WNetworkConnectionMonitor.h new file mode 100755 index 000000000..26fb58cdd --- /dev/null +++ b/thirdparty/liblastfm2/src/ws/win/WNetworkConnectionMonitor.h @@ -0,0 +1,44 @@ +/* + 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 . +*/ + +#ifndef WNETWORK_CONNECTION_MONITOR_H +#define WNETWORK_CONNECTION_MONITOR_H + +#include "../NetworkConnectionMonitor.h" +#include +#include + +namespace lastfm { class NdisEventsProxy; } + +class WNetworkConnectionMonitor : public NetworkConnectionMonitor +{ + Q_OBJECT +public: + friend class lastfm::NdisEventsProxy; + + WNetworkConnectionMonitor( QObject* parent = 0 ); + ~WNetworkConnectionMonitor(); + +private: + lastfm::NdisEventsProxy* m_ndisEventsProxy; +}; + +#endif // WNETWORK_CONNECTION_MONITOR_H + diff --git a/thirdparty/liblastfm2/src/ws/win/WNetworkConnectionMonitor_win.cpp b/thirdparty/liblastfm2/src/ws/win/WNetworkConnectionMonitor_win.cpp new file mode 100755 index 000000000..09fd97cc5 --- /dev/null +++ b/thirdparty/liblastfm2/src/ws/win/WNetworkConnectionMonitor_win.cpp @@ -0,0 +1,67 @@ +/* + 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 . +*/ + +#include "WNetworkConnectionMonitor.h" + +// WsAccessManager needs special init (on Windows), and it needs to be done +// early, so be careful about moving this +#include "../win/ComSetup.h" //must be first header or compile fail results! +#include "../win/NdisEvents.h" +static ComSetup com_setup; + +namespace lastfm { + +// bounce NdisEvents signals through here so we don't have to expose the +// NdisEvents interface in InternetConnectionMonitor :) +class NdisEventsProxy : public NdisEvents +{ +public: + NdisEventsProxy(WNetworkConnectionMonitor* icm) + :m_icm(icm) + { + } + + // WmiSink callbacks: + void onConnectionUp( BSTR /*name*/ ) + { + m_icm->setConnected( true ); + } + + void onConnectionDown( BSTR /*name*/ ) + { + m_icm->setConnected( false ); + } + + WNetworkConnectionMonitor* m_icm; +}; + +} + +WNetworkConnectionMonitor::WNetworkConnectionMonitor( QObject* parent ) : + NetworkConnectionMonitor( parent ) +{ + m_ndisEventsProxy = new lastfm::NdisEventsProxy( this ); + m_ndisEventsProxy->registerForNdisEvents(); +} + +WNetworkConnectionMonitor::~WNetworkConnectionMonitor() +{ + delete m_ndisEventsProxy; +} diff --git a/thirdparty/liblastfm2/src/ws/win/WmiSink.cpp b/thirdparty/liblastfm2/src/ws/win/WmiSink.cpp new file mode 100644 index 000000000..3d564266c --- /dev/null +++ b/thirdparty/liblastfm2/src/ws/win/WmiSink.cpp @@ -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 . +*/ +#include "WmiSink.h" +#include "NdisEvents.h" + + +WmiSink::WmiSink(NdisEvents *callback) + : m_cRef(1) + , m_callback(callback) +{} + +WmiSink::~WmiSink() +{} + +void +WmiSink::disconnect() +{ + m_callback = 0; +} + +STDMETHODIMP +WmiSink::QueryInterface(REFIID riid, LPVOID * ppv) +{ + *ppv = 0; + + if (IID_IUnknown==riid || IID_IWbemObjectSink == riid) + { + *ppv = (IWbemObjectSink *) this; + AddRef(); + return NOERROR; + } + + return E_NOINTERFACE; +} + + +ULONG +WmiSink::AddRef() +{ + return ++m_cRef; +} + +ULONG +WmiSink::Release() +{ + if (0 != --m_cRef) + return m_cRef; + + delete this; + return 0; +} + +// This method receives notification objects. +HRESULT +WmiSink::Indicate(long lObjectCount, IWbemClassObject** ppObjArray) +{ + // For each object in the array, extract the object and display the + // information in the object. + for (long i=0; iGet(L"InstanceName", 0, &vt, NULL, NULL); + ppObjArray[i]->Get(L"__Class", 0, &vtClass, NULL, NULL); + + if (!wcscmp (vtClass.bstrVal, L"MSNdis_StatusMediaDisconnect")) + { + if (m_callback) m_callback->onConnectionDown(vt.bstrVal); + } + else if (!wcscmp (vtClass.bstrVal, L"MSNdis_StatusMediaConnect")) + { + if (m_callback) m_callback->onConnectionUp(vt.bstrVal); + } + // notifications we aren't interested in right now: + // + //else if (!wcscmp (vtClass.bstrVal, L"MSNdis_NotifyAdapterRemoval")) + //{ + // bstrLog = (_bstr_t) vt.bstrVal; + // VariantClear (&vt); + // ppObjArray[i]->Get (L"DeviceName", 0, &vt, NULL, NULL); + // bstrLog += (_bstr_t) _T(": ") + (_bstr_t) vt.bstrVal + (_bstr_t) _T(" has been removed"); + // displayDlg.LogEvent (bstrLog); + //} + //else if (!wcscmp (vtClass.bstrVal, L"MSNdis_NotifyAdapterArrival")) + //{ + // bstrLog = (_bstr_t) vt.bstrVal; + // VariantClear (&vt); + // ppObjArray[i]->Get(L"DeviceName", 0, &vt, NULL, NULL); + // bstrLog += (_bstr_t) _T(": ") + (_bstr_t) vt.bstrVal + (_bstr_t) _T(" has been added"); + // displayDlg.LogEvent (bstrLog); + //} + //else if (!wcscmp (vtClass.bstrVal, L"MSNdis_StatusResetStart")) + //{ + // bstrLog = (_bstr_t) vt.bstrVal + (_bstr_t) _T(" has begun a reset"); + // displayDlg.LogEvent (bstrLog); + //} + //else if (!wcscmp (vtClass.bstrVal, L"MSNdis_StatusResetEnd")) + //{ + // bstrLog = (_bstr_t) vt.bstrVal + (_bstr_t) _T(" has finished a reset"); + // displayDlg.LogEvent (bstrLog); + //} + //else if (!wcscmp (vtClass.bstrVal, L"MSNdis_NotifyVcArrival")) + //{ + // bstrLog = (_bstr_t) _T("VC arrival: ") + (_bstr_t) vt.bstrVal; + // displayDlg.LogEvent (bstrLog); + //} + //else if (!wcscmp (vtClass.bstrVal, L"MSNdis_NotifyVcRemoval")) + //{ + // bstrLog = (_bstr_t) _T("VC removal: ") + (_bstr_t) vt.bstrVal; + // displayDlg.LogEvent (bstrLog); + //} + //else if (!wcscmp (vtClass.bstrVal, L"MSNdis_StatusMediaSpecificIndication")) + //{ + // ATLTRACE (_T("Media specific indication: %s\n"), (TCHAR *) (_bstr_t) vt.bstrVal); + // VariantClear (&vt); + // ppObjArray[i]->Get (L"NdisStatusMediaSpecificIndication", 0, &vt, NULL, NULL); + // LONG lLowerBound, lUpperBound, j; + // UCHAR ch; + // SafeArrayGetLBound (V_ARRAY (&vt), 1, &lLowerBound); + // SafeArrayGetUBound (V_ARRAY (&vt), 1, &lUpperBound); + // ATLTRACE (" "); + // for (j = lLowerBound; j<= lUpperBound; j++ ) + // { + // SafeArrayGetElement (V_ARRAY (&vt), &j, &ch); + // ATLTRACE (_T("%4i"), ch); + + // if (((j - lLowerBound) % 8 == 7) && (j <= lUpperBound)) + // ATLTRACE (_T("\n")); + // } + //} + //else if (!wcscmp (vtClass.bstrVal, L"MSNdis_StatusProtocolBind")) + //{ + // bstrLog = (_bstr_t) vt.bstrVal; + // VariantClear (&vt); + // ppObjArray[i]->Get (L"Transport", 0, &vt, NULL, NULL); + // bstrLog += (_bstr_t) _T(" is now bound to ") + (_bstr_t) vt.bstrVal; + // displayDlg.LogEvent (bstrLog); + //} + //else if (!wcscmp (vtClass.bstrVal, L"MSNdis_StatusProtocolBind")) + //{ + // bstrLog = (_bstr_t) vt.bstrVal; + // VariantClear (&vt); + // ppObjArray[i]->Get(L"Transport", 0, &vt, NULL, NULL); + // bstrLog += (_bstr_t) _T(" was unbound from ") + (_bstr_t) vt.bstrVal; + // displayDlg.LogEvent (bstrLog); + //} + //else if (!wcscmp (vtClass.bstrVal, L"MSNdis_StatusLinkSpeedChange")) + //{ + // IWbemClassObject* pWMIObj=NULL; + // bstrLog = (_bstr_t) _T("Link speed change ") + (_bstr_t) vt.bstrVal; + // VariantClear (&vt); + // ppObjArray[i]->Get (L"NdisStatusLinkSpeedChange", 0, &vt, NULL, NULL); + // if SUCCEEDED (vt.punkVal->QueryInterface (IID_IWbemClassObject, (void**)&pWMIObj)) + // { + // TCHAR szNum[50]; + // pWMIObj->Get (L"Inbound", 0, &vt2, NULL, NULL); + // _stprintf (szNum, _T(" Inbound = %u "), vt2.lVal); + // bstrLog += (_bstr_t) szNum; + // VariantClear (&vt2); + // pWMIObj->Get (L"Outbound", 0, &vt2, NULL, NULL); + // _stprintf (szNum, _T(" Outbound = %u "), vt2.lVal); + // bstrLog += (_bstr_t) szNum; + // VariantClear (&vt2); + // pWMIObj->Release (); + // pWMIObj = NULL; + // } + // displayDlg.LogEvent (bstrLog); + //} + + VariantClear (&vtClass); + VariantClear (&vt); + } + return WBEM_NO_ERROR; +} + + +// Misc. status codes sent by sink. +HRESULT +WmiSink::SetStatus(long lFlags, HRESULT hResult, BSTR strParam, IWbemClassObject __RPC_FAR *pObjParam) +{ + lFlags; + hResult; + strParam; + pObjParam; + return WBEM_NO_ERROR; +} diff --git a/thirdparty/liblastfm2/src/ws/win/WmiSink.h b/thirdparty/liblastfm2/src/ws/win/WmiSink.h new file mode 100644 index 000000000..1bce28fdd --- /dev/null +++ b/thirdparty/liblastfm2/src/ws/win/WmiSink.h @@ -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 . +*/ +#ifndef WMISINK_WIN_H +#define WMISINK_WIN_H + +#include "WbemCli.h" + +// Sink object for WMI NDIS notifications +class WmiSink : public IWbemObjectSink +{ + UINT m_cRef; + +public: + WmiSink(class NdisEvents *callback); + ~WmiSink(); + + // IUnknown members + STDMETHODIMP QueryInterface(REFIID, LPVOID *); + STDMETHODIMP_(ULONG) AddRef(void); + STDMETHODIMP_(ULONG) Release(void); + + // IWbemObjectSink + STDMETHODIMP Indicate(long, IWbemClassObject**); + STDMETHODIMP SetStatus(long, HRESULT, BSTR, IWbemClassObject *); + + void disconnect(); + +private: + class NdisEvents *m_callback; +}; + +#endif \ No newline at end of file diff --git a/thirdparty/liblastfm2/src/ws/ws.cpp b/thirdparty/liblastfm2/src/ws/ws.cpp new file mode 100644 index 000000000..89c6ece6b --- /dev/null +++ b/thirdparty/liblastfm2/src/ws/ws.cpp @@ -0,0 +1,268 @@ +/* + 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 . +*/ +#include "ws.h" +#include "../core/misc.h" +#include "NetworkAccessManager.h" +#include +#include +#include +#include +#include +#include +static QNetworkAccessManager* nam = 0; + + +QString +lastfm::ws::host() +{ + QStringList const args = QCoreApplication::arguments(); + if (args.contains( "--debug")) + return "ws.staging.audioscrobbler.com"; + + int const n = args.indexOf( "--host" ); + if (n != -1 && args.count() > n+1) + return args[n+1]; + + return LASTFM_WS_HOSTNAME; +} + +static QUrl url() +{ + QUrl url; + url.setScheme( "http" ); + url.setHost( lastfm::ws::host() ); + url.setPath( "/2.0/" ); + return url; +} + +static QString iso639() +{ + return QLocale().name().left( 2 ).toLower(); +} + +void autograph( QMap& params ) +{ + params["api_key"] = lastfm::ws::ApiKey; + params["lang"] = iso639(); +} + +static void sign( QMap& params, bool sk = true ) +{ + autograph( params ); + // it's allowed for sk to be null if we this is an auth call for instance + if (sk && lastfm::ws::SessionKey.size()) + params["sk"] = lastfm::ws::SessionKey; + + QString s; + QMapIterator i( params ); + while (i.hasNext()) { + i.next(); + s += i.key() + i.value(); + } + s += lastfm::ws::SharedSecret; + + params["api_sig"] = lastfm::md5( s.toUtf8() ); +} + + +QNetworkReply* +lastfm::ws::get( QMap params ) +{ + sign( params ); + QUrl url = ::url(); + // Qt setQueryItems doesn't encode a bunch of stuff, so we do it manually + QMapIterator i( params ); + while (i.hasNext()) { + i.next(); + QByteArray const key = QUrl::toPercentEncoding( i.key() ); + QByteArray const value = QUrl::toPercentEncoding( i.value() ); + url.addEncodedQueryItem( key, value ); + } + + qDebug() << url; + + return nam()->get( QNetworkRequest(url) ); +} + + +QNetworkReply* +lastfm::ws::post( QMap params, bool sk ) +{ + sign( params, sk ); + QByteArray query; + QMapIterator i( params ); + while (i.hasNext()) { + i.next(); + query += QUrl::toPercentEncoding( i.key() ) + + '=' + + QUrl::toPercentEncoding( i.value() ) + + '&'; + } + + return nam()->post( QNetworkRequest(url()), query ); +} + + +QByteArray +lastfm::ws::parse( QNetworkReply* reply ) throw( ParseError ) +{ + try + { + QByteArray data = reply->readAll(); + + if (!data.size()) + throw MalformedResponse; + + QDomDocument xml; + xml.setContent( data ); + QDomElement lfm = xml.documentElement(); + + if (lfm.isNull()) + throw MalformedResponse; + + QString const status = lfm.attribute( "status" ); + QDomElement error = lfm.firstChildElement( "error" ); + uint const n = lfm.childNodes().count(); + + // no elements beyond the lfm is perfectably acceptable <-- wtf? + // if (n == 0) // nothing useful in the response + if (status == "failed" || (n == 1 && !error.isNull()) ) + throw error.isNull() + ? MalformedResponse + : Error( error.attribute( "code" ).toUInt() ); + + switch (reply->error()) + { + case QNetworkReply::RemoteHostClosedError: + case QNetworkReply::ConnectionRefusedError: + case QNetworkReply::TimeoutError: + case QNetworkReply::ContentAccessDenied: + case QNetworkReply::ContentOperationNotPermittedError: + case QNetworkReply::UnknownContentError: + case QNetworkReply::ProtocolInvalidOperationError: + case QNetworkReply::ProtocolFailure: + throw TryAgainLater; + + case QNetworkReply::NoError: + default: + break; + } + + //FIXME pretty wasteful to parse XML document twice.. + return data; + } + catch (Error e) + { + switch (e) + { + case OperationFailed: + case InvalidApiKey: + case InvalidSessionKey: + // NOTE will never be received during the LoginDialog stage + // since that happens before this slot is registered with + // QMetaObject in App::App(). Neat :) + QMetaObject::invokeMethod( qApp, "onWsError", Q_ARG( lastfm::ws::Error, e ) ); + default: + throw ParseError(e); + } + } + + // bit dodgy, but prolly for the best + reply->deleteLater(); +} + + +QNetworkAccessManager* +lastfm::nam() +{ + if (!::nam) ::nam = new NetworkAccessManager( qApp ); + return ::nam; +} + + +void +lastfm::setNetworkAccessManager( QNetworkAccessManager* nam ) +{ + delete ::nam; + ::nam = nam; + nam->setParent( qApp ); // ensure it isn't deleted out from under us +} + + +/** This useful function, fromHttpDate, comes from QNetworkHeadersPrivate + * in qnetworkrequest.cpp. Qt copyright and license apply. */ +static QDateTime QByteArrayToHttpDate(const QByteArray &value) +{ + // HTTP dates have three possible formats: + // RFC 1123/822 - ddd, dd MMM yyyy hh:mm:ss "GMT" + // RFC 850 - dddd, dd-MMM-yy hh:mm:ss "GMT" + // ANSI C's asctime - ddd MMM d hh:mm:ss yyyy + // We only handle them exactly. If they deviate, we bail out. + + int pos = value.indexOf(','); + QDateTime dt; + if (pos == -1) { + // no comma -> asctime(3) format + dt = QDateTime::fromString(QString::fromLatin1(value), Qt::TextDate); + } else { + // eat the weekday, the comma and the space following it + QString sansWeekday = QString::fromLatin1(value.constData() + pos + 2); + + QLocale c = QLocale::c(); + if (pos == 3) + // must be RFC 1123 date + dt = c.toDateTime(sansWeekday, QLatin1String("dd MMM yyyy hh:mm:ss 'GMT")); + else + // must be RFC 850 date + dt = c.toDateTime(sansWeekday, QLatin1String("dd-MMM-yy hh:mm:ss 'GMT'")); + } + + if (dt.isValid()) + dt.setTimeSpec(Qt::UTC); + return dt; +} + + +QDateTime +lastfm::ws::expires( QNetworkReply* reply ) +{ + return QByteArrayToHttpDate( reply->rawHeader( "Expires" ) ); +} + + +namespace lastfm +{ + namespace ws + { + QString SessionKey; + QString Username; + + /** we leave these unset as you can't use the webservices without them + * so lets make the programmer aware of it during testing by crashing */ + const char* SharedSecret; + const char* ApiKey; + + /** if this is found set to "" we conjure ourselves a suitable one */ + const char* UserAgent = 0; + } +} + + +QDebug operator<<( QDebug, lastfm::ws::Error ); diff --git a/thirdparty/liblastfm2/src/ws/ws.h b/thirdparty/liblastfm2/src/ws/ws.h new file mode 100644 index 000000000..b6f7cd922 --- /dev/null +++ b/thirdparty/liblastfm2/src/ws/ws.h @@ -0,0 +1,161 @@ +/* + 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 . +*/ +#ifndef LASTFM_WS_H +#define LASTFM_WS_H + +#include +#include +#include +#include +#include + +#ifdef Q_CC_MSVC +// ms admits its lousy compiler doesn't care about throw declarations +#pragma warning( disable : 4290 ) +#endif + + +namespace lastfm +{ + /** if you don't set one, we create our own, our own is pretty good + * for instance, it auto detects proxy settings on windows and mac + * We take ownership of the NAM, do not delete it out from underneath us! + * So don't keep any other pointers to this around in case you accidently + * call delete on them :P */ + LASTFM_DLLEXPORT void setNetworkAccessManager( QNetworkAccessManager* nam ); + LASTFM_DLLEXPORT QNetworkAccessManager* nam(); + + namespace ws + { + /** both of these are provided when you register at http://last.fm/api */ + LASTFM_DLLEXPORT extern const char* SharedSecret; + LASTFM_DLLEXPORT extern const char* ApiKey; + + /** you need to set this for scrobbling to work (for now) + * Also the AuthenticatedUser class uses it */ + LASTFM_DLLEXPORT extern QString Username; + + /** Some webservices require authentication. See the following + * documentation: + * http://www.last.fm/api/authentication + * http://www.last.fm/api/desktopauth + * You have to authenticate and then assign to SessionKey, liblastfm does + * not do that for you. Also we do not store this. You should store this! + * You only need to authenticate once, and that key lasts forever! + */ + LASTFM_DLLEXPORT extern QString SessionKey; + + enum Error + { + NoError = 1, // because last.fm error numbers start at 2 + + /** numbers follow those at http://last.fm/api/ */ + InvalidService = 2, + InvalidMethod, + AuthenticationFailed, + InvalidFormat, + InvalidParameters, + InvalidResourceSpecified, + OperationFailed, + InvalidSessionKey, + InvalidApiKey, + ServiceOffline, + SubscribersOnly, + + Reserved13, + Reserved14, + Reserved15, + + /** Last.fm sucks. + * There may be an error in networkError(), or this may just be some + * internal error completing your request. + * Advise the user to try again in a _few_minutes_. + * For some cases, you may want to try again yourself, at this point + * in the API you will have to. Eventually we will discourage this and + * do it for you, as we don't want to strain Last.fm's servers + */ + TryAgainLater = 16, + + Reserved17, + Reserved18, + Reserved19, + + NotEnoughContent = 20, + NotEnoughMembers, + NotEnoughFans, + NotEnoughNeighbours, + + /** Last.fm fucked up, or something mangled the response on its way */ + MalformedResponse = 100, + + /** call QNetworkReply::error() as it's nothing to do with us */ + UnknownError + }; + + LASTFM_DLLEXPORT QString host(); + + /** the map needs a method entry, as per http://last.fm/api */ + LASTFM_DLLEXPORT QNetworkReply* get( QMap ); + /** generates api sig, includes api key, and posts, don't add the api + * key yourself as well--it'll break */ + LASTFM_DLLEXPORT QNetworkReply* post( QMap, bool sessionKey = true ); + + + class ParseError : public std::runtime_error + { + Error e; + public: + explicit ParseError(Error e) : std::runtime_error("lastfm::ws::Error"), e(e) + {} + Error enumValue() const { return e; } + }; + + /** Generally you don't use this, eg. if you called Artist::getInfo(), + * use the Artist::getInfo( QNetworkReply* ) function to get the + * results, you have to pass a QDomDocument because QDomElements stop + * existing when the parent DomDocument is deleted. + * + * The QByteArray is basically reply->readAll(), so all this function + * does is sanity check the response and throw if it is bad. + * + * Thus if you don't care about errors just do: reply->readAll() + * + * Not caring about errors is often fine with Qt as you just get null + * strings and that instead, and you can handle those as you go. + * + * The QByteArray is an XML document. You can parse it with QDom or + * use our much more convenient lastfm::XmlQuery. + */ + LASTFM_DLLEXPORT QByteArray parse( QNetworkReply* reply ) throw( ParseError ); + + /** returns the expiry date of this HTTP response */ + LASTFM_DLLEXPORT QDateTime expires( QNetworkReply* ); + } +} + + +inline QDebug operator<<( QDebug d, QNetworkReply::NetworkError e ) +{ + return d << lastfm::qMetaEnumString( e, "NetworkError" ); +} + +#define LASTFM_WS_HOSTNAME "ws.audioscrobbler.com" + +#endif diff --git a/thirdparty/liblastfm2/tests/TestTrack.h b/thirdparty/liblastfm2/tests/TestTrack.h new file mode 100644 index 000000000..c771a45e9 --- /dev/null +++ b/thirdparty/liblastfm2/tests/TestTrack.h @@ -0,0 +1,35 @@ +/* + 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 +#include +using lastfm::Track; + +class TestTrack : public QObject +{ + Q_OBJECT + + Track example() + { + lastfm::MutableTrack t; + t.setTitle( "Test Title" ); + t.setArtist( "Test Artist" ); + t.setAlbum( "Test Album" ); + return t; + } + +private slots: + void testClone() + { + Track original = example(); + Track copy = original; + + #define TEST( x ) QVERIFY( original.x == copy.x ) + TEST( title() ); + TEST( artist() ); + TEST( album() ); + #undef TEST + } +}; diff --git a/thirdparty/liblastfm2/tests/TestUrlBuilder.h b/thirdparty/liblastfm2/tests/TestUrlBuilder.h new file mode 100644 index 000000000..909d10e37 --- /dev/null +++ b/thirdparty/liblastfm2/tests/TestUrlBuilder.h @@ -0,0 +1,80 @@ +/* + 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 +#include +#include +#include +#include + +static inline int getResponseCode( const QUrl& url ) +{ + QNetworkAccessManager nam; + QNetworkReply* reply = nam.head( QNetworkRequest(url) ); + + QEventLoop loop; + loop.connect( reply, SIGNAL(finished()), SLOT(quit()) ); + loop.exec(); + + int const code = reply->attribute( QNetworkRequest::HttpStatusCodeAttribute ).toInt(); + + if (reply->error() != QNetworkReply::NoError) + qDebug() << url << lastfm::qMetaEnumString( reply->error(), "NetworkError" ) << code; + + return code; +} + + +class TestUrlBuilder : public QObject +{ + Q_OBJECT + +private slots: + void encode() /** @author */ + { + QFETCH( QString, input ); + QFETCH( QString, output ); + QCOMPARE( lastfm::UrlBuilder::encode( input ), output.toAscii() ); + } + + void encode_data() /** @author */ + { + QTest::addColumn("input"); + QTest::addColumn("output"); + + QTest::newRow( "ascii" ) << "Metallica" << "Metallica"; + QTest::newRow( "ascii alphanumeric" ) << "Apollo 440" << "Apollo+440"; + QTest::newRow( "ascii with symbols" ) << "some track [original version]" << "some+track+%5Boriginal+version%5D"; + QTest::newRow( "ascii with last.fm-special symbols" ) << "Survivalism [Revision #1]" << "Survivalism%2B%255BRevision%2B%25231%255D"; + } + + void no404() /** @author */ + { + QFETCH( QString, artist ); + QFETCH( QString, track ); + + QUrl url = lastfm::UrlBuilder( "music" ).slash( artist ).slash( "_" ).slash( track ).url(); + + QCOMPARE( getResponseCode( url ), 200 ); + } + + void no404_data() /** @author */ + { + QTest::addColumn("artist"); + QTest::addColumn("track"); + + #define NEW_ROW( x, y ) QTest::newRow( x " - " y ) << x << y; + NEW_ROW( "Air", "Radio #1" ); + NEW_ROW( "Pink Floyd", "Speak to Me / Breathe" ); + NEW_ROW( "Radiohead", "2 + 2 = 5" ); + NEW_ROW( "Above & Beyond", "World On Fire (Maor Levi Remix)" ); + #undef NEW_ROW + } + + void test404() /** @author */ + { + QCOMPARE( getResponseCode( QUrl("http://www.last.fm/404") ), 404 ); + } +}; diff --git a/thirdparty/liblastfm2/tests/main.cpp b/thirdparty/liblastfm2/tests/main.cpp new file mode 100644 index 000000000..5995bffa2 --- /dev/null +++ b/thirdparty/liblastfm2/tests/main.cpp @@ -0,0 +1,22 @@ +/* + 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 +#include +#include "TestTrack.h" +#include "TestUrlBuilder.h" + +int main( int argc, char** argv) +{ + QCoreApplication app( argc, argv ); + + #define TEST( Type ) { \ + Type o; \ + if (int r = QTest::qExec( &o, argc, argv ) != 0) return r; } + + TEST( TestTrack ); + TEST( TestUrlBuilder ); + return 0; +} diff --git a/thirdparty/liblastfm2/tests/tests.pro b/thirdparty/liblastfm2/tests/tests.pro new file mode 100644 index 000000000..da65f1cde --- /dev/null +++ b/thirdparty/liblastfm2/tests/tests.pro @@ -0,0 +1,4 @@ +QT = core testlib network xml +LIBS += -llastfm -L$$DESTDIR +SOURCES = main.cpp +HEADERS = TestTrack.h TestUrlBuilder.h \ No newline at end of file