pax_global_header00006660000000000000000000000064124651702440014517gustar00rootroot0000000000000052 comment=44331654256df83bc1d3cbb271a8ce3d4c464686 liblastfm-1.1.0/000077500000000000000000000000001246517024400134735ustar00rootroot00000000000000liblastfm-1.1.0/CMakeLists.txt000066400000000000000000000047311246517024400162400ustar00rootroot00000000000000cmake_minimum_required(VERSION 2.8.6) project(liblastfm) set(CMAKE_MODULE_PATH ${CMAKE_CURRENT_SOURCE_DIR}/cmake/modules) # general settings set(LASTFM_SOVERSION 1) set(LASTFM_VERSION "0x00010100") set(LASTFM_MAJOR_VERSION "1") set(LASTFM_MINOR_VERSION "1") set(LASTFM_PATCH_VERSION "0") set(LASTFM_VERSION_STRING "${LASTFM_MAJOR_VERSION}.${LASTFM_MINOR_VERSION}.${LASTFM_PATCH_VERSION}") # options option(BUILD_FINGERPRINT "Build the lastfm-fingerprint library" ON) option(BUILD_DEMOS "Build the lastfm example programs" OFF) option(BUILD_TESTS "Build liblastfm tests" ON) # installation dirs include(GNUInstallDirs) #cmake module path set(CMAKE_MODULE_PATH "${CMAKE_MODULE_PATH}" "${PROJECT_SOURCE_DIR}/cmake/Modules") # setup qt stuff set(CMAKE_AUTOMOC ON) option(BUILD_WITH_QT4 "Build liblastfm with Qt4 no matter if Qt5 was found" OFF) if( NOT BUILD_WITH_QT4 ) # try Qt5 first, and prefer that if found find_package(Qt5Core QUIET) endif() if(Qt5Core_DIR) set(LASTFM_LIB_VERSION_SUFFIX 5) message(STATUS "Found Qt5! Please keep in mind, this is highly experimental and not our main development target..") include_directories(${Qt5Core_INCLUDE_DIRS}) if(UNIX AND NOT APPLE) find_package(Qt5DBus REQUIRED) endif() # macro(qt_wrap_ui) # qt5_wrap_ui(${ARGN}) # endmacro() # # macro(qt_add_resources) # qt5_add_resources(${ARGN}) # endmacro() # # find_package(Qt5LinguistTools REQUIRED) # macro(qt_add_translation) # qt5_add_translation(${ARGN}) # endmacro() else() find_package(Qt4 COMPONENTS QtCore QtNetwork QtXml REQUIRED) macro(qt5_use_modules) endmacro() endif() if(CMAKE_COMPILER_IS_GNUCXX) add_definitions("-fno-operator-names -fvisibility-inlines-hidden -fvisibility=hidden") endif() if(UNIX AND NOT APPLE) set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wl,--no-undefined") endif() if(MSVC) set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /Zc:wchar_t-") endif(MSVC) set(LASTFM_LIB_TARGET_NAME lastfm${LASTFM_LIB_VERSION_SUFFIX} CACHE INTERNAL "Target name of liblastfm" FORCE) set(FINGERPRINT_LIB_TARGET_NAME lastfm_fingerprint${LASTFM_LIB_VERSION_SUFFIX} CACHE INTERNAL "Target name of liblastfm_fingerprint" FORCE) # main library add_subdirectory(src) # lastfm_fingerprint library if(BUILD_FINGERPRINT) add_subdirectory(src/fingerprint) endif() # demos if(BUILD_DEMOS) add_subdirectory(demos) endif() # tests if(BUILD_TESTS) enable_testing() add_subdirectory(tests) endif() liblastfm-1.1.0/COPYING000066400000000000000000000773301246517024400145400ustar00rootroot00000000000000 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 liblastfm-1.1.0/README.md000066400000000000000000000053521246517024400147570ustar00rootroot00000000000000# 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. Michael Coffey http://twitter.com/eartle Fork it: http://github.com/lastfm/liblastfm Join us for chats on IRC! Server: irc.last.fm Channel: #last.desktop # Dependencies liblastfm requires: * Qt 4.8 http://qt.digia.com/ * FFTW http://www.fftw.org/ * libsamplerate http://www.mega-nerd.com/SRC/ Additionally, to build it you will need: * cmake ## Mac OS X We recommend that you use Homebrew to install dependancies http://mxcl.github.com/homebrew/ brew install qt ## Linux/*NIX Do something like this: sudo apt-get install qt4-qmake pkg-config g++ libqt4-dev cmake libfftw-dev libsamplerate0-dev 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 eartle on GitHub. ## Windows Install Visual Studio 2008 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. Open a plain Windows shell, and see the next section. # Installing liblastfm mkdir _build && cd _build cmake .. make -j4 sudo make install # 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. # 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. All headers under src/ (not subfolders) are public by default liblastfm-1.1.0/cmake/000077500000000000000000000000001246517024400145535ustar00rootroot00000000000000liblastfm-1.1.0/cmake/Modules/000077500000000000000000000000001246517024400161635ustar00rootroot00000000000000liblastfm-1.1.0/cmake/Modules/FindDBus.cmake000066400000000000000000000042031246517024400206220ustar00rootroot00000000000000# - Try to find the low-level D-Bus library # Once done this will define # # DBUS_FOUND - system has D-Bus # DBUS_INCLUDE_DIR - the D-Bus include directory # DBUS_ARCH_INCLUDE_DIR - the D-Bus architecture-specific include directory # DBUS_LIBRARIES - the libraries needed to use D-Bus # Copyright (c) 2008, Kevin Kofler, # modeled after FindLibArt.cmake: # Copyright (c) 2006, Alexander Neundorf, # # Redistribution and use is allowed according to the terms of the BSD license. # For details see the accompanying COPYING-CMAKE-SCRIPTS file. if (DBUS_INCLUDE_DIR AND DBUS_ARCH_INCLUDE_DIR AND DBUS_LIBRARIES) # in cache already SET(DBUS_FOUND TRUE) else (DBUS_INCLUDE_DIR AND DBUS_ARCH_INCLUDE_DIR AND DBUS_LIBRARIES) IF (NOT WIN32) FIND_PACKAGE(PkgConfig) IF (PKG_CONFIG_FOUND) # use pkg-config to get the directories and then use these values # in the FIND_PATH() and FIND_LIBRARY() calls pkg_check_modules(_DBUS_PC QUIET dbus-1) ENDIF (PKG_CONFIG_FOUND) ENDIF (NOT WIN32) FIND_PATH(DBUS_INCLUDE_DIR dbus/dbus.h ${_DBUS_PC_INCLUDE_DIRS} /usr/include /usr/include/dbus-1.0 /usr/local/include ) FIND_PATH(DBUS_ARCH_INCLUDE_DIR dbus/dbus-arch-deps.h ${_DBUS_PC_INCLUDE_DIRS} /usr/lib${LIB_SUFFIX}/include /usr/lib${LIB_SUFFIX}/dbus-1.0/include /usr/lib64/include /usr/lib64/dbus-1.0/include /usr/lib/include /usr/lib/dbus-1.0/include ) FIND_LIBRARY(DBUS_LIBRARIES NAMES dbus-1 dbus PATHS ${_DBUS_PC_LIBDIR} ) if (DBUS_INCLUDE_DIR AND DBUS_ARCH_INCLUDE_DIR AND DBUS_LIBRARIES) set(DBUS_FOUND TRUE) endif (DBUS_INCLUDE_DIR AND DBUS_ARCH_INCLUDE_DIR AND DBUS_LIBRARIES) if (DBUS_FOUND) if (NOT DBus_FIND_QUIETLY) message(STATUS "Found D-Bus: ${DBUS_LIBRARIES}") endif (NOT DBus_FIND_QUIETLY) else (DBUS_FOUND) if (DBus_FIND_REQUIRED) message(FATAL_ERROR "Could NOT find D-Bus") endif (DBus_FIND_REQUIRED) endif (DBUS_FOUND) MARK_AS_ADVANCED(DBUS_INCLUDE_DIR DBUS_ARCH_INCLUDE_DIR DBUS_LIBRARIES) endif (DBUS_INCLUDE_DIR AND DBUS_ARCH_INCLUDE_DIR AND DBUS_LIBRARIES) liblastfm-1.1.0/cmake/Modules/FindLibFFTW3.cmake000066400000000000000000000036201246517024400212470ustar00rootroot00000000000000# 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 find_package(PkgConfig) pkg_check_modules(PC_FFTW3F QUIET fftw) set(FFTW3F_DEFINITIONS ${PC_FFTW3F_CFLAGS_OTHER}) 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 HINTS ${PC_FFTW3F_INCLUDEDIR} ${PC_FFTW3F_INCLUDE_DIRS}) 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 HINTS ${PC_FFTW3F_LIBDIR} ${PC_FFTW3F_LIBRARY_DIRS}) 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) liblastfm-1.1.0/cmake/Modules/FindLibSamplerate.cmake000066400000000000000000000023201246517024400225070ustar00rootroot00000000000000# 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_package(PkgConfig) pkg_check_modules(PC_LIBSAMPLERATE QUIET libsamplerate) set(LIBSAMPLERATE_DEFINITIONS ${PC_LIBSAMPLERATE_CFLAGS_OTHER}) find_library(LIBSAMPLERATE_LIBRARY NAMES samplerate libsamplerate-0 samplerate-0 HINTS ${PC_LIBSAMPLERATE_LIBDIR} ${PC_LIBSAMPLERATE_LIBRARY_DIRS}) find_path(LIBSAMPLERATE_INCLUDE_DIR samplerate.h HINTS ${PC_LIBSAMPLERATE_INCLUDEDIR} ${PC_LIBSAMPLERATE_INCLUDE_DIRS}) 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) liblastfm-1.1.0/demos/000077500000000000000000000000001246517024400146025ustar00rootroot00000000000000liblastfm-1.1.0/demos/demo1.cpp000066400000000000000000000053201246517024400163130ustar00rootroot00000000000000/* 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 "Artist.h" #include "ws.h" #include #include #include #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" liblastfm-1.1.0/demos/demo2.cpp000066400000000000000000000104771246517024400163250ustar00rootroot00000000000000/* 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 "ws.h" #include "misc.h" #include "XmlQuery.h" #include "Artist.h" #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 #warning credentials need to be filled in 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; #warning this code needs to be ported to new api, executable is most likely broken //= 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; #warning this code needs to be ported to new api, executable is most likely broken //= 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" liblastfm-1.1.0/demos/demo3.cpp000066400000000000000000000033421246517024400163170ustar00rootroot00000000000000/* 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 "Audioscrobbler.h" #include "ws.h" #include "Track.h" #include #include 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" ); 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 ); QTimer timer; timer.setSingleShot( true ); timer.setInterval( 31*1000 ); connect( &timer, SIGNAL(timeout()), &as, SLOT(submit()) ); timer.start(); app.connect( &as, SIGNAL(status(int)), SLOT(onStatus(int)) ); return app.exec(); } #include "demo3.moc" liblastfm-1.1.0/src/000077500000000000000000000000001246517024400142625ustar00rootroot00000000000000liblastfm-1.1.0/src/AbstractType.h000066400000000000000000000026701246517024400170450ustar00rootroot00000000000000/* 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 LASTFM_ABSTRACTTYPE_H #define LASTFM_ABSTRACTTYPE_H #include #include #include "global.h" namespace lastfm { class LASTFM_DLLEXPORT AbstractType { public: enum ImageSize { SmallImage, MediumImage, LargeImage, /** seemingly 174x174 */ ExtraLargeImage, MegaImage }; 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 // LASTFM_ABSTRACTTYPE_H liblastfm-1.1.0/src/Album.cpp000066400000000000000000000104431246517024400160300ustar00rootroot00000000000000/* 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 "UrlBuilder.h" #include "XmlQuery.h" #include "ws.h" #include #include #include using lastfm::Album; using lastfm::Artist; using lastfm::Mbid; namespace lastfm { class AlbumPrivate : public QSharedData { public: AlbumPrivate() {} Mbid mbid; Artist artist; QString title; QMap images; }; } Album::Album() :AbstractType(), d( new lastfm::AlbumPrivate ) { } Album::Album( Mbid mbid ) :AbstractType(), d( new lastfm::AlbumPrivate ) { d->mbid = mbid; } Album::Album( Artist artist, QString title ) :AbstractType(), d( new lastfm::AlbumPrivate ) { d->artist = artist; d->title = title; } Album::Album( const Album& other ) : d( other.d ) { } Album::~Album() { } QDomElement Album::toDomElement( QDomDocument& ) const { return QDomElement(); } QUrl Album::imageUrl( ImageSize size, bool square ) const { if( !square ) return d->images.value( size ); QUrl url = d->images.value( size ); QRegExp re( "/serve/(\\d*)s?/" ); return QUrl( url.toString().replace( re, "/serve/\\1s/" )); } void Album::setImageUrl( ImageSize size, const QString& url ) { if ( !url.isEmpty() ) d->images[size] = url; } bool Album::operator==( const Album& that ) const { return d->title == that.d->title && d->artist == that.d->artist; } bool Album::operator!=( const Album& that ) const { return d->title != that.d->title || d->artist != that.d->artist; } Album& Album::operator=( const Album& that ) { d = that.d; return *this; } QString Album::toString() const { return title(); } Album::operator QString() const { return toString(); } QString Album::title() const { return d->title; } void Album::setArtist( const QString& artist ) { return d->artist.setName( artist ); } Artist Album::artist() const { return d->artist; } Mbid Album::mbid() const { return d->mbid; } bool Album::isNull() const { return d->title.isEmpty() && d->mbid.isNull(); } QNetworkReply* lastfm::Album::getInfo( const QString& username) const { QMap map; map["method"] = "album.getInfo"; map["artist"] = d->artist; map["album"] = d->title; if (!username.isEmpty()) map["username"] = username; if (!lastfm::ws::SessionKey.isEmpty()) map["sk"] = lastfm::ws::SessionKey; return lastfm::ws::get(map); } QNetworkReply* lastfm::Album::getTags() const { QMap map; map["method"] = "album.getTags"; map["artist"] = d->artist; map["album"] = d->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"] = d->artist; map["album"] = d->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( d->artist ).slash( d->title ).url(); } QNetworkReply* lastfm::Album::addTags( const QStringList& tags ) const { if (tags.isEmpty()) return 0; QMap map; map["method"] = "album.addTags"; map["artist"] = d->artist; map["album"] = d->title; map["tags"] = tags.join( QChar(',') ); return lastfm::ws::post(map); } liblastfm-1.1.0/src/Album.h000066400000000000000000000050131246517024400154720ustar00rootroot00000000000000/* 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 "AbstractType.h" #include "Artist.h" #include "Mbid.h" namespace lastfm { class LASTFM_DLLEXPORT Album : public AbstractType { public: Album(); explicit Album( Mbid mbid ); Album( Artist artist, QString title ); Album( const Album& album ); ~Album(); QDomElement toDomElement( QDomDocument& ) const; virtual QUrl imageUrl( ImageSize size, bool square = false ) const; void setImageUrl( ImageSize size, const QString& url ); void setArtist( const QString& artist ); bool operator==( const Album& that ) const; bool operator!=( const Album& that ) const; Album& operator=( const Album& that ); QString toString() const; operator QString() const; QString title() const; Artist artist() const; Mbid mbid() const; /** artist may have been set, since we allow that in the ctor, but should we handle untitled albums? */ bool isNull() const; /** Album.getInfo WebService */ QNetworkReply* getInfo( const QString& username = "" ) 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; private: QExplicitlySharedDataPointer d; }; } #endif //LASTFM_ALBUM_H liblastfm-1.1.0/src/Artist.cpp000066400000000000000000000155371246517024400162470ustar00rootroot00000000000000/* 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 "UrlBuilder.h" #include "XmlQuery.h" #include "ws.h" #include #include #include using lastfm::Artist; using lastfm::ArtistData; using lastfm::User; using lastfm::XmlQuery; class lastfm::ArtistData : public QSharedData { public: ArtistData() {} ~ArtistData() {} QString name; QMap images; QString biography; QString biographySummary; }; Artist::Artist() :AbstractType() { d = new ArtistData; } Artist::Artist( const QString& name ) : AbstractType() { d = new ArtistData; d->name = name; } Artist::Artist( const XmlQuery& xml ) :AbstractType() { d = new ArtistData; d->name = xml["name"].text(); setImageUrl( SmallImage, xml["image size=small"].text() ); setImageUrl( MediumImage, xml["image size=medium"].text() ); setImageUrl( LargeImage, xml["image size=large"].text() ); setImageUrl( ExtraLargeImage, xml["image size=extralarge"].text() ); setImageUrl( MegaImage, xml["image size=mega"].text() ); d->biography = xml["bio"]["content"].text().trimmed(); d->biographySummary = xml["bio"]["summary"].text().trimmed(); } Artist::Artist( const Artist& artist ) :AbstractType(), d( artist.d ) { } Artist::~Artist() { } QUrl Artist::imageUrl( ImageSize size, bool square ) const { if( !square ) return d->images.value( size ); QUrl url = d->images.value( size ); QRegExp re( "/serve/(\\d*)s?/" ); return QUrl( url.toString().replace( re, "/serve/\\1s/" )); } void Artist::setImageUrl( ImageSize size, const QString& url ) { if ( !url.isEmpty() ) d->images[size] = url; } QString Artist::biography() const { return d->biography; } QString Artist::biographySummary() const { return d->biographySummary; } QMap //private Artist::params( const QString& method ) const { QMap map; map["method"] = "artist."+method; map["artist"] = d->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& username ) const { QMap map = params("getInfo"); if (!username.isEmpty()) map["username"] = username; if (!lastfm::ws::SessionKey.isEmpty()) map["sk"] = lastfm::ws::SessionKey; return ws::get( map ); } QNetworkReply* Artist::getTags() const { return ws::get( params("getTags") ); } QNetworkReply* Artist::getTopTags() const { return ws::get( params("getTopTags") ); } QNetworkReply* Artist::getTopTracks() const { return ws::get( params("getTopTracks") ); } QNetworkReply* Artist::getSimilar( int limit ) const { QMap map = params("getSimilar"); if ( limit != -1 ) map["limit"] = QString::number( limit ); return ws::get( map ); } 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; XmlQuery lfm; if ( lfm.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() ); } } else { qWarning() << lfm.parseError().message(); } return artists; } QStringList /* static */ Artist::getTopTracks( QNetworkReply* r ) { QStringList tracks; try { XmlQuery lfm; lfm.parse( r ); foreach (XmlQuery e, lfm.children( "track" )) { tracks << e["name"].text(); } } catch (ws::ParseError& e) { qWarning() << e.message(); } return tracks; } QList /* static */ Artist::list( QNetworkReply* r ) { QList artists; XmlQuery lfm; if ( lfm.parse( r ) ) { foreach (XmlQuery xq, lfm.children( "artist" )) { Artist artist( xq ); artists += artist; } } else { qWarning() << lfm.parseError().message(); } return artists; } Artist Artist::getInfo( QNetworkReply* r ) { XmlQuery lfm; if ( lfm.parse( r ) ) { Artist artist = Artist( lfm["artist"] ); return artist; } else { qWarning() << lfm.parseError().message(); 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); } bool Artist::isNull() const { return d->name.isEmpty(); } Artist& Artist::operator=( const Artist& that ) { d->name = that.name(); d->images = that.d->images; return *this; } bool lastfm::Artist::operator==( const Artist& that ) const { return d->name == that.d->name; } bool Artist::operator!=( const Artist& that ) const { return d->name != that.d->name; } bool Artist::operator<( const Artist& that ) const { return d->name < that.d->name; } Artist::operator QString() const { return d->name; } QString Artist::toString() const { return name(); } QString Artist::name() const { return QString(*this); } void Artist::setName( const QString& name ) { d->name = name; } QDomElement Artist::toDomElement( QDomDocument& ) const { return QDomElement(); } liblastfm-1.1.0/src/Artist.h000066400000000000000000000066211246517024400157060ustar00rootroot00000000000000/* 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 "AbstractType.h" #include #include class QNetworkReply; namespace lastfm { class LASTFM_DLLEXPORT Artist : public AbstractType { private: QExplicitlySharedDataPointer d; public: Artist(); ~Artist(); Artist( const QString& name ); Artist( const class XmlQuery& xml ); Artist( const Artist& artist ); /** will be QUrl() unless you got this back from a getInfo or something call */ QUrl imageUrl( ImageSize size = LargeImage, bool square = false ) const; void setImageUrl( ImageSize size, const QString& url ); bool isNull() const; /** the url for this artist's page at www.last.fm */ QUrl www() const; Artist& operator=( const Artist& that ); bool operator==( const Artist& that ) const; bool operator!=( const Artist& that ) const; bool operator<( const Artist& that ) const; operator QString() const; QString toString() const; QString name() const; void setName( const QString& name ); QString biographySummary() const; QString biography() const; QDomElement toDomElement( QDomDocument& ) const; QNetworkReply* share( const QStringList& recipients, const QString& message = "", bool isPublic = true ) const; QNetworkReply* getEvents(int limit = 0) const; QNetworkReply* getInfo( const QString& username = "" ) const; static Artist getInfo( QNetworkReply* ); QNetworkReply* getSimilar( int limit = -1 ) 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; QNetworkReply* getTopTracks() const; static QStringList getTopTracks( QNetworkReply* ); /** 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 liblastfm-1.1.0/src/Audioscrobbler.cpp000066400000000000000000000154561246517024400177400ustar00rootroot00000000000000/* 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 "Audioscrobbler.h" #include "ScrobbleCache.h" #include "Track.h" #include "User.h" #include "ws.h" #include "XmlQuery.h" #include #include namespace lastfm { class AudioscrobblerPrivate { public: AudioscrobblerPrivate(const QString& id) : m_id( id ) , m_cache( ws::Username ) {} ~AudioscrobblerPrivate() { } void parseTrack( const XmlQuery& trackXml, const Track& track ); 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() { if ( d->m_nowPlayingReply ) d->m_nowPlayingReply->abort(); if ( d->m_scrobbleReply ) d->m_scrobbleReply->abort(); 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, const QString& ) { d->m_cache.add( tracks ); emit scrobblesCached( tracks ); } void lastfm::Audioscrobbler::cacheBatch( const QList& tracks ) { cacheBatch( tracks, "" ); } 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::AudioscrobblerPrivate::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.setScrobbleErrorText( trackXml["ignoredMessage"].text() ); mTrack.setScrobbleStatus( Track::Error ); } } void lastfm::Audioscrobbler::onNowPlayingReturn() { lastfm::XmlQuery lfm; if ( lfm.parse( d->m_nowPlayingReply ) ) { qDebug() << lfm; if ( lfm.attribute("status") == "ok" ) d->parseTrack( lfm["nowplaying"], d->m_nowPlayingTrack ); else emit nowPlayingError( lfm["error"].attribute("code").toInt(), lfm["error"].text() ); } else { qDebug() << lfm.parseError().message() << lfm.parseError().enumValue(); } d->m_nowPlayingTrack = Track(); d->m_nowPlayingReply = 0; } void lastfm::Audioscrobbler::onTrackScrobbleReturn() { lastfm::XmlQuery lfm; if ( lfm.parse( d->m_scrobbleReply ) ) { qDebug() << lfm; if (lfm.attribute("status") == "ok") { int index = 0; foreach ( const XmlQuery& scrobble, lfm["scrobbles"].children("scrobble") ) d->parseTrack( scrobble, d->m_batch.at( index++ ) ); emit scrobblesSubmitted( d->m_batch ); d->m_cache.remove( d->m_batch ); d->m_batch.clear(); } else if ( d->m_scrobbleReply->error() == QNetworkReply::NoError ) { // The scrobble submission failed, but the http request was sucessful if ( !(lfm["error"].attribute("code") == "9" // Bad session || lfm["error"].attribute("code") == "11" // Service offline || lfm["error"].attribute("code") == "16") ) // Service temporarily unavailable { foreach ( const Track& track, d->m_batch ) { MutableTrack mTrack = MutableTrack( track ); mTrack.setScrobbleError( static_cast(lfm["error"].attribute("code").toInt()) ); mTrack.setScrobbleErrorText( lfm["error"].text() ); mTrack.setScrobbleStatus( Track::Error ); } emit scrobblesSubmitted( d->m_batch ); // 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; // check is there are anymore scrobbles to submit submit(); } else { qDebug() << lfm.parseError().message() << lfm.parseError().enumValue(); d->m_scrobbleReply = 0; } } liblastfm-1.1.0/src/Audioscrobbler.h000066400000000000000000000050041246517024400173710ustar00rootroot00000000000000/* 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 "global.h" #include #include namespace lastfm { class Track; /** @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 ); /* Note that this is emitted after we tried to submit the scrobbles It could just be that they have an error code */ void scrobblesSubmitted( 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&, const QString& id ); void cacheBatch( const QList& ); /** will submit the submission cache for this user */ void submit(); private slots: void onNowPlayingReturn(); void onTrackScrobbleReturn(); private: class AudioscrobblerPrivate * const d; }; } #endif liblastfm-1.1.0/src/Auth.cpp000066400000000000000000000026301246517024400156700ustar00rootroot00000000000000/* Copyright 2012 Last.fm Ltd. - Primarily authored by 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 "Auth.h" #include "ws.h" lastfm::Auth::Auth() { } QNetworkReply* lastfm::Auth::getSessionInfo() { QMap map; map["method"] = "Auth.getSessionInfo"; if (!lastfm::ws::Username.isEmpty()) map["username"] = lastfm::ws::Username; return nam()->get( QNetworkRequest( lastfm::ws::url( map, true ) ) ); } QNetworkReply* lastfm::Auth::getMobileSession( const QString& username, const QString& password ) { QMap map; map["method"] = "Auth.getMobileSession"; map["username"] = username; map["password"] = password; return 0; //return nam()->post( QNetworkRequest( lastfm::ws::url( map, true ) ) ); } liblastfm-1.1.0/src/Auth.h000066400000000000000000000020531246517024400153340ustar00rootroot00000000000000/* Copyright 2012 Last.fm Ltd. - Primarily authored by 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 . */ #pragma once #include "global.h" class QNetworkReply; namespace lastfm { class LASTFM_DLLEXPORT Auth { private: Auth(); public: static QNetworkReply* getSessionInfo(); static QNetworkReply* getMobileSession( const QString& username, const QString& password ); }; } liblastfm-1.1.0/src/CMakeLists.txt000066400000000000000000000056051246517024400170300ustar00rootroot00000000000000configure_file(${CMAKE_CURRENT_SOURCE_DIR}/global.h.in ${CMAKE_CURRENT_BINARY_DIR}/global.h) add_definitions(${QT_DEFINITIONS}) include_directories(${QT_INCLUDES} ${CMAKE_CURRENT_BINARY_DIR}) set(liblastfm_LIBRARIES ${QT_QTCORE_LIBRARY} ${QT_QTNETWORK_LIBRARY} ${QT_QTXML_LIBRARY} ) list(APPEND liblastfm_QT5_MODULES Xml Network ) set(liblastfm_SOURCES ws.cpp NetworkConnectionMonitor.cpp NetworkAccessManager.cpp InternetConnectionMonitor.cpp Xspf.cpp User.cpp Track.cpp Tasteometer.cpp Tag.cpp Playlist.cpp Mbid.cpp FingerprintId.cpp Artist.cpp Album.cpp ScrobbleCache.cpp ScrobblePoint.cpp Audioscrobbler.cpp RadioTuner.cpp RadioStation.cpp XmlQuery.cpp Url.cpp UrlBuilder.cpp misc.cpp Chart.cpp Auth.cpp Library.cpp ) if(WIN32) add_definitions("-DWIN32_LEAN_AND_MEAN") if(NOT MINGW) add_definitions("-D_ATL_DLL -D_CRT_SECURE_NO_WARNINGS") list(APPEND liblastfm_SOURCES win/WNetworkConnectionMonitor_win.cpp win/WmiSink.cpp win/NdisEvents.cpp ) list(APPEND liblastfm_LIBRARIES winhttp wbemuuid ) endif() endif() if(APPLE) #FIXME: enable this when deploying #set(CMAKE_OSX_ARCHITECTURES "i386;ppc") #set(CMAKE_OSX_DEPLOYMENT_TARGET 10.5) #set(CMAKE_OSX_SYSROOT "/Developer/SDKs/MacOSX${CMAKE_OSX_DEPLOYMENT_TARGET}.sdk") list(APPEND liblastfm_SOURCES mac/MNetworkConnectionMonitor_mac.cpp ) find_library(SYSTEMCONFIGURATION_LIBRARY SystemConfiguration) find_library(COREFOUNDATION_LIBRARY CoreFoundation) list(APPEND liblastfm_LIBRARIES ${COREFOUNDATION_LIBRARY} ${SYSTEMCONFIGURATION_LIBRARY} ) endif() if(UNIX AND NOT APPLE) list(APPEND liblastfm_SOURCES linux/LNetworkConnectionMonitor_linux.cpp ) list(APPEND liblastfm_LIBRARIES ${QT_QTDBUS_LIBRARY}) list(APPEND liblastfm_QT5_MODULES DBus) endif() add_library(${LASTFM_LIB_TARGET_NAME} SHARED ${liblastfm_SOURCES}) qt5_use_modules(${LASTFM_LIB_TARGET_NAME} ${liblastfm_QT5_MODULES}) target_link_libraries(${LASTFM_LIB_TARGET_NAME} ${liblastfm_LIBRARIES}) set_target_properties(${LASTFM_LIB_TARGET_NAME} PROPERTIES VERSION ${LASTFM_VERSION_STRING} SOVERSION ${LASTFM_SOVERSION} COMPILE_DEFINITIONS LASTFM_LIB ) install(TARGETS ${LASTFM_LIB_TARGET_NAME} RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR} ) file(GLOB liblastfm_HEADERS ${CMAKE_CURRENT_LIST_DIR}/*.h) list(APPEND liblastfm_HEADERS ${CMAKE_CURRENT_BINARY_DIR}/global.h) install(FILES ${liblastfm_HEADERS} DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/lastfm${LASTFM_LIB_VERSION_SUFFIX}/) liblastfm-1.1.0/src/Chart.cpp000066400000000000000000000055731246517024400160410ustar00rootroot00000000000000/* Copyright 2011 Last.fm Ltd. - Primarily authored by 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 "Chart.h" #include "ws.h" #include lastfm::Chart::Chart() { } QNetworkReply* lastfm::Chart::getHypedArtists( int limit, int page ) { QMap map; map["method"] = "chart.getHypedArtists"; if ( page != -1 ) map["page"] = QString::number( page ); if ( limit != -1 ) map["limit"] = QString::number( limit ); return ws::get( map ); } QNetworkReply* lastfm::Chart::getHypedTracks( int limit, int page ) { QMap map; map["method"] = "chart.getHypedTracks"; if ( page != -1 ) map["page"] = QString::number( page ); if ( limit != -1 ) map["limit"] = QString::number( limit ); return ws::get( map ); } QNetworkReply* lastfm::Chart::getLovedTracks( int limit, int page ) { QMap map; map["method"] = "chart.getLovedTracks"; if ( page != -1 ) map["page"] = QString::number( page ); if ( limit != -1 ) map["limit"] = QString::number( limit ); return ws::get( map ); } QNetworkReply* lastfm::Chart::getTopArtists( int limit, int page ) { QMap map; map["method"] = "chart.getTopArtists"; if ( page != -1 ) map["page"] = QString::number( page ); if ( limit != -1 ) map["limit"] = QString::number( limit ); return ws::get( map ); } QNetworkReply* lastfm::Chart::getTopDownloads( int limit, int page ) { QMap map; map["method"] = "chart.getTopDownloads"; if ( page != -1 ) map["page"] = QString::number( page ); if ( limit != -1 ) map["limit"] = QString::number( limit ); return ws::get( map ); } QNetworkReply* lastfm::Chart::getTopTags( int limit, int page ) { QMap map; map["method"] = "chart.getTopTags"; if ( page != -1 ) map["page"] = QString::number( page ); if ( limit != -1 ) map["limit"] = QString::number( limit ); return ws::get( map ); } QNetworkReply* lastfm::Chart::getTopTracks( int limit, int page ) { QMap map; map["method"] = "chart.getTopTracks"; if ( page != -1 ) map["page"] = QString::number( page ); if ( limit != -1 ) map["limit"] = QString::number( limit ); return ws::get( map ); } liblastfm-1.1.0/src/Chart.h000066400000000000000000000027671246517024400155100ustar00rootroot00000000000000/* Copyright 2011 Last.fm Ltd. - Primarily authored by 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_CHART_H #define LASTFM_CHART_H #include "global.h" class QNetworkReply; namespace lastfm { class LASTFM_DLLEXPORT Chart { private: Chart(); public: static QNetworkReply* getHypedArtists( int limit = -1, int page = -1 ); static QNetworkReply* getHypedTracks( int limit = -1, int page = -1 ); static QNetworkReply* getLovedTracks( int limit = -1, int page = -1 ); static QNetworkReply* getTopArtists( int limit = -1, int page = -1 ); static QNetworkReply* getTopDownloads( int limit = -1, int page = -1 ); static QNetworkReply* getTopTags( int limit = -1, int page = -1 ); static QNetworkReply* getTopTracks( int limit = -1, int page = -1 ); }; } #endif // LASTFM_CHART_H liblastfm-1.1.0/src/FingerprintId.cpp000066400000000000000000000053451246517024400175410ustar00rootroot00000000000000/* 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 "FingerprintId.h" #include "ws.h" #include "XmlQuery.h" class lastfm::FingerprintIdPrivate { public: int id; }; lastfm::FingerprintId::FingerprintId() : d( new FingerprintIdPrivate ) { d->id = -1; } lastfm::FingerprintId::FingerprintId( uint i ) : d( new FingerprintIdPrivate ) { d->id = i; } lastfm::FingerprintId::FingerprintId( const FingerprintId& that ) : d( new FingerprintIdPrivate( *that.d ) ) { } lastfm::FingerprintId::~FingerprintId() { delete d; } bool lastfm::FingerprintId::isNull() const { return d->id == -1; } QNetworkReply* lastfm::FingerprintId::getSuggestions() const { QMap map; map["method"] = "track.getFingerprintMetadata"; map["fingerprintid"] = QString::number( d->id ); return ws::get( map ); } QMap //static lastfm::FingerprintId::getSuggestions( QNetworkReply* reply ) { QMap tracks; lastfm::XmlQuery lfm; if ( lfm.parse( reply ) ) { foreach ( const lastfm::XmlQuery& track, lfm["tracks"].children("track") ) { MutableTrack t; t.setTitle( track["name"].text() ); t.setArtist( track["artist"]["name"].text() ); t.setDuration( track["duration"].text().toInt() ); t.setUrl( track["url"].text() ); t.setMbid( lastfm::Mbid( track["mbid"].text() ) ); tracks.insert( track.attribute("rank").toFloat(), t ); } } return tracks; } lastfm::FingerprintId::operator int() const { return d->id; } lastfm::FingerprintId::operator QString() const { return d->id == -1 ? "" : QString::number( d->id ); } lastfm::FingerprintId& lastfm::FingerprintId::operator=( const FingerprintId& that ) { d->id = that.d->id; return *this; } QDebug operator<<( QDebug d, lastfm::FingerprintId id) { if (id.isNull()) return d << "(null)"; else return d << int(id); } liblastfm-1.1.0/src/FingerprintId.h000066400000000000000000000032661246517024400172060ustar00rootroot00000000000000/* 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 "Track.h" namespace lastfm { class LASTFM_DLLEXPORT FingerprintId { public: FingerprintId(); FingerprintId( uint i ); FingerprintId( const FingerprintId& other ); ~FingerprintId(); bool isNull() const; /** 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; /** isEmpty() if you need to generate it */ operator QString() const; FingerprintId& operator=( const FingerprintId& other ); private: class FingerprintIdPrivate * const d; }; } LASTFM_DLLEXPORT QDebug operator<<( QDebug d, lastfm::FingerprintId id); #endif liblastfm-1.1.0/src/InternetConnectionMonitor.cpp000066400000000000000000000101201246517024400221400ustar00rootroot00000000000000/* 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" #include class lastfm::InternetConnectionMonitorPrivate { public: bool m_up; NetworkConnectionMonitor* m_networkMonitor; }; lastfm::InternetConnectionMonitor::InternetConnectionMonitor( QObject *parent ) : QObject( parent ) , d( new InternetConnectionMonitorPrivate) { d->m_up = true; d->m_networkMonitor = createNetworkConnectionMonitor(); if ( d->m_networkMonitor ) { connect( d->m_networkMonitor, SIGNAL( networkUp() ), this, SLOT( onNetworkUp() ) ); connect( d->m_networkMonitor, SIGNAL( networkDown() ), this, SLOT( onNetworkDown() ) ); } connect( nam(), SIGNAL( finished( QNetworkReply* ) ), this, SLOT( onFinished( QNetworkReply* ) ) ); } lastfm::InternetConnectionMonitor::~InternetConnectionMonitor() { delete d; } bool lastfm::InternetConnectionMonitor::isDown() const { return !d->m_up; } bool lastfm::InternetConnectionMonitor::isUp() const { return d->m_up; } void lastfm::InternetConnectionMonitor::onFinished( QNetworkReply* reply ) { if( reply->attribute( QNetworkRequest::SourceIsFromCacheAttribute).toBool() ) return; switch( reply->error() ) { case QNetworkReply::NoError: if ( !d->m_up ) { d->m_up = true; emit up(); emit connectivityChanged( d->m_up ); qDebug() << "Internet connection is reachable :)"; } break; case QNetworkReply::HostNotFoundError: case QNetworkReply::TimeoutError: case QNetworkReply::ProxyConnectionRefusedError: case QNetworkReply::ProxyConnectionClosedError: case QNetworkReply::ProxyNotFoundError: case QNetworkReply::ProxyTimeoutError: case QNetworkReply::ProxyAuthenticationRequiredError: if ( d->m_up ) { d->m_up = false; emit down(); emit connectivityChanged( d->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 d->m_up = true; emit up(); emit connectivityChanged( d->m_up ); qDebug() << "Internet connection is reachable :)"; #else qDebug() << "Network seems to be up again. Let's try if there's internet connection!"; nam()->head( QNetworkRequest( QUrl( "http://www.last.fm/" ) ) ); #endif } void lastfm::InternetConnectionMonitor::onNetworkDown() { qDebug() << "Internet is unreachable :("; d->m_up = false; emit down(); emit connectivityChanged( d->m_up ); } lastfm::NetworkConnectionMonitor* lastfm::InternetConnectionMonitor::createNetworkConnectionMonitor() { NetworkConnectionMonitor* ncm = 0; #if defined(Q_OS_MAC) ncm = new MNetworkConnectionMonitor( this ); #elif defined(Q_OS_WIN) && ! defined __MINGW32__ ncm = new WNetworkConnectionMonitor( this ); #elif defined(Q_OS_LINUX) ncm = new LNetworkConnectionMonitor( this ); #endif return ncm; } liblastfm-1.1.0/src/InternetConnectionMonitor.h000066400000000000000000000036641246517024400216240ustar00rootroot00000000000000/* 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 "global.h" #include class QNetworkReply; namespace lastfm { class NetworkConnectionMonitor; class LASTFM_DLLEXPORT InternetConnectionMonitor : public QObject { Q_OBJECT 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 ); ~InternetConnectionMonitor(); bool isDown() const; bool isUp() const; 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: class InternetConnectionMonitorPrivate * const d; }; } //namespace lastfm #endif liblastfm-1.1.0/src/Library.cpp000066400000000000000000000105231246517024400163730ustar00rootroot00000000000000/* Copyright 2011 Last.fm Ltd. - Primarily authored by 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 "Library.h" #include "ws.h" #include lastfm::Library::Library() { } QNetworkReply* lastfm::Library::addAlbum( const QList& albums ) { QMap map; map["method"] = "library.addAlbum"; for ( int i = 0 ; i < albums.count() ; ++i ) { map["artist[" + QString::number(i) + "]"] = albums[i].artist(); map["album[" + QString::number(i) + "]"] = albums[i].title(); } return ws::post( map ); } QNetworkReply* lastfm::Library::addArtist( const QList& artists ) { QMap map; map["method"] = "library.addArtist"; for ( int i = 0 ; i < artists.count() ; ++i ) map["artist[" + QString::number(i) + "]"] = artists[i].name(); return ws::post( map ); } QNetworkReply* lastfm::Library::addTrack( const Track &track ) { QMap map; map["method"] = "library.addTrack"; map["track"] = track.title(); map["artist"] = track.artist().name(); return ws::post( map ); } QNetworkReply* lastfm::Library::getAlbums( const QString& user, const lastfm::Artist& artist, int limit, int page ) { QMap map; map["method"] = "library.getAlbums"; map["user"] = user; map["artist"] = artist.name(); if ( page != -1 ) map["page"] = QString::number( page ); if ( limit != -1 ) map["limit"] = QString::number( limit ); return ws::post( map ); } QNetworkReply* lastfm::Library::getArtists( const QString& user, int limit, int page ) { QMap map; map["method"] = "library.getArtists"; map["user"] = user; if ( page != -1 ) map["page"] = QString::number( page ); if ( limit != -1 ) map["limit"] = QString::number( limit ); return ws::post( map ); } QNetworkReply* lastfm::Library::getTracks( const QString& user, const lastfm::Artist& artist, int limit, int page ) { QMap map; map["method"] = "library.getTracks"; map["user"] = user; map["artist"] = artist.name(); map["limit"] = QString::number( limit ); map["page"] = QString::number( page ); return ws::post( map ); } QNetworkReply* lastfm::Library::getTracks( const QString& user, const lastfm::Album& album, int limit, int page ) { QMap map; map["method"] = "library.getTracks"; map["user"] = user; map["album"] = album.title(); map["artist"] = album.artist().name(); if ( page != -1 ) map["page"] = QString::number( page ); if ( limit != -1 ) map["limit"] = QString::number( limit ); return ws::post( map ); } QNetworkReply* lastfm::Library::removeAlbum( const lastfm::Album& album ) { QMap map; map["method"] = "library.removeAlbum"; map["album"] = album.title(); map["artist"] = album.artist().name(); return ws::post( map ); } QNetworkReply* lastfm::Library::removeArtist( const lastfm::Artist& artist ) { QMap map; map["method"] = "library.removeArtist"; map["artist"] = artist.name(); return ws::post( map ); } QNetworkReply* lastfm::Library::removeTrack( const lastfm::Track& track ) { QMap map; map["method"] = "library.removeTrack"; map["artist"] = track.artist().name(); map["track"] = track.title(); return ws::post( map ); } QNetworkReply* lastfm::Library::removeScrobble( const lastfm::Track& track ) { QMap map; map["method"] = "library.removeScrobble"; map["artist"] = track.artist().name(); map["track"] = track.title(); map["timestamp"] = QString::number( track.timestamp().toTime_t() ); return ws::post( map ); } liblastfm-1.1.0/src/Library.h000066400000000000000000000037461246517024400160510ustar00rootroot00000000000000/* Copyright 2011 Last.fm Ltd. - Primarily authored by 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_LIBRARY_H #define LASTFM_LIBRARY_H #include "Track.h" namespace lastfm { class LASTFM_DLLEXPORT Library { private: Library(); public: static QNetworkReply* addAlbum( const QList& albums ); static QNetworkReply* addArtist( const QList& artists ); static QNetworkReply* addTrack( const lastfm::Track& tracks ); static QNetworkReply* getAlbums( const QString& user, const lastfm::Artist& artist = lastfm::Artist(), int limit = -1, int page = -1 ); static QNetworkReply* getArtists( const QString& user, int limit = -1, int page = -1 ); static QNetworkReply* getTracks( const QString& user, const lastfm::Artist& artist = lastfm::Artist(), int limit = -1, int page = -1 ); static QNetworkReply* getTracks( const QString& user, const lastfm::Album& album = lastfm::Album(), int limit = -1, int page = -1 ); static QNetworkReply* removeAlbum( const lastfm::Album& album ); static QNetworkReply* removeArtist( const lastfm::Artist& artist ); static QNetworkReply* removeTrack( const lastfm::Track& track ); static QNetworkReply* removeScrobble( const lastfm::Track& track ); }; } #endif // LASTFM_LIBRARY_H liblastfm-1.1.0/src/Mbid.cpp000066400000000000000000000031111246517024400156350ustar00rootroot00000000000000/* 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 { class MbidPrivate { public: QString id; }; Mbid::Mbid( const QString& p ) : d( new MbidPrivate ) { d->id = p; } Mbid::Mbid( const Mbid& that ) : d( new MbidPrivate( *that.d ) ) { } Mbid::~Mbid() { delete d; } bool Mbid::isNull() const { return d->id.isNull() || d->id.isEmpty(); } Mbid::operator QString() const { return d->id; } Mbid& Mbid::operator=( const Mbid& that ) { d->id = that.d->id; return *this; } 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.d->id = QString::fromLatin1( out ); return mbid; } } liblastfm-1.1.0/src/Mbid.h000066400000000000000000000025741246517024400153160ustar00rootroot00000000000000/* 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 "global.h" namespace lastfm { class LASTFM_DLLEXPORT Mbid { public: explicit Mbid( const QString& p = "" ); Mbid( const Mbid& that ); ~Mbid(); bool isNull() const; operator QString() const; Mbid& operator=( const Mbid& that ); /** 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 ); private: class MbidPrivate * const d; }; } #endif liblastfm-1.1.0/src/NetworkAccessManager.cpp000066400000000000000000000072711246517024400210430ustar00rootroot00000000000000/* 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 "ws.h" #include "misc.h" #include #include #include class NetworkAccessManagerPrivate { public: NetworkAccessManagerPrivate(); QNetworkProxy userProxy; }; NetworkAccessManagerPrivate::NetworkAccessManagerPrivate() : userProxy( QNetworkProxy( QNetworkProxy::DefaultProxy ) ) { } // TODO: Use a d-pointer on the next SONAME bump typedef QHash< const lastfm::NetworkAccessManager *, NetworkAccessManagerPrivate *> NamPrivateHash; Q_GLOBAL_STATIC( NamPrivateHash, d_func ) static NetworkAccessManagerPrivate * d( const lastfm::NetworkAccessManager * nam ) { NetworkAccessManagerPrivate * ret = d_func()->value( nam, 0 ); if ( !ret ) { ret = new NetworkAccessManagerPrivate; d_func()->insert( nam, ret ); } return ret; } static void delete_d( const lastfm::NetworkAccessManager * nam ) { const NetworkAccessManagerPrivate * ret = d_func()->value( nam, 0 ); delete ret; d_func()->remove( nam ); } namespace lastfm { LASTFM_DLLEXPORT QByteArray UserAgent; } lastfm::NetworkAccessManager::NetworkAccessManager( QObject* parent ) : QNetworkAccessManager( parent ) #if defined WIN32 && ! defined __MINGW32__ , 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() { delete_d( this ); } void lastfm::NetworkAccessManager::setUserProxy( const QNetworkProxy& proxy ) { d( this )->userProxy = proxy; } QNetworkProxy lastfm::NetworkAccessManager::proxy( const QNetworkRequest& request ) { if ( d( this )->userProxy.type() != QNetworkProxy::DefaultProxy ) return d( this )->userProxy; QList proxies = QNetworkProxyFactory::systemProxyForQuery( QNetworkProxyQuery( request.url() ) ); return proxies[0]; } 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 ); // PAC proxies can vary by domain, so we have to check everytime :( QNetworkAccessManager::setProxy( this->proxy( request ) ); return QNetworkAccessManager::createRequest( op, request, outgoingData ); } void lastfm::NetworkAccessManager::onConnectivityChanged( bool up ) { Q_UNUSED( up ); } liblastfm-1.1.0/src/NetworkAccessManager.h000066400000000000000000000041501246517024400205010ustar00rootroot00000000000000/* 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 "global.h" #include #include #include class QNetworkReply; namespace lastfm { /** Sets useragent and proxy. Auto detecting the proxy where possible. */ class LASTFM_DLLEXPORT NetworkAccessManager : public QNetworkAccessManager { Q_OBJECT #ifdef Q_OS_WIN class InternetConnectionMonitor* m_monitor; #endif public: NetworkAccessManager( QObject *parent = 0 ); ~NetworkAccessManager(); void setUserProxy( const QNetworkProxy& proxy ); /** 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 liblastfm-1.1.0/src/NetworkConnectionMonitor.cpp000066400000000000000000000027241246517024400220140ustar00rootroot00000000000000/* 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" class lastfm::NetworkConnectionMonitorPrivate { public: bool connected; }; lastfm::NetworkConnectionMonitor::NetworkConnectionMonitor( QObject* /*parent*/ ) : d( new NetworkConnectionMonitorPrivate ) { d->connected = true; } lastfm::NetworkConnectionMonitor::~NetworkConnectionMonitor() { delete d; } bool lastfm::NetworkConnectionMonitor::isConnected() const { return d->connected; } void lastfm::NetworkConnectionMonitor::setConnected( bool connected ) { if ( d->connected != connected ) { d->connected = connected; if ( connected ) emit networkUp(); else emit networkDown(); } } liblastfm-1.1.0/src/NetworkConnectionMonitor.h000066400000000000000000000024741246517024400214630ustar00rootroot00000000000000/* 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 "global.h" #include namespace lastfm { 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: class NetworkConnectionMonitorPrivate * const d; }; } #endif // NETWORK_CONNECTION_MONITOR_H liblastfm-1.1.0/src/Playlist.cpp000066400000000000000000000044351246517024400165750ustar00rootroot00000000000000/* 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.h" class lastfm::PlaylistPrivate { public: int id; }; lastfm::Playlist::Playlist() : d( new PlaylistPrivate ) { d->id = -1; } lastfm::Playlist::Playlist( const Playlist& that ) : d( new PlaylistPrivate( *that.d ) ) { } lastfm::Playlist::~Playlist() { delete d; } lastfm::Playlist::Playlist( int id ) : d( new PlaylistPrivate ) { d->id = id; } int lastfm::Playlist::id() const { return d->id; } QNetworkReply* lastfm::Playlist::addTrack( const Track& t ) const { QMap map; map["method"] = "playlist.addTrack"; map["playlistID"] = d->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( d->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); } lastfm::Playlist& lastfm::Playlist::operator=( const Playlist& that ) { d->id = that.d->id; return *this; } liblastfm-1.1.0/src/Playlist.h000066400000000000000000000026151246517024400162400ustar00rootroot00000000000000/* 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 "Xspf.h" namespace lastfm { class LASTFM_DLLEXPORT Playlist { Playlist(); class PlaylistPrivate * const d; public: Playlist( int id ); Playlist( const Playlist& that ); ~Playlist(); int id() const; QNetworkReply* addTrack( const Track& ) const; QNetworkReply* fetch() const; static QNetworkReply* create( const QString& title, const QString& description = "" ); static QNetworkReply* fetch( const QUrl& url ); Playlist& operator=( const Playlist& that ); }; } #endif liblastfm-1.1.0/src/RadioStation.cpp000066400000000000000000000170461246517024400173760ustar00rootroot00000000000000/* 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 "RadioStation.h" #include "XmlQuery.h" #include #include #include const float k_defaultRep(0.5); const float k_defaultMainstr(0.5); const bool k_defaultDisco(false); class lastfm::RadioStationData : public QSharedData { public: QUrl m_url; QString m_title; QString m_tagFilter; float m_rep; float m_mainstr; bool m_disco; }; lastfm::RadioStation::RadioStation() : d( new RadioStationData ) { } lastfm::RadioStation::RadioStation( const QString& s ) : d( new RadioStationData ) { // If it's a tag filtered station then extract that part QString tempString = s; if ( !tempString.startsWith("lastfm://tag/") ) { int index = tempString.indexOf("/tag/"); if ( index != -1 ) { d->m_tagFilter = tempString.mid( index + 5, tempString.count() - (index + 5) ); tempString = tempString.mid( 0, index ); } } d->m_url = tempString; } lastfm::RadioStation::RadioStation( const RadioStation& other ) : d(other.d) { } lastfm::RadioStation& lastfm::RadioStation::operator=( const RadioStation& that ) { d = that.d; return *this; } lastfm::RadioStation::~RadioStation() { } lastfm::RadioStation lastfm::RadioStation::library( const lastfm::User& user ) { QList users; users << user; return library( users ); } lastfm::RadioStation lastfm::RadioStation::library( QList& users ) { qSort(users.begin(), users.end()); QString url = (users.count() > 1) ? "lastfm://users/" : "lastfm://user/"; url.append( users[0].name() ); for ( int i = 1 ; i < users.count() ; ++i ) url.append( "," + users[i].name() ); url.append("/personal"); RadioStation s( url ); if( users.count() != 1 ) { QString title; for( QList::const_iterator i = users.constBegin(); i != users.constEnd(); i++ ) { if( i == users.constEnd() - 1 ) title += " and " + *i; else title += ", " + *i; } s.setTitle( title ); } return s; } lastfm::RadioStation lastfm::RadioStation::recommendations( const lastfm::User& user ) { return RadioStation( "lastfm://user/" + user + "/recommended" ); } lastfm::RadioStation lastfm::RadioStation::friends( const lastfm::User& user ) { return RadioStation( "lastfm://user/" + user + "/friends" ); } lastfm::RadioStation lastfm::RadioStation::neighbourhood( const lastfm::User& user ) { return RadioStation( "lastfm://user/" + user + "/neighbours" ); } lastfm::RadioStation lastfm::RadioStation::tag( const lastfm::Tag& tag ) { QList tags; tags << tag; return lastfm::RadioStation::tag( tags ); } lastfm::RadioStation lastfm::RadioStation::tag( QList& tag ) { qSort(tag.begin(), tag.end()); QString url = (tag.count() > 1) ? "lastfm://tag/" : "lastfm://globaltags/"; url.append( tag[0].name() ); for ( int i = 1 ; i < tag.count() ; ++i ) url.append( "*" + tag[i].name() ); return RadioStation( url ); } lastfm::RadioStation lastfm::RadioStation::similar( const lastfm::Artist& artist ) { QList artists; artists << artist; return similar( artists ); } lastfm::RadioStation lastfm::RadioStation::similar( QList& artists ) { qSort(artists.begin(), artists.end()); QString url = (artists.count() > 1) ? "lastfm://artistnames/" : "lastfm://artist/"; url.append( artists[0].name() ); for ( int i = 1 ; i < artists.count() ; ++i ) url.append( "," + artists[i].name() ); if (artists.count() == 1) url.append( "/similarartists" ); return RadioStation( url ); } lastfm::RadioStation lastfm::RadioStation::mix( const lastfm::User& user ) { return RadioStation( "lastfm://user/" + user + "/mix" ); } QString lastfm::RadioStation::url() const { return d->m_url.toString() + (d->m_tagFilter.isEmpty() ? "" : "/tag/" + d->m_tagFilter); } void lastfm::RadioStation::setTitle( const QString& title ) { // Stop the radio station getting renamed when the web services don't know what it's called if ( !d->m_title.isEmpty() && title.compare( "a radio station", Qt::CaseInsensitive ) == 0 ) return; // do not rename the current user's stations if they already have a name if ( !d->m_title.isEmpty() && d->m_url.toString().startsWith( "lastfm://user/" + User().name() ) ) return; d->m_title = title.trimmed(); } void lastfm::RadioStation::setUrl( const QString& url ) { d->m_url = url; } QString lastfm::RadioStation::title() const { return d->m_title; // + (d->m_tagFilter.isEmpty() ? "" : ": " + d->m_tagFilter); } void lastfm::RadioStation::setTagFilter( const QString& tag ) { d->m_tagFilter = tag; } QNetworkReply* lastfm::RadioStation::getSampleArtists( int limit ) const { QMap map; map["method"] = "radio.getSampleArtists"; map["station"] = d->m_url.toString(); map["limit"] = QString::number( limit ); return ws::get( map ); } QNetworkReply* lastfm::RadioStation::getTagSuggestions( int limit ) const { QMap map; map["method"] = "radio.getTagSuggestions"; map["station"] = d->m_url.toString(); map["limit"] = QString::number( limit ); return ws::get( map ); } bool lastfm::RadioStation::isLegacyPlaylist() const { return d->m_url.toString().startsWith( "lastfm://play/" ) || d->m_url.toString().startsWith( "lastfm://preview/" ) || d->m_url.toString().startsWith( "lastfm://track/" ) || d->m_url.toString().startsWith( "lastfm://playlist/" ); } //static QList lastfm::RadioStation::list( QNetworkReply* r ) { QList result; XmlQuery lfm; if ( lfm.parse( r ) ) { foreach (XmlQuery xq, lfm.children("station")) { lastfm::RadioStation rs( QUrl::fromPercentEncoding( xq["url"].text().toUtf8() ) ); rs.setTitle(xq["name"].text()); result.append(rs); } } else { qWarning() << lfm.parseError().message(); } return result; } bool lastfm::RadioStation::operator==( const RadioStation& that ) const { return this->d->m_url == that.d->m_url && this->d->m_tagFilter == that.d->m_tagFilter; } void lastfm::RadioStation::setRep(float rep) { d->m_rep = rep; } void lastfm::RadioStation::setMainstr(float mainstr) { d->m_mainstr = mainstr; } void lastfm::RadioStation::setDisco(bool disco) { d->m_disco = disco; } float lastfm::RadioStation::rep() const { return d->m_rep; } float lastfm::RadioStation::mainstr() const { return d->m_mainstr; } bool lastfm::RadioStation::disco() const { return d->m_disco; } QDebug operator<<( QDebug d, const lastfm::RadioStation& station ) { return d << station.url(); } liblastfm-1.1.0/src/RadioStation.h000066400000000000000000000061021246517024400170320ustar00rootroot00000000000000/* 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 "User.h" #include "Tag.h" #include "Artist.h" namespace lastfm { /** @author */ class LASTFM_DLLEXPORT RadioStation { public: RadioStation(); RadioStation( const QString& s ); RadioStation( const RadioStation& that ); ~RadioStation(); static RadioStation library( const lastfm::User& user ); static RadioStation library( QList& users ); static RadioStation similar( const lastfm::Artist& artist ); static RadioStation similar( QList& artist ); static RadioStation tag( const lastfm::Tag& tag ); static RadioStation tag( QList& tag ); static RadioStation recommendations( const lastfm::User& user ); static RadioStation friends( const lastfm::User& user ); static RadioStation neighbourhood( const lastfm::User& user ); static RadioStation mix( const lastfm::User& user ); QNetworkReply* getSampleArtists( int limit = 50 ) const; QNetworkReply* getTagSuggestions( int limit = 50 ) const; /** 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; /** the Last.fm url, eg. lastfm://user/mxcl/loved */ QString url() const; void setTitle( const QString& title ); void setUrl( const QString& url ); void setTagFilter( const QString& tag ); void setRep(float rep); void setMainstr(float mainstr); void setDisco(bool disco); float rep() const; float mainstr() const; bool disco() const; bool isLegacyPlaylist() const; // good for getRecentStations: static QList list( QNetworkReply* ); bool operator==( const RadioStation& that ) const; RadioStation& operator=( const RadioStation& that ); private: QSharedDataPointer d; }; } Q_DECLARE_METATYPE( lastfm::RadioStation ) LASTFM_DLLEXPORT QDebug operator<<( QDebug d, const lastfm::RadioStation& station ); #endif liblastfm-1.1.0/src/RadioTuner.cpp000066400000000000000000000173471246517024400170560ustar00rootroot00000000000000/* 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 "RadioStation.h" #include "XmlQuery.h" #include "Xspf.h" #include "ws.h" #include #include 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 class lastfm::RadioTunerPrivate : public QObject { Q_OBJECT public: QList m_playlist; uint m_retry_counter; bool m_fetchingPlaylist; bool m_requestedPlaylist; class QTimer* m_twoSecondTimer; RadioStation m_station; RadioStation m_retuneStation; RadioTunerPrivate( QObject * parent, const RadioStation& station ); /** 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. */ void fetchFiveMoreTracks(); private slots: void onTwoSecondTimeout(); }; lastfm::RadioTunerPrivate::RadioTunerPrivate( QObject *parent, const RadioStation& station ) : QObject( parent ), m_station( station ) { m_retry_counter = 0; m_fetchingPlaylist = false; m_requestedPlaylist = false; m_twoSecondTimer = new QTimer( this ); m_twoSecondTimer->setSingleShot( true ); connect( m_twoSecondTimer, SIGNAL(timeout()), SLOT(onTwoSecondTimeout())); } void RadioTunerPrivate::onTwoSecondTimeout() { if (m_requestedPlaylist) { m_requestedPlaylist = false; fetchFiveMoreTracks(); } } void RadioTunerPrivate::fetchFiveMoreTracks() { if ( !m_retuneStation.url().isEmpty() ) { // We have been asked to retune so do it now QMap map; map["method"] = "radio.tune"; map["station"] = m_retuneStation.url(); map["additional_info"] = "1"; QNetworkReply* reply = ws::post(map); connect( reply, SIGNAL(finished()), parent(), SLOT(onTuneReturn()) ); //m_retuneStation = RadioStation(); m_twoSecondTimer->stop(); } else { if ( !m_twoSecondTimer->isActive() ) { //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 connect( ws::post( map ), SIGNAL(finished()), parent(), SLOT(onGetPlaylistReturn()) ); m_fetchingPlaylist = true; } else m_requestedPlaylist = true; } } bool RadioTunerPrivate::tryAgain() { qDebug() << "Bad response count" << m_retry_counter; if (++m_retry_counter > MAX_TUNING_ATTEMPTS) return false; fetchFiveMoreTracks(); return true; } RadioTuner::RadioTuner( const RadioStation& station ) :d( new RadioTunerPrivate( this, station ) ) { qDebug() << station.url(); //Empty RadioStation implies that the radio //should tune to the previous station. if( station.url().isEmpty() ) { d->fetchFiveMoreTracks(); } else { QMap map; map["method"] = "radio.tune"; map["station"] = station.url(); map["additional_info"] = "1"; connect( ws::post(map), SIGNAL(finished()), SLOT(onTuneReturn()) ); } } RadioTuner::~RadioTuner() { } void RadioTuner::retune( const RadioStation& station ) { d->m_playlist.clear(); d->m_retuneStation = station; qDebug() << station.url(); } void RadioTuner::onTuneReturn() { if ( !d->m_retuneStation.url().isEmpty() ) { d->m_station = d->m_retuneStation; d->m_retuneStation = RadioStation(); } XmlQuery lfm; if ( lfm.parse( qobject_cast(sender()) ) ) { qDebug() << "Tune successful!"; d->m_station.setTitle( lfm["station"]["name"].text() ); d->m_station.setUrl( lfm["station"]["url"].text() ); emit title( lfm["station"]["name"].text() ); emit supportsDisco( lfm["station"]["supportsdiscovery"].text() == "1" ); d->fetchFiveMoreTracks(); } else { emit error( lfm.parseError().enumValue(), lfm.parseError().message() ); } } void RadioTuner::onGetPlaylistReturn() { // We shouldn't request another playlist for 2 seconds because we'll get the same one // in a different order. This QTimer will block until it has finished. If one or more // playlists have been requested in the meantime, it will fetch one on timeout d->m_twoSecondTimer->start( 2000 ); // This will block us fetching two playlists at once d->m_fetchingPlaylist = false; XmlQuery lfm; if ( lfm.parse( qobject_cast(sender()) ) ) { qDebug() << "Get playlist successful!"; d->m_station.setTitle( lfm["playlist"]["title"].text() ); // we don't get the radio url in the playlist //d->m_station.setUrl( lfm["station"]["url"].text() ); emit title( lfm["playlist"]["title"].text() ); Xspf* xspf = new Xspf( lfm["playlist"], this ); if ( xspf->isEmpty() ) { // give up after too many empty playlists :( if (!d->tryAgain()) emit error( ws::NotEnoughContent, "Not enough content" ); } else { d->m_retry_counter = 0; d->m_playlist << xspf->tracks(); emit trackAvailable(); } delete xspf; } else { qDebug() << lfm.parseError().message() << lfm.parseError().enumValue(); emit error( lfm.parseError().enumValue(), lfm.parseError().message() ); } } void RadioTuner::onXspfExpired() { // no-op } void RadioTuner::queueTrack( lastfm::Track& track ) { d->m_playlist.insert( 0, track ); } bool trackExpired( const Track& track ) { return (!track.extra( "expiry" ).isEmpty()) && QDateTime::currentDateTime() > QDateTime::fromTime_t( track.extra( "expiry" ).toInt() ); } Track RadioTuner::takeNextTrack() { if ( !d->m_playlist.isEmpty() ) { Track track = d->m_playlist.takeFirst(); while ( trackExpired( track ) && !d->m_playlist.isEmpty() ) track = d->m_playlist.takeFirst(); if ( !trackExpired( track ) ) return track; } else if ( !d->m_fetchingPlaylist ) d->fetchFiveMoreTracks(); return Track(); } #include "RadioTuner.moc" liblastfm-1.1.0/src/RadioTuner.h000066400000000000000000000036461246517024400165200ustar00rootroot00000000000000/* 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 "Track.h" #include "ws.h" #include namespace lastfm { class RadioStation; /** 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& ); ~RadioTuner(); Track takeNextTrack(); void retune( const RadioStation& ); void queueTrack( lastfm::Track& track ); signals: void title( const QString& ); void supportsDisco( bool supportsDisco ); void trackAvailable(); void error( lastfm::ws::Error, const QString& message ); private slots: void onTuneReturn(); void onGetPlaylistReturn(); // no-op Q_DECL_DEPRECATED void onXspfExpired(); private: class RadioTunerPrivate * const d; }; } #endif liblastfm-1.1.0/src/ScrobbleCache.cpp000066400000000000000000000142511246517024400174500ustar00rootroot00000000000000/* 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 "misc.h" #include #include #include #include #include using lastfm::ScrobbleCache; class lastfm::ScrobbleCachePrivate { public: QString m_username; QString m_path; QList m_tracks; void write(); /// writes m_tracks to m_path void read( QDomDocument& xml ); /// reads from m_path into m_tracks }; ScrobbleCache::ScrobbleCache( const QString& username ) : d( new ScrobbleCachePrivate ) { Q_ASSERT( username.length() ); d->m_path = lastfm::dir::runtimeData().filePath( username + "_subs_cache.xml" ); d->m_username = username; QDomDocument xml; d->read( xml ); } ScrobbleCache::ScrobbleCache( const ScrobbleCache& that ) : d ( new ScrobbleCachePrivate( *that.d ) ) { } ScrobbleCache& ScrobbleCache::operator=( const ScrobbleCache& that ) { d->m_username = that.d->m_username; d->m_path = that.d->m_path; d->m_tracks = that.d->m_tracks; return *this; } ScrobbleCache::~ScrobbleCache() { delete d; } void lastfm::ScrobbleCachePrivate::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 lastfm::ScrobbleCachePrivate::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) { Invalidity invalidity; if ( !isValid( track, &invalidity ) ) { qWarning() << invalidity; MutableTrack mt = MutableTrack( track ); mt.setScrobbleStatus( Track::Error ); mt.setScrobbleError( Track::Invalid ); mt.setScrobbleErrorText( "Invalid" ); } else if (track.isNull()) qDebug() << "Will not cache an empty track"; else { bool ok; int plays = track.extra( "playCount" ).toInt( &ok ); if ( !ok ) plays = 1; // Add the track that the app is sharing and set it's scrobble status d->m_tracks += track; MutableTrack( track ).setScrobbleStatus( Track::Cached ); // now add any duplicate plays as clones that are all 1 second apart for ( int i = 1 ; i < plays ; ++i ) { MutableTrack mt = MutableTrack( track.clone() ); mt.setTimeStamp( mt.timestamp().addSecs( -i ) ); d->m_tracks += mt; } } } d->write(); } int ScrobbleCache::remove( const QList& toremove ) { QMutableListIterator i( d->m_tracks ); while (i.hasNext()) { Track t = i.next(); for (int x = 0; x < toremove.count(); ++x) if (toremove[x] == t) i.remove(); } d->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 d->m_tracks.count(); } bool ScrobbleCache::isValid( const lastfm::Track& track, Invalidity* v ) { #define TEST( test, x ) \ if (test) { \ if (v) *v = x; \ return false; \ } TEST( track.duration() < ScrobblePoint::scrobbleTimeMin(), ScrobbleCache::TooShort ); TEST( !track.timestamp().isValid(), ScrobbleCache::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 ), ScrobbleCache::FromTheFuture ); TEST( track.timestamp().daysTo( QDateTime::currentDateTime() ) > 14, ScrobbleCache::FromTheDistantPast ); // Check if any required fields are empty TEST( track.artist().isNull(), ScrobbleCache::ArtistNameMissing ); TEST( track.title().isEmpty(), ScrobbleCache::TrackNameMissing ); TEST( (QStringList() << "unknown artist" << "unknown" << "[unknown]" << "[unknown artist]").contains( track.artist().name().toLower() ), ScrobbleCache::ArtistInvalid ); return true; } QList ScrobbleCache::tracks() const { return d->m_tracks; } QString ScrobbleCache::path() const { return d->m_path; } QString ScrobbleCache::username() const { return d->m_username; } liblastfm-1.1.0/src/ScrobbleCache.h000066400000000000000000000036171246517024400171210ustar00rootroot00000000000000/* 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 "Track.h" #include namespace lastfm { /** absolutely not thread-safe */ class LASTFM_DLLEXPORT ScrobbleCache { public: enum Invalidity { TooShort, ArtistNameMissing, TrackNameMissing, ArtistInvalid, NoTimestamp, FromTheFuture, FromTheDistantPast }; explicit ScrobbleCache( const QString& username ); ScrobbleCache( const ScrobbleCache& that ); ~ScrobbleCache(); /** 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& ); static bool isValid( const lastfm::Track& track, Invalidity* v = 0 ); ScrobbleCache& operator=( const ScrobbleCache& that ); QList tracks() const; QString path() const; QString username() const; private: bool operator==( const ScrobbleCache& ); //undefined class ScrobbleCachePrivate * const d; }; } #endif liblastfm-1.1.0/src/ScrobblePoint.cpp000066400000000000000000000052731246517024400175420ustar00rootroot00000000000000/* 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 "ScrobblePoint.h" #define SCROBBLE_PERCENT_MIN 50 #define SCROBBLE_PERCENT_MAX 100 #define DEFAULT_SCROBBLE_PERCENT 50 #define SCROBBLE_TIME_MIN 31 #define SCROBBLE_TIME_MAX 240 class lastfm::ScrobblePointPrivate { public: uint i; bool enforceScrobbleTimeMax; }; lastfm::ScrobblePoint::ScrobblePoint() : d( new ScrobblePointPrivate ) { d->i = SCROBBLE_TIME_MAX; d->enforceScrobbleTimeMax = true; } lastfm::ScrobblePoint::ScrobblePoint( uint j ) : d( new ScrobblePointPrivate ) { // we special case 0, returning SCROBBLE_TIME_MAX because we are // cruel and callous people if (j == 0) --j; d->i = j; d->enforceScrobbleTimeMax = true; } lastfm::ScrobblePoint::ScrobblePoint( const ScrobblePoint& that ) : d( new ScrobblePointPrivate ) { d->i = that.d->i; d->enforceScrobbleTimeMax = that.d->enforceScrobbleTimeMax; } lastfm::ScrobblePoint::~ScrobblePoint() { delete d; } void lastfm::ScrobblePoint::setEnforceScrobbleTimeMax( bool enforceScrobbleTimeMax ) { d->enforceScrobbleTimeMax = enforceScrobbleTimeMax; } lastfm::ScrobblePoint::operator uint() const { return qBound( uint(SCROBBLE_TIME_MIN), d->i, d->enforceScrobbleTimeMax ? uint(SCROBBLE_TIME_MAX) : 0xFFFFFFFF ); } lastfm::ScrobblePoint& lastfm::ScrobblePoint::operator=( const ScrobblePoint& that ) { d->i = that.d->i; d->enforceScrobbleTimeMax = that.d->enforceScrobbleTimeMax; return *this; } uint lastfm::ScrobblePoint::scrobblePercentMin() { return SCROBBLE_PERCENT_MIN; } uint lastfm::ScrobblePoint::scrobblePercentMax() { return SCROBBLE_PERCENT_MAX; } uint lastfm::ScrobblePoint::defaultScrobblePercent() { return DEFAULT_SCROBBLE_PERCENT; } uint lastfm::ScrobblePoint::scrobbleTimeMin() { return SCROBBLE_TIME_MIN; } uint lastfm::ScrobblePoint::scrobbleTimeMax() { return SCROBBLE_TIME_MAX; } liblastfm-1.1.0/src/ScrobblePoint.h000066400000000000000000000033761246517024400172110ustar00rootroot00000000000000/* 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 "global.h" namespace lastfm { class LASTFM_DLLEXPORT ScrobblePoint { public: ScrobblePoint(); ~ScrobblePoint(); /** j is in seconds, and should be 50% the duration of a track */ explicit ScrobblePoint( uint j ); ScrobblePoint( const ScrobblePoint& that ); operator uint() const; ScrobblePoint& operator=( const ScrobblePoint& that ); void setEnforceScrobbleTimeMax( bool enforceScrobbleTimeMax ); // scrobbles can occur between these two percentages of track duration static uint scrobblePercentMin(); // 50 static uint scrobblePercentMax(); // 100 static uint defaultScrobblePercent(); // 50 // Shortest track length allowed to scrobble in seconds static uint scrobbleTimeMin(); // 31 // Upper limit for scrobble time in seconds static uint scrobbleTimeMax(); // 240 private: class ScrobblePointPrivate * const d; }; } #endif liblastfm-1.1.0/src/Tag.cpp000066400000000000000000000050311246517024400155000ustar00rootroot00000000000000/* 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 "UrlBuilder.h" #include "XmlQuery.h" #include "ws.h" #include using lastfm::Tag; using lastfm::User; class lastfm::TagPrivate { public: QString name; }; Tag::Tag( const QString& name ) : d( new TagPrivate ) { d->name = name; } Tag::Tag( const Tag& that ) : d( new TagPrivate( *that.d ) ) { } Tag::~Tag() { delete d; } Tag::operator QString() const { return d->name; } QString Tag::name() const { return d->name; } lastfm::Tag Tag::operator=( const Tag& that ) const { return Tag( that.name() ); } bool Tag::operator<( const Tag& that ) const { return this->d->name < that.d->name; } QUrl Tag::www() const { return UrlBuilder( "tag" ).slash( d->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"] = d->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; XmlQuery lfm; if ( lfm.parse( r ) ) { foreach ( XmlQuery xq, lfm.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() ); } else { qDebug() << lfm.parseError().message() << lfm.parseError().enumValue(); } return tags; } liblastfm-1.1.0/src/Tag.h000066400000000000000000000040151246517024400151460ustar00rootroot00000000000000/* 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 "global.h" #include #include class QNetworkReply; namespace lastfm { class User; class LASTFM_DLLEXPORT Tag { public: Tag( const QString& name ); Tag( const Tag& that ); ~Tag(); operator QString() const; QString name() const; lastfm::Tag operator=( const Tag& that ) const; bool operator<( const Tag& that ) const; /** 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 User& user ) const; /** pass the finished QNetworkReply to Tag::list() */ QNetworkReply* search() const; /** the top global tags on Last.fm, sorted by popularity (number of times used) */ static 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* ); private: class TagPrivate * const d; }; } #endif liblastfm-1.1.0/src/Tasteometer.cpp000066400000000000000000000022561246517024400172670ustar00rootroot00000000000000/* Copyright 2011 Last.fm Ltd. - Primarily authored by 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 "Tasteometer.h" #include "User.h" lastfm::Tasteometer::Tasteometer() { } lastfm::Tasteometer::~Tasteometer() { } QNetworkReply* lastfm::Tasteometer::compare( const User& left, const User& right ) { QMap map; map["method"] = "Tasteometer.compare"; map["type1"] = "user"; map["value1"] = left.name(); map["type2"] = "user"; map["value2"] = right.name(); return lastfm::ws::get( map ); } liblastfm-1.1.0/src/Tasteometer.h000066400000000000000000000021221246517024400167240ustar00rootroot00000000000000/* Copyright 2011 Last.fm Ltd. - Primarily authored by 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 TASTEOMETER_H #define TASTEOMETER_H #include "global.h" class QNetworkReply; namespace lastfm { class User; class LASTFM_DLLEXPORT Tasteometer { public: Tasteometer(); ~Tasteometer(); public: static QNetworkReply* compare( const User& left, const User& right ); }; } #endif // TASTEOMETER_H liblastfm-1.1.0/src/Track.cpp000066400000000000000000000751121246517024400160400ustar00rootroot00000000000000/* 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 "UrlBuilder.h" #include "XmlQuery.h" #include "ws.h" #include #include #include #include class lastfm::TrackContextPrivate { public: TrackContext::Type m_type; QList m_values; static TrackContext::Type getType( const QString& typeString ); }; lastfm::TrackContext::Type lastfm::TrackContextPrivate::getType( const QString& typeString ) { lastfm::TrackContext::Type type = lastfm::TrackContext::UnknownType; if ( typeString == "artist" ) type = lastfm::TrackContext::Artist; else if ( typeString == "user" ) type = lastfm::TrackContext::User; else if ( typeString == "neighbour" ) type = lastfm::TrackContext::Neighbour; else if ( typeString == "friend" ) type = lastfm::TrackContext::Friend; return type; } lastfm::TrackContext::TrackContext() :d( new TrackContextPrivate ) { d->m_type = UnknownType; } lastfm::TrackContext::TrackContext( const QString& type, const QList& values ) :d( new TrackContextPrivate ) { d->m_values = values; d->m_type = d->getType( type ); } lastfm::TrackContext::TrackContext( const TrackContext& that ) :d( new TrackContextPrivate( *that.d ) ) { } lastfm::TrackContext::~TrackContext() { delete d; } lastfm::TrackContext::Type lastfm::TrackContext::type() const { return d->m_type; } QList lastfm::TrackContext::values() const { return d->m_values; } lastfm::TrackContext& lastfm::TrackContext::operator=( const TrackContext& that ) { d->m_type = that.d->m_type; d->m_values = that.d->m_values; return *this; } class TrackObject : public QObject { Q_OBJECT public: TrackObject( lastfm::TrackData& data ) : m_data( data ) {;} public: void forceLoveToggled( bool love ); void forceScrobbleStatusChanged(); void forceCorrected( QString correction ); private slots: void onLoveFinished(); void onUnloveFinished(); void onGotInfo(); signals: void loveToggled( bool love ); void scrobbleStatusChanged( short scrobbleStatus ); void corrected( QString correction ); private: lastfm::TrackData& m_data; }; class lastfm::TrackData : public QSharedData { friend class TrackObject; public: TrackData(); ~TrackData(); public: lastfm::Artist artist; lastfm::Artist albumArtist; lastfm::Album album; QString title; lastfm::Artist correctedArtist; lastfm::Artist correctedAlbumArtist; lastfm::Album correctedAlbum; QString correctedTitle; TrackContext context; 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 lastfm::Track::LoveStatus loved; QMap m_images; short scrobbleStatus; short scrobbleError; QString scrobbleErrorText; //FIXME I hate this, but is used for radio trackauth etc. QMap extras; struct Observer { QNetworkReply* reply; QPointer receiver; const char* method; }; QList observers; bool null; bool podcast; bool video; TrackObject* trackObject; }; lastfm::TrackData::TrackData() : trackNumber( 0 ), duration( 0 ), source( Track::UnknownSource ), rating( 0 ), fpid( -1 ), loved( Track::UnknownLoveStatus ), scrobbleStatus( Track::Null ), scrobbleError( Track::None ), null( false ), podcast( false ), video( false ) { trackObject = new TrackObject( *this ); } lastfm::TrackData::~TrackData() { delete trackObject; } lastfm::Track::Track() :AbstractType() { d = new TrackData; d->null = true; } lastfm::Track::Track( const Track& that ) :AbstractType(), d( that.d ) { } lastfm::Track::Track( const QDomElement& e ) :AbstractType() { d = new TrackData; if (e.isNull()) { d->null = true; return; } //TODO: not sure of lastfm's xml changed, but nodes have // Artist Name.. // as children isntead of Artist Name // we detect both here. QDomNode artistName = e.namedItem( "artist" ).namedItem( "name" ); if( artistName.isNull() ) { d->artist = e.namedItem( "artist" ).toElement().text(); } else { d->artist = artistName.toElement().text(); } //TODO: not sure if lastfm xml's changed, or if chart.getTopTracks uses //a different format, but the title is stored at //Title... //we detect both here. QDomNode trackTitle = e.namedItem( "name" ); if( trackTitle.isNull() ) d->title = e.namedItem( "track" ).toElement().text(); else d->title = trackTitle.toElement().text(); d->albumArtist = e.namedItem( "albumArtist" ).toElement().text(); d->album = Album( d->artist, e.namedItem( "album" ).toElement().text() ); d->correctedArtist = e.namedItem( "correctedArtist" ).toElement().text(); d->correctedAlbumArtist = e.namedItem( "correctedAlbumArtist" ).toElement().text(); d->correctedAlbum = Album( d->correctedArtist, 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::UnknownSource d->time = QDateTime::fromTime_t( e.namedItem( "timestamp" ).toElement().text().toUInt() ); d->loved = static_cast(e.namedItem( "loved" ).toElement().text().toInt()); d->scrobbleStatus = e.namedItem( "scrobbleStatus" ).toElement().text().toInt(); d->scrobbleError = e.namedItem( "scrobbleError" ).toElement().text().toInt(); d->scrobbleErrorText = e.namedItem( "scrobbleErrorText" ).toElement().text(); d->podcast = e.namedItem( "podcast" ).toElement().text().toInt(); d->video = e.namedItem( "video" ).toElement().text().toInt(); for (QDomElement image = e.firstChildElement("image") ; !image.isNull() ; image = image.nextSiblingElement("image")) d->m_images[static_cast(image.attribute("size").toInt())] = image.text(); QDomNode artistNode = e.namedItem("artistImages"); for (QDomElement artistImage = artistNode.firstChildElement("image") ; !artistImage.isNull() ; artistImage = artistImage.nextSiblingElement("image")) artist().setImageUrl( static_cast(artistImage.attribute("size").toInt()), artistImage.text() ); QDomNode albumNode = e.namedItem("albumImages"); for (QDomElement albumImage = albumNode.firstChildElement("image") ; !albumImage.isNull() ; albumImage = albumImage.nextSiblingElement("image")) album().setImageUrl( static_cast(albumImage.attribute("size").toInt()), albumImage.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 TrackObject::onLoveFinished() { lastfm::XmlQuery lfm; if ( lfm.parse( static_cast(sender()) ) ) { if ( lfm.attribute( "status" ) == "ok") m_data.loved = lastfm::Track::Loved; } emit loveToggled( m_data.loved == lastfm::Track::Loved ); } void TrackObject::onUnloveFinished() { lastfm::XmlQuery lfm; if ( lfm.parse( static_cast(sender()) ) ) { if ( lfm.attribute( "status" ) == "ok") m_data.loved = lastfm::Track::Unloved; } emit loveToggled( m_data.loved == lastfm::Track::Loved ); } void TrackObject::onGotInfo() { lastfm::TrackData::Observer observer; for ( int i = 0 ; i < m_data.observers.count() ; ++i ) { if ( m_data.observers.at( i ).reply == sender() ) { observer = m_data.observers.takeAt( i ); break; } } QNetworkReply* reply = static_cast(sender()); reply->deleteLater(); const QByteArray data = reply->readAll(); lastfm::XmlQuery lfm; if ( lfm.parse( data ) ) { QString imageUrl = lfm["track"]["image size=small"].text(); if ( !imageUrl.isEmpty() ) m_data.m_images[lastfm::AbstractType::SmallImage] = imageUrl; imageUrl = lfm["track"]["image size=medium"].text(); if ( !imageUrl.isEmpty() ) m_data.m_images[lastfm::AbstractType::MediumImage] = imageUrl; imageUrl = lfm["track"]["image size=large"].text(); if ( !imageUrl.isEmpty() ) m_data.m_images[lastfm::AbstractType::LargeImage] = imageUrl; imageUrl = lfm["track"]["image size=extralarge"].text(); if ( !imageUrl.isEmpty() ) m_data.m_images[lastfm::AbstractType::ExtraLargeImage] = imageUrl; imageUrl = lfm["track"]["image size=mega"].text(); if ( !imageUrl.isEmpty() ) m_data.m_images[lastfm::AbstractType::MegaImage] = imageUrl; if ( lfm["track"]["userloved"].text().length() > 0 ) m_data.loved = lfm["track"]["userloved"].text() == "0" ? lastfm::Track::Unloved : lastfm::Track::Loved; if ( observer.receiver ) if ( !QMetaObject::invokeMethod( observer.receiver, observer.method, Q_ARG(QByteArray, data) ) ) QMetaObject::invokeMethod( observer.receiver, observer.method ); emit loveToggled( m_data.loved == lastfm::Track::Loved ); } else { if ( observer.receiver ) if ( !QMetaObject::invokeMethod( observer.receiver, observer.method, Q_ARG(QByteArray, data) ) ) QMetaObject::invokeMethod( observer.receiver, observer.method ); } } void TrackObject::forceLoveToggled( bool love ) { emit loveToggled( love ); } void TrackObject::forceScrobbleStatusChanged() { emit scrobbleStatusChanged( m_data.scrobbleStatus ); } void TrackObject::forceCorrected( QString correction ) { emit corrected( correction ); } lastfm::Track& lastfm::Track::operator=( const Track& that ) { d = that.d; return *this; } lastfm::Track::~Track() { } lastfm::Track lastfm::Track::clone() const { Track clone = *this; clone.d.detach(); return clone; } 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( d->loved ) ); makeElement( "scrobbleStatus", QString::number( scrobbleStatus() ) ); makeElement( "scrobbleError", QString::number( scrobbleError() ) ); makeElement( "scrobbleErrorText", scrobbleErrorText() ); makeElement( "podcast", QString::number( isPodcast() ) ); makeElement( "video", QString::number( isVideo() ) ); // 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 ); } QDomElement artistElement = xml.createElement( "artistImages" ); for ( int size = SmallImage ; size <= MegaImage ; ++size ) { QString imageUrl = d->artist.imageUrl( static_cast(size) ).toString(); if ( !imageUrl.isEmpty() ) { QDomElement e = xml.createElement( "image" ); e.appendChild( xml.createTextNode( d->artist.imageUrl( static_cast(size) ).toString() ) ); e.setAttribute( "size", size ); artistElement.appendChild( e ); } } if ( artistElement.childNodes().count() != 0 ) item.appendChild( artistElement ); QDomElement albumElement = xml.createElement( "albumImages" ); for ( int size = SmallImage ; size <= MegaImage ; ++size ) { QString imageUrl = d->album.imageUrl( static_cast(size) ).toString(); if ( !imageUrl.isEmpty() ) { QDomElement e = xml.createElement( "image" ); e.appendChild( xml.createTextNode( d->album.imageUrl( static_cast(size) ).toString() ) ); e.setAttribute( "size", size ); albumElement.appendChild( e ); } } if ( albumElement.childNodes().count() != 0 ) item.appendChild( albumElement ); // 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.toString().isEmpty() && (d->correctedAlbum.toString() != d->album.toString())) || (!d->correctedArtist.isNull() && (d->correctedArtist.name() != d->artist.name())) || (!d->correctedAlbumArtist.isNull() && (d->correctedAlbumArtist.name() != d->albumArtist.name()))); } lastfm::Artist lastfm::Track::artist( Corrections corrected ) const { if ( corrected == Corrected && !d->correctedArtist.name().isEmpty() ) return d->correctedArtist; return d->artist; } lastfm::Artist lastfm::Track::albumArtist( Corrections corrected ) const { if ( corrected == Corrected && !d->correctedAlbumArtist.name().isEmpty() ) return d->correctedAlbumArtist; return d->albumArtist; } lastfm::Album lastfm::Track::album( Corrections corrected ) const { if ( corrected == Corrected && !d->correctedAlbum.title().isEmpty() ) return d->correctedAlbum; return 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; } QUrl lastfm::Track::imageUrl( 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.name().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[SmallImage] = imageUrl; imageUrl = lfm["track"]["image size=medium"].text(); if ( !imageUrl.isEmpty() ) d->m_images[MediumImage] = imageUrl; imageUrl = lfm["track"]["image size=large"].text(); if ( !imageUrl.isEmpty() ) d->m_images[LargeImage] = imageUrl; imageUrl = lfm["track"]["image size=extralarge"].text(); if ( !imageUrl.isEmpty() ) d->m_images[ExtraLargeImage] = imageUrl; imageUrl = lfm["track"]["image size=mega"].text(); if ( !imageUrl.isEmpty() ) d->m_images[MegaImage] = imageUrl; if ( lfm["track"]["userloved"].text().length() > 0) d->loved = lfm["track"]["userloved"].text() == "0" ? Unloved : Loved; d->trackObject->forceLoveToggled( d->loved == Loved ); } void lastfm::MutableTrack::setImageUrl( ImageSize size, const QString& url ) { if ( !url.isEmpty() ) d->m_images[size] = url; } 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::getSimilar( int limit ) const { QMap map = params("getSimilar"); if ( limit != -1 ) map["limit"] = QString::number( limit ); map["autocorrect"] = "1"; return ws::get( map ); } QMap > /* static */ lastfm::Track::getSimilar( QNetworkReply* r ) { QMap > tracks; try { XmlQuery lfm; if ( lfm.parse( r ) ) { foreach (XmlQuery e, lfm.children( "track" )) { QPair< QString, QString > track; track.first = e["name"].text(); XmlQuery artist = e.children( "artist" ).first(); track.second = artist["name"].text(); // convert floating percentage to int in range 0 to 10,000 int const match = e["match"].text().toFloat() * 100; tracks.insertMulti( match, track ); } } } catch (ws::ParseError& e) { qWarning() << e.message(); } return tracks; } 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( QObject *receiver, const char *method, const QString &username ) const { QMap map = params("getInfo", true); if (!username.isEmpty()) map["username"] = username; // this is so the web services knows whether to use corrections or not if (!lastfm::ws::SessionKey.isEmpty()) map["sk"] = lastfm::ws::SessionKey; QNetworkReply* reply = ws::get( map ); TrackData::Observer observer; observer.receiver = receiver; observer.method = method; observer.reply = reply; d->observers << observer; QObject::connect( reply, SIGNAL(finished()), d->trackObject, SLOT(onGotInfo())); } QNetworkReply* lastfm::Track::getBuyLinks( const QString& country ) const { QMap map = params( "getBuyLinks", true ); map["country"] = country; return ws::get( map ); } QNetworkReply* lastfm::Track::playlinks( const QList& tracks ) { QMap map; map["method"] = "Track.playlinks"; for ( int i = 0 ; i < tracks.count() ; ++i ) { if ( tracks[i].d->mbid.size()) map["mbid[" + QString::number( i ) + "]"] = tracks[i].d->mbid; else { map["artist[" + QString::number( i ) + "]"] = tracks[i].d->artist; map["track[" + QString::number( i ) + "]"] = tracks[i].d->title; } } return ws::get( map ); } 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["tag"] = tag; return ws::post(map); } QNetworkReply* lastfm::Track::updateNowPlaying() const { return updateNowPlaying(duration()); } QNetworkReply* lastfm::Track::updateNowPlaying( int duration ) const { QMap map = params("updateNowPlaying"); map["duration"] = QString::number( duration ); map["albumArtist"] = d->albumArtist; if ( !album().isNull() ) map["album"] = album(); map["context"] = extra("playerId"); return ws::post(map); } QNetworkReply* lastfm::Track::removeNowPlaying() const { QMap map; map["method"] = "track.removeNowPlaying"; 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.title().isEmpty() ) map["album"] = d->album.title(); map["chosenByUser"] = source() == Track::LastFmRadio ? "0" : "1"; 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(); map["chosenByUser[" + QString::number(i) + "]"] = tracks[i].source() == Track::LastFmRadio ? "0" : "1"; } return ws::post(map); } QUrl lastfm::Track::www() const { return UrlBuilder( "music" ).slash( artist( Corrected ) ).slash( album( Corrected ).isNull() ? QString("_") : album( Corrected )).slash( title( Corrected ) ).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 ); } bool lastfm::Track::sameObject( const Track& that ) { return (this->d == that.d); } bool lastfm::Track::operator==( const Track& that ) const { return ( title( Corrected ) == that.title( Corrected ) // if either album is empty, assume they are the same album && ( album( Corrected ).title().isEmpty() || that.album( Corrected ).title().isEmpty() || album( Corrected ) == that.album( Corrected )) && artist( Corrected ) == that.artist( Corrected )); } bool lastfm::Track::operator!=( const Track& that ) const { return !operator==( that ); } const QObject* lastfm::Track::signalProxy() const { return d->trackObject; } bool lastfm::Track::isNull() const { return d->null; } uint lastfm::Track::trackNumber() const { return d->trackNumber; } uint lastfm::Track::duration() const { // in seconds return d->duration; } lastfm::Mbid lastfm::Track::mbid() const { return lastfm::Mbid(d->mbid); } QUrl lastfm::Track::url() const { return d->url; } QDateTime lastfm::Track::timestamp() const { return d->time; } lastfm::Track::Source lastfm::Track::source() const { return static_cast(d->source); } uint lastfm::Track::fingerprintId() const { return d->fpid; } bool lastfm::Track::isLoved() const { return d->loved == Loved; } lastfm::Track::LoveStatus lastfm::Track::loveStatus() const { return d->loved; } QString lastfm::Track::durationString() const { return durationString( d->duration ); } lastfm::Track::ScrobbleStatus lastfm::Track::scrobbleStatus() const { return static_cast(d->scrobbleStatus); } lastfm::Track::ScrobbleError lastfm::Track::scrobbleError() const { return static_cast(d->scrobbleError); } QString lastfm::Track::scrobbleErrorText() const { return d->scrobbleErrorText; } /** default separator is an en-dash */ QString lastfm::Track::toString() const { return toString( Corrected ); } QString lastfm::Track::toString( Corrections corrections ) const { return toString( QChar(8211), corrections ); } lastfm::TrackContext lastfm::Track::context() const { return d->context; } // iTunes tracks might be podcasts or videos bool lastfm::Track::isPodcast() const { return d->podcast; } bool lastfm::Track::isVideo() const { return d->video; } QString lastfm::Track::extra( const QString& key ) const { return d->extras[ key ]; } bool lastfm::Track::operator<( const Track &that ) const { return this->d->time < that.d->time; } lastfm::Track::operator QVariant() const { return QVariant::fromValue( *this ); } void lastfm::MutableTrack::setCorrections( QString title, QString album, QString artist, QString albumArtist ) { d->correctedTitle = title; d->correctedArtist = artist; d->correctedAlbum = Album( artist, album ); d->correctedAlbumArtist = albumArtist; d->trackObject->forceCorrected( toString() ); } lastfm::MutableTrack::MutableTrack() { d->null = false; } lastfm::MutableTrack::MutableTrack( const Track& that ) : Track( that ) { d->null = false; } void lastfm::MutableTrack::setArtist( QString artist ) { d->artist.setName( artist.trimmed() ); d->album.setArtist( artist.trimmed() ); d->correctedAlbum.setArtist( artist.trimmed() ); } void lastfm::MutableTrack::setAlbumArtist( QString albumArtist ) { d->albumArtist.setName( albumArtist.trimmed() ); } void lastfm::MutableTrack::setAlbum( QString album ) { d->album = Album( d->artist.name(), album.trimmed() ); } void lastfm::MutableTrack::setTitle( QString title ) { d->title = title.trimmed(); } void lastfm::MutableTrack::setTrackNumber( uint n ) { d->trackNumber = n; } void lastfm::MutableTrack::setDuration( uint duration ) { d->duration = duration; } void lastfm::MutableTrack::setUrl( QUrl url ) { d->url = url; } void lastfm::MutableTrack::setSource( Source s ) { d->source = s; } void lastfm::MutableTrack::setLoved( bool loved ) { d->loved = loved ? Loved : Unloved; } void lastfm::MutableTrack::setMbid( Mbid id ) { d->mbid = id; } void lastfm::MutableTrack::setFingerprintId( uint id ) { d->fpid = id; } void lastfm::MutableTrack::setScrobbleStatus( ScrobbleStatus scrobbleStatus ) { if ( scrobbleStatus != d->scrobbleStatus ) { d->scrobbleStatus = scrobbleStatus; d->trackObject->forceScrobbleStatusChanged(); } } void lastfm::MutableTrack::setScrobbleError( ScrobbleError scrobbleError ) { d->scrobbleError = scrobbleError; } void lastfm::MutableTrack::setScrobbleErrorText( const QString& scrobbleErrorText ) { d->scrobbleErrorText = scrobbleErrorText; } void lastfm::MutableTrack::stamp() { d->time = QDateTime::currentDateTime(); } void lastfm::MutableTrack::setExtra( const QString& key, const QString& value ) { d->extras[key] = value; } void lastfm::MutableTrack::removeExtra( QString key ) { d->extras.remove( key ); } void lastfm::MutableTrack::setTimeStamp( const QDateTime& dt ) { d->time = dt; } void lastfm::MutableTrack::setContext( TrackContext context ) { d->context = context; } // iTunes tracks might be podcasts or videos void lastfm::MutableTrack::setPodcast( bool podcast ) { d->podcast = podcast; } void lastfm::MutableTrack::setVideo( bool video ) { d->video = video; } QDebug operator<<( QDebug d, const lastfm::Track& t ) { return !t.isNull() ? d << t.toString( '-' ) << t.url() : d << "Null Track object"; } #include "Track.moc" liblastfm-1.1.0/src/Track.h000066400000000000000000000227561246517024400155130ustar00rootroot00000000000000/* 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 "Album.h" namespace lastfm { class TrackData; class LASTFM_DLLEXPORT TrackContext { public: enum Type { UnknownType, User, Friend, Neighbour, Artist }; TrackContext(); TrackContext( const QString& type, const QList& values ); TrackContext( const TrackContext& that ); ~TrackContext(); Type type() const; QList values() const; TrackContext& operator=( const TrackContext& that ); private: class TrackContextPrivate * const d; }; /** 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: 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 UnknownSource = 0, LastFmRadio, Player, MediaDevice, NonPersonalisedBroadcast, // eg Shoutcast, BBC Radio 1, etc. PersonalisedRecommendation // eg Pandora, but not Last.fm }; enum LoveStatus { UnknownLoveStatus = 0, Loved, Unloved }; 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, Invalid = 300 }; Track(); explicit Track( const QDomElement& ); Track( const Track& that ); ~Track(); Track clone() const; /** 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 ); bool operator==( const Track& that ) const; bool operator!=( const Track& that ) const; Track& operator=( const Track& that ); /** * Track's private class emits three signals that may be useful for * applications: * * loveToggled( bool love ) * scrobbleStatusChanged( short scrobbleStatus ) * corrected( QString correction ) * * signalProxy() lets applications connect to them. * */ const QObject* signalProxy() const; /** only a Track() is null */ bool isNull() const; 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; uint duration() const; // in seconds Mbid mbid() const; QUrl url() const; QDateTime timestamp() const; Source source() const; uint fingerprintId() const; bool isLoved() const; LoveStatus loveStatus() const; QUrl imageUrl( ImageSize size, bool square ) const; QString durationString() const; static QString durationString( int seconds ); ScrobbleStatus scrobbleStatus() const; ScrobbleError scrobbleError() const; QString scrobbleErrorText() const; /** default separator is an en-dash */ QString toString() const; QString toString( Corrections corrections ) const; QString toString( const QChar& separator, Corrections corrections = Original ) const; /** the standard representation of this object as an XML node */ QDomElement toDomElement( class QDomDocument& ) const; TrackContext context() const; // iTunes tracks might be podcasts or videos bool isPodcast() const; bool isVideo() const; QString extra( const QString& key ) const; bool operator<( const Track &that ) const; bool isMp3() const; operator QVariant() const; //////////// lastfm::Ws /** See last.fm/api Track section */ QNetworkReply* share( const QStringList& recipients, const QString& message = "", bool isPublic = true ) const; QNetworkReply* getSimilar( int limit = -1 ) 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* ); /** you can get any QNetworkReply TagList using Tag::list( QNetworkReply* ) */ QNetworkReply* getTags() const; // for the logged in user QNetworkReply* getTopTags() const; QNetworkReply* getTopFans() const; /** method should be a method name of reciever that takes a QByteArray If that fails it will try invoking method with no arguments. */ void getInfo( QObject* receiver, const char * method, const QString& username = "" ) const; QNetworkReply* getBuyLinks( const QString& country ) const; static QNetworkReply* playlinks( const QList& tracks ); /** 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* updateNowPlaying( int duration ) const; QNetworkReply* removeNowPlaying() 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; }; /** 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(); /** 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 ); void setFromLfm( const XmlQuery& lfm ); void setImageUrl( ImageSize size, const QString& url ); void setArtist( QString artist ); void setAlbumArtist( QString albumArtist ); void setAlbum( QString album ); void setTitle( QString title ); void setCorrections( QString title, QString album, QString artist, QString albumArtist ); void setTrackNumber( uint n ); void setDuration( uint duration ); void setUrl( QUrl url ); void setSource( Source s ); void setLoved( bool loved ); void setMbid( Mbid id ); void setFingerprintId( uint id ); void setScrobbleStatus( ScrobbleStatus scrobbleStatus ); void setScrobbleError( ScrobbleError scrobbleError ); void setScrobbleErrorText( const QString& scrobbleErrorText ); void love(); void unlove(); QNetworkReply* ban(); void stamp(); void setExtra( const QString& key, const QString& value ); void removeExtra( QString key ); void setTimeStamp( const QDateTime& dt ); void setContext( TrackContext context ); // iTunes tracks might be podcasts or videos void setPodcast( bool podcast ); void setVideo( bool video ); }; } //namespace lastfm LASTFM_DLLEXPORT QDebug operator<<( QDebug d, const lastfm::Track& t ); Q_DECLARE_METATYPE( lastfm::Track ) #endif //LASTFM_TRACK_H liblastfm-1.1.0/src/Url.cpp000066400000000000000000000035061246517024400155340ustar00rootroot00000000000000/* 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 "Url.h" #if QT_VERSION >= QT_VERSION_CHECK( 5, 0, 0 ) #include #endif class lastfm::UrlPrivate { public: UrlPrivate( const QUrl& url ); QUrl url; #if QT_VERSION >= QT_VERSION_CHECK( 5, 0, 0 ) QUrlQuery query; #endif }; lastfm::UrlPrivate::UrlPrivate( const QUrl& u ) : url( u ) #if QT_VERSION >= QT_VERSION_CHECK( 5, 0, 0 ) , query( u.query() ) #endif { } lastfm::Url::Url( const QUrl& url ) :d( new UrlPrivate( url ) ) { } lastfm::Url::~Url() { delete d; } void lastfm::Url::addQueryItem( const QString& key, const QString& value ) { #if QT_VERSION >= QT_VERSION_CHECK( 5, 0, 0 ) d->query.addQueryItem( key, value ); d->url.setQuery( d->query ); #else d->url.addQueryItem( key, value ); #endif } QUrl lastfm::Url::operator()() { return url(); } lastfm::Url& lastfm::Url::operator=( const lastfm::Url& that ) { d->url = that.d->url; #if QT_VERSION >= QT_VERSION_CHECK( 5, 0, 0 ) d->query = that.d->query; #endif return *this; } QUrl lastfm::Url::url() const { return d->url; } liblastfm-1.1.0/src/Url.h000066400000000000000000000022651246517024400152020ustar00rootroot00000000000000/* 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 . */ #pragma once #include "global.h" #include namespace lastfm { class LASTFM_DLLEXPORT Url { public: explicit Url( const QUrl& url ); ~Url(); void addQueryItem( const QString& key, const QString& value ); QUrl url() const; QUrl operator()(); Url& operator=( const Url& that ); private: class UrlPrivate * const d; }; } liblastfm-1.1.0/src/UrlBuilder.cpp000066400000000000000000000075311246517024400170450ustar00rootroot00000000000000/* 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 class lastfm::UrlBuilderPrivate { public: QByteArray path; }; lastfm::UrlBuilder::UrlBuilder( const QString& base ) : d( new UrlBuilderPrivate ) { d->path = '/' + base.toLatin1(); } lastfm::UrlBuilder::UrlBuilder( const UrlBuilder& that ) : d( new UrlBuilderPrivate( *that.d ) ) { } lastfm::UrlBuilder::~UrlBuilder() { delete d; } lastfm::UrlBuilder& lastfm::UrlBuilder::slash( const QString& path ) { this->d->path += '/' + encode( path ); return *this; } QUrl lastfm::UrlBuilder::url() const { QUrl url; url.setScheme( "http" ); url.setHost( host() ); #if QT_VERSION >= QT_VERSION_CHECK( 5, 0, 0 ) url.setPath( d->path ); #else url.setEncodedPath( d->path ); #endif 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"; } } bool // static lastfm::UrlBuilder::isHost( const QUrl& url ) { QStringList hosts = QStringList() << "www.lastfm.com.br" << "www.lastfm.com.tr" << "www.lastfm.fr" << "www.lastfm.it" << "www.lastfm.de" << "www.lastfm.es" << "www.lastfm.pl" << "www.lastfm.ru" << "www.lastfm.jp" << "www.lastfm.se" << "cn.last.fm" << "www.last.fm"; return hosts.contains( url.host() ); } 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; } lastfm::UrlBuilder& lastfm::UrlBuilder::operator=( const UrlBuilder& that ) { d->path = that.d->path; return *this; } liblastfm-1.1.0/src/UrlBuilder.h000066400000000000000000000046771246517024400165220ustar00rootroot00000000000000/* 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 "global.h" #include #include namespace lastfm { /** For building www.last.fm urls. We have special rules for encoding and that */ class LASTFM_DLLEXPORT UrlBuilder { 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 ); UrlBuilder& slash( const QString& path ); UrlBuilder( const UrlBuilder& that ); ~UrlBuilder(); 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() ); /** return true if url is a last.fm url */ static bool isHost( const QUrl& url ); UrlBuilder& operator=( const UrlBuilder& that ); private: class UrlBuilderPrivate * const d; }; } #endif liblastfm-1.1.0/src/User.cpp000066400000000000000000000305661246517024400157160ustar00rootroot00000000000000/* 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 "Artist.h" #include "UrlBuilder.h" #include "XmlQuery.h" #include #include #include using lastfm::Gender; using lastfm::User; using lastfm::UserList; using lastfm::XmlQuery; class lastfm::UserListPrivate { public: UserListPrivate() : total( 0 ), page( 0 ), perPage( 0 ), totalPages( 0 ) {} int total; int page; int perPage; int totalPages; QList users; }; lastfm::UserList::UserList() : d( new UserListPrivate ) { } lastfm::UserList::UserList( const XmlQuery &lfm ) : d( new UserListPrivate ) { if ( lfm.parseError().enumValue() == lastfm::ws::NoError ) { foreach (XmlQuery e, lfm.children( "user" )) { User u( e ); d->users.append( u ); } d->total = lfm["friends"].attribute("total").toInt(); d->page = lfm["friends"].attribute("page").toInt(); d->perPage = lfm["friends"].attribute("perPage").toInt(); d->totalPages = lfm["friends"].attribute("totalPages").toInt(); } else { qDebug() << lfm.parseError().message() << lfm.parseError().enumValue(); } } lastfm::UserList::~UserList() { delete d; } lastfm::UserList::UserList( const UserList& other ) : d( new UserListPrivate( *other.d ) ) { } QList lastfm::UserList::users() { return d->users; } lastfm::UserList& lastfm::UserList::operator=( const UserList& other ) { d->total = other.d->total; d->page = other.d->page; d->perPage = other.d->perPage; d->totalPages = other.d->totalPages; d->users = other.d->users; return *this; } int lastfm::UserList::totalUsers() { return d->total; } int lastfm::UserList::currentPage() { return d->page; } int lastfm::UserList::usersPerPage() { return d->perPage; } int lastfm::UserList::totalPages() { return d->totalPages; } class lastfm::Gender::GenderPrivate { public: QString s; }; Gender::Gender() :d( new GenderPrivate ) { } Gender::Gender( const Gender& other ) :d( new GenderPrivate( *other.d ) ) { } Gender::Gender( const QString& ss ) :d( new GenderPrivate ) { d->s = ss.toLower(); } Gender::~Gender() { delete d; } bool Gender::known() const { return male() || female(); } bool Gender::male() const { return d->s == "m"; } bool Gender::female() const { return d->s == "f"; } QString Gender::toString() const { QString result; if (male()) result = "Male"; else if (female()) result = "Female"; else result = "Neuter"; return result; } Gender& Gender::operator=( const lastfm::Gender& that ) { d->s = that.d->s; return *this; } class lastfm::User::UserPrivate { public: UserPrivate() : m_name( lastfm::ws::Username ) , m_match( -1.0f ) , m_age( 0 ) , m_scrobbles( 0 ) , m_registered( QDateTime() ) , m_isSubscriber( false ) , m_canBootstrap( false ) {} QString m_name; Type m_type; QList m_images; float m_match; QString m_realName; Gender m_gender; unsigned short m_age; unsigned int m_scrobbles; QDateTime m_registered; QString m_country; bool m_isSubscriber; bool m_canBootstrap; }; User::User( const XmlQuery& xml ) :AbstractType(), d( new UserPrivate ) { d->m_name = xml["name"].text(); d->m_images << xml["image size=small"].text() << xml["image size=medium"].text() << xml["image size=large"].text(); d->m_realName = xml["realname"].text(); QString type = xml["type"].text(); if ( type == "subscriber" ) d->m_type = TypeSubscriber; else if ( type == "moderator" ) d->m_type = TypeModerator; else if ( type == "staff" ) d->m_type = TypeStaff; else if ( type == "alumni" ) d->m_type = TypeAlumni; else d->m_type = TypeUser; d->m_age = xml["age"].text().toUInt(); d->m_scrobbles = xml["playcount"].text().toUInt(); d->m_registered = QDateTime::fromTime_t(xml["registered"].attribute("unixtime").toUInt()); d->m_country = xml["country"].text(); d->m_isSubscriber = ( xml["subscriber"].text() == "1" ); d->m_canBootstrap = ( xml["bootstrap"].text() == "1" ); d->m_gender = xml["gender"].text(); d->m_images << xml["image size=small"].text() << xml["image size=medium"].text() << xml["image size=large"].text() << xml["image size=extralarge"].text(); } User::User( const User& other ) :AbstractType(), d( new UserPrivate( *other.d ) ) { } User::User() :AbstractType(), d( new UserPrivate ) { } User::User( const QString& name ) :AbstractType(), d( new UserPrivate ) { d->m_name = name; d->m_match = -1.0f; d->m_age = 0; d->m_scrobbles = 0; d->m_registered = QDateTime(); d->m_isSubscriber = false; d->m_canBootstrap = false; } User::~User() { delete d; } lastfm::User& User::operator=( const User& that ) { d->m_name = that.name(); d->m_images = that.d->m_images; d->m_realName = that.d->m_realName; d->m_match = that.d->m_match; d->m_type = that.d->m_type; d->m_age = that.d->m_age; d->m_scrobbles = that.d->m_scrobbles; d->m_registered = that.d->m_registered; d->m_country = that.d->m_country; d->m_isSubscriber = that.d->m_isSubscriber; d->m_canBootstrap = that.d->m_canBootstrap; d->m_gender = that.d->m_gender; d->m_images = that.d->m_images; return *this; } bool User::operator==(const lastfm::User& that) const { return d->m_name == that.d->m_name; } bool User::operator<(const lastfm::User& that) const { return d->m_name < that.d->m_name; } User::operator QString() const { return d->m_name; } QString User::name() const { return d->m_name; } void User::setName( const QString& name ) { d->m_name = name; } User::Type User::type() const { return d->m_type; } void User::setType( Type type ) { d->m_type = type; } QUrl User::imageUrl( 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/" )); } QMap User::params(const QString& method) const { QMap map; map["method"] = "user."+method; map["user"] = d->m_name; return map; } QNetworkReply* User::getFriends( bool recentTracks, int limit, int page ) const { QMap map = params( "getFriends" ); map["limit"] = QString::number( limit ); map["page"] = QString::number( page ); if ( recentTracks ) map["recenttracks"] = "1"; return ws::get( map ); } QNetworkReply* User::getFriendsListeningNow( int limit, int page ) const { QMap map = params( "getFriendsListeningNow" ); map["limit"] = QString::number( limit ); map["page"] = QString::number( page ); return ws::get( map ); } QNetworkReply* User::getFriendsThatListenTo( const lastfm::Artist& artist, int limit, int page ) const { QMap map = params( "getFriendsThatListenTo" ); map["artist"] = artist.name(); map["limit"] = QString::number( limit ); map["page"] = QString::number( page ); return ws::get( map ); } QNetworkReply* User::getLovedTracks( int limit, int page ) const { QMap map = params( "getLovedTracks" ); map["limit"] = QString::number( limit ); map["page"] = QString::number( page ); return ws::get( map ); } QNetworkReply* User::getTopTags() const { return ws::get( params( "getTopTags" ) ); } QNetworkReply* User::getTopArtists( QString period, int limit, int page ) const { QMap map = params( "getTopArtists" ); map["period"] = period; map["limit"] = QString::number( limit ); map["page"] = QString::number( page ); return ws::get( map ); } QNetworkReply* User::getRecentArtists() const { return ws::get( params( "getRecentArtists" ) ); } QNetworkReply* User::getRecentTracks( int limit , int page ) const { QMap map = params( "getRecentTracks" ); map["limit"] = QString::number( limit ); map["page"] = QString::number( page ); map["extended"] = "true"; return ws::get( map ); } QNetworkReply* User::getRecentStations( int limit, int page ) const { QMap map = params( "getRecentStations" ); map["limit"] = QString::number( limit ); map["page"] = QString::number( page ); return ws::get( map ); } QNetworkReply* User::getRecommendedArtists( int limit, int page ) const { QMap map = params( "getRecommendedArtists" ); map["limit"] = QString::number( limit ); map["page"] = QString::number( page ); return ws::get( map ); } QNetworkReply* User::getNeighbours( int limit, int page ) const { QMap map = params( "getNeighbours" ); map["limit"] = QString::number( limit ); map["page"] = QString::number( page ); return ws::get( map ); } QNetworkReply* User::getPlaylists() const { return ws::get( params( "getPlaylists" ) ); } UserList //static User::list( QNetworkReply* r ) { XmlQuery lfm; lfm.parse( r ); return UserList( lfm ); } QString User::toString() const { return name(); } QDomElement User::toDomElement( QDomDocument& ) const { return QDomElement(); } QNetworkReply* //static User::getInfo( const QString& username ) { QMap map; map["method"] = "user.getInfo"; map["user"] = username; return ws::post( map ); } /* QNetworkReply* //static User::getRecommendedArtists() { QMap map; map["method"] = "user.getRecommendedArtists"; return ws::post( map ); } */ QUrl User::www() const { return UrlBuilder( "user" ).slash( d->m_name ).url(); } float User::match() const { return d->m_match; } QString User::getInfoString() const { QString text; text = QString("%1").arg( d->m_realName.isEmpty() ? d->m_name : d->m_realName ); if ( d->m_age ) text.append( QString(", %1").arg( d->m_age ) ); if ( d->m_gender.known() ) text.append( QString(", %1").arg( d->m_gender.toString() ) ); if ( !d->m_country.isEmpty() ) text.append( QString(", %1").arg( d->m_country ) ); return text; } quint32 User::scrobbleCount() const { return d->m_scrobbles; } void User::setScrobbleCount( quint32 scrobbleCount ) { d->m_scrobbles = scrobbleCount; } QDateTime User::dateRegistered() const { return d->m_registered; } void User::setDateRegistered( const QDateTime& date ) { d->m_registered = date; } Gender User::gender() const { return d->m_gender; } QString User::country() const { return d->m_country; } QString User::realName() const { return d->m_realName; } void User::setRealName( const QString& realName ) { d->m_realName = realName; } void User::setImages( const QList& images ) { d->m_images = images; } unsigned short User::age() const { return d->m_age; } void User::setAge( unsigned short age ) { d->m_age = age; } bool User::isSubscriber() const { return d->m_isSubscriber; } void User::setIsSubscriber( bool subscriber ) { d->m_isSubscriber = subscriber; } bool User::canBootstrap() const { return d->m_canBootstrap; } void User::setCanBootstrap( bool canBootstrap ) { d->m_canBootstrap = canBootstrap; } void User::setGender( const QString& s ) { d->m_gender = Gender( s ); } void User::setCountry( const QString& country ) { d->m_country = country; } liblastfm-1.1.0/src/User.h000066400000000000000000000123731246517024400153570ustar00rootroot00000000000000/* 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 "AbstractType.h" #include "ws.h" namespace lastfm { class UserList; class XmlQuery; class Artist; class LASTFM_DLLEXPORT Gender { public: Gender(); Gender( const Gender& gender ); Gender( const QString& ss ); ~Gender(); bool known() const; bool male() const; bool female() const; QString toString() const; lastfm::Gender& operator=( const lastfm::Gender& that ); private: class GenderPrivate; GenderPrivate * const d; }; class LASTFM_DLLEXPORT User : public AbstractType { public: enum Type { TypeUser, TypeSubscriber, TypeModerator, TypeStaff, TypeAlumni }; public: User(); User( const QString& name ); User( const XmlQuery& xml ); User( const User& user ); ~User(); lastfm::User& operator=( const lastfm::User& that ); bool operator==(const lastfm::User& that) const; bool operator<(const lastfm::User& that) const; operator QString() const; QString name() const; void setName( const QString& name ); Type type() const; void setType( Type type ); bool isSubscriber() const; void setIsSubscriber( bool subscriber ); bool canBootstrap() const; void setCanBootstrap( bool canBootstrap ); quint32 scrobbleCount() const; void setScrobbleCount( quint32 scrobblesCount ); QDateTime dateRegistered() const; void setDateRegistered( const QDateTime& date ); Gender gender() const; QString country() const; QString realName() const; void setRealName( const QString& realName ); QUrl imageUrl( ImageSize size = LargeImage, bool square = false ) const; void setImages( const QList& images ); unsigned short age() const; void setAge( unsigned short age ); void setGender( const QString& s ); void setCountry( const QString& country ); /** use Tag::list() on the response to get a WeightedStringList */ QNetworkReply* getTopTags() const; /** use User::list() on the response to get a UserList */ QNetworkReply* getFriends( bool recentTracks = false, int limit = 50, int page = 1 ) const; QNetworkReply* getFriendsListeningNow( int limit = 50, int page = 1 ) const; QNetworkReply* getFriendsThatListenTo( const lastfm::Artist& artist, int limit = 50, int page = 1 ) const; QNetworkReply* getNeighbours( int limit = 50, int page = 1 ) const; QNetworkReply* getLovedTracks( int limit = 50, int page = 1 ) const; QNetworkReply* getPlaylists() const; QNetworkReply* getTopArtists( QString period = "overall", int limit = 50, int page = 1 ) const; QNetworkReply* getRecentTracks( int limit = 50, int page = 1 ) const; QNetworkReply* getRecentArtists() const; QNetworkReply* getRecentStations( int limit = 10, int page = 1 ) const; QNetworkReply* getRecommendedArtists( int limit = 50, int page = 1 ) const; /** 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; static UserList list( QNetworkReply* ); QString toString() const; QDomElement toDomElement( QDomDocument& ) const; /** 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; protected: QMap params( const QString& method ) const; protected: class UserPrivate; UserPrivate * const d; }; class LASTFM_DLLEXPORT UserList { public: UserList(); UserList( const XmlQuery& query ); UserList( const UserList& other ); ~UserList(); UserList& operator=( const UserList& other ); int totalUsers(); int totalPages(); int currentPage(); int usersPerPage(); QList users(); private: class UserListPrivate * const d; }; } #endif liblastfm-1.1.0/src/XmlQuery.cpp000066400000000000000000000123461246517024400165620ustar00rootroot00000000000000/* 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 #include #include #include using lastfm::XmlQuery; class lastfm::XmlQueryPrivate { public: XmlQueryPrivate(); QDomDocument domdoc; QDomElement e; lastfm::ws::ParseError error; }; lastfm::XmlQueryPrivate::XmlQueryPrivate() : error( lastfm::ws::ParseError( lastfm::ws::NoError, "" ) ) { } XmlQuery::XmlQuery() : d( new XmlQueryPrivate ) { } XmlQuery::XmlQuery( const XmlQuery& that ) : d( new XmlQueryPrivate( *that.d ) ) { } XmlQuery::XmlQuery( const QDomElement& e, const char* name ) : d( new XmlQueryPrivate ) { d->e = e; if (e.isNull()) qWarning() << "Expected node absent:" << name; } XmlQuery::~XmlQuery() { delete d; } bool XmlQuery::parse( const QByteArray& bytes ) { if ( !bytes.size() ) d->error = lastfm::ws::ParseError( lastfm::ws::MalformedResponse, "No data" ); else { if( !d->domdoc.setContent( bytes ) ) d->error = lastfm::ws::ParseError( lastfm::ws::MalformedResponse, "Invalid XML" ); else { d->e = d->domdoc.documentElement(); if (d->e.isNull()) d->error = lastfm::ws::ParseError( lastfm::ws::MalformedResponse, "Lfm is null" ); else { QString const status = d->e.attribute( "status" ); QDomElement error = d->e.firstChildElement( "error" ); uint const n = d->e.childNodes().count(); // no elements beyond the lfm is perfectably acceptable for example when // XmlQuery is used parse response of a POST request. // if (n == 0) // nothing useful in the response if (status == "failed" || (n == 1 && !error.isNull()) ) d->error = error.isNull() ? lastfm::ws::ParseError( lastfm::ws::MalformedResponse, "" ) : lastfm::ws::ParseError( lastfm::ws::Error( error.attribute( "code" ).toUInt() ), error.text() ); } } } if ( d->error.enumValue() != lastfm::ws::NoError ) { qDebug() << bytes; switch ( d->error.enumValue() ) { case lastfm::ws::OperationFailed: case lastfm::ws::InvalidApiKey: case lastfm::ws::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, d->error.enumValue() ) ); break; default: //do nothing break; } } return d->error.enumValue() == lastfm::ws::NoError; } bool XmlQuery::parse( QNetworkReply* reply ) { reply->deleteLater(); return parse( reply->readAll() ); } lastfm::ws::ParseError XmlQuery::parseError() const { return d->error; } QString XmlQuery::text() const { return d->e.text(); } QString XmlQuery::attribute( const QString& name ) const { return d->e.attribute( name ); } 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.d->e.attribute( attributeName ) == attributeValue) return e; } XmlQuery xq( d->e.firstChildElement( name ), name.toUtf8().data() ); xq.d->domdoc = this->d->domdoc; return xq; } QList XmlQuery::children( const QString& named ) const { QList elements; QDomNodeList nodes = d->e.elementsByTagName( named ); for (int x = 0; x < nodes.count(); ++x) { XmlQuery xq( nodes.at( x ).toElement() ); xq.d->domdoc = this->d->domdoc; elements += xq; } return elements; } XmlQuery::operator QDomElement() const { return d->e; } XmlQuery& XmlQuery::operator=( const XmlQuery& that ) { d->domdoc = that.d->domdoc; d->e = that.d->e; d->error = that.d->error; return *this; } QDebug operator<<( QDebug d, const lastfm::XmlQuery& xq ) { QString s; QTextStream t( &s, QIODevice::WriteOnly ); QDomElement(xq).save( t, 2 ); return d << s; } liblastfm-1.1.0/src/XmlQuery.h000066400000000000000000000061001246517024400162160ustar00rootroot00000000000000/* 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 "ws.h" #include namespace lastfm { /** Qt's XmlQuery implementation is totally unimpressive, so this is a * hack that feels like jQuery */ class LASTFM_DLLEXPORT XmlQuery { 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(); XmlQuery( const XmlQuery& that ); ~XmlQuery(); /** * Fills in the XmlQuery response by parsing raw reply @param data from the * webservice. * * @return true if successfully parsed and the response does not signify an error, * false otherwise. When false is returned, parseError() contains the error. */ bool parse( const QByteArray& data ); /** * Convenience parse() overload that takes data from the @param reply and calls * deleteLater() on it. * * @return true if successfully parsed and the response does not signify an error, * false otherwise. When false is returned, parseError() contains the error. */ bool parse( QNetworkReply* reply ); ws::ParseError parseError() const; XmlQuery( const QDomElement& e, const char* 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; QString attribute( const QString& name ) const; /** selects all children with specified name, recursively */ QList children( const QString& named ) const; operator QDomElement() const; XmlQuery& operator=( const XmlQuery& that ); private: class XmlQueryPrivate * const d; }; } LASTFM_DLLEXPORT QDebug operator<<( QDebug d, const lastfm::XmlQuery& xq ); #endif liblastfm-1.1.0/src/Xspf.cpp000066400000000000000000000062551246517024400157160ustar00rootroot00000000000000/* 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 "XmlQuery.h" #include #include class lastfm::XspfPrivate { public: QList tracks; QString title; }; lastfm::Xspf::Xspf( const QDomElement& playlist_node, QObject* parent ) :QObject( parent ) , d( new XspfPrivate ) { XmlQuery e( playlist_node ); int expirySeconds = e["link rel=http://www.last.fm/expiry"].text().toInt(); QTimer::singleShot( expirySeconds * 1000, this, SLOT(onExpired())); d->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: d->title.replace( '+', ' ' ); d->title = QUrl::fromPercentEncoding( d->title.toUtf8() ); d->title = d->title.trimmed(); foreach (XmlQuery e, e["trackList"].children( "track" )) { MutableTrack t; t.setUrl( e["location"].text() ); t.setImageUrl( AbstractType::LargeImage, e["image"].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" ); t.setSource( Track::LastFmRadio ); t.setExtra( "expiry", QString::number( QDateTime::currentDateTime().addSecs( expirySeconds ).toTime_t() ) ); t.setExtra( "playlistTitle", d->title ); QList contexts; QDomNodeList contextsNodeList = QDomElement(e["extension"]["context"]).childNodes(); for ( int i = 0 ; i < contextsNodeList.count() ; ++i ) contexts.append( contextsNodeList.item(i).toElement().text() ); if ( contexts.count() > 0 ) t.setContext( TrackContext( contextsNodeList.item(0).toElement().tagName(), contexts ) ); d->tracks << t; // outside try block since location is enough basically } } lastfm::Xspf::~Xspf() { delete d; } QString lastfm::Xspf::title() const { return d->title; } bool lastfm::Xspf::isEmpty() const { return d->tracks.isEmpty(); } lastfm::Track lastfm::Xspf::takeFirst() { return d->tracks.takeFirst(); } QList lastfm::Xspf::tracks() const { return d->tracks; } void lastfm::Xspf::onExpired() { emit expired(); } liblastfm-1.1.0/src/Xspf.h000066400000000000000000000025741246517024400153630ustar00rootroot00000000000000/* 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 "Track.h" #include namespace lastfm { class LASTFM_DLLEXPORT Xspf : public QObject { Q_OBJECT public: /** pass in the playlist node! */ Xspf( const QDomElement& playlist_node, QObject* parent ); ~Xspf(); QString title() const; bool isEmpty() const; Track takeFirst(); QList tracks() const; signals: Q_DECL_DEPRECATED void expired(); private slots: Q_DECL_DEPRECATED void onExpired(); private: class XspfPrivate * const d; }; } #endif liblastfm-1.1.0/src/fingerprint/000077500000000000000000000000001246517024400166115ustar00rootroot00000000000000liblastfm-1.1.0/src/fingerprint/CMakeLists.txt000066400000000000000000000027431246517024400213570ustar00rootroot00000000000000 #find_package(Qt4 COMPONENTS QtCore QtSql REQUIRED) #FIXME: find modules are horrible, port them to find_package_handle_standard_args find_package(LibSamplerate REQUIRED) find_package(LibFFTW3 REQUIRED) include_directories(${LIBFFTW3_INCLUDE_DIR}) include_directories(${LIBSAMPLERATE_INCLUDE_DIR}) include_directories(${QT_INCLUDES}) include_directories(${CMAKE_CURRENT_LIST_DIR}/..) include_directories(${CMAKE_CURRENT_BINARY_DIR}/..) set(lastfm_fingerprint_SOURCES Collection.cpp Fingerprint.cpp Sha256.cpp fplib/Filter.cpp fplib/FingerprintExtractor.cpp fplib/OptFFT.cpp ) set(lastfm_fingerprint_HEADERS Fingerprint.h FingerprintableSource.h ) add_library(${FINGERPRINT_LIB_TARGET_NAME} SHARED ${lastfm_fingerprint_SOURCES}) target_link_libraries(${FINGERPRINT_LIB_TARGET_NAME} ${LASTFM_LIB_TARGET_NAME} ${QT_QTSQL_LIBRARY} ${QT_QTCORE_LIBRARY} ${LIBSAMPLERATE_LIBRARY} ${LIBFFTW3_LIBRARY} ) set_target_properties(${FINGERPRINT_LIB_TARGET_NAME} PROPERTIES COMPILE_DEFINITIONS LASTFM_FINGERPRINT_LIB VERSION ${LASTFM_VERSION_STRING} SOVERSION ${LASTFM_SOVERSION} ) qt5_use_modules(${FINGERPRINT_LIB_TARGET_NAME} Network Sql Xml) install(TARGETS ${FINGERPRINT_LIB_TARGET_NAME} RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR} ) install(FILES ${lastfm_fingerprint_HEADERS} DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/lastfm${LASTFM_LIB_VERSION_SUFFIX}/) liblastfm-1.1.0/src/fingerprint/Collection.cpp000066400000000000000000000200561246517024400214130ustar00rootroot00000000000000/* 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 "misc.h" #include #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; } liblastfm-1.1.0/src/fingerprint/Collection.h000066400000000000000000000031631246517024400210600ustar00rootroot00000000000000/* 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 liblastfm-1.1.0/src/fingerprint/EXAMPLE.cpp000066400000000000000000000044351246517024400204160ustar00rootroot00000000000000/* 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 } } liblastfm-1.1.0/src/fingerprint/Fingerprint.cpp000066400000000000000000000242551246517024400216140ustar00rootroot00000000000000/* 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.h" #include "Url.h" #include #include #include #include #include #include #if QT_VERSION >= QT_VERSION_CHECK( 5, 0, 0 ) #include #endif #include #include "User.h" using lastfm::Track; static const uint k_bufferSize = 1024 * 8; static const int k_minTrackDuration = 30; class lastfm::FingerprintPrivate { public: FingerprintPrivate( const Track& t ) : m_track( t ) , m_id( -1 ) , m_duration( 0 ) , m_complete( false ) { } lastfm::Track m_track; QByteArray m_data; int m_id; int m_duration; bool m_complete; }; lastfm::Fingerprint::Fingerprint( const Track& t ) : d( new FingerprintPrivate( t ) ) { QString id = Collection::instance().getFingerprintId( t.url().toLocalFile() ); if (id.size()) { bool b; d->m_id = id.toInt( &b ); if (!b) d->m_id = -1; } } lastfm::Fingerprint::~Fingerprint() { delete d; } lastfm::FingerprintId lastfm::Fingerprint::id() const { return d->m_id; } QByteArray lastfm::Fingerprint::data() const { return d->m_data; } 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( d->m_track.url().toLocalFile() ); ms->getInfo( d->m_duration, sampleRate, bitrate, numChannels ); } catch (std::exception& e) { qWarning() << e.what(); throw HeadersError; } if (d->m_duration < k_minTrackDuration) throw TrackTooShortError; ms->skipSilence(); bool fpDone = false; fingerprint::FingerprintExtractor* extractor; try { extractor = new fingerprint::FingerprintExtractor; if (d->m_complete) { extractor->initForFullSubmit( sampleRate, numChannels ); } else { extractor->initForQuery( sampleRate, numChannels, d->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(); delete extractor; 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[] pPCMBuffer; delete extractor; throw InternalError; } } delete[] pPCMBuffer; if (!fpDone) { delete extractor; throw InternalError; } // We succeeded std::pair fpData = extractor->getFingerprint(); if (fpData.first == NULL || fpData.second == 0) { delete extractor; throw InternalError; } // Make a deep copy before extractor gets deleted d->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 (d->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 = d->m_track; QString const path = t.url().toLocalFile(); QFileInfo const fi( path ); lastfm::Url url( QUrl( "http://ws.audioscrobbler.com/fingerprint/query/" ) ); url.addQueryItem( "username", lastfm::User().name() ); url.addQueryItem( "artist", t.artist() ); url.addQueryItem( "album", t.album() ); url.addQueryItem( "track", t.title() ); url.addQueryItem( "duration", number( d->m_duration > 0 ? d->m_duration : t.duration() ) ); url.addQueryItem( "mbid", t.mbid() ); url.addQueryItem( "filename", fi.completeBaseName() ); url.addQueryItem( "fileextension", fi.completeSuffix() ); url.addQueryItem( "tracknum", number( t.trackNumber() ) ); url.addQueryItem( "sha256", sha256( path ) ); url.addQueryItem( "time", number(QDateTime::currentDateTime().toTime_t()) ); url.addQueryItem( "fpversion", QByteArray::number((int)fingerprint::FingerprintExtractor::getVersion()) ); url.addQueryItem( "fulldump", d->m_complete ? "true" : "false" ); url.addQueryItem( "noupdate", "false" ); //FIXME: talk to mir about submitting fplibversion QNetworkRequest request( url.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 += d->m_data; bytes += "\r\n"; bytes += "------------------------------8e61d618ca16--\r\n"; qDebug() << url.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. reply->deleteLater(); 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( d->m_track.url().toLocalFile(), fpid ); if (complete_fingerprint_requested) *complete_fingerprint_requested = (status == "NEW"); d->m_id = (int)fpid_as_uint; return; } bad_response: qWarning() << "Response is bad:" << response; throw BadResponseError; } lastfm::CompleteFingerprint::CompleteFingerprint( const lastfm::Track& t ) : Fingerprint( t ) { d->m_complete = true; } lastfm::CompleteFingerprint::~CompleteFingerprint() { } 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 return d; } liblastfm-1.1.0/src/fingerprint/Fingerprint.h000066400000000000000000000064711246517024400212610ustar00rootroot00000000000000/* 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 "global.h" #include "FingerprintId.h" namespace lastfm { class FingerprintableSource; class Track; class LASTFM_FINGERPRINT_DLLEXPORT Fingerprint { protected: class FingerprintPrivate * const d; 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& ); ~Fingerprint(); /** if the id isNull(), then you'll need to do generate, submit and decode */ FingerprintId id() const; /** 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; 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 LASTFM_FINGERPRINT_DLLEXPORT CompleteFingerprint : public Fingerprint { CompleteFingerprint( const lastfm::Track& t ); ~CompleteFingerprint(); }; } QDebug LASTFM_FINGERPRINT_DLLEXPORT operator<<( QDebug d, lastfm::Fingerprint::Error e ); #endif liblastfm-1.1.0/src/fingerprint/FingerprintableSource.h000066400000000000000000000031671246517024400232650ustar00rootroot00000000000000/* 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 "global.h" #include namespace lastfm { class LASTFM_FINGERPRINT_DLLEXPORT FingerprintableSource { public: virtual ~FingerprintableSource() {} /** 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 liblastfm-1.1.0/src/fingerprint/Sha256.cpp000066400000000000000000000303421246517024400202670ustar00rootroot00000000000000/*- * 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 */ liblastfm-1.1.0/src/fingerprint/Sha256.h000066400000000000000000000113541246517024400177360ustar00rootroot00000000000000/*- * 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 */ liblastfm-1.1.0/src/fingerprint/contrib/000077500000000000000000000000001246517024400202515ustar00rootroot00000000000000liblastfm-1.1.0/src/fingerprint/contrib/AacSource.cpp000066400000000000000000000623571246517024400226370ustar00rootroot00000000000000/* 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); } liblastfm-1.1.0/src/fingerprint/contrib/AacSource.h000066400000000000000000000026361246517024400222760ustar00rootroot00000000000000/* 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 liblastfm-1.1.0/src/fingerprint/contrib/AacSource_p.h000066400000000000000000000056771246517024400226250ustar00rootroot00000000000000/* 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; }; liblastfm-1.1.0/src/fingerprint/contrib/FlacSource.cpp000066400000000000000000000256331246517024400230140ustar00rootroot00000000000000/* 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); } // ----------------------------------------------------------------------------- liblastfm-1.1.0/src/fingerprint/contrib/FlacSource.h000066400000000000000000000047651246517024400224640ustar00rootroot00000000000000/* 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 liblastfm-1.1.0/src/fingerprint/contrib/MadSource.cpp000066400000000000000000000343141246517024400226440ustar00rootroot00000000000000/* 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); } // ----------------------------------------------------------------------------- liblastfm-1.1.0/src/fingerprint/contrib/MadSource.h000066400000000000000000000041461246517024400223110ustar00rootroot00000000000000/* 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 liblastfm-1.1.0/src/fingerprint/contrib/VorbisSource.cpp000066400000000000000000000140271246517024400234060ustar00rootroot00000000000000/* 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); } // ----------------------------------------------------------------------------- liblastfm-1.1.0/src/fingerprint/contrib/VorbisSource.h000066400000000000000000000027231246517024400230530ustar00rootroot00000000000000/* 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 liblastfm-1.1.0/src/fingerprint/contrib/main.cpp000066400000000000000000000113121246517024400216770ustar00rootroot00000000000000/* 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; } liblastfm-1.1.0/src/fingerprint/fplib/000077500000000000000000000000001246517024400177055ustar00rootroot00000000000000liblastfm-1.1.0/src/fingerprint/fplib/CircularArray.h000066400000000000000000000150531246517024400226250ustar00rootroot00000000000000/* 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 liblastfm-1.1.0/src/fingerprint/fplib/Filter.cpp000066400000000000000000000070611246517024400216420ustar00rootroot00000000000000/* 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 // ----------------------------------------------------------------------------- liblastfm-1.1.0/src/fingerprint/fplib/Filter.h000066400000000000000000000025641246517024400213120ustar00rootroot00000000000000/* 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 liblastfm-1.1.0/src/fingerprint/fplib/FingerprintExtractor.cpp000066400000000000000000000725351246517024400246100ustar00rootroot00000000000000/* 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 // ----------------------------------------------------------------------------- liblastfm-1.1.0/src/fingerprint/fplib/FingerprintExtractor.h000066400000000000000000000051651246517024400242500ustar00rootroot00000000000000/* 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 liblastfm-1.1.0/src/fingerprint/fplib/FloatingAverage.h000066400000000000000000000046131246517024400231200ustar00rootroot00000000000000/* 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(); const int size = m_values.size(); 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__ liblastfm-1.1.0/src/fingerprint/fplib/OptFFT.cpp000066400000000000000000000704201246517024400215160ustar00rootroot00000000000000/* 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 // ---------------------------------------------------------------------- liblastfm-1.1.0/src/fingerprint/fplib/OptFFT.h000066400000000000000000000025431246517024400211640ustar00rootroot00000000000000/* 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 liblastfm-1.1.0/src/fingerprint/fplib/fp_helper_fun.h000066400000000000000000000307601246517024400227000ustar00rootroot00000000000000/* 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 liblastfm-1.1.0/src/global.h.in000066400000000000000000000046221246517024400163040ustar00rootroot00000000000000/* 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 @LASTFM_VERSION@ #define LASTFM_VERSION_STRING "@LASTFM_VERSION_STRING@" #define LASTFM_MAJOR_VERSION @LASTFM_MAJOR_VERSION@ #define LASTFM_MINOR_VERSION @LASTFM_MINOR_VERSION@ #define LASTFM_PATCH_VERSION @LASTFM_PATCH_VERSION@ #ifndef LASTFM_LIB_STATIC #ifdef LASTFM_LIB #define LASTFM_DLLEXPORT Q_DECL_EXPORT #else #define LASTFM_DLLEXPORT Q_DECL_IMPORT #endif #ifdef LASTFM_FINGERPRINT_LIB #define LASTFM_FINGERPRINT_DLLEXPORT Q_DECL_EXPORT #else #define LASTFM_FINGERPRINT_DLLEXPORT Q_DECL_IMPORT #endif #else // LASTFM_LIB_STATIC #define LASTFM_DLLEXPORT #define LASTFM_FINGERPRINT_DLLEXPORT #endif // LASTFM_LIB_STATIC #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 ); } } #ifdef LASTFM_COLLAPSE_NAMESPACE using namespace ::lastfm; #endif #endif //LASTFM_GLOBAL_H liblastfm-1.1.0/src/linux/000077500000000000000000000000001246517024400154215ustar00rootroot00000000000000liblastfm-1.1.0/src/linux/LNetworkConnectionMonitor.h000066400000000000000000000031761246517024400227360ustar00rootroot00000000000000/* 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 "global.h" class QDBusConnection; class QDBusInterface; namespace lastfm { class LNetworkConnectionMonitor : public NetworkConnectionMonitor { Q_OBJECT typedef enum { NM_STATE_UNKNOWN = 0, NM_STATE_ASLEEP = 10, NM_STATE_DISCONNECTED = 20, NM_STATE_DISCONNECTING = 30, NM_STATE_CONNECTING = 40, NM_STATE_CONNECTED_LOCAL = 50, NM_STATE_CONNECTED_SITE = 60, NM_STATE_CONNECTED_GLOBAL = 70 } NMState; public: LNetworkConnectionMonitor( QObject* parent = 0 ); ~LNetworkConnectionMonitor(); private slots: void onStateChange( uint newState ); private: QDBusInterface* m_nmInterface; }; } #endif // LNETWORK_CONNECTION_MONITOR_H liblastfm-1.1.0/src/linux/LNetworkConnectionMonitor_linux.cpp000066400000000000000000000057261246517024400245130ustar00rootroot00000000000000/* 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 #include #define NM_DBUS_SERVICE "org.freedesktop.NetworkManager" #define NM_DBUS_PATH "/org/freedesktop/NetworkManager" #define NM_DBUS_INTERFACE "org.freedesktop.NetworkManager" lastfm::LNetworkConnectionMonitor::LNetworkConnectionMonitor( QObject* parent ) : NetworkConnectionMonitor( parent ) { m_nmInterface = new QDBusInterface( NM_DBUS_SERVICE, NM_DBUS_PATH, NM_DBUS_INTERFACE, QDBusConnection::systemBus(), this ); if ( !m_nmInterface->isValid() ) { qDebug() << "Unable to watch network state changes via D-Bus."; return; } //get current connection state QDBusReply reply = m_nmInterface->call( QDBus::AutoDetect, "state" ); if ( reply.isValid() ) { if ( reply.value() == NM_STATE_CONNECTED_GLOBAL ) { setConnected( true ); } else if ( reply.value() == NM_STATE_DISCONNECTED || reply.value() == NM_STATE_ASLEEP ) { setConnected( false ); } } else { qDebug() << "Error: " << reply.error(); } //connect network manager signals m_nmInterface->connection().connect( NM_DBUS_SERVICE, NM_DBUS_PATH, NM_DBUS_INTERFACE, "StateChanged", this, SLOT( onStateChange( uint ) ) ); } lastfm::LNetworkConnectionMonitor::~LNetworkConnectionMonitor() { delete m_nmInterface; } void lastfm::LNetworkConnectionMonitor::onStateChange( uint newState ) { qDebug() << "Networkmanager state change!"; if ( newState == NM_STATE_DISCONNECTED || newState == NM_STATE_ASLEEP ) { setConnected( false ); } else if ( newState == NM_STATE_CONNECTED_GLOBAL ) { setConnected( true ); } } #include "moc_LNetworkConnectionMonitor.cpp" liblastfm-1.1.0/src/mac/000077500000000000000000000000001246517024400150225ustar00rootroot00000000000000liblastfm-1.1.0/src/mac/MNetworkConnectionMonitor.h000066400000000000000000000027401246517024400223340ustar00rootroot00000000000000/* 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" #ifdef Q_OS_MAC #include //TODO remove #include #endif class __SCNetworkReachability; namespace lastfm { class MNetworkConnectionMonitor : public NetworkConnectionMonitor { Q_OBJECT public: MNetworkConnectionMonitor( QObject* parent = 0 ); ~MNetworkConnectionMonitor(); private slots: private: #ifdef Q_OS_MAC static void callback( SCNetworkReachabilityRef target, SCNetworkConnectionFlags flags, void *info ); #endif }; } #endif // MNETWORK_CONNECTION_MONITOR_H liblastfm-1.1.0/src/mac/MNetworkConnectionMonitor_mac.cpp000066400000000000000000000042601246517024400235060ustar00rootroot00000000000000/* 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 "moc_MNetworkConnectionMonitor.cpp" #include "../ws.h" #include #include lastfm::MNetworkConnectionMonitor* context = 0; lastfm::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 ); } lastfm::MNetworkConnectionMonitor::~MNetworkConnectionMonitor() { } void lastfm::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); // basically, avoids telling everyone that we're up already on startup if (up == b) return; up = b; context->setConnected(b); } liblastfm-1.1.0/src/mac/ProxyDict.h000066400000000000000000000050061246517024400171210ustar00rootroot00000000000000/* 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 ); } liblastfm-1.1.0/src/mbid_mp3.c000066400000000000000000000127171246517024400161300ustar00rootroot00000000000000/* * 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 #include // ----------------------------------------------------------------------------- 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)[4]) { 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 // ----------------------------------------------------------------------------- liblastfm-1.1.0/src/misc.cpp000066400000000000000000000146001246517024400157220ustar00rootroot00000000000000/* 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 #include #include #ifdef WIN32 #include #endif #ifdef Q_OS_MAC #include 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 QDir ensurePathExists( QDir dir ) { if ( !dir.exists() ) dir.mkpath( QString( "." ) ); return dir; } 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_OS_MAC) return ensurePathExists( QDir::home().filePath( "Library/Application Support" ) ); #elif defined(Q_OS_LINUX) || defined(Q_OS_UNIX) return ensurePathExists( QDir::home().filePath( ".local/share" ) ); #else return QDir::home(); #endif } QDir lastfm::dir::runtimeData() { return ensurePathExists( dataDotDot().filePath( "Last.fm" ) ); } QDir lastfm::dir::logs() { #ifdef Q_OS_MAC return ensurePathExists( QDir::home().filePath( "Library/Logs/Last.fm" ) ); #else return runtimeData(); #endif } QDir lastfm::dir::cache() { #ifdef Q_OS_MAC return ensurePathExists( QDir::home().filePath( "Library/Caches/Last.fm" ) ); #else return ensurePathExists( 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_OS_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 const char* lastfm::platform() { #ifdef Q_OS_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_WINDOWS7: return "Windows 7"; 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_OS_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"; case QSysInfo::MV_10_6: return "Mac OS X 10.6"; case QSysInfo::MV_10_7: return "Mac OS X 10.7"; case QSysInfo::MV_10_8: return "Mac OS X 10.8"; default: return "Unknown"; } #elif defined(Q_OS_LINUX) || defined(Q_OS_UNIX) return "UNIX X11"; #else return "Unknown"; #endif } QString lastfm:: md5( const QByteArray& src ) { QByteArray const digest = QCryptographicHash::hash( src, QCryptographicHash::Md5 ); return QString::fromLatin1( digest.toHex() ).rightJustified( 32, '0' ).toLower(); } #ifdef Q_OS_MAC QString lastfm::CFStringToQString( CFStringRef s ) { return QString::fromUtf8( CFStringToUtf8( s ) ); } #endif liblastfm-1.1.0/src/misc.h000066400000000000000000000031031246517024400153630ustar00rootroot00000000000000/* 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 "global.h" #include #ifdef Q_OS_MAC typedef const struct __CFString* CFStringRef; #endif namespace lastfm { namespace dir { #ifdef Q_OS_WIN LASTFM_DLLEXPORT QDir programFiles(); #endif #ifdef Q_OS_MAC LASTFM_DLLEXPORT QDir bundle(); #endif LASTFM_DLLEXPORT QDir runtimeData(); LASTFM_DLLEXPORT QDir cache(); LASTFM_DLLEXPORT QDir logs(); } #ifdef Q_OS_MAC LASTFM_DLLEXPORT QByteArray CFStringToUtf8( CFStringRef ); LASTFM_DLLEXPORT CFStringRef QStringToCFString( const QString& ); LASTFM_DLLEXPORT QString CFStringToQString( CFStringRef s ); #endif LASTFM_DLLEXPORT const char* platform(); LASTFM_DLLEXPORT QString md5( const QByteArray& src ); } #endif //LASTFM_MISC_H liblastfm-1.1.0/src/win/000077500000000000000000000000001246517024400150575ustar00rootroot00000000000000liblastfm-1.1.0/src/win/ComSetup.h000066400000000000000000000034431246517024400167730ustar00rootroot00000000000000/* 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 // This means we are targetting Windows XP onwards // bring in CoInitializeSecurity from objbase.h #define _WIN32_WINNT 0x0501 #endif #include #ifndef __MINGW32__ #include #include #endif /** @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; }; liblastfm-1.1.0/src/win/IeSettings.h000066400000000000000000000025551246517024400173150ustar00rootroot00000000000000/* 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); } }; liblastfm-1.1.0/src/win/NdisEvents.cpp000066400000000000000000000056561246517024400176610ustar00rootroot00000000000000/* 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; } liblastfm-1.1.0/src/win/NdisEvents.h000066400000000000000000000023321246517024400173120ustar00rootroot00000000000000/* 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 #ifndef __MINGW32__ #include #include #endif 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 liblastfm-1.1.0/src/win/WNetworkConnectionMonitor.h000066400000000000000000000023701246517024400224020ustar00rootroot00000000000000/* 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" 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 liblastfm-1.1.0/src/win/WNetworkConnectionMonitor_win.cpp000066400000000000000000000036771246517024400236250ustar00rootroot00000000000000/* 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" #include "moc_WNetworkConnectionMonitor.cpp" // 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; } } liblastfm-1.1.0/src/win/WmiSink.cpp000066400000000000000000000155531246517024400171550ustar00rootroot00000000000000/* 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; } liblastfm-1.1.0/src/win/WmiSink.h000066400000000000000000000025651246517024400166210ustar00rootroot00000000000000/* 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; }; #endifliblastfm-1.1.0/src/ws.cpp000066400000000000000000000167161246517024400154320ustar00rootroot00000000000000/* 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 "misc.h" #include "NetworkAccessManager.h" #include "Url.h" #include #include #include #include #include #include #include #include #include static lastfm::ws::Scheme theScheme = lastfm::ws::Http; static QMap< QThread*, QNetworkAccessManager* > threadNamHash; static QSet< QThread* > ourNamSet; static QMutex namAccessMutex; class lastfm::ws::ParseErrorPrivate { public: lastfm::ws::Error e; QString m_message; }; lastfm::ws::ParseError::ParseError( lastfm::ws::Error e, QString message ) :d( new ParseErrorPrivate ) { d->e = e; d->m_message = message; } lastfm::ws::ParseError::ParseError( const ParseError& that ) : d( new ParseErrorPrivate( *that.d ) ) { } lastfm::ws::ParseError::~ParseError() throw() { delete d; } lastfm::ws::Error lastfm::ws::ParseError::enumValue() const { return d->e; } QString lastfm::ws::ParseError::message() const { return d->m_message; } lastfm::ws::ParseError& lastfm::ws::ParseError::operator=( const ParseError& that ) { d->e = that.d->e; d->m_message = that.d->m_message; return *this; } lastfm::ws::Scheme lastfm::ws::scheme() { return QSslSocket::supportsSsl() ? theScheme : Http; } void lastfm::ws::setScheme( lastfm::ws::Scheme scheme ) { theScheme = scheme; } 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 baseUrl() { QUrl url; url.setScheme( lastfm::ws::scheme() == lastfm::ws::Https ? "https" : "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(); } void lastfm::ws::sign( QMap& params, bool sk ) { 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() ); } QUrl lastfm::ws::url( QMap params, bool sk ) { lastfm::ws::sign( params, sk ); lastfm::Url url = lastfm::Url( ::baseUrl() ); // Qt setQueryItems doesn't encode a bunch of stuff, so we do it manually QMapIterator i( params ); while (i.hasNext()) { i.next(); QString const key = i.key(); QString const value = i.value(); url.addQueryItem( key, value ); } return url.url(); } QNetworkReply* lastfm::ws::get( QMap params ) { return nam()->get( QNetworkRequest( url( params ) ) ); } 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() ) + '&'; } QNetworkRequest req( baseUrl() ); req.setHeader( QNetworkRequest::ContentTypeHeader, "application/x-www-form-urlencoded" ); return nam()->post( req, query ); } QNetworkAccessManager* lastfm::nam() { QMutexLocker l( &namAccessMutex ); QThread* thread = QThread::currentThread(); if ( !threadNamHash.contains( thread ) ) { NetworkAccessManager* newNam = new NetworkAccessManager(); threadNamHash[thread] = newNam; ourNamSet.insert( thread ); return newNam; } return threadNamHash[thread]; } void lastfm::setNetworkAccessManager( QNetworkAccessManager* nam ) { if ( !nam ) return; QMutexLocker l( &namAccessMutex ); QThread* thread = QThread::currentThread(); QNetworkAccessManager* oldNam = 0; if ( threadNamHash.contains( thread ) && ourNamSet.contains( thread ) ) oldNam = threadNamHash[thread]; if ( oldNam == nam ) { // If we're being passed back our own NAM, assume they want to // ensure that we don't delete it out from under them ourNamSet.remove( thread ); return; } threadNamHash[thread] = nam; ourNamSet.remove( thread ); if ( oldNam ) delete oldNam; } /** 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 ); QDebug operator<<( QDebug d, QNetworkReply::NetworkError e ) { return d << lastfm::qMetaEnumString( e, "NetworkError" ); } liblastfm-1.1.0/src/ws.h000066400000000000000000000130311246517024400150620ustar00rootroot00000000000000/* 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 "global.h" #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 User 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, TokenNotAuthorised, 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 }; enum Scheme { Http, Https }; /** Set the scheme for all web service calls. Note that it will only use * Https if SSL is supported on the user's machine otherwise it will * default to Http. */ LASTFM_DLLEXPORT void setScheme( Scheme scheme ); LASTFM_DLLEXPORT Scheme scheme(); LASTFM_DLLEXPORT QString host(); /** the map needs a method entry, as per http://last.fm/api */ LASTFM_DLLEXPORT QUrl url( QMap, bool sessionKey = true); 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 ); LASTFM_DLLEXPORT void sign( QMap&, bool sessionKey = true ); class LASTFM_DLLEXPORT ParseError { public: explicit ParseError( Error e, QString message ); ParseError( const ParseError& that ); ~ParseError() throw(); Error enumValue() const; QString message() const; ParseError& operator=( const ParseError& that ); private: class ParseErrorPrivate * const d; }; /** returns the expiry date of this HTTP response */ LASTFM_DLLEXPORT QDateTime expires( QNetworkReply* ); } } LASTFM_DLLEXPORT QDebug operator<<( QDebug d, QNetworkReply::NetworkError e ); #define LASTFM_WS_HOSTNAME "ws.audioscrobbler.com" #endif liblastfm-1.1.0/tests/000077500000000000000000000000001246517024400146355ustar00rootroot00000000000000liblastfm-1.1.0/tests/CMakeLists.txt000066400000000000000000000002131246517024400173710ustar00rootroot00000000000000include(lastfm_add_test.cmake) include_directories(${CMAKE_CURRENT_BINARY_DIR}/../src) lastfm_add_test(UrlBuilder) lastfm_add_test(Track) liblastfm-1.1.0/tests/TestTrack.h000066400000000000000000000015061246517024400167140ustar00rootroot00000000000000/* 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. */ #ifndef LASTFM_TESTTRACK_H #define LASTFM_TESTTRACK_H #include "Track.h" #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 } }; #endif liblastfm-1.1.0/tests/TestUrlBuilder.h000066400000000000000000000050451246517024400177230ustar00rootroot00000000000000/* 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. */ #ifndef LASTFM_TESTURLBUILDER_H #define LASTFM_TESTURLBUILDER_H #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.toLatin1() ); } 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 ); } }; #endif liblastfm-1.1.0/tests/lastfm_add_test.cmake000066400000000000000000000012611246517024400207740ustar00rootroot00000000000000macro(lastfm_add_test test_class) include_directories(${QT_INCLUDES} "${PROJECT_SOURCE_DIR}/src" ${CMAKE_CURRENT_BINARY_DIR}) set(LASTFM_TEST_CLASS ${test_class}) configure_file(main.cpp.in Test${LASTFM_TEST_CLASS}.cpp) configure_file(Test${LASTFM_TEST_CLASS}.h Test${LASTFM_TEST_CLASS}.h) add_executable(${LASTFM_TEST_CLASS}Test Test${LASTFM_TEST_CLASS}.cpp) qt5_use_modules(${LASTFM_TEST_CLASS}Test Core Test Xml Network) target_link_libraries(${LASTFM_TEST_CLASS}Test ${LASTFM_LIB_TARGET_NAME} ${QT_QTTEST_LIBRARY} ${QT_QTCORE_LIBRARY} ) add_test(NAME ${LASTFM_TEST_CLASS}Test COMMAND ${LASTFM_TEST_CLASS}Test) endmacro() liblastfm-1.1.0/tests/main.cpp.in000066400000000000000000000010401246517024400166650ustar00rootroot00000000000000/* 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 "Test@LASTFM_TEST_CLASS@.h" #include "moc_Test@LASTFM_TEST_CLASS@.cpp" 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( Test@LASTFM_TEST_CLASS@ ); return 0; }